Files
PowerToys/src/modules/powerrename/lib/PowerRenameManager.cpp
moooyo 70e1177a6a [PowerRename] Support using photo metadata to replace in the PowerRename (#41728)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
1. Introduce WIC for power rename and add new class WICMetadataExtractor
to use WIC to extract metadata.
2. Add some patterns for metadata extract.
3. Support XMP and EXIF metadata extract.
4. Add test data for xmp and exif extractor
5. Add attribution for the test data uploader.

UI:
<img width="2052" height="1415" alt="image"
src="https://github.com/user-attachments/assets/9051b12e-4e66-4fdc-a4d4-3bada661c235"
/>
<img width="284" height="170" alt="image"
src="https://github.com/user-attachments/assets/2fd67193-77a7-48f0-a5ac-08a69fe64e55"
/>
<img width="715" height="1160" alt="image"
src="https://github.com/user-attachments/assets/5fa68a8c-d129-44dd-b747-099dfbcded12"
/>

demo:


https://github.com/user-attachments/assets/e90bc206-62e5-4101-ada2-3187ee7e2039



<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] Closes: #5612
- [x] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [x] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

---------

Co-authored-by: Yu Leng <yuleng@microsoft.com>
2025-11-04 09:27:16 +08:00

1189 lines
33 KiB
C++

