diff options
Diffstat (limited to 'library')
-rw-r--r-- | library/CustomGrid.cpp | 542 | ||||
-rw-r--r-- | library/CustomGrid.h | 41 | ||||
-rw-r--r-- | library/fileHandling.cpp | 802 | ||||
-rw-r--r-- | library/fileHandling.h | 22 | ||||
-rw-r--r-- | library/globalFunctions.cpp | 16 | ||||
-rw-r--r-- | library/globalFunctions.h | 13 | ||||
-rw-r--r-- | library/misc.cpp | 14 | ||||
-rw-r--r-- | library/multithreading.cpp | 2 | ||||
-rw-r--r-- | library/processXml.cpp | 202 | ||||
-rw-r--r-- | library/processXml.h | 25 | ||||
-rw-r--r-- | library/resources.cpp | 3 | ||||
-rw-r--r-- | library/resources.h | 3 | ||||
-rw-r--r-- | library/sorting.h | 8 | ||||
-rw-r--r-- | library/statusHandler.h | 41 | ||||
-rw-r--r-- | library/tinyxml/tinyxmlparser.cpp | 2 | ||||
-rw-r--r-- | library/zstring.cpp | 89 | ||||
-rw-r--r-- | library/zstring.h | 169 |
17 files changed, 1495 insertions, 499 deletions
diff --git a/library/CustomGrid.cpp b/library/CustomGrid.cpp index cf9adc6e..b8737343 100644 --- a/library/CustomGrid.cpp +++ b/library/CustomGrid.cpp @@ -4,6 +4,12 @@ #include <wx/dc.h> #include "../algorithm.h" #include "resources.h" +#include <typeinfo> + +#ifdef FFS_WIN +#include <wx/icon.h> +#include <wx/msw/wrapwin.h> //includes "windows.h" +#endif // FFS_WIN const unsigned int MIN_ROW_COUNT = 15; @@ -93,35 +99,32 @@ public: //update dimensions of grid: no need for InsertRows, AppendRows, DeleteRows anymore!!! void updateGridSizes() { - if (gridRefUI) - { - const int currentNrRows = GetNumberRows(); + const int currentNrRows = GetNumberRows(); - if (lastNrRows < currentNrRows) + if (lastNrRows < currentNrRows) + { + if (GetView()) { - if (GetView()) - { - wxGridTableMessage msg(this, - wxGRIDTABLE_NOTIFY_ROWS_APPENDED, - currentNrRows - lastNrRows); + wxGridTableMessage msg(this, + wxGRIDTABLE_NOTIFY_ROWS_APPENDED, + currentNrRows - lastNrRows); - GetView()->ProcessTableMessage( msg ); - } + GetView()->ProcessTableMessage( msg ); } - else if (lastNrRows > currentNrRows) + } + else if (lastNrRows > currentNrRows) + { + if (GetView()) { - if (GetView()) - { - wxGridTableMessage msg(this, - wxGRIDTABLE_NOTIFY_ROWS_DELETED, - 0, - lastNrRows - currentNrRows); + wxGridTableMessage msg(this, + wxGRIDTABLE_NOTIFY_ROWS_DELETED, + 0, + lastNrRows - currentNrRows); - GetView()->ProcessTableMessage( msg ); - } + GetView()->ProcessTableMessage( msg ); } - lastNrRows = currentNrRows; } + lastNrRows = currentNrRows; const int currentNrCols = GetNumberCols(); @@ -203,6 +206,17 @@ public: } + const FileCompareLine* getRawData(const unsigned int row) + { + if (gridRefUI && row < gridRefUI->size()) + { + const FileCompareLine& cmpLine = (*gridData)[(*gridRefUI)[row]]; + return &cmpLine; + } + return NULL; + } + + protected: virtual const wxColour& getRowColor(int row) = 0; //rows that are filtered out are shown in different color @@ -277,9 +291,9 @@ public: case xmlAccess::FULL_NAME: return gridLine.fileDescrLeft.fullName.c_str(); case xmlAccess::FILENAME: //filename - return gridLine.fileDescrLeft.relativeName.AfterLast(GlobalResources::FILE_NAME_SEPARATOR).c_str(); + return wxString(gridLine.fileDescrLeft.relativeName.c_str()).AfterLast(GlobalResources::FILE_NAME_SEPARATOR); case xmlAccess::REL_PATH: //relative path - return gridLine.fileDescrLeft.relativeName.BeforeLast(GlobalResources::FILE_NAME_SEPARATOR).c_str(); + return wxString(gridLine.fileDescrLeft.relativeName.c_str()).BeforeLast(GlobalResources::FILE_NAME_SEPARATOR); case xmlAccess::SIZE: //file size { wxString fileSize = gridLine.fileDescrLeft.fileSize.ToString(); //tmp string @@ -304,6 +318,7 @@ public: { lastNrCols = 1; //ensure CustomGridTable::updateGridSizes() is working correctly } + ~CustomGridTableMiddle() {} //virtual impl. @@ -313,21 +328,6 @@ public: } - int selectedForSynchronization(const unsigned int row) // 0 == false, 1 == true, -1 == not defined - { - if (gridRefUI && row < gridRefUI->size()) - { - const FileCompareLine cmpLine = (*gridData)[(*gridRefUI)[row]]; - - if (cmpLine.selectedForSynchronization) - return 1; - else - return 0; - } - return -1; - } - - virtual const wxColour& getRowColor(int row) //rows that are filtered out are shown in different color { if (gridRefUI && unsigned(row) < gridRefUI->size()) @@ -446,9 +446,9 @@ public: case xmlAccess::FULL_NAME: return gridLine.fileDescrRight.fullName.c_str(); case xmlAccess::FILENAME: //filename - return gridLine.fileDescrRight.relativeName.AfterLast(GlobalResources::FILE_NAME_SEPARATOR).c_str(); + return wxString(gridLine.fileDescrRight.relativeName.c_str()).AfterLast(GlobalResources::FILE_NAME_SEPARATOR); case xmlAccess::REL_PATH: //relative path - return gridLine.fileDescrRight.relativeName.BeforeLast(GlobalResources::FILE_NAME_SEPARATOR).c_str(); + return wxString(gridLine.fileDescrRight.relativeName.c_str()).BeforeLast(GlobalResources::FILE_NAME_SEPARATOR); case xmlAccess::SIZE: //file size { wxString fileSize = gridLine.fileDescrRight.fileSize.ToString(); //tmp string @@ -478,7 +478,7 @@ CustomGrid::CustomGrid(wxWindow *parent, wxGrid(parent, id, pos, size, style, name), leadGrid(NULL), scrollbarsEnabled(true), - m_gridLeft(NULL), m_gridRight(NULL), m_gridMiddle(NULL), + m_gridLeft(NULL), m_gridMiddle(NULL), m_gridRight(NULL), gridDataTable(NULL), currentSortColumn(-1), sortMarker(NULL) @@ -487,10 +487,24 @@ CustomGrid::CustomGrid(wxWindow *parent, wxColour darkBlue(40, 35, 140); SetSelectionBackground(darkBlue); SetSelectionForeground(*wxWHITE); + + //enhance grid functionality; identify leading grid by keyboard input or scroll action + Connect(wxEVT_KEY_DOWN, wxEventHandler(CustomGrid::onGridAccess), NULL, this); + Connect(wxEVT_SCROLLWIN_TOP, wxEventHandler(CustomGrid::onGridAccess), NULL, this); + Connect(wxEVT_SCROLLWIN_BOTTOM, wxEventHandler(CustomGrid::onGridAccess), NULL, this); + Connect(wxEVT_SCROLLWIN_LINEUP, wxEventHandler(CustomGrid::onGridAccess), NULL, this); + Connect(wxEVT_SCROLLWIN_LINEDOWN, wxEventHandler(CustomGrid::onGridAccess), NULL, this); + Connect(wxEVT_SCROLLWIN_PAGEUP, wxEventHandler(CustomGrid::onGridAccess), NULL, this); + Connect(wxEVT_SCROLLWIN_PAGEDOWN, wxEventHandler(CustomGrid::onGridAccess), NULL, this); + Connect(wxEVT_SCROLLWIN_THUMBTRACK, wxEventHandler(CustomGrid::onGridAccess), NULL, this); + Connect(wxEVT_SCROLLWIN_THUMBRELEASE, wxEventHandler(CustomGrid::onGridAccess), NULL, this); + Connect(wxEVT_GRID_LABEL_LEFT_CLICK, wxEventHandler(CustomGrid::onGridAccess), NULL, this); + GetGridWindow()->Connect(wxEVT_LEFT_DOWN, wxEventHandler(CustomGrid::onGridAccess), NULL, this); } -void CustomGrid::initSettings(bool enableScrollbars, +void CustomGrid::initSettings(const bool enableScrollbars, + const bool showFileIcons, CustomGrid* gridLeft, CustomGrid* gridRight, CustomGrid* gridMiddle, @@ -508,6 +522,221 @@ void CustomGrid::initSettings(bool enableScrollbars, //set underlying grid data assert(gridDataTable); gridDataTable->setGridDataTable(gridRefUI, gridData); + + this->initGridRenderer(showFileIcons); + + GetGridWindow()->Connect(wxEVT_ENTER_WINDOW, wxEventHandler(CustomGrid::adjustGridHeights), NULL, this); +} + + +inline +bool gridsShouldBeCleared(const wxEvent& event) +{ + try + { + const wxMouseEvent& mouseEvent = dynamic_cast<const wxMouseEvent&> (event); + + if (mouseEvent.ControlDown() || mouseEvent.ShiftDown()) + return false; + + if (mouseEvent.ButtonDown(wxMOUSE_BTN_LEFT)) + return true; + + return false; + } + catch (std::bad_cast&) {} + + try + { + const wxKeyEvent& keyEvent = dynamic_cast<const wxKeyEvent&> (event); + + if (keyEvent.ControlDown() || keyEvent.ShiftDown()) + return false; + + switch (keyEvent.GetKeyCode()) + { + case WXK_SPACE: + case WXK_TAB: + case WXK_RETURN: + case WXK_ESCAPE: + case WXK_NUMPAD_ENTER: + case WXK_LEFT: + case WXK_UP: + case WXK_RIGHT: + case WXK_DOWN: + case WXK_PAGEUP: + case WXK_PAGEDOWN: + case WXK_NUMPAD_PAGEUP: + case WXK_NUMPAD_PAGEDOWN: + case WXK_HOME: + case WXK_END: + case WXK_NUMPAD_HOME: + case WXK_NUMPAD_END: + return true; + + default: + return false; + } + } + catch (std::bad_cast&) {} + + return false; +} + + +inline +void moveCursorWhileSelecting(const int anchor, const int oldPos, const int newPos, wxGrid* grid) +{ //note: all positions are valid in this context! + + grid->SetGridCursor(newPos, grid->GetGridCursorCol()); + grid->MakeCellVisible(newPos, grid->GetGridCursorCol()); + + if (oldPos < newPos) + { + for (int i = oldPos; i < std::min(anchor, newPos); ++i) + grid->DeselectRow(i); //remove selection + + for (int i = std::max(oldPos, anchor); i <= newPos; ++i) + grid->SelectRow(i, true); //add to selection + } + else + { + for (int i = std::max(newPos, anchor) + 1; i <= oldPos; ++i) + grid->DeselectRow(i); //remove selection + + for (int i = newPos; i <= std::min(oldPos, anchor); ++i) + grid->SelectRow(i, true); //add to selection + } +} + + +inline +void additionalGridCommands(wxEvent& event, wxGrid* grid) +{ + static int anchorRow = 0; + assert(grid->GetNumberRows() != 0); + + try + { + const wxKeyEvent& keyEvent = dynamic_cast<const wxKeyEvent&> (event); + + if (keyEvent.ShiftDown()) + { + //ensure cursorOldPos is always a valid row! + const int cursorOldPos = std::max(std::min(grid->GetGridCursorRow(), grid->GetNumberRows() - 1), 0); + + //support for shift + PageUp and shift + PageDown + switch (keyEvent.GetKeyCode()) + { + case WXK_UP: //move grid cursor also + { + const int cursorNewPos = std::max(cursorOldPos - 1, 0); + moveCursorWhileSelecting(anchorRow, cursorOldPos, cursorNewPos, grid); + } + return; //no event.Skip() + + case WXK_DOWN: //move grid cursor also + { + const int cursorNewPos = std::min(cursorOldPos + 1, grid->GetNumberRows() - 1); + moveCursorWhileSelecting(anchorRow, cursorOldPos, cursorNewPos, grid); + } + return; //no event.Skip() + + case WXK_PAGEUP: + case WXK_NUMPAD_PAGEUP: + { + const int rowsPerPage = grid->GetGridWindow()->GetSize().GetHeight() / grid->GetDefaultRowSize(); + const int cursorNewPos = std::max(cursorOldPos - rowsPerPage, 0); + moveCursorWhileSelecting(anchorRow, cursorOldPos, cursorNewPos, grid); + } + return; //no event.Skip() + + case WXK_PAGEDOWN: + case WXK_NUMPAD_PAGEDOWN: + { + const int rowsPerPage = grid->GetGridWindow()->GetSize().GetHeight() / grid->GetDefaultRowSize(); + const int cursorNewPos = std::min(cursorOldPos + rowsPerPage, grid->GetNumberRows() - 1); + moveCursorWhileSelecting(anchorRow, cursorOldPos, cursorNewPos, grid); + } + return; //no event.Skip() + + case WXK_HOME: + case WXK_NUMPAD_HOME: + { + const int cursorNewPos = 0; + moveCursorWhileSelecting(anchorRow, cursorOldPos, cursorNewPos, grid); + } + return; //no event.Skip() + + case WXK_END: + case WXK_NUMPAD_END: + { + const int cursorNewPos = grid->GetNumberRows() - 1; + moveCursorWhileSelecting(anchorRow, cursorOldPos, cursorNewPos, grid); + } + return; //no event.Skip() + + } + } + else //button without shift is pressed + { + switch (keyEvent.GetKeyCode()) + { + case WXK_HOME: + case WXK_NUMPAD_HOME: + grid->SetGridCursor(0, grid->GetGridCursorCol()); + grid->MakeCellVisible(0, grid->GetGridCursorCol()); + return; //no event.Skip() + + case WXK_END: + case WXK_NUMPAD_END: + grid->SetGridCursor(grid->GetNumberRows() - 1, grid->GetGridCursorCol()); + grid->MakeCellVisible(grid->GetNumberRows() - 1, grid->GetGridCursorCol()); + return; //no event.Skip() + } + } + } + catch (std::bad_cast&) {} + + anchorRow = grid->GetGridCursorRow(); + event.Skip(); +} + + +void CustomGrid::onGridAccess(wxEvent& event) +{ + if (leadGrid != this) + { + leadGrid = this; + + //notify grids of new user focus + m_gridLeft->leadGrid = this; + m_gridMiddle->leadGrid = this; + m_gridRight->leadGrid = this; + + wxGrid::SetFocus(); + } + + if (gridsShouldBeCleared(event)) + { + m_gridLeft->ClearSelection(); + m_gridRight->ClearSelection(); + } + + //support for additional short-cuts + additionalGridCommands(event, this); //event.Skip is handled here! +} + + +const wxGrid* CustomGrid::getLeadGrid() +{ + return leadGrid; +} + + +bool CustomGrid::isLeadGrid() +{ + return leadGrid == static_cast<const wxGrid*>(this); } @@ -522,7 +751,7 @@ void CustomGrid::SetScrollbar(int orientation, int position, int thumbSize, int //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 +void CustomGrid::adjustGridHeights(wxEvent& event) //m_gridLeft, m_gridRight, m_gridMiddle are not NULL in this context { int y1 = 0; int y2 = 0; @@ -540,17 +769,17 @@ void CustomGrid::adjustGridHeights() //m_gridLeft, m_gridRight, m_gridMiddle are 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); + m_gridLeft->SetMargins(0, 30); if (leadGrid == m_gridRight) m_gridRight->SetMargins(0, 0); else if (y2 < yMax) - m_gridRight->SetMargins(0, 50); + m_gridRight->SetMargins(0, 30); if (leadGrid == m_gridMiddle) m_gridMiddle->SetMargins(0, 0); else if (y3 < yMax) - m_gridMiddle->SetMargins(0, 50); + m_gridMiddle->SetMargins(0, 30); m_gridLeft->ForceRefresh(); m_gridRight->ForceRefresh(); @@ -559,12 +788,6 @@ void CustomGrid::adjustGridHeights() //m_gridLeft, m_gridRight, m_gridMiddle are } -void CustomGrid::setLeadGrid(const wxGrid* newLead) -{ - leadGrid = newLead; -} - - void CustomGrid::updateGridSizes() { assert(gridDataTable); @@ -755,12 +978,99 @@ CustomGridLeft::CustomGridLeft(wxWindow *parent, CustomGrid(parent, id, pos, size, style, name) {} +template <bool leftSide, bool showFileIcons> +class GridCellRenderer : public wxGridCellStringRenderer +{ +public: + GridCellRenderer(CustomGridTable* gridDataTable) : m_gridDataTable(gridDataTable) {}; + + + virtual void Draw(wxGrid& grid, + wxGridCellAttr& attr, + wxDC& dc, + const wxRect& rect, + int row, int col, + bool isSelected) + { +#ifdef FFS_WIN + //############## show windows explorer file icons ###################### + + if (showFileIcons) //evaluate at compile time + { + const int ICON_SIZE = 16; //size in pixel + + if ( m_gridDataTable->getTypeAtPos(col) == xmlAccess::FILENAME && + rect.GetWidth() >= ICON_SIZE) + { + //retrieve grid data + const FileCompareLine* rowData = m_gridDataTable->getRawData(row); + if (rowData) //valid row + { + const DefaultChar* filename; + if (leftSide) //evaluate at compile time + filename = rowData->fileDescrLeft.fullName.c_str(); + else + filename = rowData->fileDescrRight.fullName.c_str(); + + if (*filename != 0) //test if filename is empty + { + // Get the file icon. + SHFILEINFO fileInfo; + if (SHGetFileInfo(filename, + 0, + &fileInfo, + sizeof(fileInfo), + SHGFI_ICON | SHGFI_SMALLICON)) + { + wxIcon icon; + icon.SetHICON((WXHICON)fileInfo.hIcon); + icon.SetSize(ICON_SIZE, ICON_SIZE); + + //clear area where icon will be placed + wxRect rectShrinked(rect); + rectShrinked.SetWidth(ICON_SIZE + 2); //add 2 pixel border + + dc.SetPen(*wxWHITE_PEN); + dc.SetBrush(*wxWHITE_BRUSH); + dc.DrawRectangle(rectShrinked); + + //draw icon + dc.DrawIcon(icon, rectShrinked.GetX() + 2, rectShrinked.GetY()); + + rectShrinked.SetWidth(rect.GetWidth() - ICON_SIZE - 2); + rectShrinked.SetX(rect.GetX() + ICON_SIZE + 2); + wxGridCellStringRenderer::Draw(grid, attr, dc, rectShrinked, row, col, isSelected); + + if (!DestroyIcon(fileInfo.hIcon)) + throw RuntimeException(wxString(wxT("Error deallocating Icon handle!\n\n")) + FreeFileSync::getLastErrorFormatted()); + + return; + } + } + } + } + } + //default + wxGridCellStringRenderer::Draw(grid, attr, dc, rect, row, col, isSelected); + +#elif defined FFS_LINUX + wxGridCellStringRenderer::Draw(grid, attr, dc, rect, row, col, isSelected); +#endif + } + +private: + CustomGridTable* m_gridDataTable; +}; + + + bool CustomGridLeft::CreateGrid(int numRows, int numCols, wxGrid::wxGridSelectionModes selmode) { //use custom wxGridTableBase class for management of large sets of formatted data. //This is done in CreateGrid instead of SetTable method since source code is generated and wxFormbuilder invokes CreatedGrid by default. gridDataTable = new CustomGridTableLeft(); SetTable(gridDataTable, true, wxGrid::wxGridSelectRows); //give ownership to wxGrid: gridDataTable is deleted automatically in wxGrid destructor + return true; } @@ -776,11 +1086,19 @@ void CustomGridLeft::DoPrepareDC(wxDC& dc) GetViewStart(&x, &y); m_gridMiddle->Scroll(-1, y); //scroll in y-direction only m_gridRight->Scroll(x, y); - adjustGridHeights(); //keep here to ensure m_gridLeft, m_gridRight, m_gridMiddle != NULL } } +void CustomGridLeft::initGridRenderer(const bool showFileIcons) +{ + if (showFileIcons) + SetDefaultRenderer(new GridCellRenderer<true, true>(gridDataTable)); //SetDefaultRenderer takes ownership! + else + SetDefaultRenderer(new GridCellRenderer<true, false>(gridDataTable)); +} + + //---------------------------------------------------------------------------------------- CustomGridMiddle::CustomGridMiddle(wxWindow *parent, wxWindowID id, @@ -788,17 +1106,77 @@ CustomGridMiddle::CustomGridMiddle(wxWindow *parent, const wxSize& size, long style, const wxString& name) : - CustomGrid(parent, id, pos, size, style, name) {} + CustomGrid(parent, id, pos, size, style, name) +{ + const wxString header = _("Legend"); + wxString toolTip = header + wxT("\n") + + wxString().Pad(header.Len(), wxChar('-')) + wxT("\n") + + _("<| file on left side only\n") + + _("|> file on right side only\n") + + _("<< left file is newer\n") + + _(">> right file is newer\n") + + _("!= files are different\n") + + _("== files are equal\n\n"); + GetGridWindow()->SetToolTip(toolTip); +} + + +class GridCellRendererMiddle : public wxGridCellStringRenderer +{ +public: + GridCellRendererMiddle(CustomGridTable* gridDataTable) : m_gridDataTable(gridDataTable) {}; + + + virtual void Draw(wxGrid& grid, + wxGridCellAttr& attr, + wxDC& dc, + const wxRect& rect, + int row, int col, + bool isSelected) + { + //retrieve grid data + const FileCompareLine* rowData = m_gridDataTable->getRawData(row); + if (!rowData) //no valid row + { + wxGridCellStringRenderer::Draw(grid, attr, dc, rect, row, col, isSelected); + return; + } + + const int shift = std::min(11 + 3, rect.GetWidth()); //11 is width of checkbox image + + wxRect rectShrinked(rect); + + //clean first block of rect that will receive image of checkbox + rectShrinked.SetWidth(shift); + dc.SetPen(*wxWHITE_PEN); + dc.SetBrush(*wxWHITE_BRUSH); + dc.DrawRectangle(rectShrinked); + + //print image into first block + rectShrinked.SetX(1); + if (rowData->selectedForSynchronization) + dc.DrawLabel(wxEmptyString, *globalResource.bitmapCheckBoxTrue, rectShrinked, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); + else + dc.DrawLabel(wxEmptyString, *globalResource.bitmapCheckBoxFalse, rectShrinked, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); + + //print second block (default): display compare result + rectShrinked.SetWidth(rect.GetWidth() - shift); + rectShrinked.SetX(shift); + wxGridCellStringRenderer::Draw(grid, attr, dc, rectShrinked, row, col, isSelected); + } + +private: + CustomGridTable* m_gridDataTable; +}; bool CustomGridMiddle::CreateGrid(int numRows, int numCols, wxGrid::wxGridSelectionModes selmode) { - CustomGridTableMiddle* newTable = new CustomGridTableMiddle(); - gridDataTable = newTable; + gridDataTable = new CustomGridTableMiddle(); SetTable(gridDataTable, true, wxGrid::wxGridSelectRows); //give ownership to wxGrid: gridDataTable is deleted automatically in wxGrid destructor //display checkboxes (representing bool values) if row is enabled for synchronization - SetDefaultRenderer(new GridCellRendererAddCheckbox(newTable)); //SetDefaultRenderer takes ownership! + SetDefaultRenderer(new GridCellRendererMiddle(gridDataTable)); //SetDefaultRenderer takes ownership! return true; } @@ -815,46 +1193,7 @@ void CustomGridMiddle::DoPrepareDC(wxDC& dc) 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 - } -} - - -void CustomGridMiddle::GridCellRendererAddCheckbox::Draw(wxGrid& grid, - wxGridCellAttr& attr, - wxDC& dc, - const wxRect& rect, - int row, int col, - bool isSelected) -{ - const int selected = m_gridDataTable->selectedForSynchronization(row); - if (selected < 0) //no valid row - { - wxGridCellStringRenderer::Draw(grid, attr, dc, rect, row, col, isSelected); - return; } - - const int shift = std::min(11 + 3, rect.GetWidth()); //11 is width of checkbox image - - wxRect rectShrinked(rect); - - //clean first block of rect that will receive image of checkbox - rectShrinked.SetWidth(shift); - dc.SetPen(*wxWHITE_PEN); - dc.SetBrush(*wxWHITE_BRUSH); - dc.DrawRectangle(rectShrinked); - - //print image into first block - rectShrinked.SetX(1); - if (selected > 0) - dc.DrawLabel(wxEmptyString, *globalResource.bitmapCheckBoxTrue, rectShrinked, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); - else //if (selected == 0) -> redundant - dc.DrawLabel(wxEmptyString, *globalResource.bitmapCheckBoxFalse, rectShrinked, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); - - //print second block (default): display compare result - rectShrinked.SetWidth(rect.GetWidth() - shift); - rectShrinked.SetX(shift); - wxGridCellStringRenderer::Draw(grid, attr, dc, rectShrinked, row, col, isSelected); } @@ -872,6 +1211,7 @@ bool CustomGridRight::CreateGrid(int numRows, int numCols, wxGrid::wxGridSelecti { gridDataTable = new CustomGridTableRight(); SetTable(gridDataTable, true, wxGrid::wxGridSelectRows); //give ownership to wxGrid: gridDataTable is deleted automatically in wxGrid destructor + return true; } @@ -887,6 +1227,14 @@ void CustomGridRight::DoPrepareDC(wxDC& dc) 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 } } + + +void CustomGridRight::initGridRenderer(const bool showFileIcons) +{ + if (showFileIcons) + SetDefaultRenderer(new GridCellRenderer<false, true>(gridDataTable)); //SetDefaultRenderer takes ownership! + else + SetDefaultRenderer(new GridCellRenderer<false, false>(gridDataTable)); +} diff --git a/library/CustomGrid.h b/library/CustomGrid.h index 8f392975..14d62255 100644 --- a/library/CustomGrid.h +++ b/library/CustomGrid.h @@ -10,7 +10,6 @@ using namespace FreeFileSync; class CustomGridTable; -class CustomGridTableMiddle; //################################################################################## class CustomGrid : public wxGrid @@ -30,13 +29,16 @@ public: virtual void DrawColLabel(wxDC& dc, int col); - void initSettings(bool enableScrollbars, + void initSettings(const bool enableScrollbars, + const bool showFileIcons, CustomGrid* gridLeft, CustomGrid* gridRight, CustomGrid* gridMiddle, GridView* gridRefUI, FileCompareResult* gridData); + virtual void initGridRenderer(const bool showFileIcons) = 0; + //notify wxGrid that underlying table size has changed void updateGridSizes(); @@ -52,19 +54,20 @@ public: static wxString getTypeName(xmlAccess::ColumnTypes colType); - void setLeadGrid(const wxGrid* newLead); + const wxGrid* getLeadGrid(); + bool isLeadGrid(); protected: - //set visibility, position and width of columns - xmlAccess::ColumnAttributes columnSettings; + void onGridAccess(wxEvent& event); + void adjustGridHeights(wxEvent& event); - void adjustGridHeights(); + xmlAccess::ColumnAttributes columnSettings; //set visibility, position and width of columns const wxGrid* leadGrid; //grid that has user focus bool scrollbarsEnabled; CustomGrid* m_gridLeft; - CustomGrid* m_gridRight; CustomGrid* m_gridMiddle; + CustomGrid* m_gridRight; CustomGridTable* gridDataTable; @@ -72,6 +75,7 @@ protected: const wxBitmap* sortMarker; }; +//############## SPECIALIZATIONS ################### class CustomGridLeft : public CustomGrid { @@ -89,6 +93,8 @@ public: virtual void DoPrepareDC(wxDC& dc); virtual bool CreateGrid(int numRows, int numCols, wxGrid::wxGridSelectionModes selmode = wxGrid::wxGridSelectCells); + + virtual void initGridRenderer(const bool showFileIcons); }; @@ -109,24 +115,7 @@ public: virtual bool CreateGrid(int numRows, int numCols, wxGrid::wxGridSelectionModes selmode = wxGrid::wxGridSelectCells); -private: - - class GridCellRendererAddCheckbox : public wxGridCellStringRenderer - { - public: - GridCellRendererAddCheckbox(CustomGridTableMiddle* gridDataTable) : m_gridDataTable(gridDataTable) {}; - - - virtual void Draw(wxGrid& grid, - wxGridCellAttr& attr, - wxDC& dc, - const wxRect& rect, - int row, int col, - bool isSelected); - - private: - CustomGridTableMiddle* m_gridDataTable; - }; + virtual void initGridRenderer(const bool showFileIcons) {} }; @@ -146,6 +135,8 @@ public: virtual void DoPrepareDC(wxDC& dc); virtual bool CreateGrid(int numRows, int numCols, wxGrid::wxGridSelectionModes selmode = wxGrid::wxGridSelectCells); + + virtual void initGridRenderer(const bool showFileIcons); }; #endif // CUSTOMGRID_H_INCLUDED diff --git a/library/fileHandling.cpp b/library/fileHandling.cpp index cda81ef5..0dccdec7 100644 --- a/library/fileHandling.cpp +++ b/library/fileHandling.cpp @@ -6,7 +6,16 @@ #ifdef FFS_WIN #include <wx/msw/wrapwin.h> //includes "windows.h" -#endif // FFS_WIN + +#elif defined FFS_LINUX +#include <sys/stat.h> +#include <time.h> +#include <utime.h> +#include <fstream> +#include <unistd.h> +#include <dirent.h> +#include <errno.h> +#endif class RecycleBin @@ -40,7 +49,7 @@ public: fileOp.wFunc = FO_DELETE; fileOp.pFrom = filenameDoubleNull.c_str(); fileOp.pTo = NULL; - fileOp.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_SILENT; + fileOp.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_SILENT | FOF_NOERRORUI; fileOp.fAnyOperationsAborted = false; fileOp.hNameMappings = NULL; fileOp.lpszProgressTitle = NULL; @@ -75,10 +84,35 @@ bool moveToRecycleBin(const Zstring& filename) throw(RuntimeException) } -inline +bool FreeFileSync::fileExists(const Zstring& filename) +{ //symbolic links (broken or not) are also treated as existing files! +#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.c_str()); + + return (ret != INVALID_FILE_ATTRIBUTES) && !(ret & FILE_ATTRIBUTE_DIRECTORY); + +#elif defined FFS_LINUX + struct stat fileInfo; + return (lstat(filename.c_str(), &fileInfo) == 0 && + (S_ISLNK(fileInfo.st_mode) || S_ISREG(fileInfo.st_mode))); +#endif +} + + void FreeFileSync::removeFile(const Zstring& filename, const bool useRecycleBin) { - if (!wxFileExists(filename.c_str())) return; //this is NOT an error situation: the manual deletion relies on it! + //no error situation if file is not existing! manual deletion relies on it! +#ifdef FFS_WIN + if (GetFileAttributes(filename.c_str()) == INVALID_FILE_ATTRIBUTES) + return; //neither file nor any other object with that name existing + +#elif defined FFS_LINUX + struct stat fileInfo; + if (lstat(filename.c_str(), &fileInfo) != 0) + return; //neither file nor any other object (e.g. broken symlink) with that name existing +#endif if (useRecycleBin) { @@ -103,61 +137,218 @@ void FreeFileSync::removeFile(const Zstring& filename, const bool useRecycleBin) Zstring errorMessage = Zstring(_("Error deleting file:")) + wxT("\n\"") + filename + wxT("\""); throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); } -#else - if (!wxRemoveFile(filename.c_str())) - throw FileError(Zstring(_("Error deleting file:")) + wxT("\n\"") + filename + wxT("\"")); +#elif defined FFS_LINUX + if (unlink(filename.c_str()) != 0) + { + Zstring errorMessage = Zstring(_("Error deleting file:")) + wxT("\n\"") + filename + wxT("\""); + throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); + } #endif } -void FreeFileSync::removeDirectory(const Zstring& directory, const bool useRecycleBin) +class FilesDirsOnlyTraverser : public FreeFileSync::FullDetailFileTraverser { - if (!wxDirExists(directory)) return; //this is NOT an error situation: the manual deletion relies on it! +public: + FilesDirsOnlyTraverser(std::vector<Zstring>& files, std::vector<Zstring>& dirs) : + m_files(files), + m_dirs(dirs) {} - if (useRecycleBin) + virtual wxDirTraverseResult OnFile(const Zstring& filename, const FreeFileSync::FileInfo& details) { - if (!moveToRecycleBin(directory)) - throw FileError(Zstring(_("Error moving to Recycle Bin:")) + wxT("\n\"") + directory + wxT("\"")); - return; + m_files.push_back(filename); + return wxDIR_CONTINUE; + } + virtual wxDirTraverseResult OnDir(const Zstring& dirname) + { + m_dirs.push_back(dirname); + return wxDIR_IGNORE; //DON'T traverse into subdirs, removeDirectory works recursively! + } + virtual wxDirTraverseResult OnError(const Zstring& errorText) + { + throw FileError(errorText); } - std::vector<Zstring> fileList; - std::vector<Zstring> dirList; +private: + std::vector<Zstring>& m_files; + std::vector<Zstring>& m_dirs; +}; - getAllFilesAndDirs(directory, fileList, dirList); - for (unsigned int j = 0; j < fileList.size(); ++j) - removeFile(fileList[j], false); +void FreeFileSync::removeDirectory(const Zstring& directory, const bool useRecycleBin) +{ + //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 + if (dirAttr == INVALID_FILE_ATTRIBUTES) + return; //neither directory nor any other object with that name existing - dirList.insert(dirList.begin(), directory); //parent directory will be deleted last +#elif defined FFS_LINUX + struct stat dirInfo; + if (lstat(directory.c_str(), &dirInfo) != 0) + return; //neither directory nor any other object (e.g. broken symlink) with that name existing +#endif - for (int j = int(dirList.size()) - 1; j >= 0 ; --j) + if (useRecycleBin) { + if (!moveToRecycleBin(directory)) + throw FileError(Zstring(_("Error moving to Recycle Bin:")) + wxT("\n\"") + directory + wxT("\"")); + return; + } + +//attention: check if directory is a symlink! Do NOT traverse into it deleting contained files!!! #ifdef FFS_WIN - //initialize file attributes - if (!SetFileAttributes( - dirList[j].c_str(), // address of directory name - FILE_ATTRIBUTE_NORMAL)) // attributes to set + if (dirAttr & FILE_ATTRIBUTE_REPARSE_POINT) + { //remove symlink directly, support for \\?\-prefix + if (RemoveDirectory(directory.c_str()) == 0) { - Zstring errorMessage = Zstring(_("Error deleting directory:")) + wxT("\n\"") + dirList[j] + wxT("\""); + Zstring errorMessage = Zstring(_("Error deleting directory:")) + wxT("\n\"") + directory + wxT("\""); throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); } + return; + } - //remove directory, support for \\?\-prefix - if (RemoveDirectory(dirList[j].c_str()) == 0) +#elif defined FFS_LINUX + if (S_ISLNK(dirInfo.st_mode)) + { + if (unlink(directory.c_str()) != 0) { - Zstring errorMessage = Zstring(_("Error deleting directory:")) + wxT("\n\"") + dirList[j] + wxT("\""); + Zstring errorMessage = Zstring(_("Error deleting directory:")) + wxT("\n\"") + directory + wxT("\""); throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); } + return; + } +#endif + + std::vector<Zstring> fileList; + std::vector<Zstring> dirList; + + //get all files and directories from current directory (WITHOUT subdirectories!) + FilesDirsOnlyTraverser traverser(fileList, dirList); + FreeFileSync::traverseInDetail(directory, false, &traverser); //don't traverse into symlinks to directories + + //delete files + for (std::vector<Zstring>::const_iterator j = fileList.begin(); j != fileList.end(); ++j) + FreeFileSync::removeFile(*j, false); + + //delete directories recursively + for (std::vector<Zstring>::const_iterator j = dirList.begin(); j != dirList.end(); ++j) + FreeFileSync::removeDirectory(*j, false); //call recursively to correctly handle symbolic links + + //parent directory is deleted last +#ifdef FFS_WIN + //initialize file attributes + if (!SetFileAttributes( + directory.c_str(), // address of directory name + FILE_ATTRIBUTE_NORMAL)) // attributes to set + { + Zstring errorMessage = Zstring(_("Error deleting directory:")) + wxT("\n\"") + directory + wxT("\""); + throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); + } + + //remove directory, support for \\?\-prefix + if (!RemoveDirectory(directory.c_str())) + { + Zstring errorMessage = Zstring(_("Error deleting directory:")) + wxT("\n\"") + directory + wxT("\""); + throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); + } #else - if (!wxRmdir(dirList[j].c_str())) - throw FileError(Zstring(_("Error deleting directory:")) + wxT("\n\"") + dirList[j] + wxT("\"")); + if (rmdir(directory.c_str()) != 0) + { + Zstring errorMessage = Zstring(_("Error deleting directory:")) + wxT("\n\"") + directory + wxT("\""); + throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); + } #endif +} + + +#ifdef FFS_WIN +class CloseHandleOnExit +{ +public: + CloseHandleOnExit(HANDLE searchHandle) : m_searchHandle(searchHandle) {} + + ~CloseHandleOnExit() + { + FindClose(m_searchHandle); } + +private: + HANDLE m_searchHandle; +}; + + +typedef DWORD WINAPI (*GetFinalPath)( + HANDLE hFile, + LPTSTR lpszFilePath, + DWORD cchFilePath, + DWORD dwFlags); + + +class DllHandler //dynamically load windows API functions +{ +public: + DllHandler() : + getFinalPathNameByHandle(NULL), + hKernel(NULL) + { + //get a handle to the DLL module containing required functionality + hKernel = ::LoadLibrary(wxT("kernel32.dll")); + if (hKernel) + getFinalPathNameByHandle = (GetFinalPath)(::GetProcAddress(hKernel, "GetFinalPathNameByHandleW")); //load unicode version! + } + + ~DllHandler() + { + if (hKernel) ::FreeLibrary(hKernel); + } + + GetFinalPath getFinalPathNameByHandle; + +private: + HINSTANCE hKernel; +}; + +//global instance +DllHandler dynamicWinApi; + + +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); + if (hDir == INVALID_HANDLE_VALUE) + return Zstring(); + + CloseHandleOnExit dummy(hDir); + + if (dynamicWinApi.getFinalPathNameByHandle == NULL ) + throw FileError(Zstring(_("Error loading library function:")) + wxT("\n\"") + wxT("GetFinalPathNameByHandleW") + wxT("\"")); + + const unsigned BUFFER_SIZE = 10000; + TCHAR targetPath[BUFFER_SIZE]; + + const DWORD rv = dynamicWinApi.getFinalPathNameByHandle( + hDir, + targetPath, + BUFFER_SIZE, + 0); + + if (rv >= BUFFER_SIZE || rv == 0) + return Zstring(); + + return targetPath; } +#endif -void FreeFileSync::createDirectory(const Zstring& directory, const int level) +void createDirectoryRecursively(const Zstring& directory, const Zstring& templateDir, const bool copyDirectorySymLinks, const int level) { if (wxDirExists(directory.c_str())) return; @@ -165,193 +356,355 @@ void FreeFileSync::createDirectory(const Zstring& directory, const int level) if (level == 50) //catch endless recursion return; - //try to create directory, support for \\?\-prefix -#ifdef FFS_WIN - if (CreateDirectory( - directory.c_str(), // pointer to a directory path string - NULL // pointer to a security descriptor - ) != 0) - return; -#else - if (wxMkdir(directory.c_str())) //wxMkDir has different signature under Linux! - return; -#endif - - //if not successfull try to create parent folders first - Zstring parentDir; - if (endsWithPathSeparator(directory)) //may be valid on first level of recursion at most! Usually never true! + //try to create parent folders first + const Zstring dirParent = directory.BeforeLast(GlobalResources::FILE_NAME_SEPARATOR); + if (!dirParent.empty() && !wxDirExists(dirParent)) { - parentDir = directory.BeforeLast(GlobalResources::FILE_NAME_SEPARATOR); - parentDir = parentDir.BeforeLast(GlobalResources::FILE_NAME_SEPARATOR); + //call function recursively + const Zstring templateParent = templateDir.BeforeLast(GlobalResources::FILE_NAME_SEPARATOR); + createDirectoryRecursively(dirParent, templateParent, false, level + 1); //don't create symbolic links in recursion! } - else - parentDir = directory.BeforeLast(GlobalResources::FILE_NAME_SEPARATOR); - - if (parentDir.empty()) return; - - //call function recursively - createDirectory(parentDir, level + 1); //now creation should be possible #ifdef FFS_WIN - if (CreateDirectory( - directory.c_str(), // pointer to a directory path string - NULL // pointer to a security descriptor - ) == 0) + const DWORD templateAttr = ::GetFileAttributes(templateDir.c_str()); //replaces wxDirExists(): also returns successful for broken symlinks + if (templateAttr == INVALID_FILE_ATTRIBUTES) //fallback { - if (level == 0) + if (CreateDirectory( + directory.c_str(), // pointer to a directory path string + NULL) == 0 && level == 0) { - Zstring errorMessage = Zstring(_("Error creating directory:")) + wxT("\n\"") + directory + wxT("\""); + const Zstring errorMessage = Zstring(_("Error creating directory:")) + wxT("\n\"") + directory + wxT("\""); throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); } } -#else - if (!wxMkdir(directory.c_str())) //wxMkDir has different signature under Linux! + else { - if (level == 0) - throw FileError(Zstring(_("Error creating directory:")) + wxT("\n\"") + directory + wxT("\"")); + //symbolic link handling + if (!copyDirectorySymLinks && templateAttr & FILE_ATTRIBUTE_REPARSE_POINT) //create directory based on target of symbolic link + { + //get target directory of symbolic link + const Zstring targetPath = resolveDirectorySymlink(templateDir); + if (targetPath.empty()) + { + if (level == 0) + throw FileError(Zstring(_("Error resolving symbolic link:")) + wxT("\n\"") + templateDir + wxT("\"")); + } + else + { + if (CreateDirectoryEx( // this function automatically copies symbolic links if encountered + targetPath.c_str(), // pointer to path string of template directory + directory.c_str(), // pointer to a directory path string + NULL) == 0 && level == 0) + { + const Zstring errorMessage = Zstring(_("Error creating directory:")) + wxT("\n\"") + directory + wxT("\""); + throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); + } + } + } + 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 + NULL) == 0 && level == 0) + { + const Zstring errorMessage = Zstring(_("Error creating directory:")) + wxT("\n\"") + directory + wxT("\""); + throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); + } + } } -#endif -} - - -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) +#elif defined FFS_LINUX + //symbolic link handling + if (copyDirectorySymLinks) { - Zstring errorMessage = Zstring(_("Error reading folder attributes:")) + wxT("\n\"") + source + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); + //test if templateDir is a symbolic link + struct stat linkInfo; + if (lstat(templateDir.c_str(), &linkInfo) == 0 && S_ISLNK(linkInfo.st_mode)) + { + //copy symbolic link + const int BUFFER_SIZE = 10000; + char buffer[BUFFER_SIZE]; + const int bytesWritten = readlink(templateDir.c_str(), buffer, BUFFER_SIZE); + if (bytesWritten < 0 || bytesWritten == BUFFER_SIZE) + { + Zstring errorMessage = Zstring(_("Error resolving symbolic link:")) + wxT("\n\"") + templateDir + wxT("\""); + if (bytesWritten < 0) errorMessage += Zstring(wxT("\n\n")) + FreeFileSync::getLastErrorFormatted(); + throw FileError(errorMessage); + } + //set null-terminating char + buffer[bytesWritten] = 0; + + if (symlink(buffer, directory.c_str()) != 0) + { + Zstring errorMessage = Zstring(_("Error creating directory:")) + wxT("\n\"") + directory + wxT("\""); + throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); + } + return; //symlink created successfully + } } - if (!SetFileAttributes( - target.c_str(), // address of filename - attr)) // address of attributes to set + //default directory creation + if (mkdir(directory.c_str(), 0755) != 0 && level == 0) { - Zstring errorMessage = Zstring(_("Error writing folder attributes:")) + wxT("\n\"") + target + wxT("\""); + Zstring errorMessage = Zstring(_("Error creating directory:")) + wxT("\n\"") + directory + wxT("\""); throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); } -#elif defined FFS_LINUX -//Linux doesn't use attributes for files or folders + +//copy directory permissions: not sure if this is a good idea: if a directory is read-only copying/sync'ing of files will fail... + /* + if (templateDirExists) + { + struct stat fileInfo; + if (stat(templateDir.c_str(), &fileInfo) != 0) //read permissions from template directory + throw FileError(Zstring(_("Error reading file attributes:")) + wxT("\n\"") + templateDir + wxT("\"")); + + // reset the umask as we want to create the directory with exactly the same permissions as the template + wxCHANGE_UMASK(0); + + if (mkdir(directory.c_str(), fileInfo.st_mode) != 0 && level == 0) + throw FileError(Zstring(_("Error creating directory:")) + wxT("\n\"") + directory + wxT("\"")); + } + else + { + if (mkdir(directory.c_str(), 0777) != 0 && level == 0) + throw FileError(Zstring(_("Error creating directory:")) + wxT("\n\"") + directory + wxT("\"")); + } + */ #endif } -class FilesDirsOnlyTraverser : public FreeFileSync::FullDetailFileTraverser +void FreeFileSync::createDirectory(const Zstring& directory, const Zstring& templateDir, const bool copyDirectorySymLinks) { -public: - FilesDirsOnlyTraverser(std::vector<Zstring>& files, std::vector<Zstring>& dirs) : - m_files(files), - m_dirs(dirs) {} + //remove trailing separator + Zstring dirFormatted; + if (FreeFileSync::endsWithPathSeparator(directory)) + dirFormatted = directory.BeforeLast(GlobalResources::FILE_NAME_SEPARATOR); + else + dirFormatted = directory; - virtual wxDirTraverseResult OnFile(const Zstring& filename, const FreeFileSync::FileInfo& details) - { - m_files.push_back(filename); - return wxDIR_CONTINUE; - } - virtual wxDirTraverseResult OnDir(const Zstring& dirname) + Zstring templateFormatted; + if (FreeFileSync::endsWithPathSeparator(templateDir)) + templateFormatted = templateDir.BeforeLast(GlobalResources::FILE_NAME_SEPARATOR); + else + templateFormatted = templateDir; + + createDirectoryRecursively(dirFormatted, templateFormatted, copyDirectorySymLinks, 0); +} + + +#ifdef FFS_LINUX +struct MemoryAllocator +{ + MemoryAllocator() { - m_dirs.push_back(dirname); - return wxDIR_CONTINUE; + buffer = new char[bufferSize]; } - virtual wxDirTraverseResult OnError(const Zstring& errorText) + + ~MemoryAllocator() { - throw FileError(errorText); + delete [] buffer; } -private: - std::vector<Zstring>& m_files; - std::vector<Zstring>& m_dirs; + static const unsigned int bufferSize = 512 * 1024; + char* buffer; }; -void FreeFileSync::getAllFilesAndDirs(const Zstring& sourceDir, std::vector<Zstring>& files, std::vector<Zstring>& directories) throw(FileError) +void FreeFileSync::copyFile(const Zstring& sourceFile, + const Zstring& targetFile, + const bool copyFileSymLinks, + CopyFileCallback callback, + void* data) { - files.clear(); - directories.clear(); + try + { + if (FreeFileSync::fileExists(targetFile.c_str())) + throw FileError(Zstring(_("Error copying file:")) + wxT("\n\"") + sourceFile + wxT("\" -> \"") + targetFile + wxT("\"\n") + + _("Target file already existing!")); - //get all files and directories from current directory (and subdirectories) - FilesDirsOnlyTraverser traverser(files, directories); - traverseInDetail(sourceDir, false, &traverser); -} + //symbolic link handling + if (copyFileSymLinks) + { + //test if sourceFile is a symbolic link + struct stat linkInfo; + if (lstat(sourceFile.c_str(), &linkInfo) != 0) + { + Zstring errorMessage = Zstring(_("Error reading file attributes:")) + wxT("\n\"") + sourceFile + wxT("\""); + throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); + } + if (S_ISLNK(linkInfo.st_mode)) + { + //copy symbolic link + const unsigned BUFFER_SIZE = 10000; + char buffer[BUFFER_SIZE]; + const int bytesWritten = readlink(sourceFile.c_str(), buffer, BUFFER_SIZE - 1); + if (bytesWritten < 0) + { + Zstring errorMessage = Zstring(_("Error resolving symbolic link:")) + wxT("\n\"") + sourceFile + wxT("\""); + throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); + } + //set null-terminating char + buffer[bytesWritten] = 0; -#ifdef FFS_WIN -class CloseOnExit -{ -public: - CloseOnExit(HANDLE searchHandle) : m_searchHandle(searchHandle) {} + if (symlink(buffer, targetFile.c_str()) != 0) + { + Zstring errorMessage = Zstring(_("Error writing file:")) + wxT("\n\"") + targetFile + wxT("\""); + throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); + } - ~CloseOnExit() - { - FindClose(m_searchHandle); + return; //symlink created successfully + } + } + + //begin of regular file copy + struct stat fileInfo; + if (stat(sourceFile.c_str(), &fileInfo) != 0) //read file attributes from source file (resolve symlinks) + { + Zstring errorMessage = Zstring(_("Error reading file attributes:")) + wxT("\n\"") + sourceFile + wxT("\""); + throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); + } + + //open sourceFile for reading + std::ifstream fileIn(sourceFile.c_str(), std::ios_base::binary); + if (fileIn.fail()) + throw FileError(Zstring(_("Error opening file:")) + wxT("\n\"") + sourceFile + wxT("\"")); + + //create targetFile and open it for writing + std::ofstream fileOut(targetFile.c_str(), std::ios_base::binary); + if (fileOut.fail()) + throw FileError(Zstring(_("Error opening file:")) + wxT("\n\"") + targetFile + wxT("\"")); + + //copy contents of sourceFile to targetFile + wxULongLong totalBytesTransferred; + static MemoryAllocator memory; + while (true) + { + fileIn.read(memory.buffer, memory.bufferSize); + if (fileIn.eof()) //end of file? fail bit is set in this case also! + { + fileOut.write(memory.buffer, fileIn.gcount()); + if (fileOut.bad()) + throw FileError(Zstring(_("Error writing file:")) + wxT("\n\"") + targetFile + wxT("\"")); + break; + } + else if (fileIn.fail()) + throw FileError(Zstring(_("Error reading file:")) + wxT("\n\"") + sourceFile + wxT("\"")); + + + fileOut.write(memory.buffer, memory.bufferSize); + if (fileOut.bad()) + throw FileError(Zstring(_("Error writing file:")) + wxT("\n\"") + targetFile + wxT("\"")); + + totalBytesTransferred += memory.bufferSize; + + //invoke callback method to update progress indicators + if (callback) + callback(totalBytesTransferred, data); + } + + //close streams before changing attributes + fileIn.close(); + fileOut.close(); + + //adapt file modification time: + struct utimbuf newTimes; + time(&newTimes.actime); //set file access time to current time + newTimes.modtime = fileInfo.st_mtime; + if (utime(targetFile.c_str(), &newTimes) != 0) + { + Zstring errorMessage = Zstring(_("Error changing modification time:")) + wxT("\n\"") + targetFile + wxT("\""); + throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); + } + + //set file access rights + if (chmod(targetFile.c_str(), fileInfo.st_mode) != 0) + { + Zstring errorMessage = Zstring(_("Error writing file attributes:")) + wxT("\n\"") + targetFile + wxT("\""); + throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); + } } + catch (...) + { //try to delete target file if error occured, or exception was thrown in callback function + if (FreeFileSync::fileExists(targetFile.c_str())) + wxRemoveFile(targetFile.c_str()); //don't handle error situations! -private: - HANDLE m_searchHandle; -}; + throw; + } +} +#endif +#ifdef FFS_WIN inline -void getWin32FileInformation(const WIN32_FIND_DATA& input, FreeFileSync::FileInfo& output) +void setWin32FileInformation(const FILETIME& lastWriteTime, const DWORD fileSizeHigh, const DWORD fileSizeLow, 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); + wxLongLong writeTimeLong(lastWriteTime.dwHighDateTime, lastWriteTime.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 - assert(writeTimeLong.GetHi() == 0); //it should fit into a 32bit variable now - output.lastWriteTimeRaw = writeTimeLong.GetLo(); + writeTimeLong -= wxLongLong(2, 3054539008UL); //timeshift between ansi C time and FILETIME in seconds == 11644473600s + output.lastWriteTimeRaw = writeTimeLong; - output.fileSize = wxULongLong(input.nFileSizeHigh, input.nFileSizeLow); + output.fileSize = wxULongLong(fileSizeHigh, fileSizeLow); } -#elif defined FFS_LINUX -class EnhancedFileTraverser : public wxDirTraverser +inline +bool setWin32FileInformationFromSymlink(const Zstring linkName, FreeFileSync::FileInfo& output) { -public: - EnhancedFileTraverser(FreeFileSync::FullDetailFileTraverser* sink) : m_sink(sink) {} + //open handle to target of symbolic link + HANDLE hFile = CreateFile(linkName.c_str(), + 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL); + if (hFile == INVALID_HANDLE_VALUE) + return false; - wxDirTraverseResult OnFile(const wxString& filename) //virtual impl. - { - struct stat fileInfo; - if (stat(filename.c_str(), &fileInfo) != 0) - return m_sink->OnError(Zstring(_("Could not retrieve file info for:")) + wxT("\n\"") + filename.c_str() + wxT("\"")); + CloseHandleOnExit dummy(hFile); - FreeFileSync::FileInfo details; - details.lastWriteTimeRaw = fileInfo.st_mtime; //UTC time(ANSI C format); unit: 1 second - details.fileSize = fileInfo.st_size; + BY_HANDLE_FILE_INFORMATION fileInfoByHandle; - return m_sink->OnFile(filename.c_str(), details); - } + if (!GetFileInformationByHandle( + hFile, + &fileInfoByHandle)) + return false; - wxDirTraverseResult OnDir(const wxString& dirname) //virtual impl. - { - return m_sink->OnDir(dirname.c_str()); - } + //write output + setWin32FileInformation(fileInfoByHandle.ftLastWriteTime, fileInfoByHandle.nFileSizeHigh, fileInfoByHandle.nFileSizeLow, output); + + return true; +} + +#elif defined FFS_LINUX +class CloseDirOnExit +{ +public: + CloseDirOnExit(DIR* dir) : m_dir(dir) {} - wxDirTraverseResult OnOpenError(const wxString& errorText) //virtual impl. + ~CloseDirOnExit() { - return m_sink->OnError(errorText.c_str()); + closedir(m_dir); //no error handling here } private: - FreeFileSync::FullDetailFileTraverser* m_sink; + DIR* m_dir; }; #endif +template <bool traverseDirectorySymlinks> class TraverseRecursively { public: - TraverseRecursively(const bool traverseSymbolicLinks, FreeFileSync::FullDetailFileTraverser* sink) : - m_traverseSymbolicLinks(traverseSymbolicLinks), - m_sink(sink) {} + TraverseRecursively(FreeFileSync::FullDetailFileTraverser* sink) : m_sink(sink) {} bool traverse(const Zstring& directory, const int level) { -#ifdef FFS_WIN - if (level == 50) //catch endless recursion + if (level == 100) //catch endless recursion { if (m_sink->OnError(Zstring(_("Error traversing directory:")) + wxT("\n\"") + directory + wxT("\"")) == wxDIR_STOP) return false; @@ -359,6 +712,7 @@ public: return true; } +#ifdef FFS_WIN Zstring directoryFormatted = directory; if (!FreeFileSync::endsWithPathSeparator(directoryFormatted)) directoryFormatted += GlobalResources::FILE_NAME_SEPARATOR; @@ -376,13 +730,13 @@ public: return true; //else: we have a problem... report it: - Zstring errorMessage = Zstring(_("Error traversing directory:")) + wxT("\n\"") + directory + wxT("\" ") ; + Zstring errorMessage = Zstring(_("Error traversing directory:")) + wxT("\n\"") + directory + wxT("\"") ; if (m_sink->OnError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted(lastError)) == wxDIR_STOP) return false; else return true; } - CloseOnExit dummy(searchHandle); + CloseHandleOnExit dummy(searchHandle); do { //don't return "." and ".." @@ -394,7 +748,7 @@ public: const Zstring fullName = directoryFormatted + name; - if (fileMetaData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) //a directory... + if (fileMetaData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) //a directory... (for directory symlinks this flag is set too!) { switch (m_sink->OnDir(fullName)) { @@ -402,7 +756,7 @@ public: break; case wxDIR_CONTINUE: //traverse into symbolic links, junctions, etc. if requested only: - if (m_traverseSymbolicLinks || (~fileMetaData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) + if (traverseDirectorySymlinks || (~fileMetaData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) if (!this->traverse(fullName, level + 1)) return false; break; @@ -410,25 +764,25 @@ public: return false; default: assert(false); - break; } } else //a file... { FreeFileSync::FileInfo details; - getWin32FileInformation(fileMetaData, details); - switch (m_sink->OnFile(fullName, details)) + if (fileMetaData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) //dereference symlinks! { - case wxDIR_IGNORE: - case wxDIR_CONTINUE: - break; - case wxDIR_STOP: - return false; - default: - assert(false); - break; + if (!setWin32FileInformationFromSymlink(fullName, details)) //broken symlink + { + details.lastWriteTimeRaw = 0; //we are not interested in the modifiation time of the link + details.fileSize = 0; + } } + else + setWin32FileInformation(fileMetaData.ftLastWriteTime, fileMetaData.nFileSizeHigh, fileMetaData.nFileSizeLow, details); + + if (m_sink->OnFile(fullName, details) == wxDIR_STOP) + return false; } } while (FindNextFile(searchHandle, // handle to search @@ -439,42 +793,131 @@ public: return true; //everything okay //else: we have a problem... report it: - Zstring errorMessage = Zstring(_("Error traversing directory:")) + wxT("\n\"") + directory + wxT("\" ") ; + Zstring errorMessage = Zstring(_("Error traversing directory:")) + wxT("\n\"") + directory + wxT("\"") ; if (m_sink->OnError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted(lastError)) == wxDIR_STOP) return false; else return true; + #elif defined FFS_LINUX + Zstring directoryFormatted = directory; + if (FreeFileSync::endsWithPathSeparator(directoryFormatted)) + directoryFormatted = directoryFormatted.BeforeLast(GlobalResources::FILE_NAME_SEPARATOR); - //use standard file traverser and enrich output with additional file information - //could be improved with custom traversing algorithm for optimized performance - EnhancedFileTraverser traverser(m_sink); + DIR* dirObj = opendir(directoryFormatted.c_str()); + if (dirObj == NULL) + { + Zstring errorMessage = Zstring(_("Error traversing directory:")) + wxT("\n\"") + directoryFormatted + wxT("\"") ; + if (m_sink->OnError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()) == wxDIR_STOP) + return false; + else + return true; + } + CloseDirOnExit dummy(dirObj); - wxDir dir(directory.c_str()); - if (dir.IsOpened()) - dir.Traverse(traverser); + struct dirent* dirEntry; + while (!(errno = 0) && (dirEntry = readdir(dirObj)) != NULL) //set errno to 0 as unfortunately this isn't done when readdir() returns NULL when it is finished + { + //don't return "." and ".." + const wxChar* name = dirEntry->d_name; + if ( name[0] == wxChar('.') && + ((name[1] == wxChar('.') && name[2] == wxChar('\0')) || + name[1] == wxChar('\0'))) + continue; - return true; -#else - adapt this + const Zstring fullName = directoryFormatted + GlobalResources::FILE_NAME_SEPARATOR + name; + + struct stat fileInfo; + if (lstat(fullName.c_str(), &fileInfo) != 0) //lstat() does not resolve symlinks + { + const Zstring errorMessage = Zstring(_("Error reading file attributes:")) + wxT("\n\"") + fullName + wxT("\""); + if (m_sink->OnError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()) == wxDIR_STOP) + return false; + continue; + } + + const bool isSymbolicLink = S_ISLNK(fileInfo.st_mode); + if (isSymbolicLink) //dereference symbolic links + { + if (stat(fullName.c_str(), &fileInfo) != 0) //stat() resolves symlinks + { + //a broken symbolic link + FreeFileSync::FileInfo details; + details.lastWriteTimeRaw = 0; //we are not interested in the modifiation time of the link + details.fileSize = 0; + + if (m_sink->OnFile(fullName, details) == wxDIR_STOP) + return false; + continue; + } + } + + + if (S_ISDIR(fileInfo.st_mode)) //a directory... (note: symbolic links need to be dereferenced to test if they point to a directory!) + { + switch (m_sink->OnDir(fullName)) + { + case wxDIR_IGNORE: + break; + case wxDIR_CONTINUE: + if (traverseDirectorySymlinks || !isSymbolicLink) //traverse into symbolic links if requested only + { + if (!this->traverse(fullName, level + 1)) + return false; + } + break; + case wxDIR_STOP: + return false; + default: + assert(false); + } + } + else //a file... + { + FreeFileSync::FileInfo details; + details.lastWriteTimeRaw = fileInfo.st_mtime; //UTC time(ANSI C format); unit: 1 second + details.fileSize = fileInfo.st_size; + + if (m_sink->OnFile(fullName, details) == wxDIR_STOP) + return false; + } + } + + if (errno == 0) + return true; //everything okay + + //else: we have a problem... report it: + const Zstring errorMessage = Zstring(_("Error traversing directory:")) + wxT("\n\"") + directoryFormatted + wxT("\"") ; + if (m_sink->OnError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()) == wxDIR_STOP) + return false; + else + return true; #endif } private: - const bool m_traverseSymbolicLinks; FreeFileSync::FullDetailFileTraverser* m_sink; }; void FreeFileSync::traverseInDetail(const Zstring& directory, - const bool traverseSymbolicLinks, + const bool traverseDirectorySymlinks, FullDetailFileTraverser* sink) { - TraverseRecursively filewalker(traverseSymbolicLinks, sink); - filewalker.traverse(directory, 0); + if (traverseDirectorySymlinks) + { + TraverseRecursively<true> filewalker(sink); + filewalker.traverse(directory, 0); + } + else + { + TraverseRecursively<false> filewalker(sink); + filewalker.traverse(directory, 0); + } } +/* #ifdef FFS_WIN inline Zstring getDriveName(const Zstring& directoryName) //GetVolume() doesn't work under Linux! @@ -500,3 +943,4 @@ bool FreeFileSync::isFatDrive(const Zstring& directoryName) return Zstring(fileSystem).StartsWith(wxT("FAT")); } #endif //FFS_WIN +*/ diff --git a/library/fileHandling.h b/library/fileHandling.h index 7a1b1842..6c9a0400 100644 --- a/library/fileHandling.h +++ b/library/fileHandling.h @@ -26,7 +26,7 @@ namespace FreeFileSync struct FileInfo { wxULongLong fileSize; //unit: bytes! - time_t lastWriteTimeRaw; //number of seconds since Jan. 1st 1970 UTC + wxLongLong lastWriteTimeRaw; //number of seconds since Jan. 1st 1970 UTC }; //traverser interface @@ -40,8 +40,9 @@ namespace FreeFileSync }; //custom traverser with detail information about files - void traverseInDetail(const Zstring& directory, const bool traverseSymbolicLinks, FullDetailFileTraverser* sink); - void getAllFilesAndDirs(const Zstring& sourceDir, std::vector<Zstring>& files, std::vector<Zstring>& directories) throw(FileError); + void traverseInDetail(const Zstring& directory, const bool traverseDirectorySymlinks, FullDetailFileTraverser* sink); + + bool fileExists(const Zstring& filename); //replaces wxFileExists()! //recycler bool recycleBinExists(); //test existence of Recycle Bin API on current system @@ -49,12 +50,17 @@ namespace FreeFileSync //file handling 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); + void createDirectory(const Zstring& directory, const Zstring& templateDir, const bool copyDirectorySymLinks); +#ifdef FFS_LINUX + //callback function for status updates whily copying + typedef void (*CopyFileCallback)(const wxULongLong& totalBytesTransferred, void* data); -#ifdef FFS_WIN - bool isFatDrive(const Zstring& directoryName); -#endif //FFS_WIN + void copyFile(const Zstring& sourceFile, + const Zstring& targetFile, + const bool copyFileSymLinks, + CopyFileCallback callback = NULL, + void* data = NULL); +#endif } diff --git a/library/globalFunctions.cpp b/library/globalFunctions.cpp index aeeeed45..4b387293 100644 --- a/library/globalFunctions.cpp +++ b/library/globalFunctions.cpp @@ -4,13 +4,6 @@ #include <wx/file.h> -inline -int globalFunctions::round(const double d) -{ - return static_cast<int>(d<0?d-.5:d+.5); -} - - std::string globalFunctions::numberToString(const unsigned int number) { char result[100]; @@ -88,11 +81,12 @@ double globalFunctions::wxStringToDouble(const wxString& number) } -wxString& globalFunctions::includeNumberSeparator(wxString& number) +wxString globalFunctions::includeNumberSeparator(const wxString& number) { - for (int i = number.size() - 3; i > 0; i-= 3) - number.insert(i, GlobalResources::THOUSANDS_SEPARATOR); - return number; + wxString output(number); + for (int i = output.size() - 3; i > 0; i -= 3) + output.insert(i, GlobalResources::THOUSANDS_SEPARATOR); + return output; } diff --git a/library/globalFunctions.h b/library/globalFunctions.h index b4ccba5c..98e8cd1c 100644 --- a/library/globalFunctions.h +++ b/library/globalFunctions.h @@ -13,12 +13,17 @@ namespace globalFunctions { - int round(double d); //little rounding function + inline + int round(double d) //little rounding function + { + return static_cast<int>(d < 0 ? d - .5 : d + .5); + } template <class T> - T abs(const T& d) //absolute value + inline + T abs(const T& d) //absolute value { - return(d<0?-d:d); + return(d < 0 ? -d : d); } std::string numberToString(const unsigned int number); //convert number to string @@ -35,7 +40,7 @@ namespace globalFunctions int wxStringToInt( const wxString& number); //convert wxString to number double wxStringToDouble(const wxString& number); //convert wxString to number - wxString& includeNumberSeparator(wxString& number); + wxString includeNumberSeparator(const wxString& number); int readInt(std::ifstream& stream); //read int from file stream void writeInt(std::ofstream& stream, const int number); //write int to filestream diff --git a/library/misc.cpp b/library/misc.cpp index 0b0bf60b..0d5761d6 100644 --- a/library/misc.cpp +++ b/library/misc.cpp @@ -74,6 +74,9 @@ void CustomLocale::setLanguage(const int language) case wxLANGUAGE_GERMAN: languageFile = "Languages/german.lng"; break; + case wxLANGUAGE_HUNGARIAN: + languageFile = "Languages/hungarian.lng"; + break; case wxLANGUAGE_ITALIAN: languageFile = "Languages/italian.lng"; break; @@ -86,6 +89,12 @@ void CustomLocale::setLanguage(const int language) case wxLANGUAGE_PORTUGUESE: languageFile = "Languages/portuguese.lng"; break; + case wxLANGUAGE_SLOVENIAN: + languageFile = "Languages/slovenian.lng"; + break; + case wxLANGUAGE_SPANISH: + languageFile = "Languages/spanish.lng"; + break; default: languageFile.clear(); currentLanguage = wxLANGUAGE_ENGLISH; @@ -153,7 +162,8 @@ const wxChar* CustomLocale::GetString(const wxChar* szOrigString, const wxChar* //look for translation in buffer table Translation::iterator i; if ((i = translationDB.find(currentLine)) != translationDB.end()) - return (i->translation.c_str()); + return i->translation.c_str(); + //fallback - return (szOrigString); + return szOrigString; } diff --git a/library/multithreading.cpp b/library/multithreading.cpp index f7f5ed04..106d1aa7 100644 --- a/library/multithreading.cpp +++ b/library/multithreading.cpp @@ -151,7 +151,7 @@ void UpdateWhileExecuting::execute(StatusHandler* statusUpdater) while (receivingResult.WaitTimeout(UI_UPDATE_INTERVAL) == wxCOND_TIMEOUT) { - statusUpdater->requestUiRefresh(true); //ATTENTION: Exception "AbortThisProcess" may be thrown here!!! + statusUpdater->requestUiRefresh(false); //don't allow throwing exception within this call if (workDone) //workaround for a bug in wxWidgets v2.8.9 class wxCondition: signals might get lost break; //no mutex for workDone needed here: it is changed only when mainthread is in WaitTimeout() diff --git a/library/processXml.cpp b/library/processXml.cpp index 84783452..00737633 100644 --- a/library/processXml.cpp +++ b/library/processXml.cpp @@ -4,6 +4,9 @@ #include <wx/intl.h> #include "globalFunctions.h" +#ifdef FFS_WIN +#include <wx/msw/wrapwin.h> //includes "windows.h" +#endif //small helper functions bool readXmlElementValue(std::string& output, const TiXmlElement* parent, const std::string& name); @@ -109,7 +112,7 @@ xmlAccess::XmlGuiConfig xmlAccess::readGuiConfig(const wxString& filename) { //load XML if (!wxFileExists(filename)) - throw FileError(Zstring(_("The file does not exist:")) + wxT(" \"") + filename.c_str() + wxT("\"")); + throw FileError(Zstring(_("File does not exist:")) + wxT(" \"") + filename.c_str() + wxT("\"")); XmlConfigInput inputFile(filename, XML_GUI_CONFIG); @@ -129,7 +132,7 @@ xmlAccess::XmlBatchConfig xmlAccess::readBatchConfig(const wxString& filename) { //load XML if (!wxFileExists(filename)) - throw FileError(Zstring(_("The file does not exist:")) + wxT(" \"") + filename.c_str() + wxT("\"")); + throw FileError(Zstring(_("File does not exist:")) + wxT(" \"") + filename.c_str() + wxT("\"")); XmlConfigInput inputFile(filename, XML_BATCH_CONFIG); @@ -149,7 +152,7 @@ xmlAccess::XmlGlobalSettings xmlAccess::readGlobalSettings() { //load XML if (!wxFileExists(FreeFileSync::GLOBAL_CONFIG_FILE)) - throw FileError(Zstring(_("The file does not exist:")) + wxT(" \"") + FreeFileSync::GLOBAL_CONFIG_FILE.c_str() + wxT("\"")); + throw FileError(Zstring(_("File does not exist:")) + wxT(" \"") + FreeFileSync::GLOBAL_CONFIG_FILE.c_str() + wxT("\"")); XmlConfigInput inputFile(FreeFileSync::GLOBAL_CONFIG_FILE, XML_GLOBAL_SETTINGS); @@ -334,6 +337,29 @@ bool readXmlElementValue(xmlAccess::OnError& output, const TiXmlElement* parent, } +void readXmlElementTable(std::vector<wxString>& output, const TiXmlElement* parent, const std::string& name) +{ + output.clear(); + + if (parent) + { + //load elements + const TiXmlElement* element = parent->FirstChildElement(name); + while (element) + { + const char* text = element->GetText(); + if (text) //may be NULL!! + output.push_back(wxString::FromUTF8(text)); + else + break; + element = element->NextSiblingElement(); + } + } +} + +//################################################################################################################ + + bool XmlConfigInput::readXmlMainConfig(MainConfiguration& mainCfg, std::vector<FolderPair>& directoryPairs) { TiXmlElement* root = doc.RootElement(); @@ -444,6 +470,11 @@ bool XmlConfigInput::readXmlBatchConfig(xmlAccess::XmlBatchConfig& outputCfg) if (batchConfig) { readXmlElementValue(outputCfg.silent, batchConfig, "Silent"); + + std::string tempString; + if (readXmlElementValue(tempString, batchConfig, "LogfileDirectory")) + outputCfg.logFileDirectory = wxString::FromUTF8(tempString.c_str()); + readXmlElementValue(outputCfg.handleError, batchConfig, "HandleError"); } @@ -465,13 +496,16 @@ bool XmlConfigInput::readXmlGlobalSettings(xmlAccess::XmlGlobalSettings& outputC //program language readXmlElementValue(outputCfg.shared.programLanguage, global, "Language"); + //max. allowed file time deviation + int dummy = 0; + if (readXmlElementValue(dummy, global, "FileTimeTolerance")) + outputCfg.shared.fileTimeTolerance = dummy; + //traverse into symbolic links (to folders) - readXmlElementValue(outputCfg.shared.traverseSymbolicLinks, global, "TraverseSymbolicLinks"); + readXmlElementValue(outputCfg.shared.traverseDirectorySymlinks, global, "TraverseDirectorySymlinks"); -#ifdef FFS_WIN - //daylight saving time check - readXmlElementValue(outputCfg.shared.handleDstOnFat32, global, "HandleDaylightSavingTimeOnFAT"); -#endif + //copy symbolic links to files + readXmlElementValue(outputCfg.shared.copyFileSymlinks, global, "CopyFileSymlinks"); } TiXmlElement* warnings = hRoot.FirstChild("Shared").FirstChild("Warnings").ToElement(); @@ -497,6 +531,7 @@ bool XmlConfigInput::readXmlGlobalSettings(xmlAccess::XmlGlobalSettings& outputC readXmlElementValue(outputCfg.gui.deleteOnBothSides, mainWindow, "ManualDeletionOnBothSides"); readXmlElementValue(outputCfg.gui.useRecyclerForManualDeletion, mainWindow, "ManualDeletionUseRecycler"); + readXmlElementValue(outputCfg.gui.showFileIcons, mainWindow, "ShowFileIcons"); //########################################################### //read column attributes @@ -547,19 +582,26 @@ bool XmlConfigInput::readXmlGlobalSettings(xmlAccess::XmlGlobalSettings& outputC rightColumn = rightColumn->NextSiblingElement(); ++colType; } + + //load folder history elements + const TiXmlElement* historyLeft = TiXmlHandle(mainWindow).FirstChild("FolderHistoryLeft").ToElement(); + const TiXmlElement* historyRight = TiXmlHandle(mainWindow).FirstChild("FolderHistoryRight").ToElement(); + + readXmlElementTable(outputCfg.gui.folderHistoryLeft, historyLeft, "Folder"); + readXmlElementTable(outputCfg.gui.folderHistoryRight, historyRight, "Folder"); } TiXmlElement* gui = hRoot.FirstChild("Gui").ToElement(); if (gui) { //commandline for file manager integration - std::string tempString; + std::string tempString; if (readXmlElementValue(tempString, gui, "FileManager")) outputCfg.gui.commandLineFileManager = wxString::FromUTF8(tempString.c_str()); } //load config file history - TiXmlElement* cfgHistory = hRoot.FirstChild("Gui").FirstChild("History").ToElement(); + TiXmlElement* cfgHistory = hRoot.FirstChild("Gui").FirstChild("ConfigHistory").ToElement(); if (cfgHistory) { //load max. history size @@ -567,17 +609,8 @@ bool XmlConfigInput::readXmlGlobalSettings(xmlAccess::XmlGlobalSettings& outputC if (histSizeMax) //may be NULL! outputCfg.gui.cfgHistoryMaxItems = globalFunctions::stringToInt(histSizeMax); - //load history elements - TiXmlElement* cfgFile = TiXmlHandle(cfgHistory).FirstChild("File").ToElement(); - while (cfgFile) - { - const char* fileName = cfgFile->GetText(); - if (fileName) //may be NULL!! - outputCfg.gui.cfgFileHistory.push_back(wxString::FromUTF8(fileName)); - else - break; - cfgFile = cfgFile->NextSiblingElement(); - } + //load config history elements + readXmlElementTable(outputCfg.gui.cfgFileHistory, cfgHistory, "File"); } @@ -616,7 +649,7 @@ XmlConfigOutput::XmlConfigOutput(const wxString& fileName, const xmlAccess::XmlT bool XmlConfigOutput::writeToFile() { //workaround to get a FILE* from a unicode filename - wxFFile dummyFile(m_fileName, wxT("wb")); + wxFFile dummyFile(m_fileName, wxT("wb")); //save in binary mode for Linux portability of config files if (!dummyFile.IsOpened()) return false; @@ -678,6 +711,13 @@ void addXmlElement(TiXmlElement* parent, const std::string& name, const xmlAcces } +void addXmlElementTable(TiXmlElement* parent, const std::string& name, const std::vector<wxString>& input) +{ + for (std::vector<wxString>::const_iterator i = input.begin(); i != input.end(); ++i) + addXmlElement(parent, name, std::string(i->ToUTF8())); +} + + bool XmlConfigOutput::writeXmlMainConfig(const MainConfiguration& mainCfg, const std::vector<FolderPair>& directoryPairs) { TiXmlElement* root = doc.RootElement(); @@ -741,10 +781,10 @@ bool XmlConfigOutput::writeXmlMainConfig(const MainConfiguration& mainCfg, const } -bool XmlConfigOutput::writeXmlGuiConfig(const xmlAccess::XmlGuiConfig& outputCfg) +bool XmlConfigOutput::writeXmlGuiConfig(const xmlAccess::XmlGuiConfig& inputCfg) { //write main config - if (!writeXmlMainConfig(outputCfg.mainCfg, outputCfg.directoryPairs)) + if (!writeXmlMainConfig(inputCfg.mainCfg, inputCfg.directoryPairs)) return false; //write GUI specific config @@ -760,18 +800,18 @@ bool XmlConfigOutput::writeXmlGuiConfig(const xmlAccess::XmlGuiConfig& outputCfg TiXmlElement* mainWindow = new TiXmlElement("Main"); windows->LinkEndChild(mainWindow); - addXmlElement(mainWindow, "HideFiltered", outputCfg.hideFilteredElements); + addXmlElement(mainWindow, "HideFiltered", inputCfg.hideFilteredElements); - addXmlElement(guiConfig, "IgnoreErrors", outputCfg.ignoreErrors); + addXmlElement(guiConfig, "IgnoreErrors", inputCfg.ignoreErrors); return true; } -bool XmlConfigOutput::writeXmlBatchConfig(const xmlAccess::XmlBatchConfig& outputCfg) +bool XmlConfigOutput::writeXmlBatchConfig(const xmlAccess::XmlBatchConfig& inputCfg) { //write main config - if (!writeXmlMainConfig(outputCfg.mainCfg, outputCfg.directoryPairs)) + if (!writeXmlMainConfig(inputCfg.mainCfg, inputCfg.directoryPairs)) return false; //write GUI specific config @@ -781,14 +821,15 @@ bool XmlConfigOutput::writeXmlBatchConfig(const xmlAccess::XmlBatchConfig& outpu TiXmlElement* batchConfig = new TiXmlElement("BatchConfig"); root->LinkEndChild(batchConfig); - addXmlElement(batchConfig, "Silent", outputCfg.silent); - addXmlElement(batchConfig, "HandleError", outputCfg.handleError); + addXmlElement(batchConfig, "Silent", inputCfg.silent); + addXmlElement(batchConfig, "LogfileDirectory", std::string(inputCfg.logFileDirectory.ToUTF8())); + addXmlElement(batchConfig, "HandleError", inputCfg.handleError); return true; } -bool XmlConfigOutput::writeXmlGlobalSettings(const xmlAccess::XmlGlobalSettings& outputCfg) +bool XmlConfigOutput::writeXmlGlobalSettings(const xmlAccess::XmlGlobalSettings& inputCfg) { TiXmlElement* root = doc.RootElement(); if (!root) return false; @@ -799,25 +840,26 @@ bool XmlConfigOutput::writeXmlGlobalSettings(const xmlAccess::XmlGlobalSettings& root->LinkEndChild(global); //program language - addXmlElement(global, "Language", outputCfg.shared.programLanguage); + addXmlElement(global, "Language", inputCfg.shared.programLanguage); -#ifdef FFS_WIN - //daylight saving time check - addXmlElement(global, "HandleDaylightSavingTimeOnFAT", outputCfg.shared.handleDstOnFat32); -#endif + //max. allowed file time deviation + addXmlElement(global, "FileTimeTolerance", int(inputCfg.shared.fileTimeTolerance)); //traverse into symbolic links (to folders) - addXmlElement(global, "TraverseSymbolicLinks", outputCfg.shared.traverseSymbolicLinks); + addXmlElement(global, "TraverseDirectorySymlinks", inputCfg.shared.traverseDirectorySymlinks); + + //copy symbolic links to files + addXmlElement(global, "CopyFileSymlinks", inputCfg.shared.copyFileSymlinks); //warnings TiXmlElement* warnings = new TiXmlElement("Warnings"); global->LinkEndChild(warnings); //warning: dependent folders - addXmlElement(warnings, "CheckForDependentFolders", outputCfg.shared.warningDependentFolders); + addXmlElement(warnings, "CheckForDependentFolders", inputCfg.shared.warningDependentFolders); //significant difference check - addXmlElement(warnings, "CheckForSignificantDifference", outputCfg.shared.warningSignificantDifference); + addXmlElement(warnings, "CheckForSignificantDifference", inputCfg.shared.warningSignificantDifference); //################################################################### @@ -832,21 +874,22 @@ bool XmlConfigOutput::writeXmlGlobalSettings(const xmlAccess::XmlGlobalSettings& windows->LinkEndChild(mainWindow); //window size - addXmlElement(mainWindow, "Width", outputCfg.gui.widthNotMaximized); - addXmlElement(mainWindow, "Height", outputCfg.gui.heightNotMaximized); + addXmlElement(mainWindow, "Width", inputCfg.gui.widthNotMaximized); + addXmlElement(mainWindow, "Height", inputCfg.gui.heightNotMaximized); //window position - addXmlElement(mainWindow, "PosX", outputCfg.gui.posXNotMaximized); - addXmlElement(mainWindow, "PosY", outputCfg.gui.posYNotMaximized); - addXmlElement(mainWindow, "Maximized", outputCfg.gui.isMaximized); + addXmlElement(mainWindow, "PosX", inputCfg.gui.posXNotMaximized); + addXmlElement(mainWindow, "PosY", inputCfg.gui.posYNotMaximized); + addXmlElement(mainWindow, "Maximized", inputCfg.gui.isMaximized); - addXmlElement(mainWindow, "ManualDeletionOnBothSides", outputCfg.gui.deleteOnBothSides); - addXmlElement(mainWindow, "ManualDeletionUseRecycler", outputCfg.gui.useRecyclerForManualDeletion); + addXmlElement(mainWindow, "ManualDeletionOnBothSides", inputCfg.gui.deleteOnBothSides); + addXmlElement(mainWindow, "ManualDeletionUseRecycler", inputCfg.gui.useRecyclerForManualDeletion); + addXmlElement(mainWindow, "ShowFileIcons", inputCfg.gui.showFileIcons); //write column attributes TiXmlElement* leftColumn = new TiXmlElement("LeftColumns"); mainWindow->LinkEndChild(leftColumn); - xmlAccess::ColumnAttributes columnAtrribLeftCopy = outputCfg.gui.columnAttribLeft; //can't change const vector + xmlAccess::ColumnAttributes columnAtrribLeftCopy = inputCfg.gui.columnAttribLeft; //can't change const vector sort(columnAtrribLeftCopy.begin(), columnAtrribLeftCopy.end(), xmlAccess::sortByType); for (unsigned int i = 0; i < columnAtrribLeftCopy.size(); ++i) { @@ -862,7 +905,7 @@ bool XmlConfigOutput::writeXmlGlobalSettings(const xmlAccess::XmlGlobalSettings& TiXmlElement* rightColumn = new TiXmlElement("RightColumns"); mainWindow->LinkEndChild(rightColumn); - xmlAccess::ColumnAttributes columnAtrribRightCopy = outputCfg.gui.columnAttribRight; + xmlAccess::ColumnAttributes columnAtrribRightCopy = inputCfg.gui.columnAttribRight; sort(columnAtrribRightCopy.begin(), columnAtrribRightCopy.end(), xmlAccess::sortByType); for (unsigned int i = 0; i < columnAtrribRightCopy.size(); ++i) { @@ -876,17 +919,25 @@ bool XmlConfigOutput::writeXmlGlobalSettings(const xmlAccess::XmlGlobalSettings& subElement->SetAttribute("Width", colAttrib.width); } + //write folder history elements + TiXmlElement* historyLeft = new TiXmlElement("FolderHistoryLeft"); + mainWindow->LinkEndChild(historyLeft); + TiXmlElement* historyRight = new TiXmlElement("FolderHistoryRight"); + mainWindow->LinkEndChild(historyRight); + + addXmlElementTable(historyLeft, "Folder", inputCfg.gui.folderHistoryLeft); + addXmlElementTable(historyRight, "Folder", inputCfg.gui.folderHistoryRight); + + //commandline for file manager integration - addXmlElement(gui, "FileManager", std::string((outputCfg.gui.commandLineFileManager).ToUTF8())); + addXmlElement(gui, "FileManager", std::string((inputCfg.gui.commandLineFileManager).ToUTF8())); //write config file history - TiXmlElement* cfgHistory = new TiXmlElement("History"); + TiXmlElement* cfgHistory = new TiXmlElement("ConfigHistory"); gui->LinkEndChild(cfgHistory); - cfgHistory->SetAttribute("MaximumSize", globalFunctions::numberToString(outputCfg.gui.cfgHistoryMaxItems)); - - for (unsigned int i = 0; i < outputCfg.gui.cfgFileHistory.size(); ++i) - addXmlElement(cfgHistory, "File", std::string(outputCfg.gui.cfgFileHistory[i].ToUTF8())); + cfgHistory->SetAttribute("MaximumSize", globalFunctions::numberToString(inputCfg.gui.cfgHistoryMaxItems)); + addXmlElementTable(cfgHistory, "File", inputCfg.gui.cfgFileHistory); //################################################################### //write global batch settings @@ -941,8 +992,33 @@ int xmlAccess::retrieveSystemLanguage() case wxLANGUAGE_PORTUGUESE_BRAZILIAN: return wxLANGUAGE_PORTUGUESE; + //variants of wxLANGUAGE_SPANISH + case wxLANGUAGE_SPANISH_ARGENTINA: + case wxLANGUAGE_SPANISH_BOLIVIA: + case wxLANGUAGE_SPANISH_CHILE: + case wxLANGUAGE_SPANISH_COLOMBIA: + case wxLANGUAGE_SPANISH_COSTA_RICA: + case wxLANGUAGE_SPANISH_DOMINICAN_REPUBLIC: + case wxLANGUAGE_SPANISH_ECUADOR: + case wxLANGUAGE_SPANISH_EL_SALVADOR: + case wxLANGUAGE_SPANISH_GUATEMALA: + case wxLANGUAGE_SPANISH_HONDURAS: + case wxLANGUAGE_SPANISH_MEXICAN: + case wxLANGUAGE_SPANISH_MODERN: + case wxLANGUAGE_SPANISH_NICARAGUA: + case wxLANGUAGE_SPANISH_PANAMA: + case wxLANGUAGE_SPANISH_PARAGUAY: + case wxLANGUAGE_SPANISH_PERU: + case wxLANGUAGE_SPANISH_PUERTO_RICO: + case wxLANGUAGE_SPANISH_URUGUAY: + case wxLANGUAGE_SPANISH_US: + case wxLANGUAGE_SPANISH_VENEZUELA: + return wxLANGUAGE_SPANISH; + //case wxLANGUAGE_JAPANESE: //case wxLANGUAGE_POLISH: + //case wxLANGUAGE_SLOVENIAN: + //case wxLANGUAGE_HUNGARIAN: default: return lang; @@ -950,6 +1026,24 @@ int xmlAccess::retrieveSystemLanguage() } +bool xmlAccess::supportForSymbolicLinks() +{ +#ifdef FFS_WIN + OSVERSIONINFO osvi; + ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + + //symbolic links are supported starting with Vista + if (GetVersionEx(&osvi)) + return osvi.dwMajorVersion > 5; //XP has majorVersion == 5, minorVersion == 1, Vista majorVersion == 6 + + return false; +#elif defined FFS_LINUX + return true; +#endif +} + + void xmlAccess::XmlGlobalSettings::_Shared::resetWarnings() { warningDependentFolders = true; diff --git a/library/processXml.h b/library/processXml.h index 7b838958..c24ad0e7 100644 --- a/library/processXml.h +++ b/library/processXml.h @@ -66,10 +66,12 @@ namespace xmlAccess std::vector<FolderPair> directoryPairs; bool silent; - OnError handleError; //reaction on error situation during synchronization + OnError handleError; //reaction on error situation during synchronization + wxString logFileDirectory; // }; int retrieveSystemLanguage(); + bool supportForSymbolicLinks(); struct XmlGlobalSettings @@ -79,19 +81,17 @@ namespace xmlAccess { _Shared() : programLanguage(retrieveSystemLanguage()), -#ifdef FFS_WIN - handleDstOnFat32(true), -#endif - traverseSymbolicLinks(false) + fileTimeTolerance(2), //default 2s: FAT vs NTFS + traverseDirectorySymlinks(false), + copyFileSymlinks(supportForSymbolicLinks()) { resetWarnings(); } int programLanguage; -#ifdef FFS_WIN - bool handleDstOnFat32; -#endif - bool traverseSymbolicLinks; + unsigned fileTimeTolerance; //max. allowed file time deviation + bool traverseDirectorySymlinks; + bool copyFileSymlinks; //copy symbolic link instead of target file //warnings void resetWarnings(); @@ -116,8 +116,8 @@ namespace xmlAccess #endif cfgHistoryMaxItems(10), deleteOnBothSides(false), - useRecyclerForManualDeletion(FreeFileSync::recycleBinExists()) //enable if OS supports it; else user will have to activate first and then get an error message - {} + useRecyclerForManualDeletion(FreeFileSync::recycleBinExists()), //enable if OS supports it; else user will have to activate first and then get an error message + showFileIcons(true) {} int widthNotMaximized; int heightNotMaximized; @@ -130,8 +130,11 @@ namespace xmlAccess wxString commandLineFileManager; std::vector<wxString> cfgFileHistory; unsigned cfgHistoryMaxItems; + std::vector<wxString> folderHistoryLeft; + std::vector<wxString> folderHistoryRight; bool deleteOnBothSides; bool useRecyclerForManualDeletion; + bool showFileIcons; } gui; //--------------------------------------------------------------------- diff --git a/library/resources.cpp b/library/resources.cpp index 4a9d0369..f8624ed3 100644 --- a/library/resources.cpp +++ b/library/resources.cpp @@ -34,6 +34,7 @@ GlobalResources::GlobalResources() bitmapResource[wxT("right delete.png")] = (bitmapDeleteRight = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("email.png")] = (bitmapEmail = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("about.png")] = (bitmapAbout = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("about_small.png")] = (bitmapAboutSmall = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("website.png")] = (bitmapWebsite = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("exit.png")] = (bitmapExit = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("sync.png")] = (bitmapSync = new wxBitmap(wxNullBitmap)); @@ -102,6 +103,8 @@ GlobalResources::GlobalResources() bitmapResource[wxT("checkbox false.png")] = (bitmapCheckBoxFalse = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("settings.png")] = (bitmapSettings = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("settings_small.png")] = (bitmapSettingsSmall = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("recycler.png")] = (bitmapRecycler = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("shift.png")] = (bitmapShift = new wxBitmap(wxNullBitmap)); //init all the other resource files animationMoney = new wxAnimation(wxNullAnimation); diff --git a/library/resources.h b/library/resources.h index 24e8d027..6bac0d86 100644 --- a/library/resources.h +++ b/library/resources.h @@ -33,6 +33,7 @@ public: wxBitmap* bitmapDeleteRight; wxBitmap* bitmapEmail; wxBitmap* bitmapAbout; + wxBitmap* bitmapAboutSmall; wxBitmap* bitmapWebsite; wxBitmap* bitmapExit; wxBitmap* bitmapSync; @@ -101,6 +102,8 @@ public: wxBitmap* bitmapCheckBoxFalse; wxBitmap* bitmapSettings; wxBitmap* bitmapSettingsSmall; + wxBitmap* bitmapRecycler; + wxBitmap* bitmapShift; wxAnimation* animationMoney; wxAnimation* animationSync; diff --git a/library/sorting.h b/library/sorting.h index 23c219b9..171cca6d 100644 --- a/library/sorting.h +++ b/library/sorting.h @@ -124,11 +124,11 @@ bool sortByFileName(const FileCompareLine& a, const FileCompareLine& b) const wxChar* stringA = descrLineA->relativeName.c_str(); const wxChar* stringB = descrLineB->relativeName.c_str(); - size_t pos = descrLineA->relativeName.Find(GlobalResources::FILE_NAME_SEPARATOR, true); //start search beginning from end + size_t pos = descrLineA->relativeName.findFromEnd(GlobalResources::FILE_NAME_SEPARATOR); //start search beginning from end if (pos != std::string::npos) stringA += pos + 1; - pos = descrLineB->relativeName.Find(GlobalResources::FILE_NAME_SEPARATOR, true); //start search beginning from end + pos = descrLineB->relativeName.findFromEnd(GlobalResources::FILE_NAME_SEPARATOR); //start search beginning from end if (pos != std::string::npos) stringB += pos + 1; @@ -155,7 +155,7 @@ bool sortByRelativeName(const FileCompareLine& a, const FileCompareLine& b) relLengthA = descrLineA->relativeName.length(); else if (descrLineA->objType == FileDescrLine::TYPE_FILE) { - relLengthA = descrLineA->relativeName.Find(GlobalResources::FILE_NAME_SEPARATOR, true); //start search beginning from end + relLengthA = descrLineA->relativeName.findFromEnd(GlobalResources::FILE_NAME_SEPARATOR); //start search beginning from end if (relLengthA == wxNOT_FOUND) { relLengthA = 0; @@ -180,7 +180,7 @@ bool sortByRelativeName(const FileCompareLine& a, const FileCompareLine& b) relLengthB = descrLineB->relativeName.length(); else if (descrLineB->objType == FileDescrLine::TYPE_FILE) { - relLengthB = descrLineB->relativeName.Find(GlobalResources::FILE_NAME_SEPARATOR, true); //start search beginning from end + relLengthB = descrLineB->relativeName.findFromEnd(GlobalResources::FILE_NAME_SEPARATOR); //start search beginning from end if (relLengthB == wxNOT_FOUND) { relLengthB = 0; diff --git a/library/statusHandler.h b/library/statusHandler.h index 208a2df8..11517efb 100644 --- a/library/statusHandler.h +++ b/library/statusHandler.h @@ -50,19 +50,11 @@ public: //this method is triggered repeatedly by requestUiRefresh() and can be used to refresh the ui by dispatching pending events virtual void forceUiRefresh() = 0; - void requestUiRefresh(bool asyncProcessActive = false) - { - if (updateUiIsAllowed()) //test if specific time span between ui updates is over - forceUiRefresh(); - if (abortRequested && !asyncProcessActive) - abortThisProcess(); //abort can be triggered by requestAbortion() - } + void requestUiRefresh(bool allowAbort = true); //opportunity to abort must be implemented in a frequently executed method like requestUiRefresh() + void requestAbortion(); //does NOT call abortThisProcess immediately, but when appropriate (e.g. async. processes finished) + bool abortIsRequested(); - void requestAbortion() //opportunity to abort must be implemented in a frequently executed method like requestUiRefresh() - { //currently used by the UI status information screen, when button "Abort is pressed" - abortRequested = true; - } //error handling: virtual ErrorHandler::Response reportError(const Zstring& errorMessage) = 0; //recoverable error situation @@ -76,4 +68,31 @@ protected: }; + +//############################################################################## +inline +void StatusHandler::requestUiRefresh(bool allowAbort) +{ + if (updateUiIsAllowed()) //test if specific time span between ui updates is over + forceUiRefresh(); + + if (abortRequested && allowAbort) + abortThisProcess(); //abort can be triggered by requestAbortion() +} + + +inline +void StatusHandler::requestAbortion() +{ + abortRequested = true; +} + + +inline +bool StatusHandler::abortIsRequested() +{ + return abortRequested; +} + + #endif // STATUSHANDLER_H_INCLUDED diff --git a/library/tinyxml/tinyxmlparser.cpp b/library/tinyxml/tinyxmlparser.cpp index 2f6c0bc1..539f72cf 100644 --- a/library/tinyxml/tinyxmlparser.cpp +++ b/library/tinyxml/tinyxmlparser.cpp @@ -368,7 +368,7 @@ const char* TiXmlBase::SkipWhiteSpace( const char* p, TiXmlEncoding encoding ) } else { - while ( *p && IsWhiteSpace( *p ) || *p == '\n' || *p =='\r' ) + while ( (*p && IsWhiteSpace( *p )) || *p == '\n' || *p =='\r' ) ++p; } diff --git a/library/zstring.cpp b/library/zstring.cpp index 27633392..d704e741 100644 --- a/library/zstring.cpp +++ b/library/zstring.cpp @@ -104,7 +104,6 @@ bool matchesHelper(const DefaultChar* string, const DefaultChar* mask) ++string; } return false; - break; default: if (*string != ch) @@ -141,8 +140,11 @@ Zstring& Zstring::Trim(bool fromRight) { if (descr->refCount > 1) //allocate new string *this = Zstring(data, newLength); - else //overwrite this string - descr->length = newLength; + else //overwrite this strin + { + descr->length = newLength; + data[newLength] = DefaultChar(0); + } } } else @@ -180,8 +182,7 @@ Zstring& Zstring::MakeLower() { StringDescriptor* newDescr; DefaultChar* newData; - const size_t newCapacity = getCapacityToAllocate(thisLen); - allocate(1, thisLen, newCapacity, newDescr, newData); + allocate(thisLen, newDescr, newData); for (unsigned int i = 0; i < thisLen; ++i) newData[i] = defaultToLower(data[i]); @@ -244,47 +245,41 @@ Zstring& Zstring::replace(size_t pos1, size_t n1, const DefaultChar* str, size_t 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) { - const size_t oldLen = length(); - if (oldLen == 0) - { - assert(n1 == 0); - return *this = Zstring(str, n2); - } + assert(pos1 == 0 && 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; - } + const size_t newLen = oldLen - n1 + n2; + if (n1 < n2 || descr->refCount > 1) + { //allocate a new string + StringDescriptor* newDescr; + DefaultChar* newData; + allocate(newLen, newDescr, newData); + + //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: case "n2 == 0" is handled implicitly + { + 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; } @@ -358,16 +353,16 @@ void Zstring::copyBeforeWrite(const size_t capacityNeeded) if (descr->refCount > 1) { //allocate a new string - const size_t oldLength = length(); - const size_t newCapacity = getCapacityToAllocate(capacityNeeded); + const size_t oldLength = length(); + assert(oldLength <= getCapacityToAllocate(capacityNeeded)); StringDescriptor* newDescr; - DefaultChar* newData; - allocate(1, oldLength, newCapacity, newDescr, newData); + DefaultChar* newData; + allocate(capacityNeeded, newDescr, newData); + newDescr->length = oldLength; if (oldLength) { - assert(oldLength <= newCapacity); memcpy(newData, data, oldLength * sizeof(DefaultChar)); newData[oldLength] = 0; } diff --git a/library/zstring.h b/library/zstring.h index 60ba464c..2a10efa9 100644 --- a/library/zstring.h +++ b/library/zstring.h @@ -7,7 +7,11 @@ #ifndef ZSTRING_H_INCLUDED #define ZSTRING_H_INCLUDED -#include <string> +#include <cstring> +#include <cctype> +#include <assert.h> +#include <new> +#include <cstdlib> namespace FreeFileSync @@ -33,6 +37,7 @@ typedef char DefaultChar; typedef wchar_t DefaultChar; #endif +class Zsubstr; class Zstring { @@ -67,7 +72,8 @@ public: //std::string functions size_t length() const; const DefaultChar* c_str() const; - Zstring substr(size_t pos = 0, size_t len = npos) const; + Zstring substr(size_t pos = 0, size_t len = npos) const; //allocate new string + Zsubstr zsubstr(size_t pos = 0) const; //use ref-counting! bool empty() const; int compare(const DefaultChar* other) const; int compare(const Zstring& other) const; @@ -106,18 +112,35 @@ private: struct StringDescriptor { - StringDescriptor(const unsigned int refC, const size_t len, const size_t cap) : refCount(refC), length(len), capacity(cap) {} mutable unsigned int refCount; - size_t length; - size_t capacity; //allocated length without null-termination + 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); + static void allocate(const size_t newLength, Zstring::StringDescriptor*& newDescr, DefaultChar*& newData); StringDescriptor* descr; DefaultChar* data; }; +class Zsubstr //ref-counted substring of a Zstring +{ +public: + Zsubstr(); + Zsubstr(const Zstring& ref, const size_t pos); + + const DefaultChar* c_str() const; + size_t length() const; + bool StartsWith(const Zstring& begin) const; + size_t findFromEnd(const DefaultChar ch) const; + +private: + Zstring m_ref; + const DefaultChar* m_data; + size_t m_length; +}; + + //####################################################################################### //begin of implementation @@ -218,28 +241,26 @@ void testZstringForMemoryLeak(); inline -void Zstring::allocate(const unsigned int newRefCount, - const size_t newLength, - const size_t newCapacity, +size_t getCapacityToAllocate(const size_t length) +{ + return (length + (19 - length % 16)); //allocate some additional length to speed up concatenation +} + + +inline +void Zstring::allocate(const size_t newLength, 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(); - newData = (DefaultChar*)(newDescr + 1); - } - else - { - newDescr = (StringDescriptor*) malloc( sizeof(StringDescriptor)); - if (newDescr == NULL) - throw std::bad_alloc(); - newData = NULL; - } + const size_t newCapacity = getCapacityToAllocate(newLength); + assert(newCapacity); + + newDescr = (StringDescriptor*) malloc( sizeof(StringDescriptor) + (newCapacity + 1) * sizeof(DefaultChar)); + if (newDescr == NULL) + throw std::bad_alloc(); + newData = (DefaultChar*)(newDescr + 1); - newDescr->refCount = newRefCount; + newDescr->refCount = 1; newDescr->length = newLength; newDescr->capacity = newCapacity; @@ -259,7 +280,16 @@ void Zstring::allocate(const unsigned int newRefCount, inline Zstring::Zstring() { - allocate(1, 0, 0, descr, data); + //static (dummy) empty Zstring +#ifdef ZSTRING_CHAR + static Zstring emptyString(""); +#elif defined ZSTRING_WIDE_CHAR + static Zstring emptyString(L""); +#endif + + emptyString.incRef(); //implicitly handle case "this == &source" and avoid this check + descr = emptyString.descr; + data = emptyString.data; } @@ -281,7 +311,7 @@ inline Zstring::Zstring(const Zstring& source) { descr = source.descr; - data = source.data; + data = source.data; incRef(); //reference counting! } @@ -294,17 +324,9 @@ Zstring::~Zstring() 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); + allocate(length, descr, data); memcpy(data, source, length * sizeof(DefaultChar)); data[length] = 0; } @@ -548,21 +570,14 @@ size_t Zstring::size() const inline const DefaultChar* Zstring::c_str() const { - if (length()) - return data; - else -#ifdef ZSTRING_CHAR - return ""; -#elif defined ZSTRING_WIDE_CHAR - return L""; -#endif + return data; } inline bool Zstring::empty() const { - return length() == 0; + return descr->length == 0; } @@ -594,5 +609,71 @@ Zstring Zstring::operator+(const DefaultChar ch) const return Zstring(*this)+=ch; } +//##################### Zsubstr ############################# +inline +Zsubstr Zstring::zsubstr(size_t pos) const +{ + assert(pos <= length()); + return Zsubstr(*this, pos); //return reference counted string +} + + +inline +Zsubstr::Zsubstr() +{ + m_data = m_ref.c_str(); + m_length = 0; +} + + +inline +Zsubstr::Zsubstr(const Zstring& ref, const size_t pos) : + m_ref(ref), + m_data(ref.c_str() + pos), + m_length(ref.length() - pos) {} + + +inline +const DefaultChar* Zsubstr::c_str() const +{ + return m_data; +} + + +inline +size_t Zsubstr::length() const +{ + return m_length; +} + + +inline +bool Zsubstr::StartsWith(const Zstring& begin) const +{ + const size_t beginLength = begin.length(); + if (length() < beginLength) + return false; + + return defaultCompare(m_data, begin.c_str(), beginLength) == 0; +} + + +inline +size_t Zsubstr::findFromEnd(const DefaultChar ch) const +{ + if (length() == 0) + return Zstring::npos; + + size_t pos = length() - 1; + do //pos points to last char of the string + { + if (m_data[pos] == ch) + return pos; + } + while (--pos != static_cast<size_t>(-1)); + return Zstring::npos; +} + + #endif // ZSTRING_H_INCLUDED |