diff options
Diffstat (limited to 'shared')
-rw-r--r-- | shared/buildInfo.h | 12 | ||||
-rw-r--r-- | shared/customButton.cpp | 6 | ||||
-rw-r--r-- | shared/customComboBox.cpp | 4 | ||||
-rw-r--r-- | shared/dllLoader.cpp | 45 | ||||
-rw-r--r-- | shared/dllLoader.h | 13 | ||||
-rw-r--r-- | shared/fileHandling.cpp | 239 | ||||
-rw-r--r-- | shared/fileHandling.h | 6 | ||||
-rw-r--r-- | shared/fileID.cpp | 82 | ||||
-rw-r--r-- | shared/fileID.h | 194 | ||||
-rw-r--r-- | shared/fileTraverser.cpp | 22 | ||||
-rw-r--r-- | shared/localization.cpp | 13 | ||||
-rw-r--r-- | shared/longPathPrefix.cpp | 95 | ||||
-rw-r--r-- | shared/longPathPrefix.h | 27 | ||||
-rw-r--r-- | shared/recycler.cpp | 128 | ||||
-rw-r--r-- | shared/recycler.h | 21 | ||||
-rw-r--r-- | shared/shadow.cpp | 122 | ||||
-rw-r--r-- | shared/shadow.h | 3 | ||||
-rw-r--r-- | shared/zstring.cpp | 123 | ||||
-rw-r--r-- | shared/zstring.h | 24 |
19 files changed, 924 insertions, 255 deletions
diff --git a/shared/buildInfo.h b/shared/buildInfo.h new file mode 100644 index 00000000..1c14caa5 --- /dev/null +++ b/shared/buildInfo.h @@ -0,0 +1,12 @@ +#ifndef BUILDINFO_H_INCLUDED +#define BUILDINFO_H_INCLUDED + +namespace Utility +{ +//determine build info +//seems to be safer than checking for _WIN64 (defined on windows for 64-bit compilations only) while _WIN32 is always defined +static const bool is32BitBuild = sizeof(void*) == 4; +static const bool is64BitBuild = sizeof(void*) == 8; +} + +#endif // BUILDINFO_H_INCLUDED diff --git a/shared/customButton.cpp b/shared/customButton.cpp index 40c0397f..73b9d1ee 100644 --- a/shared/customButton.cpp +++ b/shared/customButton.cpp @@ -277,17 +277,17 @@ void wxButtonWithImage::refreshButtonLabel() //wxDC::DrawLabel() unfortunately isn't working for transparent images on Linux, so we need to use custom image-concatenation if (bitmapFront.IsOk()) - writeToImage(wxImage(bitmapFront.ConvertToImage()), + writeToImage(bitmapFront.ConvertToImage(), wxPoint(0, (transparentImage.GetHeight() - bitmapFront.GetHeight()) / 2), transparentImage); if (bitmapText.IsOk()) - writeToImage(wxImage(bitmapText.ConvertToImage()), + writeToImage(bitmapText.ConvertToImage(), wxPoint(bitmapFront.GetWidth() + m_spaceAfter, (transparentImage.GetHeight() - bitmapText.GetHeight()) / 2), transparentImage); if (bitmapBack.IsOk()) - writeToImage(wxImage(bitmapBack.ConvertToImage()), + writeToImage(bitmapBack.ConvertToImage(), wxPoint(bitmapFront.GetWidth() + m_spaceAfter + bitmapText.GetWidth() + m_spaceBefore, (transparentImage.GetHeight() - bitmapBack.GetHeight()) / 2), transparentImage); diff --git a/shared/customComboBox.cpp b/shared/customComboBox.cpp index f2eec7a9..e21e915c 100644 --- a/shared/customComboBox.cpp +++ b/shared/customComboBox.cpp @@ -50,6 +50,10 @@ void CustomComboBox::OnKeyEvent(wxKeyEvent& event) void CustomComboBox::addPairToFolderHistory(const wxString& newFolder, unsigned int maxHistSize) { + //don't add empty directories + if (newFolder.empty()) + return; + const wxString oldVal = this->GetValue(); //insert new folder or put it to the front if already existing diff --git a/shared/dllLoader.cpp b/shared/dllLoader.cpp index e37ded54..fbfa5b11 100644 --- a/shared/dllLoader.cpp +++ b/shared/dllLoader.cpp @@ -1,44 +1,55 @@ #include "dllLoader.h" #include <wx/msw/wrapwin.h> //includes "windows.h" +#include <map> +#include <assert.h> namespace { -class KernelDllHandler //dynamically load "kernel32.dll" +class DllHandler //dynamically load "kernel32.dll" { public: - static const KernelDllHandler& getInstance() + static DllHandler& getInstance() { - static KernelDllHandler instance; + static DllHandler instance; return instance; } - HINSTANCE getHandle() const + HINSTANCE getHandle(const std::wstring& libraryName) { - return hKernel; + HandleMap::const_iterator foundEntry = handles.find(libraryName); + if (foundEntry == handles.end()) + { + HINSTANCE newHandle = ::LoadLibrary(libraryName.c_str()); + handles.insert(std::make_pair(libraryName, newHandle)); + + assert(handles.find(libraryName) != handles.end()); + return newHandle; + } + else + return foundEntry->second; } private: - KernelDllHandler() : - hKernel(NULL) - { - //get a handle to the DLL module containing required functionality - hKernel = ::LoadLibrary(L"kernel32.dll"); - } + DllHandler() {} - ~KernelDllHandler() + ~DllHandler() { - if (hKernel) ::FreeLibrary(hKernel); + for (HandleMap::const_iterator i = handles.begin(); i != handles.end(); ++i) + if (i->second != NULL) ::FreeLibrary(i->second); } - HINSTANCE hKernel; + typedef std::map<std::wstring, HINSTANCE> HandleMap; + HandleMap handles; }; } -void* Utility::loadSymbolKernel(const std::string& functionName) +void* Utility::loadSymbol(const std::wstring& libraryName, const std::string& functionName) { - if (KernelDllHandler::getInstance().getHandle() != NULL) - return reinterpret_cast<void*>(::GetProcAddress(KernelDllHandler::getInstance().getHandle(), functionName.c_str())); + const HINSTANCE libHandle = DllHandler::getInstance().getHandle(libraryName); + + if (libHandle != NULL) + return reinterpret_cast<void*>(::GetProcAddress(libHandle, functionName.c_str())); else return NULL; } diff --git a/shared/dllLoader.h b/shared/dllLoader.h index bf62b542..5b561c5a 100644 --- a/shared/dllLoader.h +++ b/shared/dllLoader.h @@ -5,10 +5,11 @@ namespace Utility { - //load kernel dll functions -template <typename FunctionType> -FunctionType loadDllFunKernel(const std::string& functionName); +//load function from a DLL library, e.g. from kernel32.dll +//NOTE: you're allowed to take a static reference to the return value to optimize performance! :) +template <typename FunctionType> +FunctionType loadDllFunction(const std::wstring& libraryName, const std::string& functionName); @@ -22,13 +23,13 @@ FunctionType loadDllFunKernel(const std::string& functionName); //---------------Inline Implementation--------------------------------------------------- -void* loadSymbolKernel(const std::string& functionName); +void* loadSymbol(const std::wstring& libraryName, const std::string& functionName); template <typename FunctionType> inline -FunctionType loadDllFunKernel(const std::string& functionName) +FunctionType loadDllFunction(const std::wstring& libraryName, const std::string& functionName) { - return reinterpret_cast<FunctionType>(loadSymbolKernel(functionName)); + return reinterpret_cast<FunctionType>(loadSymbol(libraryName, functionName)); } #ifndef FFS_WIN diff --git a/shared/fileHandling.cpp b/shared/fileHandling.cpp index 4b9901e0..ef1d3e6c 100644 --- a/shared/fileHandling.cpp +++ b/shared/fileHandling.cpp @@ -14,9 +14,11 @@ #include <wx/utils.h> #ifdef FFS_WIN +#include "recycler.h" #include "dllLoader.h" #include <wx/msw/wrapwin.h> //includes "windows.h" #include "shadow.h" +#include "longPathPrefix.h" #elif defined FFS_LINUX #include <sys/stat.h> @@ -104,83 +106,48 @@ Zstring FreeFileSync::getFormattedDirectoryName(const Zstring& dirname) if (dirnameTmp.empty()) //an empty string is interpreted as "\"; this is not desired return Zstring(); - if (!dirnameTmp.EndsWith(zToWx(globalFunctions::FILE_NAME_SEPARATOR))) - dirnameTmp += zToWx(globalFunctions::FILE_NAME_SEPARATOR); - //replace macros expandMacros(dirnameTmp); +#ifdef FFS_WIN + /* + resolve relative names; required by: + - \\?\-prefix which needs absolute names + - Volume Shadow Copy: volume name needs to be part of each filename + - file icon buffer (at least for extensions that are acutally read from disk, e.g. "exe") + - detection of dependent directories, e.g. "\" and "C:\test" + */ + dirnameTmp = resolveRelativePath(dirnameTmp.c_str()).c_str(); +#endif + + if (!dirnameTmp.EndsWith(zToWx(globalFunctions::FILE_NAME_SEPARATOR))) + dirnameTmp += zToWx(globalFunctions::FILE_NAME_SEPARATOR); + return wxToZ(dirnameTmp); } -class RecycleBin +bool FreeFileSync::recycleBinExists() { -public: - static const RecycleBin& getInstance() - { - static RecycleBin instance; //lazy creation of RecycleBin - return instance; - } - - bool recycleBinExists() const - { - return recycleBinAvailable; - } - - bool moveToRecycleBin(const Zstring& filename) const; //throw (std::logic_error) - -private: - RecycleBin() : - recycleBinAvailable(false) - { #ifdef FFS_WIN - recycleBinAvailable = true; + return true; +#else + return false; #endif // FFS_WIN - } - - ~RecycleBin() {} - -private: - bool recycleBinAvailable; -}; +} -bool RecycleBin::moveToRecycleBin(const Zstring& filename) const //throw (std::logic_error) +inline +void moveToRecycleBin(const Zstring& filename) //throw (std::logic_error), throw (FileError) { - if (!recycleBinAvailable) //this method should ONLY be called if recycle bin is available + if (!FreeFileSync::recycleBinExists()) //this method should ONLY be called if recycle bin is available throw std::logic_error("Initialization of Recycle Bin failed!"); #ifdef FFS_WIN - Zstring filenameDoubleNull = filename + wxChar(0); - - SHFILEOPSTRUCT fileOp; - fileOp.hwnd = NULL; - fileOp.wFunc = FO_DELETE; - fileOp.pFrom = filenameDoubleNull.c_str(); - fileOp.pTo = NULL; - fileOp.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_SILENT | FOF_NOERRORUI; - fileOp.fAnyOperationsAborted = false; - fileOp.hNameMappings = NULL; - fileOp.lpszProgressTitle = NULL; - - if (SHFileOperation(&fileOp) != 0 || fileOp.fAnyOperationsAborted) return false; + FreeFileSync::moveToWindowsRecycler(filename); //throw (FileError) +#else + throw std::logic_error("No Recycler for Linux available at the moment!"); #endif - - return true; -} - - -bool FreeFileSync::recycleBinExists() -{ - return RecycleBin::getInstance().recycleBinExists(); -} - - -inline -bool moveToRecycleBin(const Zstring& filename) //throw (std::logic_error) -{ - return RecycleBin::getInstance().moveToRecycleBin(filename); } @@ -190,7 +157,7 @@ bool FreeFileSync::fileExists(const DefaultChar* filename) #ifdef FFS_WIN // we must use GetFileAttributes() instead of the ANSI C functions because // it can cope with network (UNC) paths unlike them - const DWORD ret = ::GetFileAttributes(filename); + const DWORD ret = ::GetFileAttributes(applyLongPathPrefix(filename).c_str()); return (ret != INVALID_FILE_ATTRIBUTES) && !(ret & FILE_ATTRIBUTE_DIRECTORY); //returns true for (file-)symlinks also @@ -208,7 +175,7 @@ bool FreeFileSync::dirExists(const DefaultChar* dirname) #ifdef FFS_WIN // we must use GetFileAttributes() instead of the ANSI C functions because // it can cope with network (UNC) paths unlike them - const DWORD ret = ::GetFileAttributes(dirname); + const DWORD ret = ::GetFileAttributes(applyLongPathPrefix(dirname).c_str()); return (ret != INVALID_FILE_ATTRIBUTES) && (ret & FILE_ATTRIBUTE_DIRECTORY); //returns true for (dir-)symlinks also @@ -223,7 +190,7 @@ bool FreeFileSync::dirExists(const DefaultChar* dirname) bool FreeFileSync::symlinkExists(const DefaultChar* objname) { #ifdef FFS_WIN - const DWORD ret = ::GetFileAttributes(objname); + const DWORD ret = ::GetFileAttributes(applyLongPathPrefix(objname).c_str()); return (ret != INVALID_FILE_ATTRIBUTES) && (ret & FILE_ATTRIBUTE_REPARSE_POINT); #elif defined FFS_LINUX @@ -263,8 +230,8 @@ bool FreeFileSync::isMovable(const Zstring& pathFrom, const Zstring& pathTo) const bool result = //try to move the file #ifdef FFS_WIN - ::MoveFileEx(dummyFileSource.c_str(), //__in LPCTSTR lpExistingFileName, - dummyFileTarget.c_str(), //__in_opt LPCTSTR lpNewFileName, + ::MoveFileEx(applyLongPathPrefix(dummyFileSource).c_str(), //__in LPCTSTR lpExistingFileName, + applyLongPathPrefix(dummyFileTarget).c_str(), //__in_opt LPCTSTR lpNewFileName, 0) != 0; //__in DWORD dwFlags #elif defined FFS_LINUX ::rename(dummyFileSource.c_str(), dummyFileTarget.c_str()) == 0; @@ -285,7 +252,9 @@ void FreeFileSync::removeFile(const Zstring& filename, const bool useRecycleBin) { //no error situation if file is not existing! manual deletion relies on it! #ifdef FFS_WIN - if (::GetFileAttributes(filename.c_str()) == INVALID_FILE_ATTRIBUTES) + + const Zstring filenameFmt = applyLongPathPrefix(filename); + if (::GetFileAttributes(filenameFmt.c_str()) == INVALID_FILE_ATTRIBUTES) return; //neither file nor any other object with that name existing #elif defined FFS_LINUX @@ -296,15 +265,14 @@ void FreeFileSync::removeFile(const Zstring& filename, const bool useRecycleBin) if (useRecycleBin) { - if (!moveToRecycleBin(filename)) - throw FileError(wxString(_("Error moving to Recycle Bin:")) + wxT("\n\"") + zToWx(filename) + wxT("\"")); + ::moveToRecycleBin(filename); return; } #ifdef FFS_WIN //initialize file attributes if (!::SetFileAttributes( - filename.c_str(), //address of filename + filenameFmt.c_str(), //address of filename FILE_ATTRIBUTE_NORMAL)) //attributes to set { wxString errorMessage = wxString(_("Error deleting file:")) + wxT("\n\"") + zToWx(filename) + wxT("\""); @@ -312,7 +280,7 @@ void FreeFileSync::removeFile(const Zstring& filename, const bool useRecycleBin) } //remove file, support for \\?\-prefix - if (!::DeleteFile(filename.c_str())) + if (!::DeleteFile(filenameFmt.c_str())) { wxString errorMessage = wxString(_("Error deleting file:")) + wxT("\n\"") + zToWx(filename) + wxT("\""); throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); @@ -331,9 +299,9 @@ void FreeFileSync::removeFile(const Zstring& filename, const bool useRecycleBin) void FreeFileSync::renameFile(const Zstring& oldName, const Zstring& newName) //throw (FileError); { #ifdef FFS_WIN - if (!::MoveFileEx(oldName.c_str(), //__in LPCTSTR lpExistingFileName, - newName.c_str(), //__in_opt LPCTSTR lpNewFileName, - 0)) //__in DWORD dwFlags + if (!::MoveFileEx(applyLongPathPrefix(oldName).c_str(), //__in LPCTSTR lpExistingFileName, + applyLongPathPrefix(newName).c_str(), //__in_opt LPCTSTR lpNewFileName, + 0)) //__in DWORD dwFlags { const wxString errorMessage = wxString(_("Error moving file:")) + wxT("\n\"") + zToWx(oldName) + wxT("\" ->\n\"") + zToWx(newName) + wxT("\""); throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); @@ -388,8 +356,8 @@ void FreeFileSync::moveFile(const Zstring& sourceFile, const Zstring& targetFile #ifdef FFS_WIN //first try to move the file directly without copying - if (::MoveFileEx(sourceFile.c_str(), //__in LPCTSTR lpExistingFileName, - targetFile.c_str(), //__in_opt LPCTSTR lpNewFileName, + if (::MoveFileEx(applyLongPathPrefix(sourceFile).c_str(), //__in LPCTSTR lpExistingFileName, + applyLongPathPrefix(targetFile).c_str(), //__in_opt LPCTSTR lpNewFileName, 0)) //__in DWORD dwFlags return; @@ -494,8 +462,8 @@ void moveDirectoryImpl(const Zstring& sourceDir, const Zstring& targetDir, bool { //first try to move the directory directly without copying #ifdef FFS_WIN - if (::MoveFileEx(sourceDir.c_str(), //__in LPCTSTR lpExistingFileName, - targetDir.c_str(), //__in_opt LPCTSTR lpNewFileName, + if (::MoveFileEx(applyLongPathPrefix(sourceDir).c_str(), //__in LPCTSTR lpExistingFileName, + applyLongPathPrefix(targetDir).c_str(), //__in_opt LPCTSTR lpNewFileName, 0)) //__in DWORD dwFlags return; @@ -615,7 +583,9 @@ void FreeFileSync::removeDirectory(const Zstring& directory, const bool useRecyc { //no error situation if directory is not existing! manual deletion relies on it! #ifdef FFS_WIN - const DWORD dirAttr = GetFileAttributes(directory.c_str()); //name of a file or directory + const Zstring directoryFmt = applyLongPathPrefix(directory); //support for \\?\-prefix + + const DWORD dirAttr = ::GetFileAttributes(directoryFmt.c_str()); //name of a file or directory if (dirAttr == INVALID_FILE_ATTRIBUTES) return; //neither directory nor any other object with that name existing @@ -627,8 +597,7 @@ void FreeFileSync::removeDirectory(const Zstring& directory, const bool useRecyc if (useRecycleBin) { - if (!moveToRecycleBin(directory)) - throw FileError(wxString(_("Error moving to Recycle Bin:")) + wxT("\n\"") + zToWx(directory) + wxT("\"")); + ::moveToRecycleBin(directory); return; } @@ -636,7 +605,7 @@ void FreeFileSync::removeDirectory(const Zstring& directory, const bool useRecyc #ifdef FFS_WIN //initialize file attributes if (!::SetFileAttributes( // initialize file attributes: actually NEEDED for symbolic links also! - directory.c_str(), // address of directory name + directoryFmt.c_str(), // address of directory name FILE_ATTRIBUTE_NORMAL)) // attributes to set { wxString errorMessage = wxString(_("Error deleting directory:")) + wxT("\n\"") + directory.c_str() + wxT("\""); @@ -645,9 +614,9 @@ void FreeFileSync::removeDirectory(const Zstring& directory, const bool useRecyc //attention: check if directory is a symlink! Do NOT traverse into it deleting contained files!!! - if (dirAttr & FILE_ATTRIBUTE_REPARSE_POINT) //remove symlink directly, support for \\?\-prefix + if (dirAttr & FILE_ATTRIBUTE_REPARSE_POINT) //remove symlink directly { - if (!::RemoveDirectory(directory.c_str())) + if (!::RemoveDirectory(directoryFmt.c_str())) { wxString errorMessage = wxString(_("Error deleting directory:")) + wxT("\n\"") + directory.c_str() + wxT("\""); throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); @@ -683,7 +652,7 @@ void FreeFileSync::removeDirectory(const Zstring& directory, const bool useRecyc //parent directory is deleted last #ifdef FFS_WIN //remove directory, support for \\?\-prefix - if (!::RemoveDirectory(directory.c_str())) + if (!::RemoveDirectory(directoryFmt.c_str())) #else if (::rmdir(directory.c_str()) != 0) #endif @@ -718,7 +687,7 @@ void FreeFileSync::copyFileTimes(const Zstring& sourceDir, const Zstring& target return; #ifdef FFS_WIN - HANDLE hDirRead = ::CreateFile(sourceDir.c_str(), + HANDLE hDirRead = ::CreateFile(applyLongPathPrefix(sourceDir).c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, @@ -737,7 +706,7 @@ void FreeFileSync::copyFileTimes(const Zstring& sourceDir, const Zstring& target &accessTime, &lastWriteTime)) { - HANDLE hDirWrite = ::CreateFile(targetDir.c_str(), + HANDLE hDirWrite = ::CreateFile(applyLongPathPrefix(targetDir).c_str(), FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, @@ -776,13 +745,13 @@ void FreeFileSync::copyFileTimes(const Zstring& sourceDir, const Zstring& target Zstring resolveDirectorySymlink(const Zstring& dirLinkName) //get full target path of symbolic link to a directory { //open handle to target of symbolic link - HANDLE hDir = ::CreateFile(dirLinkName.c_str(), - 0, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - NULL, - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, - NULL); + const HANDLE hDir = ::CreateFile(FreeFileSync::applyLongPathPrefix(dirLinkName).c_str(), + 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL); if (hDir == INVALID_HANDLE_VALUE) return Zstring(); @@ -799,7 +768,7 @@ Zstring resolveDirectorySymlink(const Zstring& dirLinkName) //get full target pa DWORD cchFilePath, DWORD dwFlags); static const GetFinalPathNameByHandleWFunc getFinalPathNameByHandle = - Utility::loadDllFunKernel<GetFinalPathNameByHandleWFunc>("GetFinalPathNameByHandleW"); + Utility::loadDllFunction<GetFinalPathNameByHandleWFunc>(L"kernel32.dll", "GetFinalPathNameByHandleW"); if (getFinalPathNameByHandle == NULL) throw FileError(wxString(_("Error loading library function:")) + wxT("\n\"") + wxT("GetFinalPathNameByHandleW") + wxT("\"")); @@ -885,10 +854,10 @@ void createDirectoryRecursively(const Zstring& directory, const Zstring& templat //now creation should be possible #ifdef FFS_WIN - const DWORD templateAttr = ::GetFileAttributes(templateDir.c_str()); //replaces wxDirExists(): also returns successful for broken symlinks + const DWORD templateAttr = ::GetFileAttributes(applyLongPathPrefix(templateDir).c_str()); //replaces wxDirExists(): also returns successful for broken symlinks if (templateAttr == INVALID_FILE_ATTRIBUTES) //fallback { - if (!::CreateDirectory(directory.c_str(), // pointer to a directory path string + if (!::CreateDirectory(applyLongPathPrefixCreateDir(directory).c_str(), // pointer to a directory path string NULL) && level == 0) { const wxString errorMessage = wxString(_("Error creating directory:")) + wxT("\n\"") + directory.c_str() + wxT("\""); @@ -910,8 +879,8 @@ void createDirectoryRecursively(const Zstring& directory, const Zstring& templat else { if (!::CreateDirectoryEx( // this function automatically copies symbolic links if encountered - linkPath.c_str(), // pointer to path string of template directory - directory.c_str(), // pointer to a directory path string + applyLongPathPrefix(linkPath).c_str(), // pointer to path string of template directory + applyLongPathPrefixCreateDir(directory).c_str(), // pointer to a directory path string NULL) && level == 0) { const wxString errorMessage = wxString(_("Error creating directory:")) + wxT("\n\"") + directory.c_str() + wxT("\""); @@ -924,12 +893,16 @@ void createDirectoryRecursively(const Zstring& directory, const Zstring& templat } else //in all other cases { - if (!::CreateDirectoryEx( // this function automatically copies symbolic links if encountered - templateDir.c_str(), // pointer to path string of template directory - directory.c_str(), // pointer to a directory path string + if (!::CreateDirectoryEx( // this function automatically copies symbolic links if encountered + applyLongPathPrefix(templateDir).c_str(), // pointer to path string of template directory + applyLongPathPrefixCreateDir(directory).c_str(), // pointer to a directory path string NULL) && level == 0) { - const wxString errorMessage = wxString(_("Error creating directory:")) + wxT("\n\"") + directory.c_str() + wxT("\""); + const wxString errorMessage = templateAttr & FILE_ATTRIBUTE_REPARSE_POINT ? + //give a more meaningful errormessage if copying a symbolic link failed, e.g. "C:\Users\ZenJu\Application Data" + (wxString(_("Error copying symbolic link:")) + wxT("\n\"") + templateDir.c_str() + wxT("\" ->\n\"") + directory.c_str() + wxT("\"")) : + + (wxString(_("Error creating directory:")) + wxT("\n\"") + directory.c_str() + wxT("\"")); throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); } @@ -960,7 +933,7 @@ void createDirectoryRecursively(const Zstring& directory, const Zstring& templat if (symlink(buffer, directory.c_str()) != 0) { - wxString errorMessage = wxString(_("Error creating directory:")) + wxT("\n\"") + zToWx(directory) + wxT("\""); + const wxString errorMessage = wxString(_("Error copying symbolic link:")) + wxT("\n\"") + zToWx(templateDir) + wxT("\" ->\n\"") + zToWx(directory) + wxT("\""); throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); } return; //symlink created successfully @@ -1022,7 +995,7 @@ Zstring createTempName(const Zstring& filename) { //if it's not unique, add a postfix number int postfix = 1; - while (FreeFileSync::fileExists(output + DefaultChar('_') + numberToZstring(postfix))) + while (FreeFileSync::fileExists(output + DefaultStr("_") + numberToZstring(postfix))) ++postfix; output += Zstring(DefaultStr("_")) + numberToZstring(postfix); @@ -1107,9 +1080,10 @@ bool supportForNonEncryptedDestination() ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); - //symbolic links are supported starting with Vista + //encrypted destination is not supported with Windows 2000 if (GetVersionEx(&osvi)) - return osvi.dwMajorVersion >= 5 && osvi.dwMinorVersion >= 1; //XP has majorVersion == 5, minorVersion == 1, Vista majorVersion == 6 + return osvi.dwMajorVersion > 5 || + (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion > 0); //2000 has majorVersion == 5, minorVersion == 0 //overview: http://msdn.microsoft.com/en-us/library/ms724834(VS.85).aspx return false; } @@ -1121,6 +1095,10 @@ void FreeFileSync::copyFile(const Zstring& sourceFile, FreeFileSync::ShadowCopy* shadowCopyHandler, FreeFileSync::CopyFileCallback* callback) { + //FreeFileSync::fileExists(targetFile.c_str()) -> avoid this call, performance; + //if target exists (very unlikely, because sync-algorithm deletes it) renaming below will fail! + + DWORD copyFlags = COPY_FILE_FAIL_IF_EXISTS; //copy symbolic links instead of the files pointed at @@ -1133,11 +1111,11 @@ void FreeFileSync::copyFile(const Zstring& sourceFile, if (nonEncSupported) copyFlags |= COPY_FILE_ALLOW_DECRYPTED_DESTINATION; - const Zstring temporary = createTempName(targetFile); //use temporary file until a correct date has been set + if (!::CopyFileEx( //same performance as CopyFile() - sourceFile.c_str(), - temporary.c_str(), + applyLongPathPrefix(sourceFile).c_str(), + applyLongPathPrefix(temporary).c_str(), copyCallbackInternal, callback, NULL, @@ -1152,6 +1130,7 @@ void FreeFileSync::copyFile(const Zstring& sourceFile, (lastError == ERROR_SHARING_VIOLATION || lastError == ERROR_LOCK_VIOLATION)) { + //shadowFilename already contains prefix: E.g. "\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1\Program Files\FFS\sample.dat" const Zstring shadowFilename(shadowCopyHandler->makeShadowCopy(sourceFile)); copyFile(shadowFilename, //transferred bytes is automatically reset when new file is copied targetFile, @@ -1161,12 +1140,33 @@ void FreeFileSync::copyFile(const Zstring& sourceFile, return; } - const wxString errorMessage = wxString(_("Error copying file:")) + wxT("\n\"") + sourceFile.c_str() + wxT("\" ->\n\"") + targetFile.c_str() + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted(lastError)); + //assemble error message... + const wxString errorMessage = wxString(_("Error copying file:")) + wxT("\n\"") + sourceFile.c_str() + wxT("\" ->\n\"") + targetFile.c_str() + wxT("\"") + + wxT("\n\n") + FreeFileSync::getLastErrorFormatted(lastError); + + throw FileError(errorMessage); } - //rename temporary file - FreeFileSync::renameFile(temporary, targetFile); + try + { + //rename temporary file + FreeFileSync::renameFile(temporary, targetFile); + } + catch (...) //if renaming temporary failed: cleanup + { + try + { + removeFile(temporary, false); //throw (FileError, std::logic_error); + } + catch(...) {} + + //this can only happen in very obscure situations: while scanning, target didn't exist, but while sync'ing it suddenly does (e.g. network drop?) + if (FreeFileSync::fileExists(targetFile.c_str())) + throw FileError(wxString(_("Error copying file:")) + wxT("\n\"") + zToWx(sourceFile) + wxT("\" ->\n\"") + zToWx(targetFile) + wxT("\"\n\n") + + _("Target file already existing!")); + + throw; + } //copy creation date (last modification date is redundantly written, too) copyFileTimes(sourceFile, targetFile); //throw() @@ -1230,7 +1230,7 @@ void FreeFileSync::copyFile(const Zstring& sourceFile, if (symlink(buffer, targetFile.c_str()) != 0) { - const wxString errorMessage = wxString(_("Error writing file:")) + wxT("\n\"") + zToWx(targetFile) + wxT("\""); + const wxString errorMessage = wxString(_("Error copying symbolic link:")) + wxT("\n\"") + zToWx(sourceFile) + wxT("\" ->\n\"") + zToWx(targetFile) + wxT("\""); throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); } @@ -1254,12 +1254,12 @@ void FreeFileSync::copyFile(const Zstring& sourceFile, //create targetFile and open it for writing const Zstring temporary = createTempName(targetFile); //use temporary file until a correct date has been set - std::ofstream fileOut(temporary.c_str(), std::ios_base::binary); - if (fileOut.fail()) - throw FileError(wxString(_("Error opening file:")) + wxT("\n\"") + zToWx(targetFile) + wxT("\"")); - try { + std::ofstream fileOut(temporary.c_str(), std::ios_base::binary); + if (fileOut.fail()) + throw FileError(wxString(_("Error opening file:")) + wxT("\n\"") + zToWx(targetFile) + wxT("\"")); + //copy contents of sourceFile to targetFile wxULongLong totalBytesTransferred; static MemoryAllocator memory; @@ -1326,8 +1326,11 @@ void FreeFileSync::copyFile(const Zstring& sourceFile, catch (...) { //try to delete target file if error occured, or exception was thrown in callback function + //no data-loss, because of "fileExists(targetFile))" check at the beginning! if (FreeFileSync::fileExists(targetFile)) ::unlink(targetFile); //don't handle error situations! + + //clean-up temporary if (FreeFileSync::fileExists(temporary)) ::unlink(temporary); //don't handle error situations! diff --git a/shared/fileHandling.h b/shared/fileHandling.h index b12f6f03..95d8fddd 100644 --- a/shared/fileHandling.h +++ b/shared/fileHandling.h @@ -1,5 +1,5 @@ -#ifndef RECYCLER_H_INCLUDED -#define RECYCLER_H_INCLUDED +#ifndef RECYCLER2_H_INCLUDED +#define RECYCLER2_H_INCLUDED #include "zstring.h" #include "fileError.h" @@ -83,4 +83,4 @@ void copyFile(const Zstring& sourceFile, } -#endif // RECYCLER_H_INCLUDED +#endif // RECYCLER2_H_INCLUDED diff --git a/shared/fileID.cpp b/shared/fileID.cpp new file mode 100644 index 00000000..005707dc --- /dev/null +++ b/shared/fileID.cpp @@ -0,0 +1,82 @@ +#include "fileID.h" + +#ifdef FFS_WIN +#include "staticAssert.h" +#include <wx/msw/wrapwin.h> //includes "windows.h" +#include "longPathPrefix.h" + +#elif defined FFS_LINUX + +#endif + + + +#ifdef FFS_WIN +class CloseHandleOnExit +{ +public: + CloseHandleOnExit(HANDLE fileHandle) : fileHandle_(fileHandle) {} + + ~CloseHandleOnExit() + { + ::CloseHandle(fileHandle_); + } + +private: + HANDLE fileHandle_; +}; + + +Utility::FileID Utility::retrieveFileID(const Zstring& filename) +{ + //ensure our DWORD_FFS really is the same as DWORD + assert_static(sizeof(Utility::FileID::DWORD_FFS) == sizeof(DWORD)); + +//WARNING: CreateFile() is SLOW, while GetFileInformationByHandle() is quite cheap! +//http://msdn.microsoft.com/en-us/library/aa363788(VS.85).aspx + + const HANDLE hFile = ::CreateFile(FreeFileSync::applyLongPathPrefix(filename).c_str(), + 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, //FILE_FLAG_BACKUP_SEMANTICS needed to open directories + NULL); + if (hFile != INVALID_HANDLE_VALUE) + { + CloseHandleOnExit dummy(hFile); + + BY_HANDLE_FILE_INFORMATION info; + if (::GetFileInformationByHandle(hFile, &info)) + { + return Utility::FileID(info.dwVolumeSerialNumber, + info.nFileIndexHigh, + info.nFileIndexLow); + } + } + return Utility::FileID(); //empty ID +} + + +#elif defined FFS_LINUX +Utility::FileID Utility::retrieveFileID(const Zstring& filename) +{ + struct stat fileInfo; + if (::lstat(filename.c_str(), &fileInfo) == 0) //lstat() does not resolve symlinks + return Utility::FileID(fileInfo.st_dev, fileInfo.st_ino); + + return Utility::FileID(); //empty ID +} +#endif + + +bool Utility::sameFileSpecified(const Zstring& file1, const Zstring& file2) +{ + const Utility::FileID id1 = retrieveFileID(file1); + const Utility::FileID id2 = retrieveFileID(file2); + + if (id1 != FileID() && id2 != FileID()) + return id1 == id2; + + return false; +} diff --git a/shared/fileID.h b/shared/fileID.h new file mode 100644 index 00000000..7944b368 --- /dev/null +++ b/shared/fileID.h @@ -0,0 +1,194 @@ +#ifndef FILEID_H_INCLUDED +#define FILEID_H_INCLUDED + +#include <wx/stream.h> +#include "zstring.h" + +#ifdef FFS_WIN +#elif defined FFS_LINUX +#include <sys/stat.h> +#endif + + +//unique file identifier +namespace Utility +{ +class FileID +{ +public: + //standard copy constructor and assignment operator are okay! + + FileID(wxInputStream& stream); //read + void toStream(wxOutputStream& stream) const; //write + + bool isNull() const; + bool operator==(const FileID& rhs) const; + bool operator!=(const FileID& rhs) const; + bool operator<(const FileID& rhs) const; + + FileID(); +#ifdef FFS_WIN + typedef unsigned long DWORD_FFS; //we don't want do include "windows.h" or "<wx/msw/wrapwin.h>" here, do we? + + FileID(DWORD_FFS dwVolumeSN, + DWORD_FFS fileIndexHi, + DWORD_FFS fileIndexLo); +#elif defined FFS_LINUX + FileID(dev_t devId, + ino_t inId); +#endif +private: +#ifdef FFS_WIN + DWORD_FFS dwVolumeSerialNumber; + DWORD_FFS nFileIndexHigh; + DWORD_FFS nFileIndexLow; +#elif defined FFS_LINUX + dev_t deviceId; + ino_t inodeId; +#endif +}; + +//get unique file id (symbolic link handling: opens the link!!!) +//error condition: returns FileID () +FileID retrieveFileID(const Zstring& filename); + +//test whether two distinct paths point to the same file or directory: +// true: paths point to same files/dirs +// false: error occured OR point to different files/dirs +bool sameFileSpecified(const Zstring& file1, const Zstring& file2); +} + + + + + + + + + + + + + + + + + + + +//---------------Inline Implementation--------------------------------------------------- +#ifdef FFS_WIN +inline +Utility::FileID::FileID() : + dwVolumeSerialNumber(0), + nFileIndexHigh(0), + nFileIndexLow(0) {} + +inline +Utility::FileID::FileID(DWORD_FFS dwVolumeSN, + DWORD_FFS fileIndexHi, + DWORD_FFS fileIndexLo) : + dwVolumeSerialNumber(dwVolumeSN), + nFileIndexHigh(fileIndexHi), + nFileIndexLow(fileIndexLo) {} + +inline +bool Utility::FileID::isNull() const +{ + return dwVolumeSerialNumber == 0 && + nFileIndexHigh == 0 && + nFileIndexLow == 0; +} + +inline +bool Utility::FileID::operator==(const FileID& rhs) const +{ + return dwVolumeSerialNumber == rhs.dwVolumeSerialNumber && + nFileIndexHigh == rhs.nFileIndexHigh && + nFileIndexLow == rhs.nFileIndexLow; +} + +inline +bool Utility::FileID::operator<(const FileID& rhs) const +{ + if (dwVolumeSerialNumber != rhs.dwVolumeSerialNumber) + return dwVolumeSerialNumber < rhs.dwVolumeSerialNumber; + + if (nFileIndexHigh != rhs.nFileIndexHigh) + return nFileIndexHigh < rhs.nFileIndexHigh; + + return nFileIndexLow < rhs.nFileIndexLow; +} + +inline +Utility::FileID::FileID(wxInputStream& stream) //read +{ + stream.Read(&dwVolumeSerialNumber, sizeof(dwVolumeSerialNumber)); + stream.Read(&nFileIndexHigh, sizeof(nFileIndexHigh)); + stream.Read(&nFileIndexLow, sizeof(nFileIndexLow)); +} + +inline +void Utility::FileID::toStream(wxOutputStream& stream) const //write +{ + stream.Write(&dwVolumeSerialNumber, sizeof(dwVolumeSerialNumber)); + stream.Write(&nFileIndexHigh, sizeof(nFileIndexHigh)); + stream.Write(&nFileIndexLow, sizeof(nFileIndexLow)); +} + +#elif defined FFS_LINUX +inline +Utility::FileID::FileID() : + deviceId(0), + inodeId(0) {} + +inline +Utility::FileID::FileID(dev_t devId, + ino_t inId) : + deviceId(devId), + inodeId(inId) {} + +inline +bool Utility::FileID::isNull() const +{ + return deviceId == 0 && + inodeId == 0; +} + +inline +bool Utility::FileID::operator==(const FileID& rhs) const +{ + return deviceId == rhs.deviceId && + inodeId == rhs.inodeId; +} + +inline +bool Utility::FileID::operator<(const FileID& rhs) const +{ + if (deviceId != rhs.deviceId) + return deviceId < rhs.deviceId; + + return inodeId < rhs.inodeId; +} + +inline +Utility::FileID::FileID(wxInputStream& stream) //read +{ + stream.Read(&deviceId, sizeof(deviceId)); + stream.Read(&inodeId, sizeof(inodeId)); +} + +inline +void Utility::FileID::toStream(wxOutputStream& stream) const //write +{ + stream.Write(&deviceId, sizeof(deviceId)); + stream.Write(&inodeId, sizeof(inodeId)); +} +#endif +inline +bool Utility::FileID::operator!=(const FileID& rhs) const +{ + return !(*this == rhs); +} + +#endif // FILEID_H_INCLUDED diff --git a/shared/fileTraverser.cpp b/shared/fileTraverser.cpp index c323f1a3..7d2615bf 100644 --- a/shared/fileTraverser.cpp +++ b/shared/fileTraverser.cpp @@ -6,6 +6,7 @@ #ifdef FFS_WIN #include <wx/msw/wrapwin.h> //includes "windows.h" +#include "longPathPrefix.h" #elif defined FFS_LINUX #include <sys/stat.h> @@ -22,7 +23,7 @@ public: ~CloseHandleOnExit() { - CloseHandle(fileHandle_); + ::CloseHandle(fileHandle_); } private: @@ -65,7 +66,7 @@ inline bool setWin32FileInformationFromSymlink(const Zstring linkName, FreeFileSync::TraverseCallback::FileInfo& output) { //open handle to target of symbolic link - HANDLE hFile = ::CreateFile(linkName.c_str(), + HANDLE hFile = ::CreateFile(FreeFileSync::applyLongPathPrefix(linkName).c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, @@ -129,8 +130,8 @@ bool traverseDirectory(const Zstring& directory, FreeFileSync::TraverseCallback* directory + globalFunctions::FILE_NAME_SEPARATOR; WIN32_FIND_DATA fileMetaData; - HANDLE searchHandle = FindFirstFile((directoryFormatted + DefaultChar('*')).c_str(), //__in LPCTSTR lpFileName - &fileMetaData); //__out LPWIN32_FIND_DATA lpFindFileData + HANDLE searchHandle = ::FindFirstFile(applyLongPathPrefix(directoryFormatted + DefaultChar('*')).c_str(), //__in LPCTSTR lpFileName + &fileMetaData); //__out LPWIN32_FIND_DATA lpFindFileData //no noticable performance difference compared to FindFirstFileEx with FindExInfoBasic, FIND_FIRST_EX_CASE_SENSITIVE and/or FIND_FIRST_EX_LARGE_FETCH if (searchHandle == INVALID_HANDLE_VALUE) @@ -140,8 +141,10 @@ bool traverseDirectory(const Zstring& directory, FreeFileSync::TraverseCallback* return true; //else: we have a problem... report it: - const wxString errorMessage = wxString(_("Error traversing directory:")) + wxT("\n\"") + zToWx(directory) + wxT("\"") ; - switch (sink->onError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted(lastError))) + const wxString errorMessage = wxString(_("Error traversing directory:")) + wxT("\n\"") + zToWx(directory) + wxT("\"") + wxT("\n\n") + + FreeFileSync::getLastErrorFormatted(lastError); + + switch (sink->onError(errorMessage)) { case TraverseCallback::TRAVERSING_STOP: return false; @@ -205,8 +208,8 @@ bool traverseDirectory(const Zstring& directory, FreeFileSync::TraverseCallback* } } } - while (FindNextFile(searchHandle, // handle to search - &fileMetaData)); // pointer to structure for data on found file + while (::FindNextFile(searchHandle, // handle to search + &fileMetaData)); // pointer to structure for data on found file const DWORD lastError = ::GetLastError(); if (lastError == ERROR_NO_MORE_FILES) @@ -362,3 +365,6 @@ void FreeFileSync::traverseFolder(const Zstring& directory, else traverseDirectory<false>(directoryFormatted, sink, 0); } + + + diff --git a/shared/localization.cpp b/shared/localization.cpp index ad3cbb99..5f017989 100644 --- a/shared/localization.cpp +++ b/shared/localization.cpp @@ -53,7 +53,7 @@ LocalizationInfo::LocalizationInfo() newEntry.languageID = wxLANGUAGE_SPANISH; newEntry.languageName = wxT("Español"); newEntry.languageFile = wxT("spanish.lng"); - newEntry.translatorName = wxT("David RodrÃguez"); + newEntry.translatorName = wxT("Alexis MartÃnez"); newEntry.languageFlag = wxT("spain.png"); locMapping.push_back(newEntry); @@ -134,6 +134,13 @@ LocalizationInfo::LocalizationInfo() newEntry.languageFlag = wxT("finland.png"); locMapping.push_back(newEntry); + newEntry.languageID = wxLANGUAGE_SWEDISH; + newEntry.languageName = wxT("Svenska"); + newEntry.languageFile = wxT("swedish.lng"); + newEntry.translatorName = wxT("Ã…ke Engelbrektson"); + newEntry.languageFlag = wxT("sweden.png"); + locMapping.push_back(newEntry); + newEntry.languageID = wxLANGUAGE_TURKISH; newEntry.languageName = wxT("Türkçe"); newEntry.languageFile = wxT("turkish.lng"); @@ -230,6 +237,10 @@ int mapLanguageDialect(const int language) case wxLANGUAGE_SPANISH_VENEZUELA: return wxLANGUAGE_SPANISH; + //variants of wxLANGUAGE_SWEDISH + case wxLANGUAGE_SWEDISH_FINLAND: + return wxLANGUAGE_SWEDISH; + //case wxLANGUAGE_CZECH: //case wxLANGUAGE_FINNISH: //case wxLANGUAGE_JAPANESE: diff --git a/shared/longPathPrefix.cpp b/shared/longPathPrefix.cpp new file mode 100644 index 00000000..9ce74c8b --- /dev/null +++ b/shared/longPathPrefix.cpp @@ -0,0 +1,95 @@ +#include "longPathPrefix.h" +#include <boost/scoped_array.hpp> +#include <wx/msw/wrapwin.h> //includes "windows.h" +#include "fileError.h" +#include "systemFunctions.h" +#include "stringConv.h" +#include <wx/intl.h> + +namespace +{ +Zstring getFullPathName(const Zstring& relativeName, size_t proposedBufferSize = 1000) +{ + using namespace FreeFileSync; + + boost::scoped_array<DefaultChar> fullPath(new DefaultChar[proposedBufferSize]); + const DWORD rv = ::GetFullPathName( + relativeName.c_str(), //__in LPCTSTR lpFileName, + proposedBufferSize, //__in DWORD nBufferLength, + fullPath.get(), //__out LPTSTR lpBuffer, + NULL); //__out LPTSTR *lpFilePart + if (rv == 0 || rv == proposedBufferSize) + throw FileError(wxString(_("Error resolving full path name:")) + wxT("\n\"") + zToWx(relativeName) + wxT("\"") + + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); + if (rv > proposedBufferSize) + return getFullPathName(relativeName, rv); + + return fullPath.get(); +} +} + + +Zstring FreeFileSync::resolveRelativePath(const Zstring& path) //throw() +{ + try + { + return getFullPathName(path); + } + catch (...) + { + return path; + } +} + + +//there are two flavors of long path prefix: one for UNC paths, one for regular paths +const Zstring LONG_PATH_PREFIX = DefaultStr("\\\\?\\"); +const Zstring LONG_PATH_PREFIX_UNC = DefaultStr("\\\\?\\UNC"); + +template <size_t max_path> +inline +Zstring applyLongPathPrefixImpl(const Zstring& path) +{ + if ( path.length() >= max_path && //maximum allowed path length without prefix is (MAX_PATH - 1) + !path.StartsWith(LONG_PATH_PREFIX)) + { + if (path.StartsWith(DefaultStr("\\\\"))) //UNC-name, e.g. \\zenju-pc\Users + return LONG_PATH_PREFIX_UNC + path.AfterFirst(DefaultChar('\\')); //convert to \\?\UNC\zenju-pc\Users + else + return LONG_PATH_PREFIX + path; //prepend \\?\ prefix + } + + //fallback + return path; +} + + +Zstring FreeFileSync::applyLongPathPrefix(const Zstring& path) +{ + return applyLongPathPrefixImpl<MAX_PATH>(path); +} + + +Zstring FreeFileSync::applyLongPathPrefixCreateDir(const Zstring& path) //throw() +{ + //special rule for ::CreateDirectoryEx(): MAX_PATH - 12(=^ 8.3 filename) is threshold + return applyLongPathPrefixImpl<MAX_PATH - 12>(path); +} + + +Zstring FreeFileSync::removeLongPathPrefix(const Zstring& path) //throw() +{ + if (path.StartsWith(LONG_PATH_PREFIX)) + { + Zstring finalPath = path; + if (path.StartsWith(LONG_PATH_PREFIX_UNC)) //UNC-name + finalPath.Replace(LONG_PATH_PREFIX_UNC, DefaultStr("\\"), false); + else + finalPath.Replace(LONG_PATH_PREFIX, DefaultStr(""), false); + return finalPath; + } + + //fallback + return path; +} + diff --git a/shared/longPathPrefix.h b/shared/longPathPrefix.h new file mode 100644 index 00000000..e4834184 --- /dev/null +++ b/shared/longPathPrefix.h @@ -0,0 +1,27 @@ +#ifndef LONGPATHPREFIX_H_INCLUDED +#define LONGPATHPREFIX_H_INCLUDED + +#ifndef FFS_WIN +use in windows build only! +#endif + +#include "zstring.h" + +namespace FreeFileSync +{ + +Zstring resolveRelativePath(const Zstring& path); //throw() + +//handle filenames longer-equal 260 (== MAX_PATH) characters by applying \\?\-prefix (Reference: http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx#maxpath) +/* +1. path must be absolute +2. if path is smaller than MAX_PATH nothing is changed! +3. path may already contain \\?\-prefix +*/ +Zstring applyLongPathPrefix(const Zstring& path); //throw() +Zstring applyLongPathPrefixCreateDir(const Zstring& path); //throw() -> special rule for ::CreateDirectoryEx(): MAX_PATH - 12(=^ 8.3 filename) is threshold + +Zstring removeLongPathPrefix(const Zstring& path); //throw() +} + +#endif // LONGPATHPREFIX_H_INCLUDED diff --git a/shared/recycler.cpp b/shared/recycler.cpp new file mode 100644 index 00000000..b3bb87dd --- /dev/null +++ b/shared/recycler.cpp @@ -0,0 +1,128 @@ +#include "recycler.h" +#include "dllLoader.h" +#include <wx/intl.h> +#include <wx/msw/wrapwin.h> //includes "windows.h" +#include "buildInfo.h" +#include "staticAssert.h" +#include <algorithm> +#include <functional> +//#include "../shared/longPathPrefix.h" + +const std::wstring& getRecyclerDllName() +{ + static const std::wstring filename( + Utility::is64BitBuild ? + L"Recycler_x64.dll": + L"Recycler_Win32.dll"); + + assert_static(Utility::is32BitBuild || Utility::is64BitBuild); + + return filename; +} + + +bool vistaOrLater() +{ + OSVERSIONINFO osvi; + ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + + //IFileOperation is supported with Vista and later + if (GetVersionEx(&osvi)) + return osvi.dwMajorVersion > 5; + //XP has majorVersion == 5, minorVersion == 1 + //Vista has majorVersion == 6, minorVersion == 0 + //version overview: http://msdn.microsoft.com/en-us/library/ms724834(VS.85).aspx + return false; +} + +/* +Performance test: delete 1000 files +------------------------------------ +SHFileOperation - single file 33s +SHFileOperation - multiple files 2,1s +IFileOperation - single file 33s +IFileOperation - multiple files 2,1s + +=> SHFileOperation and IFileOperation have nearly IDENTICAL performance characteristics! + +Nevertheless, let's use IFileOperation for better error reporting! +*/ + +void FreeFileSync::moveToWindowsRecycler(const Zstring& fileToDelete) //throw (FileError) +{ + std::vector<Zstring> fileNames; + fileNames.push_back(fileToDelete); + moveToWindowsRecycler(fileNames); //throw (FileError) +} + + +void FreeFileSync::moveToWindowsRecycler(const std::vector<Zstring>& filesToDelete) //throw (FileError) +{ + if (filesToDelete.empty()) + return; + + static const bool useIFileOperation = vistaOrLater(); + + if (useIFileOperation) //new recycle bin usage: available since Vista + { + typedef bool (*MoveToRecycleBinFunc)( + const wchar_t* fileNames[], + size_t fileNo, //size of fileNames array + wchar_t* errorMessage, + size_t errorBufferLen); + + static const MoveToRecycleBinFunc moveToRecycler = + Utility::loadDllFunction<MoveToRecycleBinFunc>(getRecyclerDllName().c_str(), "moveToRecycleBin"); + + if (moveToRecycler == NULL) + throw FileError(wxString(_("Could not load a required DLL:")) + wxT(" \"") + getRecyclerDllName().c_str() + wxT("\"")); + + //#warning moving long file paths to recycler does not work! clarify! +// std::vector<Zstring> temp; +// std::transform(filesToDelete.begin(), filesToDelete.end(), +// std::back_inserter(temp), std::ptr_fun(FreeFileSync::removeLongPathPrefix)); //::IFileOperation() can't handle \\?\-prefix! + + std::vector<const wchar_t*> fileNames; + std::transform(filesToDelete.begin(), filesToDelete.end(), + std::back_inserter(fileNames), std::mem_fun_ref(&Zstring::c_str)); + + wchar_t errorMessage[2000]; + if (!(*moveToRecycler)(&fileNames[0], //array must not be empty + fileNames.size(), + errorMessage, + 2000)) + { + throw FileError(wxString(_("Error moving to Recycle Bin:")) + wxT("\n\"") + fileNames[0] + wxT("\"") + //report first file only... better than nothing + + wxT("\n\n") + + wxT("(") + errorMessage + wxT(")")); + } + } + else //regular recycle bin usage: available since XP + { + Zstring filenameDoubleNull; + for (std::vector<Zstring>::const_iterator i = filesToDelete.begin(); i != filesToDelete.end(); ++i) + { + //#warning moving long file paths to recycler does not work! clarify! + //filenameDoubleNull += removeLongPathPrefix(*i); //::SHFileOperation() can't handle \\?\-prefix! + filenameDoubleNull += *i; //::SHFileOperation() can't handle \\?\-prefix! + filenameDoubleNull += DefaultChar(0); + } + + SHFILEOPSTRUCT fileOp; + fileOp.hwnd = NULL; + fileOp.wFunc = FO_DELETE; + fileOp.pFrom = filenameDoubleNull.c_str(); + fileOp.pTo = NULL; + fileOp.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_SILENT | FOF_NOERRORUI; + fileOp.fAnyOperationsAborted = false; + fileOp.hNameMappings = NULL; + fileOp.lpszProgressTitle = NULL; + + if (SHFileOperation(&fileOp) != 0 || fileOp.fAnyOperationsAborted) + { + throw FileError(wxString(_("Error moving to Recycle Bin:")) + wxT("\n\"") + filenameDoubleNull.c_str() + wxT("\"")); //report first file only... better than nothing + } + } +} + diff --git a/shared/recycler.h b/shared/recycler.h new file mode 100644 index 00000000..14aff4c0 --- /dev/null +++ b/shared/recycler.h @@ -0,0 +1,21 @@ +#ifndef RECYCLER_H_INCLUDED +#define RECYCLER_H_INCLUDED + +#include "fileError.h" +#include "zstring.h" +#include <vector> + +#ifndef FFS_WIN +use in windows build only! +#endif + + +namespace FreeFileSync +{ +//single-file processing +void moveToWindowsRecycler(const Zstring& fileToDelete); //throw (FileError) +//multi-file processing: about a factor of 15 faster than single-file +void moveToWindowsRecycler(const std::vector<Zstring>& filesToDelete); //throw (FileError) -> on error reports about first file only! +} + +#endif // RECYCLER_H_INCLUDED diff --git a/shared/shadow.cpp b/shared/shadow.cpp index 29eec53b..7e3b34c0 100644 --- a/shared/shadow.cpp +++ b/shared/shadow.cpp @@ -2,6 +2,10 @@ #include <wx/msw/wrapwin.h> //includes "windows.h" #include <wx/intl.h> #include "systemConstants.h" +#include "dllLoader.h" +#include <stdexcept> +#include "staticAssert.h" +#include "buildInfo.h" using FreeFileSync::ShadowCopy; @@ -12,7 +16,6 @@ bool newerThanXP() ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); - //symbolic links are supported starting with Vista if (GetVersionEx(&osvi)) return osvi.dwMajorVersion > 5 || (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion > 1) ; @@ -42,93 +45,78 @@ bool runningWOW64() //test if process is running under WOW64 (reference http://m } -class ShadowCopy::ShadowlDllHandler //dynamically load windows API functions +const wxString& getShadowDllName() { - typedef bool (*CreateShadowCopyFct)( //volumeName must end with "\", while shadowVolName does not end with "\" - const wchar_t* volumeName, - wchar_t* shadowVolName, - unsigned int shadowBufferLen, - void** backupHandle, - wchar_t* errorMessage, - unsigned int errorBufferLen); + /* + distinguish a bunch of VSS builds: we use XP and Server 2003 implementations... + VSS version and compatibility overview: http://msdn.microsoft.com/en-us/library/aa384627(VS.85).aspx + */ - typedef void (*ReleaseShadowCopyFct)(void* backupHandle); + static const wxString filename( + Utility::is64BitBuild ? + (newerThanXP() ? + wxT("Shadow_Server2003_x64.dll") : + wxT("Shadow_XP_x64.dll")) : -public: - static const wxString& getShadowDllName() - { - /* - distinguish a bunch of VSS builds: we use XP and Server 2003 implementations... - VSS version and compatibility overview: http://msdn.microsoft.com/en-us/library/aa384627(VS.85).aspx - */ -#if defined _WIN64 //note: _WIN32 is defined for 64-bit compilations, too, while _WIN64 only for 64-bit - static const wxString filename(newerThanXP() ? - wxT("Shadow_Server2003_x64.dll") : - wxT("Shadow_XP_x64.dll")); -#elif defined(_WIN32) - static const wxString filename(newerThanXP() ? - wxT("Shadow_Server2003_win32.dll") : - wxT("Shadow_XP_win32.dll")); -#else - Are we at 128 bit already? -#endif - return filename; - } + (newerThanXP() ? + wxT("Shadow_Server2003_Win32.dll") : + wxT("Shadow_XP_Win32.dll"))); - ShadowlDllHandler() : - createShadowCopy(NULL), - releaseShadowCopy(NULL), - hShadow(NULL) - { - //get a handle to the DLL module containing the required functionality - hShadow = ::LoadLibrary(getShadowDllName().c_str()); - if (hShadow) - { - createShadowCopy = reinterpret_cast<CreateShadowCopyFct>(::GetProcAddress(hShadow, "createShadowCopy")); - releaseShadowCopy = reinterpret_cast<ReleaseShadowCopyFct>(::GetProcAddress(hShadow, "releaseShadowCopy")); - } - } + assert_static(Utility::is32BitBuild || Utility::is64BitBuild); - ~ShadowlDllHandler() - { - if (hShadow) ::FreeLibrary(hShadow); - } - - CreateShadowCopyFct createShadowCopy; - ReleaseShadowCopyFct releaseShadowCopy; + return filename; +} -private: - HINSTANCE hShadow; -}; //############################################################################################################# ShadowCopy::ShadowCopy() : - backupHandle(NULL) -{ - shadowDll = new ShadowlDllHandler; -} + backupHandle(NULL) {} ShadowCopy::~ShadowCopy() { if (backupHandle != NULL) - shadowDll->releaseShadowCopy(backupHandle); + { + typedef void (*ReleaseShadowCopyFct)(void* backupHandle); + static const ReleaseShadowCopyFct releaseShadowCopy = + Utility::loadDllFunction<ReleaseShadowCopyFct>(getShadowDllName().c_str(), "releaseShadowCopy"); + + if (releaseShadowCopy == NULL) + throw std::logic_error("Could not load \"releaseShadowCopy\"!"); //shouldn't arrive here! - delete shadowDll; + releaseShadowCopy(backupHandle); + } } Zstring ShadowCopy::makeShadowCopy(const Zstring& inputFile) { + typedef bool (*CreateShadowCopyFct)( //volumeName must end with "\", while shadowVolName does not end with "\" + const wchar_t* volumeName, + wchar_t* shadowVolName, + unsigned int shadowBufferLen, + void** backupHandle, + wchar_t* errorMessage, + unsigned int errorBufferLen); + static const CreateShadowCopyFct createShadowCopy = + Utility::loadDllFunction<CreateShadowCopyFct>(getShadowDllName().c_str(), "createShadowCopy"); + + + typedef void (*ReleaseShadowCopyFct)(void* backupHandle); + static const ReleaseShadowCopyFct releaseShadowCopy = + Utility::loadDllFunction<ReleaseShadowCopyFct>(getShadowDllName().c_str(), "releaseShadowCopy"); + + + //check if shadow copy dll was loaded correctly - if ( shadowDll->createShadowCopy == NULL || - shadowDll->releaseShadowCopy == NULL) + if ( createShadowCopy == NULL || + releaseShadowCopy == NULL) { wxString errorMsg = _("Error copying locked file %x!"); errorMsg.Replace(wxT("%x"), wxString(wxT("\"")) + inputFile.c_str() + wxT("\"")); throw FileError(errorMsg + wxT("\n\n") + _("Error starting Volume Shadow Copy Service!") + wxT("\n") + - _("Could not load a required DLL:") + wxT(" \"") + ShadowlDllHandler::getShadowDllName() + wxT("\"")); + _("Could not load a required DLL:") + wxT(" \"") + getShadowDllName() + wxT("\"")); } //VSS does not support running under WOW64 except for Windows XP and Windows Server 2003 @@ -146,7 +134,7 @@ Zstring ShadowCopy::makeShadowCopy(const Zstring& inputFile) //--------------------------------------------------------------------------------------------------------- wchar_t volumeNameRaw[1000]; - if (!GetVolumePathName(inputFile.c_str(), //__in LPCTSTR lpszFileName, + if (!::GetVolumePathName(inputFile.c_str(), //__in LPCTSTR lpszFileName, volumeNameRaw, //__out LPTSTR lpszVolumePathName, 1000)) //__in DWORD cchBufferLength { @@ -164,7 +152,7 @@ Zstring ShadowCopy::makeShadowCopy(const Zstring& inputFile) //release old shadow copy if (backupHandle != NULL) { - shadowDll->releaseShadowCopy(backupHandle); + releaseShadowCopy(backupHandle); backupHandle = NULL; } realVolumeLast.clear(); //...if next call fails... @@ -175,7 +163,7 @@ Zstring ShadowCopy::makeShadowCopy(const Zstring& inputFile) void* backupHandleTmp = NULL; wchar_t errorMessage[1000]; - if (!shadowDll->createShadowCopy( + if (!createShadowCopy( volumeNameFormatted.c_str(), shadowVolName, 1000, @@ -194,7 +182,8 @@ Zstring ShadowCopy::makeShadowCopy(const Zstring& inputFile) backupHandle = backupHandleTmp; } - const size_t pos = inputFile.find(volumeNameFormatted); + //input file is always absolute! directory formatting takes care of this! Therefore volume name can always be found. + const size_t pos = inputFile.find(volumeNameFormatted); //inputFile needs NOT to begin with volumeNameFormatted: consider for example \\?\ prefix! if (pos == Zstring::npos) { wxString errorMsg = _("Error copying locked file %x!"); @@ -209,4 +198,3 @@ Zstring ShadowCopy::makeShadowCopy(const Zstring& inputFile) return shadowVolumeLast + Zstring(inputFile.c_str() + pos + volumeNameFormatted.length()); } - diff --git a/shared/shadow.h b/shared/shadow.h index 79e0e59c..78100f78 100644 --- a/shared/shadow.h +++ b/shared/shadow.h @@ -23,9 +23,6 @@ private: ShadowCopy(const ShadowCopy&); ShadowCopy& operator=(const ShadowCopy&); - class ShadowlDllHandler; - const ShadowlDllHandler* shadowDll; - Zstring realVolumeLast; //buffer last volume name Zstring shadowVolumeLast; //buffer last created shadow volume void* backupHandle; diff --git a/shared/zstring.cpp b/shared/zstring.cpp index cb288ea2..c3d5ba8e 100644 --- a/shared/zstring.cpp +++ b/shared/zstring.cpp @@ -4,6 +4,7 @@ #ifdef FFS_WIN #include <wx/msw/wrapwin.h> //includes "windows.h" #include "dllLoader.h" +#include <boost/scoped_array.hpp> #endif //FFS_WIN #ifdef __WXDEBUG__ @@ -45,60 +46,122 @@ AllocationCount& AllocationCount::getInstance() } #endif - #ifdef FFS_WIN +bool hasInvariantLocale() +{ + OSVERSIONINFO osvi; + ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + + //invariant locale has been introduced with XP + if (GetVersionEx(&osvi)) + return osvi.dwMajorVersion > 5 || + (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion >= 1); //XP has majorVersion == 5, minorVersion == 1 + //overview: http://msdn.microsoft.com/en-us/library/ms724834(VS.85).aspx + return false; +} #ifndef LOCALE_INVARIANT #define LOCALE_INVARIANT 0x007f #endif +//warning: LOCALE_INVARIANT is NOT available with Windows 2000, so we have to make yet another distinction... +namespace +{ +const LCID invariantLocale = hasInvariantLocale() ? + LOCALE_INVARIANT : + MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT); //see: http://msdn.microsoft.com/en-us/goglobal/bb688122.aspx +} + inline -int compareStringsWin32(const wchar_t* a, const wchar_t* b, const int aCount = -1, const int bCount = -1) +int compareFilenamesWin32(const wchar_t* a, const wchar_t* b, size_t sizeA, size_t sizeB) { - //try to call "CompareStringOrdinal" first for low-level string comparison: unfortunately available not before Windows Vista! + //try to call "CompareStringOrdinal" for low-level string comparison: unfortunately available not before Windows Vista! + //by a factor ~3 faster than old string comparison using "LCMapString" typedef int (WINAPI *CompareStringOrdinalFunc)( LPCWSTR lpString1, int cchCount1, LPCWSTR lpString2, int cchCount2, BOOL bIgnoreCase); - static const CompareStringOrdinalFunc ordinalCompare = Utility::loadDllFunKernel<CompareStringOrdinalFunc>("CompareStringOrdinal"); + static const CompareStringOrdinalFunc ordinalCompare = Utility::loadDllFunction<CompareStringOrdinalFunc>(L"kernel32.dll", "CompareStringOrdinal"); - - //we're lucky here! This additional test for "CompareStringOrdinal" has no noticeable performance impact!! - if (ordinalCompare != NULL) + if (ordinalCompare != NULL) //this additional test has no noticeable performance impact { const int rv = (*ordinalCompare)( a, //pointer to first string - aCount, //size, in bytes or characters, of first string + sizeA, //size, in bytes or characters, of first string b, //pointer to second string - bCount, //size, in bytes or characters, of second string + sizeB, //size, in bytes or characters, of second string true); //ignore case - if (rv == 0) throw std::runtime_error("Error comparing strings (ordinal)!"); else return rv - 2; //convert to C-style string compare result } - else //fallback to "CompareString". Attention: this function is NOT accurate: for example "weiß" == "weiss"!!! + else //fallback { - //DON'T use lstrcmpi() here! It uses word sort and is locale dependent! - //Use CompareString() with "SORT_STRINGSORT" instead!!! +//do NOT use "CompareString"; this function is NOT accurate (even with LOCALE_INVARIANT and SORT_STRINGSORT): for example "weiß" == "weiss"!!! +//the only reliable way to compare filenames (with XP) is to call "CharUpper" or "LCMapString": - const int rv = CompareString( - LOCALE_INVARIANT, //locale independent - NORM_IGNORECASE | SORT_STRINGSORT, //comparison-style options - a, //pointer to first string - aCount, //size, in bytes or characters, of first string - b, //pointer to second string - bCount); //size, in bytes or characters, of second string + const size_t minSize = std::min(sizeA, sizeB); - if (rv == 0) - throw std::runtime_error("Error comparing strings!"); - else - return rv - 2; //convert to C-style string compare result + if (minSize == 0) //LCMapString does not allow input sizes of 0! + return sizeA - sizeB; + + int rv = 0; //always initialize... + if (minSize <= 5000) //performance optimization: stack + { + wchar_t bufferA[5000]; + wchar_t bufferB[5000]; + + if (::LCMapString( //faster than CharUpperBuff + wmemcpy or CharUpper + wmemcpy and same speed like ::CompareString() + invariantLocale, //__in LCID Locale, + LCMAP_UPPERCASE, //__in DWORD dwMapFlags, + a, //__in LPCTSTR lpSrcStr, + minSize, //__in int cchSrc, + bufferA, //__out LPTSTR lpDestStr, + 5000 //__in int cchDest + ) == 0) + throw std::runtime_error("Error comparing strings! (LCMapString)"); + + if (::LCMapString(invariantLocale, LCMAP_UPPERCASE, b, minSize, bufferB, 5000) == 0) + throw std::runtime_error("Error comparing strings! (LCMapString)"); + + rv = ::wmemcmp(bufferA, bufferB, minSize); + } + else //use freestore + { + boost::scoped_array<wchar_t> bufferA(new wchar_t[minSize]); + boost::scoped_array<wchar_t> bufferB(new wchar_t[minSize]); + + if (::LCMapString(invariantLocale, LCMAP_UPPERCASE, a, minSize, bufferA.get(), minSize) == 0) + throw std::runtime_error("Error comparing strings! (LCMapString: FS)"); + + if (::LCMapString(invariantLocale, LCMAP_UPPERCASE, b, minSize, bufferB.get(), minSize) == 0) + throw std::runtime_error("Error comparing strings! (LCMapString: FS)"); + + rv = ::wmemcmp(bufferA.get(), bufferB.get(), minSize); + } + + return rv == 0 ? + sizeA - sizeB : + rv; } + +// const int rv = CompareString( +// invariantLocale, //locale independent +// NORM_IGNORECASE | SORT_STRINGSORT, //comparison-style options +// a, //pointer to first string +// aCount, //size, in bytes or characters, of first string +// b, //pointer to second string +// bCount); //size, in bytes or characters, of second string +// +// if (rv == 0) +// throw std::runtime_error("Error comparing strings!"); +// else +// return rv - 2; //convert to C-style string compare result } #endif @@ -106,13 +169,13 @@ int compareStringsWin32(const wchar_t* a, const wchar_t* b, const int aCount = - #ifdef FFS_WIN int Zstring::CmpNoCase(const DefaultChar* other) const { - return ::compareStringsWin32(c_str(), other); //way faster than wxString::CmpNoCase()!! + return ::compareFilenamesWin32(c_str(), other, length(), ::wcslen(other)); //way faster than wxString::CmpNoCase() } int Zstring::CmpNoCase(const Zstring& other) const { - return ::compareStringsWin32(c_str(), other.c_str(), length(), other.length()); //way faster than wxString::CmpNoCase()!! + return ::compareFilenamesWin32(c_str(), other.c_str(), length(), other.length()); //way faster than wxString::CmpNoCase() } #endif @@ -277,14 +340,17 @@ std::vector<Zstring> Zstring::Tokenize(const DefaultChar delimiter) const #ifdef FFS_WIN -Zstring& Zstring::MakeLower() +Zstring& Zstring::MakeUpper() { const size_t thisLen = length(); if (thisLen == 0) return *this; reserve(thisLen); //make unshared - ::CharLower(data()); //use Windows' lower case conversion + + //use Windows' upper case conversion: faster than ::CharUpper() + if (::LCMapString(invariantLocale, LCMAP_UPPERCASE, data(), thisLen, data(), thisLen) == 0) + throw std::runtime_error("Error converting to upper case! (LCMapString)"); return *this; } @@ -484,3 +550,4 @@ void Zstring::reserve(size_t capacityNeeded) //make unshared and check capacity descr->capacity = newCapacity; } } + diff --git a/shared/zstring.h b/shared/zstring.h index d0be30bf..cb047e15 100644 --- a/shared/zstring.h +++ b/shared/zstring.h @@ -52,7 +52,7 @@ public: #ifdef FFS_WIN int CmpNoCase(const DefaultChar* other) const; int CmpNoCase(const Zstring& other) const; - Zstring& MakeLower(); + Zstring& MakeUpper(); #endif int Cmp(const DefaultChar* other) const; int Cmp(const Zstring& other) const; @@ -134,6 +134,28 @@ template <class T> Zstring numberToZstring(const T& number); //convert number to Zstring + + + + + + + + + + + + + + + + + + + + + + //####################################################################################### //begin of implementation |