#include "pch.h"
#include "PowerRenameManager.h"
#include "PowerRenameRegEx.h" // Default RegEx handler
#include <algorithm>
#include <shlobj.h>
#include <cstring>
#include "helpers.h"
#include "trace.h"
#include <Renaming.h>
namespace fs = std::filesystem;
extern HINSTANCE g_hostHInst;
// The default FOF flags to use in the rename operations
#define FOF_DEFAULTFLAGS (FOF_ALLOWUNDO | FOFX_ADDUNDORECORD | FOFX_SHOWELEVATIONPROMPT | FOF_RENAMEONCOLLISION)
IFACEMETHODIMP_(ULONG)
CPowerRenameManager::AddRef()
{
return InterlockedIncrement(&m_refCount);
}
IFACEMETHODIMP_(ULONG)
CPowerRenameManager::Release()
{
long refCount = InterlockedDecrement(&m_refCount);
if (refCount == 0)
{
delete this;
}
return refCount;
}
IFACEMETHODIMP CPowerRenameManager::QueryInterface(_In_ REFIID riid, _Outptr_ void** ppv)
{
static const QITAB qit[] = {
QITABENT(CPowerRenameManager, IPowerRenameManager),
QITABENT(CPowerRenameManager, IPowerRenameRegExEvents),
{ 0 }
};
return QISearch(this, qit, riid, ppv);
}
IFACEMETHODIMP CPowerRenameManager::Advise(_In_ IPowerRenameManagerEvents* renameOpEvents, _Out_ DWORD* cookie)
{
CSRWExclusiveAutoLock lock(&m_lockEvents);
m_cookie++;
RENAME_MGR_EVENT srme;
srme.cookie = m_cookie;
srme.pEvents = renameOpEvents;
renameOpEvents->AddRef();
m_powerRenameManagerEvents.push_back(srme);
*cookie = m_cookie;
return S_OK;
}
IFACEMETHODIMP CPowerRenameManager::UnAdvise(_In_ DWORD cookie)
{
HRESULT hr = E_FAIL;
CSRWExclusiveAutoLock lock(&m_lockEvents);
for (std::vector<RENAME_MGR_EVENT>::iterator it = m_powerRenameManagerEvents.begin(); it != m_powerRenameManagerEvents.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 CPowerRenameManager::Start()
{
return E_NOTIMPL;
}
IFACEMETHODIMP CPowerRenameManager::Stop()
{
return E_NOTIMPL;
}
IFACEMETHODIMP CPowerRenameManager::Rename(_In_ HWND hwndParent, bool closeWindow)
{
m_hwndParent = hwndParent;
m_closeUIWindowAfterRenaming = closeWindow;
return _PerformFileOperation();
}
IFACEMETHODIMP CPowerRenameManager::UpdateChildrenPath(_In_ int parentId, _In_ size_t oldParentPathSize)
{
auto parentIt = m_renameItems.find(parentId);
if (parentIt != m_renameItems.end())
{
UINT depth = 0;
winrt::check_hresult(parentIt->second->GetDepth(&depth));
PWSTR renamedPath = nullptr;
winrt::check_hresult(parentIt->second->GetPath(&renamedPath));
std::wstring renamedPathStr{ renamedPath };
for (auto it = ++parentIt; it != m_renameItems.end(); ++it)
{
UINT nextDepth = 0;
winrt::check_hresult(it->second->GetDepth(&nextDepth));
if (nextDepth > depth)
{
// This is child, update path
PWSTR path = nullptr;
winrt::check_hresult(it->second->GetPath(&path));
std::wstring pathStr{ path };
std::wstring newPath = pathStr.replace(0, oldParentPathSize, renamedPath);
it->second->PutPath(newPath.c_str());
}
else
{
break;
}
}
}
return S_OK;
}
IFACEMETHODIMP CPowerRenameManager::GetCloseUIWindowAfterRenaming(_Out_ bool* closeUIWindowAfterRenaming)
{
*closeUIWindowAfterRenaming = m_closeUIWindowAfterRenaming;
return S_OK;
}
IFACEMETHODIMP CPowerRenameManager::Reset()
{
// Stop all threads and wait
// Reset all rename items
return E_NOTIMPL;
}
IFACEMETHODIMP CPowerRenameManager::Shutdown()
{
_ClearRegEx();
_Cleanup();
return S_OK;
}
IFACEMETHODIMP CPowerRenameManager::AddItem(_In_ IPowerRenameItem* pItem)
{
HRESULT hr = E_FAIL;
// Scope lock
{
CSRWExclusiveAutoLock lock(&m_lockItems);
int id = 0;
pItem->GetId(&id);
// Verify the item isn't already added
if (m_renameItems.find(id) == m_renameItems.end())
{
m_renameItems[id] = pItem;
m_isVisible.push_back(true);
pItem->AddRef();
hr = S_OK;
}
}
return hr;
}
IFACEMETHODIMP CPowerRenameManager::GetItemByIndex(_In_ UINT index, _COM_Outptr_ IPowerRenameItem** ppItem)
{
*ppItem = nullptr;
CSRWSharedAutoLock lock(&m_lockItems);
HRESULT hr = E_FAIL;
if (index < m_renameItems.size())
{
std::map<int, IPowerRenameItem*>::iterator it = m_renameItems.begin();
std::advance(it, index);
*ppItem = it->second;
(*ppItem)->AddRef();
hr = S_OK;
}
return hr;
}
IFACEMETHODIMP CPowerRenameManager::GetVisibleItemByIndex(_In_ UINT index, _COM_Outptr_ IPowerRenameItem** ppItem)
{
*ppItem = nullptr;
CSRWSharedAutoLock lock(&m_lockItems);
UINT count = 0;
HRESULT hr = E_FAIL;
if (m_filter == PowerRenameFilters::None)
{
hr = GetItemByIndex(index, ppItem);
}
else if (SUCCEEDED(GetVisibleItemCount(&count)) && index < count)
{
const UINT realIndex = GetVisibleItemRealIndex(index);
hr = GetItemByIndex(realIndex, ppItem);
}
return hr;
}
uint32_t CPowerRenameManager::GetVisibleItemRealIndex(const uint32_t index) const
{
UINT realIndex = 0, visibleIndex = 0;
for (size_t i = 0; i < m_isVisible.size(); i++)
{
if (m_isVisible[i] && visibleIndex == index)
{
realIndex = static_cast<UINT>(i);
break;
}
if (m_isVisible[i])
{
visibleIndex++;
}
}
return realIndex;
}
IFACEMETHODIMP CPowerRenameManager::GetItemById(_In_ int id, _COM_Outptr_ IPowerRenameItem** ppItem)
{
*ppItem = nullptr;
CSRWSharedAutoLock lock(&m_lockItems);
HRESULT hr = E_FAIL;
std::map<int, IPowerRenameItem*>::iterator it;
it = m_renameItems.find(id);
if (it != m_renameItems.end())
{
*ppItem = m_renameItems[id];
(*ppItem)->AddRef();
hr = S_OK;
}
return hr;
}
IFACEMETHODIMP CPowerRenameManager::GetItemCount(_Out_ UINT* count)
{
CSRWSharedAutoLock lock(&m_lockItems);
*count = static_cast<UINT>(m_renameItems.size());
return S_OK;
}
IFACEMETHODIMP CPowerRenameManager::SetVisible()
{
CSRWSharedAutoLock lock(&m_lockItems);
HRESULT hr = E_FAIL;
UINT lastVisibleDepth = 0;
size_t i = m_isVisible.size() - 1;
PWSTR searchTerm = nullptr;
for (auto rit = m_renameItems.rbegin(); rit != m_renameItems.rend(); ++rit, --i)
{
bool isVisible = false;
if (m_filter == PowerRenameFilters::ShouldRename &&
(FAILED(m_spRegEx->GetSearchTerm(&searchTerm)) || searchTerm && wcslen(searchTerm) == 0))
{
isVisible = true;
}
else
{
rit->second->IsItemVisible(m_filter, m_flags, &isVisible);
}
UINT itemDepth = 0;
rit->second->GetDepth(&itemDepth);
//Make an item visible if it has a least one visible subitem
if (isVisible)
{
lastVisibleDepth = itemDepth;
}
else if (lastVisibleDepth == itemDepth + 1)
{
isVisible = true;
lastVisibleDepth = itemDepth;
}
m_isVisible[i] = isVisible;
hr = S_OK;
}
return hr;
}
IFACEMETHODIMP CPowerRenameManager::GetVisibleItemCount(_Out_ UINT* count)
{
*count = 0;
CSRWSharedAutoLock lock(&m_lockItems);
if (m_filter != PowerRenameFilters::None)
{
SetVisible();
for (size_t i = 0; i < m_isVisible.size(); i++)
{
if (m_isVisible[i])
{
(*count)++;
}
}
}
else
{
GetItemCount(count);
}
return S_OK;
}
IFACEMETHODIMP CPowerRenameManager::GetSelectedItemCount(_Out_ UINT* count)
{
*count = 0;
CSRWSharedAutoLock lock(&m_lockItems);
for (auto it : m_renameItems)
{
IPowerRenameItem* pItem = it.second;
bool selected = false;
if (SUCCEEDED(pItem->GetSelected(&selected)) && selected)
{
(*count)++;
}
}
return S_OK;
}
IFACEMETHODIMP CPowerRenameManager::GetRenameItemCount(_Out_ UINT* count)
{
*count = 0;
CSRWSharedAutoLock lock(&m_lockItems);
for (auto it : m_renameItems)
{
IPowerRenameItem* pItem = it.second;
bool shouldRename = false;
if (SUCCEEDED(pItem->ShouldRenameItem(m_flags, &shouldRename)) && shouldRename)
{
(*count)++;
}
}
return S_OK;
}
IFACEMETHODIMP CPowerRenameManager::GetFlags(_Out_ DWORD* flags)
{
_EnsureRegEx();
*flags = m_flags;
return S_OK;
}
IFACEMETHODIMP CPowerRenameManager::PutFlags(_In_ DWORD flags)
{
if (flags != m_flags)
{
m_flags = flags;
_EnsureRegEx();
m_spRegEx->PutFlags(flags);
}
return S_OK;
}
IFACEMETHODIMP CPowerRenameManager::GetFilter(_Out_ DWORD* filter)
{
*filter = m_filter;
return S_OK;
}
IFACEMETHODIMP CPowerRenameManager::SwitchFilter(_In_ int)
{
switch (m_filter)
{
case PowerRenameFilters::None:
m_filter = PowerRenameFilters::ShouldRename;
break;
case PowerRenameFilters::ShouldRename:
m_filter = PowerRenameFilters::None;
break;
}
return S_OK;
}
IFACEMETHODIMP CPowerRenameManager::GetRenameRegEx(_COM_Outptr_ IPowerRenameRegEx** ppRegEx)
{
*ppRegEx = nullptr;
HRESULT hr = _EnsureRegEx();
if (SUCCEEDED(hr))
{
*ppRegEx = m_spRegEx;
(*ppRegEx)->AddRef();
}
return hr;
}
IFACEMETHODIMP CPowerRenameManager::PutRenameRegEx(_In_ IPowerRenameRegEx* pRegEx)
{
_ClearRegEx();
m_spRegEx = pRegEx;
return S_OK;
}
IFACEMETHODIMP CPowerRenameManager::GetRenameItemFactory(_COM_Outptr_ IPowerRenameItemFactory** ppItemFactory)
{
*ppItemFactory = nullptr;
HRESULT hr = E_FAIL;
if (m_spItemFactory)
{
hr = S_OK;
*ppItemFactory = m_spItemFactory;
(*ppItemFactory)->AddRef();
}
return hr;
}
IFACEMETHODIMP CPowerRenameManager::PutRenameItemFactory(_In_ IPowerRenameItemFactory* pItemFactory)
{
m_spItemFactory = pItemFactory;
return S_OK;
}
IFACEMETHODIMP CPowerRenameManager::OnSearchTermChanged(_In_ PCWSTR /*searchTerm*/)
{
_PerformRegExRename();
return S_OK;
}
IFACEMETHODIMP CPowerRenameManager::OnReplaceTermChanged(_In_ PCWSTR /*replaceTerm*/)
{
_PerformRegExRename();
return S_OK;
}
IFACEMETHODIMP CPowerRenameManager::OnFlagsChanged(_In_ DWORD flags)
{
// Flags were updated in the rename regex. Update our preview.
m_flags = flags;
_PerformRegExRename();
return S_OK;
}
IFACEMETHODIMP CPowerRenameManager::OnFileTimeChanged(_In_ SYSTEMTIME /*fileTime*/)
{
_PerformRegExRename();
return S_OK;
}
IFACEMETHODIMP CPowerRenameManager::OnMetadataChanged()
{
_PerformRegExRename();
return S_OK;
}
HRESULT CPowerRenameManager::s_CreateInstance(_Outptr_ IPowerRenameManager** ppsrm)
{
*ppsrm = nullptr;
CPowerRenameManager* psrm = new CPowerRenameManager();
HRESULT hr = E_OUTOFMEMORY;
if (psrm)
{
hr = psrm->_Init();
if (SUCCEEDED(hr))
{
hr = psrm->QueryInterface(IID_PPV_ARGS(ppsrm));
}
psrm->Release();
}
return hr;
}
CPowerRenameManager::CPowerRenameManager() :
m_refCount(1)
{
InitializeCriticalSection(&m_critsecReentrancy);
}
CPowerRenameManager::~CPowerRenameManager()
{
DeleteCriticalSection(&m_critsecReentrancy);
}
HRESULT CPowerRenameManager::_Init()
{
// Guaranteed to succeed
m_startFileOpWorkerEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
m_startRegExWorkerEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
m_cancelRegExWorkerEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
m_hwndMessage = CreateMsgWindow(g_hostHInst, s_msgWndProc, this);
return S_OK;
}
// Custom messages for worker threads
enum
{
SRM_REGEX_ITEM_UPDATED = (WM_APP + 1), // Single rename item processed by regex worker thread
SRM_REGEX_ITEM_RENAMED_KEEP_UI, // Single rename item processed by rename worker thread in case UI remains opened
SRM_REGEX_STARTED, // RegEx operation was started
SRM_REGEX_CANCELED, // Regex operation was canceled
SRM_REGEX_COMPLETE, // Regex worker thread completed
SRM_FILEOP_COMPLETE // File Operation worker thread completed
};
struct WorkerThreadData
{
HWND hwndManager = nullptr;
HANDLE startEvent = nullptr;
HANDLE cancelEvent = nullptr;
HWND hwndParent = nullptr;
CComPtr<IPowerRenameManager> spsrm;
};
// Msg-only worker window proc for communication from our worker threads
LRESULT CALLBACK CPowerRenameManager::s_msgWndProc(_In_ HWND hwnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam)
{
LRESULT lRes = 0;
CPowerRenameManager* pThis = reinterpret_cast<CPowerRenameManager*>(GetWindowLongPtr(hwnd, 0));
if (pThis != nullptr)
{
lRes = pThis->_WndProc(hwnd, uMsg, wParam, lParam);
if (uMsg == WM_NCDESTROY)
{
SetWindowLongPtr(hwnd, 0, NULL);
pThis->m_hwndMessage = nullptr;
}
}
else
{
lRes = DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return lRes;
}
LRESULT CPowerRenameManager::_WndProc(_In_ HWND hwnd, _In_ UINT msg, _In_ WPARAM wParam, _In_ LPARAM lParam)
{
LRESULT lRes = 0;
AddRef();
switch (msg)
{
case SRM_REGEX_ITEM_UPDATED:
{
// Do nothing.
break;
}
case SRM_REGEX_ITEM_RENAMED_KEEP_UI:
{
int id = static_cast<int>(lParam);
CComPtr<IPowerRenameItem> spItem;
if (SUCCEEDED(GetItemById(id, &spItem)))
{
_OnRename(spItem);
}
break;
}
case SRM_REGEX_STARTED:
_OnRegExStarted(static_cast<DWORD>(wParam));
break;
case SRM_REGEX_CANCELED:
_OnRegExCanceled(static_cast<DWORD>(wParam));
break;
case SRM_REGEX_COMPLETE:
_OnRegExCompleted(static_cast<DWORD>(wParam));
break;
default:
lRes = DefWindowProc(hwnd, msg, wParam, lParam);
break;
}
Release();
return lRes;
}
void CPowerRenameManager::_LogOperationTelemetry()
{
UINT renameItemCount = 0;
UINT selectedItemCount = 0;
UINT totalItemCount = 0;
DWORD flags = 0;
GetItemCount(&totalItemCount);
GetSelectedItemCount(&selectedItemCount);
GetRenameItemCount(&renameItemCount);
GetFlags(&flags);
// Enumerate extensions used into a map
std::map<std::wstring, int> extensionsMap;
for (UINT i = 0; i < totalItemCount; i++)
{
CComPtr<IPowerRenameItem> spItem;
if (SUCCEEDED(GetItemByIndex(i, &spItem)))
{
PWSTR originalName;
if (SUCCEEDED(spItem->GetOriginalName(&originalName)))
{
std::wstring extension = fs::path(originalName).extension().wstring();
std::map<std::wstring, int>::iterator it = extensionsMap.find(extension);
if (it == extensionsMap.end())
{
extensionsMap.insert({ extension, 1 });
}
else
{
it->second++;
}
CoTaskMemFree(originalName);
}
}
}
std::wstring extensionList = L"";
for (auto elem : extensionsMap)
{
extensionList.append(elem.first);
extensionList.append(L":");
extensionList.append(std::to_wstring(elem.second));
extensionList.append(L",");
}
Trace::RenameOperation(totalItemCount, selectedItemCount, renameItemCount, flags, extensionList.c_str());
}
HRESULT CPowerRenameManager::_PerformFileOperation()
{
// Do we have items to rename?
UINT renameItemCount = 0;
if (FAILED(GetRenameItemCount(&renameItemCount)) || renameItemCount == 0)
{
return E_FAIL;
}
_LogOperationTelemetry();
// Wait for existing regex thread to finish
_WaitForRegExWorkerThread();
// Create worker thread which will perform the actual rename
HRESULT hr = _CreateFileOpWorkerThread();
if (SUCCEEDED(hr))
{
_OnRenameStarted();
// Signal the worker thread that they can start working. We needed to wait until we
// were ready to process thread messages.
SetEvent(m_startFileOpWorkerEvent);
while (true)
{
// Check if worker thread has exited
if (WaitForSingleObject(m_fileOpWorkerThreadHandle, 0) == WAIT_OBJECT_0)
{
break;
}
MSG msg;
while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
if (msg.message == SRM_FILEOP_COMPLETE)
{
// Worker thread completed
break;
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
_OnRenameCompleted();
}
return S_OK;
}
HRESULT CPowerRenameManager::_CreateFileOpWorkerThread()
{
WorkerThreadData* pwtd = new WorkerThreadData;
HRESULT hr = E_OUTOFMEMORY;
if (pwtd)
{
pwtd->hwndManager = m_hwndMessage;
pwtd->startEvent = m_startRegExWorkerEvent;
pwtd->cancelEvent = nullptr;
pwtd->spsrm = this;
m_fileOpWorkerThreadHandle = CreateThread(nullptr, 0, s_fileOpWorkerThread, pwtd, 0, nullptr);
hr = E_FAIL;
if (m_fileOpWorkerThreadHandle)
{
hr = S_OK;
}
else
{
delete pwtd;
}
}
return hr;
}
DWORD WINAPI CPowerRenameManager::s_fileOpWorkerThread(_In_ void* pv)
{
if (SUCCEEDED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)))
{
WorkerThreadData* pwtd = static_cast<WorkerThreadData*>(pv);
if (pwtd)
{
bool closeUIWindowAfterRenaming = true;
pwtd->spsrm->GetCloseUIWindowAfterRenaming(&closeUIWindowAfterRenaming);
// Wait to be told we can begin
if (WaitForSingleObject(pwtd->startEvent, INFINITE) == WAIT_OBJECT_0)
{
CComPtr<IPowerRenameRegEx> spRenameRegEx;
if (SUCCEEDED(pwtd->spsrm->GetRenameRegEx(&spRenameRegEx)))
{
// Create IFileOperation interface
CComPtr<IFileOperation> spFileOp;
if (SUCCEEDED(CoCreateInstance(CLSID_FileOperation, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&spFileOp))))
{
DWORD flags = 0;
spRenameRegEx->GetFlags(&flags);
UINT itemCount = 0;
pwtd->spsrm->GetItemCount(&itemCount);
// We add the items to the operation in depth-first order. This allows child items to be
// renamed before parent items.
// Creating a vector of vectors of items of the same depth
std::vector<std::vector<UINT>> matrix(itemCount);
for (UINT u = 0; u < itemCount; u++)
{
CComPtr<IPowerRenameItem> spItem;
if (SUCCEEDED(pwtd->spsrm->GetItemByIndex(u, &spItem)))
{
UINT depth = 0;
spItem->GetDepth(&depth);
matrix[depth].push_back(u);
}
}
// From the greatest depth first, add all items of that depth to the operation
for (LONG v = itemCount - 1; v >= 0; v--)
{
for (auto it : matrix[v])
{
CComPtr<IPowerRenameItem> spItem;
if (SUCCEEDED(pwtd->spsrm->GetItemByIndex(it, &spItem)))
{
bool shouldRename = false;
if (SUCCEEDED(spItem->ShouldRenameItem(flags, &shouldRename)) && shouldRename)
{
PWSTR newName = nullptr;
if (SUCCEEDED(spItem->GetNewName(&newName)))
{
CComPtr<IShellItem> spShellItem;
if (SUCCEEDED(spItem->GetShellItem(&spShellItem)))
{
spFileOp->RenameItem(spShellItem, newName, nullptr);
if (!closeUIWindowAfterRenaming)
{
// Update item data
PWSTR originalName = nullptr;
winrt::check_hresult(spItem->GetOriginalName(&originalName));
std::wstring originalNameStr{ originalName };
PWSTR path = nullptr;
winrt::check_hresult(spItem->GetPath(&path));
std::wstring pathStr{ path };
size_t oldPathSize = pathStr.size();
auto fileNamePos = pathStr.find_last_of(L"\\");
pathStr.replace(fileNamePos + 1, originalNameStr.length(), std::wstring{ newName });
spItem->PutPath(pathStr.c_str());
spItem->PutOriginalName(newName);
spItem->PutNewName(nullptr);
// if folder, update children path
bool isFolder = false;
winrt::check_hresult(spItem->GetIsFolder(&isFolder));
if (isFolder)
{
int id = -1;
winrt::check_hresult(spItem->GetId(&id));
pwtd->spsrm->UpdateChildrenPath(id, oldPathSize);
}
int id = -1;
winrt::check_hresult(spItem->GetId(&id));
PostMessage(pwtd->hwndManager, SRM_REGEX_ITEM_RENAMED_KEEP_UI, GetCurrentThreadId(), id);
}
}
CoTaskMemFree(newName);
}
}
}
}
}
// Set the operation flags
if (SUCCEEDED(spFileOp->SetOperationFlags(FOF_DEFAULTFLAGS)))
{
// Set the parent window
if (pwtd->hwndParent)
{
spFileOp->SetOwnerWindow(pwtd->hwndParent);
}
// Perform the operation
// We don't care about the return code here. We would rather
// return control back to explorer so the user can cleanly
// undo the operation if it failed halfway through.
spFileOp->PerformOperations();
}
}
}
}
// Send the manager thread the completion message
PostMessage(pwtd->hwndManager, SRM_FILEOP_COMPLETE, GetCurrentThreadId(), 0);
delete pwtd;
}
CoUninitialize();
}
return 0;
}
HRESULT CPowerRenameManager::_PerformRegExRename()
{
HRESULT hr = E_FAIL;
if (!TryEnterCriticalSection(&m_critsecReentrancy))
{
// Ensure we do not re-enter since we pump messages here.
// TODO: If we do, post a message back to ourselves
}
else
{
// Ensure previous thread is canceled
_CancelRegExWorkerThread();
// Create worker thread which will message us progress and completion.
hr = _CreateRegExWorkerThread();
if (SUCCEEDED(hr))
{
ResetEvent(m_cancelRegExWorkerEvent);
// Signal the worker thread that they can start working. We needed to wait until we
// were ready to process thread messages.
SetEvent(m_startRegExWorkerEvent);
}
}
return hr;
}
HRESULT CPowerRenameManager::_CreateRegExWorkerThread()
{
WorkerThreadData* pwtd = new WorkerThreadData;
HRESULT hr = E_OUTOFMEMORY;
if (pwtd)
{
pwtd->hwndManager = m_hwndMessage;
pwtd->startEvent = m_startRegExWorkerEvent;
pwtd->cancelEvent = m_cancelRegExWorkerEvent;
pwtd->hwndParent = m_hwndParent;
pwtd->spsrm = this;
m_regExWorkerThreadHandle = CreateThread(nullptr, 0, s_regexWorkerThread, pwtd, 0, nullptr);
hr = E_FAIL;
if (m_regExWorkerThreadHandle)
{
hr = S_OK;
}
else
{
delete pwtd;
}
}
return hr;
}
DWORD WINAPI CPowerRenameManager::s_regexWorkerThread(_In_ void* pv)
{
try
{
winrt::check_hresult(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE));
WorkerThreadData* pwtd = static_cast<WorkerThreadData*>(pv);
if (pwtd)
{
PostMessage(pwtd->hwndManager, SRM_REGEX_STARTED, GetCurrentThreadId(), 0);
// Wait to be told we can begin
if (WaitForSingleObject(pwtd->startEvent, INFINITE) == WAIT_OBJECT_0)
{
CComPtr<IPowerRenameRegEx> spRenameRegEx;
winrt::check_hresult(pwtd->spsrm->GetRenameRegEx(&spRenameRegEx));
UINT itemCount = 0;
unsigned long itemEnumIndex = 0;
winrt::check_hresult(pwtd->spsrm->GetItemCount(&itemCount));
for (UINT u = 0; u < itemCount; u++)
{
// Check if cancel event is signaled
if (WaitForSingleObject(pwtd->cancelEvent, 0) == WAIT_OBJECT_0)
{
// Canceled from manager
// Send the manager thread the canceled message
PostMessage(pwtd->hwndManager, SRM_REGEX_CANCELED, GetCurrentThreadId(), 0);
break;
}
CComPtr<IPowerRenameItem> spItem;
winrt::check_hresult(pwtd->spsrm->GetItemByIndex(u, &spItem));
DoRename(spRenameRegEx, itemEnumIndex, spItem);
}
}
// Send the manager thread the completion message
PostMessage(pwtd->hwndManager, SRM_REGEX_COMPLETE, GetCurrentThreadId(), 0);
delete pwtd;
}
CoUninitialize();
}
catch (...)
{
// TODO: an exception can happen while typing the expression and the syntax is not correct yet,
// we need to be more granular and raise an exception only when a real problem happened.
// MessageBox(NULL, L"RegexWorkerThread failed to execute.\nPlease report the bug to https://aka.ms/powerToysReportBug", L"PowerRename Error", MB_OK);
}
return 0;
}
void CPowerRenameManager::_CancelRegExWorkerThread()
{
if (m_startRegExWorkerEvent)
{
SetEvent(m_startRegExWorkerEvent);
}
if (m_cancelRegExWorkerEvent)
{
SetEvent(m_cancelRegExWorkerEvent);
}
_WaitForRegExWorkerThread();
}
void CPowerRenameManager::_WaitForRegExWorkerThread()
{
if (m_regExWorkerThreadHandle)
{
WaitForSingleObject(m_regExWorkerThreadHandle, INFINITE);
CloseHandle(m_regExWorkerThreadHandle);
m_regExWorkerThreadHandle = nullptr;
}
}
void CPowerRenameManager::_Cancel()
{
SetEvent(m_startFileOpWorkerEvent);
_CancelRegExWorkerThread();
}
HRESULT CPowerRenameManager::_EnsureRegEx()
{
HRESULT hr = S_OK;
if (!m_spRegEx)
{
// Create the default regex handler
hr = CPowerRenameRegEx::s_CreateInstance(&m_spRegEx);
if (SUCCEEDED(hr))
{
hr = _InitRegEx();
// Get the flags
if (SUCCEEDED(hr))
{
m_spRegEx->GetFlags(&m_flags);
}
}
}
return hr;
}
HRESULT CPowerRenameManager::_InitRegEx()
{
HRESULT hr = E_FAIL;
if (m_spRegEx)
{
hr = m_spRegEx->Advise(this, &m_regExAdviseCookie);
}
return hr;
}
void CPowerRenameManager::_ClearRegEx()
{
if (m_spRegEx)
{
m_spRegEx->UnAdvise(m_regExAdviseCookie);
m_regExAdviseCookie = 0;
}
}
void CPowerRenameManager::_OnRename(_In_ IPowerRenameItem* renameItem)
{
CSRWSharedAutoLock lock(&m_lockEvents);
for (auto it : m_powerRenameManagerEvents)
{
if (it.pEvents)
{
it.pEvents->OnRename(renameItem);
}
}
}
void CPowerRenameManager::_OnError(_In_ IPowerRenameItem* renameItem)
{
CSRWSharedAutoLock lock(&m_lockEvents);
for (auto it : m_powerRenameManagerEvents)
{
if (it.pEvents)
{
it.pEvents->OnError(renameItem);
}
}
}
void CPowerRenameManager::_OnRegExStarted(_In_ DWORD threadId)
{
CSRWSharedAutoLock lock(&m_lockEvents);
for (auto it : m_powerRenameManagerEvents)
{
if (it.pEvents)
{
it.pEvents->OnRegExStarted(threadId);
}
}
}
void CPowerRenameManager::_OnRegExCanceled(_In_ DWORD threadId)
{
CSRWSharedAutoLock lock(&m_lockEvents);
for (auto it : m_powerRenameManagerEvents)
{
if (it.pEvents)
{
it.pEvents->OnRegExCanceled(threadId);
}
}
}
void CPowerRenameManager::_OnRegExCompleted(_In_ DWORD threadId)
{
CSRWSharedAutoLock lock(&m_lockEvents);
for (auto it : m_powerRenameManagerEvents)
{
if (it.pEvents)
{
it.pEvents->OnRegExCompleted(threadId);
}
}
}
void CPowerRenameManager::_OnRenameStarted()
{
CSRWSharedAutoLock lock(&m_lockEvents);
for (auto it : m_powerRenameManagerEvents)
{
if (it.pEvents)
{
it.pEvents->OnRenameStarted();
}
}
}
void CPowerRenameManager::_OnRenameCompleted()
{
CSRWSharedAutoLock lock(&m_lockEvents);
for (auto it : m_powerRenameManagerEvents)
{
if (it.pEvents)
{
it.pEvents->OnRenameCompleted(m_closeUIWindowAfterRenaming);
}
}
}
void CPowerRenameManager::_ClearEventHandlers()
{
CSRWExclusiveAutoLock lock(&m_lockEvents);
// Cleanup event handlers
for (std::vector<RENAME_MGR_EVENT>::iterator it = m_powerRenameManagerEvents.begin(); it != m_powerRenameManagerEvents.end(); ++it)
{
it->cookie = 0;
if (it->pEvents)
{
it->pEvents->Release();
it->pEvents = nullptr;
}
}
m_powerRenameManagerEvents.clear();
}
void CPowerRenameManager::_ClearPowerRenameItems()
{
CSRWExclusiveAutoLock lock(&m_lockItems);
// Cleanup rename items
for (std::map<int, IPowerRenameItem*>::iterator it = m_renameItems.begin(); it != m_renameItems.end(); ++it)
{
IPowerRenameItem* pItem = it->second;
if (pItem)
{
pItem->Release();
it->second = nullptr;
}
}
m_renameItems.clear();
}
void CPowerRenameManager::_Cleanup()
{
if (m_hwndMessage)
{
DestroyWindow(m_hwndMessage);
m_hwndMessage = nullptr;
}
CloseHandle(m_startFileOpWorkerEvent);
m_startFileOpWorkerEvent = nullptr;
CloseHandle(m_startRegExWorkerEvent);
m_startRegExWorkerEvent = nullptr;
CloseHandle(m_cancelRegExWorkerEvent);
m_cancelRegExWorkerEvent = nullptr;
_ClearRegEx();
_ClearEventHandlers();
_ClearPowerRenameItems();
}