// ************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: http://www.gnu.org/licenses/gpl.html * // * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * // ************************************************************************** #include "grid_view.h" #include "sorting.h" #include "../synchronization.h" #include //#include using namespace zen; template void getNumbers(const FileSystemObject& fsObj, StatusResult& result) { struct GetValues : public FSObjectVisitor { GetValues(StatusResult& res) : result_(res) {} virtual void visit(const FilePair& fileObj) { if (!fileObj.isEmpty()) { result_.filesizeLeftView += fileObj.getFileSize(); ++result_.filesOnLeftView; } if (!fileObj.isEmpty()) { result_.filesizeRightView += fileObj.getFileSize(); ++result_.filesOnRightView; } } virtual void visit(const SymlinkPair& linkObj) { if (!linkObj.isEmpty()) ++result_.filesOnLeftView; if (!linkObj.isEmpty()) ++result_.filesOnRightView; } virtual void visit(const DirPair& dirObj) { if (!dirObj.isEmpty()) ++result_.foldersOnLeftView; if (!dirObj.isEmpty()) ++result_.foldersOnRightView; } StatusResult& result_; } getVal(result); fsObj.accept(getVal); } template void GridView::updateView(Predicate pred) { viewRef.clear(); rowPositions.clear(); rowPositionsFirstChild.clear(); std::for_each(sortedRef.begin(), sortedRef.end(), [&](const RefIndex& ref) { if (const FileSystemObject* fsObj = FileSystemObject::retrieve(ref.objId)) if (pred(*fsObj)) { //save row position for direct random access to FilePair or DirPair this->rowPositions.insert(std::make_pair(ref.objId, viewRef.size())); //costs: 0.28 µs per call - MSVC based on std::set //"this->" required by two-pass lookup as enforced by GCC 4.7 //save row position to identify first child *on sorted subview* of DirPair or BaseDirPair in case latter are filtered out const HierarchyObject* parent = &fsObj->parent(); for (;;) //map all yet unassociated parents to this row { const auto rv = this->rowPositionsFirstChild.insert(std::make_pair(parent, viewRef.size())); if (!rv.second) break; if (auto dirObj = dynamic_cast(parent)) parent = &(dirObj->parent()); else break; } //build subview this->viewRef.push_back(ref.objId); } }); } ptrdiff_t GridView::findRowDirect(FileSystemObject::ObjectIdConst objId) const { auto it = rowPositions.find(objId); return it != rowPositions.end() ? it->second : -1; } ptrdiff_t GridView::findRowFirstChild(const HierarchyObject* hierObj) const { auto it = rowPositionsFirstChild.find(hierObj); return it != rowPositionsFirstChild.end() ? it->second : -1; } GridView::StatusCmpResult::StatusCmpResult() : existsLeftOnly (false), existsRightOnly (false), existsLeftNewer (false), existsRightNewer(false), existsDifferent (false), existsEqual (false), existsConflict (false), filesOnLeftView (0), foldersOnLeftView (0), filesOnRightView (0), foldersOnRightView(0) {} GridView::StatusCmpResult GridView::updateCmpResult(bool hideFiltered, //maps sortedRef to viewRef bool leftOnlyFilesActive, bool rightOnlyFilesActive, bool leftNewerFilesActive, bool rightNewerFilesActive, bool differentFilesActive, bool equalFilesActive, bool conflictFilesActive) { StatusCmpResult output; updateView([&](const FileSystemObject& fsObj) -> bool { if (hideFiltered && !fsObj.isActive()) return false; switch (fsObj.getCategory()) { case FILE_LEFT_SIDE_ONLY: output.existsLeftOnly = true; if (!leftOnlyFilesActive) return false; break; case FILE_RIGHT_SIDE_ONLY: output.existsRightOnly = true; if (!rightOnlyFilesActive) return false; break; case FILE_LEFT_NEWER: output.existsLeftNewer = true; if (!leftNewerFilesActive) return false; break; case FILE_RIGHT_NEWER: output.existsRightNewer = true; if (!rightNewerFilesActive) return false; break; case FILE_DIFFERENT: output.existsDifferent = true; if (!differentFilesActive) return false; break; case FILE_EQUAL: case FILE_DIFFERENT_METADATA: //= sub-category of equal output.existsEqual = true; if (!equalFilesActive) return false; break; case FILE_CONFLICT: output.existsConflict = true; if (!conflictFilesActive) return false; break; } //calculate total number of bytes for each side getNumbers(fsObj, output); return true; }); return output; } GridView::StatusSyncPreview::StatusSyncPreview() : existsSyncCreateLeft (false), existsSyncCreateRight(false), existsSyncDeleteLeft (false), existsSyncDeleteRight(false), existsSyncDirLeft (false), existsSyncDirRight (false), existsSyncDirNone (false), existsSyncEqual (false), existsConflict (false), filesOnLeftView (0), foldersOnLeftView (0), filesOnRightView (0), foldersOnRightView(0) {} GridView::StatusSyncPreview GridView::updateSyncPreview(bool hideFiltered, //maps sortedRef to viewRef bool syncCreateLeftActive, bool syncCreateRightActive, bool syncDeleteLeftActive, bool syncDeleteRightActive, bool syncDirOverwLeftActive, bool syncDirOverwRightActive, bool syncDirNoneActive, bool syncEqualActive, bool conflictFilesActive) { StatusSyncPreview output; updateView([&](const FileSystemObject& fsObj) -> bool { if (hideFiltered && !fsObj.isActive()) return false; switch (fsObj.getSyncOperation()) //evaluate comparison result and sync direction { case SO_CREATE_NEW_LEFT: output.existsSyncCreateLeft = true; if (!syncCreateLeftActive) return false; break; case SO_CREATE_NEW_RIGHT: output.existsSyncCreateRight = true; if (!syncCreateRightActive) return false; break; case SO_DELETE_LEFT: output.existsSyncDeleteLeft = true; if (!syncDeleteLeftActive) return false; break; case SO_DELETE_RIGHT: output.existsSyncDeleteRight = true; if (!syncDeleteRightActive) return false; break; case SO_OVERWRITE_RIGHT: case SO_COPY_METADATA_TO_RIGHT: //no extra button on screen case SO_MOVE_RIGHT_SOURCE: case SO_MOVE_RIGHT_TARGET: output.existsSyncDirRight = true; if (!syncDirOverwRightActive) return false; break; case SO_OVERWRITE_LEFT: case SO_COPY_METADATA_TO_LEFT: //no extra button on screen case SO_MOVE_LEFT_TARGET: case SO_MOVE_LEFT_SOURCE: output.existsSyncDirLeft = true; if (!syncDirOverwLeftActive) return false; break; case SO_DO_NOTHING: output.existsSyncDirNone = true; if (!syncDirNoneActive) return false; break; case SO_EQUAL: output.existsSyncEqual = true; if (!syncEqualActive) return false; break; case SO_UNRESOLVED_CONFLICT: output.existsConflict = true; if (!conflictFilesActive) return false; break; } //calculate total number of bytes for each side getNumbers(fsObj, output); return true; }); return output; } std::vector GridView::getAllFileRef(const std::set& rows) { std::vector output; auto iterLast = rows.lower_bound(rowsOnView()); //loop over valid rows only! std::for_each(rows.begin(), iterLast, [&](size_t pos) { if (FileSystemObject* fsObj = FileSystemObject::retrieve(viewRef[pos])) output.push_back(fsObj); }); return output; } void GridView::removeInvalidRows() { viewRef.clear(); rowPositions.clear(); rowPositionsFirstChild.clear(); //remove rows that have been deleted meanwhile vector_remove_if(sortedRef, [&](const RefIndex& refIdx) { return FileSystemObject::retrieve(refIdx.objId) == nullptr; }); } class GridView::SerializeHierarchy { public: SerializeHierarchy(std::vector& sortedRef, size_t index) : index_(index), sortedRef_(sortedRef) {} void execute(HierarchyObject& hierObj) { std::for_each(hierObj.refSubFiles().begin(), hierObj.refSubFiles().end(), *this); std::for_each(hierObj.refSubLinks().begin(), hierObj.refSubLinks().end(), *this); std::for_each(hierObj.refSubDirs ().begin(), hierObj.refSubDirs ().end(), *this); } void operator()(FilePair& fileObj) { sortedRef_.push_back(RefIndex(index_, fileObj.getId())); } void operator()(SymlinkPair& linkObj) { sortedRef_.push_back(RefIndex(index_, linkObj.getId())); } void operator()(DirPair& dirObj) { sortedRef_.push_back(RefIndex(index_, dirObj.getId())); execute(dirObj); //add recursion here to list sub-objects directly below parent! } private: size_t index_; std::vector& sortedRef_; }; void GridView::setData(FolderComparison& folderCmp) { //clear everything std::vector().swap(viewRef); //free mem std::vector().swap(sortedRef); // currentSort.reset(); folderPairCount = std::count_if(begin(folderCmp), end(folderCmp), [](const BaseDirPair& baseObj) //count non-empty pairs to distinguish single/multiple folder pair cases { return !baseObj.getBaseDirPf().empty() || !baseObj.getBaseDirPf().empty(); }); for (auto it = begin(folderCmp); it != end(folderCmp); ++it) SerializeHierarchy(sortedRef, it - begin(folderCmp)).execute(*it); } //------------------------------------ SORTING TEMPLATES ------------------------------------------------ template class GridView::LessRelativeName : public std::binary_function { public: bool operator()(const RefIndex a, const RefIndex b) const { //presort by folder pair if (a.folderIndex != b.folderIndex) return ascending ? a.folderIndex < b.folderIndex : a.folderIndex > b.folderIndex; const FileSystemObject* fsObjA = FileSystemObject::retrieve(a.objId); const FileSystemObject* fsObjB = FileSystemObject::retrieve(b.objId); if (!fsObjA) //invalid rows shall appear at the end return false; else if (!fsObjB) return true; return lessRelativeName(*fsObjA, *fsObjB); } }; template class GridView::LessShortFileName : public std::binary_function { public: bool operator()(const RefIndex a, const RefIndex b) const { const FileSystemObject* fsObjA = FileSystemObject::retrieve(a.objId); const FileSystemObject* fsObjB = FileSystemObject::retrieve(b.objId); if (!fsObjA) //invalid rows shall appear at the end return false; else if (!fsObjB) return true; return lessShortFileName(*fsObjA, *fsObjB); } }; template class GridView::LessFilesize : public std::binary_function { public: bool operator()(const RefIndex a, const RefIndex b) const { const FileSystemObject* fsObjA = FileSystemObject::retrieve(a.objId); const FileSystemObject* fsObjB = FileSystemObject::retrieve(b.objId); if (!fsObjA) //invalid rows shall appear at the end return false; else if (!fsObjB) return true; return lessFilesize(*fsObjA, *fsObjB); } }; template class GridView::LessFiletime : public std::binary_function { public: bool operator()(const RefIndex a, const RefIndex b) const { const FileSystemObject* fsObjA = FileSystemObject::retrieve(a.objId); const FileSystemObject* fsObjB = FileSystemObject::retrieve(b.objId); if (!fsObjA) //invalid rows shall appear at the end return false; else if (!fsObjB) return true; return lessFiletime(*fsObjA, *fsObjB); } }; template class GridView::LessExtension : public std::binary_function { public: bool operator()(const RefIndex a, const RefIndex b) const { const FileSystemObject* fsObjA = FileSystemObject::retrieve(a.objId); const FileSystemObject* fsObjB = FileSystemObject::retrieve(b.objId); if (!fsObjA) //invalid rows shall appear at the end return false; else if (!fsObjB) return true; return lessExtension(*fsObjA, *fsObjB); } }; template class GridView::LessCmpResult : public std::binary_function { public: bool operator()(const RefIndex a, const RefIndex b) const { const FileSystemObject* fsObjA = FileSystemObject::retrieve(a.objId); const FileSystemObject* fsObjB = FileSystemObject::retrieve(b.objId); if (!fsObjA) //invalid rows shall appear at the end return false; else if (!fsObjB) return true; return lessCmpResult(*fsObjA, *fsObjB); } }; template class GridView::LessSyncDirection : public std::binary_function { public: bool operator()(const RefIndex a, const RefIndex b) const { const FileSystemObject* fsObjA = FileSystemObject::retrieve(a.objId); const FileSystemObject* fsObjB = FileSystemObject::retrieve(b.objId); if (!fsObjA) //invalid rows shall appear at the end return false; else if (!fsObjB) return true; return lessSyncDirection(*fsObjA, *fsObjB); } }; //------------------------------------------------------------------------------------------------------- bool GridView::getDefaultSortDirection(ColumnTypeRim type) //true: ascending; false: descending { switch (type) { case COL_TYPE_SIZE: case COL_TYPE_DATE: return false; case COL_TYPE_DIRECTORY: case COL_TYPE_FULL_PATH: case COL_TYPE_REL_PATH: case COL_TYPE_FILENAME: case COL_TYPE_EXTENSION: return true; } assert(false); return true; } void GridView::sortView(ColumnTypeRim type, bool onLeft, bool ascending) { viewRef.clear(); rowPositions.clear(); rowPositionsFirstChild.clear(); currentSort = make_unique(type, onLeft, ascending); switch (type) { case COL_TYPE_FULL_PATH: case COL_TYPE_REL_PATH: if ( ascending) std::sort(sortedRef.begin(), sortedRef.end(), LessRelativeName()); else if (!ascending) std::sort(sortedRef.begin(), sortedRef.end(), LessRelativeName()); break; case COL_TYPE_FILENAME: if ( ascending && onLeft) std::sort(sortedRef.begin(), sortedRef.end(), LessShortFileName()); else if ( ascending && !onLeft) std::sort(sortedRef.begin(), sortedRef.end(), LessShortFileName()); else if (!ascending && onLeft) std::sort(sortedRef.begin(), sortedRef.end(), LessShortFileName()); else if (!ascending && !onLeft) std::sort(sortedRef.begin(), sortedRef.end(), LessShortFileName()); break; case COL_TYPE_SIZE: if ( ascending && onLeft) std::sort(sortedRef.begin(), sortedRef.end(), LessFilesize()); else if ( ascending && !onLeft) std::sort(sortedRef.begin(), sortedRef.end(), LessFilesize()); else if (!ascending && onLeft) std::sort(sortedRef.begin(), sortedRef.end(), LessFilesize()); else if (!ascending && !onLeft) std::sort(sortedRef.begin(), sortedRef.end(), LessFilesize()); break; case COL_TYPE_DATE: if ( ascending && onLeft) std::sort(sortedRef.begin(), sortedRef.end(), LessFiletime()); else if ( ascending && !onLeft) std::sort(sortedRef.begin(), sortedRef.end(), LessFiletime()); else if (!ascending && onLeft) std::sort(sortedRef.begin(), sortedRef.end(), LessFiletime()); else if (!ascending && !onLeft) std::sort(sortedRef.begin(), sortedRef.end(), LessFiletime()); break; case COL_TYPE_EXTENSION: if ( ascending && onLeft) std::stable_sort(sortedRef.begin(), sortedRef.end(), LessExtension()); else if ( ascending && !onLeft) std::stable_sort(sortedRef.begin(), sortedRef.end(), LessExtension()); else if (!ascending && onLeft) std::stable_sort(sortedRef.begin(), sortedRef.end(), LessExtension()); else if (!ascending && !onLeft) std::stable_sort(sortedRef.begin(), sortedRef.end(), LessExtension()); break; //case SORT_BY_CMP_RESULT: // if ( ascending) std::stable_sort(sortedRef.begin(), sortedRef.end(), LessCmpResult()); // else if (!ascending) std::stable_sort(sortedRef.begin(), sortedRef.end(), LessCmpResult()); // break; //case SORT_BY_SYNC_DIRECTION: // if ( ascending) std::stable_sort(sortedRef.begin(), sortedRef.end(), LessSyncDirection()); // else if (!ascending) std::stable_sort(sortedRef.begin(), sortedRef.end(), LessSyncDirection()); // break; case COL_TYPE_DIRECTORY: if ( ascending) std::stable_sort(sortedRef.begin(), sortedRef.end(), [](const RefIndex a, const RefIndex b) { return a.folderIndex < b.folderIndex; }); else if (!ascending) std::stable_sort(sortedRef.begin(), sortedRef.end(), [](const RefIndex a, const RefIndex b) { return a.folderIndex > b.folderIndex; }); break; } }