2020-04-29 13:02:18 -07:00
|
|
|
#include "pch.h"
|
2019-10-17 20:57:19 -07:00
|
|
|
#include "Helpers.h"
|
2025-11-04 09:27:16 +08:00
|
|
|
#include "MetadataTypes.h"
|
2020-07-16 14:24:49 +03:00
|
|
|
#include <regex>
|
2019-10-17 20:57:19 -07:00
|
|
|
#include <ShlGuid.h>
|
2020-07-02 11:52:01 +03:00
|
|
|
#include <cstring>
|
|
|
|
|
#include <filesystem>
|
2025-11-04 09:27:16 +08:00
|
|
|
#include <unordered_map>
|
|
|
|
|
#include <unordered_set>
|
|
|
|
|
#include <algorithm>
|
2020-07-02 11:52:01 +03:00
|
|
|
|
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
|
|
2021-10-25 15:40:19 +02:00
|
|
|
namespace
|
|
|
|
|
{
|
|
|
|
|
const int MAX_INPUT_STRING_LEN = 1024;
|
|
|
|
|
|
|
|
|
|
const wchar_t c_rootRegPath[] = L"Software\\Microsoft\\PowerRename";
|
2025-11-04 09:27:16 +08:00
|
|
|
|
|
|
|
|
// Helper function: Find the longest matching pattern starting at the given position
|
|
|
|
|
// Returns the matched pattern name, or empty string if no match found
|
|
|
|
|
std::wstring FindLongestPattern(
|
|
|
|
|
const std::wstring& input,
|
|
|
|
|
size_t startPos,
|
|
|
|
|
size_t maxPatternLength,
|
|
|
|
|
const std::unordered_set<std::wstring>& validPatterns)
|
|
|
|
|
{
|
|
|
|
|
const size_t remaining = input.length() - startPos;
|
|
|
|
|
const size_t searchLength = std::min(maxPatternLength, remaining);
|
|
|
|
|
|
|
|
|
|
// Try to match from longest to shortest to ensure greedy matching
|
|
|
|
|
// e.g., DATE_TAKEN_YYYY should be matched before DATE_TAKEN_YY
|
|
|
|
|
for (size_t len = searchLength; len > 0; --len)
|
|
|
|
|
{
|
|
|
|
|
std::wstring candidate = input.substr(startPos, len);
|
|
|
|
|
if (validPatterns.find(candidate) != validPatterns.end())
|
|
|
|
|
{
|
|
|
|
|
return candidate;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return L"";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Helper function: Get the replacement value for a pattern
|
|
|
|
|
// Returns the actual metadata value if available; if not, returns the pattern name with $ prefix
|
|
|
|
|
std::wstring GetPatternValue(
|
|
|
|
|
const std::wstring& patternName,
|
|
|
|
|
const PowerRenameLib::MetadataPatternMap& patterns)
|
|
|
|
|
{
|
|
|
|
|
auto it = patterns.find(patternName);
|
|
|
|
|
|
|
|
|
|
// Return actual value if found and valid (non-empty)
|
|
|
|
|
if (it != patterns.end() && !it->second.empty())
|
|
|
|
|
{
|
|
|
|
|
return it->second;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Return pattern name with $ prefix if value is unavailable
|
|
|
|
|
// This provides visual feedback that the field exists but has no data
|
|
|
|
|
return L"$" + patternName;
|
|
|
|
|
}
|
2021-10-25 15:40:19 +02:00
|
|
|
}
|
|
|
|
|
|
2020-07-02 11:52:01 +03:00
|
|
|
HRESULT GetTrimmedFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR source)
|
|
|
|
|
{
|
2020-12-14 12:28:12 +03:00
|
|
|
HRESULT hr = E_INVALIDARG;
|
|
|
|
|
if (source)
|
2020-07-02 11:52:01 +03:00
|
|
|
{
|
|
|
|
|
PWSTR newName = nullptr;
|
|
|
|
|
hr = SHStrDup(source, &newName);
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
|
|
|
|
size_t firstValidIndex = 0, lastValidIndex = wcslen(newName) - 1;
|
|
|
|
|
while (firstValidIndex <= lastValidIndex && iswspace(newName[firstValidIndex]))
|
|
|
|
|
{
|
|
|
|
|
firstValidIndex++;
|
|
|
|
|
}
|
|
|
|
|
while (firstValidIndex <= lastValidIndex && (iswspace(newName[lastValidIndex]) || newName[lastValidIndex] == L'.'))
|
|
|
|
|
{
|
|
|
|
|
lastValidIndex--;
|
|
|
|
|
}
|
|
|
|
|
newName[lastValidIndex + 1] = '\0';
|
|
|
|
|
|
|
|
|
|
hr = StringCchCopy(result, cchMax, newName + firstValidIndex);
|
|
|
|
|
}
|
|
|
|
|
CoTaskMemFree(newName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return hr;
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-14 14:42:37 +01:00
|
|
|
HRESULT GetTransformedFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR source, DWORD flags, bool isFolder)
|
2020-07-02 11:52:01 +03:00
|
|
|
{
|
|
|
|
|
std::locale::global(std::locale(""));
|
2020-12-14 12:28:12 +03:00
|
|
|
HRESULT hr = E_INVALIDARG;
|
2024-06-04 04:11:12 -07:00
|
|
|
|
|
|
|
|
auto contractionOrSingleQuotedWordCheck = [](std::wstring stem, size_t i) {
|
|
|
|
|
return !i || stem[i - 1] != '\'' || (i == 1 || iswpunct(stem[i - 2]) || iswspace(stem[i - 2]));
|
|
|
|
|
};
|
|
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
if (source && flags)
|
2020-07-02 11:52:01 +03:00
|
|
|
{
|
|
|
|
|
if (flags & Uppercase)
|
|
|
|
|
{
|
2021-12-14 14:42:37 +01:00
|
|
|
if (isFolder)
|
2020-07-02 11:52:01 +03:00
|
|
|
{
|
2021-12-14 14:42:37 +01:00
|
|
|
hr = StringCchCopy(result, cchMax, source);
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
|
|
|
|
std::transform(result, result + wcslen(result), result, ::towupper);
|
|
|
|
|
}
|
2020-07-02 11:52:01 +03:00
|
|
|
}
|
2021-12-14 14:42:37 +01:00
|
|
|
else
|
2020-07-02 11:52:01 +03:00
|
|
|
{
|
2021-12-14 14:42:37 +01:00
|
|
|
if (flags & NameOnly)
|
2020-07-02 11:52:01 +03:00
|
|
|
{
|
2021-12-14 14:42:37 +01:00
|
|
|
std::wstring stem = fs::path(source).stem().wstring();
|
|
|
|
|
std::transform(stem.begin(), stem.end(), stem.begin(), ::towupper);
|
|
|
|
|
hr = StringCchPrintf(result, cchMax, L"%s%s", stem.c_str(), fs::path(source).extension().c_str());
|
|
|
|
|
}
|
|
|
|
|
else if (flags & ExtensionOnly)
|
|
|
|
|
{
|
|
|
|
|
std::wstring extension = fs::path(source).extension().wstring();
|
|
|
|
|
if (!extension.empty())
|
|
|
|
|
{
|
|
|
|
|
std::transform(extension.begin(), extension.end(), extension.begin(), ::towupper);
|
|
|
|
|
hr = StringCchPrintf(result, cchMax, L"%s%s", fs::path(source).stem().c_str(), extension.c_str());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
hr = StringCchCopy(result, cchMax, source);
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
|
|
|
|
std::transform(result, result + wcslen(result), result, ::towupper);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-07-02 11:52:01 +03:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
hr = StringCchCopy(result, cchMax, source);
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
|
|
|
|
std::transform(result, result + wcslen(result), result, ::towupper);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-12-14 14:42:37 +01:00
|
|
|
}
|
|
|
|
|
else if (flags & Lowercase)
|
|
|
|
|
{
|
|
|
|
|
if (isFolder)
|
2020-07-02 11:52:01 +03:00
|
|
|
{
|
|
|
|
|
hr = StringCchCopy(result, cchMax, source);
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
2021-12-14 14:42:37 +01:00
|
|
|
std::transform(result, result + wcslen(result), result, ::towlower);
|
2020-07-02 11:52:01 +03:00
|
|
|
}
|
|
|
|
|
}
|
2021-12-14 14:42:37 +01:00
|
|
|
else
|
2020-07-02 11:52:01 +03:00
|
|
|
{
|
2021-12-14 14:42:37 +01:00
|
|
|
if (flags & NameOnly)
|
2020-07-02 11:52:01 +03:00
|
|
|
{
|
2021-12-14 14:42:37 +01:00
|
|
|
std::wstring stem = fs::path(source).stem().wstring();
|
|
|
|
|
std::transform(stem.begin(), stem.end(), stem.begin(), ::towlower);
|
|
|
|
|
hr = StringCchPrintf(result, cchMax, L"%s%s", stem.c_str(), fs::path(source).extension().c_str());
|
|
|
|
|
}
|
|
|
|
|
else if (flags & ExtensionOnly)
|
|
|
|
|
{
|
|
|
|
|
std::wstring extension = fs::path(source).extension().wstring();
|
|
|
|
|
if (!extension.empty())
|
|
|
|
|
{
|
|
|
|
|
std::transform(extension.begin(), extension.end(), extension.begin(), ::towlower);
|
|
|
|
|
hr = StringCchPrintf(result, cchMax, L"%s%s", fs::path(source).stem().c_str(), extension.c_str());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
hr = StringCchCopy(result, cchMax, source);
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
|
|
|
|
std::transform(result, result + wcslen(result), result, ::towlower);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-07-02 11:52:01 +03:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
hr = StringCchCopy(result, cchMax, source);
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
|
|
|
|
std::transform(result, result + wcslen(result), result, ::towlower);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (flags & Titlecase)
|
|
|
|
|
{
|
|
|
|
|
if (!(flags & ExtensionOnly))
|
|
|
|
|
{
|
|
|
|
|
std::vector<std::wstring> exceptions = { L"a", L"an", L"to", L"the", L"at", L"by", L"for", L"in", L"of", L"on", L"up", L"and", L"as", L"but", L"or", L"nor" };
|
2021-12-14 14:42:37 +01:00
|
|
|
std::wstring stem = isFolder ? source : fs::path(source).stem().wstring();
|
|
|
|
|
std::wstring extension = isFolder ? L"" : fs::path(source).extension().wstring();
|
2020-07-02 11:52:01 +03:00
|
|
|
|
|
|
|
|
size_t stemLength = stem.length();
|
|
|
|
|
bool isFirstWord = true;
|
|
|
|
|
|
|
|
|
|
while (stemLength > 0 && (iswspace(stem[stemLength - 1]) || iswpunct(stem[stemLength - 1])))
|
|
|
|
|
{
|
|
|
|
|
stemLength--;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < stemLength; i++)
|
|
|
|
|
{
|
2024-06-04 04:11:12 -07:00
|
|
|
if (!i || iswspace(stem[i - 1]) || (iswpunct(stem[i - 1]) && contractionOrSingleQuotedWordCheck(stem, i)))
|
2020-07-02 11:52:01 +03:00
|
|
|
{
|
|
|
|
|
if (iswspace(stem[i]) || iswpunct(stem[i]))
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
size_t wordLength = 0;
|
|
|
|
|
while (i + wordLength < stemLength && !iswspace(stem[i + wordLength]) && !iswpunct(stem[i + wordLength]))
|
|
|
|
|
{
|
|
|
|
|
wordLength++;
|
|
|
|
|
}
|
2024-06-04 04:11:12 -07:00
|
|
|
|
|
|
|
|
auto subStr = stem.substr(i, wordLength);
|
|
|
|
|
std::transform(subStr.begin(), subStr.end(), subStr.begin(), ::towlower);
|
|
|
|
|
if (isFirstWord || i + wordLength == stemLength || std::find(exceptions.begin(), exceptions.end(), subStr) == exceptions.end())
|
2020-07-02 11:52:01 +03:00
|
|
|
{
|
|
|
|
|
stem[i] = towupper(stem[i]);
|
|
|
|
|
isFirstWord = false;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
stem[i] = towlower(stem[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
stem[i] = towlower(stem[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
hr = StringCchPrintf(result, cchMax, L"%s%s", stem.c_str(), extension.c_str());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
hr = StringCchCopy(result, cchMax, source);
|
|
|
|
|
}
|
2023-08-14 16:53:49 +02:00
|
|
|
}
|
2021-04-09 08:24:06 -04:00
|
|
|
else if (flags & Capitalized)
|
|
|
|
|
{
|
|
|
|
|
if (!(flags & ExtensionOnly))
|
|
|
|
|
{
|
2021-12-14 14:42:37 +01:00
|
|
|
std::wstring stem = isFolder ? source : fs::path(source).stem().wstring();
|
|
|
|
|
std::wstring extension = isFolder ? L"" : fs::path(source).extension().wstring();
|
2021-04-09 08:24:06 -04:00
|
|
|
|
|
|
|
|
size_t stemLength = stem.length();
|
|
|
|
|
|
|
|
|
|
while (stemLength > 0 && (iswspace(stem[stemLength - 1]) || iswpunct(stem[stemLength - 1])))
|
|
|
|
|
{
|
|
|
|
|
stemLength--;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < stemLength; i++)
|
|
|
|
|
{
|
2024-06-04 04:11:12 -07:00
|
|
|
if (!i || iswspace(stem[i - 1]) || (iswpunct(stem[i - 1]) && contractionOrSingleQuotedWordCheck(stem, i)))
|
2021-04-09 08:24:06 -04:00
|
|
|
{
|
|
|
|
|
if (iswspace(stem[i]) || iswpunct(stem[i]))
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2024-06-04 04:11:12 -07:00
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
stem[i] = towupper(stem[i]);
|
|
|
|
|
}
|
2021-04-09 08:24:06 -04:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
stem[i] = towlower(stem[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
hr = StringCchPrintf(result, cchMax, L"%s%s", stem.c_str(), extension.c_str());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
hr = StringCchCopy(result, cchMax, source);
|
|
|
|
|
}
|
2020-07-02 11:52:01 +03:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
hr = StringCchCopy(result, cchMax, source);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return hr;
|
|
|
|
|
}
|
2019-10-17 20:57:19 -07:00
|
|
|
|
2023-08-14 16:53:49 +02:00
|
|
|
bool isFileTimeUsed(_In_ PCWSTR source)
|
2020-08-20 02:53:09 +03:00
|
|
|
{
|
|
|
|
|
bool used = false;
|
2025-04-16 07:45:03 +05:30
|
|
|
static const std::array patterns = {
|
|
|
|
|
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$Y" },
|
|
|
|
|
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$M" },
|
|
|
|
|
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$D" },
|
|
|
|
|
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$h" },
|
|
|
|
|
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$m" },
|
|
|
|
|
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$s" },
|
|
|
|
|
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$f" },
|
|
|
|
|
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$H" },
|
|
|
|
|
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$T" },
|
|
|
|
|
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$t" }
|
|
|
|
|
};
|
|
|
|
|
|
2023-08-14 16:53:49 +02:00
|
|
|
for (size_t i = 0; !used && i < patterns.size(); i++)
|
2020-08-20 02:53:09 +03:00
|
|
|
{
|
2023-08-14 16:53:49 +02:00
|
|
|
if (std::regex_search(source, patterns[i]))
|
2020-08-20 02:53:09 +03:00
|
|
|
{
|
|
|
|
|
used = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return used;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-04 09:27:16 +08:00
|
|
|
bool isMetadataUsed(_In_ PCWSTR source, PowerRenameLib::MetadataType metadataType, _In_opt_ PCWSTR filePath, bool isFolder)
|
|
|
|
|
{
|
|
|
|
|
if (!source) return false;
|
|
|
|
|
|
|
|
|
|
// Early exit: If file path is provided, check file type first (fastest checks)
|
|
|
|
|
// This avoids expensive pattern matching for files that don't support metadata
|
|
|
|
|
if (filePath != nullptr)
|
|
|
|
|
{
|
|
|
|
|
// Folders don't support metadata extraction
|
|
|
|
|
if (isFolder)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if file path is valid
|
|
|
|
|
if (wcslen(filePath) == 0)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get file extension
|
|
|
|
|
std::wstring extension = fs::path(filePath).extension().wstring();
|
|
|
|
|
|
|
|
|
|
// Convert to lowercase for case-insensitive comparison
|
|
|
|
|
std::transform(extension.begin(), extension.end(), extension.begin(), ::towlower);
|
|
|
|
|
|
|
|
|
|
// According to the metadata support table, only these formats support metadata extraction:
|
|
|
|
|
// - JPEG (IFD, Exif, XMP, GPS, IPTC) - supports fast metadata encoding
|
|
|
|
|
// - TIFF (IFD, Exif, XMP, GPS, IPTC) - supports fast metadata encoding
|
|
|
|
|
// - PNG (text chunks)
|
|
|
|
|
static const std::unordered_set<std::wstring> supportedExtensions = {
|
|
|
|
|
L".jpg",
|
|
|
|
|
L".jpeg",
|
|
|
|
|
L".png",
|
|
|
|
|
L".tif",
|
|
|
|
|
L".tiff"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// If file type doesn't support metadata, no need to check patterns
|
|
|
|
|
if (supportedExtensions.find(extension) == supportedExtensions.end())
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Now check if any metadata pattern exists in the source string
|
|
|
|
|
// This is the most expensive check, so we do it last
|
|
|
|
|
std::wstring str(source);
|
|
|
|
|
|
|
|
|
|
// Get supported patterns for the specified metadata type
|
|
|
|
|
auto supportedPatterns = PowerRenameLib::MetadataPatternExtractor::GetSupportedPatterns(metadataType);
|
|
|
|
|
|
|
|
|
|
// Check if any metadata pattern exists in the source string
|
|
|
|
|
for (const auto& pattern : supportedPatterns)
|
|
|
|
|
{
|
|
|
|
|
std::wstring searchPattern = L"$" + pattern;
|
|
|
|
|
if (str.find(searchPattern) != std::wstring::npos)
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// No metadata pattern found
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
HRESULT GetDatedFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR source, SYSTEMTIME fileTime)
|
2020-07-16 14:24:49 +03:00
|
|
|
{
|
2020-08-20 02:53:09 +03:00
|
|
|
std::locale::global(std::locale(""));
|
2023-08-14 16:53:49 +02:00
|
|
|
HRESULT hr = E_INVALIDARG;
|
2020-12-14 12:28:12 +03:00
|
|
|
if (source && wcslen(source) > 0)
|
2020-07-16 14:24:49 +03:00
|
|
|
{
|
|
|
|
|
std::wstring res(source);
|
2020-08-20 02:53:09 +03:00
|
|
|
wchar_t replaceTerm[MAX_PATH] = { 0 };
|
|
|
|
|
wchar_t formattedDate[MAX_PATH] = { 0 };
|
|
|
|
|
|
|
|
|
|
wchar_t localeName[LOCALE_NAME_MAX_LENGTH];
|
|
|
|
|
if (GetUserDefaultLocaleName(localeName, LOCALE_NAME_MAX_LENGTH) == 0)
|
|
|
|
|
{
|
|
|
|
|
StringCchCopy(localeName, LOCALE_NAME_MAX_LENGTH, L"en_US");
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-16 07:45:03 +05:30
|
|
|
int hour12 = (fileTime.wHour % 12);
|
|
|
|
|
if (hour12 == 0)
|
|
|
|
|
{
|
|
|
|
|
hour12 = 12;
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%04d"), L"$01", fileTime.wYear);
|
2020-08-20 02:53:09 +03:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$YYYY"), replaceTerm);
|
|
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", (fileTime.wYear % 100));
|
2025-11-04 09:27:16 +08:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$YY(?![A-Z])"), replaceTerm); // Negative lookahead prevents matching $YYY, $YYYY, or metadata patterns
|
2020-08-20 02:53:09 +03:00
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", (fileTime.wYear % 10));
|
2025-11-04 09:27:16 +08:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$Y(?![A-Z])"), replaceTerm); // Negative lookahead prevents matching $YY, $YYYY, or metadata patterns
|
2023-08-14 16:53:49 +02:00
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
GetDateFormatEx(localeName, NULL, &fileTime, L"MMMM", formattedDate, MAX_PATH, NULL);
|
2020-08-20 02:53:09 +03:00
|
|
|
formattedDate[0] = towupper(formattedDate[0]);
|
|
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", formattedDate);
|
|
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$MMMM"), replaceTerm);
|
2023-08-14 16:53:49 +02:00
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
GetDateFormatEx(localeName, NULL, &fileTime, L"MMM", formattedDate, MAX_PATH, NULL);
|
2020-08-20 02:53:09 +03:00
|
|
|
formattedDate[0] = towupper(formattedDate[0]);
|
|
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", formattedDate);
|
2025-11-04 09:27:16 +08:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$MMM(?!M)"), replaceTerm); // Negative lookahead prevents matching $MMMM
|
2023-08-14 16:53:49 +02:00
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", fileTime.wMonth);
|
2025-11-04 09:27:16 +08:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$MM(?![A-Z])"), replaceTerm); // Negative lookahead prevents matching $MMM, $MMMM, or metadata patterns
|
2020-08-20 02:53:09 +03:00
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", fileTime.wMonth);
|
2025-11-04 09:27:16 +08:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$M(?![A-Z])"), replaceTerm); // Negative lookahead prevents matching $MM, $MMM, $MMMM, or metadata patterns
|
2020-08-20 02:53:09 +03:00
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
GetDateFormatEx(localeName, NULL, &fileTime, L"dddd", formattedDate, MAX_PATH, NULL);
|
2020-08-20 02:53:09 +03:00
|
|
|
formattedDate[0] = towupper(formattedDate[0]);
|
|
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", formattedDate);
|
|
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$DDDD"), replaceTerm);
|
2023-08-14 16:53:49 +02:00
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
GetDateFormatEx(localeName, NULL, &fileTime, L"ddd", formattedDate, MAX_PATH, NULL);
|
2020-08-20 02:53:09 +03:00
|
|
|
formattedDate[0] = towupper(formattedDate[0]);
|
|
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", formattedDate);
|
2025-11-04 09:27:16 +08:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$DDD(?![A-Z])"), replaceTerm); // Negative lookahead prevents matching $DDDD or metadata patterns
|
2020-08-20 02:53:09 +03:00
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", fileTime.wDay);
|
2025-11-04 09:27:16 +08:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$DD(?![A-Z])"), replaceTerm); // Negative lookahead prevents matching $DDD, $DDDD, or metadata patterns
|
2020-07-16 14:24:49 +03:00
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", fileTime.wDay);
|
2025-11-04 09:27:16 +08:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$D(?![A-Z])"), replaceTerm); // Negative lookahead prevents matching $DD, $DDD, $DDDD, or metadata patterns like $DATE_TAKEN_YYYY
|
2020-07-16 14:24:49 +03:00
|
|
|
|
2025-04-16 07:45:03 +05:30
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", hour12);
|
2025-11-04 09:27:16 +08:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$HH(?![A-Z])"), replaceTerm); // Negative lookahead prevents matching $HHH or metadata patterns
|
2025-04-16 07:45:03 +05:30
|
|
|
|
|
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", hour12);
|
2025-11-04 09:27:16 +08:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$H(?![A-Z])"), replaceTerm); // Negative lookahead prevents matching $HH or metadata patterns
|
2025-04-16 07:45:03 +05:30
|
|
|
|
|
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", (fileTime.wHour < 12) ? L"AM" : L"PM");
|
|
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$TT"), replaceTerm);
|
|
|
|
|
|
|
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", (fileTime.wHour < 12) ? L"am" : L"pm");
|
|
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$tt"), replaceTerm);
|
|
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", fileTime.wHour);
|
2025-11-04 09:27:16 +08:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$hh(?!h)"), replaceTerm); // Negative lookahead prevents matching $hhh
|
2020-07-16 14:24:49 +03:00
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", fileTime.wHour);
|
2025-11-04 09:27:16 +08:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$h(?!h)"), replaceTerm); // Negative lookahead prevents matching $hh
|
2020-07-16 14:24:49 +03:00
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", fileTime.wMinute);
|
2025-11-04 09:27:16 +08:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$mm(?!m)"), replaceTerm); // Negative lookahead prevents matching $mmm
|
2020-07-16 14:24:49 +03:00
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", fileTime.wMinute);
|
2025-11-04 09:27:16 +08:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$m(?!m)"), replaceTerm); // Negative lookahead prevents matching $mm
|
2020-07-16 14:24:49 +03:00
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", fileTime.wSecond);
|
2025-11-04 09:27:16 +08:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$ss(?!s)"), replaceTerm); // Negative lookahead prevents matching $sss
|
2020-07-16 14:24:49 +03:00
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", fileTime.wSecond);
|
2025-11-04 09:27:16 +08:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$s(?!s)"), replaceTerm); // Negative lookahead prevents matching $ss
|
2020-07-16 14:24:49 +03:00
|
|
|
|
2020-12-14 12:28:12 +03:00
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%03d"), L"$01", fileTime.wMilliseconds);
|
2025-11-04 09:27:16 +08:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$fff(?!f)"), replaceTerm); // Negative lookahead prevents matching $ffff
|
2020-07-16 14:24:49 +03:00
|
|
|
|
2023-08-14 16:53:49 +02:00
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", fileTime.wMilliseconds / 10);
|
2025-11-04 09:27:16 +08:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$ff(?!f)"), replaceTerm); // Negative lookahead prevents matching $fff
|
2020-07-16 14:24:49 +03:00
|
|
|
|
2023-08-14 16:53:49 +02:00
|
|
|
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", fileTime.wMilliseconds / 100);
|
2025-11-04 09:27:16 +08:00
|
|
|
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$f(?!f)"), replaceTerm); // Negative lookahead prevents matching $ff or $fff
|
2020-07-16 14:24:49 +03:00
|
|
|
|
|
|
|
|
hr = StringCchCopy(result, cchMax, res.c_str());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return hr;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-04 09:27:16 +08:00
|
|
|
HRESULT GetMetadataFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR source, const PowerRenameLib::MetadataPatternMap& patterns)
|
|
|
|
|
{
|
|
|
|
|
if (!source || wcslen(source) == 0)
|
|
|
|
|
{
|
|
|
|
|
return E_INVALIDARG;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::wstring input(source);
|
|
|
|
|
std::wstring output;
|
|
|
|
|
output.reserve(input.length() * 2); // Reserve space to avoid frequent reallocations
|
|
|
|
|
|
|
|
|
|
// Build pattern lookup table for fast validation
|
|
|
|
|
// Using all possible patterns to recognize valid pattern names even when metadata is unavailable
|
|
|
|
|
auto allPatterns = PowerRenameLib::MetadataPatternExtractor::GetAllPossiblePatterns();
|
|
|
|
|
std::unordered_set<std::wstring> validPatterns;
|
|
|
|
|
validPatterns.reserve(allPatterns.size());
|
|
|
|
|
size_t maxPatternLength = 0;
|
|
|
|
|
for (const auto& pattern : allPatterns)
|
|
|
|
|
{
|
|
|
|
|
validPatterns.insert(pattern);
|
|
|
|
|
maxPatternLength = std::max(maxPatternLength, pattern.length());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t pos = 0;
|
|
|
|
|
while (pos < input.length())
|
|
|
|
|
{
|
|
|
|
|
// Handle regular characters
|
|
|
|
|
if (input[pos] != L'$')
|
|
|
|
|
{
|
|
|
|
|
output += input[pos];
|
|
|
|
|
pos++;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Count consecutive dollar signs
|
|
|
|
|
size_t dollarCount = 0;
|
|
|
|
|
while (pos < input.length() && input[pos] == L'$')
|
|
|
|
|
{
|
|
|
|
|
dollarCount++;
|
|
|
|
|
pos++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Even number of dollars: all are escaped (e.g., $$ -> $, $$$$ -> $$)
|
|
|
|
|
if (dollarCount % 2 == 0)
|
|
|
|
|
{
|
|
|
|
|
output.append(dollarCount / 2, L'$');
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Odd number of dollars: pairs are escaped, last one might be a pattern prefix
|
|
|
|
|
// e.g., $ -> might be pattern, $$$ -> $ + might be pattern
|
|
|
|
|
size_t escapedDollars = dollarCount / 2;
|
|
|
|
|
|
|
|
|
|
// If no more characters, output all dollar signs
|
|
|
|
|
if (pos >= input.length())
|
|
|
|
|
{
|
|
|
|
|
output.append(dollarCount, L'$');
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Try to match a pattern (greedy matching for longest pattern)
|
|
|
|
|
std::wstring matchedPattern = FindLongestPattern(input, pos, maxPatternLength, validPatterns);
|
|
|
|
|
|
|
|
|
|
if (matchedPattern.empty())
|
|
|
|
|
{
|
|
|
|
|
// No pattern matched, output all dollar signs
|
|
|
|
|
output.append(dollarCount, L'$');
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Pattern matched
|
|
|
|
|
output.append(escapedDollars, L'$'); // Output escaped dollars first
|
|
|
|
|
|
|
|
|
|
// Replace pattern with its value or keep pattern name if value unavailable
|
|
|
|
|
std::wstring replacementValue = GetPatternValue(matchedPattern, patterns);
|
|
|
|
|
output += replacementValue;
|
|
|
|
|
|
|
|
|
|
pos += matchedPattern.length();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return StringCchCopy(result, cchMax, output.c_str());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2021-04-02 03:07:12 -07:00
|
|
|
HRESULT GetShellItemArrayFromDataObject(_In_ IUnknown* dataSource, _COM_Outptr_ IShellItemArray** items)
|
2020-07-22 13:27:58 -07:00
|
|
|
{
|
|
|
|
|
*items = nullptr;
|
|
|
|
|
CComPtr<IDataObject> dataObj;
|
|
|
|
|
HRESULT hr;
|
|
|
|
|
if (SUCCEEDED(dataSource->QueryInterface(IID_PPV_ARGS(&dataObj))))
|
|
|
|
|
{
|
|
|
|
|
hr = SHCreateShellItemArrayFromDataObject(dataObj, IID_PPV_ARGS(items));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
hr = dataSource->QueryInterface(IID_PPV_ARGS(items));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return hr;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-20 16:59:20 +03:00
|
|
|
BOOL GetEnumeratedFileName(__out_ecount(cchMax) PWSTR pszUniqueName, UINT cchMax, __in PCWSTR pszTemplate, __in_opt PCWSTR pszDir, unsigned long ulMinLong, __inout unsigned long* pulNumUsed)
|
2019-10-17 20:57:19 -07:00
|
|
|
{
|
|
|
|
|
PWSTR pszName = nullptr;
|
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
|
BOOL fRet = FALSE;
|
|
|
|
|
int cchDir = 0;
|
|
|
|
|
|
|
|
|
|
if (0 != cchMax && pszUniqueName)
|
|
|
|
|
{
|
|
|
|
|
*pszUniqueName = 0;
|
|
|
|
|
if (pszDir)
|
|
|
|
|
{
|
|
|
|
|
hr = StringCchCopy(pszUniqueName, cchMax, pszDir);
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
|
|
|
|
hr = PathCchAddBackslashEx(pszUniqueName, cchMax, &pszName, nullptr);
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
|
|
|
|
cchDir = lstrlen(pszDir);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
cchDir = 0;
|
|
|
|
|
pszName = pszUniqueName;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
hr = E_INVALIDARG;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int cchTmp = 0;
|
|
|
|
|
int cchStem = 0;
|
|
|
|
|
PCWSTR pszStem = nullptr;
|
|
|
|
|
PCWSTR pszRest = nullptr;
|
|
|
|
|
wchar_t szFormat[MAX_PATH] = { 0 };
|
|
|
|
|
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
|
|
|
|
pszStem = pszTemplate;
|
|
|
|
|
|
|
|
|
|
pszRest = StrChr(pszTemplate, L'(');
|
|
|
|
|
while (pszRest)
|
|
|
|
|
{
|
|
|
|
|
PCWSTR pszEndUniq = CharNext(pszRest);
|
|
|
|
|
while (*pszEndUniq && *pszEndUniq >= L'0' && *pszEndUniq <= L'9')
|
|
|
|
|
{
|
|
|
|
|
pszEndUniq++;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-28 15:33:36 +02:00
|
|
|
if (*pszEndUniq == L')' && (*CharNext(pszEndUniq) == L'\0' || CharNext(pszEndUniq) == PathFindExtension(pszTemplate)))
|
2019-10-17 20:57:19 -07:00
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pszRest = StrChr(CharNext(pszRest), L'(');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!pszRest)
|
|
|
|
|
{
|
|
|
|
|
pszRest = PathFindExtension(pszTemplate);
|
2023-02-08 13:10:28 +00:00
|
|
|
cchStem = static_cast<int>(pszRest - pszTemplate);
|
2019-10-17 20:57:19 -07:00
|
|
|
|
|
|
|
|
hr = StringCchCopy(szFormat, ARRAYSIZE(szFormat), L" (%lu)");
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
pszRest++;
|
|
|
|
|
|
2023-02-08 13:10:28 +00:00
|
|
|
cchStem = static_cast<int>(pszRest - pszTemplate);
|
2019-10-17 20:57:19 -07:00
|
|
|
|
|
|
|
|
while (*pszRest && *pszRest >= L'0' && *pszRest <= L'9')
|
|
|
|
|
{
|
|
|
|
|
pszRest++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hr = StringCchCopy(szFormat, ARRAYSIZE(szFormat), L"%lu");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unsigned long ulMax = 0;
|
|
|
|
|
unsigned long ulMin = 0;
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
|
|
|
|
int cchFormat = lstrlen(szFormat);
|
|
|
|
|
if (cchFormat < 3)
|
|
|
|
|
{
|
|
|
|
|
*pszUniqueName = L'\0';
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
ulMin = ulMinLong;
|
|
|
|
|
cchTmp = cchMax - cchDir - cchStem - (cchFormat - 3);
|
|
|
|
|
switch (cchTmp)
|
|
|
|
|
{
|
|
|
|
|
case 1:
|
|
|
|
|
ulMax = 10;
|
|
|
|
|
break;
|
|
|
|
|
case 2:
|
|
|
|
|
ulMax = 100;
|
|
|
|
|
break;
|
|
|
|
|
case 3:
|
|
|
|
|
ulMax = 1000;
|
|
|
|
|
break;
|
|
|
|
|
case 4:
|
|
|
|
|
ulMax = 10000;
|
|
|
|
|
break;
|
|
|
|
|
case 5:
|
|
|
|
|
ulMax = 100000;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
if (cchTmp <= 0)
|
|
|
|
|
{
|
|
|
|
|
ulMax = ulMin;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
ulMax = 1000000;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
|
|
|
|
hr = StringCchCopyN(pszName, pszUniqueName + cchMax - pszName, pszStem, cchStem);
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
|
|
|
|
PWSTR pszDigit = pszName + cchStem;
|
|
|
|
|
|
|
|
|
|
for (unsigned long ul = ulMin; ((ul < ulMax) && (!fRet)); ul++)
|
|
|
|
|
{
|
|
|
|
|
wchar_t szTemp[MAX_PATH] = { 0 };
|
|
|
|
|
hr = StringCchPrintf(szTemp, ARRAYSIZE(szTemp), szFormat, ul);
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
|
|
|
|
hr = StringCchCat(szTemp, ARRAYSIZE(szTemp), pszRest);
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
|
|
|
|
hr = StringCchCopy(pszDigit, pszUniqueName + cchMax - pszDigit, szTemp);
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
|
|
|
|
if (!PathFileExists(pszUniqueName))
|
|
|
|
|
{
|
|
|
|
|
(*pulNumUsed) = ul;
|
|
|
|
|
fRet = TRUE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!fRet)
|
|
|
|
|
{
|
|
|
|
|
*pszUniqueName = L'\0';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return fRet;
|
|
|
|
|
}
|
2020-07-22 13:27:58 -07:00
|
|
|
|
2022-06-30 22:10:14 +02:00
|
|
|
// Iterate through the shell items array and checks if at least 1 item has SFGAO_CANRENAME.
|
Updates for check-spelling v0.0.25 (#40386)
## Summary of the Pull Request
- #39572 updated check-spelling but ignored:
> 🐣 Breaking Changes
[Code Scanning action requires a Code Scanning
Ruleset](https://github.com/check-spelling/check-spelling/wiki/Breaking-Change:-Code-Scanning-action-requires-a-Code-Scanning-Ruleset)
If you use SARIF reporting, then instead of the workflow yielding an ❌
when it fails, it will rely on [github-advanced-security
🤖](https://github.com/apps/github-advanced-security) to report the
failure. You will need to adjust your checks for PRs.
This means that check-spelling hasn't been properly doing its job 😦.
I'm sorry, I should have pushed a thing to this repo earlier,...
Anyway, as with most refreshes, this comes with a number of fixes, some
are fixes for typos that snuck in before the 0.0.25 upgrade, some are
for things that snuck in after, some are based on new rules in
spell-check-this, and some are hand written patterns based on running
through this repository a few times.
About the 🐣 **breaking change**: someone needs to create a ruleset for
this repository (see [Code Scanning action requires a Code Scanning
Ruleset: Sample ruleset
](https://github.com/check-spelling/check-spelling/wiki/Breaking-Change:-Code-Scanning-action-requires-a-Code-Scanning-Ruleset#sample-ruleset)).
The alternative to adding a ruleset is to change the condition to not
use sarif for this repository. In general, I think the github
integration from sarif is prettier/more helpful, so I think that it's
the better choice.
You can see an example of it working in:
- https://github.com/check-spelling-sandbox/PowerToys/pull/23
---------
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
Co-authored-by: Mike Griese <migrie@microsoft.com>
Co-authored-by: Dustin L. Howett <dustin@howett.net>
2025-07-08 18:16:52 -04:00
|
|
|
// We do not enumerate child items - only items the user selected.
|
2022-06-30 22:10:14 +02:00
|
|
|
bool ShellItemArrayContainsRenamableItem(_In_ IShellItemArray* shellItemArray)
|
|
|
|
|
{
|
|
|
|
|
bool hasRenamable = false;
|
|
|
|
|
IEnumShellItems* spesi;
|
|
|
|
|
if (SUCCEEDED(shellItemArray->EnumItems(&spesi)))
|
|
|
|
|
{
|
|
|
|
|
ULONG celtFetched;
|
|
|
|
|
IShellItem* spsi;
|
|
|
|
|
while ((S_OK == spesi->Next(1, &spsi, &celtFetched)))
|
|
|
|
|
{
|
|
|
|
|
SFGAOF attrs;
|
|
|
|
|
if (SUCCEEDED(spsi->GetAttributes(SFGAO_CANRENAME, &attrs)) &&
|
|
|
|
|
attrs & SFGAO_CANRENAME)
|
|
|
|
|
{
|
|
|
|
|
hasRenamable = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return hasRenamable;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-22 13:27:58 -07:00
|
|
|
// Iterate through the data source and checks if at least 1 item has SFGAO_CANRENAME.
|
Updates for check-spelling v0.0.25 (#40386)
## Summary of the Pull Request
- #39572 updated check-spelling but ignored:
> 🐣 Breaking Changes
[Code Scanning action requires a Code Scanning
Ruleset](https://github.com/check-spelling/check-spelling/wiki/Breaking-Change:-Code-Scanning-action-requires-a-Code-Scanning-Ruleset)
If you use SARIF reporting, then instead of the workflow yielding an ❌
when it fails, it will rely on [github-advanced-security
🤖](https://github.com/apps/github-advanced-security) to report the
failure. You will need to adjust your checks for PRs.
This means that check-spelling hasn't been properly doing its job 😦.
I'm sorry, I should have pushed a thing to this repo earlier,...
Anyway, as with most refreshes, this comes with a number of fixes, some
are fixes for typos that snuck in before the 0.0.25 upgrade, some are
for things that snuck in after, some are based on new rules in
spell-check-this, and some are hand written patterns based on running
through this repository a few times.
About the 🐣 **breaking change**: someone needs to create a ruleset for
this repository (see [Code Scanning action requires a Code Scanning
Ruleset: Sample ruleset
](https://github.com/check-spelling/check-spelling/wiki/Breaking-Change:-Code-Scanning-action-requires-a-Code-Scanning-Ruleset#sample-ruleset)).
The alternative to adding a ruleset is to change the condition to not
use sarif for this repository. In general, I think the github
integration from sarif is prettier/more helpful, so I think that it's
the better choice.
You can see an example of it working in:
- https://github.com/check-spelling-sandbox/PowerToys/pull/23
---------
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
Co-authored-by: Mike Griese <migrie@microsoft.com>
Co-authored-by: Dustin L. Howett <dustin@howett.net>
2025-07-08 18:16:52 -04:00
|
|
|
// We do not enumerate child items - only items the user selected.
|
2020-07-22 13:27:58 -07:00
|
|
|
bool DataObjectContainsRenamableItem(_In_ IUnknown* dataSource)
|
|
|
|
|
{
|
|
|
|
|
bool hasRenamable = false;
|
|
|
|
|
CComPtr<IShellItemArray> spsia;
|
2023-03-08 14:51:33 +01:00
|
|
|
if (dataSource && SUCCEEDED(GetShellItemArrayFromDataObject(dataSource, &spsia)))
|
2020-07-22 13:27:58 -07:00
|
|
|
{
|
|
|
|
|
CComPtr<IEnumShellItems> spesi;
|
|
|
|
|
if (SUCCEEDED(spsia->EnumItems(&spesi)))
|
|
|
|
|
{
|
|
|
|
|
ULONG celtFetched;
|
|
|
|
|
CComPtr<IShellItem> spsi;
|
|
|
|
|
while ((S_OK == spesi->Next(1, &spsi, &celtFetched)))
|
|
|
|
|
{
|
|
|
|
|
SFGAOF attrs;
|
|
|
|
|
if (SUCCEEDED(spsi->GetAttributes(SFGAO_CANRENAME, &attrs)) &&
|
|
|
|
|
attrs & SFGAO_CANRENAME)
|
|
|
|
|
{
|
|
|
|
|
hasRenamable = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return hasRenamable;
|
|
|
|
|
}
|
2021-04-02 03:07:12 -07:00
|
|
|
|
|
|
|
|
HWND CreateMsgWindow(_In_ HINSTANCE hInst, _In_ WNDPROC pfnWndProc, _In_ void* p)
|
|
|
|
|
{
|
|
|
|
|
WNDCLASS wc = { 0 };
|
|
|
|
|
PCWSTR wndClassName = L"MsgWindow";
|
|
|
|
|
|
|
|
|
|
wc.lpfnWndProc = DefWindowProc;
|
|
|
|
|
wc.cbWndExtra = sizeof(void*);
|
|
|
|
|
wc.hInstance = hInst;
|
2023-02-08 13:10:28 +00:00
|
|
|
wc.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_BTNFACE + 1);
|
2021-04-02 03:07:12 -07:00
|
|
|
wc.lpszClassName = wndClassName;
|
|
|
|
|
|
|
|
|
|
RegisterClass(&wc);
|
|
|
|
|
|
|
|
|
|
HWND hwnd = CreateWindowEx(
|
|
|
|
|
0, wndClassName, nullptr, 0, 0, 0, 0, 0, HWND_MESSAGE, 0, hInst, nullptr);
|
|
|
|
|
if (hwnd)
|
|
|
|
|
{
|
2023-02-08 13:10:28 +00:00
|
|
|
SetWindowLongPtr(hwnd, 0, reinterpret_cast<LONG_PTR>(p));
|
2021-04-02 03:07:12 -07:00
|
|
|
if (pfnWndProc)
|
|
|
|
|
{
|
2023-02-08 13:10:28 +00:00
|
|
|
SetWindowLongPtr(hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(pfnWndProc));
|
2021-04-02 03:07:12 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return hwnd;
|
|
|
|
|
}
|
2021-10-25 15:40:19 +02:00
|
|
|
|
|
|
|
|
std::wstring GetRegString(const std::wstring& valueName, const std::wstring& subPath)
|
|
|
|
|
{
|
|
|
|
|
wchar_t value[MAX_INPUT_STRING_LEN];
|
|
|
|
|
value[0] = L'\0';
|
|
|
|
|
DWORD type = REG_SZ;
|
|
|
|
|
DWORD size = MAX_INPUT_STRING_LEN * sizeof(wchar_t);
|
|
|
|
|
std::wstring completePath = std::wstring(c_rootRegPath) + subPath;
|
|
|
|
|
if (SHGetValue(HKEY_CURRENT_USER, completePath.c_str(), valueName.c_str(), &type, value, &size) == ERROR_SUCCESS)
|
|
|
|
|
{
|
|
|
|
|
return std::wstring(value);
|
|
|
|
|
}
|
|
|
|
|
return std::wstring{};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unsigned int GetRegNumber(const std::wstring& valueName, unsigned int defaultValue)
|
|
|
|
|
{
|
|
|
|
|
DWORD type = REG_DWORD;
|
|
|
|
|
DWORD data = 0;
|
|
|
|
|
DWORD size = sizeof(DWORD);
|
|
|
|
|
if (SHGetValue(HKEY_CURRENT_USER, c_rootRegPath, valueName.c_str(), &type, &data, &size) == ERROR_SUCCESS)
|
|
|
|
|
{
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
return defaultValue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SetRegNumber(const std::wstring& valueName, unsigned int value)
|
|
|
|
|
{
|
|
|
|
|
SHSetValue(HKEY_CURRENT_USER, c_rootRegPath, valueName.c_str(), REG_DWORD, &value, sizeof(value));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool GetRegBoolean(const std::wstring& valueName, bool defaultValue)
|
|
|
|
|
{
|
|
|
|
|
DWORD value = GetRegNumber(valueName.c_str(), defaultValue ? 1 : 0);
|
|
|
|
|
return (value == 0) ? false : true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SetRegBoolean(const std::wstring& valueName, bool value)
|
|
|
|
|
{
|
|
|
|
|
SetRegNumber(valueName, value ? 1 : 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool LastModifiedTime(const std::wstring& filePath, FILETIME* lpFileTime)
|
|
|
|
|
{
|
|
|
|
|
WIN32_FILE_ATTRIBUTE_DATA attr{};
|
|
|
|
|
if (GetFileAttributesExW(filePath.c_str(), GetFileExInfoStandard, &attr))
|
|
|
|
|
{
|
|
|
|
|
*lpFileTime = attr.ftLastWriteTime;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2024-06-20 18:26:31 +03:00
|
|
|
|
|
|
|
|
std::wstring CreateGuidStringWithoutBrackets()
|
|
|
|
|
{
|
|
|
|
|
GUID guid;
|
|
|
|
|
if (CoCreateGuid(&guid) == S_OK)
|
|
|
|
|
{
|
|
|
|
|
OLECHAR* guidString;
|
|
|
|
|
if (StringFromCLSID(guid, &guidString) == S_OK)
|
|
|
|
|
{
|
|
|
|
|
std::wstring guidStr{ guidString };
|
|
|
|
|
CoTaskMemFree(guidString);
|
|
|
|
|
return guidStr.substr(1, guidStr.length() - 2);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return L"";
|
2025-11-04 09:27:16 +08:00
|
|
|
}
|