[PowerRename] Fix tests inconsistency, improve test performance (#8129)

* Move retrieveing file attibutes to PowerRenameRegex
Move file attributes unit tests to PowerRenameRegexTests
Add file time field to MockPowerRenameItem

* Add file attributes unittests to PowerRenameManagerTests

* Change variable name

* Rearrange function arguments

* Check if file attributes are used only once

* Change variable name LocalTime -> fileTime, date -> time

* Set fileTime as a member of PowerRenameRegEx rather than passing as an argument

* Change function name isFileAttributesUsed() -> isFileTimeUsed()
Check before resetting fileTime

* Fix small bugs

* Fix typos

* Refactor for readability, move free calls to reachable places

* Fix search for area empty bug
searchTerm being empty is not an invalid argument rather it must return OK without any operation
Tests must check if Replace()  returns S_OK becuase later it checks its result

* Check return values of method calls in PowerRenameManager
Remove received argments checks from some methods because argument being null or empty string doesnt mean it is invalid or method fails

* Fix formatting. Remove overlooked comment. Fix error message.

* Change HRESULT declarations according to coding style

* Fix unhandled case. Refactor.
This commit is contained in:
Mehmet Murat Akburak 2020-12-14 12:28:12 +03:00 committed by GitHub
parent 4403876320
commit da22e21a0e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 552 additions and 334 deletions

View File

@ -2126,6 +2126,7 @@ tif
TILEDWINDOW
timeinfo
Timeline
timeunion
timeutil
titlecase
tlb
@ -2191,6 +2192,7 @@ uint
UIPI
UIs
ul
ULARGE
ULLONG
ulong
unchecks

View File

@ -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));

View File

@ -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;
}

View File

@ -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<IShellItemArray> spsia;
HRESULT hr = _GetShellItemArrayFromDataOject(dataSource, &spsia);
if (SUCCEEDED(hr))
HRESULT hr = E_FAIL;
if (SUCCEEDED(_GetShellItemArrayFromDataOject(dataSource, &spsia)))
{
CComPtr<IEnumShellItems> spesi;
hr = spsia->EnumItems(&spesi);
if (SUCCEEDED(hr))
if (SUCCEEDED(spsia->EnumItems(&spesi)))
{
hr = _ParseEnumItems(spesi, psrm);
}

View File

@ -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(

View File

@ -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;

View File

@ -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);

View File

@ -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;
};

View File

@ -8,6 +8,7 @@
#include "window_helpers.h"
#include <filesystem>
#include "trace.h"
#include <winrt/base.h>
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<WorkerThreadData*>(pv);
if (pwtd)
{
@ -823,192 +839,201 @@ DWORD WINAPI CPowerRenameManager::s_regexWorkerThread(_In_ void* pv)
if (WaitForSingleObject(pwtd->startEvent, INFINITE) == WAIT_OBJECT_0)
{
CComPtr<IPowerRenameRegEx> 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<IPowerRenameItem> 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(&currentNewName));
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<IPowerRenameItem> 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(&currentNewName);
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;
}

View File

@ -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);

View File

@ -5,7 +5,7 @@
#include <string>
#include <algorithm>
#include <boost/regex.hpp>
#include <helpers.cpp>
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);
}
}
}

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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);
};
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);
};

View File

@ -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();

View File

@ -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();

View File

@ -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;
};

View File

@ -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<IPowerRenameItem> 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<IPowerRenameManager> mgr;
Assert::IsTrue(CPowerRenameManager::s_CreateInstance(&mgr) == S_OK);
CComPtr<IPowerRenameItem> 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<IPowerRenameItem> 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);

View File

@ -59,7 +59,7 @@ TEST_METHOD(ReplaceNoSearchOrReplaceTerm)
CComPtr<IPowerRenameRegEx> 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);

View File

@ -53,7 +53,7 @@ TEST_METHOD(ReplaceNoSearchOrReplaceTerm)
CComPtr<IPowerRenameRegEx> 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<IPowerRenameRegEx> 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<IPowerRenameRegEx> 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<IPowerRenameRegEx> 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);