Post by JJYour code is the one who told FindFirstFile to enumerate files within that
folder. So you'll have to make the code to skip that directory contents from
being enumerated.
I simply skipped files where I get a GetLastError() of ERROR_ACCESS
_DENIED. And I dont't use CharLowerBuff / CharUpperBuff anymore because
the length of the output-string can be different because of UTF-16.
Here's the new code:
#include <windows.h>
#include <iostream>
#include <vector>
#include <string>
#include <utility>
#include <cstdlib>
#include <cstring>
#include <cassert>
#include <cctype>
using namespace std;
bool recurseThroughSubdirs = false,
toUppercase = false,
force = false,
hidden = false,
quietMode = false;
bool error = false;
void renameFiles( wchar_t const *filePattern, size_t pathLength );
void usage();
int main()
{
try
{
struct Option
{
wchar_t const *str;
void (*fn)();
};
static Option const options[] =
{
L"-r", []() { ::recurseThroughSubdirs = true; },
L"-s", []() { ::recurseThroughSubdirs = true; },
L"-u", []() { ::toUppercase = true; },
L"-f", []() { ::force = true; },
L"-q", []() { ::quietMode = true; },
L"-h", []() { ::hidden = true; },
L"-?", []() { usage(); }
};
int argc;
wchar_t **argv = CommandLineToArgvW( GetCommandLineW(),
&argc ),
**argvScn = argv + 1,
**argvScnEnd = argv + argc;
if( !argv )
return EXIT_FAILURE;
for( ; argvScn < argvScnEnd; argvScn++ )
{
bool match = false;
for( Option const &opt : options )
if( wcscmp( opt.str, *argvScn ) == 0 )
{
match = true;
opt.fn();
break;
}
if( !match )
break;
}
vector<wstring> filePatterns;
for( ; argvScn < argvScnEnd; argvScn++ )
filePatterns.emplace_back( *argvScn );
for( wstring &fp : filePatterns )
{
size_t pathLength;
for( pathLength = fp.length(); pathLength && fp[pathLength
- 1] != '\\'; --pathLength );
renameFiles( fp.c_str(), pathLength );
}
return !::error ? EXIT_SUCCESS : EXIT_FAILURE;
}
catch( bad_alloc & )
{
wcerr << L"out of memory" << endl;
return EXIT_FAILURE;
}
catch( ... )
{
return EXIT_FAILURE;
}
}
bool convertFileName( wchar_t const *oldFileName, size_t ofnLlength,
wstring &newFileName, bool uppercase, bool force );
struct WinFind
{
WinFind()
{
hFind = INVALID_HANDLE_VALUE;
}
~WinFind()
{
if( hFind != INVALID_HANDLE_VALUE )
FindClose( hFind );
}
void close()
{
FindClose( hFind );
hFind = INVALID_HANDLE_VALUE;
}
HANDLE hFind;
};
void renameFiles( wchar_t const *filePattern, size_t pathLength )
{
DWORD dwError;
WIN32_FIND_DATAW fd;
WinFind find;
if( (find.hFind = FindFirstFileW( filePattern, &fd )) !=
INVALID_HANDLE_VALUE )
{
do
if( wcscmp( fd.cFileName, L"." ) != 0 && wcscmp(
fd.cFileName, L".." ) != 0 &&
(!(fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) ||
::hidden) )
{
size_t oldFnLength = wcslen( fd.cFileName );
wstring newFileName;
if( !convertFileName( fd.cFileName, oldFnLength,
newFileName, ::toUppercase, ::force ) )
{
wcerr << "error convertig filename \"" <<
fd.cFileName << L"\" to " << (!::toUppercase ? L"lowecase" :
L"uppercase") << endl;
::error = true;
continue;
}
wstring oldFullFileName,
newFullFileName;
oldFullFileName.reserve( pathLength + oldFnLength );
oldFullFileName.append( filePattern, pathLength );
oldFullFileName += fd.cFileName;
newFileName.reserve( pathLength + newFileName.length() );
newFullFileName.append( filePattern, pathLength );
newFullFileName += newFileName;
if( oldFullFileName != newFullFileName )
{
bool success = MoveFileW( oldFullFileName.c_str(),
newFullFileName.c_str() );
if( !::quietMode )
wcout << L"\"" << oldFullFileName << L"\" ->
\"" << newFullFileName << (success ? L"\"" : L"\" - failue") << endl;
::error |= !success;
}
}
while( FindNextFileW( find.hFind, &fd ) );
if( GetLastError() != ERROR_NO_MORE_FILES )
::error = true,
wcerr << "error finding next file for pattern:" << endl <<
L"\"" << filePattern << L"\"" << endl;
find.close();
}
else
if( (dwError = GetLastError()) != ERROR_FILE_NOT_FOUND &&
dwError != ERROR_ACCESS_DENIED )
::error = true,
wcerr << "error finding first file for pattern:" << endl <<
L"\"" << filePattern << L"\"" << endl;
if( ::recurseThroughSubdirs )
{
wstring subdirPattern;
subdirPattern.reserve( pathLength + 1 );
subdirPattern.append( filePattern, pathLength );
subdirPattern += L"*";
if( (find.hFind = FindFirstFileW( subdirPattern.c_str(), &fd ))
!= INVALID_HANDLE_VALUE )
{
do
if( fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY &&
wcscmp( fd.cFileName, L"." ) != 0 && wcscmp(
fd.cFileName, L".." ) != 0 )
{
wstring nextFilePattern;
size_t nextPathLength;
nextPathLength = pathLength + wcslen( fd.cFileName
) + 1;
nextFilePattern.reserve( nextPathLength + wcslen(
filePattern + pathLength ) );
nextFilePattern.append( filePattern, pathLength );
nextFilePattern += fd.cFileName;
nextFilePattern += L"\\";
nextFilePattern += filePattern + pathLength;
renameFiles( nextFilePattern.c_str(), nextPathLength );
}
while( FindNextFileW( find.hFind, &fd ) );
if( GetLastError() != ERROR_NO_MORE_FILES )
::error = true,
wcerr << "error finding next directory for pattern:" <<
endl << L"\"" << subdirPattern << L"\"" << endl;
}
else
if( (dwError = GetLastError()) != ERROR_FILE_NOT_FOUND &&
dwError != ERROR_ACCESS_DENIED )
::error = true,
wcerr << "error finding first directory for pattern:"
<< endl << L"\"" << subdirPattern << L"\"" << endl;
}
}
bool caseCvt( wchar_t const *str, size_t length, bool uppercase, wstring
&newStr );
unsigned utf16Length( wchar_t const *ch, wchar_t const *end );
int isLowercaseOrUppercase( wchar_t const *ch, wchar_t const *end, bool
uppercase );
bool convertFileName( wchar_t const *oldFileName, size_t ofnLlength,
wstring &newFileName, bool uppercase, bool force )
{
newFileName.reserve( 2 * ofnLlength );
newFileName.resize( 0 );
if( force )
return caseCvt( oldFileName, ofnLlength, uppercase, newFileName );
for( wchar_t const *part = oldFileName,
*partsEnd = oldFileName + ofnLlength;
part != partsEnd; )
{
wchar_t const *partScn;
for( partScn = part; ; )
if( partScn != partsEnd && *partScn != L'.' )
{
int caseRet = isLowercaseOrUppercase( partScn,
partsEnd, uppercase );
if( caseRet < 0 )
return false;
partScn += utf16Length( partScn, partsEnd );
if( caseRet )
{
unsigned len;
for( ; partScn != partsEnd && *partScn != L'.'; )
if( (len = utf16Length( partScn, partsEnd )) != 0 )
partScn += len;
else
return false;
for( ; partScn != partsEnd && *partScn == L'.';
++partScn );
newFileName.append( part, partScn - part );
break;
}
}
else
{
wstring convertedPart;
if( !caseCvt( part, partScn - part, uppercase,
convertedPart ) )
return false;
newFileName += convertedPart;
if( partScn != partsEnd )
newFileName += L".",
++partScn;
break;
}
part = partScn;
}
return true;
}
bool caseCvt( wchar_t const *str, size_t length, bool uppercase, wstring
&newStr )
{
if( length == 0 )
{
newStr = L"";
return true;
}
DWORD dwMapFlags = !uppercase ? LCMAP_LOWERCASE : LCMAP_UPPERCASE;
size_t newSize = (size_t)LCMapStringEx( LOCALE_NAME_INVARIANT,
dwMapFlags, str, length, nullptr, 0, nullptr, nullptr, 0 );
if( newSize == 0 )
return false;
newStr.resize( newSize );
return LCMapStringEx( LOCALE_NAME_INVARIANT, dwMapFlags, str,
length, &newStr[0], newSize, nullptr, nullptr, 0 ) != 0;
}
inline
unsigned utf16Length( wchar_t const *ch, wchar_t const *end )
{
assert(ch < end);
if( (*ch & 0b1111'1000'0000'0000) != 0b1101'1000'0000'0000 )
return 1;
unsigned length = 2 - (*ch >> 10 & 1);
assert(length == 1 || (ch + 1) < end);
return (size_t)(end - ch) >= length ? length : 0;
}
int isLowercaseOrUppercase( wchar_t const *ch, wchar_t const *end, bool
uppercase )
{
wchar_t chConverted[2];
unsigned length = utf16Length( ch, end );
if( length == 0 )
return -1;
int newLength = LCMapStringEx( LOCALE_NAME_INVARIANT, !uppercase ?
LCMAP_LOWERCASE : LCMAP_UPPERCASE, ch, length, chConverted, 2, nullptr,
nullptr, 0 );
if( newLength == 0 )
return -2;
return newLength == length && memcmp( ch, chConverted, length *
sizeof(wchar_t) ) == 0 ? 1 : 0;
}
void usage()
{
wcout << L"-? help\n"
"-r / -s recurse through subdirectories\n"
"-u uppercase instead of lowercase\n"
"-f force lowercase / uppercase\n"
"-q quiet mode";
}