From 4046be06720932a57a0f49416b0144b2858824d0 Mon Sep 17 00:00:00 2001 From: Daniel Wilhelm Date: Fri, 18 Apr 2014 16:59:06 +0200 Subject: 2.0 --- library/CustomGrid.cpp | 949 +++++++++++++++++++++--------------- library/CustomGrid.h | 150 ++++-- library/ShadowCopy/ShadowDll.vcproj | 219 +++++++++ library/ShadowCopy/dllmain.cpp | 22 + library/ShadowCopy/shadow.cpp | 178 +++++++ library/ShadowCopy/shadow.h | 33 ++ library/fileError.h | 25 + library/fileHandling.cpp | 203 ++++++-- library/fileHandling.h | 45 +- library/filter.cpp | 97 ++-- library/filter.h | 6 +- library/globalFunctions.cpp | 49 +- library/globalFunctions.h | 88 ++-- library/iconBuffer.cpp | 298 +++++++++++ library/iconBuffer.h | 51 ++ library/localization.cpp | 208 +++++--- library/localization.h | 52 +- library/pch.h | 23 +- library/processXml.cpp | 117 +++-- library/processXml.h | 81 +-- library/resources.cpp | 34 +- library/resources.h | 32 +- library/shadow.cpp | 148 ++++++ library/shadow.h | 31 ++ library/statistics.cpp | 17 +- library/statistics.h | 13 +- library/zstring.cpp | 6 +- library/zstring.h | 60 +-- 28 files changed, 2355 insertions(+), 880 deletions(-) create mode 100644 library/ShadowCopy/ShadowDll.vcproj create mode 100644 library/ShadowCopy/dllmain.cpp create mode 100644 library/ShadowCopy/shadow.cpp create mode 100644 library/ShadowCopy/shadow.h create mode 100644 library/fileError.h create mode 100644 library/iconBuffer.cpp create mode 100644 library/iconBuffer.h create mode 100644 library/shadow.cpp create mode 100644 library/shadow.h (limited to 'library') diff --git a/library/CustomGrid.cpp b/library/CustomGrid.cpp index 048bc5e0..591230e2 100644 --- a/library/CustomGrid.cpp +++ b/library/CustomGrid.cpp @@ -6,16 +6,19 @@ #include "resources.h" #include #include "../ui/gridView.h" +#include "../synchronization.h" #ifdef FFS_WIN +#include #include -#include //includes "windows.h" +#include "iconBuffer.h" +#include "statusHandler.h" +#include #elif defined FFS_LINUX #include #endif - using namespace FreeFileSync; @@ -234,6 +237,7 @@ public: return xmlAccess::ColumnTypes(1000); } + virtual Zstring getFileName(const unsigned int row) const = 0; private: std::vector columnPositions; @@ -271,11 +275,11 @@ public: switch (getTypeAtPos(col)) { case xmlAccess::FULL_PATH: - return wxString(gridLine->fileDescrLeft.fullName.c_str()).BeforeLast(GlobalResources::FILE_NAME_SEPARATOR); + return wxString(gridLine->fileDescrLeft.fullName.c_str()).BeforeLast(FreeFileSync::FILE_NAME_SEPARATOR); case xmlAccess::FILENAME: //filename - return wxString(gridLine->fileDescrLeft.relativeName.c_str()).AfterLast(GlobalResources::FILE_NAME_SEPARATOR); + return wxString(gridLine->fileDescrLeft.relativeName.c_str()).AfterLast(FreeFileSync::FILE_NAME_SEPARATOR); case xmlAccess::REL_PATH: //relative path - return wxString(gridLine->fileDescrLeft.relativeName.c_str()).BeforeLast(GlobalResources::FILE_NAME_SEPARATOR); + return wxString(gridLine->fileDescrLeft.relativeName.c_str()).BeforeLast(FreeFileSync::FILE_NAME_SEPARATOR); case xmlAccess::DIRECTORY: return gridDataView->getFolderPair(row).leftDirectory.c_str(); case xmlAccess::SIZE: //file size @@ -289,6 +293,17 @@ public: return wxEmptyString; } + + virtual Zstring getFileName(const unsigned int row) const + { + const FileCompareLine* gridLine = getRawData(row); + if (gridLine) + return Zstring(gridLine->fileDescrLeft.fullName); + else + return Zstring(); + } + + private: virtual const wxColour& getRowColor(int row) //rows that are filtered out are shown in different color { @@ -340,11 +355,11 @@ public: switch (getTypeAtPos(col)) { case xmlAccess::FULL_PATH: - return wxString(gridLine->fileDescrRight.fullName.c_str()).BeforeLast(GlobalResources::FILE_NAME_SEPARATOR); + return wxString(gridLine->fileDescrRight.fullName.c_str()).BeforeLast(FreeFileSync::FILE_NAME_SEPARATOR); case xmlAccess::FILENAME: //filename - return wxString(gridLine->fileDescrRight.relativeName.c_str()).AfterLast(GlobalResources::FILE_NAME_SEPARATOR); + return wxString(gridLine->fileDescrRight.relativeName.c_str()).AfterLast(FreeFileSync::FILE_NAME_SEPARATOR); case xmlAccess::REL_PATH: //relative path - return wxString(gridLine->fileDescrRight.relativeName.c_str()).BeforeLast(GlobalResources::FILE_NAME_SEPARATOR); + return wxString(gridLine->fileDescrRight.relativeName.c_str()).BeforeLast(FreeFileSync::FILE_NAME_SEPARATOR); case xmlAccess::DIRECTORY: return gridDataView->getFolderPair(row).rightDirectory.c_str(); case xmlAccess::SIZE: //file size @@ -358,6 +373,17 @@ public: return wxEmptyString; } + + virtual Zstring getFileName(const unsigned int row) const + { + const FileCompareLine* gridLine = getRawData(row); + if (gridLine) + return Zstring(gridLine->fileDescrRight.fullName); + else + return Zstring(); + } + + private: virtual const wxColour& getRowColor(int row) //rows that are filtered out are shown in different color { @@ -476,27 +502,43 @@ CustomGrid::CustomGrid(wxWindow *parent, m_gridMiddle(NULL), m_gridRight(NULL), isLeading(false), - currentSortColumn(-1), - sortMarker(NULL) + m_marker(-1, ASCENDING) { //set color of selections 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(CustomGridLeft* gridLeft, + CustomGridMiddle* gridMiddle, + CustomGridRight* gridRight, + const GridView* gridDataView) +{ + assert(this == gridLeft || this == gridRight || this == gridMiddle); + + //these grids will scroll together + m_gridLeft = gridLeft; + m_gridRight = gridRight; + m_gridMiddle = gridMiddle; + + //set underlying grid data + setGridDataTable(gridDataView); + + //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); + GetGridWindow()->Connect(wxEVT_RIGHT_DOWN, wxEventHandler(CustomGrid::onGridAccess), NULL, this); GetGridWindow()->Connect(wxEVT_ENTER_WINDOW, wxEventHandler(CustomGrid::adjustGridHeights), NULL, this); } @@ -507,58 +549,22 @@ bool CustomGrid::isLeadGrid() const } -inline -bool gridsShouldBeCleared(const wxEvent& event) +void CustomGrid::RefreshCell(int row, int col) { - try - { - const wxMouseEvent& mouseEvent = dynamic_cast (event); - - if (mouseEvent.ControlDown() || mouseEvent.ShiftDown()) - return false; - - if (mouseEvent.ButtonDown(wxMOUSE_BTN_LEFT)) - return true; + wxRect rectScrolled(CellToRect(row, col)); - return false; - } - catch (std::bad_cast&) {} - - try - { - const wxKeyEvent& keyEvent = dynamic_cast (event); + CalcScrolledPosition(rectScrolled.x, rectScrolled.y, &rectScrolled.x, &rectScrolled.y); - if (keyEvent.ControlDown() || keyEvent.ShiftDown()) - return false; + GetGridWindow()->RefreshRect(rectScrolled); //note: CellToRect() and YToRow work on m_gridWindow NOT on the whole grid! +} - 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&) {} +void CustomGrid::DoPrepareDC(wxDC& dc) +{ + wxScrollHelper::DoPrepareDC(dc); - return false; + if (isLeadGrid()) //avoid back coupling + alignOtherGrids(m_gridLeft, m_gridMiddle, m_gridRight); //scroll other grids } @@ -681,29 +687,84 @@ void additionalGridCommands(wxEvent& event, wxGrid* grid) } +inline +bool gridsShouldBeCleared(const wxEvent& event) +{ + try + { + const wxMouseEvent& mouseEvent = dynamic_cast (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 (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; + } + + return false; + } + catch (std::bad_cast&) {} + + return false; +} + + void CustomGrid::onGridAccess(wxEvent& event) { if (!isLeading) { - isLeading = true; - //notify other grids of new user focus - if (m_gridLeft != this) - m_gridLeft->isLeading = false; - if (m_gridMiddle != this) - m_gridMiddle->isLeading = false; - if (m_gridRight != this) - m_gridRight->isLeading = false; + m_gridLeft->isLeading = m_gridLeft == this; + m_gridMiddle->isLeading = m_gridMiddle == this; + m_gridRight->isLeading = m_gridRight == this; wxGrid::SetFocus(); } + //clear grids if (gridsShouldBeCleared(event)) { m_gridLeft->ClearSelection(); + m_gridMiddle->ClearSelection(); m_gridRight->ClearSelection(); } + //update row labels NOW (needed when scrolling if buttons keep being pressed) + m_gridLeft->GetGridRowLabelWindow()->Update(); + m_gridRight->GetGridRowLabelWindow()->Update(); + //support for additional short-cuts additionalGridCommands(event, this); //event.Skip is handled here! } @@ -712,48 +773,46 @@ void CustomGrid::onGridAccess(wxEvent& event) //workaround: ensure that all grids are properly aligned: add some extra window space to grids that have no horizontal scrollbar void CustomGrid::adjustGridHeights(wxEvent& event) { - if (m_gridLeft && m_gridRight && m_gridMiddle) - { - int y1 = 0; - int y2 = 0; - int y3 = 0; - int dummy = 0; + //m_gridLeft, m_gridRight, m_gridMiddle not NULL because called after initSettings() - m_gridLeft->GetViewStart(&dummy, &y1); - m_gridRight->GetViewStart(&dummy, &y2); - m_gridMiddle->GetViewStart(&dummy, &y3); + int y1 = 0; + int y2 = 0; + int y3 = 0; + int dummy = 0; - if (y1 != y2 || y2 != y3) - { - int yMax = std::max(y1, std::max(y2, y3)); - - if (m_gridLeft->isLeadGrid()) //do not handle case (y1 == yMax) here!!! Avoid back coupling! - m_gridLeft->SetMargins(0, 0); - else if (y1 < yMax) - m_gridLeft->SetMargins(0, 30); - - if (m_gridRight->isLeadGrid()) - m_gridRight->SetMargins(0, 0); - else if (y2 < yMax) - m_gridRight->SetMargins(0, 30); - - if (m_gridMiddle->isLeadGrid()) - m_gridMiddle->SetMargins(0, 0); - else if (y3 < yMax) - m_gridMiddle->SetMargins(0, 30); - - m_gridLeft->ForceRefresh(); - m_gridRight->ForceRefresh(); - m_gridMiddle->ForceRefresh(); - } + m_gridLeft->GetViewStart(&dummy, &y1); + m_gridRight->GetViewStart(&dummy, &y2); + m_gridMiddle->GetViewStart(&dummy, &y3); + + if (y1 != y2 || y2 != y3) + { + int yMax = std::max(y1, std::max(y2, y3)); + + if (m_gridLeft->isLeadGrid()) //do not handle case (y1 == yMax) here!!! Avoid back coupling! + m_gridLeft->SetMargins(0, 0); + else if (y1 < yMax) + m_gridLeft->SetMargins(0, 30); + + if (m_gridRight->isLeadGrid()) + m_gridRight->SetMargins(0, 0); + else if (y2 < yMax) + m_gridRight->SetMargins(0, 30); + + if (m_gridMiddle->isLeadGrid()) + m_gridMiddle->SetMargins(0, 0); + else if (y3 < yMax) + m_gridMiddle->SetMargins(0, 30); + + m_gridLeft->ForceRefresh(); + m_gridRight->ForceRefresh(); + m_gridMiddle->ForceRefresh(); } } -void CustomGrid::setSortMarker(const int sortColumn, const wxBitmap* bitmap) +void CustomGrid::setSortMarker(SortMarker marker) { - currentSortColumn = sortColumn; - sortMarker = bitmap; + m_marker = marker; } @@ -761,8 +820,13 @@ void CustomGrid::DrawColLabel(wxDC& dc, int col) { wxGrid::DrawColLabel(dc, col); - if (col == currentSortColumn) - dc.DrawBitmap(*sortMarker, GetColRight(col) - 16 - 2, 2, true); //respect 2-pixel border + if (col == m_marker.first) + { + if (m_marker.second == ASCENDING) + dc.DrawBitmap(*GlobalResources::getInstance().bitmapSmallUp, GetColRight(col) - 16 - 2, 2, true); //respect 2-pixel border + else + dc.DrawBitmap(*GlobalResources::getInstance().bitmapSmallDown, GetColRight(col) - 16 - 2, 2, true); //respect 2-pixel border + } } @@ -821,118 +885,119 @@ std::set CustomGrid::getAllSelectedRows() const //############################################################################################ //CustomGrid specializations -template +#ifdef FFS_WIN +template class GridCellRenderer : public wxGridCellStringRenderer { public: - GridCellRenderer(CustomGridTableRim* gridDataTable) : m_gridDataTable(gridDataTable) {}; + GridCellRenderer(CustomGridRim::LoadSuccess& loadIconSuccess, const CustomGridTableRim* gridDataTable) : + m_loadIconSuccess(loadIconSuccess), + m_gridDataTable(gridDataTable) {} virtual void Draw(wxGrid& grid, wxGridCellAttr& attr, wxDC& dc, - const wxRect& rect, + const wxRect& rect, //unscrolled 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) + rect.GetWidth() >= IconBuffer::ICON_SIZE) { //retrieve grid data - const FileCompareLine* rowData = m_gridDataTable->getRawData(row); - if (rowData) //valid row + const Zstring fileName = m_gridDataTable->getFileName(row); + if (!fileName.empty()) { - const DefaultChar* filename; - if (leftSide) //evaluate at compile time - filename = rowData->fileDescrLeft.fullName.c_str(); - else - filename = rowData->fileDescrRight.fullName.c_str(); - - if (*filename != DefaultChar(0)) //test if filename is empty + // Partitioning: + // _____________________ + // | 2 pix | icon | rest | + // --------------------- + + //clear area where icon will be placed + wxRect rectShrinked(rect); + rectShrinked.SetWidth(IconBuffer::ICON_SIZE + 2); //add 2 pixel border + wxGridCellRenderer::Draw(grid, attr, dc, rectShrinked, row, col, isSelected); + + //try to draw icon + wxIcon icon; + const bool iconLoaded = IconBuffer::getInstance().requestIcon(fileName, &icon); //returns false if icon is not in buffer + if (iconLoaded) + dc.DrawIcon(icon, rectShrinked.GetX() + 2, rectShrinked.GetY()); + + //----------------------------------------------------------------------------------------------- + //only mark as successful if icon was drawn fully! + //(attention: when scrolling, rows get partially updated, which can result in the upper half being blank!) + + //rect where icon was placed + wxRect iconRect(rect); //unscrolled + iconRect.x += 2; + iconRect.SetWidth(IconBuffer::ICON_SIZE); + + //convert to scrolled coordinates + grid.CalcScrolledPosition(iconRect.x, iconRect.y, &iconRect.x, &iconRect.y); + + bool iconDrawnFully = false; + wxRegionIterator regionsInv(grid.GetGridWindow()->GetUpdateRegion()); + while (regionsInv) { - // Get the file icon. - SHFILEINFO fileInfo; - fileInfo.hIcon = 0; //initialize hIcon - - if (SHGetFileInfo(filename, //NOTE: CoInitializeEx()/CoUninitialize() implicitly called by wxWidgets on program startup! - 0, - &fileInfo, - sizeof(fileInfo), - SHGFI_ICON | SHGFI_SMALLICON)) + if (regionsInv.GetRect().Contains(iconRect)) { - //clear area where icon will be placed - wxRect rectShrinked(rect); - rectShrinked.SetWidth(ICON_SIZE + 2); //add 2 pixel border - wxGridCellRenderer::Draw(grid, attr, dc, rectShrinked, row, col, isSelected); - - //draw icon - if (fileInfo.hIcon != 0) //fix for weird error: SHGetFileInfo() might return successfully WITHOUT filling fileInfo.hIcon!! - { //bug report: https://sourceforge.net/tracker/?func=detail&aid=2768004&group_id=234430&atid=1093080 - wxIcon icon; - icon.SetHICON(static_cast(fileInfo.hIcon)); - icon.SetSize(ICON_SIZE, ICON_SIZE); - - dc.DrawIcon(icon, rectShrinked.GetX() + 2, rectShrinked.GetY()); - - if (!DestroyIcon(fileInfo.hIcon)) - throw RuntimeException(wxString(wxT("Error deallocating Icon handle!\n\n")) + FreeFileSync::getLastErrorFormatted()); - } - - //draw rest - rectShrinked.SetWidth(rect.GetWidth() - ICON_SIZE - 2); - rectShrinked.SetX(rect.GetX() + ICON_SIZE + 2); - wxGridCellStringRenderer::Draw(grid, attr, dc, rectShrinked, row, col, isSelected); - return; + iconDrawnFully = true; + break; } + ++regionsInv; } + //----------------------------------------------------------------------------------------------- + + + //save status of last icon load -> used for async. icon loading + m_loadIconSuccess[row] = iconLoaded && iconDrawnFully; + + //draw rest + wxRect rest(rect); //unscrolled + rest.x += IconBuffer::ICON_SIZE + 2; + rest.width -= IconBuffer::ICON_SIZE + 2; + + wxGridCellStringRenderer::Draw(grid, attr, dc, rest, row, col, isSelected); + 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: + CustomGridRim::LoadSuccess& m_loadIconSuccess; const CustomGridTableRim* const m_gridDataTable; }; +#endif //---------------------------------------------------------------------------------------- -void CustomGridRim::initSettings(const bool showFileIcons, - CustomGrid* gridLeft, - CustomGrid* gridRight, - CustomGrid* gridMiddle, - const GridView* gridDataView) -{ - //these grids will scroll together - m_gridLeft = gridLeft; - m_gridRight = gridRight; - m_gridMiddle = gridMiddle; - - //set underlying grid data - assert(gridDataTable); - gridDataTable->setGridDataTable(gridDataView); - - enableFileIcons(showFileIcons); -} +CustomGridRim::CustomGridRim(wxWindow *parent, + wxWindowID id, + const wxPoint& pos, + const wxSize& size, + long style, + const wxString& name) : + CustomGrid(parent, id, pos, size, style, name) +#ifdef FFS_WIN + , fileIconsAreEnabled(false) +#endif +{} void CustomGridRim::updateGridSizes() { - assert(gridDataTable); - gridDataTable->updateGridSizes(); + assert(getGridDataTable()); + getGridDataTable()->updateGridSizes(); } @@ -1046,8 +1111,8 @@ void CustomGridRim::setColumnAttributes(const xmlAccess::ColumnAttributes& attr) newPositions.push_back(columnSettings[i].type); //set column positions - assert(gridDataTable); - gridDataTable->setupColumns(newPositions); + assert(getGridDataTable()); + getGridDataTable()->setupColumns(newPositions); //set column width (set them after setupColumns!) for (unsigned int i = 0; i < newPositions.size(); ++i) @@ -1071,8 +1136,8 @@ void CustomGridRim::setColumnAttributes(const xmlAccess::ColumnAttributes& attr) xmlAccess::ColumnTypes CustomGridRim::getTypeAtPos(unsigned pos) const { - assert(gridDataTable); - return gridDataTable->getTypeAtPos(pos); + assert(getGridDataTable()); + return getGridDataTable()->getTypeAtPos(pos); } @@ -1097,6 +1162,132 @@ wxString CustomGridRim::getTypeName(xmlAccess::ColumnTypes colType) return wxEmptyString; //dummy } + +CustomGridTableRim* CustomGridRim::getGridDataTable() +{ //let the non-const call the const version: see Meyers Effective C++ + return const_cast(static_cast(this)->getGridDataTable()); +} + + +#ifdef FFS_WIN +void CustomGridRim::enableFileIcons(const bool value) +{ + fileIconsAreEnabled = value; + + if (value) + SetDefaultRenderer(new GridCellRenderer(loadIconSuccess, getGridDataTable())); //SetDefaultRenderer takes ownership! + else + SetDefaultRenderer(new GridCellRenderer(loadIconSuccess, getGridDataTable())); + + Refresh(); +} + + +CustomGridRim::VisibleRowRange CustomGridRim::getVisibleRows() +{ + int dummy = -1; + int height = -1; + GetGridWindow()->GetClientSize(&dummy, &height); + + if (height >= 0) + { + int topRowY = -1; + CalcUnscrolledPosition(0, 0, &dummy, &topRowY); + + if (topRowY >= 0) + { + const int topRow = YToRow(topRowY); + const int rowCount = static_cast(ceil(height / static_cast(GetDefaultRowSize()))); // = height / rowHeight rounded up + const int bottomRow = topRow + rowCount - 1; + + return VisibleRowRange(topRow, bottomRow); //"top" means here top of the screen: => smaller value + } + } + + return VisibleRowRange(0, 0); +} + + +void CustomGridRim::getIconsToBeLoaded(std::vector& newLoad) //loads all (not yet) drawn icons +{ + newLoad.clear(); + + if (fileIconsAreEnabled) //don't check too often! give worker thread some time to fetch data + { + const CustomGridTableRim* gridDataTable = getGridDataTable(); + const VisibleRowRange rowsOnScreen = getVisibleRows(); + const int totalCols = const_cast(gridDataTable)->GetNumberCols(); + const int totalRows = const_cast(gridDataTable)->GetNumberRows(); + + //loop over all visible rows + const int firstRow = rowsOnScreen.first; + const int lastRow = std::min(int(rowsOnScreen.second), totalRows - 1); + const int rowNo = lastRow - firstRow + 1; + + for (int i = 0; i < rowNo; ++i) + { + //alternate when adding rows: first, last, first + 1, last - 1 ... + const int currentRow = i % 2 == 0 ? + firstRow + i / 2 : + lastRow - (i - 1) / 2; + + LoadSuccess::const_iterator j = loadIconSuccess.find(currentRow); + if (j != loadIconSuccess.end() && j->second == false) //find failed attempts to load icon + { + const Zstring fileName = gridDataTable->getFileName(currentRow); + if (!fileName.empty()) + { + //test if they are already loaded in buffer: + if (FreeFileSync::IconBuffer::getInstance().requestIcon(fileName)) + { + //exists in buffer: refresh Row + for (int k = 0; k < totalCols; ++k) + if (gridDataTable->getTypeAtPos(k) == xmlAccess::FILENAME) + { + RefreshCell(currentRow, k); + break; + } + } + else //not yet in buffer: mark for async. loading + { + newLoad.push_back(fileName); + } + } + } + } + } +} + +//---------------------------------------------------------------------------------------- + + +//update file icons periodically: use SINGLE instance to coordinate left and right grid at once +IconUpdater::IconUpdater(CustomGridLeft* leftGrid, CustomGridRight* rightGrid) : + m_leftGrid(leftGrid), + m_rightGrid(rightGrid), + m_timer(new wxTimer) //connect timer event for async. icon loading +{ + m_timer->Connect(wxEVT_TIMER, wxEventHandler(IconUpdater::loadIconsAsynchronously), NULL, this); + m_timer->Start(50); //timer interval +} + + +void IconUpdater::loadIconsAsynchronously(wxEvent& event) //loads all (not yet) drawn icons +{ + std::vector iconsLeft; + m_leftGrid->getIconsToBeLoaded(iconsLeft); + + std::vector newLoad; + m_rightGrid->getIconsToBeLoaded(newLoad); + + globalFunctions::mergeVectors(iconsLeft, newLoad); + + FreeFileSync::IconBuffer::getInstance().setWorkload(newLoad); //attention: newLoad is invalidated after this call!!! + + //event.Skip(); +} +#endif + //---------------------------------------------------------------------------------------- @@ -1106,7 +1297,8 @@ CustomGridLeft::CustomGridLeft(wxWindow *parent, const wxSize& size, long style, const wxString& name) : - CustomGridRim(parent, id, pos, size, style, name) {} + CustomGridRim(parent, id, pos, size, style, name), + gridDataTable(NULL) {} bool CustomGridLeft::CreateGrid(int numRows, int numCols, wxGrid::wxGridSelectionModes selmode) @@ -1120,29 +1312,28 @@ bool CustomGridLeft::CreateGrid(int numRows, int numCols, wxGrid::wxGridSelectio } -void CustomGridLeft::enableFileIcons(const bool value) +void CustomGridLeft::setGridDataTable(const GridView* gridDataView) { - if (value) - SetDefaultRenderer(new GridCellRenderer(gridDataTable)); //SetDefaultRenderer takes ownership! - else - SetDefaultRenderer(new GridCellRenderer(gridDataTable)); + //set underlying grid data + assert(gridDataTable); + gridDataTable->setGridDataTable(gridDataView); +} - Refresh(); + +const CustomGridTableRim* CustomGridLeft::getGridDataTable() const +{ + return gridDataTable; } //this method is called when grid view changes: useful for parallel updating of multiple grids -void CustomGridLeft::DoPrepareDC(wxDC& dc) +void CustomGridLeft::alignOtherGrids(CustomGrid* gridLeft, CustomGrid* gridMiddle, CustomGrid* gridRight) { - wxScrollHelper::DoPrepareDC(dc); - - int x, y = 0; - if (isLeadGrid()) //avoid back coupling - { - GetViewStart(&x, &y); - m_gridMiddle->Scroll(-1, y); //scroll in y-direction only - m_gridRight->Scroll(x, y); - } + int x = 0; + int y = 0; + GetViewStart(&x, &y); + gridMiddle->Scroll(-1, y); //scroll in y-direction only + gridRight->Scroll(x, y); } @@ -1153,7 +1344,8 @@ CustomGridRight::CustomGridRight(wxWindow *parent, const wxSize& size, long style, const wxString& name) : - CustomGridRim(parent, id, pos, size, style, name) {} + CustomGridRim(parent, id, pos, size, style, name), + gridDataTable(NULL) {} bool CustomGridRight::CreateGrid(int numRows, int numCols, wxGrid::wxGridSelectionModes selmode) @@ -1165,33 +1357,49 @@ bool CustomGridRight::CreateGrid(int numRows, int numCols, wxGrid::wxGridSelecti } -void CustomGridRight::enableFileIcons(const bool value) +void CustomGridRight::setGridDataTable(const GridView* gridDataView) { - if (value) - SetDefaultRenderer(new GridCellRenderer(gridDataTable)); //SetDefaultRenderer takes ownership! - else - SetDefaultRenderer(new GridCellRenderer(gridDataTable)); + //set underlying grid data + assert(gridDataTable); + gridDataTable->setGridDataTable(gridDataView); +} - Refresh(); + +const CustomGridTableRim* CustomGridRight::getGridDataTable() const +{ + return gridDataTable; } //this method is called when grid view changes: useful for parallel updating of multiple grids -void CustomGridRight::DoPrepareDC(wxDC& dc) +void CustomGridRight::alignOtherGrids(CustomGrid* gridLeft, CustomGrid* gridMiddle, CustomGrid* gridRight) { - wxScrollHelper::DoPrepareDC(dc); - - int x, y = 0; - if (isLeadGrid()) //avoid back coupling - { - GetViewStart(&x, &y); - m_gridLeft->Scroll(x, y); - m_gridMiddle->Scroll(-1, y); - } + int x = 0; + int y = 0; + GetViewStart(&x, &y); + gridLeft->Scroll(x, y); + gridMiddle->Scroll(-1, y); } //---------------------------------------------------------------------------------------- +class GridCellRendererMiddle : public wxGridCellStringRenderer +{ +public: + GridCellRendererMiddle(const CustomGridMiddle* middleGrid) : m_gridMiddle(middleGrid) {}; + + virtual void Draw(wxGrid& grid, + wxGridCellAttr& attr, + wxDC& dc, + const wxRect& rect, + int row, int col, + bool isSelected); + +private: + const CustomGridMiddle* const m_gridMiddle; +}; + + //define new event types const wxEventType FFS_CHECK_ROWS_EVENT = wxNewEventType(); //attention! do NOT place in header to keep (generated) id unique! const wxEventType FFS_SYNC_DIRECTION_EVENT = wxNewEventType(); @@ -1220,13 +1428,46 @@ CustomGridMiddle::CustomGridMiddle(wxWindow *parent, { //connect events for dynamic selection of sync direction GetGridWindow()->Connect(wxEVT_MOTION, wxMouseEventHandler(CustomGridMiddle::OnMouseMovement), NULL, this); - GetGridWindow()->Connect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler(CustomGridMiddle::OnLeaveWindow), NULL, this); GetGridWindow()->Connect(wxEVT_LEFT_UP, wxMouseEventHandler(CustomGridMiddle::OnLeftMouseUp), NULL, this); GetGridWindow()->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(CustomGridMiddle::OnLeftMouseDown), NULL, this); } +bool CustomGridMiddle::CreateGrid(int numRows, int numCols, wxGrid::wxGridSelectionModes selmode) +{ + 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 GridCellRendererMiddle(this)); //SetDefaultRenderer takes ownership! + + return true; +} + + +#ifdef FFS_WIN //get rid of scrollbars; Windows: overwrite virtual method +void CustomGridMiddle::SetScrollbar(int orientation, int position, int thumbSize, int range, bool refresh) +{ + wxWindow::SetScrollbar(orientation, 0, 0, 0, refresh); +} +#endif + + +void CustomGridMiddle::setGridDataTable(const GridView* gridDataView) //called once on startup +{ + //set underlying grid data + assert(gridDataTable); + gridDataTable->setGridDataTable(gridDataView); + +#ifdef FFS_LINUX //get rid of scrollbars; Linux: change policy for GtkScrolledWindow + GtkWidget* gridWidget = wxWindow::m_widget; + GtkScrolledWindow* scrolledWindow = GTK_SCROLLED_WINDOW(gridWidget); + gtk_scrolled_window_set_policy(scrolledWindow, GTK_POLICY_NEVER, GTK_POLICY_NEVER); +#endif +} + + void CustomGridMiddle::OnMouseMovement(wxMouseEvent& event) { const int highlightedRowOld = highlightedRow; @@ -1234,25 +1475,17 @@ void CustomGridMiddle::OnMouseMovement(wxMouseEvent& event) if (selectionRowBegin == -1) //change highlightning only if currently not dragging mouse { highlightedRow = mousePosToRow(event.GetPosition(), &highlightedPos); - if (highlightedRow >= 0) RefreshRow(highlightedRow); + if (highlightedRow >= 0) + RefreshCell(highlightedRow, 0); if ( highlightedRowOld >= 0 && highlightedRow != highlightedRowOld) - RefreshRow(highlightedRowOld); + RefreshCell(highlightedRowOld, 0); } event.Skip(); } -void CustomGridMiddle::RefreshRow(int row) -{ - wxRect rectScrolled(CellToRect(row, 0)); - CalcScrolledPosition(rectScrolled.x, rectScrolled.y, &rectScrolled.x, &rectScrolled.y); - - GetGridWindow()->Refresh(false, &rectScrolled); //note: CellToRect() and YToRow work on m_gridWindow NOT on the whole grid! -} - - void CustomGridMiddle::OnLeaveWindow(wxMouseEvent& event) { highlightedRow = -1; @@ -1357,36 +1590,6 @@ int CustomGridMiddle::mousePosToRow(const wxPoint pos, BlockPosition* block) } -void CustomGridMiddle::initSettings(CustomGrid* gridLeft, - CustomGrid* gridRight, - CustomGrid* gridMiddle, - const FreeFileSync::GridView* gridDataView) -{ - //these grids will scroll together - m_gridLeft = gridLeft; - m_gridRight = gridRight; - m_gridMiddle = gridMiddle; - - //set underlying grid data - assert(gridDataTable); - gridDataTable->setGridDataTable(gridDataView); - -#ifdef FFS_LINUX //get rid of scrollbars; Linux: change policy for GtkScrolledWindow - GtkWidget* gridWidget = wxWindow::m_widget; - GtkScrolledWindow* scrolledWindow = GTK_SCROLLED_WINDOW(gridWidget); - gtk_scrolled_window_set_policy(scrolledWindow, GTK_POLICY_NEVER, GTK_POLICY_NEVER); -#endif -} - - -#ifdef FFS_WIN //get rid of scrollbars; Windows: overwrite virtual method -void CustomGridMiddle::SetScrollbar(int orientation, int position, int thumbSize, int range, bool refresh) -{ - wxWindow::SetScrollbar(orientation, 0, 0, 0, refresh); -} -#endif - - void CustomGridMiddle::enableSyncPreview(bool value) { assert(gridDataTable); @@ -1427,152 +1630,128 @@ void CustomGridMiddle::updateGridSizes() } -class GridCellRendererMiddle : public wxGridCellStringRenderer +void GridCellRendererMiddle::Draw(wxGrid& grid, + wxGridCellAttr& attr, + wxDC& dc, + const wxRect& rect, + int row, int col, + bool isSelected) { -public: - GridCellRendererMiddle(const CustomGridMiddle* middleGrid) : m_gridMiddle(middleGrid) {}; - - - virtual void Draw(wxGrid& grid, - wxGridCellAttr& attr, - wxDC& dc, - const wxRect& rect, - int row, int col, - bool isSelected) + //retrieve grid data + const FileCompareLine* const rowData = m_gridMiddle->gridDataTable->getRawData(row); + if (rowData != NULL) //if valid row... { - //retrieve grid data - const FileCompareLine* const rowData = m_gridMiddle->gridDataTable->getRawData(row); - if (rowData) //no valid row + if (rect.GetWidth() > CHECK_BOX_WIDTH) { - if (rect.GetWidth() > CHECK_BOX_WIDTH) - { - wxRect rectShrinked(rect); + wxRect rectShrinked(rect); - //clean first block of rect that will receive image of checkbox - rectShrinked.SetWidth(CHECK_BOX_WIDTH); - wxGridCellRenderer::Draw(grid, attr, dc, rectShrinked, row, col, isSelected); + //clean first block of rect that will receive image of checkbox + rectShrinked.SetWidth(CHECK_BOX_WIDTH); + wxGridCellRenderer::Draw(grid, attr, dc, rectShrinked, row, col, isSelected); - //print image into first block - rectShrinked.SetX(rect.GetX() + 1); - bool selected = rowData->selectedForSynchronization; + //print image into first block + rectShrinked.SetX(rect.GetX() + 1); + bool selected = rowData->selectedForSynchronization; - //HIGHLIGHTNING: - if ( row == m_gridMiddle->highlightedRow && - m_gridMiddle->highlightedPos == CustomGridMiddle::BLOCKPOS_CHECK_BOX) - selected = !selected; - - if (selected) - dc.DrawLabel(wxEmptyString, *globalResource.bitmapCheckBoxTrue, rectShrinked, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); - else - dc.DrawLabel(wxEmptyString, *globalResource.bitmapCheckBoxFalse, rectShrinked, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); - - //clean remaining block of rect that will receive image of checkbox/directions - rectShrinked.SetWidth(rect.GetWidth() - CHECK_BOX_WIDTH); - rectShrinked.SetX(rect.GetX() + CHECK_BOX_WIDTH); - wxGridCellRenderer::Draw(grid, attr, dc, rectShrinked, row, col, isSelected); - - //print remaining block - if (m_gridMiddle->gridDataTable->syncPreviewIsActive()) //synchronization preview - { - //print sync direction into second block + //HIGHLIGHTNING: + if ( row == m_gridMiddle->highlightedRow && + m_gridMiddle->highlightedPos == CustomGridMiddle::BLOCKPOS_CHECK_BOX) + selected = !selected; - //HIGHLIGHTNING: - if (row == m_gridMiddle->highlightedRow && m_gridMiddle->highlightedPos != CustomGridMiddle::BLOCKPOS_CHECK_BOX) - switch (m_gridMiddle->highlightedPos) - { - case CustomGridMiddle::BLOCKPOS_CHECK_BOX: - break; - case CustomGridMiddle::BLOCKPOS_LEFT: - dc.DrawLabel(wxEmptyString, *globalResource.bitmapSyncDirLeftSmall, rectShrinked, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); - break; - case CustomGridMiddle::BLOCKPOS_MIDDLE: - dc.DrawLabel(wxEmptyString, *globalResource.bitmapSyncDirNoneSmall, rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); - break; - case CustomGridMiddle::BLOCKPOS_RIGHT: - dc.DrawLabel(wxEmptyString, *globalResource.bitmapSyncDirRightSmall, rectShrinked, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL); - break; - } - else //default - switch (rowData->direction) - { - case SYNC_DIR_LEFT: - dc.DrawLabel(wxEmptyString, *globalResource.bitmapSyncDirLeftSmall, rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); - break; - case SYNC_DIR_RIGHT: - dc.DrawLabel(wxEmptyString, *globalResource.bitmapSyncDirRightSmall, rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); - break; - case SYNC_DIR_NONE: - dc.DrawLabel(wxEmptyString, *globalResource.bitmapSyncDirNoneSmall, rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); - break; - case SYNC_UNRESOLVED_CONFLICT: - dc.DrawLabel(wxEmptyString, *globalResource.bitmapConflictSmall, rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); - break; - } - } - else //comparison results view - { - switch (rowData->cmpResult) + if (selected) + dc.DrawLabel(wxEmptyString, *GlobalResources::getInstance().bitmapCheckBoxTrue, rectShrinked, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); + else + dc.DrawLabel(wxEmptyString, *GlobalResources::getInstance().bitmapCheckBoxFalse, rectShrinked, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); + + //clean remaining block of rect that will receive image of checkbox/directions + rectShrinked.SetWidth(rect.GetWidth() - CHECK_BOX_WIDTH); + rectShrinked.SetX(rect.GetX() + CHECK_BOX_WIDTH); + wxGridCellRenderer::Draw(grid, attr, dc, rectShrinked, row, col, isSelected); + + //print remaining block + if (m_gridMiddle->gridDataTable->syncPreviewIsActive()) //synchronization preview + { + //print sync direction into second block + + //HIGHLIGHTNING: + if (row == m_gridMiddle->highlightedRow && m_gridMiddle->highlightedPos != CustomGridMiddle::BLOCKPOS_CHECK_BOX) + switch (m_gridMiddle->highlightedPos) { - case FILE_LEFT_SIDE_ONLY: - dc.DrawLabel(wxEmptyString, *globalResource.bitmapLeftOnlySmall, rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); - break; - case FILE_RIGHT_SIDE_ONLY: - dc.DrawLabel(wxEmptyString, *globalResource.bitmapRightOnlySmall, rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); - break; - case FILE_LEFT_NEWER: - dc.DrawLabel(wxEmptyString, *globalResource.bitmapLeftNewerSmall, rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); + case CustomGridMiddle::BLOCKPOS_CHECK_BOX: break; - case FILE_RIGHT_NEWER: - dc.DrawLabel(wxEmptyString, *globalResource.bitmapRightNewerSmall, rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); + case CustomGridMiddle::BLOCKPOS_LEFT: + dc.DrawLabel(wxEmptyString, getSyncOpImage(rowData->cmpResult, true, SYNC_DIR_LEFT), rectShrinked, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); break; - case FILE_DIFFERENT: - dc.DrawLabel(wxEmptyString, *globalResource.bitmapDifferentSmall, rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); + case CustomGridMiddle::BLOCKPOS_MIDDLE: + dc.DrawLabel(wxEmptyString, getSyncOpImage(rowData->cmpResult, true, SYNC_DIR_NONE), rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); break; - case FILE_EQUAL: - dc.DrawLabel(wxEmptyString, *globalResource.bitmapEqualSmall, rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); - break; - case FILE_CONFLICT: - dc.DrawLabel(wxEmptyString, *globalResource.bitmapConflictSmall, rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); + case CustomGridMiddle::BLOCKPOS_RIGHT: + dc.DrawLabel(wxEmptyString, getSyncOpImage(rowData->cmpResult, true, SYNC_DIR_RIGHT), rectShrinked, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL); break; } + else //default + { + const wxBitmap& syncOpIcon = getSyncOpImage(rowData->cmpResult, rowData->selectedForSynchronization, rowData->direction); + dc.DrawLabel(wxEmptyString, syncOpIcon, rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); + } + } + else //comparison results view + { + switch (rowData->cmpResult) + { + case FILE_LEFT_SIDE_ONLY: + dc.DrawLabel(wxEmptyString, *GlobalResources::getInstance().bitmapLeftOnlySmall, rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); + break; + case FILE_RIGHT_SIDE_ONLY: + dc.DrawLabel(wxEmptyString, *GlobalResources::getInstance().bitmapRightOnlySmall, rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); + break; + case FILE_LEFT_NEWER: + dc.DrawLabel(wxEmptyString, *GlobalResources::getInstance().bitmapLeftNewerSmall, rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); + break; + case FILE_RIGHT_NEWER: + dc.DrawLabel(wxEmptyString, *GlobalResources::getInstance().bitmapRightNewerSmall, rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); + break; + case FILE_DIFFERENT: + dc.DrawLabel(wxEmptyString, *GlobalResources::getInstance().bitmapDifferentSmall, rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); + break; + case FILE_EQUAL: + dc.DrawLabel(wxEmptyString, *GlobalResources::getInstance().bitmapEqualSmall, rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); + break; + case FILE_CONFLICT: + dc.DrawLabel(wxEmptyString, *GlobalResources::getInstance().bitmapConflictSmall, rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); + break; } - - return; } - } - //fallback - wxGridCellStringRenderer::Draw(grid, attr, dc, rect, row, col, isSelected); + return; + } } -private: - const CustomGridMiddle* const m_gridMiddle; -}; + //fallback + wxGridCellStringRenderer::Draw(grid, attr, dc, rect, row, col, isSelected); +} -bool CustomGridMiddle::CreateGrid(int numRows, int numCols, wxGrid::wxGridSelectionModes selmode) +//this method is called when grid view changes: useful for parallel updating of multiple grids +void CustomGridMiddle::alignOtherGrids(CustomGrid* gridLeft, CustomGrid* gridMiddle, CustomGrid* gridRight) { - 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 GridCellRendererMiddle(this)); //SetDefaultRenderer takes ownership! - - return true; + int x = 0; + int y = 0; + GetViewStart(&x, &y); + gridLeft->Scroll(-1, y); + gridRight->Scroll(-1, y); } -//this method is called when grid view changes: useful for parallel updating of multiple grids -void CustomGridMiddle::DoPrepareDC(wxDC& dc) +void CustomGridMiddle::DrawColLabel(wxDC& dc, int col) { - wxScrollHelper::DoPrepareDC(dc); + CustomGrid::DrawColLabel(dc, col); - int x, y = 0; - if (isLeadGrid()) //avoid back coupling - { - GetViewStart(&x, &y); - m_gridLeft->Scroll(-1, y); - m_gridRight->Scroll(-1, y); - } + const wxRect rect(GetColLeft(col), 0, GetColWidth(col), GetColLabelSize()); + + if (gridDataTable->syncPreviewIsActive()) + dc.DrawLabel(wxEmptyString, *GlobalResources::getInstance().bitmapSyncViewSmall, rect, wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL); + else + dc.DrawLabel(wxEmptyString, *GlobalResources::getInstance().bitmapCmpViewSmall, rect, wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL); } diff --git a/library/CustomGrid.h b/library/CustomGrid.h index 89ec39d0..98e86e11 100644 --- a/library/CustomGrid.h +++ b/library/CustomGrid.h @@ -5,12 +5,19 @@ #include #include "../structures.h" #include "processXml.h" +#include +#include - -class CustomGridTable; class CustomGridTableRim; +class CustomGridTableLeft; +class CustomGridTableRight; class CustomGridTableMiddle; class GridCellRendererMiddle; +class wxTimer; +class CustomGridRim; +class CustomGridLeft; +class CustomGridMiddle; +class CustomGridRight; namespace FreeFileSync { @@ -43,50 +50,89 @@ public: virtual ~CustomGrid() {} - virtual void DrawColLabel(wxDC& dc, int col); + void initSettings(CustomGridLeft* gridLeft, + CustomGridMiddle* gridMiddle, + CustomGridRight* gridRight, + const FreeFileSync::GridView* gridDataView); std::set getAllSelectedRows() const; //set sort direction indicator on UI - void setSortMarker(const int sortColumn, const wxBitmap* bitmap = &wxNullBitmap); + typedef int SortColumn; + + enum SortDirection + { + ASCENDING, + DESCENDING, + }; + + typedef std::pair SortMarker; + void setSortMarker(SortMarker marker); bool isLeadGrid() const; protected: - CustomGrid* m_gridLeft; - CustomGrid* m_gridMiddle; - CustomGrid* m_gridRight; + void RefreshCell(int row, int col); + + virtual void DrawColLabel(wxDC& dc, int col); private: + virtual void setGridDataTable(const FreeFileSync::GridView* gridDataView) = 0; + +//this method is called when grid view changes: useful for parallel updating of multiple grids + virtual void DoPrepareDC(wxDC& dc); + + virtual void alignOtherGrids(CustomGrid* gridLeft, CustomGrid* gridMiddle, CustomGrid* gridRight) = 0; + void onGridAccess(wxEvent& event); void adjustGridHeights(wxEvent& event); + CustomGrid* m_gridLeft; + CustomGrid* m_gridMiddle; + CustomGrid* m_gridRight; + bool isLeading; //identify grid that has user focus - int currentSortColumn; - const wxBitmap* sortMarker; + + SortMarker m_marker; +}; + + +template +class GridCellRenderer; + + +//----------------------------------------------------------- +#ifdef FFS_WIN +class IconUpdater : public wxEvtHandler //update file icons periodically: use SINGLE instance to coordinate left and right grid at once +{ +public: + IconUpdater(CustomGridLeft* leftGrid, CustomGridRight* rightGrid); + +private: + void loadIconsAsynchronously(wxEvent& event); //loads all (not yet) drawn icons + + CustomGridRim* m_leftGrid; + CustomGridRim* m_rightGrid; + + std::auto_ptr m_timer; //user timer event to periodically update icons: better than idle event because also active when scrolling! :) }; +#endif //############## SPECIALIZATIONS ################### class CustomGridRim : public CustomGrid { + friend class IconUpdater; + template + friend class GridCellRenderer; + public: CustomGridRim(wxWindow *parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, - const wxString& name) : - CustomGrid(parent, id, pos, size, style, name), - gridDataTable(NULL) {} - - ~CustomGridRim() {} - - void initSettings(const bool showFileIcons, //workaround: though this coding better belongs into a constructor - CustomGrid* gridLeft, //this is not possible due to source code generation (information not available at time of construction) - CustomGrid* gridRight, - CustomGrid* gridMiddle, - const FreeFileSync::GridView* gridDataView); + const wxString& name); //notify wxGrid that underlying table size has changed void updateGridSizes(); @@ -99,12 +145,32 @@ public: xmlAccess::ColumnTypes getTypeAtPos(unsigned pos) const; static wxString getTypeName(xmlAccess::ColumnTypes colType); - virtual void enableFileIcons(const bool value) = 0; - -protected: - CustomGridTableRim* gridDataTable; +#ifdef FFS_WIN + void enableFileIcons(const bool value); +#endif private: + CustomGridTableRim* getGridDataTable(); + virtual const CustomGridTableRim* getGridDataTable() const = 0; + +#ifdef FFS_WIN + //asynchronous icon loading + void getIconsToBeLoaded(std::vector& newLoad); //loads all (not yet) drawn icons + + typedef unsigned int FromRow; + typedef unsigned int ToRow; + typedef std::pair VisibleRowRange; + VisibleRowRange getVisibleRows(); + + + typedef unsigned int RowNumber; + typedef bool IconLoaded; + typedef std::map LoadSuccess; + LoadSuccess loadIconSuccess; //save status of last icon load when drawing on GUI + + bool fileIconsAreEnabled; +#endif + xmlAccess::ColumnAttributes columnSettings; //set visibility, position and width of columns }; @@ -119,14 +185,16 @@ public: long style = wxWANTS_CHARS, const wxString& name = wxGridNameStr); - ~CustomGridLeft() {} - virtual bool CreateGrid(int numRows, int numCols, wxGrid::wxGridSelectionModes selmode = wxGrid::wxGridSelectCells); - virtual void enableFileIcons(const bool value); +private: + virtual void setGridDataTable(const FreeFileSync::GridView* gridDataView); + virtual const CustomGridTableRim* getGridDataTable() const; //this method is called when grid view changes: useful for parallel updating of multiple grids - virtual void DoPrepareDC(wxDC& dc); + virtual void alignOtherGrids(CustomGrid* gridLeft, CustomGrid* gridMiddle, CustomGrid* gridRight); + + CustomGridTableLeft* gridDataTable; }; @@ -140,14 +208,16 @@ public: long style = wxWANTS_CHARS, const wxString& name = wxGridNameStr); - ~CustomGridRight() {} - virtual bool CreateGrid(int numRows, int numCols, wxGrid::wxGridSelectionModes selmode = wxGrid::wxGridSelectCells); - virtual void enableFileIcons(const bool value); +private: + virtual void setGridDataTable(const FreeFileSync::GridView* gridDataView); + virtual const CustomGridTableRim* getGridDataTable() const; //this method is called when grid view changes: useful for parallel updating of multiple grids - virtual void DoPrepareDC(wxDC& dc); + virtual void alignOtherGrids(CustomGrid* gridLeft, CustomGrid* gridMiddle, CustomGrid* gridRight); + + CustomGridTableRight* gridDataTable; }; @@ -163,35 +233,31 @@ public: long style = wxWANTS_CHARS, const wxString& name = wxGridNameStr); - ~CustomGridMiddle() {} - virtual bool CreateGrid(int numRows, int numCols, wxGrid::wxGridSelectionModes selmode = wxGrid::wxGridSelectCells); - void initSettings(CustomGrid* gridLeft, //workaround: though this coding better belongs into a constructor - CustomGrid* gridRight, //this is not possible due to source code generation (information not available at time of construction) - CustomGrid* gridMiddle, - const FreeFileSync::GridView* gridDataView); - void enableSyncPreview(bool value); //notify wxGrid that underlying table size has changed void updateGridSizes(); +private: #ifdef FFS_WIN //get rid of scrollbars; Windows: overwrite virtual method virtual void SetScrollbar(int orientation, int position, int thumbSize, int range, bool refresh = true); #endif + virtual void setGridDataTable(const FreeFileSync::GridView* gridDataView); + //this method is called when grid view changes: useful for parallel updating of multiple grids - virtual void DoPrepareDC(wxDC& dc); + virtual void alignOtherGrids(CustomGrid* gridLeft, CustomGrid* gridMiddle, CustomGrid* gridRight); + + virtual void DrawColLabel(wxDC& dc, int col); -private: void OnMouseMovement(wxMouseEvent& event); void OnLeaveWindow(wxMouseEvent& event); void OnLeftMouseDown(wxMouseEvent& event); void OnLeftMouseUp(wxMouseEvent& event); //small helper methods - void RefreshRow(int row); enum BlockPosition //each cell can be divided into four blocks concerning mouse selections { BLOCKPOS_CHECK_BOX, diff --git a/library/ShadowCopy/ShadowDll.vcproj b/library/ShadowCopy/ShadowDll.vcproj new file mode 100644 index 00000000..637aad0c --- /dev/null +++ b/library/ShadowCopy/ShadowDll.vcproj @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/library/ShadowCopy/dllmain.cpp b/library/ShadowCopy/dllmain.cpp new file mode 100644 index 00000000..834b4f88 --- /dev/null +++ b/library/ShadowCopy/dllmain.cpp @@ -0,0 +1,22 @@ +// dllmain.cpp : Definiert den Einstiegspunkt fr die DLL-Anwendung. + +#define WIN32_LEAN_AND_MEAN +#include + + +BOOL APIENTRY DllMain( HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + diff --git a/library/ShadowCopy/shadow.cpp b/library/ShadowCopy/shadow.cpp new file mode 100644 index 00000000..e10a4eb8 --- /dev/null +++ b/library/ShadowCopy/shadow.cpp @@ -0,0 +1,178 @@ +#include "shadow.h" + +#define WIN32_LEAN_AND_MEAN +#include "windows.h" +#include "inc/vss.h" +#include "inc/vswriter.h" +#include "inc/vsbackup.h" +#include +#include +#include +#include + +//typedef GUID VSS_ID; + + +void writeString(const wchar_t* input, wchar_t* output, unsigned int outputBufferLen) +{ + const unsigned int newSize = min(wcslen(input) + 1, outputBufferLen); //including null-termination + memcpy(output, input, newSize * sizeof(wchar_t)); +} + + +std::wstring numberToHexString(const long number) +{ + wchar_t result[100]; + swprintf(result, 100, L"0x%08x", number); + return std::wstring(result); +} + + +void writeErrorMsg(const wchar_t* input, HRESULT hr, wchar_t* output, unsigned int outputBufferLen) +{ + std::wstring formattedMsg(input); + formattedMsg += L" ("; + formattedMsg += numberToHexString(hr); + formattedMsg += L": "; + formattedMsg += _com_error(hr).ErrorMessage(); + formattedMsg += L")"; + + writeString(formattedMsg.c_str(), output, outputBufferLen); +} + + +bool shadow::createShadowCopy(const wchar_t* volumeName, + wchar_t* shadowVolName, + unsigned int shadowBufferLen, + void** backupHandle, + wchar_t* errorMessage, + unsigned int errorBufferLen) +{ + //MessageBox(0, L"backup err", L"", 0); */ + *backupHandle = NULL; + HRESULT hr = NULL; + + IVssBackupComponents* pBackupComponents = NULL; + if (FAILED(hr = CreateVssBackupComponents(&pBackupComponents))) + { + if (hr == E_ACCESSDENIED) + writeErrorMsg(L"The caller does not have sufficient backup privileges or is not an administrator.", hr, errorMessage, errorBufferLen); + else + writeErrorMsg(L"Error calling \"CreateVssBackupComponents\".", hr, errorMessage, errorBufferLen); + return false; + } + + + if (FAILED(hr = pBackupComponents->InitializeForBackup())) + { + releaseShadowCopy(pBackupComponents); + writeErrorMsg(L"Error calling \"InitializeForBackup\".", hr, errorMessage, errorBufferLen); + return false; + } + + + IVssAsync* pWriteMetaData = NULL; + if (FAILED(hr = pBackupComponents->GatherWriterMetadata( &pWriteMetaData ))) + { + releaseShadowCopy(pBackupComponents); + writeErrorMsg(L"\"Shadow.dll\" might be incompatible with this Operating System!\n\ + Error calling \"GatherWriterMetadata\".", hr, errorMessage, errorBufferLen); + return false; + } + + //wait for shadow copy writers to complete + hr = pWriteMetaData->Wait(); + pWriteMetaData->Release(); + if (FAILED(hr)) + { + releaseShadowCopy(pBackupComponents); + writeErrorMsg(L"Error calling \"ppWriteMetaData->Wait\".", hr, errorMessage, errorBufferLen); + return false; + } + + + VSS_ID snapshotSetId = {0}; + if (FAILED(hr = pBackupComponents->StartSnapshotSet( &snapshotSetId ))) + { + releaseShadowCopy(pBackupComponents); + writeErrorMsg(L"Error calling \"StartSnapshotSet\".", hr, errorMessage, errorBufferLen); + return false; + } + + + VSS_ID SnapShotId = {0}; + if (FAILED(hr = pBackupComponents->AddToSnapshotSet(const_cast(volumeName), GUID_NULL, &SnapShotId))) + { + releaseShadowCopy(pBackupComponents); + writeErrorMsg(L"Error calling \"AddToSnapshotSet\".", hr, errorMessage, errorBufferLen); + return false; + } + + + if (FAILED(hr = pBackupComponents->SetBackupState( false, false, VSS_BT_FULL ))) + { + releaseShadowCopy(pBackupComponents); + writeErrorMsg(L"Error calling \"SetBackupState\".", hr, errorMessage, errorBufferLen); + return false; + } + + + IVssAsync* pPrepare = NULL; + if (FAILED(hr = pBackupComponents->PrepareForBackup( &pPrepare ))) + { + releaseShadowCopy(pBackupComponents); + writeErrorMsg(L"Error calling \"PrepareForBackup\".", hr, errorMessage, errorBufferLen); + return false; + } + + hr = pPrepare->Wait(); + pPrepare->Release(); + if (FAILED(hr)) + { + releaseShadowCopy(pBackupComponents); + writeErrorMsg(L"Error calling \"pPrepare->Wait\".", hr, errorMessage, errorBufferLen); + return false; + } + + + IVssAsync* pDoShadowCopy = NULL; + if (FAILED(hr = pBackupComponents->DoSnapshotSet( &pDoShadowCopy ))) + { + releaseShadowCopy(pBackupComponents); + writeErrorMsg(L"Error calling \"DoSnapshotSet\".", hr, errorMessage, errorBufferLen); + return false; + } + + hr = pDoShadowCopy->Wait(); + pDoShadowCopy->Release(); + if (FAILED(hr)) + { + releaseShadowCopy(pBackupComponents); + writeErrorMsg(L"Error calling \"pPrepare->Wait\".", hr, errorMessage, errorBufferLen); + return false; + } + + VSS_SNAPSHOT_PROP props; + if (FAILED(hr = pBackupComponents->GetSnapshotProperties( SnapShotId, &props ))) + { + releaseShadowCopy(pBackupComponents); + writeErrorMsg(L"Error calling \"GetSnapshotProperties\".", hr, errorMessage, errorBufferLen); + return false; + } + + //finally: write volume name of newly created shadow copy + writeString(props.m_pwszSnapshotDeviceObject, shadowVolName, shadowBufferLen); + + VssFreeSnapshotProperties(&props); + + *backupHandle = pBackupComponents; + + return true; +} + + +void shadow::releaseShadowCopy(void* backupHandle) +{ + if (backupHandle != NULL) + static_cast(backupHandle)->Release(); +} diff --git a/library/ShadowCopy/shadow.h b/library/ShadowCopy/shadow.h new file mode 100644 index 00000000..e25c6a32 --- /dev/null +++ b/library/ShadowCopy/shadow.h @@ -0,0 +1,33 @@ +#ifndef SHADOWCOPY_H +#define SHADOWCOPY_H + +#ifdef SHADOWDLL_EXPORTS +#define SHADOWDLL_API extern "C" __declspec(dllexport) +#else +#define SHADOWDLL_API extern "C" __declspec(dllimport) +#endif + + +namespace shadow +{ + //COM needs to be initialized before calling any of these functions! CoInitializeEx/CoUninitialize + + + //volumeName must end with "\", while shadowVolName does not end with "\" + SHADOWDLL_API + bool createShadowCopy(const wchar_t* volumeName, + wchar_t* shadowVolName, + unsigned int shadowBufferLen, + void** backupHandle, + wchar_t* errorMessage, + unsigned int errorBufferLen); + + + //don't forget to release the backupHandle after shadow copy is not needed anymore! + SHADOWDLL_API + void releaseShadowCopy(void* backupHandle); +} + + + +#endif //SHADOWCOPY_H diff --git a/library/fileError.h b/library/fileError.h new file mode 100644 index 00000000..214cdecb --- /dev/null +++ b/library/fileError.h @@ -0,0 +1,25 @@ +#ifndef FILEERROR_H_INCLUDED +#define FILEERROR_H_INCLUDED + +#include "zstring.h" +#include "fileError.h" + +namespace FreeFileSync +{ + class FileError //Exception class used to notify file/directory copy/delete errors + { + public: + FileError(const Zstring& message) : + errorMessage(message) {} + + const Zstring& show() const + { + return errorMessage; + } + + private: + Zstring errorMessage; + }; +} + +#endif // FILEERROR_H_INCLUDED diff --git a/library/fileHandling.cpp b/library/fileHandling.cpp index b6d3d9d7..06431b81 100644 --- a/library/fileHandling.cpp +++ b/library/fileHandling.cpp @@ -7,6 +7,7 @@ #ifdef FFS_WIN #include //includes "windows.h" +#include "shadow.h" #elif defined FFS_LINUX #include @@ -18,75 +19,76 @@ #include #endif +using FreeFileSync::FileError; + class RecycleBin { public: - RecycleBin() : - recycleBinAvailable(false) + static const RecycleBin& getInstance() { -#ifdef FFS_WIN - recycleBinAvailable = true; -#endif // FFS_WIN + static RecycleBin instance; //lazy creation of RecycleBin + return instance; } - ~RecycleBin() {} - - bool recycleBinExists() + bool recycleBinExists() const { return recycleBinAvailable; } - bool moveToRecycleBin(const Zstring& filename) - { - if (!recycleBinAvailable) //this method should ONLY be called if recycle bin is available - throw RuntimeException(_("Initialization of Recycle Bin failed!")); + bool moveToRecycleBin(const Zstring& filename) const; +private: + RecycleBin() : + recycleBinAvailable(false) + { #ifdef FFS_WIN - Zstring filenameDoubleNull = filename + wxChar(0); - - SHFILEOPSTRUCT fileOp; - fileOp.hwnd = NULL; - fileOp.wFunc = FO_DELETE; - fileOp.pFrom = filenameDoubleNull.c_str(); - fileOp.pTo = NULL; - fileOp.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_SILENT | FOF_NOERRORUI; - fileOp.fAnyOperationsAborted = false; - fileOp.hNameMappings = NULL; - fileOp.lpszProgressTitle = NULL; - - if (SHFileOperation(&fileOp //pointer to an SHFILEOPSTRUCT structure that contains information the function needs to carry out - ) != 0 || fileOp.fAnyOperationsAborted) return false; -#else - assert(false); -#endif - - return true; + recycleBinAvailable = true; +#endif // FFS_WIN } + ~RecycleBin() {} + private: bool recycleBinAvailable; }; -inline -RecycleBin& getRecycleBin() +bool RecycleBin::moveToRecycleBin(const Zstring& filename) const { - static RecycleBin instance; //lazy creation of RecycleBin - return instance; + if (!recycleBinAvailable) //this method should ONLY be called if recycle bin is available + throw RuntimeException(_("Initialization of Recycle Bin failed!")); + +#ifdef FFS_WIN + Zstring filenameDoubleNull = filename + wxChar(0); + + SHFILEOPSTRUCT fileOp; + fileOp.hwnd = NULL; + fileOp.wFunc = FO_DELETE; + fileOp.pFrom = filenameDoubleNull.c_str(); + fileOp.pTo = NULL; + fileOp.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_SILENT | FOF_NOERRORUI; + fileOp.fAnyOperationsAborted = false; + fileOp.hNameMappings = NULL; + fileOp.lpszProgressTitle = NULL; + + if (SHFileOperation(&fileOp) != 0 || fileOp.fAnyOperationsAborted) return false; +#endif + + return true; } bool FreeFileSync::recycleBinExists() { - return getRecycleBin().recycleBinExists(); + return RecycleBin::getInstance().recycleBinExists(); } inline bool moveToRecycleBin(const Zstring& filename) throw(RuntimeException) { - return getRecycleBin().moveToRecycleBin(filename); + return RecycleBin::getInstance().moveToRecycleBin(filename); } @@ -286,7 +288,7 @@ private: class KernelDllHandler //dynamically load windows API functions { - typedef DWORD WINAPI (*GetFinalPath)( + typedef DWORD (WINAPI *GetFinalPath)( HANDLE hFile, LPTSTR lpszFilePath, DWORD cchFilePath, @@ -365,11 +367,11 @@ void createDirectoryRecursively(const Zstring& directory, const Zstring& templat return; //try to create parent folders first - const Zstring dirParent = directory.BeforeLast(GlobalResources::FILE_NAME_SEPARATOR); + const Zstring dirParent = directory.BeforeLast(FreeFileSync::FILE_NAME_SEPARATOR); if (!dirParent.empty() && !wxDirExists(dirParent)) { //call function recursively - const Zstring templateParent = templateDir.BeforeLast(GlobalResources::FILE_NAME_SEPARATOR); + const Zstring templateParent = templateDir.BeforeLast(FreeFileSync::FILE_NAME_SEPARATOR); createDirectoryRecursively(dirParent, templateParent, false, level + 1); //don't create symbolic links in recursion! } @@ -488,13 +490,13 @@ void FreeFileSync::createDirectory(const Zstring& directory, const Zstring& temp //remove trailing separator Zstring dirFormatted; if (FreeFileSync::endsWithPathSeparator(directory)) - dirFormatted = directory.BeforeLast(GlobalResources::FILE_NAME_SEPARATOR); + dirFormatted = directory.BeforeLast(FreeFileSync::FILE_NAME_SEPARATOR); else dirFormatted = directory; Zstring templateFormatted; if (FreeFileSync::endsWithPathSeparator(templateDir)) - templateFormatted = templateDir.BeforeLast(GlobalResources::FILE_NAME_SEPARATOR); + templateFormatted = templateDir.BeforeLast(FreeFileSync::FILE_NAME_SEPARATOR); else templateFormatted = templateDir; @@ -502,7 +504,95 @@ void FreeFileSync::createDirectory(const Zstring& directory, const Zstring& temp } -#ifdef FFS_LINUX +#ifdef FFS_WIN + +#ifndef COPY_FILE_COPY_SYMLINK +const DWORD COPY_FILE_COPY_SYMLINK = 0x00000800; +#endif + +DWORD CALLBACK copyCallbackInternal( + LARGE_INTEGER totalFileSize, + LARGE_INTEGER totalBytesTransferred, + LARGE_INTEGER streamSize, + LARGE_INTEGER streamBytesTransferred, + DWORD dwStreamNumber, + DWORD dwCallbackReason, + HANDLE hSourceFile, + HANDLE hDestinationFile, + LPVOID lpData) +{ + using FreeFileSync::CopyFileCallback; + + //small performance optimization: it seems this callback function is called for every 64 kB (depending on cluster size). + static unsigned int callNr = 0; + if (++callNr % 50 == 0) //reduce by factor of 50 =^ 10-20 calls/sec + { + if (lpData != NULL) + { + //some odd check for some possible(?) error condition + if (totalBytesTransferred.HighPart < 0) //let's see if someone answers the call... + wxMessageBox(wxT("You've just discovered a bug in WIN32 API function \"CopyFileEx\"! \n\n\ + Please write a mail to the author of FreeFileSync at zhnmju123@gmx.de and simply state that\n\ + \"totalBytesTransferred.HighPart can be below zero\"!\n\n\ + This will then be handled in future versions of FreeFileSync.\n\nThanks -ZenJu"), wxT("Warning")); + + + CopyFileCallback* callback = static_cast(lpData); + + switch (callback->updateCopyStatus(wxULongLong(totalBytesTransferred.HighPart, totalBytesTransferred.LowPart))) + { + case CopyFileCallback::CONTINUE: + break; + case CopyFileCallback::CANCEL: + return PROGRESS_CANCEL; + } + } + } + + return PROGRESS_CONTINUE; +} + + +void FreeFileSync::copyFile(const Zstring& sourceFile, + const Zstring& targetFile, + const bool copyFileSymLinks, + ShadowCopy* shadowCopyHandler, + CopyFileCallback* callback) +{ + DWORD copyFlags = COPY_FILE_FAIL_IF_EXISTS; + + if (copyFileSymLinks) //copy symbolic links instead of the files pointed at + copyFlags |= COPY_FILE_COPY_SYMLINK; + + if (!::CopyFileEx( //same performance as CopyFile() + sourceFile.c_str(), + targetFile.c_str(), + copyCallbackInternal, + callback, + NULL, + copyFlags)) + { + const DWORD lastError = ::GetLastError(); + + //if file is locked (try to) use Windows Volume Shadow Copy Service + if (lastError == ERROR_SHARING_VIOLATION && shadowCopyHandler != NULL) + { + const Zstring shadowFilename(shadowCopyHandler->makeShadowCopy(sourceFile)); + FreeFileSync::copyFile(shadowFilename, //transferred bytes is automatically reset when new file is copied + targetFile, + copyFileSymLinks, + shadowCopyHandler, + callback); + return; + } + + const Zstring errorMessage = Zstring(_("Error copying file:")) + wxT("\n\"") + sourceFile + wxT("\" -> \"") + targetFile + wxT("\""); + throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted(lastError)); + } +} + + +#elif defined FFS_LINUX struct MemoryAllocator { MemoryAllocator() @@ -523,9 +613,10 @@ struct MemoryAllocator void FreeFileSync::copyFile(const Zstring& sourceFile, const Zstring& targetFile, const bool copyFileSymLinks, - CopyFileCallback callback, - void* data) + CopyFileCallback* callback) { + using FreeFileSync::CopyFileCallback; + try { if (FreeFileSync::fileExists(targetFile.c_str())) @@ -609,8 +700,18 @@ void FreeFileSync::copyFile(const Zstring& sourceFile, totalBytesTransferred += memory.bufferSize; //invoke callback method to update progress indicators - if (callback) - callback(totalBytesTransferred, data); + if (callback != NULL) + { + switch (callback->updateCopyStatus(totalBytesTransferred)) + { + case CopyFileCallback::CONTINUE: + break; + case CopyFileCallback::CANCEL: + fileOut.close(); + wxRemoveFile(targetFile.c_str()); //don't handle error situations! + return; + } + } } //close streams before changing attributes @@ -741,7 +842,7 @@ public: //ensure directoryFormatted ends with backslash const Zstring directoryFormatted = FreeFileSync::endsWithPathSeparator(directory) ? directory : - directory + GlobalResources::FILE_NAME_SEPARATOR; + directory + FreeFileSync::FILE_NAME_SEPARATOR; WIN32_FIND_DATA fileMetaData; HANDLE searchHandle = FindFirstFile((directoryFormatted + DefaultChar('*')).c_str(), //pointer to name of file to search for @@ -847,7 +948,7 @@ public: const Zstring fullName = FreeFileSync::endsWithPathSeparator(directory) ? //e.g. "/" directory + name : - directory + GlobalResources::FILE_NAME_SEPARATOR + name; + directory + FreeFileSync::FILE_NAME_SEPARATOR + name; struct stat fileInfo; if (lstat(fullName.c_str(), &fileInfo) != 0) //lstat() does not resolve symlinks @@ -929,7 +1030,7 @@ void FreeFileSync::traverseInDetail(const Zstring& directory, Zstring directoryFormatted = directory; #ifdef FFS_LINUX //remove trailing slash if (directoryFormatted.size() > 1 && FreeFileSync::endsWithPathSeparator(directoryFormatted)) - directoryFormatted = directoryFormatted.BeforeLast(GlobalResources::FILE_NAME_SEPARATOR); + directoryFormatted = directoryFormatted.BeforeLast(FreeFileSync::FILE_NAME_SEPARATOR); #endif if (traverseDirectorySymlinks) @@ -954,7 +1055,7 @@ Zstring getDriveName(const Zstring& directoryName) //GetVolume() doesn't work un if (volumeName.empty()) return Zstring(); - return volumeName + wxFileName::GetVolumeSeparator().c_str() + GlobalResources::FILE_NAME_SEPARATOR; + return volumeName + wxFileName::GetVolumeSeparator().c_str() + FreeFileSync::FILE_NAME_SEPARATOR; } diff --git a/library/fileHandling.h b/library/fileHandling.h index de068d1f..3abdbe07 100644 --- a/library/fileHandling.h +++ b/library/fileHandling.h @@ -3,21 +3,7 @@ #include #include "zstring.h" - -class FileError //Exception class used to notify file/directory copy/delete errors -{ -public: - FileError(const Zstring& message) : - errorMessage(message) {} - - const Zstring& show() const - { - return errorMessage; - } - -private: - Zstring errorMessage; -}; +#include "fileError.h" namespace FreeFileSync @@ -50,15 +36,34 @@ namespace FreeFileSync void removeDirectory(const Zstring& directory, const bool useRecycleBin); void removeFile(const Zstring& filename, const bool useRecycleBin); 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); + + class CopyFileCallback //callback functionality + { + public: + virtual ~CopyFileCallback() {} + + enum Response + { + CONTINUE, + CANCEL + }; + virtual Response updateCopyStatus(const wxULongLong& totalBytesTransferred) = 0; + }; + + class ShadowCopy; +#ifdef FFS_WIN void copyFile(const Zstring& sourceFile, const Zstring& targetFile, const bool copyFileSymLinks, - CopyFileCallback callback = NULL, - void* data = NULL); + ShadowCopy* shadowCopyHandler = NULL, //supply handler for making shadow copies + CopyFileCallback* callback = NULL); + +#elif defined FFS_LINUX + void copyFile(const Zstring& sourceFile, + const Zstring& targetFile, + const bool copyFileSymLinks, + CopyFileCallback* callback); #endif } diff --git a/library/filter.cpp b/library/filter.cpp index d9ba8c5f..65513630 100644 --- a/library/filter.cpp +++ b/library/filter.cpp @@ -4,6 +4,7 @@ #include #include #include "resources.h" +#include "globalFunctions.h" using FreeFileSync::FilterProcess; @@ -36,14 +37,6 @@ void compoundStringToTable(const Zstring& compoundInput, const DefaultChar* deli } -inline -void mergeVectors(std::vector& changing, const std::vector& input) -{ - for (std::vector::const_iterator i = input.begin(); i != input.end(); ++i) - changing.push_back(*i); -} - - std::vector compoundStringToFilter(const Zstring& filterString) { //delimiters may be ';' or '\n' @@ -55,7 +48,8 @@ std::vector compoundStringToFilter(const Zstring& filterString) { std::vector newEntries; compoundStringToTable(*i, wxT("\n"), newEntries); - mergeVectors(filterList, newEntries); + + globalFunctions::mergeVectors(newEntries, filterList); } return filterList; @@ -76,12 +70,12 @@ void addFilterEntry(const Zstring& filtername, std::set& fileFilter, st #endif //remove leading separators (keep BEFORE test for Zstring::empty()!) - if (filterFormatted.length() > 0 && *filterFormatted.c_str() == GlobalResources::FILE_NAME_SEPARATOR) + if (filterFormatted.length() > 0 && *filterFormatted.c_str() == FreeFileSync::FILE_NAME_SEPARATOR) filterFormatted = Zstring(filterFormatted.c_str() + 1); if (!filterFormatted.empty()) { - //Test if filterFormatted ends with GlobalResources::FILE_NAME_SEPARATOR, ignoring '*' and '?'. + //Test if filterFormatted ends with FreeFileSync::FILE_NAME_SEPARATOR, ignoring '*' and '?'. //If so, treat as filter for directory and add to directoryFilter. const DefaultChar* filter = filterFormatted.c_str(); int i = filterFormatted.length() - 1; @@ -93,7 +87,7 @@ void addFilterEntry(const Zstring& filtername, std::set& fileFilter, st break; } - if (i >= 0 && filter[i] == GlobalResources::FILE_NAME_SEPARATOR) //last FILE_NAME_SEPARATOR found + if (i >= 0 && filter[i] == FreeFileSync::FILE_NAME_SEPARATOR) //last FILE_NAME_SEPARATOR found { if (i != int(filterFormatted.length()) - 1) // "name\*" { @@ -124,7 +118,7 @@ bool matchesFilter(const DefaultChar* name, const std::set& filter) const DefaultChar* const nameFormatted = name; //nothing to do here #endif - for (std::set::iterator j = filter.begin(); j != filter.end(); ++j) + for (std::set::const_iterator j = filter.begin(); j != filter.end(); ++j) if (Zstring::Matches(nameFormatted, *j)) return true; @@ -140,39 +134,43 @@ FilterProcess::FilterProcess(const wxString& includeFilter, const wxString& excl //load filter into vectors of strings //delimiters may be ';' or '\n' - std::vector includeList = compoundStringToFilter(includeFilter.c_str()); - std::vector excludeList = compoundStringToFilter(excludeFilter.c_str()); + const std::vector includeList = compoundStringToFilter(includeFilter.c_str()); + const std::vector excludeList = compoundStringToFilter(excludeFilter.c_str()); //setup include/exclude filters for files and directories - for (std::vector::iterator i = includeList.begin(); i != includeList.end(); ++i) + for (std::vector::const_iterator i = includeList.begin(); i != includeList.end(); ++i) addFilterEntry(*i, filterFileIn, filterFolderIn); - for (std::vector::iterator i = excludeList.begin(); i != excludeList.end(); ++i) + for (std::vector::const_iterator i = excludeList.begin(); i != excludeList.end(); ++i) addFilterEntry(*i, filterFileEx, filterFolderEx); } -bool FilterProcess::matchesFileFilter(const DefaultChar* relFilename) const +bool FilterProcess::matchesFileFilterIncl(const DefaultChar* relFilename) const +{ + return matchesFilter(relFilename, filterFileIn); //process include filters +} + + +bool FilterProcess::matchesFileFilterExcl(const DefaultChar* relFilename) const +{ + return matchesFilter(relFilename, filterFileEx); //process exclude filters +} + + +bool FilterProcess::matchesDirFilterIncl(const DefaultChar* relDirname) const { - if ( matchesFilter(relFilename, filterFileIn) && //process include filters - !matchesFilter(relFilename, filterFileEx)) //process exclude filters - return true; - else - return false; + return matchesFilter(relDirname, filterFolderIn); //process include filters } -bool FilterProcess::matchesDirFilter(const DefaultChar* relDirname) const +bool FilterProcess::matchesDirFilterExcl(const DefaultChar* relDirname) const { - if ( matchesFilter(relDirname, filterFolderIn) && //process include filters - !matchesFilter(relDirname, filterFolderEx)) //process exclude filters - return true; - else - return false; + return matchesFilter(relDirname, filterFolderEx); //process exclude filters } -void FilterProcess::filterGridData(FolderComparison& folderCmp) const +void FilterProcess::filterGridData(FreeFileSync::FolderComparison& folderCmp) const { //execute filtering... for (FolderComparison::iterator j = folderCmp.begin(); j != folderCmp.end(); ++j) @@ -181,40 +179,31 @@ void FilterProcess::filterGridData(FolderComparison& folderCmp) const for (FileComparison::iterator i = fileCmp.begin(); i != fileCmp.end(); ++i) { - //left hand side - if (i->fileDescrLeft.objType == FileDescrLine::TYPE_FILE) - { - if (!matchesFileFilter(i->fileDescrLeft.relativeName.c_str())) - { - i->selectedForSynchronization = false; - continue; - } - } - else if (i->fileDescrLeft.objType == FileDescrLine::TYPE_DIRECTORY) - { - if (!matchesDirFilter(i->fileDescrLeft.relativeName.c_str())) - { - i->selectedForSynchronization = false; - continue; - } - } - //right hand side - else if (i->fileDescrRight.objType == FileDescrLine::TYPE_FILE) + + const FileDescrLine& fileDescr = i->fileDescrLeft.objType != FileDescrLine::TYPE_NOTHING ? + i->fileDescrLeft : + i->fileDescrRight; + + if (fileDescr.objType == FileDescrLine::TYPE_FILE) { - if (!matchesFileFilter(i->fileDescrRight.relativeName.c_str())) + if ( !matchesFileFilterIncl(fileDescr.relativeName.c_str()) || + matchesFileFilterExcl(fileDescr.relativeName.c_str())) { i->selectedForSynchronization = false; continue; } } - else if (i->fileDescrRight.objType == FileDescrLine::TYPE_DIRECTORY) + else if (fileDescr.objType == FileDescrLine::TYPE_DIRECTORY) { - if (!matchesDirFilter(i->fileDescrRight.relativeName.c_str())) + if ( !matchesDirFilterIncl(fileDescr.relativeName.c_str()) || + matchesDirFilterExcl(fileDescr.relativeName.c_str())) { i->selectedForSynchronization = false; continue; } } + else + assert(false); i->selectedForSynchronization = true; } @@ -236,14 +225,14 @@ void inOrExcludeAllRows(FreeFileSync::FolderComparison& folderCmp) } -void FilterProcess::includeAllRowsOnGrid(FolderComparison& folderCmp) +void FilterProcess::includeAllRowsOnGrid(FreeFileSync::FolderComparison& folderCmp) { //remove all filters on currentGridData inOrExcludeAllRows(folderCmp); } -void FilterProcess::excludeAllRowsOnGrid(FolderComparison& folderCmp) +void FilterProcess::excludeAllRowsOnGrid(FreeFileSync::FolderComparison& folderCmp) { //exclude all rows on currentGridData inOrExcludeAllRows(folderCmp); diff --git a/library/filter.h b/library/filter.h index dd716680..85df591c 100644 --- a/library/filter.h +++ b/library/filter.h @@ -11,8 +11,10 @@ namespace FreeFileSync public: FilterProcess(const wxString& includeFilter, const wxString& excludeFilter); - bool matchesFileFilter(const DefaultChar* relFilename) const; - bool matchesDirFilter(const DefaultChar* relDirname) const; + bool matchesFileFilterIncl(const DefaultChar* relFilename) const; + bool matchesFileFilterExcl(const DefaultChar* relFilename) const; + bool matchesDirFilterIncl(const DefaultChar* relDirname) const; + bool matchesDirFilterExcl(const DefaultChar* relDirname) const; void filterGridData(FolderComparison& folderCmp) const; diff --git a/library/globalFunctions.cpp b/library/globalFunctions.cpp index 7c4a1b92..33932d67 100644 --- a/library/globalFunctions.cpp +++ b/library/globalFunctions.cpp @@ -1,40 +1,10 @@ #include "globalFunctions.h" -#include "resources.h" #include #include #include - - -std::string globalFunctions::numberToString(const unsigned int number) -{ - char result[100]; - sprintf(result, "%u", number); - return std::string(result); -} - - -std::string globalFunctions::numberToString(const int number) -{ - char result[100]; - sprintf(result, "%d", number); - return std::string(result); -} - - -std::string globalFunctions::numberToString(const long number) -{ - char result[100]; - sprintf(result, "%ld", number); - return std::string(result); -} - - -std::string globalFunctions::numberToString(const float number) -{ - char result[100]; - sprintf(result, "%f", number); - return std::string(result); -} +#include "../structures.h" +#include +#include wxString globalFunctions::numberToWxString(const unsigned int number) @@ -97,7 +67,7 @@ wxString globalFunctions::includeNumberSeparator(const wxString& number) { wxString output(number); for (int i = output.size() - 3; i > 0; i -= 3) - output.insert(i, GlobalResources::THOUSANDS_SEPARATOR); + output.insert(i, FreeFileSync::THOUSANDS_SEPARATOR); return output; } @@ -136,9 +106,10 @@ void globalFunctions::writeInt(wxOutputStream& stream, const int number) //############################################################################ Performance::Performance() : - resultWasShown(false) + resultWasShown(false), + timer(new wxStopWatch) { - timer.Start(); + timer->Start(); } @@ -152,8 +123,8 @@ Performance::~Performance() void Performance::showResult() { resultWasShown = true; - wxMessageBox(globalFunctions::numberToWxString(unsigned(timer.Time())) + wxT(" ms")); - timer.Start(); //reset timer + wxMessageBox(globalFunctions::numberToWxString(unsigned(timer->Time())) + wxT(" ms")); + timer->Start(); //reset timer } @@ -203,5 +174,5 @@ void DebugLog::write(const wxString& logText) wxString getCodeLocation(const wxString file, const int line) { - return wxString(file).AfterLast(GlobalResources::FILE_NAME_SEPARATOR) + wxT(", LINE ") + globalFunctions::numberToWxString(line) + wxT(" | "); + return wxString(file).AfterLast(FreeFileSync::FILE_NAME_SEPARATOR) + wxT(", LINE ") + globalFunctions::numberToWxString(line) + wxT(" | "); } diff --git a/library/globalFunctions.h b/library/globalFunctions.h index 622babcb..689fa5f1 100644 --- a/library/globalFunctions.h +++ b/library/globalFunctions.h @@ -6,9 +6,14 @@ #include #include #include -#include -#include #include +#include +#include + +class wxInputStream; +class wxOutputStream; +class wxStopWatch; + namespace globalFunctions { @@ -25,10 +30,13 @@ namespace globalFunctions return(d < 0 ? -d : d); } - std::string numberToString(const unsigned int number); //convert number to string - std::string numberToString(const int number); //convert number to string - std::string numberToString(const long number); //convert number to string - std::string numberToString(const float number); //convert number to string + template + inline std::string numberToString(const T& number) //convert number to string the C++ way + { + std::stringstream ss; + ss << number; + return ss.str(); + } wxString numberToWxString(const unsigned int number); //convert number to wxString wxString numberToWxString(const int number); //convert number to wxString @@ -54,6 +62,43 @@ namespace globalFunctions { return wxLongLong(number.GetHi(), number.GetLo()); } + + + //Note: the following lines are a performance optimization for deleting elements from a vector. It is incredibly faster to create a new +//vector and leave specific elements out than to delete row by row and force recopying of most elements for each single deletion (linear vs quadratic runtime) + template + void removeRowsFromVector(const std::set& rowsToRemove, std::vector& grid) + { + if (rowsToRemove.size() > 0) + { + std::vector temp; + + std::set::const_iterator rowToSkipIndex = rowsToRemove.begin(); + int rowToSkip = *rowToSkipIndex; + + for (int i = 0; i < int(grid.size()); ++i) + { + if (i != rowToSkip) + temp.push_back(grid[i]); + else + { + ++rowToSkipIndex; + if (rowToSkipIndex != rowsToRemove.end()) + rowToSkip = *rowToSkipIndex; + } + } + grid.swap(temp); + } + } + + + template + inline + void mergeVectors(const std::vector& input, std::vector& changing) + { + for (typename std::vector::const_iterator i = input.begin(); i != input.end(); ++i) //nested dependent typename: see Meyers effective C++ + changing.push_back(*i); + } } @@ -67,7 +112,7 @@ public: private: bool resultWasShown; - wxStopWatch timer; + std::auto_ptr timer; }; //two macros for quick performance measurements @@ -106,7 +151,6 @@ public: wxString show() const { - return errorMessage; } @@ -115,32 +159,4 @@ private: }; -//Note: the following lines are a performance optimization for deleting elements from a vector. It is incredibly faster to create a new -//vector and leave specific elements out than to delete row by row and force recopying of most elements for each single deletion (linear vs quadratic runtime) -template -void removeRowsFromVector(std::vector& grid, const std::set& rowsToRemove) -{ - if (rowsToRemove.size() > 0) - { - std::vector temp; - - std::set::iterator rowToSkipIndex = rowsToRemove.begin(); - int rowToSkip = *rowToSkipIndex; - - for (int i = 0; i < int(grid.size()); ++i) - { - if (i != rowToSkip) - temp.push_back(grid[i]); - else - { - ++rowToSkipIndex; - if (rowToSkipIndex != rowsToRemove.end()) - rowToSkip = *rowToSkipIndex; - } - } - grid.swap(temp); - } -} - - #endif // GLOBALFUNCTIONS_H_INCLUDED diff --git a/library/iconBuffer.cpp b/library/iconBuffer.cpp new file mode 100644 index 00000000..a969f689 --- /dev/null +++ b/library/iconBuffer.cpp @@ -0,0 +1,298 @@ +#include "iconBuffer.h" +#include +#include "globalFunctions.h" +#include +#include +#include //includes "windows.h" +#include +#include "../algorithm.h" +#include +#include +#include + +using FreeFileSync::IconBuffer; + + +class BasicString //simple thread safe string class +{ +public: + BasicString() + { + data = new DefaultChar[1]; //compliance with delete [] + *data = 0; //compliance with c_str() + } + + BasicString(const Zstring& other) //make DEEP COPY from Zstring! + { + data = new DefaultChar[other.length() + 1]; + memcpy(data, other.c_str(), (other.length() + 1) * sizeof(DefaultChar)); + } + + BasicString(const BasicString& other) + { + const size_t otherLen = defaultLength(other.c_str()); + data = new DefaultChar[otherLen + 1]; + memcpy(data, other.c_str(), (otherLen + 1) * sizeof(DefaultChar)); + } + + ~BasicString() + { + delete [] data; + } + + BasicString& operator=(const BasicString& other) + { //exception safety; handle self-assignment implicitly + const size_t otherLen = defaultLength(other.c_str()); + DefaultChar* dataNew = new DefaultChar[otherLen + 1]; + memcpy(dataNew, other.c_str(), (otherLen + 1) * sizeof(DefaultChar)); + + delete data; + data = dataNew; + + return *this; + } + + const DefaultChar* c_str() const + { + return data; + } + +private: + DefaultChar* data; +}; + + +class WorkerThread : public wxThread +{ +public: + WorkerThread(IconBuffer* iconBuff); + + void setWorkload(const std::vector& load); //(re-)set new workload of icons to be retrieved + void quitThread(); + + virtual ExitCode Entry(); + +private: + void doWork(); + +//---------------------- Shared Data ------------------------- + typedef BasicString FileName; + + wxCriticalSection lockWorkload; //use for locking shared data + std::vector workload; //processes last elements of vector first! + bool threadHasMutex; + bool threadExitIsRequested; +//------------------------------------------------------------ + + //event: icon buffer -> woker thread + wxMutex threadIsListening; + wxCondition continueWork; //wake up thread + + IconBuffer* iconBuffer; +}; + + +WorkerThread::WorkerThread(IconBuffer* iconBuff) : + wxThread(wxTHREAD_JOINABLE), + threadHasMutex(false), + threadExitIsRequested(false), + threadIsListening(), + continueWork(threadIsListening), + iconBuffer(iconBuff) +{ + if (Create() != wxTHREAD_NO_ERROR) + throw RuntimeException(wxString(wxT("Error creating icon buffer worker thread!"))); + + if (Run() != wxTHREAD_NO_ERROR) + throw RuntimeException(wxString(wxT("Error starting icon buffer worker thread!"))); + + //wait until thread has aquired mutex + bool hasMutex = false; + while (!hasMutex) + { + wxMilliSleep(5); + wxCriticalSectionLocker dummy(lockWorkload); + hasMutex = threadHasMutex; + } //polling -> it's not nice, but works and is no issue +} + + +void WorkerThread::setWorkload(const std::vector& load) //(re-)set new workload of icons to be retrieved +{ + wxCriticalSectionLocker dummy(lockWorkload); + + workload.clear(); + for (std::vector::const_iterator i = load.begin(); i != load.end(); ++i) + workload.push_back(*i); //make DEEP COPY from Zstring! + + if (!workload.empty()) + continueWork.Signal(); //wake thread IF he is waiting +} + + +void WorkerThread::quitThread() +{ + wxMutexLocker dummy(threadIsListening); //wait until thread is in waiting state + threadExitIsRequested = true; //no sharing conflicts in this situation + continueWork.Signal(); //exit thread +} + + +wxThread::ExitCode WorkerThread::Entry() +{ + try + { + wxMutexLocker dummy(threadIsListening); //this lock needs to be called from WITHIN the thread => calling it from constructor(Main thread) would be useless + { //this mutex STAYS locked all the time except of continueWork.Wait()! + wxCriticalSectionLocker dummy(lockWorkload); + threadHasMutex = true; + } + + while (true) + { + continueWork.Wait(); //waiting for continueWork.Signal(); unlocks Mutex "threadIsListening" + + //no mutex needed in this context + if (threadExitIsRequested) //no mutex here: atomicity is not prob for a bool, but visibility (e.g. caching in registers) + return 0; //shouldn't be a problem nevertheless because of implicit memory barrier caused by mutex.Lock() in .Wait() + + //do work: get the file icon. + doWork(); + } + } + catch (RuntimeException& e) //exceptions must be catched per thread + { + wxMessageBox(e.show()); + return 0; + } +} + + +void WorkerThread::doWork() +{ + BasicString fileName; //don't use Zstring: reference-counted objects are NOT THREADSAFE!!! e.g. double deletion might happen + + //do work: get the file icon. + while (true) + { + { + wxCriticalSectionLocker dummy(lockWorkload); + if (workload.empty()) + break; //enter waiting state + fileName = workload.back(); + workload.pop_back(); + } + + if (iconBuffer->requestIcon(Zstring(fileName.c_str()))) //thread safety: Zstring okay, won't be reference-counted in requestIcon() + break; //icon already in buffer: enter waiting state + + //load icon + SHFILEINFO fileInfo; + fileInfo.hIcon = 0; //initialize hIcon + + if (SHGetFileInfo(fileName.c_str(), //NOTE: CoInitializeEx()/CoUninitialize() implicitly called by wxWidgets on program startup! + 0, + &fileInfo, + sizeof(fileInfo), + SHGFI_ICON | SHGFI_SMALLICON) && + + fileInfo.hIcon != 0) //fix for weird error: SHGetFileInfo() might return successfully WITHOUT filling fileInfo.hIcon!! + { //bug report: https://sourceforge.net/tracker/?func=detail&aid=2768004&group_id=234430&atid=1093080 + + wxIcon newIcon; //attention: wxIcon uses reference counting! + newIcon.SetHICON(fileInfo.hIcon); + newIcon.SetSize(IconBuffer::ICON_SIZE, IconBuffer::ICON_SIZE); + + iconBuffer->insertIntoBuffer(fileName.c_str(), newIcon); //thread safety: icon may be deleted only within insertIntoBuffer() + + //freeing of icon handle seems to happen somewhere beyond wxIcon destructor + //if (!DestroyIcon(fileInfo.hIcon)) + // throw RuntimeException(wxString(wxT("Error deallocating Icon handle!\n\n")) + FreeFileSync::getLastErrorFormatted()); + + } + else + { + //if loading of icon fails for whatever reason, just save a dummy icon to avoid re-loading + iconBuffer->insertIntoBuffer(fileName.c_str(), wxNullIcon); + } + } +} + +//--------------------------------------------------------------------------------------------------- + +typedef Zstring FileName; +class IconDB : public std::map {}; +class IconDbSequence : public std::queue {}; + +//--------------------------------------------------------------------------------------------------- + + +IconBuffer& IconBuffer::getInstance() +{ + static IconBuffer instance; + return instance; +} + + +IconBuffer::IconBuffer() : + lockIconDB( new wxCriticalSection), + buffer( new IconDB), + bufSequence(new IconDbSequence), + worker( new WorkerThread(this)) //might throw exceptions! +{} + + +IconBuffer::~IconBuffer() +{ + worker->quitThread(); + worker->Wait(); //wait until thread has exitted +} + + +bool IconBuffer::requestIcon(const Zstring& fileName, wxIcon* icon) +{ + wxCriticalSectionLocker dummy(*lockIconDB); + IconDB::const_iterator i = buffer->find(fileName); + + if (i != buffer->end()) + { + if (icon != NULL) + *icon = i->second; + return true; + } + + return false; +} + + +void IconBuffer::setWorkload(const std::vector& load) +{ + worker->setWorkload(load); +} + + +void IconBuffer::insertIntoBuffer(const DefaultChar* fileName, const wxIcon& icon) //called by worker thread +{ + if (icon.IsOk()) //this check won't hurt + { + wxCriticalSectionLocker dummy(*lockIconDB); + + const Zstring fileNameZ = fileName; + + const std::pair rc = buffer->insert(IconDB::value_type(fileNameZ, icon)); + + if (rc.second) //if insertion took place + bufSequence->push(fileNameZ); + + assert(buffer->size() == bufSequence->size()); + + //remove elements if buffer becomes too big: + if (buffer->size() > 1000) //limit buffer size: critical because large buffers seem to cause various wxIcon/wxBitmap issues! + { + //remove oldest element + buffer->erase(bufSequence->front()); + bufSequence->pop(); + } + } +} + diff --git a/library/iconBuffer.h b/library/iconBuffer.h new file mode 100644 index 00000000..ba905d22 --- /dev/null +++ b/library/iconBuffer.h @@ -0,0 +1,51 @@ +#ifndef ICONBUFFER_H_INCLUDED +#define ICONBUFFER_H_INCLUDED + +#ifndef FFS_WIN +#warning //this header should be used in the windows build only! +#endif + +#include +#include "zstring.h" +#include + +class wxCriticalSection; +class WorkerThread; +class IconDB; +class IconDbSequence; +class wxIcon; + + +namespace FreeFileSync +{ + class IconBuffer + { + friend class ::WorkerThread; + + public: + static IconBuffer& getInstance(); + + bool requestIcon(const Zstring& fileName, wxIcon* icon = NULL); //returns false if icon is not in buffer + void setWorkload(const std::vector& load); //(re-)set new workload of icons to be retrieved; + + static const int ICON_SIZE = 16; //size in pixel + + private: + IconBuffer(); + ~IconBuffer(); + + //methods used by worker thread + void insertIntoBuffer(const DefaultChar* fileName, const wxIcon& icon); + +//---------------------- Shared Data ------------------------- + std::auto_ptr lockIconDB; + std::auto_ptr buffer; //use synchronisation when accessing this! +//------------------------------------------------------------ + + std::auto_ptr bufSequence; //save sequence of buffer entry to delte olderst elements + + std::auto_ptr worker; + }; +} + +#endif // ICONBUFFER_H_INCLUDED diff --git a/library/localization.cpp b/library/localization.cpp index ef433012..9b5918a8 100644 --- a/library/localization.cpp +++ b/library/localization.cpp @@ -1,25 +1,134 @@ -#include "localization.h" +#include "localization.h" #include -#include "resources.h" +#include "../structures.h" #include "globalFunctions.h" #include #include +#include +#include "resources.h" + +using FreeFileSync::CustomLocale; +using FreeFileSync::LocalizationInfo; //_("Browse") <- dummy string for wxDirPickerCtrl to be recognized by automatic text extraction! -struct TranslationLine +const std::vector& LocalizationInfo::getMapping() { - wxString original; - wxString translation; + static LocalizationInfo instance; + return instance.locMapping; +} - bool operator<(const TranslationLine& b) const - { - return (original < b.original); - } -}; -class Translation : public std::set {}; +LocalizationInfo::LocalizationInfo() +{ + FreeFileSync::LocInfoLine newEntry; + + newEntry.languageID = wxLANGUAGE_GERMAN; + newEntry.languageName = wxT("Deutsch"); + newEntry.languageFile = "Languages/german.lng"; + newEntry.translatorName = wxT("ZenJu"); + newEntry.languageFlag = GlobalResources::getInstance().bitmapGermany; + locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_ENGLISH; + newEntry.languageName = wxT("English"); + newEntry.languageFile = ""; + newEntry.translatorName = wxT("ZenJu"); + newEntry.languageFlag = GlobalResources::getInstance().bitmapEngland; + locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_SPANISH; + newEntry.languageName = wxT("Español"); + newEntry.languageFile = "Languages/spanish.lng"; + newEntry.translatorName = wxT("David Rodríguez"); + newEntry.languageFlag = GlobalResources::getInstance().bitmapSpain; + locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_FRENCH; + newEntry.languageName = wxT("Français"); + newEntry.languageFile = "Languages/french.lng"; + newEntry.translatorName = wxT("Jean-François Hartmann"); + newEntry.languageFlag = GlobalResources::getInstance().bitmapFrance; + locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_ITALIAN; + newEntry.languageName = wxT("Italiano"); + newEntry.languageFile = "Languages/italian.lng"; + newEntry.translatorName = wxT("Emmo"); + newEntry.languageFlag = GlobalResources::getInstance().bitmapItaly; + locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_HUNGARIAN; + newEntry.languageName = wxT("Magyar"); + newEntry.languageFile = "Languages/hungarian.lng"; + newEntry.translatorName = wxT("Demon"); + newEntry.languageFlag = GlobalResources::getInstance().bitmapHungary; + locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_DUTCH; + newEntry.languageName = wxT("Nederlands"); + newEntry.languageFile = "Languages/dutch.lng"; + newEntry.translatorName = wxT("M.D. Vrakking"); + newEntry.languageFlag = GlobalResources::getInstance().bitmapHolland; + locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_RUSSIAN; + newEntry.languageName = wxT("Pусский язык"); + newEntry.languageFile = "Languages/russian.lng"; + newEntry.translatorName = wxT("Svobodniy"); + newEntry.languageFlag = GlobalResources::getInstance().bitmapRussia; + locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_POLISH; + newEntry.languageName = wxT("Polski"); + newEntry.languageFile = "Languages/polish.lng"; + newEntry.translatorName = wxT("Wojtek Pietruszewski"); + newEntry.languageFlag = GlobalResources::getInstance().bitmapPoland; + locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_PORTUGUESE; + newEntry.languageName = wxT("Português"); + newEntry.languageFile = "Languages/portuguese.lng"; + newEntry.translatorName = wxT("QuestMark"); + newEntry.languageFlag = GlobalResources::getInstance().bitmapPortugal; + locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_PORTUGUESE_BRAZILIAN; + newEntry.languageName = wxT("Português do Brasil"); + newEntry.languageFile = "Languages/portuguese_br.lng"; + newEntry.translatorName = wxT("Edison Aranha"); + newEntry.languageFlag = GlobalResources::getInstance().bitmapBrazil; + locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_SLOVENIAN; + newEntry.languageName = wxT("Slovenščina"); + newEntry.languageFile = "Languages/slovenian.lng"; + newEntry.translatorName = wxT("Matej Badalic"); + newEntry.languageFlag = GlobalResources::getInstance().bitmapSlovakia; + locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_JAPANESE; + newEntry.languageName = wxT("日本語"); + newEntry.languageFile = "Languages/japanese.lng"; + newEntry.translatorName = wxT("Tilt"); + newEntry.languageFlag = GlobalResources::getInstance().bitmapJapan; + locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_CHINESE_SIMPLIFIED; + newEntry.languageName = wxT("简体中文"); + newEntry.languageFile = "Languages/chinese_simple.lng"; + newEntry.translatorName = wxT("Misty Wu"); + newEntry.languageFlag = GlobalResources::getInstance().bitmapChina; + locMapping.push_back(newEntry); +} + + + +typedef wxString TextOriginal; +typedef wxString TextTranslation; + +class Translation : public std::map {}; CustomLocale::CustomLocale() : @@ -83,51 +192,19 @@ void exchangeEscapeChars(wxString& data) void CustomLocale::setLanguage(const int language) { - currentLanguage = language; - + //default: english std::string languageFile; - switch (language) - { - case wxLANGUAGE_CHINESE_SIMPLIFIED: - languageFile = "Languages/chinese_simple.lng"; - break; - case wxLANGUAGE_DUTCH: - languageFile = "Languages/dutch.lng"; - break; - case wxLANGUAGE_FRENCH: - languageFile = "Languages/french.lng"; - break; - case wxLANGUAGE_GERMAN: - languageFile = "Languages/german.lng"; - break; - case wxLANGUAGE_HUNGARIAN: - languageFile = "Languages/hungarian.lng"; - break; - case wxLANGUAGE_ITALIAN: - languageFile = "Languages/italian.lng"; - break; - case wxLANGUAGE_JAPANESE: - languageFile = "Languages/japanese.lng"; - break; - case wxLANGUAGE_POLISH: - languageFile = "Languages/polish.lng"; - break; - case wxLANGUAGE_PORTUGUESE: - languageFile = "Languages/portuguese.lng"; - break; - case wxLANGUAGE_PORTUGUESE_BRAZILIAN: - languageFile = "Languages/portuguese_br.lng"; - break; - case wxLANGUAGE_SLOVENIAN: - languageFile = "Languages/slovenian.lng"; - break; - case wxLANGUAGE_SPANISH: - languageFile = "Languages/spanish.lng"; - break; - default: - languageFile.clear(); - currentLanguage = wxLANGUAGE_ENGLISH; - } + currentLanguage = wxLANGUAGE_ENGLISH; + + //(try to) retrieve language filename + for (std::vector::const_iterator i = LocalizationInfo::getMapping().begin(); i != LocalizationInfo::getMapping().end(); ++i) + if (language == i->languageID) + { + languageFile = i->languageFile; + currentLanguage = i->languageID; + break; + } + static bool initialized = false; //wxLocale is a static global too! if (!initialized) @@ -145,7 +222,7 @@ void CustomLocale::setLanguage(const int language) std::ifstream langFile(languageFile.c_str(), std::ios::binary); if (langFile) { - TranslationLine currentLine; + wxString original; //Delimiter: //---------- @@ -162,39 +239,36 @@ void CustomLocale::setLanguage(const int language) exchangeEscapeChars(formattedString); if (rowNumber%2 == 0) - currentLine.original = formattedString; + original = formattedString; else { if (!formattedString.empty()) { - currentLine.translation = formattedString; - translationDB->insert(currentLine); + const wxString translation = formattedString; + translationDB->insert(std::pair(original, translation)); } } } langFile.close(); } else - wxMessageBox(wxString(_("Error reading file:")) + wxT(" \"") + wxString(languageFile.c_str(), wxConvUTF8) + wxT("\""), _("An exception occured!"), wxOK | wxICON_ERROR); + wxMessageBox(wxString(_("Error reading file:")) + wxT(" \"") + wxString(languageFile.c_str(), wxConvUTF8) + wxT("\""), _("Error"), wxOK | wxICON_ERROR); } else ; //if languageFile is empty texts will be english per default //these global variables need to be redetermined on language selection - GlobalResources::DECIMAL_POINT = _("."); - GlobalResources::THOUSANDS_SEPARATOR = _(","); + FreeFileSync::DECIMAL_POINT = _("."); + FreeFileSync::THOUSANDS_SEPARATOR = _(","); } const wxChar* CustomLocale::GetString(const wxChar* szOrigString, const wxChar* szDomain) const { - TranslationLine currentLine; - currentLine.original = szOrigString; - //look for translation in buffer table - const Translation::iterator i = translationDB->find(currentLine); + const Translation::const_iterator i = translationDB->find(szOrigString); if (i != translationDB->end()) - return i->translation.c_str(); + return i->second.c_str(); //fallback return szOrigString; diff --git a/library/localization.h b/library/localization.h index cf29d06d..7a63fd9c 100644 --- a/library/localization.h +++ b/library/localization.h @@ -2,31 +2,55 @@ #define MISC_H_INCLUDED #include +#include +#include class Translation; -class CustomLocale : public wxLocale +namespace FreeFileSync { -public: - CustomLocale(); - ~CustomLocale(); + struct LocInfoLine + { + int languageID; + wxString languageName; + std::string languageFile; + wxString translatorName; + wxBitmap* languageFlag; + }; + + + class LocalizationInfo + { + public: + static const std::vector& getMapping(); + + private: + LocalizationInfo(); + + std::vector locMapping; + }; - void setLanguage(const int language); - int getLanguage() const + class CustomLocale : public wxLocale { - return currentLanguage; - } + public: + CustomLocale(); + ~CustomLocale(); - const wxChar* GetString(const wxChar* szOrigString, const wxChar* szDomain = NULL) const; + void setLanguage(const int language); - static const std::string FfsLanguageDat; + int getLanguage() const + { + return currentLanguage; + } -private: - Translation* translationDB; - int currentLanguage; -}; + const wxChar* GetString(const wxChar* szOrigString, const wxChar* szDomain = NULL) const; + private: + Translation* translationDB; + int currentLanguage; + }; +} #endif // MISC_H_INCLUDED diff --git a/library/pch.h b/library/pch.h index 50f9fda3..b561d448 100644 --- a/library/pch.h +++ b/library/pch.h @@ -1,8 +1,10 @@ #ifndef FFS_PRECOMPILED_HEADER #define FFS_PRECOMPILED_HEADER -DO NOT USE THIS FILE: -FOR SOME REASON IT CORRUPTS COMPILATION!!! +//pay attention when using this file: might cause issues! +#ifndef __WXDEBUG__ +do NOT use in release build! +#endif //##################################################### // basic wxWidgets headers @@ -12,22 +14,17 @@ FOR SOME REASON IT CORRUPTS COMPILATION!!! #include -#ifndef WX_PRECOMP -#include -#endif - //##################################################### // #include other rarely changing headers here //STL headers -#include -#include #include -#include #include +#include +#include +#include +#include #include - -//C headers #include #ifdef FFS_LINUX @@ -36,7 +33,6 @@ FOR SOME REASON IT CORRUPTS COMPILATION!!! //other wxWidgets headers #include -#include #include #include #include @@ -90,9 +86,10 @@ FOR SOME REASON IT CORRUPTS COMPILATION!!! //other #include "tinyxml/tinyxml.h" +#include #ifdef FFS_WIN -#include +#include //includes "windows.h" #endif //FFS_WIN //##################################################### diff --git a/library/processXml.cpp b/library/processXml.cpp index 750bd879..4b8ffe27 100644 --- a/library/processXml.cpp +++ b/library/processXml.cpp @@ -11,15 +11,12 @@ using namespace FreeFileSync; -const wxString xmlAccess::LAST_CONFIG_FILE = wxT("LastRun.ffs_gui"); -const wxString xmlAccess::GLOBAL_CONFIG_FILE = wxT("GlobalSettings.xml"); - //small helper functions bool readXmlElementValue(std::string& output, const TiXmlElement* parent, const std::string& name); bool readXmlElementValue(int& output, const TiXmlElement* parent, const std::string& name); bool readXmlElementValue(CompareVariant& output, const TiXmlElement* parent, const std::string& name); -bool readXmlElementValue(SyncDirection& output, const TiXmlElement* parent, const std::string& name); +bool readXmlElementValue(SyncDirection& output, const TiXmlElement* parent, const std::string& name); bool readXmlElementValue(bool& output, const TiXmlElement* parent, const std::string& name); void addXmlElement(TiXmlElement* parent, const std::string& name, const std::string& value); @@ -158,18 +155,18 @@ xmlAccess::XmlBatchConfig xmlAccess::readBatchConfig(const wxString& filename) xmlAccess::XmlGlobalSettings xmlAccess::readGlobalSettings() { //load XML - if (!wxFileExists(xmlAccess::GLOBAL_CONFIG_FILE)) - throw FileError(Zstring(_("File does not exist:")) + wxT(" \"") + xmlAccess::GLOBAL_CONFIG_FILE.c_str() + wxT("\"")); + if (!wxFileExists(FreeFileSync::getGlobalConfigFile())) + throw FileError(Zstring(_("File does not exist:")) + wxT(" \"") + FreeFileSync::getGlobalConfigFile().c_str() + wxT("\"")); - XmlConfigInput inputFile(xmlAccess::GLOBAL_CONFIG_FILE, XML_GLOBAL_SETTINGS); + XmlConfigInput inputFile(FreeFileSync::getGlobalConfigFile(), XML_GLOBAL_SETTINGS); XmlGlobalSettings outputCfg; if (!inputFile.loadedSuccessfully()) - throw FileError(Zstring(_("Error reading file:")) + wxT(" \"") + xmlAccess::GLOBAL_CONFIG_FILE.c_str() + wxT("\"")); + throw FileError(Zstring(_("Error reading file:")) + wxT(" \"") + FreeFileSync::getGlobalConfigFile().c_str() + wxT("\"")); if (!inputFile.readXmlGlobalSettings(outputCfg)) - throw FileError(Zstring(_("Error parsing configuration file:")) + wxT(" \"") + xmlAccess::GLOBAL_CONFIG_FILE.c_str() + wxT("\"")); + throw FileError(Zstring(_("Error parsing configuration file:")) + wxT(" \"") + FreeFileSync::getGlobalConfigFile().c_str() + wxT("\"")); return outputCfg; } @@ -201,12 +198,12 @@ void xmlAccess::writeBatchConfig(const wxString& filename, const XmlBatchConfig& void xmlAccess::writeGlobalSettings(const XmlGlobalSettings& outputCfg) { - XmlConfigOutput outputFile(xmlAccess::GLOBAL_CONFIG_FILE, XML_GLOBAL_SETTINGS); + XmlConfigOutput outputFile(FreeFileSync::getGlobalConfigFile(), XML_GLOBAL_SETTINGS); //populate and write XML tree if ( !outputFile.writeXmlGlobalSettings(outputCfg) || //add GUI layout configuration settings !outputFile.writeToFile()) //save XML - throw FileError(Zstring(_("Error writing file:")) + wxT(" \"") + xmlAccess::GLOBAL_CONFIG_FILE.c_str() + wxT("\"")); + throw FileError(Zstring(_("Error writing file:")) + wxT(" \"") + FreeFileSync::getGlobalConfigFile().c_str() + wxT("\"")); return; } @@ -509,43 +506,49 @@ bool XmlConfigInput::readXmlGlobalSettings(xmlAccess::XmlGlobalSettings& outputC if (global) { //try to read program language setting - readXmlElementValue(outputCfg.shared.programLanguage, global, "Language"); + readXmlElementValue(outputCfg.programLanguage, global, "Language"); //max. allowed file time deviation int dummy = 0; if (readXmlElementValue(dummy, global, "FileTimeTolerance")) - outputCfg.shared.fileTimeTolerance = dummy; + outputCfg.fileTimeTolerance = dummy; //ignore +/- 1 hour due to DST change - readXmlElementValue(outputCfg.shared.ignoreOneHourDiff, global, "IgnoreOneHourDifference"); + readXmlElementValue(outputCfg.ignoreOneHourDiff, global, "IgnoreOneHourDifference"); //traverse into symbolic links (to folders) - readXmlElementValue(outputCfg.shared.traverseDirectorySymlinks, global, "TraverseDirectorySymlinks"); + readXmlElementValue(outputCfg.traverseDirectorySymlinks, global, "TraverseDirectorySymlinks"); //copy symbolic links to files - readXmlElementValue(outputCfg.shared.copyFileSymlinks, global, "CopyFileSymlinks"); + readXmlElementValue(outputCfg.copyFileSymlinks, global, "CopyFileSymlinks"); //last update check - readXmlElementValue(outputCfg.shared.lastUpdateCheck, global, "LastCheckForUpdates"); + readXmlElementValue(outputCfg.lastUpdateCheck, global, "LastCheckForUpdates"); } TiXmlElement* warnings = hRoot.FirstChild("Shared").FirstChild("Warnings").ToElement(); if (warnings) { //folder dependency check - readXmlElementValue(outputCfg.shared.warningDependentFolders, warnings, "CheckForDependentFolders"); + readXmlElementValue(outputCfg.warnings.warningDependentFolders, warnings, "CheckForDependentFolders"); //significant difference check - readXmlElementValue(outputCfg.shared.warningSignificantDifference, warnings, "CheckForSignificantDifference"); + readXmlElementValue(outputCfg.warnings.warningSignificantDifference, warnings, "CheckForSignificantDifference"); //check free disk space - readXmlElementValue(outputCfg.shared.warningNotEnoughDiskSpace, warnings, "CheckForFreeDiskSpace"); + readXmlElementValue(outputCfg.warnings.warningNotEnoughDiskSpace, warnings, "CheckForFreeDiskSpace"); //check for unresolved conflicts - readXmlElementValue(outputCfg.shared.warningUnresolvedConflicts, warnings, "CheckForUnresolvedConflicts"); + readXmlElementValue(outputCfg.warnings.warningUnresolvedConflicts, warnings, "CheckForUnresolvedConflicts"); + + //check for very old dates or dates in the future + readXmlElementValue(outputCfg.warnings.warningInvalidDate, warnings, "CheckForInvalidFileDate"); + + //check for changed files with same modification date + readXmlElementValue(outputCfg.warnings.warningSameDateDiffSize, warnings, "SameDateDifferentFileSize"); - //small reminder that synchronization will be starting immediately - readXmlElementValue(outputCfg.shared.warningSynchronizationStarting, warnings, "SynchronizationStarting"); + //check for files that have a difference in file modification date below 1 hour when DST check is active + readXmlElementValue(outputCfg.warnings.warningDSTChangeWithinHour, warnings, "FileChangeWithinHour"); } //gui specific global settings (optional) @@ -553,16 +556,18 @@ bool XmlConfigInput::readXmlGlobalSettings(xmlAccess::XmlGlobalSettings& outputC if (mainWindow) { //read application window size and position - readXmlElementValue(outputCfg.gui.widthNotMaximized, mainWindow, "Width"); + readXmlElementValue(outputCfg.gui.widthNotMaximized, mainWindow, "Width"); readXmlElementValue(outputCfg.gui.heightNotMaximized, mainWindow, "Height"); - readXmlElementValue(outputCfg.gui.posXNotMaximized, mainWindow, "PosX"); - readXmlElementValue(outputCfg.gui.posYNotMaximized, mainWindow, "PosY"); - readXmlElementValue(outputCfg.gui.isMaximized, mainWindow, "Maximized"); + readXmlElementValue(outputCfg.gui.posXNotMaximized, mainWindow, "PosX"); + readXmlElementValue(outputCfg.gui.posYNotMaximized, mainWindow, "PosY"); + readXmlElementValue(outputCfg.gui.isMaximized, mainWindow, "Maximized"); - readXmlElementValue(outputCfg.gui.deleteOnBothSides, mainWindow, "ManualDeletionOnBothSides"); + readXmlElementValue(outputCfg.gui.deleteOnBothSides, mainWindow, "ManualDeletionOnBothSides"); readXmlElementValue(outputCfg.gui.useRecyclerForManualDeletion, mainWindow, "ManualDeletionUseRecycler"); - readXmlElementValue(outputCfg.gui.showFileIcons, mainWindow, "ShowFileIcons"); - readXmlElementValue(outputCfg.gui.popupOnConfigChange, mainWindow, "PopupOnConfigChange"); + readXmlElementValue(outputCfg.gui.showFileIconsLeft, mainWindow, "ShowFileIconsLeft"); + readXmlElementValue(outputCfg.gui.showFileIconsRight, mainWindow, "ShowFileIconsRight"); + readXmlElementValue(outputCfg.gui.popupOnConfigChange, mainWindow, "PopupOnConfigChange"); + readXmlElementValue(outputCfg.gui.showSummaryBeforeSync, mainWindow, "SummaryBeforeSync"); //########################################################### //read column attributes @@ -638,6 +643,8 @@ bool XmlConfigInput::readXmlGlobalSettings(xmlAccess::XmlGlobalSettings& outputC //load config history elements readXmlElementTable(outputCfg.gui.folderHistoryRight, historyRight, "Folder"); } + + readXmlElementValue(outputCfg.gui.selectedTabBottomLeft, mainWindow, "SelectedTabBottomLeft"); } TiXmlElement* gui = hRoot.FirstChild("Gui").ToElement(); @@ -667,9 +674,6 @@ bool XmlConfigInput::readXmlGlobalSettings(xmlAccess::XmlGlobalSettings& outputC TiXmlElement* batch = hRoot.FirstChild("Batch").ToElement(); if (!batch) return false; - -// if (!readXmlElementValue(outputCfg.dummy, global, "Language")) return false; - return true; } @@ -889,41 +893,47 @@ bool XmlConfigOutput::writeXmlGlobalSettings(const xmlAccess::XmlGlobalSettings& root->LinkEndChild(global); //program language - addXmlElement(global, "Language", inputCfg.shared.programLanguage); + addXmlElement(global, "Language", inputCfg.programLanguage); //max. allowed file time deviation - addXmlElement(global, "FileTimeTolerance", int(inputCfg.shared.fileTimeTolerance)); + addXmlElement(global, "FileTimeTolerance", int(inputCfg.fileTimeTolerance)); //ignore +/- 1 hour due to DST change - addXmlElement(global, "IgnoreOneHourDifference", inputCfg.shared.ignoreOneHourDiff); + addXmlElement(global, "IgnoreOneHourDifference", inputCfg.ignoreOneHourDiff); //traverse into symbolic links (to folders) - addXmlElement(global, "TraverseDirectorySymlinks", inputCfg.shared.traverseDirectorySymlinks); + addXmlElement(global, "TraverseDirectorySymlinks", inputCfg.traverseDirectorySymlinks); //copy symbolic links to files - addXmlElement(global, "CopyFileSymlinks", inputCfg.shared.copyFileSymlinks); + addXmlElement(global, "CopyFileSymlinks", inputCfg.copyFileSymlinks); //last update check - addXmlElement(global, "LastCheckForUpdates", inputCfg.shared.lastUpdateCheck); + addXmlElement(global, "LastCheckForUpdates", inputCfg.lastUpdateCheck); //warnings TiXmlElement* warnings = new TiXmlElement("Warnings"); global->LinkEndChild(warnings); //warning: dependent folders - addXmlElement(warnings, "CheckForDependentFolders", inputCfg.shared.warningDependentFolders); + addXmlElement(warnings, "CheckForDependentFolders", inputCfg.warnings.warningDependentFolders); //significant difference check - addXmlElement(warnings, "CheckForSignificantDifference", inputCfg.shared.warningSignificantDifference); + addXmlElement(warnings, "CheckForSignificantDifference", inputCfg.warnings.warningSignificantDifference); //check free disk space - addXmlElement(warnings, "CheckForFreeDiskSpace", inputCfg.shared.warningNotEnoughDiskSpace); + addXmlElement(warnings, "CheckForFreeDiskSpace", inputCfg.warnings.warningNotEnoughDiskSpace); //check for unresolved conflicts - addXmlElement(warnings, "CheckForUnresolvedConflicts", inputCfg.shared.warningUnresolvedConflicts); + addXmlElement(warnings, "CheckForUnresolvedConflicts", inputCfg.warnings.warningUnresolvedConflicts); + + //check for very old dates or dates in the future + addXmlElement(warnings, "CheckForInvalidFileDate", inputCfg.warnings.warningInvalidDate); - //small reminder that synchronization will be starting immediately - addXmlElement(warnings, "SynchronizationStarting", inputCfg.shared.warningSynchronizationStarting); + //check for changed files with same modification date + addXmlElement(warnings, "SameDateDifferentFileSize", inputCfg.warnings.warningSameDateDiffSize); + + //check for files that have a difference in file modification date below 1 hour when DST check is active + addXmlElement(warnings, "FileChangeWithinHour", inputCfg.warnings.warningDSTChangeWithinHour); //################################################################### @@ -948,8 +958,10 @@ bool XmlConfigOutput::writeXmlGlobalSettings(const xmlAccess::XmlGlobalSettings& addXmlElement(mainWindow, "ManualDeletionOnBothSides", inputCfg.gui.deleteOnBothSides); addXmlElement(mainWindow, "ManualDeletionUseRecycler", inputCfg.gui.useRecyclerForManualDeletion); - addXmlElement(mainWindow, "ShowFileIcons" , inputCfg.gui.showFileIcons); - addXmlElement(mainWindow, "PopupOnConfigChange" , inputCfg.gui.popupOnConfigChange); + addXmlElement(mainWindow, "ShowFileIconsLeft", inputCfg.gui.showFileIconsLeft); + addXmlElement(mainWindow, "ShowFileIconsRight", inputCfg.gui.showFileIconsRight); + addXmlElement(mainWindow, "PopupOnConfigChange", inputCfg.gui.popupOnConfigChange); + addXmlElement(mainWindow, "SummaryBeforeSync", inputCfg.gui.showSummaryBeforeSync); //write column attributes TiXmlElement* leftColumn = new TiXmlElement("LeftColumns"); @@ -996,6 +1008,7 @@ bool XmlConfigOutput::writeXmlGlobalSettings(const xmlAccess::XmlGlobalSettings& historyRight->SetAttribute("MaximumSize", globalFunctions::numberToString(inputCfg.gui.folderHistRightMax)); addXmlElementTable(historyRight, "Folder", inputCfg.gui.folderHistoryRight); + addXmlElement(mainWindow, "SelectedTabBottomLeft", inputCfg.gui.selectedTabBottomLeft); //commandline for file manager integration addXmlElement(gui, "FileManager", std::string((inputCfg.gui.commandLineFileManager).ToUTF8())); @@ -1056,6 +1069,10 @@ int xmlAccess::retrieveSystemLanguage() case wxLANGUAGE_CHINESE_TAIWAN: return wxLANGUAGE_CHINESE_SIMPLIFIED; + //variants of wxLANGUAGE_RUSSIAN + case wxLANGUAGE_RUSSIAN_UKRAINE: + return wxLANGUAGE_RUSSIAN; + //variants of wxLANGUAGE_SPANISH case wxLANGUAGE_SPANISH_ARGENTINA: case wxLANGUAGE_SPANISH_BOLIVIA: @@ -1110,11 +1127,13 @@ bool xmlAccess::supportForSymbolicLinks() } -void xmlAccess::XmlGlobalSettings::_Shared::resetWarnings() +void xmlAccess::WarningMessages::resetWarnings() { warningDependentFolders = true; warningSignificantDifference = true; warningNotEnoughDiskSpace = true; warningUnresolvedConflicts = true; - warningSynchronizationStarting = true; + warningInvalidDate = true; + warningSameDateDiffSize = true; + warningDSTChangeWithinHour = true; } diff --git a/library/processXml.h b/library/processXml.h index 313fbfae..15972289 100644 --- a/library/processXml.h +++ b/library/processXml.h @@ -6,9 +6,6 @@ namespace xmlAccess { - extern const wxString LAST_CONFIG_FILE; - extern const wxString GLOBAL_CONFIG_FILE; - enum OnError { ON_ERROR_POPUP, @@ -93,38 +90,45 @@ namespace xmlAccess bool supportForSymbolicLinks(); + struct WarningMessages + { + WarningMessages() + { + resetWarnings(); + } + + void resetWarnings(); + + bool warningDependentFolders; + bool warningSignificantDifference; + bool warningNotEnoughDiskSpace; + bool warningUnresolvedConflicts; + bool warningInvalidDate; + bool warningSameDateDiffSize; + bool warningDSTChangeWithinHour; + }; + + struct XmlGlobalSettings { //--------------------------------------------------------------------- - struct _Shared - { - _Shared() : - programLanguage(retrieveSystemLanguage()), - fileTimeTolerance(2), //default 2s: FAT vs NTFS - ignoreOneHourDiff(true), - traverseDirectorySymlinks(false), - copyFileSymlinks(supportForSymbolicLinks()), - lastUpdateCheck(0) - { - resetWarnings(); - } - - int programLanguage; - unsigned int fileTimeTolerance; //max. allowed file time deviation - bool ignoreOneHourDiff; //ignore +/- 1 hour due to DST change - bool traverseDirectorySymlinks; - bool copyFileSymlinks; //copy symbolic link instead of target file - long lastUpdateCheck; //time of last update check - - //warnings - void resetWarnings(); - - bool warningDependentFolders; - bool warningSignificantDifference; - bool warningNotEnoughDiskSpace; - bool warningUnresolvedConflicts; - bool warningSynchronizationStarting; - } shared; + //Shared (GUI/BATCH) settings + XmlGlobalSettings() : + programLanguage(retrieveSystemLanguage()), + fileTimeTolerance(2), //default 2s: FAT vs NTFS + ignoreOneHourDiff(true), + traverseDirectorySymlinks(false), + copyFileSymlinks(supportForSymbolicLinks()), + lastUpdateCheck(0) {} + + int programLanguage; + unsigned int fileTimeTolerance; //max. allowed file time deviation + bool ignoreOneHourDiff; //ignore +/- 1 hour due to DST change + bool traverseDirectorySymlinks; + bool copyFileSymlinks; //copy symbolic link instead of target file + long lastUpdateCheck; //time of last update check + + WarningMessages warnings; //--------------------------------------------------------------------- struct _Gui @@ -143,10 +147,13 @@ namespace xmlAccess cfgHistoryMax(10), folderHistLeftMax(12), folderHistRightMax(12), + selectedTabBottomLeft(0), deleteOnBothSides(false), useRecyclerForManualDeletion(FreeFileSync::recycleBinExists()), //enable if OS supports it; else user will have to activate first and then get an error message - showFileIcons(true), - popupOnConfigChange(true) {} + showFileIconsLeft(true), + showFileIconsRight(true), + popupOnConfigChange(true), + showSummaryBeforeSync(true) {} int widthNotMaximized; int heightNotMaximized; @@ -168,10 +175,14 @@ namespace xmlAccess std::vector folderHistoryRight; unsigned int folderHistRightMax; + int selectedTabBottomLeft; + bool deleteOnBothSides; bool useRecyclerForManualDeletion; - bool showFileIcons; + bool showFileIconsLeft; + bool showFileIconsRight; bool popupOnConfigChange; + bool showSummaryBeforeSync; } gui; //--------------------------------------------------------------------- diff --git a/library/resources.cpp b/library/resources.cpp index 9e43a78a..38f2a051 100644 --- a/library/resources.cpp +++ b/library/resources.cpp @@ -5,20 +5,15 @@ #include #include #include "globalFunctions.h" +#include "../structures.h" -#ifdef FFS_WIN -const wxChar GlobalResources::FILE_NAME_SEPARATOR = '\\'; -#elif defined FFS_LINUX -const wxChar GlobalResources::FILE_NAME_SEPARATOR = '/'; -#else -assert(false); -#endif -//these two global variables are language-dependent => cannot be set statically! See CustomLocale -const wxChar* GlobalResources::DECIMAL_POINT = wxEmptyString; -const wxChar* GlobalResources::THOUSANDS_SEPARATOR = wxEmptyString; +const GlobalResources& GlobalResources::getInstance() +{ + static GlobalResources instance; + return instance; +} -GlobalResources globalResource; //init resources on program startup GlobalResources::GlobalResources() { @@ -42,6 +37,7 @@ GlobalResources::GlobalResources() bitmapResource[wxT("sync.png")] = (bitmapSync = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("sync disabled.png")] = (bitmapSyncDisabled = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("swap.png")] = (bitmapSwap = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("swapSmall.png")] = (bitmapSwapSmall = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("help.png")] = (bitmapHelp = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("leftOnly.png")] = (bitmapLeftOnly = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("leftOnlyAct.png")] = (bitmapLeftOnlyAct = new wxBitmap(wxNullBitmap)); @@ -67,6 +63,7 @@ GlobalResources::GlobalResources() bitmapResource[wxT("exclude.png")] = (bitmapExclude = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("filter active.png")] = (bitmapFilterOn = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("filter not active.png")] = (bitmapFilterOff = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("filter_small.png")] = (bitmapFilterSmall = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("warning.png")] = (bitmapWarning = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("warningSmall.png")] = (bitmapWarningSmall = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("error.png")] = (bitmapError = new wxBitmap(wxNullBitmap)); @@ -124,6 +121,7 @@ GlobalResources::GlobalResources() bitmapResource[wxT("brazil.png")] = (bitmapBrazil = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("slovakia.png")] = (bitmapSlovakia = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("spain.png")] = (bitmapSpain = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("russia.png")] = (bitmapRussia = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("syncDirLeftAct.png")] = (bitmapSyncDirLeftAct = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("syncDirLeftDeact.png")] = (bitmapSyncDirLeftDeact = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("syncDirRightAct.png")] = (bitmapSyncDirRightAct = new wxBitmap(wxNullBitmap)); @@ -133,6 +131,10 @@ GlobalResources::GlobalResources() bitmapResource[wxT("syncDirLeftSmall.png")] = (bitmapSyncDirLeftSmall = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("syncDirRightSmall.png")] = (bitmapSyncDirRightSmall = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("syncDirNoneSmall.png")] = (bitmapSyncDirNoneSmall = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("createLeftSmall.png")] = (bitmapCreateLeftSmall = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("createRightSmall.png")] = (bitmapCreateRightSmall = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("deleteLeftSmall.png")] = (bitmapDeleteLeftSmall = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("deleteRightSmall.png")] = (bitmapDeleteRightSmall = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("leftOnlySmall.png")] = (bitmapLeftOnlySmall = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("rightOnlySmall.png")] = (bitmapRightOnlySmall = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("leftNewerSmall.png")] = (bitmapLeftNewerSmall = new wxBitmap(wxNullBitmap)); @@ -144,8 +146,8 @@ GlobalResources::GlobalResources() bitmapResource[wxT("update.png")] = (bitmapUpdate = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("delete.png")] = (bitmapDelete = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("data.png")] = (bitmapData = new wxBitmap(wxNullBitmap)); - bitmapResource[wxT("cmpView.png")] = (bitmapCmpView = new wxBitmap(wxNullBitmap)); - bitmapResource[wxT("syncView.png")] = (bitmapSyncView = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("cmpViewSmall.png")] = (bitmapCmpViewSmall = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("syncViewSmall.png")] = (bitmapSyncViewSmall = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("toggleViewSmall.png")] = (bitmapSwitchViewSmall = new wxBitmap(wxNullBitmap)); @@ -187,9 +189,9 @@ void loadAnimFromZip(wxZipInputStream& zipInput, wxAnimation* animation) } -void GlobalResources::load() +void GlobalResources::load() const { - wxFileInputStream input(wxT("Resources.dat")); + wxFileInputStream input(FreeFileSync::getInstallationDir() + FreeFileSync::FILE_NAME_SEPARATOR + wxT("Resources.dat")); if (input.IsOk()) //if not... we don't want to react too harsh here { //activate support for .png files @@ -214,7 +216,7 @@ void GlobalResources::load() } #ifdef FFS_WIN - *programIcon = wxIcon(wxT("ffsIcon1")); + *programIcon = wxIcon(wxT("A_PROGRAM_ICON")); #else #include "FreeFileSync.xpm" *programIcon = wxIcon(FreeFileSync_xpm); diff --git a/library/resources.h b/library/resources.h index 60b4ab48..1ce7d20e 100644 --- a/library/resources.h +++ b/library/resources.h @@ -10,16 +10,7 @@ class GlobalResources { public: - GlobalResources(); - ~GlobalResources(); - - void load(); - - static const wxChar FILE_NAME_SEPARATOR; - - //language dependent global variables: need to be initialized by CustomLocale on program startup and language switch - static const wxChar* DECIMAL_POINT; - static const wxChar* THOUSANDS_SEPARATOR; + static const GlobalResources& getInstance(); //image resource objects wxBitmap* bitmapArrowLeft; @@ -41,6 +32,7 @@ public: wxBitmap* bitmapSync; wxBitmap* bitmapSyncDisabled; wxBitmap* bitmapSwap; + wxBitmap* bitmapSwapSmall; wxBitmap* bitmapHelp; wxBitmap* bitmapLeftOnly; wxBitmap* bitmapLeftOnlyAct; @@ -66,6 +58,7 @@ public: wxBitmap* bitmapExclude; wxBitmap* bitmapFilterOn; wxBitmap* bitmapFilterOff; + wxBitmap* bitmapFilterSmall; wxBitmap* bitmapWarning; wxBitmap* bitmapWarningSmall; wxBitmap* bitmapError; @@ -123,6 +116,7 @@ public: wxBitmap* bitmapBrazil; wxBitmap* bitmapSlovakia; wxBitmap* bitmapSpain; + wxBitmap* bitmapRussia; wxBitmap* bitmapSyncDirLeftAct; wxBitmap* bitmapSyncDirLeftDeact; wxBitmap* bitmapSyncDirRightAct; @@ -132,6 +126,10 @@ public: wxBitmap* bitmapSyncDirLeftSmall; wxBitmap* bitmapSyncDirRightSmall; wxBitmap* bitmapSyncDirNoneSmall; + wxBitmap* bitmapCreateLeftSmall; + wxBitmap* bitmapCreateRightSmall; + wxBitmap* bitmapDeleteLeftSmall; + wxBitmap* bitmapDeleteRightSmall; wxBitmap* bitmapLeftOnlySmall; wxBitmap* bitmapRightOnlySmall; wxBitmap* bitmapLeftNewerSmall; @@ -143,8 +141,8 @@ public: wxBitmap* bitmapUpdate; wxBitmap* bitmapDelete; wxBitmap* bitmapData; - wxBitmap* bitmapCmpView; - wxBitmap* bitmapSyncView; + wxBitmap* bitmapCmpViewSmall; + wxBitmap* bitmapSyncViewSmall; wxBitmap* bitmapSwitchViewSmall; wxAnimation* animationMoney; @@ -152,12 +150,14 @@ public: wxIcon* programIcon; + void load() const; //loads bitmap resources on program startup: logical const! + private: + GlobalResources(); + ~GlobalResources(); + //resource mapping - std::map bitmapResource; + mutable std::map bitmapResource; }; - -extern GlobalResources globalResource; //loads bitmap resources on program startup - #endif // RESOURCES_H_INCLUDED diff --git a/library/shadow.cpp b/library/shadow.cpp new file mode 100644 index 00000000..c353bcb2 --- /dev/null +++ b/library/shadow.cpp @@ -0,0 +1,148 @@ +#include "shadow.h" +#include //includes "windows.h" +#include +#include "../structures.h" + +using FreeFileSync::ShadowCopy; + + +class ShadowlDllHandler //dynamically load windows API functions +{ + typedef bool (*CreateShadowCopyFct)( //volumeName must end with "\", while shadowVolName does not end with "\" + const wchar_t* volumeName, + wchar_t* shadowVolName, + unsigned int shadowBufferLen, + void** backupHandle, + wchar_t* errorMessage, + unsigned int errorBufferLen); + + typedef void (*ReleaseShadowCopyFct)(void* backupHandle); + +public: + static const ShadowlDllHandler& getInstance() + { + static ShadowlDllHandler instance; + return instance; + } + + CreateShadowCopyFct createShadowCopy; + ReleaseShadowCopyFct releaseShadowCopy; + +private: + ShadowlDllHandler() : + createShadowCopy(NULL), + releaseShadowCopy(NULL), + hShadow(NULL) + { + //get a handle to the DLL module containing the required functionality + hShadow = ::LoadLibrary(L"Shadow.dll"); + if (hShadow) + { + createShadowCopy = reinterpret_cast(::GetProcAddress(hShadow, "createShadowCopy")); + releaseShadowCopy = reinterpret_cast(::GetProcAddress(hShadow, "releaseShadowCopy")); + } + } + + ~ShadowlDllHandler() + { + if (hShadow) ::FreeLibrary(hShadow); + } + + HINSTANCE hShadow; +}; + + +ShadowCopy::ShadowCopy() : + backupHandle(NULL) {} + + +ShadowCopy::~ShadowCopy() +{ + if (backupHandle != NULL) + ShadowlDllHandler::getInstance().releaseShadowCopy(backupHandle); +} + + +bool ShadowCopy::isOkay() +{ + //check that all functions could be loaded + return ShadowlDllHandler::getInstance().createShadowCopy != NULL && + ShadowlDllHandler::getInstance().releaseShadowCopy != NULL; +} + + +Zstring ShadowCopy::makeShadowCopy(const Zstring& inputFile) throw(FreeFileSync::FileError) +{ + //check if shadow copy dll was loaded correctly + if (!isOkay()) + throw FileError(Zstring(_("Error copying locked file %x!")).Replace(wxT("%x"), Zstring(wxT("\"")) + inputFile + wxT("\"")) + wxT("\n\n") + + _("Error starting Volume Shadow Copy Service!") + wxT("\n") + + _("Please copy the appropriate \"Shadow.dll\" (located in \"Shadow.zip\" archive) into the FreeFileSync installation directory to enable this feature.")); + + + wchar_t volumeNameRaw[1000]; + + if (!GetVolumePathName(inputFile.c_str(), //__in LPCTSTR lpszFileName, + volumeNameRaw, //__out LPTSTR lpszVolumePathName, + 1000)) //__in DWORD cchBufferLength + throw FileError(Zstring(_("Error copying locked file %x!")).Replace(wxT("%x"), Zstring(wxT("\"")) + inputFile + wxT("\"")) + wxT("\n\n") + + _("Could not determine volume name for file:") + wxT("\n\"") + inputFile + wxT("\"")); + + Zstring volumeNameFormatted = volumeNameRaw; + if (!volumeNameFormatted.EndsWith(FreeFileSync::FILE_NAME_SEPARATOR)) + volumeNameFormatted += FreeFileSync::FILE_NAME_SEPARATOR; + + if (volumeNameFormatted != realVolumeLast) + { + //release old shadow copy + if (backupHandle != NULL) + { + ShadowlDllHandler::getInstance().releaseShadowCopy(backupHandle); + backupHandle = NULL; + } + realVolumeLast.clear(); //...if next call fails... + shadowVolumeLast.clear(); //...if next call fails... + + //start shadow volume copy service: + wchar_t shadowVolName[1000]; + void* backupHandleTmp = NULL; + wchar_t errorMessage[1000]; + + if (!ShadowlDllHandler::getInstance().createShadowCopy( + volumeNameFormatted.c_str(), + shadowVolName, + 1000, + &backupHandleTmp, + errorMessage, + 1000)) + throw FileError(Zstring(_("Error copying locked file %x!")).Replace(wxT("%x"), Zstring(wxT("\"")) + inputFile + wxT("\"")) + wxT("\n\n") + + _("Error starting Volume Shadow Copy Service!") + wxT("\n") + + wxT("(") + errorMessage + wxT(")")); + + realVolumeLast = volumeNameFormatted; + shadowVolumeLast = Zstring(shadowVolName) + FreeFileSync::FILE_NAME_SEPARATOR; //shadowVolName NEVER has a trailing backslash + backupHandle = backupHandleTmp; + } + + const size_t pos = inputFile.find(volumeNameFormatted); + if (pos == Zstring::npos) + { + Zstring msg = _("Volume name %x not part of filename %y!"); + msg.Replace(wxT("%x"), Zstring(wxT("\"")) + volumeNameFormatted + wxT("\""), false); + msg.Replace(wxT("%y"), Zstring(wxT("\"")) + inputFile + wxT("\""), false); + throw FileError(Zstring(_("Error copying locked file %x!")).Replace(wxT("%x"), Zstring(wxT("\"")) + inputFile + wxT("\"")) + wxT("\n\n") + + msg); + } + + //return filename alias on shadow copy volume + return shadowVolumeLast + Zstring(inputFile.c_str() + pos + volumeNameFormatted.length()); +} + + + + + + + + + diff --git a/library/shadow.h b/library/shadow.h new file mode 100644 index 00000000..ded9d746 --- /dev/null +++ b/library/shadow.h @@ -0,0 +1,31 @@ +#ifndef SHADOW_H_INCLUDED +#define SHADOW_H_INCLUDED + +#ifndef FFS_WIN +#warning //this header should be used in the windows build only! +#endif + +#include "zstring.h" +#include "fileError.h" + + +namespace FreeFileSync +{ + class ShadowCopy //buffer access to Windows Volume Shadow Copy Service + { + public: + ShadowCopy(); + ~ShadowCopy(); + + Zstring makeShadowCopy(const Zstring& inputFile) throw(FileError); //returns filename on shadow copy + + private: + bool isOkay(); + + Zstring realVolumeLast; //buffer last volume name + Zstring shadowVolumeLast; //buffer last created shadow volume + void* backupHandle; + }; +} + +#endif // SHADOW_H_INCLUDED diff --git a/library/statistics.cpp b/library/statistics.cpp index ec03c59e..f2506da8 100644 --- a/library/statistics.cpp +++ b/library/statistics.cpp @@ -5,6 +5,11 @@ #include "statusHandler.h" #include "../algorithm.h" #include +#include + + +RetrieveStatistics::RetrieveStatistics() : + timer(new wxStopWatch) {} RetrieveStatistics::~RetrieveStatistics() @@ -30,7 +35,7 @@ void RetrieveStatistics::writeEntry(const double value, const int objects) statEntry newEntry; newEntry.value = value; newEntry.objects = objects; - newEntry.time = timer.Time(); + newEntry.time = timer->Time(); data.push_back(newEntry); } @@ -102,8 +107,8 @@ Statistics::Statistics(const int totalObjectCount, windowSizeRemTime(windowSizeRemainingTime), windowSizeBPS(windowSizeBytesPerSecond), windowMax(std::max(windowSizeRemainingTime, windowSizeBytesPerSecond)), - remainingTimeLast(256*256*256*100) //something "big" -{} + remainingTimeLast(256*256*256*100), //something "big" + timer(new wxStopWatch) {} void Statistics::addMeasurement(const int objectsCurrent, const double dataCurrent) @@ -111,7 +116,7 @@ void Statistics::addMeasurement(const int objectsCurrent, const double dataCurre record newEntry; newEntry.objects = objectsCurrent; newEntry.data = dataCurrent; - newEntry.time = timer.Time(); + newEntry.time = timer->Time(); //insert new record measurements.push_back(newEntry); @@ -176,13 +181,13 @@ wxString Statistics::getBytesPerSecond() const void Statistics::pauseTimer() { - timer.Pause(); + timer->Pause(); } void Statistics::resumeTimer() { - timer.Resume(); + timer->Resume(); } /* diff --git a/library/statistics.h b/library/statistics.h index 3d4179db..f0eafad8 100644 --- a/library/statistics.h +++ b/library/statistics.h @@ -2,13 +2,18 @@ #define STATISTICS_H_INCLUDED #include -#include #include +#include +#include + +class wxStopWatch; +class wxString; + class RetrieveStatistics { public: - wxDEPRECATED(RetrieveStatistics() {}) //generate compiler warnings as a reminder to remove code after measurements + wxDEPRECATED( RetrieveStatistics() ); //generate compiler warnings as a reminder to remove code after measurements ~RetrieveStatistics(); void writeEntry(const double value, const int objects); @@ -22,7 +27,7 @@ private: }; std::vector data; - wxStopWatch timer; + std::auto_ptr timer; }; @@ -61,7 +66,7 @@ private: }; std::list measurements; - wxStopWatch timer; + std::auto_ptr timer; }; #endif // STATISTICS_H_INCLUDED diff --git a/library/zstring.cpp b/library/zstring.cpp index ddf1fc73..b26ee451 100644 --- a/library/zstring.cpp +++ b/library/zstring.cpp @@ -19,7 +19,7 @@ AllocationCount::~AllocationCount() } -AllocationCount& AllocationCount::getGlobal() +AllocationCount& AllocationCount::getInstance() { static AllocationCount global; return global; @@ -260,7 +260,7 @@ Zstring& Zstring::replace(size_t pos1, size_t n1, const DefaultChar* str, size_t } const size_t newLen = oldLen - n1 + n2; - if (n1 < n2 || descr->refCount > 1) + if (newLen > oldLen || descr->refCount > 1) { //allocate a new string StringDescriptor* newDescr; DefaultChar* newData; @@ -279,7 +279,7 @@ Zstring& Zstring::replace(size_t pos1, size_t n1, const DefaultChar* str, size_t else //overwrite current string: case "n2 == 0" is handled implicitly { memcpy(data + pos1, str, n2 * sizeof(DefaultChar)); - if (n1 > n2) + if (newLen < oldLen) { memmove(data + pos1 + n2, data + pos1 + n1, (oldLen - pos1 - n1) * sizeof(DefaultChar)); data[newLen] = 0; diff --git a/library/zstring.h b/library/zstring.h index 007922d8..bca50862 100644 --- a/library/zstring.h +++ b/library/zstring.h @@ -11,7 +11,6 @@ #include #include #include -#include namespace FreeFileSync @@ -23,18 +22,10 @@ namespace FreeFileSync } -#ifdef FFS_WIN -#define ZSTRING_WIDE_CHAR //use wide character strings - -#elif defined FFS_LINUX -#define ZSTRING_CHAR //use char strings -#endif - - #ifdef ZSTRING_CHAR -typedef char DefaultChar; +typedef char DefaultChar; //use char strings #elif defined ZSTRING_WIDE_CHAR -typedef wchar_t DefaultChar; +typedef wchar_t DefaultChar; //use wide character strings #endif class Zsubstr; @@ -54,6 +45,7 @@ public: bool StartsWith(const DefaultChar* begin) const; bool StartsWith(const Zstring& begin) const; bool EndsWith(const DefaultChar* end) const; + bool EndsWith(const DefaultChar end) const; bool EndsWith(const Zstring& end) const; #ifdef FFS_WIN int CmpNoCase(const DefaultChar* other) const; @@ -76,6 +68,7 @@ public: 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; + void clear(); int compare(const DefaultChar* other) const; int compare(const Zstring& other) const; int compare(const size_t pos1, const size_t n1, const DefaultChar* other) const; @@ -119,7 +112,7 @@ private: size_t length; size_t capacity; //allocated length without null-termination }; - static void allocate(const size_t newLength, Zstring::StringDescriptor*& newDescr, DefaultChar*& newData); + static void allocate(const size_t newLength, StringDescriptor*& newDescr, DefaultChar*& newData); StringDescriptor* descr; DefaultChar* data; @@ -211,13 +204,13 @@ int defaultCompare(const wchar_t* str1, const wchar_t* str2, const size_t count) } inline -wchar_t* defaultStrFind(const wchar_t* str1, const wchar_t* str2) +const wchar_t* defaultStrFind(const wchar_t* str1, const wchar_t* str2) { return wcsstr(str1, str2); } inline -wchar_t* defaultStrFind(const wchar_t* str1, int ch) +const wchar_t* defaultStrFind(const wchar_t* str1, int ch) { return wcschr(str1, ch); } @@ -251,7 +244,7 @@ public: --count; } - static AllocationCount& getGlobal(); + static AllocationCount& getInstance(); private: AllocationCount() : count(0) {} @@ -277,17 +270,15 @@ void Zstring::allocate(const size_t newLength, 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 = static_cast(operator new [] (sizeof(StringDescriptor) + (newCapacity + 1) * sizeof(DefaultChar))); + newData = reinterpret_cast(newDescr + 1); newDescr->refCount = 1; newDescr->length = newLength; newDescr->capacity = newCapacity; #ifdef __WXDEBUG__ - AllocationCount::getGlobal().inc(); //test Zstring for memory leaks + AllocationCount::getInstance().inc(); //test Zstring for memory leaks #endif } @@ -361,10 +352,10 @@ void Zstring::decRef() assert(descr && descr->refCount >= 1); //descr points to the begin of the allocated memory block if (--descr->refCount == 0) { - free(descr); //this must NEVER be changed!! E.g. Trim() relies on descr being start of allocated memory block + operator delete [] (descr); //this must NEVER be changed!! E.g. Trim() relies on descr being start of allocated memory block descr = NULL; #ifdef __WXDEBUG__ - AllocationCount::getGlobal().dec(); //test Zstring for memory leaks + AllocationCount::getInstance().dec(); //test Zstring for memory leaks #endif } } @@ -473,6 +464,16 @@ bool Zstring::EndsWith(const DefaultChar* end) const } +inline +bool Zstring::EndsWith(const DefaultChar end) const +{ + const size_t thisLength = length(); + if (thisLength < 1) + return false; + return *(c_str() + thisLength - 1) == end; +} + + inline bool Zstring::EndsWith(const Zstring& end) const { @@ -610,6 +611,13 @@ bool Zstring::empty() const } +inline +void Zstring::clear() +{ + *this = Zstring(); +} + + inline DefaultChar Zstring::operator[](const size_t pos) const { @@ -690,16 +698,12 @@ bool Zsubstr::StartsWith(const Zstring& begin) const 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 + size_t pos = length(); + while (--pos != static_cast(-1)) { if (m_data[pos] == ch) return pos; } - while (--pos != static_cast(-1)); return Zstring::npos; } -- cgit