From 0616d6b5f8130db0bbd751ffa467d6f04ffbfe07 Mon Sep 17 00:00:00 2001 From: Chris Davis Date: Thu, 11 Feb 2021 22:03:19 -0800 Subject: [PATCH] Add progress dialog for long startup enumerations --- SmartRenameLib/Helpers.cpp | 85 ---------- SmartRenameLib/Helpers.h | 1 - SmartRenameLib/SmartRenameEnum.cpp | 226 +++++++++++++++++++++++++ SmartRenameLib/SmartRenameEnum.h | 51 ++++++ SmartRenameLib/SmartRenameInterfaces.h | 17 ++ SmartRenameLib/SmartRenameLib.vcxproj | 2 + SmartRenameUI/SmartRenameUI.cpp | 100 ++++++++--- SmartRenameUI/SmartRenameUI.h | 12 +- 8 files changed, 383 insertions(+), 111 deletions(-) create mode 100644 SmartRenameLib/SmartRenameEnum.cpp create mode 100644 SmartRenameLib/SmartRenameEnum.h diff --git a/SmartRenameLib/Helpers.cpp b/SmartRenameLib/Helpers.cpp index 22371f0..18103ee 100644 --- a/SmartRenameLib/Helpers.cpp +++ b/SmartRenameLib/Helpers.cpp @@ -90,91 +90,6 @@ HBITMAP CreateBitmapFromIcon(_In_ HICON hIcon, _In_opt_ UINT width, _In_opt_ UIN return hBitmapResult; } -HRESULT _ParseEnumItems(_In_ IEnumShellItems* pesi, _In_ ISmartRenameManager* psrm, _In_opt_ IProgressDialog* ppd, _In_ int depth = 0) -{ - HRESULT hr = E_INVALIDARG; - - // We shouldn't get this deep since we only enum the contents of - // regular folders but adding just in case - if ((pesi) && (depth < (MAX_PATH / 2))) - { - hr = S_OK; - - ULONG celtFetched; - CComPtr spsi; - while ((S_OK == pesi->Next(1, &spsi, &celtFetched)) && (SUCCEEDED(hr))) - { - if (ppd && ppd->HasUserCancelled()) - { - // Cancelled by user - hr = E_ABORT; - break; - } - - CComPtr spsrif; - hr = psrm->get_renameItemFactory(&spsrif); - if (SUCCEEDED(hr)) - { - CComPtr spNewItem; - hr = spsrif->Create(spsi, &spNewItem); - if (SUCCEEDED(hr)) - { - spNewItem->put_depth(depth); - hr = psrm->AddItem(spNewItem); - if (SUCCEEDED(hr) && ppd) - { - // Update the progress dialog - PWSTR pathDisplay = nullptr; - if (SUCCEEDED(spNewItem->get_path(&pathDisplay))) - { - ppd->SetLine(2, pathDisplay, TRUE, nullptr); - CoTaskMemFree(pathDisplay); - } - } - } - - if (SUCCEEDED(hr)) - { - bool isFolder = false; - if (SUCCEEDED(spNewItem->get_isFolder(&isFolder)) && isFolder) - { - // Bind to the IShellItem for the IEnumShellItems interface - CComPtr spesiNext; - hr = spsi->BindToHandler(nullptr, BHID_EnumItems, IID_PPV_ARGS(&spesiNext)); - if (SUCCEEDED(hr)) - { - // Parse the folder contents recursively - hr = _ParseEnumItems(spesiNext, psrm, ppd, depth + 1); - } - } - } - } - - spsi = nullptr; - } - } - - return hr; -} - -// Iterate through the data object and add paths to the rotation manager -HRESULT EnumerateDataObject(_In_ IDataObject* pdo, _In_ ISmartRenameManager* psrm, _In_opt_ IProgressDialog* ppd) -{ - CComPtr spsia; - HRESULT hr = SHCreateShellItemArrayFromDataObject(pdo, IID_PPV_ARGS(&spsia)); - if (SUCCEEDED(hr)) - { - CComPtr spesi; - hr = spsia->EnumItems(&spesi); - if (SUCCEEDED(hr)) - { - hr = _ParseEnumItems(spesi, psrm, ppd); - } - } - - return hr; -} - HWND CreateMsgWindow(_In_ HINSTANCE hInst, _In_ WNDPROC pfnWndProc, _In_ void* p) { WNDCLASS wc = { 0 }; diff --git a/SmartRenameLib/Helpers.h b/SmartRenameLib/Helpers.h index e7458a9..338e725 100644 --- a/SmartRenameLib/Helpers.h +++ b/SmartRenameLib/Helpers.h @@ -1,6 +1,5 @@ #pragma once -HRESULT EnumerateDataObject(_In_ IDataObject* pdo, _In_ ISmartRenameManager* psrm, _In_opt_ IProgressDialog* ppd); HRESULT GetIconIndexFromPath(_In_ PCWSTR path, _Out_ int* index); HBITMAP CreateBitmapFromIcon(_In_ HICON hIcon, _In_opt_ UINT width = 0, _In_opt_ UINT height = 0); HWND CreateMsgWindow(_In_ HINSTANCE hInst, _In_ WNDPROC pfnWndProc, _In_ void* p); diff --git a/SmartRenameLib/SmartRenameEnum.cpp b/SmartRenameLib/SmartRenameEnum.cpp new file mode 100644 index 0000000..aeb6c6a --- /dev/null +++ b/SmartRenameLib/SmartRenameEnum.cpp @@ -0,0 +1,226 @@ +#include "stdafx.h" +#include "SmartRenameEnum.h" +#include + +IFACEMETHODIMP_(ULONG) CSmartRenameEnum::AddRef() +{ + return InterlockedIncrement(&m_refCount); +} + +IFACEMETHODIMP_(ULONG) CSmartRenameEnum::Release() +{ + long refCount = InterlockedDecrement(&m_refCount); + + if (refCount == 0) + { + delete this; + } + return refCount; +} + +IFACEMETHODIMP CSmartRenameEnum::QueryInterface(_In_ REFIID riid, _Outptr_ void** ppv) +{ + static const QITAB qit[] = { + QITABENT(CSmartRenameEnum, ISmartRenameEnum), + { 0 } + }; + return QISearch(this, qit, riid, ppv); +} + +IFACEMETHODIMP CSmartRenameEnum::Advise(_In_ ISmartRenameEnumEvents* events, _Out_ DWORD* cookie) +{ + CSRWExclusiveAutoLock lock(&m_lockEvents); + m_cookie++; + RENAME_ENUM_EVENT srme; + srme.cookie = m_cookie; + srme.pEvents = events; + events->AddRef(); + m_renameEnumEvents.push_back(srme); + + *cookie = m_cookie; + + return S_OK; +} + +IFACEMETHODIMP CSmartRenameEnum::UnAdvise(_In_ DWORD cookie) +{ + HRESULT hr = E_FAIL; + CSRWExclusiveAutoLock lock(&m_lockEvents); + + for (std::vector::iterator it = m_renameEnumEvents.begin(); it != m_renameEnumEvents.end(); ++it) + { + if (it->cookie == cookie) + { + hr = S_OK; + it->cookie = 0; + if (it->pEvents) + { + it->pEvents->Release(); + it->pEvents = nullptr; + } + break; + } + } + + return hr; +} + + +IFACEMETHODIMP CSmartRenameEnum::Start() +{ + _OnStarted(); + m_canceled = false; + CComPtr spsia; + HRESULT hr = SHCreateShellItemArrayFromDataObject(m_spdo, IID_PPV_ARGS(&spsia)); + if (SUCCEEDED(hr)) + { + CComPtr spesi; + hr = spsia->EnumItems(&spesi); + if (SUCCEEDED(hr)) + { + hr = _ParseEnumItems(spesi); + } + } + + _OnCompleted(); + return hr; +} + +IFACEMETHODIMP CSmartRenameEnum::Cancel() +{ + m_canceled = true; + return S_OK; +} + +HRESULT CSmartRenameEnum::s_CreateInstance(_In_ IDataObject* pdo, _In_ _In_ ISmartRenameManager* psrm, _In_ REFIID iid, _Outptr_ void** resultInterface) +{ + *resultInterface = nullptr; + + CSmartRenameEnum* newRenameEnum = new CSmartRenameEnum(); + HRESULT hr = newRenameEnum ? S_OK : E_OUTOFMEMORY; + if (SUCCEEDED(hr)) + { + hr = newRenameEnum->_Init(pdo, psrm); + if (SUCCEEDED(hr)) + { + hr = newRenameEnum->QueryInterface(iid, resultInterface); + } + + newRenameEnum->Release(); + } + return hr; +} + +CSmartRenameEnum::CSmartRenameEnum() : + m_refCount(1) +{ +} + +CSmartRenameEnum::~CSmartRenameEnum() +{ +} + + +void CSmartRenameEnum::_OnStarted() +{ + CSRWSharedAutoLock lock(&m_lockEvents); + + for (std::vector::iterator it = m_renameEnumEvents.begin(); it != m_renameEnumEvents.end(); ++it) + { + if (it->pEvents) + { + it->pEvents->OnStarted(); + } + } +} + +void CSmartRenameEnum::_OnCompleted() +{ + CSRWSharedAutoLock lock(&m_lockEvents); + + for (std::vector::iterator it = m_renameEnumEvents.begin(); it != m_renameEnumEvents.end(); ++it) + { + if (it->pEvents) + { + it->pEvents->OnCompleted(m_canceled); + } + } +} + +void CSmartRenameEnum::_OnFoundItem(_In_ ISmartRenameItem* item) +{ + CSRWSharedAutoLock lock(&m_lockEvents); + + for (std::vector::iterator it = m_renameEnumEvents.begin(); it != m_renameEnumEvents.end(); ++it) + { + if (it->pEvents) + { + it->pEvents->OnFoundItem(item); + } + } +} + +HRESULT CSmartRenameEnum::_Init(_In_ IDataObject* pdo, _In_ ISmartRenameManager* psrm) +{ + m_spdo = pdo; + m_spsrm = psrm; + return S_OK; +} + +HRESULT CSmartRenameEnum::_ParseEnumItems(_In_ IEnumShellItems* pesi, _In_ int depth) +{ + HRESULT hr = E_INVALIDARG; + + // We shouldn't get this deep since we only enum the contents of + // regular folders but adding just in case + if ((pesi) && (depth < (MAX_PATH / 2))) + { + hr = S_OK; + + ULONG celtFetched; + CComPtr spsi; + while ((S_OK == pesi->Next(1, &spsi, &celtFetched)) && (SUCCEEDED(hr))) + { + if (m_canceled) + { + return E_ABORT; + } + + CComPtr spsrif; + hr = m_spsrm->get_renameItemFactory(&spsrif); + if (SUCCEEDED(hr)) + { + CComPtr spNewItem; + // Failure may be valid if we come across a shell item that does + // not support a file system path. In that case we simply ignore + // the item. + if (SUCCEEDED(spsrif->Create(spsi, &spNewItem))) + { + spNewItem->put_depth(depth); + _OnFoundItem(spNewItem); + hr = m_spsrm->AddItem(spNewItem); + if (SUCCEEDED(hr)) + { + bool isFolder = false; + if (SUCCEEDED(spNewItem->get_isFolder(&isFolder)) && isFolder) + { + // Bind to the IShellItem for the IEnumShellItems interface + CComPtr spesiNext; + hr = spsi->BindToHandler(nullptr, BHID_EnumItems, IID_PPV_ARGS(&spesiNext)); + if (SUCCEEDED(hr)) + { + // Parse the folder contents recursively + hr = _ParseEnumItems(spesiNext, depth + 1); + } + } + } + } + } + + spsi = nullptr; + } + } + + return hr; +} + diff --git a/SmartRenameLib/SmartRenameEnum.h b/SmartRenameLib/SmartRenameEnum.h new file mode 100644 index 0000000..2ea4f66 --- /dev/null +++ b/SmartRenameLib/SmartRenameEnum.h @@ -0,0 +1,51 @@ +#pragma once +#include "stdafx.h" +#include "SmartRenameInterfaces.h" +#include +#include "srwlock.h" + +class CSmartRenameEnum : + public ISmartRenameEnum +{ +public: + // IUnknown + IFACEMETHODIMP QueryInterface(_In_ REFIID iid, _Outptr_ void** resultInterface); + IFACEMETHODIMP_(ULONG) AddRef(); + IFACEMETHODIMP_(ULONG) Release(); + + // ISmartRenameEnum + IFACEMETHODIMP Advise(_In_ ISmartRenameEnumEvents* events, _Out_ DWORD* cookie); + IFACEMETHODIMP UnAdvise(_In_ DWORD cookie); + IFACEMETHODIMP Start(); + IFACEMETHODIMP Cancel(); + +public: + static HRESULT s_CreateInstance(_In_ IDataObject* pdo, _In_ _In_ ISmartRenameManager* psrm, _In_ REFIID iid, _Outptr_ void** resultInterface); + +protected: + CSmartRenameEnum(); + virtual ~CSmartRenameEnum(); + + HRESULT _Init(_In_ IDataObject* pdo, _In_ ISmartRenameManager* psrm); + HRESULT _ParseEnumItems(_In_ IEnumShellItems* pesi, _In_ int depth = 0); + + void _OnStarted(); + void _OnCompleted(); + void _OnFoundItem(_In_ ISmartRenameItem* item); + + struct RENAME_ENUM_EVENT + { + ISmartRenameEnumEvents* pEvents; + DWORD cookie; + }; + + DWORD m_cookie = 0; + + CSRWLock m_lockEvents; + _Guarded_by_(m_lockEvents) std::vector m_renameEnumEvents; + + CComPtr m_spsrm; + CComPtr m_spdo; + bool m_canceled = false; + long m_refCount = 0; +}; \ No newline at end of file diff --git a/SmartRenameLib/SmartRenameInterfaces.h b/SmartRenameLib/SmartRenameInterfaces.h index ba666d4..a97fcf6 100644 --- a/SmartRenameLib/SmartRenameInterfaces.h +++ b/SmartRenameLib/SmartRenameInterfaces.h @@ -113,3 +113,20 @@ interface __declspec(uuid("04AAFABE-B76E-4E13-993A-B5941F52B139")) ISmartRenameM IFACEMETHOD(AddMRUString)(_In_ PCWSTR entry) = 0; }; +interface __declspec(uuid("7ABDE437-7AAF-4545-AA52-EC47A6F428C1")) ISmartRenameEnumEvents : public IUnknown +{ +public: + IFACEMETHOD(OnStarted)() = 0; + IFACEMETHOD(OnCompleted)(_In_ bool canceled) = 0; + IFACEMETHOD(OnFoundItem)(_In_ ISmartRenameItem* item) = 0; +}; + +interface __declspec(uuid("2EFBAB41-A841-47B5-898B-B1CFBF151855")) ISmartRenameEnum : public IUnknown +{ +public: + IFACEMETHOD(Advise)(_In_ ISmartRenameEnumEvents* events, _Out_ DWORD* cookie) = 0; + IFACEMETHOD(UnAdvise)(_In_ DWORD cookie) = 0; + IFACEMETHOD(Start)() = 0; + IFACEMETHOD(Cancel)() = 0; +}; + diff --git a/SmartRenameLib/SmartRenameLib.vcxproj b/SmartRenameLib/SmartRenameLib.vcxproj index 2f81517..9efa8a3 100644 --- a/SmartRenameLib/SmartRenameLib.vcxproj +++ b/SmartRenameLib/SmartRenameLib.vcxproj @@ -142,6 +142,7 @@ + @@ -153,6 +154,7 @@ + diff --git a/SmartRenameUI/SmartRenameUI.cpp b/SmartRenameUI/SmartRenameUI.cpp index 76f0853..4e00d96 100644 --- a/SmartRenameUI/SmartRenameUI.cpp +++ b/SmartRenameUI/SmartRenameUI.cpp @@ -6,6 +6,7 @@ #include #include #include +#include extern HINSTANCE g_hInst; @@ -223,6 +224,61 @@ IFACEMETHODIMP CSmartRenameUI::OnRenameCompleted() return S_OK; } +// ISmartRenameEnumEvent +IFACEMETHODIMP CSmartRenameUI::OnStarted() +{ + m_enumStartTick = GetTickCount64(); + return S_OK; +} + +IFACEMETHODIMP CSmartRenameUI::OnCompleted(_In_ bool canceled) +{ + if (m_sppd) + { + m_sppd->StopProgressDialog(); + m_sppd = nullptr; + } + + return S_OK; +} +IFACEMETHODIMP CSmartRenameUI::OnFoundItem(_In_ ISmartRenameItem* item) +{ + // Check if we need to create the progress dialog. We delay m_progressDlgDelayMS before + // showing the progress dialog so the user does not see it briefly on every launch. + if (!m_sppd && (GetTickCount64() - m_enumStartTick > m_progressDlgDelayMS)) + { + if (SUCCEEDED(CoCreateInstance(CLSID_ProgressDialog, NULL, CLSCTX_INPROC, IID_PPV_ARGS(&m_sppd)))) + { + wchar_t buff[100] = { 0 }; + LoadString(g_hInst, IDS_LOADING, buff, ARRAYSIZE(buff)); + m_sppd->SetLine(1, buff, FALSE, NULL); + LoadString(g_hInst, IDS_APP_TITLE, buff, ARRAYSIZE(buff)); + m_sppd->SetTitle(buff); + m_sppd->StartProgressDialog(m_hwnd, NULL, PROGDLG_MARQUEEPROGRESS, NULL); + } + } + + if (m_sppd) + { + if (m_sppd->HasUserCancelled() && m_spsre) + { + // Cancel the enumeration + m_spsre->Cancel(); + } + else + { + // Update the progress dialog + PWSTR pathDisplay = nullptr; + if (SUCCEEDED(item->get_path(&pathDisplay))) + { + m_sppd->SetLine(2, pathDisplay, TRUE, nullptr); + CoTaskMemFree(pathDisplay); + } + } + } + return S_OK; +} + // IDropTarget IFACEMETHODIMP CSmartRenameUI::DragEnter(_In_ IDataObject* pdtobj, DWORD /* grfKeyState */, POINTL pt, _Inout_ DWORD* pdwEffect) { @@ -365,36 +421,32 @@ HRESULT CSmartRenameUI::_EnumerateItems(_In_ IDataObject* pdtobj) // Enumerate the data object and popuplate the manager if (m_spsrm) { - // Add a progress dialog in case enumeration of items takes a long time - // This also allows the user to cancel enumeration. - CComPtr sppd; - hr = CoCreateInstance(CLSID_ProgressDialog, NULL, CLSCTX_INPROC, IID_PPV_ARGS(&sppd)); + m_disableCountUpdate = true; + + // Ensure we re-create the enumerator + m_spsre = nullptr; + hr = CSmartRenameEnum::s_CreateInstance(pdtobj, m_spsrm, IID_PPV_ARGS(&m_spsre)); if (SUCCEEDED(hr)) { - wchar_t buff[100] = { 0 }; - LoadString(g_hInst, IDS_LOADING, buff, ARRAYSIZE(buff)); - sppd->SetLine(1, buff, FALSE, NULL); - LoadString(g_hInst, IDS_APP_TITLE, buff, ARRAYSIZE(buff)); - sppd->SetTitle(buff); - sppd->StartProgressDialog(m_hwnd, NULL, PROGDLG_MARQUEEPROGRESS, NULL); - - m_disableCountUpdate = true; - - hr = EnumerateDataObject(pdtobj, m_spsrm, sppd); - - m_disableCountUpdate = false; - - sppd->StopProgressDialog(); - + DWORD dwCookie = 0; + hr = m_spsre->Advise(this, &dwCookie); if (SUCCEEDED(hr)) { - UINT itemCount = 0; - m_spsrm->GetItemCount(&itemCount); - m_listview.SetItemCount(itemCount); - - _UpdateCounts(); + hr = m_spsre->Start(); + m_spsre->UnAdvise(dwCookie); } } + + m_disableCountUpdate = false; + + if (SUCCEEDED(hr)) + { + UINT itemCount = 0; + m_spsrm->GetItemCount(&itemCount); + m_listview.SetItemCount(itemCount); + + _UpdateCounts(); + } } return hr; diff --git a/SmartRenameUI/SmartRenameUI.h b/SmartRenameUI/SmartRenameUI.h index 7a9a5db..5fcff3c 100644 --- a/SmartRenameUI/SmartRenameUI.h +++ b/SmartRenameUI/SmartRenameUI.h @@ -31,7 +31,8 @@ class CSmartRenameListView class CSmartRenameUI : public IDropTarget, public ISmartRenameUI, - public ISmartRenameManagerEvents + public ISmartRenameManagerEvents, + public ISmartRenameEnumEvents { public: CSmartRenameUI() : @@ -62,6 +63,11 @@ class CSmartRenameUI : IFACEMETHODIMP OnRenameStarted(); IFACEMETHODIMP OnRenameCompleted(); + // ISmartRenameEnumEvents + IFACEMETHODIMP OnStarted(); + IFACEMETHODIMP OnCompleted(_In_ bool canceled); + IFACEMETHODIMP OnFoundItem(_In_ ISmartRenameItem* item); + // IDropTarget IFACEMETHODIMP DragEnter(_In_ IDataObject* pdtobj, DWORD grfKeyState, POINTL pt, _Inout_ DWORD* pdwEffect); IFACEMETHODIMP DragOver(DWORD grfKeyState, POINTL pt, _Inout_ DWORD* pdwEffect); @@ -135,12 +141,16 @@ class CSmartRenameUI : int m_initialHeight = 0; int m_lastWidth = 0; int m_lastHeight = 0; + ULONGLONG m_enumStartTick = 0; + const ULONGLONG m_progressDlgDelayMS = 3000; CComPtr m_spsrm; + CComPtr m_spsre; CComPtr m_spdo; CComPtr m_spdth; CComPtr m_spSearchAC; CComPtr m_spSearchACL; CComPtr m_spReplaceAC; CComPtr m_spReplaceACL; + CComPtr m_sppd; CSmartRenameListView m_listview; }; \ No newline at end of file