diff options
author | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 16:55:48 +0200 |
---|---|---|
committer | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 16:55:48 +0200 |
commit | daea231de0ae28fc8343f29f09d0457cc0591461 (patch) | |
tree | a1d572442d2c903e40741a859ad47c8b0d740969 /library | |
parent | 1.13 (diff) | |
download | FreeFileSync-daea231de0ae28fc8343f29f09d0457cc0591461.tar.gz FreeFileSync-daea231de0ae28fc8343f29f09d0457cc0591461.tar.bz2 FreeFileSync-daea231de0ae28fc8343f29f09d0457cc0591461.zip |
1.14
Diffstat (limited to 'library')
-rw-r--r-- | library/CustomGrid.cpp | 94 | ||||
-rw-r--r-- | library/CustomGrid.h | 19 | ||||
-rw-r--r-- | library/fileHandling.cpp | 288 | ||||
-rw-r--r-- | library/fileHandling.h | 33 | ||||
-rw-r--r-- | library/globalFunctions.cpp | 4 | ||||
-rw-r--r-- | library/globalFunctions.h | 3 | ||||
-rw-r--r-- | library/misc.cpp | 5 | ||||
-rw-r--r-- | library/multithreading.cpp | 31 | ||||
-rw-r--r-- | library/multithreading.h | 1 | ||||
-rw-r--r-- | library/processXml.cpp | 102 | ||||
-rw-r--r-- | library/processXml.h | 6 | ||||
-rw-r--r-- | library/resources.cpp | 30 | ||||
-rw-r--r-- | library/sorting.h | 185 | ||||
-rw-r--r-- | library/statusHandler.cpp | 28 | ||||
-rw-r--r-- | library/statusHandler.h | 4 | ||||
-rw-r--r-- | library/zstring.cpp | 396 | ||||
-rw-r--r-- | library/zstring.h | 580 |
17 files changed, 1547 insertions, 262 deletions
diff --git a/library/CustomGrid.cpp b/library/CustomGrid.cpp index 1abf73a7..25ceb537 100644 --- a/library/CustomGrid.cpp +++ b/library/CustomGrid.cpp @@ -2,6 +2,7 @@ #include "globalFunctions.h" #include "resources.h" #include <wx/dc.h> +#include "../algorithm.h" const unsigned int MinimumRows = 15; @@ -18,6 +19,7 @@ public: gridData(0), lastNrRows(MinimumRows) {} + ~CustomGridTableBase() {} @@ -27,6 +29,7 @@ public: this->gridData = gridData; } + void SetGridIdentifier(int id) { gridIdentifier = id; @@ -43,13 +46,13 @@ public: return MinimumRows; //grid is initialized with this number of rows } + virtual bool IsEmptyCell( int row, int col ) { return (GetValue(row, col) == wxEmptyString); } - inline wxString evaluateCmpResult(const CompareFilesResult result, const bool selectedForSynchronization) { if (selectedForSynchronization) @@ -106,7 +109,7 @@ public: case 2: //file size return _("<Directory>"); case 3: //date - return gridLine.fileDescrLeft.lastWriteTime; + return wxEmptyString; } } else if (gridLine.fileDescrLeft.objType == FileDescrLine::TYPE_FILE) @@ -120,10 +123,13 @@ public: case 2: //file size return globalFunctions::includeNumberSeparator(fileSize = gridLine.fileDescrLeft.fileSize.ToString()); case 3: //date - return gridLine.fileDescrLeft.lastWriteTime; + return FreeFileSync::utcTimeToLocalString(gridLine.fileDescrLeft.lastWriteTimeRaw); } } } + else + assert (false); + break; case 2: @@ -140,7 +146,7 @@ public: case 2: //file size return _("<Directory>"); case 3: //date - return gridLine.fileDescrRight.lastWriteTime; + return wxEmptyString; } } else if (gridLine.fileDescrRight.objType == FileDescrLine::TYPE_FILE) @@ -154,20 +160,24 @@ public: case 2: //file size return globalFunctions::includeNumberSeparator(fileSize = gridLine.fileDescrRight.fileSize.ToString()); case 3: //date - return gridLine.fileDescrRight.lastWriteTime; + return FreeFileSync::utcTimeToLocalString(gridLine.fileDescrRight.lastWriteTimeRaw); } } } + else + assert (false); + break; case 3: if (col < 1) - { - return evaluateCmpResult(gridLine.cmpResult, gridLine.selectedForSynchronization);; - } + return evaluateCmpResult(gridLine.cmpResult, gridLine.selectedForSynchronization); + else + assert (false); break; default: + assert (false); break; } } @@ -176,6 +186,7 @@ public: return wxEmptyString; } + virtual void SetValue( int row, int col, const wxString& value ) { assert (false); //should not be used, since values are retrieved directly from gridRefUI @@ -319,11 +330,10 @@ CustomGrid::CustomGrid(wxWindow *parent, const wxString& name) : wxGrid(parent, id, pos, size, style, name), scrollbarsEnabled(true), - m_gridLeft(0), m_gridRight(0), m_gridMiddle(0), - gridDataTable(0), + m_gridLeft(NULL), m_gridRight(NULL), m_gridMiddle(NULL), + gridDataTable(NULL), currentSortColumn(-1), - sortMarker(0) -{} + sortMarker(NULL) {} CustomGrid::~CustomGrid() {} @@ -335,7 +345,7 @@ bool CustomGrid::CreateGrid(int numRows, int numCols, wxGrid::wxGridSelectionMod //This is done in CreateGrid instead of SetTable method since source code is generated and wxFormbuilder invokes CreatedGrid by default. gridDataTable = new CustomGridTableBase(numRows, numCols); - SetTable(gridDataTable, true, selmode); //give ownership to CustomGrid: gridDataTable is deleted automatically in CustomGrid destructor + SetTable(gridDataTable, true, selmode); //give ownership to wxGrid: gridDataTable is deleted automatically in wxGrid destructor return true; } @@ -356,7 +366,7 @@ void CustomGrid::SetScrollbar(int orientation, int position, int thumbSize, int } -//ensure that all grids are properly aligned: add some extra window space to grids that have no horizontal scrollbar +//workaround: ensure that all grids are properly aligned: add some extra window space to grids that have no horizontal scrollbar void CustomGrid::adjustGridHeights() //m_gridLeft, m_gridRight, m_gridMiddle are not NULL in this context { int y1 = 0; @@ -372,17 +382,17 @@ void CustomGrid::adjustGridHeights() //m_gridLeft, m_gridRight, m_gridMiddle are { int yMax = max(y1, max(y2, y3)); - if (leadingPanel == 1) //do not handle case (y1 == yMax) here!!! Avoid back coupling! + if (::leadGrid == m_gridLeft) //do not handle case (y1 == yMax) here!!! Avoid back coupling! m_gridLeft->SetMargins(0, 0); else if (y1 < yMax) m_gridLeft->SetMargins(0, 50); - if (leadingPanel == 2) + if (::leadGrid == m_gridRight) m_gridRight->SetMargins(0, 0); else if (y2 < yMax) m_gridRight->SetMargins(0, 50); - if (leadingPanel == 3) + if (::leadGrid == m_gridMiddle) m_gridMiddle->SetMargins(0, 0); else if (y3 < yMax) m_gridMiddle->SetMargins(0, 50); @@ -400,26 +410,29 @@ void CustomGrid::DoPrepareDC(wxDC& dc) wxScrollHelper::DoPrepareDC(dc); int x, y = 0; - if (leadingPanel == 1 && this == m_gridLeft) //avoid back coupling + if (this == ::leadGrid) //avoid back coupling { - GetViewStart(&x, &y); - m_gridRight->Scroll(x, y); - m_gridMiddle->Scroll(-1, y); //scroll in y-direction only - adjustGridHeights(); //keep here to ensure m_gridLeft, m_gridRight, m_gridMiddle != NULL - } - else if (leadingPanel == 2 && this == m_gridRight) //avoid back coupling - { - GetViewStart(&x, &y); - m_gridLeft->Scroll(x, y); - m_gridMiddle->Scroll(-1, y); - adjustGridHeights(); //keep here to ensure m_gridLeft, m_gridRight, m_gridMiddle != NULL - } - else if (leadingPanel == 3 && this == m_gridMiddle) //avoid back coupling - { - GetViewStart(&x, &y); - m_gridLeft->Scroll(-1, y); - m_gridRight->Scroll(-1, y); - adjustGridHeights(); //keep here to ensure m_gridLeft, m_gridRight, m_gridMiddle != NULL + if (this == m_gridLeft) + { + GetViewStart(&x, &y); + m_gridRight->Scroll(x, y); + m_gridMiddle->Scroll(-1, y); //scroll in y-direction only + adjustGridHeights(); //keep here to ensure m_gridLeft, m_gridRight, m_gridMiddle != NULL + } + else if (this == m_gridRight) + { + GetViewStart(&x, &y); + m_gridLeft->Scroll(x, y); + m_gridMiddle->Scroll(-1, y); + adjustGridHeights(); //keep here to ensure m_gridLeft, m_gridRight, m_gridMiddle != NULL + } + else if (this == m_gridMiddle) + { + GetViewStart(&x, &y); + m_gridLeft->Scroll(-1, y); + m_gridRight->Scroll(-1, y); + adjustGridHeights(); //keep here to ensure m_gridLeft, m_gridRight, m_gridMiddle != NULL + } } } @@ -427,12 +440,10 @@ void CustomGrid::DoPrepareDC(wxDC& dc) //these classes will scroll together, hence the name ;) void CustomGrid::setScrollFriends(CustomGrid* gridLeft, CustomGrid* gridRight, CustomGrid* gridMiddle) { - assert(gridLeft); - assert(gridRight); - assert(gridMiddle); + assert(gridLeft && gridRight && gridMiddle); - m_gridLeft = gridLeft; - m_gridRight = gridRight; + m_gridLeft = gridLeft; + m_gridRight = gridRight; m_gridMiddle = gridMiddle; assert(gridDataTable); @@ -479,3 +490,4 @@ void CustomGrid::DrawColLabel(wxDC& dc, int col) dc.DrawBitmap(*sortMarker, GetColRight(col) - 16 - 2, 2, true); //respect 2-pixel border } } + diff --git a/library/CustomGrid.h b/library/CustomGrid.h index 3547b090..bd2f3fa8 100644 --- a/library/CustomGrid.h +++ b/library/CustomGrid.h @@ -12,7 +12,7 @@ class CustomGridTableBase; //################################################################################## -extern int leadingPanel; +extern const wxGrid* leadGrid; //this global variable is not very nice... class CustomGrid : public wxGrid { @@ -26,27 +26,20 @@ public: ~CustomGrid(); - bool CreateGrid(int numRows, int numCols, wxGrid::wxGridSelectionModes selmode = wxGrid::wxGridSelectCells); - - void deactivateScrollbars(); - + virtual bool CreateGrid(int numRows, int numCols, wxGrid::wxGridSelectionModes selmode = wxGrid::wxGridSelectCells); //overwrite virtual method to finally get rid of the scrollbars - void SetScrollbar(int orientation, int position, int thumbSize, int range, bool refresh = true); - + virtual void SetScrollbar(int orientation, int position, int thumbSize, int range, bool refresh = true); //this method is called when grid view changes: useful for parallel updating of multiple grids - void DoPrepareDC(wxDC& dc); + virtual void DoPrepareDC(wxDC& dc); + virtual void DrawColLabel(wxDC& dc, int col); + void deactivateScrollbars(); void setScrollFriends(CustomGrid* gridLeft, CustomGrid* gridRight, CustomGrid* gridMiddle); - void setGridDataTable(GridView* gridRefUI, FileCompareResult* gridData); - void updateGridSizes(); - //set sort direction indicator on UI void setSortMarker(const int sortColumn, const wxBitmap* bitmap = &wxNullBitmap); - void DrawColLabel(wxDC& dc, int col); - private: void adjustGridHeights(); diff --git a/library/fileHandling.cpp b/library/fileHandling.cpp index e46a2b69..eb14b2f6 100644 --- a/library/fileHandling.cpp +++ b/library/fileHandling.cpp @@ -4,9 +4,16 @@ #include "resources.h" #ifdef FFS_WIN -#include <windows.h> +#include <wx/msw/wrapwin.h> //includes "windows.h" #endif // FFS_WIN +inline +bool endsWithPathSeparator(const wxChar* name) +{ + size_t len = wxStrlen(name); + return len && (name[len - 1] == GlobalResources::FILE_NAME_SEPARATOR); +} + class RecycleBin { @@ -26,13 +33,13 @@ public: return recycleBinAvailable; } - bool moveToRecycleBin(const wxString& filename) + bool moveToRecycleBin(const Zstring& filename) { if (!recycleBinAvailable) //this method should ONLY be called if recycle bin is available - throw RuntimeException(_("Initialization of Recycle Bin failed! It cannot be used!")); + throw RuntimeException(_("Initialization of Recycle Bin failed!")); #ifdef FFS_WIN - wxString filenameDoubleNull = filename + wxChar(0); + Zstring filenameDoubleNull = filename + wxChar(0); SHFILEOPSTRUCT fileOp; fileOp.hwnd = NULL; @@ -68,21 +75,21 @@ bool FreeFileSync::recycleBinExists() inline -bool moveToRecycleBin(const wxString& filename) throw(RuntimeException) +bool moveToRecycleBin(const Zstring& filename) throw(RuntimeException) { return recyclerInstance.moveToRecycleBin(filename); } inline -void FreeFileSync::removeFile(const wxString& filename, const bool useRecycleBin) +void FreeFileSync::removeFile(const Zstring& filename, const bool useRecycleBin) { if (!wxFileExists(filename)) return; //this is NOT an error situation: the manual deletion relies on it! if (useRecycleBin) { if (!moveToRecycleBin(filename)) - throw FileError(wxString(_("Error moving to recycle bin: ")) + wxT("\"") + filename + wxT("\"")); + throw FileError(wxString(_("Error moving to recycle bin:")) + wxT(" \"") + filename.c_str() + wxT("\"")); return; } @@ -90,103 +97,126 @@ void FreeFileSync::removeFile(const wxString& filename, const bool useRecycleBin if (!SetFileAttributes( filename.c_str(), //address of filename FILE_ATTRIBUTE_NORMAL //attributes to set - )) throw FileError(wxString(_("Error deleting file: ")) + wxT("\"") + filename + wxT("\"")); + )) throw FileError(wxString(_("Error deleting file:")) + wxT(" \"") + filename.c_str() + wxT("\"")); #endif //FFS_WIN if (!wxRemoveFile(filename)) - throw FileError(wxString(_("Error deleting file: ")) + wxT("\"") + filename + wxT("\"")); + throw FileError(wxString(_("Error deleting file:")) + wxT(" \"") + filename.c_str() + wxT("\"")); } -void FreeFileSync::removeDirectory(const wxString& directory, const bool useRecycleBin) +void FreeFileSync::removeDirectory(const Zstring& directory, const bool useRecycleBin) { if (!wxDirExists(directory)) return; //this is NOT an error situation: the manual deletion relies on it! if (useRecycleBin) { if (!moveToRecycleBin(directory)) - throw FileError(wxString(_("Error moving to recycle bin: ")) + wxT("\"") + directory + wxT("\"")); + throw FileError(wxString(_("Error moving to recycle bin:")) + wxT(" \"") + directory.c_str() + wxT("\"")); return; } - wxArrayString fileList; - wxArrayString dirList; + vector<Zstring> fileList; + vector<Zstring> dirList; try - { - //should be executed in own scope so that directory access does not disturb deletion! + { //should be executed in own scope so that directory access does not disturb deletion! getAllFilesAndDirs(directory, fileList, dirList); } catch (const FileError& e) { - throw FileError(wxString(_("Error deleting directory: ")) + wxT("\"") + directory + wxT("\"")); + throw FileError(wxString(_("Error deleting directory:")) + wxT(" \"") + directory.c_str() + wxT("\"")); } - for (unsigned int j = 0; j < fileList.GetCount(); ++j) - removeFile(fileList[j], useRecycleBin); + for (unsigned int j = 0; j < fileList.size(); ++j) + removeFile(fileList[j], false); - dirList.Insert(directory, 0); //this directory will be deleted last + dirList.insert(dirList.begin(), directory); //this directory will be deleted last - for (int j = int(dirList.GetCount()) - 1; j >= 0 ; --j) + for (int j = int(dirList.size()) - 1; j >= 0 ; --j) { #ifdef FFS_WIN if (!SetFileAttributes( - dirList[j].c_str(), // address of directory name - FILE_ATTRIBUTE_NORMAL // attributes to set - )) throw FileError(wxString(_("Error deleting directory: ")) + wxT("\"") + dirList[j] + wxT("\"")); + dirList[j].c_str(), // address of directory name + FILE_ATTRIBUTE_NORMAL)) // attributes to set + throw FileError(wxString(_("Error deleting directory:")) + wxT(" \"") + dirList[j].c_str() + wxT("\"")); #endif // FFS_WIN if (!wxRmdir(dirList[j])) - throw FileError(wxString(_("Error deleting directory: ")) + wxT("\"") + dirList[j] + wxT("\"")); + throw FileError(wxString(_("Error deleting directory:")) + wxT(" \"") + dirList[j].c_str() + wxT("\"")); } } -void FreeFileSync::createDirectory(const wxString& directory, int level) +void FreeFileSync::createDirectory(const Zstring& directory, const int level) { if (wxDirExists(directory)) return; - if (level == 50) //catch endless loop + if (level == 50) //catch endless recursion return; //try to create directory if (wxMkdir(directory)) return; - //if not successfull try to create containing folders first - wxString createFirstDir = wxDir(directory).GetName().BeforeLast(GlobalResources::FILE_NAME_SEPARATOR); + //if not successfull try to create parent folders first + Zstring parentDir; + if (endsWithPathSeparator(directory.c_str())) //may be valid on first level of recursion at most! Usually never true! + { + parentDir = directory.BeforeLast(GlobalResources::FILE_NAME_SEPARATOR); + parentDir = parentDir.BeforeLast(GlobalResources::FILE_NAME_SEPARATOR); + } + else + parentDir = directory.BeforeLast(GlobalResources::FILE_NAME_SEPARATOR); + + if (parentDir.empty()) return; //call function recursively - if (createFirstDir.IsEmpty()) return; - createDirectory(createFirstDir, level + 1); + createDirectory(parentDir, level + 1); //now creation should be possible if (!wxMkdir(directory)) { if (level == 0) - throw FileError(wxString(_("Error creating directory ")) + wxT("\"") + directory + wxT("\"")); + throw FileError(wxString(_("Error creating directory:")) + wxT(" \"") + directory.c_str() + wxT("\"")); } } +void FreeFileSync::copyFolderAttributes(const Zstring& source, const Zstring& target) +{ +#ifdef FFS_WIN + DWORD attr = GetFileAttributes(source.c_str()); // address of the name of a file or directory + if (attr == 0xFFFFFFFF) + throw FileError(wxString(_("Error reading file attributes:")) + wxT(" \"") + source.c_str() + wxT("\"")); + + if (!SetFileAttributes( + target.c_str(), // address of filename + attr)) // address of attributes to set + throw FileError(wxString(_("Error writing file attributes:")) + wxT(" \"") + target.c_str() + wxT("\"")); +#elif defined FFS_LINUX +//Linux doesn't use attributes for files or folders +#endif +} + + class GetAllFilesSimple : public wxDirTraverser { public: - GetAllFilesSimple(wxArrayString& files, wxArrayString& subDirectories) : + GetAllFilesSimple(vector<Zstring>& files, vector<Zstring>& subDirectories) : m_files(files), m_dirs(subDirectories) {} - wxDirTraverseResult OnDir(const wxString& dirname) { - m_dirs.Add(dirname); + m_dirs.push_back(dirname); return wxDIR_CONTINUE; } wxDirTraverseResult OnFile(const wxString& filename) { - m_files.Add(filename); + m_files.push_back(filename); return wxDIR_CONTINUE; } @@ -197,22 +227,198 @@ public: } private: - wxArrayString& m_files; - wxArrayString& m_dirs; + vector<Zstring>& m_files; + vector<Zstring>& m_dirs; }; -void FreeFileSync::getAllFilesAndDirs(const wxString& sourceDir, wxArrayString& files, wxArrayString& directories) throw(FileError) +void FreeFileSync::getAllFilesAndDirs(const Zstring& sourceDir, vector<Zstring>& files, vector<Zstring>& directories) throw(FileError) { - files.Clear(); - directories.Clear(); + files.clear(); + directories.clear(); //get all files and directories from current directory (and subdirectories) wxDir dir(sourceDir); GetAllFilesSimple traverser(files, directories); if (dir.Traverse(traverser) == (size_t)-1) - throw FileError(wxString(_("Error traversing directory ")) + wxT("\"") + sourceDir + wxT("\"")); + throw FileError(wxString(_("Error traversing directory:")) + wxT(" \"") + sourceDir.c_str() + wxT("\"")); +} + + +#ifdef FFS_WIN +class CloseOnExit +{ +public: + CloseOnExit(HANDLE searchHandle) : m_searchHandle(searchHandle) {} + + ~CloseOnExit() + { + FindClose(m_searchHandle); + } + +private: + HANDLE m_searchHandle; +}; + + +inline +void getWin32FileInformation(const WIN32_FIND_DATA& input, FreeFileSync::FileInfo& output) +{ + //convert UTC FILETIME to ANSI C format (number of seconds since Jan. 1st 1970 UTC) + wxULongLong writeTimeLong(input.ftLastWriteTime.dwHighDateTime, input.ftLastWriteTime.dwLowDateTime); + writeTimeLong /= 10000000; //reduce precision to 1 second (FILETIME has unit 10^-7 s) + writeTimeLong -= wxULongLong(2, 3054539008UL); //timeshift between ansi C time and FILETIME in seconds == 11644473600s + output.lastWriteTimeRaw = writeTimeLong.GetLo(); //it should fit into a 32bit variable now + assert(writeTimeLong.GetHi() == 0); + + output.fileSize = wxULongLong(input.nFileSizeHigh, input.nFileSizeLow); +} + +#elif defined FFS_LINUX +class EnhancedFileTraverser : public wxDirTraverser +{ +public: + EnhancedFileTraverser(FreeFileSync::FullDetailFileTraverser* sink) : m_sink(sink) {} + + virtual wxDirTraverseResult OnFile(const wxString& filename) + { + struct stat fileInfo; + if (stat(filename.c_str(), &fileInfo) != 0) + return m_sink->OnError(Zstring(_("Could not retrieve file info for:")) + wxT(" \"") + filename.c_str() + wxT("\"")); + + FreeFileSync::FileInfo details; + details.lastWriteTimeRaw = fileInfo.st_mtime; //UTC time(ANSI C format); unit: 1 second + details.fileSize = fileInfo.st_size; + + return m_sink->OnFile(filename, details); + } + + virtual wxDirTraverseResult OnDir(const wxString& dirname) + { + return m_sink->OnDir(dirname); + } + + virtual wxDirTraverseResult OnOpenError(const wxString& errorText) + { + return m_sink->OnError(errorText); + } + +private: + FreeFileSync::FullDetailFileTraverser* m_sink; +}; +#endif + + +bool FreeFileSync::traverseInDetail(const Zstring& directory, FullDetailFileTraverser* sink, const int level) +{ +#ifdef FFS_WIN + if (level == 50) //catch endless recursion + { + if (sink->OnError(Zstring(_("Error traversing directory:")) + wxT(" ") + directory) == wxDIR_STOP) + return false; + else + return true; + } + + Zstring directoryFormatted = directory; + if (!endsWithPathSeparator(directoryFormatted.c_str())) + directoryFormatted += GlobalResources::FILE_NAME_SEPARATOR; + + const Zstring filespec = directoryFormatted + wxT("*.*"); + + WIN32_FIND_DATA fileMetaData; + HANDLE searchHandle = FindFirstFile(filespec.c_str(), //pointer to name of file to search for + &fileMetaData); //pointer to returned information + + if (searchHandle == INVALID_HANDLE_VALUE) + { + if (GetLastError() == ERROR_FILE_NOT_FOUND) + return true; + //else: we have a problem... + if (sink->OnError(Zstring(_("Error traversing directory:")) + wxT(" ") + directoryFormatted) == wxDIR_STOP) + return false; + else + return true; + } + CloseOnExit dummy(searchHandle); + + do + { //don't return "." and ".." + const wxChar* name = fileMetaData.cFileName; + if ( name[0] == wxChar('.') && + ((name[1] == wxChar('.') && name[2] == wxChar('\0')) || + name[1] == wxChar('\0'))) + continue; + + const Zstring fullName = directoryFormatted + name; + if (fileMetaData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) //a directory... + { + switch (sink->OnDir(fullName)) + { + case wxDIR_IGNORE: + break; + case wxDIR_CONTINUE: + if (!traverseInDetail(fullName, sink, level + 1)) + return false; + else + break; + case wxDIR_STOP: + return false; + default: + assert(false); + break; + } + } + else //a file... + { + FileInfo details; + getWin32FileInformation(fileMetaData, details); + + switch (sink->OnFile(fullName, details)) + { + case wxDIR_IGNORE: + case wxDIR_CONTINUE: + break; + case wxDIR_STOP: + return false; + default: + assert(false); + break; + } + } + } + while (FindNextFile(searchHandle, // handle to search + &fileMetaData)); // pointer to structure for data on found file + + if (GetLastError() == ERROR_NO_MORE_FILES) + return true; //everything okay + else //an error occured + { + if (sink->OnError(Zstring(_("Error traversing directory:")) + wxT(" ") + directoryFormatted) == wxDIR_STOP) + return false; + else + return true; + } +#elif defined FFS_LINUX + + //use standard file traverser and enrich output with additional file information + //could be improved with own traversing algorithm for optimized performance + EnhancedFileTraverser traverser(sink); + + wxDir dir(directory); + if (dir.IsOpened()) + dir.Traverse(traverser); + + return true; +#else + adapt this +#endif } + + + + + diff --git a/library/fileHandling.h b/library/fileHandling.h index a4f43391..cd86333b 100644 --- a/library/fileHandling.h +++ b/library/fileHandling.h @@ -3,7 +3,7 @@ #include "globalFunctions.h" #include <wx/dir.h> - +#include "zstring.h" class FileError //Exception class used to notify file/directory copy/delete errors { @@ -22,15 +22,38 @@ private: namespace FreeFileSync { - void getAllFilesAndDirs(const wxString& sourceDir, wxArrayString& files, wxArrayString& directories) throw(FileError); + void getAllFilesAndDirs(const Zstring& sourceDir, vector<Zstring>& files, vector<Zstring>& directories) throw(FileError); //recycler bool recycleBinExists(); //test existence of Recycle Bin API on current system //file handling - void removeDirectory(const wxString& directory, const bool useRecycleBin); - void removeFile(const wxString& filename, const bool useRecycleBin); - void createDirectory(const wxString& directory, int level = 0); //level is used internally only + void removeDirectory(const Zstring& directory, const bool useRecycleBin); + void removeFile(const Zstring& filename, const bool useRecycleBin); + void createDirectory(const Zstring& directory, const int level = 0); //level is used internally only + void copyFolderAttributes(const Zstring& source, const Zstring& target); + +//################################ + //custom traverser with detail information about files + + struct FileInfo + { + wxULongLong fileSize; //unit: bytes! + time_t lastWriteTimeRaw; //number of seconds since Jan. 1st 1970 UTC + }; + + //traverser interface + class FullDetailFileTraverser + { + public: + virtual ~FullDetailFileTraverser() {} + virtual wxDirTraverseResult OnFile(const Zstring& filename, const FileInfo& details) = 0; + virtual wxDirTraverseResult OnDir(const Zstring& dirname) = 0; + virtual wxDirTraverseResult OnError(const Zstring& errorText) = 0; + }; + + bool traverseInDetail(const Zstring& directory, FullDetailFileTraverser* sink, const int level = 0); //return value and level are used internally only +//################################ } diff --git a/library/globalFunctions.cpp b/library/globalFunctions.cpp index 7e99e036..7f8b57c0 100644 --- a/library/globalFunctions.cpp +++ b/library/globalFunctions.cpp @@ -73,7 +73,7 @@ int globalFunctions::wxStringToInt(const wxString& number) if (number.ToLong(&result)) return result; else - throw RuntimeException(_("Error when converting wxString to long")); + throw RuntimeException(wxString(_("Conversion error:")) + wxT(" wxString -> long")); } @@ -84,7 +84,7 @@ double globalFunctions::wxStringToDouble(const wxString& number) if (number.ToDouble(&result)) return result; else - throw RuntimeException(_("Error when converting wxString to double")); + throw RuntimeException(wxString(_("Conversion error:")) + wxT(" wxString -> double")); } diff --git a/library/globalFunctions.h b/library/globalFunctions.h index add3c79d..f46a5906 100644 --- a/library/globalFunctions.h +++ b/library/globalFunctions.h @@ -44,9 +44,6 @@ namespace globalFunctions int readInt(wxInputStream& stream); //read int from file stream void writeInt(wxOutputStream& stream, const int number); //write int to filestream - - void startPerformance(); //helper method for quick performance measurement - void stopPerformance(); } diff --git a/library/misc.cpp b/library/misc.cpp index f7aea8bd..050be108 100644 --- a/library/misc.cpp +++ b/library/misc.cpp @@ -75,6 +75,9 @@ void CustomLocale::setLanguage(const int language) case wxLANGUAGE_DUTCH: languageFile = "dutch.lng"; break; + case wxLANGUAGE_CHINESE_SIMPLIFIED: + languageFile = "chinese_simple.lng"; + break; default: languageFile.clear(); currentLanguage = wxLANGUAGE_ENGLISH; @@ -123,7 +126,7 @@ void CustomLocale::setLanguage(const int language) langFile.close(); } else - wxMessageBox(wxString(_("Could not read language file ")) + wxT("\"") + wxString(languageFile.c_str(), wxConvUTF8) + wxT("\""), _("An exception occured!"), wxOK | wxICON_ERROR); + wxMessageBox(wxString(_("Error reading file:")) + wxT(" \"") + wxString(languageFile.c_str(), wxConvUTF8) + wxT("\""), _("An exception occured!"), wxOK | wxICON_ERROR); } else ; //if languageFile is empty texts will be english per default diff --git a/library/multithreading.cpp b/library/multithreading.cpp index 753e6651..bf5918d2 100644 --- a/library/multithreading.cpp +++ b/library/multithreading.cpp @@ -1,36 +1,9 @@ #include "multithreading.h" #include <wx/utils.h> -#include <wx/app.h> -#include <wx/timer.h> -//#include "windows.h" +//#include <wx/msw/wrapwin.h> //includes "windows.h" //MessageBox(0, "hi", "", 0); -void updateUiNow() -{ - //process UI events and prevent application from "not responding" -> NO performance issue! - wxTheApp->Yield(); - - // while (wxTheApp->Pending()) - // wxTheApp->Dispatch(); -} - - -bool updateUiIsAllowed() -{ - static wxLongLong lastExec = 0; - - wxLongLong newExec = wxGetLocalTimeMillis(); - - if (newExec - lastExec >= UI_UPDATE_INTERVAL) //perform ui updates not more often than necessary - { - lastExec = newExec; - return true; - } - return false; -} - - /*choreography: ------------- --------------- @@ -97,7 +70,7 @@ public: threadHandler->readyToReceiveResult.Lock(); threadHandler->receivingResult.Signal(); // kind of a double notice that work is completed - threadHandler->workDone = true; // workaround for wxCondition bug (wxWidgets v2.8.9, signal might geht lost) + threadHandler->workDone = true; // Workaround for wxWidgets: bug in wxCondition (wxWidgets v2.8.9, signal might geht lost) threadHandler->readyToReceiveResult.Unlock(); } diff --git a/library/multithreading.h b/library/multithreading.h index 68ad7492..bf0da145 100644 --- a/library/multithreading.h +++ b/library/multithreading.h @@ -2,6 +2,7 @@ #define MULTITHREADING_H_INCLUDED #include "statusHandler.h" +#include <wx/thread.h> class WorkerThread; diff --git a/library/processXml.cpp b/library/processXml.cpp index 2ce22b3e..04048124 100644 --- a/library/processXml.cpp +++ b/library/processXml.cpp @@ -115,10 +115,10 @@ XmlGuiConfig xmlAccess::readGuiConfig(const wxString& filename) XmlGuiConfig outputCfg; if (!inputFile.loadedSuccessfully()) - throw FileError(wxString(_("Could not open configuration file ")) + wxT("\"") + filename + wxT("\"")); + throw FileError(wxString(_("Error reading file:")) + wxT(" \"") + filename + wxT("\"")); if (!inputFile.readXmlGuiConfig(outputCfg)) //read GUI layout configuration - throw FileError(wxString(_("Error parsing configuration file ")) + wxT("\"") + filename + wxT("\"")); + throw FileError(wxString(_("Error parsing configuration file:")) + wxT(" \"") + filename + wxT("\"")); return outputCfg; } @@ -132,10 +132,10 @@ XmlBatchConfig xmlAccess::readBatchConfig(const wxString& filename) XmlBatchConfig outputCfg; if (!inputFile.loadedSuccessfully()) - throw FileError(wxString(_("Could not open configuration file ")) + wxT("\"") + filename + wxT("\"")); + throw FileError(wxString(_("Error reading file:")) + wxT(" \"") + filename + wxT("\"")); if (!inputFile.readXmlBatchConfig(outputCfg)) - throw FileError(wxString(_("Error parsing configuration file ")) + wxT("\"") + filename + wxT("\"")); + throw FileError(wxString(_("Error parsing configuration file:")) + wxT(" \"") + filename + wxT("\"")); return outputCfg; } @@ -149,10 +149,10 @@ XmlGlobalSettings xmlAccess::readGlobalSettings() XmlGlobalSettings outputCfg; if (!inputFile.loadedSuccessfully()) - throw FileError(wxString(_("Could not open configuration file ")) + wxT("\"") + FreeFileSync::GLOBAL_CONFIG_FILE + wxT("\"")); + throw FileError(wxString(_("Error reading file:")) + wxT(" \"") + FreeFileSync::GLOBAL_CONFIG_FILE + wxT("\"")); if (!inputFile.readXmlGlobalSettings(outputCfg)) - throw FileError(wxString(_("Error parsing configuration file ")) + wxT("\"") + FreeFileSync::GLOBAL_CONFIG_FILE + wxT("\"")); + throw FileError(wxString(_("Error parsing configuration file:")) + wxT(" \"") + FreeFileSync::GLOBAL_CONFIG_FILE + wxT("\"")); return outputCfg; } @@ -165,7 +165,7 @@ void xmlAccess::writeGuiConfig(const wxString& filename, const XmlGuiConfig& inp //populate and write XML tree if ( !outputFile.writeXmlGuiConfig(inputCfg) || //add GUI layout configuration settings !outputFile.writeToFile()) //save XML - throw FileError(wxString(_("Could not write configuration file ")) + wxT("\"") + filename + wxT("\"")); + throw FileError(wxString(_("Error writing file:")) + wxT(" \"") + filename + wxT("\"")); return; } @@ -177,7 +177,7 @@ void xmlAccess::writeBatchConfig(const wxString& filename, const XmlBatchConfig& //populate and write XML tree if ( !outputFile.writeXmlBatchConfig(inputCfg) || //add GUI layout configuration settings !outputFile.writeToFile()) //save XML - throw FileError(wxString(_("Could not write configuration file ")) + wxT("\"") + filename + wxT("\"")); + throw FileError(wxString(_("Error writing file:")) + wxT(" \"") + filename + wxT("\"")); return; } @@ -189,7 +189,7 @@ void xmlAccess::writeGlobalSettings(const XmlGlobalSettings& inputCfg) //populate and write XML tree if ( !outputFile.writeXmlGlobalSettings(inputCfg) || //add GUI layout configuration settings !outputFile.writeToFile()) //save XML - throw FileError(wxString(_("Could not write configuration file ")) + wxT("\"") + FreeFileSync::GLOBAL_CONFIG_FILE + wxT("\"")); + throw FileError(wxString(_("Error writing file:")) + wxT(" \"") + FreeFileSync::GLOBAL_CONFIG_FILE + wxT("\"")); return; } @@ -338,10 +338,10 @@ bool XmlConfigInput::readXmlMainConfig(MainConfiguration& mainCfg, vector<Folder FolderPair newPair; if (!readXmlElementValue(tempString, folderPair, "Left")) return false; - newPair.leftDirectory = wxString::FromUTF8(tempString.c_str()); + newPair.leftDirectory = wxString::FromUTF8(tempString.c_str()).c_str(); if (!readXmlElementValue(tempString, folderPair, "Right")) return false; - newPair.rightDirectory = wxString::FromUTF8(tempString.c_str()); + newPair.rightDirectory = wxString::FromUTF8(tempString.c_str()).c_str(); directoryPairs.push_back(newPair); folderPair = folderPair->NextSiblingElement(); @@ -462,7 +462,6 @@ bool XmlConfigInput::readXmlGlobalSettings(XmlGlobalSettings& outputCfg) outputCfg.gui.columnWidthLeft.push_back(stringToInt(width)); else break; - leftColumn = leftColumn->NextSiblingElement(); } @@ -474,9 +473,31 @@ bool XmlConfigInput::readXmlGlobalSettings(XmlGlobalSettings& outputCfg) outputCfg.gui.columnWidthRight.push_back(stringToInt(width)); else break; - rightColumn = rightColumn->NextSiblingElement(); } + + //read column positions + TiXmlElement* leftColumnPos = TiXmlHandle(mainWindow).FirstChild("LeftColumnPositions").FirstChild("Position").ToElement(); + while (leftColumnPos) + { + const char* width = leftColumnPos->GetText(); + if (width) //may be NULL!! + outputCfg.gui.columnPositionsLeft.push_back(stringToInt(width)); + else + break; + leftColumnPos = leftColumnPos->NextSiblingElement(); + } + + TiXmlElement* rightColumnPos = TiXmlHandle(mainWindow).FirstChild("RightColumnPositions").FirstChild("Position").ToElement(); + while (rightColumnPos) + { + const char* width = rightColumnPos->GetText(); + if (width) //may be NULL!! + outputCfg.gui.columnPositionsRight.push_back(stringToInt(width)); + else + break; + rightColumnPos = rightColumnPos->NextSiblingElement(); + } } @@ -589,8 +610,8 @@ bool XmlConfigOutput::writeXmlMainConfig(const MainConfiguration& mainCfg, const TiXmlElement* folderPair = new TiXmlElement("Pair"); folders->LinkEndChild(folderPair); - addXmlElement(folderPair, "Left", string((i->leftDirectory).ToUTF8())); - addXmlElement(folderPair, "Right", string((i->rightDirectory).ToUTF8())); + addXmlElement(folderPair, "Left", string(wxString(i->leftDirectory.c_str()).ToUTF8())); + addXmlElement(folderPair, "Right", string(wxString(i->rightDirectory.c_str()).ToUTF8())); } //########################################################### @@ -726,6 +747,18 @@ bool XmlConfigOutput::writeXmlGlobalSettings(const XmlGlobalSettings& inputCfg) for (unsigned int i = 0; i < inputCfg.gui.columnWidthRight.size(); ++i) addXmlElement(rightColumn, "Width", inputCfg.gui.columnWidthRight[i]); + //write column positions + TiXmlElement* leftColumnPos = new TiXmlElement("LeftColumnPositions"); + mainWindow->LinkEndChild(leftColumnPos); + + for (unsigned int i = 0; i < inputCfg.gui.columnPositionsLeft.size(); ++i) + addXmlElement(leftColumnPos, "Position", inputCfg.gui.columnPositionsLeft[i]); + + TiXmlElement* rightColumnPos = new TiXmlElement("RightColumnPositions"); + mainWindow->LinkEndChild(rightColumnPos); + + for (unsigned int i = 0; i < inputCfg.gui.columnPositionsRight.size(); ++i) + addXmlElement(rightColumnPos, "Position", inputCfg.gui.columnPositionsRight[i]); //################################################################### //write batch settings @@ -735,3 +768,42 @@ bool XmlConfigOutput::writeXmlGlobalSettings(const XmlGlobalSettings& inputCfg) return true; } + + +int xmlAccess::retrieveSystemLanguage() //map language dialects +{ + const int lang = wxLocale::GetSystemLanguage(); + + switch (lang) + { + case wxLANGUAGE_GERMAN_AUSTRIAN: + case wxLANGUAGE_GERMAN_BELGIUM: + case wxLANGUAGE_GERMAN_LIECHTENSTEIN: + case wxLANGUAGE_GERMAN_LUXEMBOURG: + case wxLANGUAGE_GERMAN_SWISS: + return wxLANGUAGE_GERMAN; + + case wxLANGUAGE_FRENCH_BELGIAN: + case wxLANGUAGE_FRENCH_CANADIAN: + case wxLANGUAGE_FRENCH_LUXEMBOURG: + case wxLANGUAGE_FRENCH_MONACO: + case wxLANGUAGE_FRENCH_SWISS: + return wxLANGUAGE_FRENCH; + + //case wxLANGUAGE_JAPANESE: + + case wxLANGUAGE_DUTCH_BELGIAN: + return wxLANGUAGE_DUTCH; + + case wxLANGUAGE_CHINESE: + case wxLANGUAGE_CHINESE_TRADITIONAL: + case wxLANGUAGE_CHINESE_HONGKONG: + case wxLANGUAGE_CHINESE_MACAU: + case wxLANGUAGE_CHINESE_SINGAPORE: + case wxLANGUAGE_CHINESE_TAIWAN: + return wxLANGUAGE_CHINESE_SIMPLIFIED; + + default: + return lang; + } +} diff --git a/library/processXml.h b/library/processXml.h index e359e7b0..bc81556e 100644 --- a/library/processXml.h +++ b/library/processXml.h @@ -3,7 +3,6 @@ #include "../FreeFileSync.h" #include "tinyxml/tinyxml.h" -#include <wx/intl.h> using namespace FreeFileSync; @@ -42,13 +41,14 @@ namespace xmlAccess bool silent; }; + int retrieveSystemLanguage(); struct XmlGlobalSettings { struct _Global { _Global() : - programLanguage(wxLocale::GetSystemLanguage()), + programLanguage(retrieveSystemLanguage()), #ifdef FFS_WIN dstCheckActive(true), #endif @@ -77,6 +77,8 @@ namespace xmlAccess bool isMaximized; vector<int> columnWidthLeft; vector<int> columnWidthRight; + vector<int> columnPositionsLeft; + vector<int> columnPositionsRight; } gui; //struct _Batch diff --git a/library/resources.cpp b/library/resources.cpp index 68eeb4d6..9169bf85 100644 --- a/library/resources.cpp +++ b/library/resources.cpp @@ -3,6 +3,7 @@ #include <wx/zipstrm.h> #include <wx/image.h> #include <wx/icon.h> +#include <wx/mstream.h> #include "globalFunctions.h" #ifdef FFS_WIN @@ -111,6 +112,24 @@ GlobalResources::~GlobalResources() } +void loadAnimFromZip(wxZipInputStream& zipInput, wxAnimation* animation) +{ + //Workaround for wxWidgets: + //construct seekable input stream (zip-input stream is non-seekable) for wxAnimation::Load() + //luckily this method call is very fast: below measurement precision! + vector<unsigned char> data; + data.reserve(10000); + + int newValue = 0; + while ((newValue = zipInput.GetC()) != wxEOF) + data.push_back(newValue); + + wxMemoryInputStream seekAbleStream(&data.front(), data.size()); //stream does not take ownership of data + + animation->Load(seekAbleStream, wxANIMATION_TYPE_GIF); +} + + void GlobalResources::load() { wxFileInputStream input(wxT("Resources.dat")); @@ -121,21 +140,22 @@ void GlobalResources::load() wxZipInputStream resourceFile(input); - wxZipEntry* entry; + wxZipEntry* entry = NULL; map<wxString, wxBitmap*>::iterator bmp; while ((entry = resourceFile.GetNextEntry())) { wxString name = entry->GetName(); - //just to be sure: search if entry is available in map + //search if entry is available in map if ((bmp = bitmapResource.find(name)) != bitmapResource.end()) *(bmp->second) = wxBitmap(wxImage(resourceFile, wxBITMAP_TYPE_PNG)); + else if (name == wxT("money.gif")) + loadAnimFromZip(resourceFile, animationMoney); + else if (name == wxT("working.gif")) + loadAnimFromZip(resourceFile, animationSync); } } - animationMoney->LoadFile(wxT("Resources.a01")); - animationSync->LoadFile(wxT("Resources.a02")); - #ifdef FFS_WIN programIcon = new wxIcon(wxT("ffsIcon1")); #else diff --git a/library/sorting.h b/library/sorting.h index bf643176..560e246a 100644 --- a/library/sorting.h +++ b/library/sorting.h @@ -3,6 +3,7 @@ #include "../FreeFileSync.h" #include "resources.h" +#include "globalFunctions.h" enum SideToSort @@ -12,34 +13,6 @@ enum SideToSort }; -template <bool sortAscending> -inline -bool cmpString(const wxString& a, const wxString& b) -{ - if (a.IsEmpty()) - return false; //if a and b are empty: false, if a empty, b not empty: also false, since empty rows should appear at the end - else if (b.IsEmpty()) - return true; //empty rows after filled rows: return true - - //if a and b not empty: - if (sortAscending) - return (a < b); - else - return (a > b); -} - - -template <bool sortAscending> -inline -bool cmpLargeInt(const wxULongLong& a, const wxULongLong& b) -{ - if (sortAscending) - return (a < b); - else - return (a > b); -} - - template <SideToSort side> inline void getDescrLine(const FileCompareLine& a, const FileCompareLine& b, const FileDescrLine*& descrLineA, const FileDescrLine*& descrLineB) @@ -54,49 +27,68 @@ void getDescrLine(const FileCompareLine& a, const FileCompareLine& b, const File descrLineA = &a.fileDescrRight; descrLineB = &b.fileDescrRight; } - else assert(false); + else + assert(false); } +template <bool sortAscending> inline -wxChar formatChar(const wxChar& c) +bool stringSmallerThan(const wxChar* stringA, const wxChar* stringB) { - return c; - //return wxTolower(c); <- this is slow as hell! sorting slower by factor 10 +#ifdef FFS_WIN //case-insensitive comparison! + return sortAscending ? + FreeFileSync::compareStringsWin32(stringA, stringB) < 0 : //way faster than wxString::CmpNoCase() in windows build!!! + FreeFileSync::compareStringsWin32(stringA, stringB) > 0; +#else + while (*stringA == *stringB) + { + if (*stringA == wxChar(0)) //strings are equal + return false; + + ++stringA; + ++stringB; + } + return sortAscending ? *stringA < *stringB : *stringA > *stringB; //wxChar(0) is handled correctly +#endif } inline int compareString(const wxChar* stringA, const wxChar* stringB, const int lengthA, const int lengthB) { +#ifdef FFS_WIN //case-insensitive comparison! + return FreeFileSync::compareStringsWin32(stringA, stringB, lengthA, lengthB); //way faster than wxString::CmpNoCase() in the windows build!!! +#else int i = 0; if (lengthA == lengthB) { for (i = 0; i < lengthA; ++i) { - if (formatChar(stringA[i]) != formatChar(stringB[i])) + if (stringA[i] != stringB[i]) break; } - return i == lengthA ? 0 : formatChar(stringA[i]) < formatChar(stringB[i]) ? -1 : 1; + return i == lengthA ? 0 : stringA[i] < stringB[i] ? -1 : 1; } else if (lengthA < lengthB) { for (i = 0; i < lengthA; ++i) { - if (formatChar(stringA[i]) != formatChar(stringB[i])) + if (stringA[i] != stringB[i]) break; } - return i == lengthA ? -1 : formatChar(stringA[i]) < formatChar(stringB[i]) ? -1 : 1; + return i == lengthA ? -1 : stringA[i] < stringB[i] ? -1 : 1; } else { for (i = 0; i < lengthB; ++i) { - if (formatChar(stringA[i]) != formatChar(stringB[i])) + if (stringA[i] != stringB[i]) break; } - return i == lengthB ? 1 : formatChar(stringA[i]) < formatChar(stringB[i]) ? -1 : 1; + return i == lengthB ? 1 : stringA[i] < stringB[i] ? -1 : 1; } +#endif } @@ -119,43 +111,23 @@ bool sortByFileName(const FileCompareLine& a, const FileCompareLine& b) return true; else { - const wxChar* stringA = NULL; - const wxChar* stringB = NULL; - int lenghtA = 0; - int lenghtB = 0; + const wxChar* stringA = descrLineA->relativeName.c_str(); + const wxChar* stringB = descrLineB->relativeName.c_str(); - int pos = descrLineA->relativeName.Find(GlobalResources::FILE_NAME_SEPARATOR, true); //start search beginning from end - if (pos == wxNOT_FOUND) - { - stringA = descrLineA->relativeName.c_str(); - lenghtA = descrLineA->relativeName.Len(); - } - else - { - stringA = descrLineA->relativeName.c_str() + pos + 1; - lenghtA = descrLineA->relativeName.Len() - (pos + 1); - } + size_t pos = descrLineA->relativeName.Find(GlobalResources::FILE_NAME_SEPARATOR, true); //start search beginning from end + if (pos != string::npos) + stringA += pos + 1; pos = descrLineB->relativeName.Find(GlobalResources::FILE_NAME_SEPARATOR, true); //start search beginning from end - if (pos == wxNOT_FOUND) - { - stringB = descrLineB->relativeName.c_str(); - lenghtB = descrLineB->relativeName.Len(); - } - else - { - stringB = descrLineB->relativeName.c_str() + pos + 1; - lenghtB = descrLineB->relativeName.Len() - (pos + 1); - } + if (pos != string::npos) + stringB += pos + 1; - int rv = compareString(stringA, stringB, lenghtA, lenghtB); - return sortAscending ? (rv == -1) : (rv == 1); + return stringSmallerThan<sortAscending>(stringA, stringB); } } template <bool sortAscending, SideToSort side> -inline bool sortByRelativeName(const FileCompareLine& a, const FileCompareLine& b) { const FileDescrLine* descrLineA = NULL; @@ -163,67 +135,59 @@ bool sortByRelativeName(const FileCompareLine& a, const FileCompareLine& b) getDescrLine<side>(a, b, descrLineA, descrLineB); //extract relative name and filename - const wxChar* relStringA = NULL; - const wxChar* fileStringA = NULL; - int relLenghtA = 0; + const wxChar* relStringA = descrLineA->relativeName.c_str(); //mustn't be NULL for CompareString() API to work correctly + const wxChar* fileStringA = relStringA; + int relLengthA = 0; int fileLengthA = 0; + if (descrLineA->objType == FileDescrLine::TYPE_DIRECTORY) - { - relStringA = descrLineA->relativeName.c_str(); - relLenghtA = descrLineA->relativeName.Len(); - } + relLengthA = descrLineA->relativeName.length(); else if (descrLineA->objType == FileDescrLine::TYPE_FILE) { - relLenghtA = descrLineA->relativeName.Find(GlobalResources::FILE_NAME_SEPARATOR, true); //start search beginning from end - if (relLenghtA == wxNOT_FOUND) + relLengthA = descrLineA->relativeName.Find(GlobalResources::FILE_NAME_SEPARATOR, true); //start search beginning from end + if (relLengthA == wxNOT_FOUND) { - relLenghtA = 0; - fileStringA = descrLineA->relativeName.c_str(); - fileLengthA = descrLineA->relativeName.Len(); + relLengthA = 0; + fileLengthA = descrLineA->relativeName.length(); } else { - relStringA = descrLineA->relativeName.c_str(); - fileStringA = descrLineA->relativeName.c_str() + relLenghtA + 1; - fileLengthA = descrLineA->relativeName.Len() - (relLenghtA + 1); + fileStringA += relLengthA + 1; + fileLengthA = descrLineA->relativeName.length() - (relLengthA + 1); } } else return false; //empty rows should be on end of list - const wxChar* relStringB = NULL; - const wxChar* fileStringB = NULL; - int relLenghtB = 0; + const wxChar* relStringB = descrLineB->relativeName.c_str(); //mustn't be NULL for CompareString() API to work correctly + const wxChar* fileStringB = relStringB; + int relLengthB = 0; int fileLengthB = 0; + if (descrLineB->objType == FileDescrLine::TYPE_DIRECTORY) - { - relStringB = descrLineB->relativeName.c_str(); - relLenghtB = descrLineB->relativeName.Len(); - } + relLengthB = descrLineB->relativeName.length(); else if (descrLineB->objType == FileDescrLine::TYPE_FILE) { - relLenghtB = descrLineB->relativeName.Find(GlobalResources::FILE_NAME_SEPARATOR, true); //start search beginning from end - if (relLenghtB == wxNOT_FOUND) + relLengthB = descrLineB->relativeName.Find(GlobalResources::FILE_NAME_SEPARATOR, true); //start search beginning from end + if (relLengthB == wxNOT_FOUND) { - relLenghtB = 0; - fileStringB = descrLineB->relativeName.c_str(); - fileLengthB = descrLineB->relativeName.Len(); + relLengthB = 0; + fileLengthB = descrLineB->relativeName.length(); } else { - relStringB = descrLineB->relativeName.c_str(); - fileStringB = descrLineB->relativeName.c_str() + relLenghtB + 1; - fileLengthB = descrLineB->relativeName.Len() - (relLenghtB + 1); + fileStringB += relLengthB + 1; + fileLengthB = descrLineB->relativeName.length() - (relLengthB + 1); } } else return true; //empty rows should be on end of list //compare relative names without filenames first - int rv = compareString(relStringA, relStringB, relLenghtA, relLenghtB); + int rv = compareString(relStringA, relStringB, relLengthA, relLengthB); if (rv != 0) - return sortAscending ? (rv == -1) : (rv == 1); + return sortAscending ? (rv < 0) : (rv > 0); else //compare the filenames { if (descrLineB->objType == FileDescrLine::TYPE_DIRECTORY) //directories shall appear before files @@ -231,8 +195,9 @@ bool sortByRelativeName(const FileCompareLine& a, const FileCompareLine& b) else if (descrLineA->objType == FileDescrLine::TYPE_DIRECTORY) return true; - rv = compareString(fileStringA, fileStringB, fileLengthA, fileLengthB); - return sortAscending ? (rv == -1) : (rv == 1); + return sortAscending ? + compareString(fileStringA, fileStringB, fileLengthA, fileLengthB) < 0 : + compareString(fileStringA, fileStringB, fileLengthA, fileLengthB) > 0; } } @@ -254,8 +219,10 @@ bool sortByFileSize(const FileCompareLine& a, const FileCompareLine& b) return false; else if (descrLineB->objType == FileDescrLine::TYPE_DIRECTORY) return true; - else //use unformatted filesizes and sort by size - return cmpLargeInt<sortAscending>(descrLineA->fileSize, descrLineB->fileSize); + else + return sortAscending ? + descrLineA->fileSize < descrLineB->fileSize : + descrLineA->fileSize > descrLineB->fileSize; } @@ -267,7 +234,19 @@ bool sortByDate(const FileCompareLine& a, const FileCompareLine& b) const FileDescrLine* descrLineB = NULL; getDescrLine<side>(a, b, descrLineA, descrLineB); - return cmpString<sortAscending>(descrLineA->lastWriteTime, descrLineB->lastWriteTime); + //presort types: first files, then directories then empty rows + if (descrLineA->objType == FileDescrLine::TYPE_NOTHING) + return false; //empty rows always last + else if (descrLineB->objType == FileDescrLine::TYPE_NOTHING) + return true; //empty rows always last + else if (descrLineA->objType == FileDescrLine::TYPE_DIRECTORY) + return false; + else if (descrLineB->objType == FileDescrLine::TYPE_DIRECTORY) + return true; + else + return sortAscending ? + descrLineA->lastWriteTimeRaw < descrLineB->lastWriteTimeRaw : + descrLineA->lastWriteTimeRaw > descrLineB->lastWriteTimeRaw; } diff --git a/library/statusHandler.cpp b/library/statusHandler.cpp new file mode 100644 index 00000000..e19c9904 --- /dev/null +++ b/library/statusHandler.cpp @@ -0,0 +1,28 @@ +#include "statusHandler.h" +#include <wx/app.h> +#include <wx/timer.h> + + +void updateUiNow() +{ + //process UI events and prevent application from "not responding" -> NO performance issue! + wxTheApp->Yield(); + + // while (wxTheApp->Pending()) + // wxTheApp->Dispatch(); +} + + +bool updateUiIsAllowed() +{ + static wxLongLong lastExec = 0; + + wxLongLong newExec = wxGetLocalTimeMillis(); + + if (newExec - lastExec >= UI_UPDATE_INTERVAL) //perform ui updates not more often than necessary + { + lastExec = newExec; + return true; + } + return false; +} diff --git a/library/statusHandler.h b/library/statusHandler.h index 70f8c2d8..4c12035d 100644 --- a/library/statusHandler.h +++ b/library/statusHandler.h @@ -2,7 +2,7 @@ #define STATUSHANDLER_H_INCLUDED #include <wx/string.h> -#include <wx/thread.h> + const int UI_UPDATE_INTERVAL = 100; //perform ui updates not more often than necessary, 100 seems to be a good value with only a minimal performance loss @@ -21,7 +21,7 @@ public: enum Response { - CONTINUE_NEXT = 10, + IGNORE_ERROR = 10, RETRY }; virtual Response reportError(const wxString& text) = 0; diff --git a/library/zstring.cpp b/library/zstring.cpp new file mode 100644 index 00000000..b84c9512 --- /dev/null +++ b/library/zstring.cpp @@ -0,0 +1,396 @@ +#include "zstring.h" +#include <wx/intl.h> +#include "globalFunctions.h" + + +#ifdef FFS_WIN +#include <wx/msw/wrapwin.h> //includes "windows.h" +#endif //FFS_WIN + + +#ifdef __WXDEBUG__ +int allocCount = 0; //test Zstring for memory leaks + +void testZstringForMemoryLeak() +{ + if (allocCount != 0) +#ifdef FFS_WIN + MessageBox(NULL, wxT("Fatal Error! Allocation problem with Zstring! (No problem if it occures while Unit testing only!)"), wxString::Format(wxT("%i"), allocCount), 0); +#else + throw; +#endif //FFS_WIN +} +#endif + + +#ifdef FFS_WIN +int FreeFileSync::compareStringsWin32(const wchar_t* a, const wchar_t* b) +{ + return lstrcmpi( + a, //address of first string + b); //address of second string +} + + +//equivalent implementation, but slightly(!!!) slower: +int FreeFileSync::compareStringsWin32(const wchar_t* a, const wchar_t* b, const int aCount, const int bCount) +{ + int rv = CompareString( + LOCALE_USER_DEFAULT, //locale identifier + NORM_IGNORECASE, //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 RuntimeException(wxString(_("Error comparing strings!"))); + else + return rv - 2; +} +#endif + + +size_t Zstring::Replace(const defaultChar* old, const defaultChar* replacement, bool replaceAll) +{ + const size_t oldLen = defaultLength(old); + const size_t replacementLen = defaultLength(replacement); + size_t uiCount = 0; //count of replacements made + + size_t pos = 0; + while (true) + { + pos = find(old, pos); + if (pos == npos) + break; + + replace(pos, oldLen, replacement, replacementLen); + pos += replacementLen; //move past the string that was replaced + + ++uiCount; //increase replace count + + // stop now? + if (!replaceAll) + break; + } + return uiCount; +} + + +bool matchesHelper(const defaultChar* string, const defaultChar* mask) +{ + for (defaultChar ch; (ch = *mask) != 0; ++mask) + { + switch (ch) + { + case defaultChar('?'): + if (*string == 0) + return false; + else + ++string; + break; + + case defaultChar('*'): + //advance to next non-*/? char + do + { + ++mask; + ch = *mask; + } + while (ch == defaultChar('*') || ch == defaultChar('?')); + //if match ends with '*': + if (ch == defaultChar(0)) + return true; + + ++mask; + while ((string = defaultStrFind(string, ch)) != NULL) + { + if (matchesHelper(string + 1, mask)) + return true; + ++string; + } + return false; + break; + + default: + if (*string != ch) + return false; + else + ++string; + } + } + return *string == 0; +} + + +bool Zstring::Matches(const defaultChar* mask) const +{ + return matchesHelper(c_str(), mask); +} + + +Zstring& Zstring::Trim(bool fromRight) +{ + const size_t thisLen = length(); + if (thisLen == 0) + return *this; + + if (fromRight) + { + const defaultChar* cursor = data + thisLen - 1; + while (cursor != data - 1 && defaultIsWhiteSpace(*cursor)) //break when pointing one char further than last skipped element + --cursor; + ++cursor; + + const size_t newLength = cursor - data; + if (newLength != thisLen) + { + if (descr->refCount > 1) //allocate new string + *this = Zstring(data, newLength); + else //overwrite this string + descr->length = newLength; + } + } + else + { + defaultChar* cursor = data; + defaultChar ch; + while ((ch = *cursor) != 0 && defaultIsWhiteSpace(ch)) + ++cursor; + + const size_t diff = cursor - data; + if (diff) + { + if (descr->refCount > 1) //allocate new string + *this = Zstring(cursor, thisLen - diff); + else + { //overwrite this string + data = cursor; //no problem when deallocating data, since descr points to begin of allocated area + descr->capacity -= diff; + descr->length -= diff; + } + } + } + + return *this; +} + + +Zstring& Zstring::MakeLower() +{ + const size_t thisLen = length(); + if (thisLen == 0) + return *this; + + if (descr->refCount > 1) //allocate new string + { + StringDescriptor* newDescr; + defaultChar* newData; + const size_t newCapacity = getCapacityToAllocate(thisLen); + allocate(1, thisLen, newCapacity, newDescr, newData); + + for (unsigned int i = 0; i < thisLen; ++i) + newData[i] = defaultToLower(data[i]); + newData[thisLen] = 0; + + decRef(); + descr = newDescr; + data = newData; + } + else + { //overwrite this string + for (unsigned int i = 0; i < thisLen; ++i) + data[i] = defaultToLower(data[i]); + } + + return *this; +} + + +//############################################################### +//std::string functions +Zstring Zstring::substr(size_t pos, size_t len) const +{ + if (len == npos) + { + assert(pos <= length()); + return Zstring(c_str() + pos, length() - pos); //reference counting not used: different length + } + else + { + assert(length() - pos >= len); + return Zstring(c_str() + pos, len); + } +} + + +size_t Zstring::rfind(const defaultChar ch, size_t pos) const +{ + const size_t thisLen = length(); + if (thisLen == 0) + return npos; + + if (pos == npos) + pos = thisLen - 1; + else + assert(pos <= length()); + + do //pos points to last char of the string + { + if (data[pos] == ch) + return pos; + } + while (--pos != static_cast<size_t>(-1)); + return npos; +} + + +Zstring& Zstring::replace(size_t pos1, size_t n1, const defaultChar* str, size_t n2) +{ + assert(str < c_str() || c_str() + length() < str); //str mustn't point to data in this string + assert(n1 <= length() - pos1); + + if (n2 != 0) + { + const size_t oldLen = length(); + if (oldLen == 0) + { + assert(n1 == 0); + return *this = Zstring(str, n2); + } + + const size_t newLen = oldLen - n1 + n2; + if (n1 < n2 || descr->refCount > 1) + { //allocate a new string + const size_t newCapacity = getCapacityToAllocate(newLen); + + StringDescriptor* newDescr; + defaultChar* newData; + allocate(1, newLen, newCapacity, newDescr, newData); + //StringDescriptor* newDescr = new StringDescriptor(1, newLen, newCapacity); + //defaultChar* newData = new defaultChar[newCapacity + 1]; + + //assemble new string with replacement + memcpy(newData, data, pos1 * sizeof(defaultChar)); + memcpy(newData + pos1, str, n2 * sizeof(defaultChar)); + memcpy(newData + pos1 + n2, data + pos1 + n1, (oldLen - pos1 - n1) * sizeof(defaultChar)); + newData[newLen] = 0; + + decRef(); + data = newData; + descr = newDescr; + } + else + { //overwrite current string + memcpy(data + pos1, str, n2 * sizeof(defaultChar)); + if (n1 > n2) + { + memmove(data + pos1 + n2, data + pos1 + n1, (oldLen - pos1 - n1) * sizeof(defaultChar)); + data[newLen] = 0; + descr->length = newLen; + } + } + } + return *this; +} + + +Zstring& Zstring::operator=(const defaultChar* source) +{ + const size_t sourceLen = defaultLength(source); + if (sourceLen == 0) + return *this = Zstring(); + + if (descr->refCount > 1 || descr->capacity < sourceLen) //allocate new string + *this = Zstring(source, sourceLen); + else + { //overwrite this string + memcpy(data, source, sourceLen * sizeof(defaultChar)); + data[sourceLen] = 0; + descr->length = sourceLen; + } + return *this; +} + + +Zstring& Zstring::operator+=(const Zstring& other) +{ + const size_t otherLen = other.length(); + if (otherLen != 0) + { + const size_t thisLen = length(); + const size_t newLen = thisLen + otherLen; + copyBeforeWrite(newLen); + + memcpy(data + thisLen, other.c_str(), otherLen * sizeof(defaultChar)); + data[newLen] = 0; + descr->length = newLen; + } + return *this; +} + + +Zstring& Zstring::operator+=(const defaultChar* other) +{ + const size_t otherLen = defaultLength(other); + if (otherLen != 0) + { + const size_t thisLen = length(); + const size_t newLen = thisLen + otherLen; + copyBeforeWrite(newLen); + + memcpy(data + thisLen, other, otherLen * sizeof(defaultChar)); + data[newLen] = 0; + descr->length = newLen; + } + return *this; +} + + +Zstring& Zstring::operator+=(defaultChar ch) +{ + const size_t oldLen = length(); + copyBeforeWrite(oldLen + 1); + data[oldLen] = ch; + data[oldLen + 1] = 0; + ++descr->length; + return *this; +} + + +void Zstring::copyBeforeWrite(const size_t capacityNeeded) +{ + assert(capacityNeeded != 0); + + if (descr->refCount > 1) + { //allocate a new string + const size_t oldLength = length(); + const size_t newCapacity = getCapacityToAllocate(capacityNeeded); + + StringDescriptor* newDescr; + defaultChar* newData; + allocate(1, oldLength, newCapacity, newDescr, newData); + //StringDescriptor* newDescr = new StringDescriptor(1, oldLength, newCapacity); + //defaultChar* newData = new defaultChar[newCapacity + 1]; + + if (oldLength) + { + assert(oldLength <= newCapacity); + memcpy(newData, data, oldLength * sizeof(defaultChar)); + newData[oldLength] = 0; + } + decRef(); + descr = newDescr; + data = newData; + } + else if (descr->capacity < capacityNeeded) + { //try to resize the current string (allocate anew if necessary) + const size_t newCapacity = getCapacityToAllocate(capacityNeeded); + + descr = (StringDescriptor*) realloc(descr, sizeof(StringDescriptor) + (newCapacity + 1) * sizeof(defaultChar)); + if (descr == NULL) + throw; //std::bad_alloc& e + data = (defaultChar*)(descr + 1); + descr->capacity = newCapacity; + } +} diff --git a/library/zstring.h b/library/zstring.h new file mode 100644 index 00000000..00590d4f --- /dev/null +++ b/library/zstring.h @@ -0,0 +1,580 @@ +/*************************************************************** + * Purpose: High performance string class + * Author: ZenJu (zhnmju123@gmx.de) + * Created: Jan. 2009 + **************************************************************/ + +#ifndef ZSTRING_H_INCLUDED +#define ZSTRING_H_INCLUDED + +#include <wx/string.h> + +namespace FreeFileSync +{ +#ifdef FFS_WIN + //super fast case-insensitive string comparison: way faster than wxString::CmpNoCase()!!! + int compareStringsWin32(const wchar_t* a, const wchar_t* b); + int compareStringsWin32(const wchar_t* a, const wchar_t* b, const int aCount, const int bCount); +#endif //FFS_WIN +} + +#ifdef FFS_WIN +#define ZSTRING_WIDE_CHAR //use wide character strings + +#elif defined FFS_LINUX +#define ZSTRING_CHAR //use char strings +#endif + + +#ifdef ZSTRING_CHAR +typedef char defaultChar; +#elif defined ZSTRING_WIDE_CHAR +typedef wchar_t defaultChar; +#endif + +class Zstring +{ +public: + Zstring(); + Zstring(const defaultChar* source); //string is copied: O(length) + Zstring(const defaultChar* source, size_t length); //string is copied: O(length) + Zstring(const Zstring& source); //reference-counting => O(1) + Zstring(const wxString& source); //string is copied: O(length) + ~Zstring(); + + operator const defaultChar*() const; //implicit conversion to C string + operator const wxString() const; //implicit conversion to wxString + + //wxWidgets functions + bool StartsWith(const defaultChar* begin) const; + bool StartsWith(const Zstring& begin) const; + bool EndsWith(const defaultChar* end) const; + bool EndsWith(const Zstring& end) const; +#ifdef FFS_WIN + int CmpNoCase(const defaultChar* other) const; + int CmpNoCase(const Zstring& other) const; +#endif + int Cmp(const defaultChar* other) const; + int Cmp(const Zstring& other) const; + size_t Replace(const defaultChar* old, const defaultChar* replacement, bool replaceAll = true); + Zstring AfterLast(defaultChar ch) const; + Zstring BeforeLast(defaultChar ch) const; + size_t Find(defaultChar ch, bool fromEnd) const; + bool Matches(const defaultChar* mask) const; + Zstring& Trim(bool fromRight); //from right or left + Zstring& MakeLower(); + + //std::string functions + size_t length() const; + const defaultChar* c_str() const; + Zstring substr(size_t pos = 0, size_t len = npos) const; + bool empty() const; + int compare(const defaultChar* other) const; + int compare(const Zstring& other) const; + int compare(const size_t pos1, const size_t n1, const defaultChar* other) const; + size_t find(const defaultChar* str, const size_t pos = 0 ) const; + size_t find(const defaultChar ch, const size_t pos = 0) const; + size_t rfind(const defaultChar ch, size_t pos = npos) const; + Zstring& replace(size_t pos1, size_t n1, const defaultChar* str, size_t n2); + + Zstring& operator=(const Zstring& source); + Zstring& operator=(const defaultChar* source); + + bool operator==(const Zstring& other) const; + bool operator==(const defaultChar* other) const; + + Zstring& operator+=(const Zstring& other); + Zstring& operator+=(const defaultChar* other); + Zstring& operator+=(defaultChar ch); + + Zstring operator+(const Zstring& string2) const; + Zstring operator+(const defaultChar* string2) const; + Zstring operator+(const defaultChar ch) const; + + static const size_t npos = static_cast<size_t>(-1); + +private: + void initAndCopy(const defaultChar* source, size_t length); + void incRef(); //support for reference-counting + void decRef(); // + void copyBeforeWrite(const size_t capacityNeeded); //and copy-on-write + + struct StringDescriptor + { + StringDescriptor(const unsigned int refC, const size_t len, const size_t cap) : refCount(refC), length(len), capacity(cap) {} + unsigned int refCount; + size_t length; + size_t capacity; //allocated length without null-termination + }; + static void allocate(const unsigned int newRefCount, const size_t newLength, const size_t newCapacity, Zstring::StringDescriptor*& newDescr, defaultChar*& newData); + + StringDescriptor* descr; + defaultChar* data; +}; + + +//####################################################################################### +//begin of implementation + +#ifdef ZSTRING_CHAR +inline +size_t defaultLength(const char* input) +{ + return strlen(input); +} + +inline +int defaultCompare(const char* str1, const char* str2) +{ + return strcmp(str1, str2); +} + +inline +int defaultCompare(const char* str1, const char* str2, const size_t count) +{ + return strncmp(str1, str2, count); +} + +inline +char* defaultStrFind(const char* str1, const char* str2) +{ + return strstr(str1, str2); +} + +inline +char* defaultStrFind(const char* str1, int ch) +{ + return strchr(str1, ch); +} + +inline +bool defaultIsWhiteSpace(const char ch) +{ + // some compilers (e.g. VC++ 6.0) return true for a call to isspace('\xEA') => exclude char(128) to char(255) + return (ch < 128) && isspace(ch) != 0; +} + +inline +char defaultToLower(const char ch) +{ + return tolower(ch); +} + +#elif defined ZSTRING_WIDE_CHAR +inline +size_t defaultLength(const wchar_t* input) +{ + return wcslen(input); +} + +inline +int defaultCompare(const wchar_t* str1, const wchar_t* str2) +{ + return wcscmp(str1, str2); +} + +inline +int defaultCompare(const wchar_t* str1, const wchar_t* str2, const size_t count) +{ + return wcsncmp(str1, str2, count); +} + +inline +wchar_t* defaultStrFind(const wchar_t* str1, const wchar_t* str2) +{ + return wcsstr(str1, str2); +} + +inline +wchar_t* defaultStrFind(const wchar_t* str1, int ch) +{ + return wcschr(str1, ch); +} + +inline +bool defaultIsWhiteSpace(const wchar_t ch) +{ + // some compilers (e.g. VC++ 6.0) return true for a call to isspace('\xEA') => exclude char(128) to char(255) + return (ch < 128 || ch > 255) && iswspace(ch) != 0; +} + +inline +wchar_t defaultToLower(const wchar_t ch) +{ + return towlower(ch); +} +#endif + + +#ifdef __WXDEBUG__ +extern int allocCount; //test Zstring for memory leaks +void testZstringForMemoryLeak(); +#endif + + +inline +void Zstring::allocate(const unsigned int newRefCount, + const size_t newLength, + const size_t newCapacity, + StringDescriptor*& newDescr, + defaultChar*& newData) +{ //allocate and set data for new string + if (newCapacity) + { + newDescr = (StringDescriptor*) malloc( sizeof(StringDescriptor) + (newCapacity + 1) * sizeof(defaultChar)); + if (newDescr == NULL) + throw; //std::bad_alloc& e + newData = (defaultChar*)(newDescr + 1); + } + else + { + newDescr = (StringDescriptor*) malloc( sizeof(StringDescriptor)); + if (newDescr == NULL) + throw; //std::bad_alloc& e + newData = NULL; + } + + newDescr->refCount = newRefCount; + newDescr->length = newLength; + newDescr->capacity = newCapacity; + +#ifdef __WXDEBUG__ + ++allocCount; //test Zstring for memory leaks + + static bool isRegistered = false; + if (!isRegistered) + { + isRegistered = true; + atexit(testZstringForMemoryLeak); + } +#endif +} + + +inline +Zstring::Zstring() +{ + allocate(1, 0, 0, descr, data); +} + + +inline +Zstring::Zstring(const defaultChar* source) +{ + initAndCopy(source, defaultLength(source)); +} + + +inline +Zstring::Zstring(const defaultChar* source, size_t length) +{ + initAndCopy(source, length); +} + + +inline +Zstring::Zstring(const Zstring& source) +{ + descr = source.descr; + data = source.data; + incRef(); //reference counting! +} + + +inline +Zstring::Zstring(const wxString& source) +{ + initAndCopy(source.c_str(), source.length()); +} + + +inline +Zstring::~Zstring() +{ + decRef(); +} + + +inline +size_t getCapacityToAllocate(const size_t length) +{ + return (length + (19 - length % 16)); //allocate some additional length to speed up concatenation +} + + +inline +void Zstring::initAndCopy(const defaultChar* source, size_t length) +{ + const size_t newCapacity = getCapacityToAllocate(length); + allocate(1, length, newCapacity, descr, data); + memcpy(data, source, length * sizeof(defaultChar)); + data[length] = 0; +} + + +inline +void Zstring::incRef() +{ + assert(descr); + ++descr->refCount; +} + + +inline +void Zstring::decRef() +{ + assert(descr && descr->refCount >= 1); + if (--descr->refCount == 0) + { + assert(descr); //descr points to the begin of the allocated memory block + free(descr); //this must NEVER be changed!! E.g. Trim() relies on descr being start of allocated memory block +#ifdef __WXDEBUG__ + --allocCount; //test Zstring for memory leaks +#endif + } +} + + +#ifdef FFS_WIN +inline +int Zstring::CmpNoCase(const defaultChar* other) const +{ + return FreeFileSync::compareStringsWin32(c_str(), other); //way faster than wxString::CmpNoCase()!! +} + + +inline +int Zstring::CmpNoCase(const Zstring& other) const +{ + return FreeFileSync::compareStringsWin32(c_str(), other.c_str()); //way faster than wxString::CmpNoCase()!! +} +#endif + + +inline +Zstring::operator const defaultChar*() const +{ + return c_str(); +} + + +inline +Zstring& Zstring::operator=(const Zstring& source) +{ + if (this != &source) + { + decRef(); + descr = source.descr; + data = source.data; + incRef(); + } + return *this; +} + + +inline +size_t Zstring::Find(defaultChar ch, bool fromEnd) const +{ + if (fromEnd) + return rfind(ch, npos); + else + return find(ch, 0); +} + + +// get all characters after the last occurence of ch +// (returns the whole string if ch not found) +inline +Zstring Zstring::AfterLast(defaultChar ch) const +{ + size_t pos = rfind(ch, npos); + if (pos == npos ) + return *this; + else + return c_str() + pos + 1; +} + + +// get all characters before the last occurence of ch +// (returns empty string if ch not found) +inline +Zstring Zstring::BeforeLast(defaultChar ch) const +{ + size_t pos = rfind(ch, npos); + + if (pos != npos && pos != 0 ) + return Zstring(data, pos); //data is non-empty string in this context: else ch would not have been found! + else + return Zstring(); +} + + +inline +bool Zstring::StartsWith(const defaultChar* begin) const +{ + const size_t beginLength = defaultLength(begin); + if (length() < beginLength) + return false; + return compare(0, beginLength, begin) == 0; +} + + +inline +bool Zstring::StartsWith(const Zstring& begin) const +{ + const size_t beginLength = begin.length(); + if (length() < beginLength) + return false; + return compare(0, beginLength, begin) == 0; +} + + +inline +bool Zstring::EndsWith(const defaultChar* end) const +{ + const size_t thisLength = length(); + const size_t endLength = defaultLength(end); + if (thisLength < endLength) + return false; + return compare(thisLength - endLength, endLength, end) == 0; +} + + +inline +bool Zstring::EndsWith(const Zstring& end) const +{ + const size_t thisLength = length(); + const size_t endLength = end.length(); + if (thisLength < endLength) + return false; + return compare(thisLength - endLength, endLength, end) == 0; +} + + +inline +size_t Zstring::find(const defaultChar* str, const size_t pos) const +{ + assert(pos <= length()); + const defaultChar* thisStr = c_str(); + const defaultChar* found = defaultStrFind(thisStr + pos, str); + return found == NULL ? npos : found - thisStr; +} + + +inline +size_t Zstring::find(const defaultChar ch, const size_t pos) const +{ + assert(pos <= length()); + const defaultChar* thisStr = c_str(); + const defaultChar* found = defaultStrFind(thisStr + pos, ch); + return found == NULL ? npos : found - thisStr; +} + + +inline +Zstring::operator const wxString() const +{ + return wxString(c_str()); +} + + +inline +int Zstring::Cmp(const defaultChar* other) const +{ + return compare(other); +} + + +inline +int Zstring::Cmp(const Zstring& other) const +{ + return defaultCompare(c_str(), other.c_str()); //overload using strcmp(char*, char*) should be fastest! +} + + +inline +bool Zstring::operator==(const Zstring& other) const +{ + return defaultCompare(c_str(), other.c_str()) == 0; //overload using strcmp(char*, char*) should be fastest! +} + + +inline +bool Zstring::operator==(const defaultChar* other) const +{ + return compare(other) == 0; +} + + +inline +int Zstring::compare(const Zstring& other) const +{ + return defaultCompare(c_str(), other.c_str()); //overload using strcmp(char*, char*) should be fastest! +} + + +inline +int Zstring::compare(const defaultChar* other) const +{ + return defaultCompare(c_str(), other); //overload using strcmp(char*, char*) should be fastest! +} + + +inline +int Zstring::compare(const size_t pos1, const size_t n1, const defaultChar* other) const +{ + assert(length() - pos1 >= n1); + return defaultCompare(c_str() + pos1, other, n1); +} + + +inline +size_t Zstring::length() const +{ + return descr->length; +} + + +inline +const defaultChar* Zstring::c_str() const +{ + if (length()) + return data; + else +#ifdef ZSTRING_CHAR + return ""; +#elif defined ZSTRING_WIDE_CHAR + return L""; +#endif +} + + +inline +bool Zstring::empty() const +{ + return length() == 0; +} + + +inline +Zstring Zstring::operator+(const Zstring& string2) const +{ + return Zstring(*this)+=string2; +} + + +inline +Zstring Zstring::operator+(const defaultChar* string2) const +{ + return Zstring(*this)+=string2; +} + + +inline +Zstring Zstring::operator+(const defaultChar ch) const +{ + return Zstring(*this)+=ch; +} + + +#endif // ZSTRING_H_INCLUDED |