diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 424276b0d4..3ad73129db 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -2126,6 +2126,7 @@ tif TILEDWINDOW timeinfo Timeline +timeunion timeutil titlecase tlb @@ -2191,6 +2192,7 @@ uint UIPI UIs ul +ULARGE ULLONG ulong unchecks diff --git a/src/modules/powerrename/dll/PowerRenameExt.cpp b/src/modules/powerrename/dll/PowerRenameExt.cpp index 86c05af165..4d1c3a685f 100644 --- a/src/modules/powerrename/dll/PowerRenameExt.cpp +++ b/src/modules/powerrename/dll/PowerRenameExt.cpp @@ -123,8 +123,8 @@ HRESULT CPowerRenameMenu::InvokeCommand(_In_ LPCMINVOKECOMMANDINFO pici) { Trace::Invoked(); InvokeStruct* pInvokeData = new (std::nothrow) InvokeStruct; - hr = pInvokeData ? S_OK : E_OUTOFMEMORY; - if (SUCCEEDED(hr)) + hr = E_OUTOFMEMORY; + if (pInvokeData) { pInvokeData->hwndParent = pici->hwnd; hr = CoMarshalInterThreadInterfaceInStream(__uuidof(m_spdo), m_spdo, &(pInvokeData->pstrm)); @@ -248,8 +248,8 @@ HRESULT __stdcall CPowerRenameMenu::Invoke(IShellItemArray* psiItemArray, IBindC #endif Trace::Invoked(); InvokeStruct* pInvokeData = new (std::nothrow) InvokeStruct; - HRESULT hr = pInvokeData ? S_OK : E_OUTOFMEMORY; - if (SUCCEEDED(hr)) + HRESULT hr = E_OUTOFMEMORY; + if (pInvokeData) { pInvokeData->hwndParent = nullptr; hr = CoMarshalInterThreadInterfaceInStream(__uuidof(psiItemArray), psiItemArray, &(pInvokeData->pstrm)); diff --git a/src/modules/powerrename/dll/dllmain.cpp b/src/modules/powerrename/dll/dllmain.cpp index 606add869e..25881e62d3 100644 --- a/src/modules/powerrename/dll/dllmain.cpp +++ b/src/modules/powerrename/dll/dllmain.cpp @@ -123,9 +123,10 @@ STDAPI DllCanUnloadNow(void) // STDAPI DllGetClassObject(_In_ REFCLSID clsid, _In_ REFIID riid, _Outptr_ void** ppv) { + HRESULT hr = E_FAIL; *ppv = NULL; CPowerRenameClassFactory* pClassFactory = new CPowerRenameClassFactory(clsid); - HRESULT hr = pClassFactory->QueryInterface(riid, ppv); + hr = pClassFactory->QueryInterface(riid, ppv); pClassFactory->Release(); return hr; } diff --git a/src/modules/powerrename/lib/Helpers.cpp b/src/modules/powerrename/lib/Helpers.cpp index 02a022a2cd..0c01f06597 100644 --- a/src/modules/powerrename/lib/Helpers.cpp +++ b/src/modules/powerrename/lib/Helpers.cpp @@ -9,8 +9,8 @@ namespace fs = std::filesystem; HRESULT GetTrimmedFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR source) { - HRESULT hr = (source && wcslen(source) > 0) ? S_OK : E_INVALIDARG; - if (SUCCEEDED(hr)) + HRESULT hr = E_INVALIDARG; + if (source) { PWSTR newName = nullptr; hr = SHStrDup(source, &newName); @@ -38,8 +38,8 @@ HRESULT GetTrimmedFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR source) HRESULT GetTransformedFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR source, DWORD flags) { std::locale::global(std::locale("")); - HRESULT hr = (source && wcslen(source) > 0 && flags) ? S_OK : E_INVALIDARG; - if (SUCCEEDED(hr)) + HRESULT hr = E_INVALIDARG; + if (source && flags) { if (flags & Uppercase) { @@ -169,7 +169,7 @@ HRESULT GetTransformedFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR sour return hr; } -bool isFileAttributesUsed(_In_ PCWSTR source) +bool isFileTimeUsed(_In_ PCWSTR source) { bool used = false; std::wstring patterns[] = { L"(([^\\$]|^)(\\$\\$)*)\\$Y", L"(([^\\$]|^)(\\$\\$)*)\\$M", L"(([^\\$]|^)(\\$\\$)*)\\$D", @@ -185,11 +185,11 @@ bool isFileAttributesUsed(_In_ PCWSTR source) return used; } -HRESULT GetDatedFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR source, SYSTEMTIME LocalTime) +HRESULT GetDatedFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR source, SYSTEMTIME fileTime) { std::locale::global(std::locale("")); - HRESULT hr = (source && wcslen(source) > 0) ? S_OK : E_INVALIDARG; - if (SUCCEEDED(hr)) + HRESULT hr = E_INVALIDARG; + if (source && wcslen(source) > 0) { std::wstring res(source); wchar_t replaceTerm[MAX_PATH] = { 0 }; @@ -201,72 +201,72 @@ HRESULT GetDatedFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR source, SY StringCchCopy(localeName, LOCALE_NAME_MAX_LENGTH, L"en_US"); } - StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%04d"), L"$01", LocalTime.wYear); + StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%04d"), L"$01", fileTime.wYear); res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$YYYY"), replaceTerm); - StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", (LocalTime.wYear % 100)); + StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", (fileTime.wYear % 100)); res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$YY"), replaceTerm); - StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", (LocalTime.wYear % 10)); + StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", (fileTime.wYear % 10)); res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$Y"), replaceTerm); - GetDateFormatEx(localeName, NULL, &LocalTime, L"MMMM", formattedDate, MAX_PATH, NULL); + GetDateFormatEx(localeName, NULL, &fileTime, L"MMMM", formattedDate, MAX_PATH, NULL); formattedDate[0] = towupper(formattedDate[0]); StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", formattedDate); res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$MMMM"), replaceTerm); - GetDateFormatEx(localeName, NULL, &LocalTime, L"MMM", formattedDate, MAX_PATH, NULL); + GetDateFormatEx(localeName, NULL, &fileTime, L"MMM", formattedDate, MAX_PATH, NULL); formattedDate[0] = towupper(formattedDate[0]); StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", formattedDate); res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$MMM"), replaceTerm); - StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", LocalTime.wMonth); + StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", fileTime.wMonth); res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$MM"), replaceTerm); - StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", LocalTime.wMonth); + StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", fileTime.wMonth); res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$M"), replaceTerm); - GetDateFormatEx(localeName, NULL, &LocalTime, L"dddd", formattedDate, MAX_PATH, NULL); + GetDateFormatEx(localeName, NULL, &fileTime, L"dddd", formattedDate, MAX_PATH, NULL); formattedDate[0] = towupper(formattedDate[0]); StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", formattedDate); res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$DDDD"), replaceTerm); - GetDateFormatEx(localeName, NULL, &LocalTime, L"ddd", formattedDate, MAX_PATH, NULL); + GetDateFormatEx(localeName, NULL, &fileTime, L"ddd", formattedDate, MAX_PATH, NULL); formattedDate[0] = towupper(formattedDate[0]); StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", formattedDate); res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$DDD"), replaceTerm); - StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", LocalTime.wDay); + StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", fileTime.wDay); res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$DD"), replaceTerm); - StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", LocalTime.wDay); + StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", fileTime.wDay); res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$D"), replaceTerm); - StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", LocalTime.wHour); + StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", fileTime.wHour); res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$hh"), replaceTerm); - StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", LocalTime.wHour); + StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", fileTime.wHour); res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$h"), replaceTerm); - StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", LocalTime.wMinute); + StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", fileTime.wMinute); res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$mm"), replaceTerm); - StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", LocalTime.wMinute); + StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", fileTime.wMinute); res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$m"), replaceTerm); - StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", LocalTime.wSecond); + StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", fileTime.wSecond); res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$ss"), replaceTerm); - StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", LocalTime.wSecond); + StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", fileTime.wSecond); res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$s"), replaceTerm); - StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%03d"), L"$01", LocalTime.wMilliseconds); + StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%03d"), L"$01", fileTime.wMilliseconds); res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$fff"), replaceTerm); - StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", LocalTime.wMilliseconds/10); + StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", fileTime.wMilliseconds/10); res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$ff"), replaceTerm); - StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", LocalTime.wMilliseconds/100); + StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", fileTime.wMilliseconds/100); res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$f"), replaceTerm); hr = StringCchCopy(result, cchMax, res.c_str()); @@ -346,12 +346,11 @@ HRESULT _ParseEnumItems(_In_ IEnumShellItems* pesi, _In_ IPowerRenameManager* ps HRESULT EnumerateDataObject(_In_ IUnknown* dataSource, _In_ IPowerRenameManager* psrm) { CComPtr spsia; - HRESULT hr = _GetShellItemArrayFromDataOject(dataSource, &spsia); - if (SUCCEEDED(hr)) + HRESULT hr = E_FAIL; + if (SUCCEEDED(_GetShellItemArrayFromDataOject(dataSource, &spsia))) { CComPtr spesi; - hr = spsia->EnumItems(&spesi); - if (SUCCEEDED(hr)) + if (SUCCEEDED(spsia->EnumItems(&spesi))) { hr = _ParseEnumItems(spesi, psrm); } diff --git a/src/modules/powerrename/lib/Helpers.h b/src/modules/powerrename/lib/Helpers.h index 64c5638322..39e4792167 100644 --- a/src/modules/powerrename/lib/Helpers.h +++ b/src/modules/powerrename/lib/Helpers.h @@ -5,8 +5,8 @@ HRESULT GetTrimmedFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR source); HRESULT GetTransformedFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR source, DWORD flags); -HRESULT GetDatedFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR source, SYSTEMTIME LocalTime); -bool isFileAttributesUsed(_In_ PCWSTR source); +HRESULT GetDatedFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR source, SYSTEMTIME fileTime); +bool isFileTimeUsed(_In_ PCWSTR source); bool DataObjectContainsRenamableItem(_In_ IUnknown* dataSource); HRESULT EnumerateDataObject(_In_ IUnknown* pdo, _In_ IPowerRenameManager* psrm); BOOL GetEnumeratedFileName( diff --git a/src/modules/powerrename/lib/PowerRenameInterfaces.h b/src/modules/powerrename/lib/PowerRenameInterfaces.h index e6adb876a5..ee2303c1fd 100644 --- a/src/modules/powerrename/lib/PowerRenameInterfaces.h +++ b/src/modules/powerrename/lib/PowerRenameInterfaces.h @@ -31,6 +31,7 @@ public: IFACEMETHOD(OnSearchTermChanged)(_In_ PCWSTR searchTerm) = 0; IFACEMETHOD(OnReplaceTermChanged)(_In_ PCWSTR replaceTerm) = 0; IFACEMETHOD(OnFlagsChanged)(_In_ DWORD flags) = 0; + IFACEMETHOD(OnFileTimeChanged)(_In_ SYSTEMTIME fileTime) = 0; }; interface __declspec(uuid("E3ED45B5-9CE0-47E2-A595-67EB950B9B72")) IPowerRenameRegEx : public IUnknown @@ -44,6 +45,8 @@ public: IFACEMETHOD(PutReplaceTerm)(_In_ PCWSTR replaceTerm) = 0; IFACEMETHOD(GetFlags)(_Out_ DWORD* flags) = 0; IFACEMETHOD(PutFlags)(_In_ DWORD flags) = 0; + IFACEMETHOD(PutFileTime)(_In_ SYSTEMTIME fileTime) = 0; + IFACEMETHOD(ResetFileTime)() = 0; IFACEMETHOD(Replace)(_In_ PCWSTR source, _Outptr_ PWSTR* result) = 0; }; @@ -51,7 +54,7 @@ interface __declspec(uuid("C7F59201-4DE1-4855-A3A2-26FC3279C8A5")) IPowerRenameI { public: IFACEMETHOD(GetPath)(_Outptr_ PWSTR* path) = 0; - IFACEMETHOD(GetDate)(_Outptr_ SYSTEMTIME* date) = 0; + IFACEMETHOD(GetTime)(_Outptr_ SYSTEMTIME* time) = 0; IFACEMETHOD(GetShellItem)(_Outptr_ IShellItem** ppsi) = 0; IFACEMETHOD(GetOriginalName)(_Outptr_ PWSTR* originalName) = 0; IFACEMETHOD(GetNewName)(_Outptr_ PWSTR* newName) = 0; diff --git a/src/modules/powerrename/lib/PowerRenameItem.cpp b/src/modules/powerrename/lib/PowerRenameItem.cpp index 08364675f8..10078eb3b2 100644 --- a/src/modules/powerrename/lib/PowerRenameItem.cpp +++ b/src/modules/powerrename/lib/PowerRenameItem.cpp @@ -34,19 +34,24 @@ IFACEMETHODIMP CPowerRenameItem::GetPath(_Outptr_ PWSTR* path) { *path = nullptr; CSRWSharedAutoLock lock(&m_lock); - HRESULT hr = m_path ? S_OK : E_FAIL; - if (SUCCEEDED(hr)) + HRESULT hr = E_FAIL; + if (m_path) { hr = SHStrDup(m_path, path); } return hr; } -IFACEMETHODIMP CPowerRenameItem::GetDate(_Outptr_ SYSTEMTIME* date) +IFACEMETHODIMP CPowerRenameItem::GetTime(_Outptr_ SYSTEMTIME* time) { CSRWSharedAutoLock lock(&m_lock); - HRESULT hr = m_isDateParsed ? S_OK : E_FAIL ; - if (!m_isDateParsed) + HRESULT hr = E_FAIL ; + + if (m_isTimeParsed) + { + hr = S_OK; + } + else { HANDLE hFile = CreateFileW(m_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); if (hFile != INVALID_HANDLE_VALUE) @@ -59,8 +64,8 @@ IFACEMETHODIMP CPowerRenameItem::GetDate(_Outptr_ SYSTEMTIME* date) { if (SystemTimeToTzSpecificLocalTime(NULL, &SystemTime, &LocalTime)) { - m_date = LocalTime; - m_isDateParsed = true; + m_time = LocalTime; + m_isTimeParsed = true; hr = S_OK; } } @@ -68,7 +73,7 @@ IFACEMETHODIMP CPowerRenameItem::GetDate(_Outptr_ SYSTEMTIME* date) } CloseHandle(hFile); } - *date = m_date; + *time = m_time; return hr; } @@ -80,8 +85,8 @@ IFACEMETHODIMP CPowerRenameItem::GetShellItem(_Outptr_ IShellItem** ppsi) IFACEMETHODIMP CPowerRenameItem::GetOriginalName(_Outptr_ PWSTR* originalName) { CSRWSharedAutoLock lock(&m_lock); - HRESULT hr = m_originalName ? S_OK : E_FAIL; - if (SUCCEEDED(hr)) + HRESULT hr = E_FAIL; + if (m_originalName) { hr = SHStrDup(m_originalName, originalName); } @@ -104,8 +109,8 @@ IFACEMETHODIMP CPowerRenameItem::PutNewName(_In_opt_ PCWSTR newName) IFACEMETHODIMP CPowerRenameItem::GetNewName(_Outptr_ PWSTR* newName) { CSRWSharedAutoLock lock(&m_lock); - HRESULT hr = m_newName ? S_OK : E_FAIL; - if (SUCCEEDED(hr)) + HRESULT hr = S_OK; + if (m_newName) { hr = SHStrDup(m_newName, newName); } @@ -217,9 +222,10 @@ HRESULT CPowerRenameItem::s_CreateInstance(_In_opt_ IShellItem* psi, _In_ REFIID *resultInterface = nullptr; CPowerRenameItem *newRenameItem = new CPowerRenameItem(); - HRESULT hr = newRenameItem ? S_OK : E_OUTOFMEMORY; - if (SUCCEEDED(hr)) + HRESULT hr = E_OUTOFMEMORY; + if (newRenameItem) { + hr = S_OK ; if (psi != nullptr) { hr = newRenameItem->_Init(psi); diff --git a/src/modules/powerrename/lib/PowerRenameItem.h b/src/modules/powerrename/lib/PowerRenameItem.h index c3d5360a49..cb5ad8cc37 100644 --- a/src/modules/powerrename/lib/PowerRenameItem.h +++ b/src/modules/powerrename/lib/PowerRenameItem.h @@ -15,7 +15,7 @@ public: // IPowerRenameItem IFACEMETHODIMP GetPath(_Outptr_ PWSTR* path); - IFACEMETHODIMP GetDate(_Outptr_ SYSTEMTIME* date); + IFACEMETHODIMP GetTime(_Outptr_ SYSTEMTIME* time); IFACEMETHODIMP GetShellItem(_Outptr_ IShellItem** ppsi); IFACEMETHODIMP GetOriginalName(_Outptr_ PWSTR* originalName); IFACEMETHODIMP PutNewName(_In_opt_ PCWSTR newName); @@ -50,7 +50,7 @@ protected: bool m_selected = true; bool m_isFolder = false; - bool m_isDateParsed = false; + bool m_isTimeParsed = false; bool m_canRename = true; int m_id = -1; int m_iconIndex = -1; @@ -59,7 +59,7 @@ protected: PWSTR m_path = nullptr; PWSTR m_originalName = nullptr; PWSTR m_newName = nullptr; - SYSTEMTIME m_date; + SYSTEMTIME m_time = {0}; CSRWLock m_lock; long m_refCount = 0; }; diff --git a/src/modules/powerrename/lib/PowerRenameManager.cpp b/src/modules/powerrename/lib/PowerRenameManager.cpp index 596adf4eee..c2e31d3f71 100644 --- a/src/modules/powerrename/lib/PowerRenameManager.cpp +++ b/src/modules/powerrename/lib/PowerRenameManager.cpp @@ -8,6 +8,7 @@ #include "window_helpers.h" #include #include "trace.h" +#include namespace fs = std::filesystem; @@ -415,12 +416,18 @@ IFACEMETHODIMP CPowerRenameManager::OnFlagsChanged(_In_ DWORD flags) return S_OK; } +IFACEMETHODIMP CPowerRenameManager::OnFileTimeChanged(_In_ SYSTEMTIME /*fileTime*/) +{ + _PerformRegExRename(); + return S_OK; +} + HRESULT CPowerRenameManager::s_CreateInstance(_Outptr_ IPowerRenameManager** ppsrm) { *ppsrm = nullptr; CPowerRenameManager *psrm = new CPowerRenameManager(); - HRESULT hr = psrm ? S_OK : E_OUTOFMEMORY; - if (SUCCEEDED(hr)) + HRESULT hr = E_OUTOFMEMORY; + if (psrm) { hr = psrm->_Init(); if (SUCCEEDED(hr)) @@ -645,16 +652,20 @@ HRESULT CPowerRenameManager::_PerformFileOperation() HRESULT CPowerRenameManager::_CreateFileOpWorkerThread() { WorkerThreadData* pwtd = new WorkerThreadData; - HRESULT hr = pwtd ? S_OK : E_OUTOFMEMORY; - if (SUCCEEDED(hr)) + 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 = (m_fileOpWorkerThreadHandle) ? S_OK : E_FAIL; - if (FAILED(hr)) + hr = E_FAIL; + if (m_fileOpWorkerThreadHandle) + { + hr = S_OK; + } + else { delete pwtd; } @@ -791,8 +802,8 @@ HRESULT CPowerRenameManager::_PerformRegExRename() HRESULT CPowerRenameManager::_CreateRegExWorkerThread() { WorkerThreadData* pwtd = new WorkerThreadData; - HRESULT hr = pwtd ? S_OK : E_OUTOFMEMORY; - if (SUCCEEDED(hr)) + HRESULT hr = E_OUTOFMEMORY; + if (pwtd) { pwtd->hwndManager = m_hwndMessage; pwtd->startEvent = m_startRegExWorkerEvent; @@ -800,8 +811,12 @@ HRESULT CPowerRenameManager::_CreateRegExWorkerThread() pwtd->hwndParent = m_hwndParent; pwtd->spsrm = this; m_regExWorkerThreadHandle = CreateThread(nullptr, 0, s_regexWorkerThread, pwtd, 0, nullptr); - hr = (m_regExWorkerThreadHandle) ? S_OK : E_FAIL; - if (FAILED(hr)) + hr = E_FAIL; + if (m_regExWorkerThreadHandle) + { + hr = S_OK; + } + else { delete pwtd; } @@ -812,8 +827,9 @@ HRESULT CPowerRenameManager::_CreateRegExWorkerThread() DWORD WINAPI CPowerRenameManager::s_regexWorkerThread(_In_ void* pv) { - if (SUCCEEDED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE))) + try { + winrt::check_hresult(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)); WorkerThreadData* pwtd = reinterpret_cast(pv); if (pwtd) { @@ -823,192 +839,201 @@ DWORD WINAPI CPowerRenameManager::s_regexWorkerThread(_In_ void* pv) if (WaitForSingleObject(pwtd->startEvent, INFINITE) == WAIT_OBJECT_0) { CComPtr spRenameRegEx; - if (SUCCEEDED(pwtd->spsrm->GetRenameRegEx(&spRenameRegEx))) + + winrt::check_hresult(pwtd->spsrm->GetRenameRegEx(&spRenameRegEx)); + + DWORD flags = 0; + winrt::check_hresult(spRenameRegEx->GetFlags(&flags)); + + PWSTR replaceTerm = nullptr; + bool useFileTime = false; + + winrt::check_hresult(spRenameRegEx->GetReplaceTerm(&replaceTerm)); + + if (isFileTimeUsed(replaceTerm)) { - DWORD flags = 0; - spRenameRegEx->GetFlags(&flags); + useFileTime = true; + } - UINT itemCount = 0; - unsigned long itemEnumIndex = 1; - pwtd->spsrm->GetItemCount(&itemCount); - for (UINT u = 0; u <= itemCount; u++) + UINT itemCount = 0; + unsigned long itemEnumIndex = 1; + 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) { - // 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 spItem; + winrt::check_hresult(pwtd->spsrm->GetItemByIndex(u, &spItem)); + + int id = -1; + winrt::check_hresult(spItem->GetId(&id)); + + bool isFolder = false; + bool isSubFolderContent = false; + winrt::check_hresult(spItem->GetIsFolder(&isFolder)); + winrt::check_hresult(spItem->GetIsSubFolderContent(&isSubFolderContent)); + if ((isFolder && (flags & PowerRenameFlags::ExcludeFolders)) || + (!isFolder && (flags & PowerRenameFlags::ExcludeFiles)) || + (isSubFolderContent && (flags & PowerRenameFlags::ExcludeSubfolders))) + { + // Exclude this item from renaming. Ensure new name is cleared. + winrt::check_hresult(spItem->PutNewName(nullptr)); + + // Send the manager thread the item processed message + PostMessage(pwtd->hwndManager, SRM_REGEX_ITEM_UPDATED, GetCurrentThreadId(), id); + + continue; + } + + PWSTR originalName = nullptr; + winrt::check_hresult(spItem->GetOriginalName(&originalName)); + + + PWSTR currentNewName = nullptr; + winrt::check_hresult(spItem->GetNewName(¤tNewName)); + + wchar_t sourceName[MAX_PATH] = { 0 }; + if (flags & NameOnly) + { + StringCchCopy(sourceName, ARRAYSIZE(sourceName), fs::path(originalName).stem().c_str()); + } + else if (flags & ExtensionOnly) + { + std::wstring extension = fs::path(originalName).extension().wstring(); + if (!extension.empty() && extension.front() == '.') { - // Canceled from manager - // Send the manager thread the canceled message - PostMessage(pwtd->hwndManager, SRM_REGEX_CANCELED, GetCurrentThreadId(), 0); - break; + extension = extension.erase(0, 1); } + StringCchCopy(sourceName, ARRAYSIZE(sourceName), extension.c_str()); + } + else + { + StringCchCopy(sourceName, ARRAYSIZE(sourceName), originalName); + } - CComPtr spItem; - if (SUCCEEDED(pwtd->spsrm->GetItemByIndex(u, &spItem))) + SYSTEMTIME fileTime = { 0 }; + + if (useFileTime) + { + winrt::check_hresult(spItem->GetTime(&fileTime)); + winrt::check_hresult(spRenameRegEx->PutFileTime(fileTime)); + } + + PWSTR newName = nullptr; + + // Failure here means we didn't match anything or had nothing to match + // Call put_newName with null in that case to reset it + winrt::check_hresult(spRenameRegEx->Replace(sourceName, &newName)); + + if (useFileTime) + { + winrt::check_hresult(spRenameRegEx->ResetFileTime()); + } + + wchar_t resultName[MAX_PATH] = { 0 }; + + PWSTR newNameToUse = nullptr; + + // newName == nullptr likely means we have an empty search string. We should leave newNameToUse + // as nullptr so we clear the renamed column + // Except string transformation is selected. + + if (newName == nullptr && (flags & Uppercase || flags & Lowercase || flags & Titlecase)) + { + SHStrDup(sourceName, &newName); + } + + if (newName != nullptr) + { + newNameToUse = resultName; + if (flags & NameOnly) { - int id = -1; - spItem->GetId(&id); - - bool isFolder = false; - bool isSubFolderContent = false; - spItem->GetIsFolder(&isFolder); - spItem->GetIsSubFolderContent(&isSubFolderContent); - if ((isFolder && (flags & PowerRenameFlags::ExcludeFolders)) || - (!isFolder && (flags & PowerRenameFlags::ExcludeFiles)) || - (isSubFolderContent && (flags & PowerRenameFlags::ExcludeSubfolders))) + StringCchPrintf(resultName, ARRAYSIZE(resultName), L"%s%s", newName, fs::path(originalName).extension().c_str()); + } + else if (flags & ExtensionOnly) + { + std::wstring extension = fs::path(originalName).extension().wstring(); + if (!extension.empty()) { - // Exclude this item from renaming. Ensure new name is cleared. - spItem->PutNewName(nullptr); - - // Send the manager thread the item processed message - PostMessage(pwtd->hwndManager, SRM_REGEX_ITEM_UPDATED, GetCurrentThreadId(), id); - - continue; + StringCchPrintf(resultName, ARRAYSIZE(resultName), L"%s.%s", fs::path(originalName).stem().c_str(), newName); } - - PWSTR originalName = nullptr; - if (SUCCEEDED(spItem->GetOriginalName(&originalName))) + else { - PWSTR currentNewName = nullptr; - spItem->GetNewName(¤tNewName); - - wchar_t sourceName[MAX_PATH] = { 0 }; - if (flags & NameOnly) - { - StringCchCopy(sourceName, ARRAYSIZE(sourceName), fs::path(originalName).stem().c_str()); - } - else if (flags & ExtensionOnly) - { - std::wstring extension = fs::path(originalName).extension().wstring(); - if (!extension.empty() && extension.front() == '.') - { - extension = extension.erase(0, 1); - } - StringCchCopy(sourceName, ARRAYSIZE(sourceName), extension.c_str()); - } - else - { - StringCchCopy(sourceName, ARRAYSIZE(sourceName), originalName); - } - - wchar_t newReplaceTerm[MAX_PATH] = { 0 }; - PWSTR replaceTerm = nullptr; - SYSTEMTIME LocalTime; - - if (SUCCEEDED(spRenameRegEx->GetReplaceTerm(&replaceTerm)) && isFileAttributesUsed(replaceTerm)) - { - if (SUCCEEDED(spItem->GetDate(&LocalTime))) - { - if (SUCCEEDED(GetDatedFileName(newReplaceTerm, ARRAYSIZE(newReplaceTerm), replaceTerm, LocalTime))) - { - spRenameRegEx->PutReplaceTerm(newReplaceTerm); - } - } - } - - PWSTR newName = nullptr; - // Failure here means we didn't match anything or had nothing to match - // Call put_newName with null in that case to reset it - spRenameRegEx->Replace(sourceName, &newName); - - spRenameRegEx->PutReplaceTerm(replaceTerm); - - wchar_t resultName[MAX_PATH] = { 0 }; - - PWSTR newNameToUse = nullptr; - - // newName == nullptr likely means we have an empty search string. We should leave newNameToUse - // as nullptr so we clear the renamed column - // Except string transformation is selected. - - if (newName == nullptr && (flags & Uppercase || flags & Lowercase || flags & Titlecase)) - { - SHStrDup(sourceName, &newName); - } - - if (newName != nullptr) - { - newNameToUse = resultName; - if (flags & NameOnly) - { - StringCchPrintf(resultName, ARRAYSIZE(resultName), L"%s%s", newName, fs::path(originalName).extension().c_str()); - } - else if (flags & ExtensionOnly) - { - std::wstring extension = fs::path(originalName).extension().wstring(); - if (!extension.empty()) - { - StringCchPrintf(resultName, ARRAYSIZE(resultName), L"%s.%s", fs::path(originalName).stem().c_str(), newName); - } - else - { - StringCchCopy(resultName, ARRAYSIZE(resultName), originalName); - } - } - else - { - StringCchCopy(resultName, ARRAYSIZE(resultName), newName); - } - } - - wchar_t trimmedName[MAX_PATH] = { 0 }; - if (newNameToUse != nullptr && SUCCEEDED(GetTrimmedFileName(trimmedName, ARRAYSIZE(trimmedName), newNameToUse))) - { - newNameToUse = trimmedName; - } - - wchar_t transformedName[MAX_PATH] = { 0 }; - if (newNameToUse != nullptr && (flags & Uppercase || flags & Lowercase || flags & Titlecase)) - { - if (SUCCEEDED(GetTransformedFileName(transformedName, ARRAYSIZE(transformedName), newNameToUse, flags))) - { - newNameToUse = transformedName; - } - } - - // No change from originalName so set newName to - // null so we clear it from our UI as well. - if (lstrcmp(originalName, newNameToUse) == 0) - { - newNameToUse = nullptr; - } - - wchar_t uniqueName[MAX_PATH] = { 0 }; - if (newNameToUse != nullptr && (flags & EnumerateItems)) - { - unsigned long countUsed = 0; - if (GetEnumeratedFileName(uniqueName, ARRAYSIZE(uniqueName), newNameToUse, nullptr, itemEnumIndex, &countUsed)) - { - newNameToUse = uniqueName; - } - itemEnumIndex++; - } - - spItem->PutNewName(newNameToUse); - - // Was there a change? - if (lstrcmp(currentNewName, newNameToUse) != 0) - { - // Send the manager thread the item processed message - PostMessage(pwtd->hwndManager, SRM_REGEX_ITEM_UPDATED, GetCurrentThreadId(), id); - } - - CoTaskMemFree(newName); - CoTaskMemFree(replaceTerm); - CoTaskMemFree(currentNewName); - CoTaskMemFree(originalName); + StringCchCopy(resultName, ARRAYSIZE(resultName), originalName); } } + else + { + StringCchCopy(resultName, ARRAYSIZE(resultName), newName); + } } + + wchar_t trimmedName[MAX_PATH] = { 0 }; + if (newNameToUse != nullptr) + { + winrt::check_hresult(GetTrimmedFileName(trimmedName, ARRAYSIZE(trimmedName), newNameToUse)); + newNameToUse = trimmedName; + } + + wchar_t transformedName[MAX_PATH] = { 0 }; + if (newNameToUse != nullptr && (flags & Uppercase || flags & Lowercase || flags & Titlecase)) + { + winrt::check_hresult(GetTransformedFileName(transformedName, ARRAYSIZE(transformedName), newNameToUse, flags)); + newNameToUse = transformedName; + } + + // No change from originalName so set newName to + // null so we clear it from our UI as well. + if (lstrcmp(originalName, newNameToUse) == 0) + { + newNameToUse = nullptr; + } + + wchar_t uniqueName[MAX_PATH] = { 0 }; + if (newNameToUse != nullptr && (flags & EnumerateItems)) + { + unsigned long countUsed = 0; + if (GetEnumeratedFileName(uniqueName, ARRAYSIZE(uniqueName), newNameToUse, nullptr, itemEnumIndex, &countUsed)) + { + newNameToUse = uniqueName; + } + itemEnumIndex++; + } + + winrt::check_hresult(spItem->PutNewName(newNameToUse)); + + // Was there a change? + if (lstrcmp(currentNewName, newNameToUse) != 0) + { + // Send the manager thread the item processed message + PostMessage(pwtd->hwndManager, SRM_REGEX_ITEM_UPDATED, GetCurrentThreadId(), id); + } + CoTaskMemFree(newName); + CoTaskMemFree(currentNewName); + CoTaskMemFree(originalName); } + CoTaskMemFree(replaceTerm); } // Send the manager thread the completion message PostMessage(pwtd->hwndManager, SRM_REGEX_COMPLETE, GetCurrentThreadId(), 0); delete pwtd; + } CoUninitialize(); } + catch (...) + { + MessageBox(NULL, L"RegexWorkerThread failed to execute.\nPlease report the bug to https://aka.ms/powerToysReportBug", L"PowerRename Error", MB_OK); + } return 0; } diff --git a/src/modules/powerrename/lib/PowerRenameManager.h b/src/modules/powerrename/lib/PowerRenameManager.h index aff5819d01..c14f563432 100644 --- a/src/modules/powerrename/lib/PowerRenameManager.h +++ b/src/modules/powerrename/lib/PowerRenameManager.h @@ -46,6 +46,7 @@ public: IFACEMETHODIMP OnSearchTermChanged(_In_ PCWSTR searchTerm); IFACEMETHODIMP OnReplaceTermChanged(_In_ PCWSTR replaceTerm); IFACEMETHODIMP OnFlagsChanged(_In_ DWORD flags); + IFACEMETHODIMP OnFileTimeChanged(_In_ SYSTEMTIME fileTime); static HRESULT s_CreateInstance(_Outptr_ IPowerRenameManager** ppsrm); diff --git a/src/modules/powerrename/lib/PowerRenameRegEx.cpp b/src/modules/powerrename/lib/PowerRenameRegEx.cpp index 1bafd89d76..457d395577 100644 --- a/src/modules/powerrename/lib/PowerRenameRegEx.cpp +++ b/src/modules/powerrename/lib/PowerRenameRegEx.cpp @@ -5,7 +5,7 @@ #include #include #include - +#include using namespace std; using std::regex_error; @@ -76,8 +76,8 @@ IFACEMETHODIMP CPowerRenameRegEx::UnAdvise(_In_ DWORD cookie) IFACEMETHODIMP CPowerRenameRegEx::GetSearchTerm(_Outptr_ PWSTR* searchTerm) { *searchTerm = nullptr; - HRESULT hr = m_searchTerm ? S_OK : E_FAIL; - if (SUCCEEDED(hr)) + HRESULT hr = S_OK; + if (m_searchTerm) { CSRWSharedAutoLock lock(&m_lock); hr = SHStrDup(m_searchTerm, searchTerm); @@ -88,8 +88,8 @@ IFACEMETHODIMP CPowerRenameRegEx::GetSearchTerm(_Outptr_ PWSTR* searchTerm) IFACEMETHODIMP CPowerRenameRegEx::PutSearchTerm(_In_ PCWSTR searchTerm) { bool changed = false; - HRESULT hr = searchTerm ? S_OK : E_INVALIDARG; - if (SUCCEEDED(hr)) + HRESULT hr = S_OK; + if (searchTerm) { CSRWExclusiveAutoLock lock(&m_lock); if (m_searchTerm == nullptr || lstrcmp(searchTerm, m_searchTerm) != 0) @@ -111,8 +111,8 @@ IFACEMETHODIMP CPowerRenameRegEx::PutSearchTerm(_In_ PCWSTR searchTerm) IFACEMETHODIMP CPowerRenameRegEx::GetReplaceTerm(_Outptr_ PWSTR* replaceTerm) { *replaceTerm = nullptr; - HRESULT hr = m_replaceTerm ? S_OK : E_FAIL; - if (SUCCEEDED(hr)) + HRESULT hr = S_OK; + if (m_replaceTerm) { CSRWSharedAutoLock lock(&m_lock); hr = SHStrDup(m_replaceTerm, replaceTerm); @@ -123,8 +123,8 @@ IFACEMETHODIMP CPowerRenameRegEx::GetReplaceTerm(_Outptr_ PWSTR* replaceTerm) IFACEMETHODIMP CPowerRenameRegEx::PutReplaceTerm(_In_ PCWSTR replaceTerm) { bool changed = false; - HRESULT hr = replaceTerm ? S_OK : E_INVALIDARG; - if (SUCCEEDED(hr)) + HRESULT hr = S_OK; + if (replaceTerm) { CSRWExclusiveAutoLock lock(&m_lock); if (m_replaceTerm == nullptr || lstrcmp(replaceTerm, m_replaceTerm) != 0) @@ -159,13 +159,45 @@ IFACEMETHODIMP CPowerRenameRegEx::PutFlags(_In_ DWORD flags) return S_OK; } +IFACEMETHODIMP CPowerRenameRegEx::PutFileTime(_In_ SYSTEMTIME fileTime) +{ + union timeunion + { + FILETIME fileTime; + ULARGE_INTEGER ul; + }; + + timeunion ft1; + timeunion ft2; + + SystemTimeToFileTime(&m_fileTime, &ft1.fileTime); + SystemTimeToFileTime(&fileTime, &ft2.fileTime); + + if (ft2.ul.QuadPart != ft1.ul.QuadPart) + { + m_fileTime = fileTime; + m_useFileTime = true; + _OnFileTimeChanged(); + } + return S_OK; +} + +IFACEMETHODIMP CPowerRenameRegEx::ResetFileTime() +{ + SYSTEMTIME ZERO = { 0 }; + m_fileTime = ZERO; + m_useFileTime = false; + _OnFileTimeChanged(); + return S_OK; +} + HRESULT CPowerRenameRegEx::s_CreateInstance(_Outptr_ IPowerRenameRegEx** renameRegEx) { *renameRegEx = nullptr; CPowerRenameRegEx *newRenameRegEx = new CPowerRenameRegEx(); - HRESULT hr = newRenameRegEx ? S_OK : E_OUTOFMEMORY; - if (SUCCEEDED(hr)) + HRESULT hr = E_OUTOFMEMORY; + if (newRenameRegEx) { hr = newRenameRegEx->QueryInterface(IID_PPV_ARGS(renameRegEx)); newRenameRegEx->Release(); @@ -194,73 +226,90 @@ HRESULT CPowerRenameRegEx::Replace(_In_ PCWSTR source, _Outptr_ PWSTR* result) *result = nullptr; CSRWSharedAutoLock lock(&m_lock); - HRESULT hr = (source && wcslen(source) > 0 && m_searchTerm && wcslen(m_searchTerm) > 0) ? S_OK : E_INVALIDARG; - if (SUCCEEDED(hr)) + HRESULT hr = S_OK; + if (!(m_searchTerm && wcslen(m_searchTerm) > 0 && source && wcslen(source) > 0)) { - wstring res = source; - try + return hr; + } + wstring res = source; + try + { + // TODO: creating the regex could be costly. May want to cache this. + wchar_t newReplaceTerm[MAX_PATH] = { 0 }; + bool fileTimeErrorOccurred = false; + if (m_useFileTime) { - // TODO: creating the regex could be costly. May want to cache this. - std::wstring sourceToUse(source); - std::wstring searchTerm(m_searchTerm); - std::wstring replaceTerm(m_replaceTerm ? wstring(m_replaceTerm) : wstring(L"")); + if (FAILED(GetDatedFileName(newReplaceTerm, ARRAYSIZE(newReplaceTerm), m_replaceTerm, m_fileTime))) + fileTimeErrorOccurred = true; + } - replaceTerm = regex_replace(replaceTerm, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$[0]"), L"$1$$$0"); - replaceTerm = regex_replace(replaceTerm, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$([1-9])"), L"$1$0$4"); + std::wstring sourceToUse(source); + std::wstring searchTerm(m_searchTerm); + std::wstring replaceTerm(L""); + if (m_useFileTime && !fileTimeErrorOccurred) + { + replaceTerm = wstring(newReplaceTerm); + } + else if (m_replaceTerm) + { + replaceTerm = wstring(m_replaceTerm); + } - if (m_flags & UseRegularExpressions) + replaceTerm = regex_replace(replaceTerm, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$[0]"), L"$1$$$0"); + replaceTerm = regex_replace(replaceTerm, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$([1-9])"), L"$1$0$4"); + + if (m_flags & UseRegularExpressions) + { + if (_useBoostLib) { - if (_useBoostLib) + boost::wregex pattern(m_searchTerm, (!(m_flags & CaseSensitive)) ? boost::regex::icase | boost::regex::ECMAScript : boost::regex::ECMAScript); + if (m_flags & MatchAllOccurences) { - boost::wregex pattern(m_searchTerm, (!(m_flags & CaseSensitive)) ? boost::regex::icase | boost::regex::ECMAScript : boost::regex::ECMAScript); - if (m_flags & MatchAllOccurences) - { - res = boost::regex_replace(wstring(source), pattern, replaceTerm); - } - else - { - res = boost::regex_replace(wstring(source), pattern, replaceTerm, boost::regex_constants::format_first_only); - } + res = boost::regex_replace(wstring(source), pattern, replaceTerm); } else { - std::wregex pattern(m_searchTerm, (!(m_flags & CaseSensitive)) ? regex_constants::icase | regex_constants::ECMAScript : regex_constants::ECMAScript); - if (m_flags & MatchAllOccurences) - { - res = regex_replace(wstring(source), pattern, replaceTerm); - } - else - { - res = regex_replace(wstring(source), pattern, replaceTerm, regex_constants::format_first_only); - } + res = boost::regex_replace(wstring(source), pattern, replaceTerm, boost::regex_constants::format_first_only); } } else { - // Simple search and replace - size_t pos = 0; - do + std::wregex pattern(m_searchTerm, (!(m_flags & CaseSensitive)) ? regex_constants::icase | regex_constants::ECMAScript : regex_constants::ECMAScript); + if (m_flags & MatchAllOccurences) { - pos = _Find(sourceToUse, searchTerm, (!(m_flags & CaseSensitive)), pos); - if (pos != std::string::npos) - { - res = sourceToUse.replace(pos, searchTerm.length(), replaceTerm); - pos += replaceTerm.length(); - } - - if (!(m_flags & MatchAllOccurences)) - { - break; - } - } while (pos != std::string::npos); + res = regex_replace(wstring(source), pattern, replaceTerm); + } + else + { + res = regex_replace(wstring(source), pattern, replaceTerm, regex_constants::format_first_only); + } } - - hr = SHStrDup(res.c_str(), result); } - catch (regex_error e) + else { - hr = E_FAIL; + // Simple search and replace + size_t pos = 0; + do + { + pos = _Find(sourceToUse, searchTerm, (!(m_flags & CaseSensitive)), pos); + if (pos != std::string::npos) + { + res = sourceToUse.replace(pos, searchTerm.length(), replaceTerm); + pos += replaceTerm.length(); + } + + if (!(m_flags & MatchAllOccurences)) + { + break; + } + } while (pos != std::string::npos); } + + hr = SHStrDup(res.c_str(), result); + } + catch (regex_error e) + { + hr = E_FAIL; } return hr; } @@ -316,3 +365,16 @@ void CPowerRenameRegEx::_OnFlagsChanged() } } } + +void CPowerRenameRegEx::_OnFileTimeChanged() +{ + CSRWSharedAutoLock lock(&m_lockEvents); + + for (auto it : m_renameRegExEvents) + { + if (it.pEvents) + { + it.pEvents->OnFileTimeChanged(m_fileTime); + } + } +} diff --git a/src/modules/powerrename/lib/PowerRenameRegEx.h b/src/modules/powerrename/lib/PowerRenameRegEx.h index 1bef0c2885..9997ea8cab 100644 --- a/src/modules/powerrename/lib/PowerRenameRegEx.h +++ b/src/modules/powerrename/lib/PowerRenameRegEx.h @@ -25,6 +25,8 @@ public: IFACEMETHODIMP PutReplaceTerm(_In_ PCWSTR replaceTerm); IFACEMETHODIMP GetFlags(_Out_ DWORD* flags); IFACEMETHODIMP PutFlags(_In_ DWORD flags); + IFACEMETHODIMP PutFileTime(_In_ SYSTEMTIME fileTime); + IFACEMETHODIMP ResetFileTime(); IFACEMETHODIMP Replace(_In_ PCWSTR source, _Outptr_ PWSTR* result); static HRESULT s_CreateInstance(_Outptr_ IPowerRenameRegEx **renameRegEx); @@ -36,6 +38,7 @@ protected: void _OnSearchTermChanged(); void _OnReplaceTermChanged(); void _OnFlagsChanged(); + void _OnFileTimeChanged(); size_t _Find(std::wstring data, std::wstring toSearch, bool caseInsensitive, size_t pos); @@ -44,6 +47,9 @@ protected: PWSTR m_searchTerm = nullptr; PWSTR m_replaceTerm = nullptr; + SYSTEMTIME m_fileTime = {0}; + bool m_useFileTime = false; + CSRWLock m_lock; CSRWLock m_lockEvents; diff --git a/src/modules/powerrename/lib/Settings.cpp b/src/modules/powerrename/lib/Settings.cpp index 961c8057ae..ca8104e1b8 100644 --- a/src/modules/powerrename/lib/Settings.cpp +++ b/src/modules/powerrename/lib/Settings.cpp @@ -312,15 +312,16 @@ HRESULT CRenameMRU::CreateInstance(_In_ const std::wstring& filePath, _In_ const { *ppUnk = nullptr; unsigned int maxMRUSize = CSettingsInstance().GetMaxMRUSize(); - HRESULT hr = maxMRUSize > 0 ? S_OK : E_FAIL; - if (SUCCEEDED(hr)) + HRESULT hr = E_FAIL; + if (maxMRUSize > 0) { CRenameMRU* renameMRU = new CRenameMRU(maxMRUSize, filePath, regPath); - hr = renameMRU ? S_OK : E_OUTOFMEMORY; - if (SUCCEEDED(hr)) + hr = E_OUTOFMEMORY; + if (renameMRU) { renameMRU->QueryInterface(IID_PPV_ARGS(ppUnk)); renameMRU->Release(); + hr = S_OK; } } diff --git a/src/modules/powerrename/ui/PowerRenameUI.cpp b/src/modules/powerrename/ui/PowerRenameUI.cpp index 83abdd4850..a999566e4c 100644 --- a/src/modules/powerrename/ui/PowerRenameUI.cpp +++ b/src/modules/powerrename/ui/PowerRenameUI.cpp @@ -120,8 +120,8 @@ HRESULT CPowerRenameUI::s_CreateInstance(_In_ IPowerRenameManager* psrm, _In_opt { *ppsrui = nullptr; CPowerRenameUI* prui = new CPowerRenameUI(); - HRESULT hr = prui ? S_OK : E_OUTOFMEMORY; - if (SUCCEEDED(hr)) + HRESULT hr = E_OUTOFMEMORY; + if (prui) { // Pass the IPowerRenameManager to the IPowerRenameUI so it can subscribe to events hr = prui->_Initialize(psrm, dataSource, enableDragDrop); diff --git a/src/modules/powerrename/unittests/MockPowerRenameItem.cpp b/src/modules/powerrename/unittests/MockPowerRenameItem.cpp index 9a338d8fca..f55fced132 100644 --- a/src/modules/powerrename/unittests/MockPowerRenameItem.cpp +++ b/src/modules/powerrename/unittests/MockPowerRenameItem.cpp @@ -1,14 +1,14 @@ #include "pch.h" #include "MockPowerRenameItem.h" -HRESULT CMockPowerRenameItem::CreateInstance(_In_opt_ PCWSTR path, _In_opt_ PCWSTR originalName, _In_ UINT depth, _In_ bool isFolder, _Outptr_ IPowerRenameItem** ppItem) +HRESULT CMockPowerRenameItem::CreateInstance(_In_opt_ PCWSTR path, _In_opt_ PCWSTR originalName, _In_ UINT depth, _In_ bool isFolder, _In_ SYSTEMTIME time, _Outptr_ IPowerRenameItem** ppItem) { *ppItem = nullptr; CMockPowerRenameItem* newItem = new CMockPowerRenameItem(); - HRESULT hr = newItem ? S_OK : E_OUTOFMEMORY; - if (SUCCEEDED(hr)) + HRESULT hr = E_OUTOFMEMORY; + if (newItem) { - newItem->Init(path, originalName, depth, isFolder); + newItem->Init(path, originalName, depth, isFolder, time); hr = newItem->QueryInterface(IID_PPV_ARGS(ppItem)); newItem->Release(); } @@ -16,7 +16,7 @@ HRESULT CMockPowerRenameItem::CreateInstance(_In_opt_ PCWSTR path, _In_opt_ PCWS return hr; } -void CMockPowerRenameItem::Init(_In_opt_ PCWSTR path, _In_opt_ PCWSTR originalName, _In_ UINT depth, _In_ bool isFolder) +void CMockPowerRenameItem::Init(_In_opt_ PCWSTR path, _In_opt_ PCWSTR originalName, _In_ UINT depth, _In_ bool isFolder, _In_ SYSTEMTIME time) { if (path != nullptr) { @@ -30,4 +30,6 @@ void CMockPowerRenameItem::Init(_In_opt_ PCWSTR path, _In_opt_ PCWSTR originalNa m_depth = depth; m_isFolder = isFolder; + m_time = time; + m_isTimeParsed = true; } diff --git a/src/modules/powerrename/unittests/MockPowerRenameItem.h b/src/modules/powerrename/unittests/MockPowerRenameItem.h index 9525a4db7d..10bd022700 100644 --- a/src/modules/powerrename/unittests/MockPowerRenameItem.h +++ b/src/modules/powerrename/unittests/MockPowerRenameItem.h @@ -7,6 +7,6 @@ class CMockPowerRenameItem : public CPowerRenameItem { public: - static HRESULT CreateInstance(_In_opt_ PCWSTR path, _In_opt_ PCWSTR originalName, _In_ UINT depth, _In_ bool isFolder, _Outptr_ IPowerRenameItem** ppItem); - void Init(_In_opt_ PCWSTR path, _In_opt_ PCWSTR originalName, _In_ UINT depth, _In_ bool isFolder); -}; \ No newline at end of file + static HRESULT CreateInstance(_In_opt_ PCWSTR path, _In_opt_ PCWSTR originalName, _In_ UINT depth, _In_ bool isFolder, _In_ SYSTEMTIME time, _Outptr_ IPowerRenameItem** ppItem); + void Init(_In_opt_ PCWSTR path, _In_opt_ PCWSTR originalName, _In_ UINT depth, _In_ bool isFolder, _In_ SYSTEMTIME time); +}; diff --git a/src/modules/powerrename/unittests/MockPowerRenameManagerEvents.cpp b/src/modules/powerrename/unittests/MockPowerRenameManagerEvents.cpp index db09e2727d..b69efae3bf 100644 --- a/src/modules/powerrename/unittests/MockPowerRenameManagerEvents.cpp +++ b/src/modules/powerrename/unittests/MockPowerRenameManagerEvents.cpp @@ -81,8 +81,8 @@ HRESULT CMockPowerRenameManagerEvents::s_CreateInstance(_In_ IPowerRenameManager { *ppsrui = nullptr; CMockPowerRenameManagerEvents* events = new CMockPowerRenameManagerEvents(); - HRESULT hr = events != nullptr ? S_OK : E_OUTOFMEMORY; - if (SUCCEEDED(hr)) + HRESULT hr = E_OUTOFMEMORY; + if (events != nullptr) { hr = events->QueryInterface(IID_PPV_ARGS(ppsrui)); events->Release(); diff --git a/src/modules/powerrename/unittests/MockPowerRenameRegExEvents.cpp b/src/modules/powerrename/unittests/MockPowerRenameRegExEvents.cpp index f76bb4e30b..a882802499 100644 --- a/src/modules/powerrename/unittests/MockPowerRenameRegExEvents.cpp +++ b/src/modules/powerrename/unittests/MockPowerRenameRegExEvents.cpp @@ -56,12 +56,18 @@ IFACEMETHODIMP CMockPowerRenameRegExEvents::OnFlagsChanged(_In_ DWORD flags) return S_OK; } +IFACEMETHODIMP CMockPowerRenameRegExEvents::OnFileTimeChanged(_In_ SYSTEMTIME fileTime) +{ + m_fileTime = fileTime; + return S_OK; +} + HRESULT CMockPowerRenameRegExEvents::s_CreateInstance(_Outptr_ IPowerRenameRegExEvents** ppsrree) { *ppsrree = nullptr; CMockPowerRenameRegExEvents* psrree = new CMockPowerRenameRegExEvents(); - HRESULT hr = psrree ? S_OK : E_OUTOFMEMORY; - if (SUCCEEDED(hr)) + HRESULT hr = E_OUTOFMEMORY; + if (psrree) { hr = psrree->QueryInterface(IID_PPV_ARGS(ppsrree)); psrree->Release(); diff --git a/src/modules/powerrename/unittests/MockPowerRenameRegExEvents.h b/src/modules/powerrename/unittests/MockPowerRenameRegExEvents.h index 89dd8720f4..f65108b123 100644 --- a/src/modules/powerrename/unittests/MockPowerRenameRegExEvents.h +++ b/src/modules/powerrename/unittests/MockPowerRenameRegExEvents.h @@ -18,6 +18,7 @@ public: IFACEMETHODIMP OnSearchTermChanged(_In_ PCWSTR searchTerm); IFACEMETHODIMP OnReplaceTermChanged(_In_ PCWSTR replaceTerm); IFACEMETHODIMP OnFlagsChanged(_In_ DWORD flags); + IFACEMETHODIMP OnFileTimeChanged(_In_ SYSTEMTIME fileTime); static HRESULT s_CreateInstance(_Outptr_ IPowerRenameRegExEvents** ppsrree); @@ -35,5 +36,6 @@ public: PWSTR m_searchTerm = nullptr; PWSTR m_replaceTerm = nullptr; DWORD m_flags = 0; + SYSTEMTIME m_fileTime = { 0 }; long m_refCount; }; diff --git a/src/modules/powerrename/unittests/PowerRenameManagerTests.cpp b/src/modules/powerrename/unittests/PowerRenameManagerTests.cpp index cdac42a4a4..dad75999db 100644 --- a/src/modules/powerrename/unittests/PowerRenameManagerTests.cpp +++ b/src/modules/powerrename/unittests/PowerRenameManagerTests.cpp @@ -32,7 +32,7 @@ namespace PowerRenameManagerTests int depth; }; - void RenameHelper(_In_ rename_pairs * renamePairs, _In_ int numPairs, _In_ std::wstring searchTerm, _In_ std::wstring replaceTerm, SYSTEMTIME LocalTime, _In_ DWORD flags) + void RenameHelper(_In_ rename_pairs * renamePairs, _In_ int numPairs, _In_ std::wstring searchTerm, _In_ std::wstring replaceTerm, SYSTEMTIME fileTime, _In_ DWORD flags) { // Create a single item (in a temp directory) and verify rename works as expected CTestFileHelper testFileHelper; @@ -59,12 +59,11 @@ namespace PowerRenameManagerTests for (int i = 0; i < numPairs; i++) { CComPtr item; - CMockPowerRenameItem::CreateInstance(testFileHelper.GetFullPath( - renamePairs[i].originalName) - .c_str(), + CMockPowerRenameItem::CreateInstance(testFileHelper.GetFullPath(renamePairs[i].originalName).c_str(), renamePairs[i].originalName.c_str(), renamePairs[i].depth, !renamePairs[i].isFile, + fileTime, &item); int itemId = 0; @@ -84,20 +83,21 @@ namespace PowerRenameManagerTests Assert::IsTrue(mgr->GetRenameRegEx(&renRegEx) == S_OK); renRegEx->PutFlags(flags); renRegEx->PutSearchTerm(searchTerm.c_str()); - if (isFileAttributesUsed(replaceTerm.c_str()) && SUCCEEDED(GetDatedFileName(newReplaceTerm, ARRAYSIZE(newReplaceTerm), replaceTerm.c_str(), LocalTime))) - { - renRegEx->PutReplaceTerm(newReplaceTerm); - } - else - { - renRegEx->PutReplaceTerm(replaceTerm.c_str()); - } - Sleep(1000); + renRegEx->PutReplaceTerm(replaceTerm.c_str()); // Perform the rename - Assert::IsTrue(mgr->Rename(0) == S_OK); + bool replaceSuccess = false; + for (int step = 0; step < 20; step++) + { + replaceSuccess = mgr->Rename(0) == S_OK; + if (replaceSuccess) + { + break; + } + Sleep(10); + } - Sleep(1000); + Assert::IsTrue(replaceSuccess); // Verify the rename occurred for (int i = 0; i < numPairs; i++) @@ -128,7 +128,7 @@ namespace PowerRenameManagerTests CComPtr mgr; Assert::IsTrue(CPowerRenameManager::s_CreateInstance(&mgr) == S_OK); CComPtr item; - CMockPowerRenameItem::CreateInstance(L"foo", L"foo", 0, false, &item); + CMockPowerRenameItem::CreateInstance(L"foo", L"foo", 0, false, SYSTEMTIME{0}, &item); mgr->AddItem(item); Assert::IsTrue(mgr->Shutdown() == S_OK); } @@ -143,7 +143,7 @@ namespace PowerRenameManagerTests DWORD cookie = 0; Assert::IsTrue(mgr->Advise(mgrEvents, &cookie) == S_OK); CComPtr item; - CMockPowerRenameItem::CreateInstance(L"foo", L"foo", 0, false, &item); + CMockPowerRenameItem::CreateInstance(L"foo", L"foo", 0, false, SYSTEMTIME{0}, &item); int itemId = 0; Assert::IsTrue(item->GetId(&itemId) == S_OK); mgr->AddItem(item); @@ -290,6 +290,7 @@ namespace PowerRenameManagerTests RenameHelper(renamePairs, ARRAYSIZE(renamePairs), L"foo", L"bar", SYSTEMTIME{ 2020, 7, 3, 22, 15, 6, 42, 453 }, DEFAULT_FLAGS | Lowercase | ExtensionOnly); } + TEST_METHOD (VerifyFileAttributesNoPadding) { rename_pairs renamePairs[] = { @@ -311,26 +312,26 @@ namespace PowerRenameManagerTests TEST_METHOD (VerifyFileAttributesMonthandDayNames) { std::locale::global(std::locale("")); - SYSTEMTIME LocalTime = { 2020, 1, 3, 1, 15, 6, 42, 453 }; + SYSTEMTIME fileTime = { 2020, 1, 3, 1, 15, 6, 42, 453 }; wchar_t localeName[LOCALE_NAME_MAX_LENGTH]; wchar_t result[MAX_PATH] = L"bar"; wchar_t formattedDate[MAX_PATH]; if (GetUserDefaultLocaleName(localeName, LOCALE_NAME_MAX_LENGTH) == 0) StringCchCopy(localeName, LOCALE_NAME_MAX_LENGTH, L"en_US"); - GetDateFormatEx(localeName, NULL, &LocalTime, L"MMM", formattedDate, MAX_PATH, NULL); + GetDateFormatEx(localeName, NULL, &fileTime, L"MMM", formattedDate, MAX_PATH, NULL); formattedDate[0] = towupper(formattedDate[0]); StringCchPrintf(result, MAX_PATH, TEXT("%s%s"), result, formattedDate); - - GetDateFormatEx(localeName, NULL, &LocalTime, L"MMMM", formattedDate, MAX_PATH, NULL); + + GetDateFormatEx(localeName, NULL, &fileTime, L"MMMM", formattedDate, MAX_PATH, NULL); formattedDate[0] = towupper(formattedDate[0]); StringCchPrintf(result, MAX_PATH, TEXT("%s-%s"), result, formattedDate); - GetDateFormatEx(localeName, NULL, &LocalTime, L"ddd", formattedDate, MAX_PATH, NULL); + GetDateFormatEx(localeName, NULL, &fileTime, L"ddd", formattedDate, MAX_PATH, NULL); formattedDate[0] = towupper(formattedDate[0]); StringCchPrintf(result, MAX_PATH, TEXT("%s-%s"), result, formattedDate); - GetDateFormatEx(localeName, NULL, &LocalTime, L"dddd", formattedDate, MAX_PATH, NULL); + GetDateFormatEx(localeName, NULL, &fileTime, L"dddd", formattedDate, MAX_PATH, NULL); formattedDate[0] = towupper(formattedDate[0]); StringCchPrintf(result, MAX_PATH, TEXT("%s-%s"), result, formattedDate); diff --git a/src/modules/powerrename/unittests/PowerRenameRegExBoostTests.cpp b/src/modules/powerrename/unittests/PowerRenameRegExBoostTests.cpp index 9fe33b3a18..2436683005 100644 --- a/src/modules/powerrename/unittests/PowerRenameRegExBoostTests.cpp +++ b/src/modules/powerrename/unittests/PowerRenameRegExBoostTests.cpp @@ -59,7 +59,7 @@ TEST_METHOD(ReplaceNoSearchOrReplaceTerm) CComPtr renameRegEx; Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK); PWSTR result = nullptr; - Assert::IsTrue(renameRegEx->Replace(L"foobar", &result) != S_OK); + Assert::IsTrue(renameRegEx->Replace(L"foobar", &result) == S_OK); Assert::IsTrue(result == nullptr); CoTaskMemFree(result); } @@ -427,6 +427,8 @@ TEST_METHOD(VerifyEventsFire) Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK); Assert::IsTrue(renameRegEx->PutSearchTerm(L"FOO") == S_OK); Assert::IsTrue(renameRegEx->PutReplaceTerm(L"BAR") == S_OK); + Assert::IsTrue(renameRegEx->PutFileTime(SYSTEMTIME{0}) == S_OK); + Assert::IsTrue(renameRegEx->ResetFileTime() == S_OK); Assert::IsTrue(lstrcmpi(L"FOO", mockEvents->m_searchTerm) == 0); Assert::IsTrue(lstrcmpi(L"BAR", mockEvents->m_replaceTerm) == 0); Assert::IsTrue(flags == mockEvents->m_flags); diff --git a/src/modules/powerrename/unittests/PowerRenameRegExTests.cpp b/src/modules/powerrename/unittests/PowerRenameRegExTests.cpp index 356dbe42b9..3949c584d1 100644 --- a/src/modules/powerrename/unittests/PowerRenameRegExTests.cpp +++ b/src/modules/powerrename/unittests/PowerRenameRegExTests.cpp @@ -53,7 +53,7 @@ TEST_METHOD(ReplaceNoSearchOrReplaceTerm) CComPtr renameRegEx; Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK); PWSTR result = nullptr; - Assert::IsTrue(renameRegEx->Replace(L"foobar", &result) != S_OK); + Assert::IsTrue(renameRegEx->Replace(L"foobar", &result) == S_OK); Assert::IsTrue(result == nullptr); CoTaskMemFree(result); } @@ -369,6 +369,103 @@ TEST_METHOD(VerifyHandleCapturingGroups) } } +TEST_METHOD (VerifyFileAttributesNoPadding) +{ + CComPtr renameRegEx; + Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK); + DWORD flags = MatchAllOccurences | UseRegularExpressions ; + SYSTEMTIME fileTime = SYSTEMTIME{ 2020, 7, 3, 22, 15, 6, 42, 453 }; + Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK); + + SearchReplaceExpected sreTable[] = { + //search, replace, test, result + { L"foo", L"bar$YY-$M-$D-$h-$m-$s-$f", L"foo", L"bar20-7-22-15-6-42-4" }, + }; + + for (int i = 0; i < ARRAYSIZE(sreTable); i++) + { + PWSTR result = nullptr; + Assert::IsTrue(renameRegEx->PutSearchTerm(sreTable[i].search) == S_OK); + Assert::IsTrue(renameRegEx->PutReplaceTerm(sreTable[i].replace) == S_OK); + Assert::IsTrue(renameRegEx->PutFileTime(fileTime) == S_OK); + Assert::IsTrue(renameRegEx->Replace(sreTable[i].test, &result) == S_OK); + Assert::IsTrue(wcscmp(result, sreTable[i].expected) == 0); + CoTaskMemFree(result); + } +} + +TEST_METHOD (VerifyFileAttributesPadding) +{ + CComPtr renameRegEx; + Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK); + DWORD flags = MatchAllOccurences | UseRegularExpressions; + Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK); + SYSTEMTIME fileTime = SYSTEMTIME{ 2020, 7, 3, 22, 15, 6, 42, 453 }; + SearchReplaceExpected sreTable[] = { + //search, replace, test, result + { L"foo", L"bar$YYYY-$MM-$DD-$hh-$mm-$ss-$fff", L"foo", L"bar2020-07-22-15-06-42-453" }, + }; + + for (int i = 0; i < ARRAYSIZE(sreTable); i++) + { + PWSTR result = nullptr; + Assert::IsTrue(renameRegEx->PutSearchTerm(sreTable[i].search) == S_OK); + Assert::IsTrue(renameRegEx->PutReplaceTerm(sreTable[i].replace) == S_OK); + Assert::IsTrue(renameRegEx->PutFileTime(fileTime) == S_OK); + Assert::IsTrue(renameRegEx->Replace(sreTable[i].test, &result) == S_OK); + Assert::IsTrue(wcscmp(result, sreTable[i].expected) == 0); + CoTaskMemFree(result); + } +} + +TEST_METHOD (VerifyFileAttributesMonthandDayNames) +{ + CComPtr renameRegEx; + Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK); + DWORD flags = MatchAllOccurences | UseRegularExpressions; + Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK); + + std::locale::global(std::locale("")); + SYSTEMTIME fileTime = { 2020, 1, 3, 1, 15, 6, 42, 453 }; + wchar_t localeName[LOCALE_NAME_MAX_LENGTH]; + wchar_t result[MAX_PATH] = L"bar"; + wchar_t formattedDate[MAX_PATH]; + if (GetUserDefaultLocaleName(localeName, LOCALE_NAME_MAX_LENGTH) == 0) + StringCchCopy(localeName, LOCALE_NAME_MAX_LENGTH, L"en_US"); + + GetDateFormatEx(localeName, NULL, &fileTime, L"MMM", formattedDate, MAX_PATH, NULL); + formattedDate[0] = towupper(formattedDate[0]); + StringCchPrintf(result, MAX_PATH, TEXT("%s%s"), result, formattedDate); + + GetDateFormatEx(localeName, NULL, &fileTime, L"MMMM", formattedDate, MAX_PATH, NULL); + formattedDate[0] = towupper(formattedDate[0]); + StringCchPrintf(result, MAX_PATH, TEXT("%s-%s"), result, formattedDate); + + GetDateFormatEx(localeName, NULL, &fileTime, L"ddd", formattedDate, MAX_PATH, NULL); + formattedDate[0] = towupper(formattedDate[0]); + StringCchPrintf(result, MAX_PATH, TEXT("%s-%s"), result, formattedDate); + + GetDateFormatEx(localeName, NULL, &fileTime, L"dddd", formattedDate, MAX_PATH, NULL); + formattedDate[0] = towupper(formattedDate[0]); + StringCchPrintf(result, MAX_PATH, TEXT("%s-%s"), result, formattedDate); + + SearchReplaceExpected sreTable[] = { + //search, replace, test, result + { L"foo", L"bar$MMM-$MMMM-$DDD-$DDDD", L"foo", result }, + }; + + for (int i = 0; i < ARRAYSIZE(sreTable); i++) + { + PWSTR result = nullptr; + Assert::IsTrue(renameRegEx->PutSearchTerm(sreTable[i].search) == S_OK); + Assert::IsTrue(renameRegEx->PutReplaceTerm(sreTable[i].replace) == S_OK); + Assert::IsTrue(renameRegEx->PutFileTime(fileTime) == S_OK); + Assert::IsTrue(renameRegEx->Replace(sreTable[i].test, &result) == S_OK); + Assert::IsTrue(wcscmp(result, sreTable[i].expected) == 0); + CoTaskMemFree(result); + } +} + TEST_METHOD(VerifyLookbehindFails) { // Standard Library Regex Engine does not support lookbehind, thus test should fail. @@ -406,6 +503,8 @@ TEST_METHOD(VerifyEventsFire) Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK); Assert::IsTrue(renameRegEx->PutSearchTerm(L"FOO") == S_OK); Assert::IsTrue(renameRegEx->PutReplaceTerm(L"BAR") == S_OK); + Assert::IsTrue(renameRegEx->PutFileTime(SYSTEMTIME{ 0 }) == S_OK); + Assert::IsTrue(renameRegEx->ResetFileTime() == S_OK); Assert::IsTrue(lstrcmpi(L"FOO", mockEvents->m_searchTerm) == 0); Assert::IsTrue(lstrcmpi(L"BAR", mockEvents->m_replaceTerm) == 0); Assert::IsTrue(flags == mockEvents->m_flags);