diff options
author | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 17:28:01 +0200 |
---|---|---|
committer | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 17:28:01 +0200 |
commit | fe9eb89ebc1b3c33cbac00a3fa095a14faef9113 (patch) | |
tree | 8a3bb620a9acb83fe0057061a86e8f2cb91a9fe1 /zen | |
parent | 5.21 (diff) | |
download | FreeFileSync-fe9eb89ebc1b3c33cbac00a3fa095a14faef9113.tar.gz FreeFileSync-fe9eb89ebc1b3c33cbac00a3fa095a14faef9113.tar.bz2 FreeFileSync-fe9eb89ebc1b3c33cbac00a3fa095a14faef9113.zip |
5.22
Diffstat (limited to 'zen')
-rw-r--r-- | zen/FindFilePlus/FindFilePlus.vcxproj | 10 | ||||
-rw-r--r-- | zen/IFileOperation/FileOperation_Vista.vcxproj | 18 | ||||
-rw-r--r-- | zen/IFileOperation/file_op.cpp | 12 | ||||
-rw-r--r-- | zen/com_util.h | 8 | ||||
-rw-r--r-- | zen/dir_watcher.cpp | 236 | ||||
-rw-r--r-- | zen/dir_watcher.h | 1 | ||||
-rw-r--r-- | zen/error_log.h | 2 | ||||
-rw-r--r-- | zen/file_error.h | 4 | ||||
-rw-r--r-- | zen/file_handling.cpp | 59 | ||||
-rw-r--r-- | zen/file_handling.h | 1 | ||||
-rw-r--r-- | zen/file_io.cpp | 11 | ||||
-rw-r--r-- | zen/file_io.h | 6 | ||||
-rw-r--r-- | zen/file_traverser.cpp | 10 | ||||
-rw-r--r-- | zen/file_traverser.h | 4 | ||||
-rw-r--r-- | zen/fixed_list.h | 4 | ||||
-rw-r--r-- | zen/format_unit.cpp | 15 | ||||
-rw-r--r-- | zen/i18n.h | 41 | ||||
-rw-r--r-- | zen/notify_removal.cpp | 8 | ||||
-rw-r--r-- | zen/osx_string.h | 2 | ||||
-rw-r--r-- | zen/privilege.cpp | 11 | ||||
-rw-r--r-- | zen/process_priority.cpp | 4 | ||||
-rw-r--r-- | zen/read_txt.cpp | 10 | ||||
-rw-r--r-- | zen/read_txt.h | 4 | ||||
-rw-r--r-- | zen/scroll_window_under_cursor.cpp | 6 | ||||
-rw-r--r-- | zen/shell_execute.h | 104 | ||||
-rw-r--r-- | zen/string_tools.h | 2 | ||||
-rw-r--r-- | zen/symlink_target.h | 2 | ||||
-rw-r--r-- | zen/sys_error.h | 13 | ||||
-rw-r--r-- | zen/zstring.cpp | 14 |
29 files changed, 443 insertions, 179 deletions
diff --git a/zen/FindFilePlus/FindFilePlus.vcxproj b/zen/FindFilePlus/FindFilePlus.vcxproj index 15ea6adf..56650735 100644 --- a/zen/FindFilePlus/FindFilePlus.vcxproj +++ b/zen/FindFilePlus/FindFilePlus.vcxproj @@ -78,10 +78,10 @@ <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">FindFilePlus_$(Platform)</TargetName> <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">FindFilePlus_$(Platform)</TargetName> <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">FindFilePlus_$(Platform)</TargetName> - <IncludePath Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">C:\Data\WinDDK\inc\ddk;C:\Data\WinDDK\inc\api;C:\Data\WinDDK\inc\crt;$(WindowsSdkDir)\include;$(VCInstallDir)include</IncludePath> - <IncludePath Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">C:\Data\WinDDK\inc\ddk;C:\Data\WinDDK\inc\api;C:\Data\WinDDK\inc\crt;$(WindowsSdkDir)\include;$(VCInstallDir)include</IncludePath> - <IncludePath Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">C:\Data\WinDDK\inc\ddk;C:\Data\WinDDK\inc\api;C:\Data\WinDDK\inc\crt;$(WindowsSdkDir)\include;$(VCInstallDir)include</IncludePath> - <IncludePath Condition="'$(Configuration)|$(Platform)'=='Release|x64'">C:\Data\WinDDK\inc\ddk;C:\Data\WinDDK\inc\api;C:\Data\WinDDK\inc\crt;$(WindowsSdkDir)\include;$(VCInstallDir)include</IncludePath> + <IncludePath Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">C:\Data\C++\WinDDK\inc\ddk;C:\Data\C++\WinDDK\inc\api;C:\Data\C++\WinDDK\inc\crt;$(WindowsSdkDir)\include;$(VCInstallDir)include</IncludePath> + <IncludePath Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">C:\Data\C++\WinDDK\inc\ddk;C:\Data\C++\WinDDK\inc\api;C:\Data\C++\WinDDK\inc\crt;$(WindowsSdkDir)\include;$(VCInstallDir)include</IncludePath> + <IncludePath Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">C:\Data\C++\WinDDK\inc\ddk;C:\Data\C++\WinDDK\inc\api;C:\Data\C++\WinDDK\inc\crt;$(WindowsSdkDir)\include;$(VCInstallDir)include</IncludePath> + <IncludePath Condition="'$(Configuration)|$(Platform)'=='Release|x64'">C:\Data\C++\WinDDK\inc\ddk;C:\Data\C++\WinDDK\inc\api;C:\Data\C++\WinDDK\inc\crt;$(WindowsSdkDir)\include;$(VCInstallDir)include</IncludePath> </PropertyGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> <BuildLog> @@ -102,6 +102,7 @@ <AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories> <SmallerTypeCheck>true</SmallerTypeCheck> <MultiProcessorCompilation>true</MultiProcessorCompilation> + <EnableEnhancedInstructionSet>NoExtensions</EnableEnhancedInstructionSet> </ClCompile> <Link> <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> @@ -171,6 +172,7 @@ <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed> <AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories> <MultiProcessorCompilation>true</MultiProcessorCompilation> + <EnableEnhancedInstructionSet>NoExtensions</EnableEnhancedInstructionSet> </ClCompile> <Link> <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> diff --git a/zen/IFileOperation/FileOperation_Vista.vcxproj b/zen/IFileOperation/FileOperation_Vista.vcxproj index 20f09a00..3ff45843 100644 --- a/zen/IFileOperation/FileOperation_Vista.vcxproj +++ b/zen/IFileOperation/FileOperation_Vista.vcxproj @@ -96,10 +96,11 @@ <SuppressStartupBanner>true</SuppressStartupBanner> <DebugInformationFormat>EditAndContinue</DebugInformationFormat> <DisableSpecificWarnings>4100;4996;4512</DisableSpecificWarnings> - <AdditionalIncludeDirectories>../..;C:\Program Files\C++\boost</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories>../..;C:\Data\C++\boost</AdditionalIncludeDirectories> <SmallerTypeCheck>true</SmallerTypeCheck> <ForcedIncludeFiles>zen/warn_static.h</ForcedIncludeFiles> <MultiProcessorCompilation>true</MultiProcessorCompilation> + <EnableEnhancedInstructionSet>NoExtensions</EnableEnhancedInstructionSet> </ClCompile> <Link> <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> @@ -111,7 +112,7 @@ </ProfileGuidedDatabase> <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> <TargetMachine>MachineX86</TargetMachine> - <AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage\lib</AdditionalLibraryDirectories> + <AdditionalLibraryDirectories>C:\Data\C++\Boost\stage\lib</AdditionalLibraryDirectories> <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup> @@ -134,7 +135,7 @@ <SuppressStartupBanner>true</SuppressStartupBanner> <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> <DisableSpecificWarnings>4100;4996;4512</DisableSpecificWarnings> - <AdditionalIncludeDirectories>../..;C:\Program Files\C++\boost</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories>../..;C:\Data\C++\boost</AdditionalIncludeDirectories> <SmallerTypeCheck>true</SmallerTypeCheck> <ForcedIncludeFiles>zen/warn_static.h</ForcedIncludeFiles> <MultiProcessorCompilation>true</MultiProcessorCompilation> @@ -149,7 +150,7 @@ </ProfileGuidedDatabase> <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> <TargetMachine>MachineX64</TargetMachine> - <AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage_x64\lib</AdditionalLibraryDirectories> + <AdditionalLibraryDirectories>C:\Data\C++\Boost\stage_x64\lib</AdditionalLibraryDirectories> <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup> @@ -170,9 +171,10 @@ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed> <DisableSpecificWarnings>4100;4996;4512</DisableSpecificWarnings> - <AdditionalIncludeDirectories>../..;C:\Program Files\C++\boost</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories>../..;C:\Data\C++\boost</AdditionalIncludeDirectories> <ForcedIncludeFiles>zen/warn_static.h</ForcedIncludeFiles> <MultiProcessorCompilation>true</MultiProcessorCompilation> + <EnableEnhancedInstructionSet>NoExtensions</EnableEnhancedInstructionSet> </ClCompile> <Link> <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> @@ -186,7 +188,7 @@ </ProfileGuidedDatabase> <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> <TargetMachine>MachineX86</TargetMachine> - <AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage\lib</AdditionalLibraryDirectories> + <AdditionalLibraryDirectories>C:\Data\C++\Boost\stage\lib</AdditionalLibraryDirectories> <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup> @@ -210,7 +212,7 @@ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed> <DisableSpecificWarnings>4100;4996;4512</DisableSpecificWarnings> - <AdditionalIncludeDirectories>../..;C:\Program Files\C++\boost</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories>../..;C:\Data\C++\boost</AdditionalIncludeDirectories> <ForcedIncludeFiles>zen/warn_static.h</ForcedIncludeFiles> <MultiProcessorCompilation>true</MultiProcessorCompilation> </ClCompile> @@ -226,7 +228,7 @@ </ProfileGuidedDatabase> <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> <TargetMachine>MachineX64</TargetMachine> - <AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage_x64\lib</AdditionalLibraryDirectories> + <AdditionalLibraryDirectories>C:\Data\C++\Boost\stage_x64\lib</AdditionalLibraryDirectories> <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup> diff --git a/zen/IFileOperation/file_op.cpp b/zen/IFileOperation/file_op.cpp index 218c6f99..27a2565b 100644 --- a/zen/IFileOperation/file_op.cpp +++ b/zen/IFileOperation/file_op.cpp @@ -229,7 +229,7 @@ void moveToRecycleBin(const wchar_t* fileNames[], //throw SysError if (!somethingExists(fileNames[i])) continue; } - throw SysError(formatComError(std::wstring(L"Error calling \"SHCreateItemFromParsingName\" for file:\n") + L"\'" + fileNames[i] + L"\'.", hr)); + throw SysError(formatComError(std::wstring(L"Error calling \"SHCreateItemFromParsingName\" for file:\n") + L"\"" + fileNames[i] + L"\".", hr)); } ZEN_COM_CHECK(fileOp->DeleteItem(psiFile.get(), nullptr)); @@ -256,11 +256,11 @@ void moveToRecycleBin(const wchar_t* fileNames[], //throw SysError if (!processes.empty()) { - std::wstring errorMsg = L"The file \'" + lastError->first + L"\' is locked by another process:"; + std::wstring errorMsg = L"The file \"" + lastError->first + L"\" is locked by another process:"; std::for_each(processes.begin(), processes.end(), [&](const std::wstring& proc) { errorMsg += L'\n'; errorMsg += proc; }); throw SysError(errorMsg); //message is descriptive enough, no need to evaluate HRESULT! } - throw SysError(formatComError(std::wstring(L"Error during \"PerformOperations\" for file:\n") + L"\'" + lastError->first + L"\'.", lastError->second)); + throw SysError(formatComError(std::wstring(L"Error during \"PerformOperations\" for file:\n") + L"\"" + lastError->first + L"\".", lastError->second)); } throw; } @@ -298,7 +298,7 @@ void copyFile(const wchar_t* sourceFile, //throw SysError nullptr, IID_PPV_ARGS(psiSourceFile.init())); if (FAILED(hr)) - throw SysError(formatComError(std::wstring(L"Error calling \"SHCreateItemFromParsingName\" for file:\n") + L"\'" + sourceFile + L"\'.", hr)); + throw SysError(formatComError(std::wstring(L"Error calling \"SHCreateItemFromParsingName\" for file:\n") + L"\"" + sourceFile + L"\".", hr)); } const size_t pos = std::wstring(targetFile).find_last_of(L'\\'); @@ -315,7 +315,7 @@ void copyFile(const wchar_t* sourceFile, //throw SysError nullptr, IID_PPV_ARGS(psiTargetFolder.init())); if (FAILED(hr)) - throw SysError(formatComError(std::wstring(L"Error calling \"SHCreateItemFromParsingName\" for folder:\n") + L"\'" + targetFolder + L"\'.", hr)); + throw SysError(formatComError(std::wstring(L"Error calling \"SHCreateItemFromParsingName\" for folder:\n") + L"\"" + targetFolder + L"\".", hr)); } //schedule file copy operation @@ -440,7 +440,7 @@ std::vector<std::wstring> getLockingProcesses(const wchar_t* filename) //throw S &buffer[0], //__out LPTSTR lpExeName, &bufferSize)) //__inout PDWORD lpdwSize if (bufferSize < buffer.size()) - processName += std::wstring(L", ") + L"\'" + &buffer[0] + L"\'"; + processName += std::wstring(L", ") + L"\"" + &buffer[0] + L"\""; } } output.push_back(processName); diff --git a/zen/com_util.h b/zen/com_util.h index 4f7cd0b8..5189b48e 100644 --- a/zen/com_util.h +++ b/zen/com_util.h @@ -15,7 +15,7 @@ namespace zen { //get an enumeration interface as a std::vector of bound(!) ComPtr(s) template <class T, class U> -std::vector<ComPtr<T> > convertEnum(const ComPtr<U>& enumObj); //enumObj: must have the "_NewEnum" property that supports the IEnumUnknown interface +std::vector<ComPtr<T>> convertEnum(const ComPtr<U>& enumObj); //enumObj: must have the "_NewEnum" property that supports the IEnumUnknown interface /* extract text from com object member function returning a single BSTR: HRESULT ComInterface::MemFun([out] BSTR *pbstr); @@ -68,9 +68,9 @@ private: //############################ inline implemenatation ################################## template <class T, class U> inline -std::vector<ComPtr<T> > convertEnum(const ComPtr<U>& enumObj) +std::vector<ComPtr<T>> convertEnum(const ComPtr<U>& enumObj) { - std::vector<ComPtr<T> > output; + std::vector<ComPtr<T>> output; if (enumObj) { @@ -118,4 +118,4 @@ std::wstring getText(ComPtr<T> comObj, MemFun memFun) } -#endif //COM_UTILITY_HEADER
\ No newline at end of file +#endif //COM_UTILITY_HEADER diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp index e53b63e2..17efda00 100644 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -21,7 +21,10 @@ #include "file_traverser.h" #elif defined ZEN_MAC -//#include <CoreFoundation/FSEvents.h> +//#include <sys/types.h> +#include <sys/event.h> +//#include <sys/time.h> +#include "file_traverser.h" #endif using namespace zen; @@ -39,7 +42,7 @@ public: boost::lock_guard<boost::mutex> dummy(lockAccess); if (bytesWritten == 0) //according to docu this may happen in case of internal buffer overflow: report some "dummy" change - changedFiles.push_back(DirWatcher::Entry(DirWatcher::ACTION_CREATE, L"Overflow!")); + changedFiles.push_back(DirWatcher::Entry(DirWatcher::ACTION_CREATE, L"Overflow.")); else { const char* bufPos = &buffer[0]; @@ -49,13 +52,10 @@ public: const Zstring fullname = dirname + Zstring(notifyInfo.FileName, notifyInfo.FileNameLength / sizeof(WCHAR)); - //skip modifications sent by changed directories: reason for change, child element creation/deletion, will notify separately! - //and if this child element is a .ffs_lock file we'll want to ignore all associated events! [&] { - //if (notifyInfo.Action == FILE_ACTION_RENAMED_OLD_NAME) //reporting FILE_ACTION_RENAMED_NEW_NAME should suffice; - // return; //note: this is NOT a cross-directory move, which will show up as create + delete - + //skip modifications sent by changed directories: reason for change, child element creation/deletion, will notify separately! + //and if this child element is a .ffs_lock file we'll want to ignore all associated events! if (notifyInfo.Action == FILE_ACTION_MODIFIED) { //note: this check will not work if top watched directory has been renamed @@ -64,6 +64,7 @@ public: return; } + //note: a move across directories will show up as FILE_ACTION_ADDED/FILE_ACTION_REMOVED! switch (notifyInfo.Action) { case FILE_ACTION_ADDED: @@ -156,12 +157,7 @@ public: FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, nullptr); if (hDir == INVALID_HANDLE_VALUE) - { - const DWORD lastError = ::GetLastError(); //copy before making other system calls! - const std::wstring errorMsg = replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(directory)); - const std::wstring errorDescr = formatSystemError(L"CreateFile", lastError); - throw FileError(errorMsg, errorDescr); - } + throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(directory)), formatSystemError(L"CreateFile", getLastError())); //end of constructor, no need to start managing "hDir" } @@ -374,14 +370,6 @@ std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void() #elif defined ZEN_LINUX -struct DirWatcher::Pimpl -{ - Zstring dirname; - int notifDescr; - std::map<int, Zstring> watchDescrs; //watch descriptor and corresponding (sub-)directory name (postfixed with separator!) -}; - - namespace { class DirsOnlyTraverser : public zen::TraverseCallback @@ -396,8 +384,8 @@ public: dirs_.push_back(fullName); return this; } - virtual HandleError reportDirError (const std::wstring& msg) { throw FileError(msg); } - virtual HandleError reportItemError(const std::wstring& msg, const Zchar* shortName) { throw FileError(msg); } + virtual HandleError reportDirError (const std::wstring& msg, size_t retryNumber) { throw FileError(msg); } + virtual HandleError reportItemError(const std::wstring& msg, size_t retryNumber, const Zchar* shortName) { throw FileError(msg); } private: std::vector<Zstring>& dirs_; @@ -405,26 +393,33 @@ private: } +struct DirWatcher::Pimpl +{ + Zstring baseDirname; + int notifDescr; + std::map<int, Zstring> watchDescrs; //watch descriptor and (sub-)directory name (postfixed with separator) -> owned by "notifDescr" +}; + + DirWatcher::DirWatcher(const Zstring& directory) : //throw FileError pimpl_(new Pimpl) { - //still in main thread + //get all subdirectories Zstring dirname = directory; if (endsWith(dirname, FILE_NAME_SEPARATOR)) dirname.resize(dirname.size() - 1); - //get all subdirectories - std::vector<Zstring> fullDirList; - fullDirList.push_back(dirname); - - DirsOnlyTraverser traverser(fullDirList); //throw FileError - zen::traverseFolder(dirname, traverser); //don't traverse into symlinks (analog to windows build) + std::vector<Zstring> fullDirList { dirname }; + { + DirsOnlyTraverser traverser(fullDirList); //throw FileError + zen::traverseFolder(dirname, traverser); //don't traverse into symlinks (analog to windows build) + } //init - pimpl_->dirname = directory; + pimpl_->baseDirname = directory; pimpl_->notifDescr = ::inotify_init(); if (pimpl_->notifDescr == -1) - throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(dirname)), formatSystemError(L"inotify_init", getLastError())); + throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(directory)), formatSystemError(L"inotify_init", getLastError())); zen::ScopeGuard guardDescr = zen::makeGuard([&] { ::close(pimpl_->notifDescr); }); @@ -436,14 +431,13 @@ DirWatcher::DirWatcher(const Zstring& directory) : //throw FileError initSuccess = ::fcntl(pimpl_->notifDescr, F_SETFL, flags | O_NONBLOCK) != -1; } if (!initSuccess) - throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(dirname)), formatSystemError(L"fcntl", getLastError())); + throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(directory)), formatSystemError(L"fcntl", getLastError())); //add watches - std::for_each(fullDirList.begin(), fullDirList.end(), - [&](Zstring subdir) + for (const Zstring& subdir : fullDirList) { int wd = ::inotify_add_watch(pimpl_->notifDescr, subdir.c_str(), - IN_ONLYDIR | //watch directories only + IN_ONLYDIR | //"Only watch pathname if it is a directory." IN_DONT_FOLLOW | //don't follow symbolic links IN_CREATE | IN_MODIFY | @@ -454,15 +448,10 @@ DirWatcher::DirWatcher(const Zstring& directory) : //throw FileError IN_MOVED_TO | IN_MOVE_SELF); if (wd == -1) - { - const ErrorCode lastError = getLastError(); //copy before making other system calls! - const std::wstring errorMsg = replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(subdir)); - const std::wstring errorDescr = formatSystemError(L"inotify_add_watch", lastError); - throw FileError(errorMsg, errorDescr); - } + throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(subdir)), formatSystemError(L"inotify_add_watch", getLastError())); pimpl_->watchDescrs.insert(std::make_pair(wd, appendSeparator(subdir))); - }); + } guardDescr.dismiss(); } @@ -476,12 +465,12 @@ DirWatcher::~DirWatcher() std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void()>&) //throw FileError { - //non-blocking call, see O_NONBLOCK - std::vector<char> buffer(1024 * (sizeof(struct ::inotify_event) + 16)); + std::vector<char> buffer(512 * (sizeof(struct ::inotify_event) + NAME_MAX + 1)); ssize_t bytesRead = 0; do { + //non-blocking call, see O_NONBLOCK bytesRead = ::read(pimpl_->notifDescr, &buffer[0], buffer.size()); } while (bytesRead < 0 && errno == EINTR); //"Interrupted function call; When this happens, you should try the call again." @@ -491,7 +480,7 @@ std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void() if (errno == EAGAIN) //this error is ignored in all inotify wrappers I found return std::vector<Entry>(); - throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(pimpl_->dirname)), formatSystemError(L"read", getLastError())); + throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(pimpl_->baseDirname)), formatSystemError(L"read", getLastError())); } std::vector<Entry> output; @@ -530,27 +519,178 @@ std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void() } #elif defined ZEN_MAC +namespace +{ +class DirsOnlyTraverser : public zen::TraverseCallback +{ +public: + DirsOnlyTraverser(std::vector<Zstring>& dirs) : dirs_(dirs) {} + + virtual void onFile (const Zchar* shortName, const Zstring& fullName, const FileInfo& details) {} + virtual HandleLink onSymlink(const Zchar* shortName, const Zstring& fullName, const SymlinkInfo& details) { return LINK_SKIP; } + virtual TraverseCallback* onDir(const Zchar* shortName, const Zstring& fullName) + { + dirs_.push_back(fullName); + return this; + } + virtual HandleError reportDirError (const std::wstring& msg, size_t retryNumber) { throw FileError(msg); } + virtual HandleError reportItemError(const std::wstring& msg, size_t retryNumber, const Zchar* shortName) { throw FileError(msg); } + +private: + std::vector<Zstring>& dirs_; +}; + + +class DirDescriptor //throw FileError +{ +public: + DirDescriptor(const Zstring& dirname) : dirname_(dirname) + { + fdDir = ::open(dirname.c_str(), O_EVTONLY); //"descriptor requested for event notifications only"; O_EVTONLY does not exist on Linux + if (fdDir == -1) + throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(dirname)), formatSystemError(L"open", getLastError())); + } + + ~DirDescriptor() { if (fdDir != -1) ::close(fdDir); } //check for "-1" only needed by move-constructor + + DirDescriptor(DirDescriptor&& other) : fdDir(other.fdDir), dirname_(std::move(other.dirname_)) { other.fdDir = -1; } + + int getDescriptor() const { return fdDir; } + Zstring getDirname() const { return dirname_; } + +private: + DirDescriptor(const DirDescriptor&) = delete; + DirDescriptor& operator=(const DirDescriptor&) = delete; + + int fdDir; + Zstring dirname_; +}; +} warn_static("finish") struct DirWatcher::Pimpl { + Zstring baseDirname; + int queueDescr; + std::map<int, DirDescriptor> watchDescrs; //directory descriptors and corresponding (sub-)directory name (postfixed with separator!) + std::vector<struct ::kevent> changelist; }; -DirWatcher::DirWatcher(const Zstring& directory) //throw FileError +DirWatcher::DirWatcher(const Zstring& directory) : //throw FileError + pimpl_(new Pimpl) { - throw FileError(L"Dir Watcher is not yet implemented!"); + //get all subdirectories + Zstring dirname = directory; + if (endsWith(dirname, FILE_NAME_SEPARATOR)) + dirname.resize(dirname.size() - 1); + + std::vector<Zstring> fullDirList { dirname }; + { + DirsOnlyTraverser traverser(fullDirList); //throw FileError + zen::traverseFolder(dirname, traverser); //don't traverse into symlinks (analog to windows build) + } + + pimpl_->baseDirname = directory; + + pimpl_->queueDescr = ::kqueue(); + if (pimpl_->queueDescr == -1) + throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(directory)), formatSystemError(L"kqueue", getLastError())); + zen::ScopeGuard guardDescr = zen::makeGuard([&] { ::close(pimpl_->queueDescr); }); + + for (const Zstring& subdir : fullDirList) + { + DirDescriptor descr(subdir); + const int rawDescr = descr.getDescriptor(); + pimpl_->watchDescrs.insert(std::make_pair(rawDescr, std::move(descr))); + + pimpl_->changelist.push_back({}); + EV_SET(&pimpl_->changelist.back(), + rawDescr, //identifier for this event + EVFILT_VNODE, //filter for event + EV_ADD | EV_CLEAR, //general flags + NOTE_DELETE | NOTE_REVOKE | NOTE_RENAME | NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB, //filter-specific flags + 0, //filter-specific data + nullptr); //opaque user data identifier + } + + //what about EINTR? + struct ::timespec timeout = {}; //=> poll + if (::kevent(pimpl_->queueDescr, &pimpl_->changelist[0], pimpl_->changelist.size(), nullptr, 0, &timeout) < 0) + throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(directory)), formatSystemError(L"kevent", getLastError())); + + guardDescr.dismiss(); } DirWatcher::~DirWatcher() { + ::close(pimpl_->queueDescr); } std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void()>&) //throw FileError { std::vector<Entry> output; + + std::vector<struct ::kevent> events(512); + for (;;) + { + assert(!pimpl_->changelist.empty()); //contains at least parent directory + struct ::timespec timeout = {}; //=> poll + + int evtCount = 0; + do + { + evtCount = ::kevent(pimpl_->queueDescr, //int kq, + &pimpl_->changelist[0], //const struct kevent* changelist, + pimpl_->changelist.size(), //int nchanges, + &events[0], //struct kevent* eventlist, + events.size(), //int nevents, + &timeout); //const struct timespec* timeout + } + while (evtCount < 0 && errno == EINTR); + + if (evtCount == -1) + throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(pimpl_->baseDirname)), formatSystemError(L"kevent", getLastError())); + + for (int i = 0; i < evtCount; ++i) + { + const auto& evt = events[i]; + + auto it = pimpl_->watchDescrs.find(static_cast<int>(evt.ident)); + if (it == pimpl_->watchDescrs.end()) + throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(pimpl_->baseDirname)), L"Received event from unknown source."); + + //"If an error occurs [...] and there is enough room in the eventlist, then the event will + // be placed in the eventlist with EV_ERROR set in flags and the system error in data." + if (evt.flags & EV_ERROR) + throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(it->second.getDirname())), formatSystemError(L"kevent", static_cast<ErrorCode>(evt.data))); + + assert(evt.filter == EVFILT_VNODE); + if (evt.filter == EVFILT_VNODE) + { + if (evt.fflags & NOTE_DELETE) + wxMessageBox(L"NOTE_DELETE "+ it->second.getDirname()); + else if (evt.fflags & NOTE_REVOKE) + wxMessageBox(L"NOTE_REVOKE "+ it->second.getDirname()); + else if (evt.fflags & NOTE_RENAME) + wxMessageBox(L"NOTE_RENAME "+ it->second.getDirname()); + else if (evt.fflags & NOTE_WRITE) + wxMessageBox(L"NOTE_WRITE "+ it->second.getDirname()); + else if (evt.fflags & NOTE_EXTEND) + wxMessageBox(L"NOTE_EXTEND "+ it->second.getDirname()); + else if (evt.fflags & NOTE_ATTRIB) + wxMessageBox(L"NOTE_ATTRIB "+ it->second.getDirname()); + else + assert(false); + } + } + + if (evtCount < events.size()) + break; + } + return output; } #endif diff --git a/zen/dir_watcher.h b/zen/dir_watcher.h index eaee5aab..233bdc59 100644 --- a/zen/dir_watcher.h +++ b/zen/dir_watcher.h @@ -16,6 +16,7 @@ namespace zen { //Windows: ReadDirectoryChangesW http://msdn.microsoft.com/en-us/library/aa365465(v=vs.85).aspx //Linux: inotify http://linux.die.net/man/7/inotify +//OS X: kqueue http://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/kqueue.2.html //watch directory including subdirectories /* diff --git a/zen/error_log.h b/zen/error_log.h index 05049d27..de4d3c9e 100644 --- a/zen/error_log.h +++ b/zen/error_log.h @@ -96,7 +96,7 @@ String formatMessageImpl(const LogEntry& entry) //internal linkage case TYPE_ERROR: return _("Error"); case TYPE_FATAL_ERROR: - return _("Fatal Error"); + return _("Serious Error"); } assert(false); return std::wstring(); diff --git a/zen/file_error.h b/zen/file_error.h index db8b371d..5d655239 100644 --- a/zen/file_error.h +++ b/zen/file_error.h @@ -49,9 +49,9 @@ inline std::wstring fmtFileName(const Zstring& filename) { std::wstring output; - output += L'\''; + output += L'\"'; output += utfCvrtTo<std::wstring>(filename); - output += L'\''; + output += L'\"'; return output; } } diff --git a/zen/file_handling.cpp b/zen/file_handling.cpp index 3f8d5bbd..fd4239ed 100644 --- a/zen/file_handling.cpp +++ b/zen/file_handling.cpp @@ -205,29 +205,29 @@ void getFileAttrib(const Zstring& filename, FileAttrib& attr, ProcSymlink procSl const int rv = procSl == SYMLINK_FOLLOW ? :: stat(filename.c_str(), &fileInfo) : ::lstat(filename.c_str(), &fileInfo); - if (rv != 0) //follow symbolic links + if (rv != 0) throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(filename)), formatSystemError(L"stat", getLastError())); attr.fileSize = UInt64(fileInfo.st_size); attr.modificationTime = fileInfo.st_mtime; #endif } -} -UInt64 zen::getFilesize(const Zstring& filename) //throw FileError +Int64 getFileTime(const Zstring& filename, ProcSymlink procSl) //throw FileError { FileAttrib attr; - getFileAttrib(filename, attr, SYMLINK_FOLLOW); //throw FileError - return attr.fileSize; + getFileAttrib(filename, attr, procSl); //throw FileError + return attr.modificationTime; +} } -Int64 zen::getFileTime(const Zstring& filename, ProcSymlink procSl) //throw FileError +UInt64 zen::getFilesize(const Zstring& filename) //throw FileError { FileAttrib attr; - getFileAttrib(filename, attr, procSl); //throw FileError - return attr.modificationTime; + getFileAttrib(filename, attr, SYMLINK_FOLLOW); //throw FileError + return attr.fileSize; } @@ -537,8 +537,8 @@ public: dirs_.push_back(fullName); return nullptr; //DON'T traverse into subdirs; removeDirectory works recursively! } - virtual HandleError reportDirError (const std::wstring& msg) { throw FileError(msg); } - virtual HandleError reportItemError(const std::wstring& msg, const Zchar* shortName) { throw FileError(msg); } + virtual HandleError reportDirError (const std::wstring& msg, size_t retryNumber) { throw FileError(msg); } + virtual HandleError reportItemError(const std::wstring& msg, size_t retryNumber, const Zchar* shortName) { throw FileError(msg); } private: CollectFilesFlat(const CollectFilesFlat&); @@ -1821,14 +1821,17 @@ struct CallbackData const Zstring& targetFile) : sourceFile_(sourceFile), targetFile_(targetFile), - userCallback(cb) {} + userCallback(cb), + fileInfoSrc(), + fileInfoTrg() {} const Zstring& sourceFile_; const Zstring& targetFile_; CallbackCopyFile* const userCallback; //optional! ErrorHandling errorHandler; - FileAttrib newAttrib; //modified by CopyFileEx() at beginning + BY_HANDLE_FILE_INFORMATION fileInfoSrc; //modified by CopyFileEx() at beginning + BY_HANDLE_FILE_INFORMATION fileInfoTrg; // Int64 bytesReported; //used internally to calculate bytes transferred delta }; @@ -1852,7 +1855,7 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize, if source is a symlink and COPY_FILE_COPY_SYMLINK is NOT specified, this callback is called and hSourceFile is a handle to the *target* of the link! file time handling: - ::CopyFileEx() will copy file modification time (only) over from source file AFTER the last invokation of this callback + ::CopyFileEx() will (only) copy file modification time over from source file AFTER the last invokation of this callback => it is possible to adapt file creation time of target in here, but NOT file modification time! CAVEAT: if ::CopyFileEx() fails to set modification time, it silently ignores this error and returns success!!! see procmon log in: https://sourceforge.net/tracker/?func=detail&atid=1093080&aid=3514569&group_id=234430 @@ -1870,38 +1873,31 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize, dwStreamNumber == 1) //consider ADS! { //#################### return source file attributes ################################ - BY_HANDLE_FILE_INFORMATION fileInfoSrc = {}; - if (!::GetFileInformationByHandle(hSourceFile, &fileInfoSrc)) + if (!::GetFileInformationByHandle(hSourceFile, &cbd.fileInfoSrc)) { cbd.errorHandler.reportError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(cbd.sourceFile_)), formatSystemError(L"GetFileInformationByHandle", getLastError())); return PROGRESS_CANCEL; } - BY_HANDLE_FILE_INFORMATION fileInfoTrg = {}; - if (!::GetFileInformationByHandle(hDestinationFile, &fileInfoTrg)) + if (!::GetFileInformationByHandle(hDestinationFile, &cbd.fileInfoTrg)) { cbd.errorHandler.reportError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(cbd.targetFile_)), formatSystemError(L"GetFileInformationByHandle", getLastError())); return PROGRESS_CANCEL; } - cbd.newAttrib.fileSize = UInt64(fileInfoSrc.nFileSizeLow, fileInfoSrc.nFileSizeHigh); - cbd.newAttrib.modificationTime = toTimeT(fileInfoSrc.ftLastWriteTime); //no DST hack (yet) - cbd.newAttrib.sourceFileId = extractFileID(fileInfoSrc); - cbd.newAttrib.targetFileId = extractFileID(fileInfoTrg); - //#################### switch to sparse file copy if req. ####################### - if (canCopyAsSparse(fileInfoSrc.dwFileAttributes, cbd.targetFile_)) //throw () + if (canCopyAsSparse(cbd.fileInfoSrc.dwFileAttributes, cbd.targetFile_)) //throw () { cbd.errorHandler.reportErrorShouldCopyAsSparse(); //use a different copy routine! return PROGRESS_CANCEL; } //#################### copy file creation time ################################ - ::SetFileTime(hDestinationFile, &fileInfoSrc.ftCreationTime, nullptr, nullptr); //no error handling! + ::SetFileTime(hDestinationFile, &cbd.fileInfoSrc.ftCreationTime, nullptr, nullptr); //no error handling! //#################### copy NTFS compressed attribute ######################### - const bool sourceIsCompressed = (fileInfoSrc.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0; - const bool targetIsCompressed = (fileInfoTrg.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0; //already set by CopyFileEx if target parent folder is compressed! + const bool sourceIsCompressed = (cbd.fileInfoSrc.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0; + const bool targetIsCompressed = (cbd.fileInfoTrg.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0; //already set by CopyFileEx if target parent folder is compressed! if (sourceIsCompressed && !targetIsCompressed) { USHORT cmpState = COMPRESSION_FORMAT_DEFAULT; @@ -2026,7 +2022,7 @@ void copyFileWindowsDefault(const Zstring& sourceFile, if (lastError == ERROR_INVALID_PARAMETER && dst::isFatDrive(targetFile) && getFilesize(sourceFile) >= 4U * UInt64(1024U * 1024 * 1024)) //throw FileError - errorDescr += L"\nFAT volume cannot store files larger than 4 gigabyte!"; + errorDescr += L"\nFAT volumes cannot store files larger than 4 gigabyte."; //note: ERROR_INVALID_PARAMETER can also occur when copying to a SharePoint server or MS SkyDrive and the target filename is of a restricted type. } @@ -2036,13 +2032,18 @@ void copyFileWindowsDefault(const Zstring& sourceFile, } if (newAttrib) - *newAttrib = cbd.newAttrib; + { + newAttrib->fileSize = UInt64(cbd.fileInfoSrc.nFileSizeLow, cbd.fileInfoSrc.nFileSizeHigh); + newAttrib->modificationTime = toTimeT(cbd.fileInfoSrc.ftLastWriteTime); //no DST hack (yet) + newAttrib->sourceFileId = extractFileID(cbd.fileInfoSrc); + newAttrib->targetFileId = extractFileID(cbd.fileInfoTrg); + } { //DST hack const Int64 modTime = getFileTime(sourceFile, SYMLINK_FOLLOW); //throw FileError setFileTime(targetFile, modTime, SYMLINK_FOLLOW); //throw FileError - //caveat: - ::CopyFileEx() silently *ignores* failure to set modification time!!! => we need to set again in order to catch such errors! + //caveat: - ::CopyFileEx() silently *ignores* failure to set modification time!!! => we need to set it again but with proper error checking! // - this sequence leads to a loss of precision of up to 1 sec! // - perf-loss on USB sticks with many small files of about 30%! damn! diff --git a/zen/file_handling.h b/zen/file_handling.h index c437e7bc..2c4f7938 100644 --- a/zen/file_handling.h +++ b/zen/file_handling.h @@ -29,7 +29,6 @@ enum ProcSymlink SYMLINK_FOLLOW }; -Int64 getFileTime(const Zstring& filename, ProcSymlink procSl); //throw FileError void setFileTime(const Zstring& filename, const Int64& modificationTime, ProcSymlink procSl); //throw FileError //symlink handling: always evaluate target diff --git a/zen/file_io.cpp b/zen/file_io.cpp index 45cbd028..fbca9089 100644 --- a/zen/file_io.cpp +++ b/zen/file_io.cpp @@ -177,7 +177,7 @@ size_t FileInput::read(void* buffer, size_t bytesToRead) //returns actual number if (bytesRead < bytesToRead) if (!eof()) //pathologic!? - throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilename())), L"Incomplete read!"); //user should never see this + throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilename())), L"Incomplete read."); //user should never see this #endif if (bytesRead > bytesToRead) @@ -305,7 +305,7 @@ void FileOutput::write(const void* buffer, size_t bytesToWrite) //throw FileErro throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilename())), formatSystemError(functionName, getLastError())); if (bytesWritten != bytesToWrite) //must be fulfilled for synchronous writes! - throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilename())), L"Incomplete write!"); //user should never see this + throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilename())), L"Incomplete write."); //user should never see this } @@ -318,12 +318,7 @@ FileInputUnbuffered::FileInputUnbuffered(const Zstring& filename) : FileInputBas fdFile = ::open(filename.c_str(), O_RDONLY); if (fdFile == -1) //don't check "< 0" -> docu seems to allow "-2" to be a valid file handle - { - const ErrorCode lastError = getLastError(); //copy before making other system calls! - const std::wstring errorMsg = replaceCpy(_("Cannot open file %x."), L"%x", fmtFileName(filename)); - const std::wstring errorDescr = formatSystemError(L"open", lastError); - throw FileError(errorMsg, errorDescr); - } + throw FileError(replaceCpy(_("Cannot open file %x."), L"%x", fmtFileName(filename)), formatSystemError(L"open", getLastError())); } diff --git a/zen/file_io.h b/zen/file_io.h index 0c7506a1..2e9e828e 100644 --- a/zen/file_io.h +++ b/zen/file_io.h @@ -22,10 +22,8 @@ namespace zen { #ifdef ZEN_WIN static const char LINE_BREAK[] = "\r\n"; -#elif defined ZEN_LINUX -static const char LINE_BREAK[] = "\n"; -#elif defined ZEN_MAC -static const char LINE_BREAK[] = "\r"; +#elif defined ZEN_LINUX || defined ZEN_MAC +static const char LINE_BREAK[] = "\n"; //since OS X apple uses newline, too #endif //buffered file IO optimized for sequential read/write accesses + better error reporting + long path support (following symlinks) diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp index a3e8491a..959a6071 100644 --- a/zen/file_traverser.cpp +++ b/zen/file_traverser.cpp @@ -36,7 +36,7 @@ namespace template <class Command> inline //function object expecting to throw FileError if operation fails bool tryReportingDirError(Command cmd, zen::TraverseCallback& callback) //return "true" on success, "false" if error was ignored { - for (;;) + for (size_t retryNumber = 0;; ++retryNumber) try { cmd(); //throw FileError @@ -44,7 +44,7 @@ bool tryReportingDirError(Command cmd, zen::TraverseCallback& callback) //return } catch (const FileError& e) { - switch (callback.reportDirError(e.toString())) + switch (callback.reportDirError(e.toString(), retryNumber)) { case TraverseCallback::ON_ERROR_RETRY: break; @@ -57,7 +57,7 @@ bool tryReportingDirError(Command cmd, zen::TraverseCallback& callback) //return template <class Command> inline //function object expecting to throw FileError if operation fails bool tryReportingItemError(Command cmd, zen::TraverseCallback& callback, const Zchar* shortName) //return "true" on success, "false" if error was ignored { - for (;;) + for (size_t retryNumber = 0;; ++retryNumber) try { cmd(); //throw FileError @@ -65,7 +65,7 @@ bool tryReportingItemError(Command cmd, zen::TraverseCallback& callback, const Z } catch (const FileError& e) { - switch (callback.reportItemError(e.toString(), shortName)) + switch (callback.reportItemError(e.toString(), retryNumber, shortName)) { case TraverseCallback::ON_ERROR_RETRY: break; @@ -559,7 +559,7 @@ private: { bufferUtfDecomposed.resize(lenMax); if (::CFStringGetFileSystemRepresentation(cfStr, &bufferUtfDecomposed[0], lenMax)) //get decomposed UTF form (verified!) despite ambiguous documentation - shortName = &bufferUtfDecomposed[0]; + shortName = &bufferUtfDecomposed[0]; //attention: => don't access "shortName" after recursion in "traverse"! } } //const char* sampleDecomposed = "\x6f\xcc\x81.txt"; diff --git a/zen/file_traverser.h b/zen/file_traverser.h index a1d8ac9e..d686e9b8 100644 --- a/zen/file_traverser.h +++ b/zen/file_traverser.h @@ -52,8 +52,8 @@ struct TraverseCallback //nullptr: ignore directory, non-nullptr: traverse into using the (new) callback => implement releaseDirTraverser() if necessary! virtual void releaseDirTraverser(TraverseCallback* trav) {} - virtual HandleError reportDirError (const std::wstring& msg) = 0; //failed directory traversal -> consider directory data as incomplete! - virtual HandleError reportItemError(const std::wstring& msg, const Zchar* shortName) = 0; //failed to get data for single file/dir/symlink only! + virtual HandleError reportDirError (const std::wstring& msg, size_t retryNumber) = 0; //failed directory traversal -> consider directory data as incomplete! + virtual HandleError reportItemError(const std::wstring& msg, size_t retryNumber, const Zchar* shortName) = 0; //failed to get data for single file/dir/symlink only! }; diff --git a/zen/fixed_list.h b/zen/fixed_list.h index e3083c89..d38dbae5 100644 --- a/zen/fixed_list.h +++ b/zen/fixed_list.h @@ -12,7 +12,7 @@ namespace zen { //std::list(C++11) compatible class for inplace element construction supporting non-copyable/movable types -//may be replaced by C++11 std::list when available +//may be replaced by C++11 std::list when available...or never... template <class T> class FixedList { @@ -48,7 +48,7 @@ public: ListIterator& operator++() { iter = iter->next; return *this; } inline friend bool operator==(const ListIterator& lhs, const ListIterator& rhs) { return lhs.iter == rhs.iter; } inline friend bool operator!=(const ListIterator& lhs, const ListIterator& rhs) { return !(lhs == rhs); } - U& operator* () { return iter->val; } + U& operator* () { return iter->val; } U* operator->() { return &iter->val; } private: NodeT* iter; diff --git a/zen/format_unit.cpp b/zen/format_unit.cpp index 4f16e3e1..4b39d5a9 100644 --- a/zen/format_unit.cpp +++ b/zen/format_unit.cpp @@ -40,7 +40,7 @@ std::wstring zen::filesizeToShortString(Int64 size) //if (size < 0) return _("Error"); -> really? if (numeric::abs(size) <= 999) - return replaceCpy(_P("1 byte", "%x bytes", to<int>(size)), L"%x", numberTo<std::wstring>(size)); + return _P("1 byte", "%x bytes", to<int>(size)); double sizeInUnit = to<double>(size); auto formatUnit = [&](const std::wstring& unitTxt) { return replaceCpy(unitTxt, L"%x", formatThreeDigitPrecision(sizeInUnit)); }; @@ -79,17 +79,16 @@ enum UnitRemTime std::wstring formatUnitTime(int val, UnitRemTime unit) { - auto subst = [&](const std::wstring& output) { return replaceCpy(output, L"%x", zen::numberTo<std::wstring>(val)); }; switch (unit) { case URT_SEC: - return subst(_P("1 sec", "%x sec", val)); + return _P("1 sec", "%x sec", val); case URT_MIN: - return subst(_P("1 min", "%x min", val)); + return _P("1 min", "%x min", val); case URT_HOUR: - return subst(_P("1 hour", "%x hours", val)); + return _P("1 hour", "%x hours", val); case URT_DAY: - return subst(_P("1 day", "%x days", val)); + return _P("1 day", "%x days", val); } assert(false); return _("Error"); @@ -256,8 +255,8 @@ std::wstring zen::ffs_Impl::includeNumberSeparator(const std::wstring& number) const lconv* localInfo = ::localeconv(); //always bound according to doc const std::wstring& thousandSep = utfCvrtTo<std::wstring>(localInfo->thousands_sep); - // THOUSANDS_SEPARATOR = std::use_facet<std::numpunct<wchar_t> >(std::locale("")).thousands_sep(); - why not working? - // DECIMAL_POINT = std::use_facet<std::numpunct<wchar_t> >(std::locale("")).decimal_point(); + // THOUSANDS_SEPARATOR = std::use_facet<std::numpunct<wchar_t>>(std::locale("")).thousands_sep(); - why not working? + // DECIMAL_POINT = std::use_facet<std::numpunct<wchar_t>>(std::locale("")).decimal_point(); std::wstring output(number); size_t i = output.size(); @@ -9,16 +9,20 @@ #include <string> #include <memory> +#include <cstdint> +#include "string_tools.h" + //minimal layer enabling text translation - without platform/library dependencies! #ifndef WXINTL_NO_GETTEXT_MACRO #error WXINTL_NO_GETTEXT_MACRO must be defined to deactivate wxWidgets underscore macro #endif -#define ZEN_CONCAT_SUB(X, Y) X ## Y -#define _(s) zen::implementation::translate(ZEN_CONCAT_SUB(L, s)) -#define _P(s, p, n) zen::implementation::translate(ZEN_CONCAT_SUB(L, s), ZEN_CONCAT_SUB(L, p), n) - +#define ZEN_TRANS_CONCAT_SUB(X, Y) X ## Y +#define _(s) zen::implementation::translate(ZEN_TRANS_CONCAT_SUB(L, s)) +#define _P(s, p, n) zen::implementation::translate(ZEN_TRANS_CONCAT_SUB(L, s), ZEN_TRANS_CONCAT_SUB(L, p), n) +//source and translation are required to use %x as number placeholder +//for plural form, which will be substituted automatically!!! namespace zen { @@ -28,7 +32,7 @@ struct TranslationHandler virtual ~TranslationHandler() {} virtual std::wstring translate(const std::wstring& text) = 0; //simple translation - virtual std::wstring translate(const std::wstring& singular, const std::wstring& plural, int n) = 0; + virtual std::wstring translate(const std::wstring& singular, const std::wstring& plural, std::int64_t n) = 0; }; void setTranslator(TranslationHandler* newHandler = nullptr); //takes ownership @@ -46,14 +50,6 @@ TranslationHandler* getTranslator(); - - - - - - - - //######################## implementation ############################## namespace implementation { @@ -64,18 +60,27 @@ std::wstring translate(const std::wstring& text) } //translate plural forms: "%x day" "%x days" -//returns "%x day" if n == 1; "%x days" else for english language +//returns "1 day" if n == 1; "123 days" if n == 123 for english language inline -std::wstring translate(const std::wstring& singular, const std::wstring& plural, int n) +std::wstring translate(const std::wstring& singular, const std::wstring& plural, std::int64_t n) { - if (n < 0) n = -n; - return getTranslator() ? getTranslator()->translate(singular, plural, n) : n == 1 ? singular : plural; + assert(contains(plural, L"%x")); + + if (getTranslator()) + { + std::wstring translation = getTranslator()->translate(singular, plural, n); + assert(!contains(translation, L"%x")); + return translation; + } + else + return replaceCpy(std::abs(n) == 1 ? singular : plural, L"%x", zen::numberTo<std::wstring>(n)); } template <class T> inline std::wstring translate(const std::wstring& singular, const std::wstring& plural, T n) { - return translate(singular, plural, static_cast<int>(n % 1000000)); + static_assert(sizeof(n) <= sizeof(std::int64_t), ""); + return translate(singular, plural, static_cast<std::int64_t>(n)); } inline diff --git a/zen/notify_removal.cpp b/zen/notify_removal.cpp index 4b00cadf..d1fbc6ed 100644 --- a/zen/notify_removal.cpp +++ b/zen/notify_removal.cpp @@ -82,7 +82,7 @@ MessageProvider::MessageProvider() : windowHandle(nullptr) { if (!hMainModule) - throw FileError(_("Failed to register to receive system messages."), formatSystemError(L"GetModuleHandle", getLastError())); + throw FileError(_("Unable to register to receive system messages."), formatSystemError(L"GetModuleHandle", getLastError())); //register the main window class WNDCLASS wc = {}; @@ -91,7 +91,7 @@ MessageProvider::MessageProvider() : wc.lpszClassName = dummyWindowName; if (::RegisterClass(&wc) == 0) - throw FileError(_("Failed to register to receive system messages."), formatSystemError(L"RegisterClass", getLastError())); + throw FileError(_("Unable to register to receive system messages."), formatSystemError(L"RegisterClass", getLastError())); ScopeGuard guardClass = makeGuard([&] { ::UnregisterClass(dummyWindowName, hMainModule); }); @@ -108,7 +108,7 @@ MessageProvider::MessageProvider() : hMainModule, //HINSTANCE hInstance, nullptr); //LPVOID lpParam if (!windowHandle) - throw FileError(_("Failed to register to receive system messages."), formatSystemError(L"CreateWindow", getLastError())); + throw FileError(_("Unable to register to receive system messages."), formatSystemError(L"CreateWindow", getLastError())); guardClass.dismiss(); } @@ -156,7 +156,7 @@ public: if (lastError != ERROR_CALL_NOT_IMPLEMENTED && //fail on SAMBA share: this shouldn't be a showstopper! lastError != ERROR_SERVICE_SPECIFIC_ERROR && //neither should be fail for "Pogoplug" mapped network drives lastError != ERROR_INVALID_DATA) //this seems to happen for a NetDrive-mapped FTP server - throw zen::FileError(_("Failed to register to receive system messages."), formatSystemError(L"RegisterDeviceNotification", lastError)); + throw zen::FileError(_("Unable to register to receive system messages."), formatSystemError(L"RegisterDeviceNotification", lastError)); } guardProvider.dismiss(); diff --git a/zen/osx_string.h b/zen/osx_string.h index 0d77345c..38c54023 100644 --- a/zen/osx_string.h +++ b/zen/osx_string.h @@ -48,7 +48,7 @@ Zstring cfStringToZstring(const CFStringRef& cfStr) if (::CFStringGetCString(cfStr, &*buffer.begin(), bufferSize, kCFStringEncodingUTF8)) { - buffer.resize(zen::strLength(&*buffer.begin())); //caveat: memory consumption of returned string! + buffer.resize(zen::strLength(buffer.c_str())); //caveat: memory consumption of returned string! return buffer; } } diff --git a/zen/privilege.cpp b/zen/privilege.cpp index d4f956a8..98eaad43 100644 --- a/zen/privilege.cpp +++ b/zen/privilege.cpp @@ -3,6 +3,7 @@ #include "thread.h" //includes <boost/thread.hpp> #include "zstring.h" #include "scope_guard.h" +#include "win_ver.h" using namespace zen; @@ -68,9 +69,17 @@ void setPrivilege(LPCTSTR privilege, bool enable) //throw FileError nullptr)) //__out_opt PDWORD ReturnLength throw FileError(replaceCpy(_("Cannot set privilege %x."), L"%x", std::wstring(L"\"") + privilege + L"\""), formatSystemError(L"AdjustTokenPrivileges", getLastError())); - const ErrorCode lastError = getLastError(); + ErrorCode lastError = getLastError(); if (lastError == ERROR_NOT_ALL_ASSIGNED) //check although previous function returned with success! + { +#ifdef __MINGW32__ //Shobjidl.h +#define ERROR_ELEVATION_REQUIRED 740L +#endif + if (vistaOrLater()) //replace this useless error code with what it *really* means! + lastError = ERROR_ELEVATION_REQUIRED; + throw FileError(replaceCpy(_("Cannot set privilege %x."), L"%x", std::wstring(L"\"") + privilege + L"\""), formatSystemError(L"AdjustTokenPrivileges", lastError)); + } } diff --git a/zen/process_priority.cpp b/zen/process_priority.cpp index b09b35f5..3ac2f068 100644 --- a/zen/process_priority.cpp +++ b/zen/process_priority.cpp @@ -28,7 +28,7 @@ struct PreventStandby::Pimpl {}; PreventStandby::PreventStandby() { if (::SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED) == 0) - throw FileError(_("Failed to suspend system sleep mode.")); //no GetLastError() support? + throw FileError(_("Unable to suspend system sleep mode.")); //no GetLastError() support? } @@ -116,7 +116,7 @@ PreventStandby::PreventStandby() : pimpl(make_unique<Pimpl>()) CFSTR("FreeFileSync"), &pimpl->assertionID); if (rv != kIOReturnSuccess) - throw FileError(_("Failed to suspend system sleep mode."), replaceCpy<std::wstring>(L"IOReturn Code %x", L"%x", numberTo<std::wstring>(rv))); //could not find a better way to convert IOReturn to string + throw FileError(_("Unable to suspend system sleep mode."), replaceCpy<std::wstring>(L"IOReturn Code %x", L"%x", numberTo<std::wstring>(rv))); //could not find a better way to convert IOReturn to string } PreventStandby::~PreventStandby() diff --git a/zen/read_txt.cpp b/zen/read_txt.cpp index 7566ff14..23649846 100644 --- a/zen/read_txt.cpp +++ b/zen/read_txt.cpp @@ -1,3 +1,9 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + #include "read_txt.h" using namespace zen; @@ -42,7 +48,7 @@ std::string detectLineBreak(const Zstring& filename) //throw FileError } -ExtractLines::ExtractLines(const Zstring& filename, const std::string& lineBreak) : //throw FileError +LineExtractor::LineExtractor(const Zstring& filename, const std::string& lineBreak) : //throw FileError inputStream(filename), bufferLogBegin(buffer.begin()), lineBreak_(lineBreak) { if (lineBreak.empty()) @@ -50,7 +56,7 @@ ExtractLines::ExtractLines(const Zstring& filename, const std::string& lineBreak } -bool ExtractLines::getLine(std::string& output) //throw FileError +bool LineExtractor::getLine(std::string& output) //throw FileError { warn_static("don't use lineBreak, but support any of r, n, rn!!!") for (;;) diff --git a/zen/read_txt.h b/zen/read_txt.h index 0678ac52..92892716 100644 --- a/zen/read_txt.h +++ b/zen/read_txt.h @@ -13,10 +13,10 @@ namespace zen { -class ExtractLines +class LineExtractor { public: - ExtractLines(const Zstring& filename, const std::string& lineBreak = std::string()); //throw FileError + LineExtractor(const Zstring& filename, const std::string& lineBreak = std::string()); //throw FileError bool getLine(std::string& output); //throw FileError private: diff --git a/zen/scroll_window_under_cursor.cpp b/zen/scroll_window_under_cursor.cpp index ccc00bcf..76a5ab4a 100644 --- a/zen/scroll_window_under_cursor.cpp +++ b/zen/scroll_window_under_cursor.cpp @@ -49,9 +49,9 @@ LRESULT CALLBACK mouseInputHook(int nCode, WPARAM wParam, LPARAM lParam) return ::CallNextHookEx(nullptr, nCode, wParam, lParam); } -struct Dummy +struct InstallMouseHook { - Dummy() + InstallMouseHook() { hHook = ::SetWindowsHookEx(WH_GETMESSAGE, //__in int idHook, mouseInputHook, //__in HOOKPROC lpfn, @@ -60,7 +60,7 @@ struct Dummy assert(hHook); } - ~Dummy() + ~InstallMouseHook() { if (hHook) ::UnhookWindowsHookEx(hHook); diff --git a/zen/shell_execute.h b/zen/shell_execute.h new file mode 100644 index 00000000..14a6a40e --- /dev/null +++ b/zen/shell_execute.h @@ -0,0 +1,104 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#ifndef EXECUTE_HEADER_23482134578134134 +#define EXECUTE_HEADER_23482134578134134 + +#include "file_error.h" + +#ifdef ZEN_WIN +#include "scope_guard.h" +#include "win.h" //includes "windows.h" + +#elif defined ZEN_LINUX || defined ZEN_MAC +#include "thread.h" +#include <stdlib.h> //::system() +#endif + + +namespace zen +{ +//launch commandline and report errors via popup dialog +//windows: COM needs to be initialized before calling this function! +enum ExecutionType +{ + EXEC_TYPE_SYNC, + EXEC_TYPE_ASYNC +}; + +namespace +{ +void shellExecute2(const Zstring& command, ExecutionType type) //throw FileError +{ +#ifdef ZEN_WIN + //parse commandline + Zstring commandTmp = command; + trim(commandTmp, true, false); //CommandLineToArgvW() does not like leading spaces + + std::vector<Zstring> argv; + int argc = 0; + if (LPWSTR* tmp = ::CommandLineToArgvW(commandTmp.c_str(), &argc)) + { + ZEN_ON_SCOPE_EXIT(::LocalFree(tmp)); + std::copy(tmp, tmp + argc, std::back_inserter(argv)); + } + + Zstring filename; + Zstring arguments; + if (!argv.empty()) + { + filename = argv[0]; + for (auto iter = argv.begin() + 1; iter != argv.end(); ++iter) + arguments += (iter != argv.begin() ? L" " : L"") + + (iter->empty() || std::any_of(iter->begin(), iter->end(), &isWhiteSpace<wchar_t>) ? L"\"" + *iter + L"\"" : *iter); + } + + SHELLEXECUTEINFO execInfo = {}; + execInfo.cbSize = sizeof(execInfo); + + //SEE_MASK_NOASYNC is equal to SEE_MASK_FLAG_DDEWAIT, but former is defined not before Win SDK 6.0 + execInfo.fMask = type == EXEC_TYPE_SYNC ? (SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_DDEWAIT) : 0; //don't use SEE_MASK_ASYNCOK -> returns successful despite errors! + execInfo.fMask |= SEE_MASK_UNICODE | SEE_MASK_FLAG_NO_UI; //::ShellExecuteEx() shows a non-blocking pop-up dialog on errors -> we want a blocking one + execInfo.lpVerb = nullptr; + execInfo.lpFile = filename.c_str(); + execInfo.lpParameters = arguments.c_str(); + execInfo.nShow = SW_SHOWNORMAL; + + if (!::ShellExecuteEx(&execInfo)) //__inout LPSHELLEXECUTEINFO lpExecInfo + throw FileError(_("Incorrect command line:") + L"\nFile: " + filename + L"\nArg: " + arguments, + formatSystemError(L"ShellExecuteEx", getLastError())); + + if (execInfo.hProcess) + { + ZEN_ON_SCOPE_EXIT(::CloseHandle(execInfo.hProcess)); + + if (type == EXEC_TYPE_SYNC) + ::WaitForSingleObject(execInfo.hProcess, INFINITE); + } + +#elif defined ZEN_LINUX || defined ZEN_MAC + /* + we cannot use wxExecute due to various issues: + - screws up encoding on OS X for non-ASCII characters + - does not provide any reasonable error information + - uses a zero-sized dummy window as a hack to keep focus which leaves a useless empty icon in ALT-TAB list in Windows + */ + + if (type == EXEC_TYPE_SYNC) + { + //Posix::system - execute a shell command + int rv = ::system(command.c_str()); //do NOT use std::system as its documentation says nothing about "WEXITSTATUS(rv)", ect... + if (rv == -1 || WEXITSTATUS(rv) == 127) //http://linux.die.net/man/3/system "In case /bin/sh could not be executed, the exit status will be that of a command that does exit(127)" + throw FileError(_("Incorrect command line:") + L"\n" + command); + } + else + async([=] { int rv = ::system(command.c_str()); (void)rv; }); +#endif +} +} +} + +#endif //EXECUTE_HEADER_23482134578134134 diff --git a/zen/string_tools.h b/zen/string_tools.h index 180056cb..35e5af2b 100644 --- a/zen/string_tools.h +++ b/zen/string_tools.h @@ -535,7 +535,7 @@ Num stringTo(const S& str, Int2Type<NUM_TYPE_OTHER>) //default string to number { typedef typename GetCharType<S>::Type CharType; Num number = 0; - std::basic_istringstream<CharType>(copyStringTo<std::basic_string<CharType> >(str)) >> number; + std::basic_istringstream<CharType>(copyStringTo<std::basic_string<CharType>>(str)) >> number; return number; } diff --git a/zen/symlink_target.h b/zen/symlink_target.h index e3e2ac4d..73d67927 100644 --- a/zen/symlink_target.h +++ b/zen/symlink_target.h @@ -132,7 +132,7 @@ Zstring getSymlinkRawTargetString_impl(const Zstring& linkPath) //throw FileErro reparseData.MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR)); } else - throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(linkPath)), L"Not a symbolic link or junction!"); + throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(linkPath)), L"Not a symbolic link or junction."); //absolute symlinks and junctions technically start with \??\ while relative ones do not if (startsWith(output, Zstr("\\??\\"))) diff --git a/zen/sys_error.h b/zen/sys_error.h index cea2f5f9..dc60c77f 100644 --- a/zen/sys_error.h +++ b/zen/sys_error.h @@ -73,13 +73,17 @@ std::wstring formatSystemError(const std::wstring& functionName, long long lastE inline std::wstring formatSystemError(const std::wstring& functionName, ErrorCode lastError) { - //determine error code if none was specified -> still required?? + const ErrorCode currentError = getLastError(); //not necessarily == lastError + + //determine error code if none was specified if (lastError == 0) - lastError = getLastError(); + lastError = currentError; std::wstring output = replaceCpy(_("Error Code %x:"), L"%x", numberTo<std::wstring>(lastError)); #ifdef ZEN_WIN + ZEN_ON_SCOPE_EXIT(::SetLastError(currentError)); //this function must not change active system error variable! + LPWSTR buffer = nullptr; if (::FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_MAX_WIDTH_MASK | @@ -91,13 +95,12 @@ std::wstring formatSystemError(const std::wstring& functionName, ErrorCode lastE output += L" "; output += buffer; } - ::SetLastError(lastError); //restore last error #elif defined ZEN_LINUX || defined ZEN_MAC + ZEN_ON_SCOPE_EXIT(errno = currentError); + output += L" "; output += utfCvrtTo<std::wstring>(::strerror(lastError)); - - errno = lastError; //restore errno #endif if (!endsWith(output, L" ")) //Windows messages seem to end with a blank... output += L" "; diff --git a/zen/zstring.cpp b/zen/zstring.cpp index 6eb68f7b..83eb5127 100644 --- a/zen/zstring.cpp +++ b/zen/zstring.cpp @@ -34,14 +34,14 @@ public: { boost::lock_guard<boost::mutex> dummy(lockActStrings); if (!activeStrings.insert(std::make_pair(ptr, size)).second) - reportProblem("Fatal Error: New memory points into occupied space: " + rawMemToString(ptr, size)); + reportProblem("Serious Error: New memory points into occupied space: " + rawMemToString(ptr, size)); } void remove(const void* ptr) { boost::lock_guard<boost::mutex> dummy(lockActStrings); if (activeStrings.erase(ptr) != 1) - reportProblem("Fatal Error: No memory available for deallocation at this location!"); + reportProblem("Serious Error: No memory available for deallocation at this location!"); } static LeakChecker& instance() { static LeakChecker inst; return inst; } @@ -154,7 +154,7 @@ int z_impl::compareFilenamesNoCase(const wchar_t* lhs, const wchar_t* rhs, size_ static_cast<int>(sizeRhs), //__in int cchCount2, true); //__in BOOL bIgnoreCase if (rv <= 0) - throw std::runtime_error("Error comparing strings (ordinal)!"); + throw std::runtime_error("Error comparing strings (CompareStringOrdinal)."); else return rv - 2; //convert to C-style string compare result } @@ -179,10 +179,10 @@ int z_impl::compareFilenamesNoCase(const wchar_t* lhs, const wchar_t* rhs, size_ minSize, //__in int cchSrc, bufferA, //__out LPTSTR lpDestStr, MAX_PATH) == 0) //__in int cchDest - throw std::runtime_error("Error comparing strings! (LCMapString)"); + throw std::runtime_error("Error comparing strings (LCMapString)."); if (::LCMapString(ZSTRING_INVARIANT_LOCALE, LCMAP_UPPERCASE, rhs, minSize, bufferB, MAX_PATH) == 0) - throw std::runtime_error("Error comparing strings! (LCMapString)"); + throw std::runtime_error("Error comparing strings (LCMapString)."); const int rv = ::wmemcmp(bufferA, bufferB, minSize); if (rv != 0) @@ -194,10 +194,10 @@ int z_impl::compareFilenamesNoCase(const wchar_t* lhs, const wchar_t* rhs, size_ std::vector<wchar_t> bufferB(minSize); if (::LCMapString(ZSTRING_INVARIANT_LOCALE, LCMAP_UPPERCASE, lhs, minSize, &bufferA[0], minSize) == 0) - throw std::runtime_error("Error comparing strings! (LCMapString: FS)"); + throw std::runtime_error("Error comparing strings (LCMapString: FS)."); if (::LCMapString(ZSTRING_INVARIANT_LOCALE, LCMAP_UPPERCASE, rhs, minSize, &bufferB[0], minSize) == 0) - throw std::runtime_error("Error comparing strings! (LCMapString: FS)"); + throw std::runtime_error("Error comparing strings (LCMapString: FS)."); const int rv = ::wmemcmp(&bufferA[0], &bufferB[0], minSize); if (rv != 0) |