summaryrefslogtreecommitdiff
path: root/library
diff options
context:
space:
mode:
Diffstat (limited to 'library')
-rw-r--r--library/CustomGrid.cpp542
-rw-r--r--library/CustomGrid.h41
-rw-r--r--library/fileHandling.cpp802
-rw-r--r--library/fileHandling.h22
-rw-r--r--library/globalFunctions.cpp16
-rw-r--r--library/globalFunctions.h13
-rw-r--r--library/misc.cpp14
-rw-r--r--library/multithreading.cpp2
-rw-r--r--library/processXml.cpp202
-rw-r--r--library/processXml.h25
-rw-r--r--library/resources.cpp3
-rw-r--r--library/resources.h3
-rw-r--r--library/sorting.h8
-rw-r--r--library/statusHandler.h41
-rw-r--r--library/tinyxml/tinyxmlparser.cpp2
-rw-r--r--library/zstring.cpp89
-rw-r--r--library/zstring.h169
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
bgstack15