summaryrefslogtreecommitdiff
path: root/synchronization.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'synchronization.cpp')
-rw-r--r--synchronization.cpp2536
1 files changed, 0 insertions, 2536 deletions
diff --git a/synchronization.cpp b/synchronization.cpp
deleted file mode 100644
index f5785b1a..00000000
--- a/synchronization.cpp
+++ /dev/null
@@ -1,2536 +0,0 @@
-// **************************************************************************
-// * 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 "synchronization.h"
-#include <memory>
-#include <deque>
-#include <stdexcept>
-#include <wx/file.h> //get rid!?
-#include <zen/format_unit.h>
-#include <zen/scope_guard.h>
-#include <zen/process_priority.h>
-#include <zen/file_handling.h>
-#include <zen/recycler.h>
-#include <zen/optional.h>
-#include <zen/symlink_target.h>
-#include <zen/file_io.h>
-#include <zen/time.h>
-#include "lib/resolve_path.h"
-#include "lib/db_file.h"
-#include "lib/dir_exist_async.h"
-#include "lib/cmp_filetime.h"
-#include "lib/status_handler_impl.h"
-#include "lib/versioning.h"
-
-#ifdef ZEN_WIN
-#include <zen/long_path_prefix.h>
-#include <zen/perf.h>
-#include "lib/shadow.h"
-#endif
-
-using namespace zen;
-
-
-namespace
-{
-inline
-int getCUD(const SyncStatistics& stat)
-{
- return stat.getCreate() +
- stat.getUpdate() +
- stat.getDelete();
-}
-}
-
-void SyncStatistics::init()
-{
- createLeft = 0;
- createRight = 0;
- updateLeft = 0;
- updateRight = 0;
- deleteLeft = 0;
- deleteRight = 0;
- rowsTotal = 0;
-}
-
-
-SyncStatistics::SyncStatistics(const FolderComparison& folderCmp)
-{
- init();
- std::for_each(begin(folderCmp), end(folderCmp), [&](const BaseDirPair& baseDirObj) { recurse(baseDirObj); });
-}
-
-
-SyncStatistics::SyncStatistics(const HierarchyObject& hierObj)
-{
- init();
- recurse(hierObj);
-}
-
-
-SyncStatistics::SyncStatistics(const FilePair& fileObj)
-{
- init();
- processFile(fileObj);
- rowsTotal += 1;
-}
-
-
-inline
-void SyncStatistics::recurse(const HierarchyObject& hierObj)
-{
- for (const FilePair& fileObj : hierObj.refSubFiles())
- processFile(fileObj);
- for (const SymlinkPair& linkObj : hierObj.refSubLinks())
- processLink(linkObj);
- for (const DirPair& dirObj : hierObj.refSubDirs())
- processDir(dirObj);
-
- rowsTotal += hierObj.refSubDirs(). size();
- rowsTotal += hierObj.refSubFiles().size();
- rowsTotal += hierObj.refSubLinks().size();
-}
-
-
-inline
-void SyncStatistics::processFile(const FilePair& fileObj)
-{
- switch (fileObj.getSyncOperation()) //evaluate comparison result and sync direction
- {
- case SO_CREATE_NEW_LEFT:
- ++createLeft;
- dataToProcess += to<Int64>(fileObj.getFileSize<RIGHT_SIDE>());
- break;
-
- case SO_CREATE_NEW_RIGHT:
- ++createRight;
- dataToProcess += to<Int64>(fileObj.getFileSize<LEFT_SIDE>());
- break;
-
- case SO_DELETE_LEFT:
- ++deleteLeft;
- break;
-
- case SO_DELETE_RIGHT:
- ++deleteRight;
- break;
-
- case SO_MOVE_LEFT_TARGET:
- ++updateLeft;
- break;
-
- case SO_MOVE_RIGHT_TARGET:
- ++updateRight;
- break;
-
- case SO_MOVE_LEFT_SOURCE: //ignore; already counted
- case SO_MOVE_RIGHT_SOURCE: //
- break;
-
- case SO_OVERWRITE_LEFT:
- ++updateLeft;
- dataToProcess += to<Int64>(fileObj.getFileSize<RIGHT_SIDE>());
- break;
-
- case SO_OVERWRITE_RIGHT:
- ++updateRight;
- dataToProcess += to<Int64>(fileObj.getFileSize<LEFT_SIDE>());
- break;
-
- case SO_UNRESOLVED_CONFLICT:
- conflictMsgs.push_back(std::make_pair(fileObj.getObjRelativeName(), fileObj.getSyncOpConflict()));
- break;
-
- case SO_COPY_METADATA_TO_LEFT:
- ++updateLeft;
- break;
-
- case SO_COPY_METADATA_TO_RIGHT:
- ++updateRight;
- break;
-
- case SO_DO_NOTHING:
- case SO_EQUAL:
- break;
- }
-}
-
-
-inline
-void SyncStatistics::processLink(const SymlinkPair& linkObj)
-{
- switch (linkObj.getSyncOperation()) //evaluate comparison result and sync direction
- {
- case SO_CREATE_NEW_LEFT:
- ++createLeft;
- break;
-
- case SO_CREATE_NEW_RIGHT:
- ++createRight;
- break;
-
- case SO_DELETE_LEFT:
- ++deleteLeft;
- break;
-
- case SO_DELETE_RIGHT:
- ++deleteRight;
- break;
-
- case SO_OVERWRITE_LEFT:
- case SO_COPY_METADATA_TO_LEFT:
- ++updateLeft;
- break;
-
- case SO_OVERWRITE_RIGHT:
- case SO_COPY_METADATA_TO_RIGHT:
- ++updateRight;
- break;
-
- case SO_UNRESOLVED_CONFLICT:
- conflictMsgs.push_back(std::make_pair(linkObj.getObjRelativeName(), linkObj.getSyncOpConflict()));
- break;
-
- case SO_MOVE_LEFT_SOURCE:
- case SO_MOVE_RIGHT_SOURCE:
- case SO_MOVE_LEFT_TARGET:
- case SO_MOVE_RIGHT_TARGET:
- assert(false);
- case SO_DO_NOTHING:
- case SO_EQUAL:
- break;
- }
-}
-
-
-inline
-void SyncStatistics::processDir(const DirPair& dirObj)
-{
- switch (dirObj.getSyncOperation()) //evaluate comparison result and sync direction
- {
- case SO_CREATE_NEW_LEFT:
- ++createLeft;
- break;
-
- case SO_CREATE_NEW_RIGHT:
- ++createRight;
- break;
-
- case SO_DELETE_LEFT: //if deletion variant == user-defined directory existing on other volume, this results in a full copy + delete operation!
- ++deleteLeft; //however we cannot (reliably) anticipate this situation, fortunately statistics can be adapted during sync!
- break;
-
- case SO_DELETE_RIGHT:
- ++deleteRight;
- break;
-
- case SO_UNRESOLVED_CONFLICT:
- conflictMsgs.push_back(std::make_pair(dirObj.getObjRelativeName(), dirObj.getSyncOpConflict()));
- break;
-
- case SO_COPY_METADATA_TO_LEFT:
- ++updateLeft;
- break;
-
- case SO_COPY_METADATA_TO_RIGHT:
- ++updateRight;
- break;
-
- case SO_OVERWRITE_LEFT:
- case SO_OVERWRITE_RIGHT:
- case SO_MOVE_LEFT_SOURCE:
- case SO_MOVE_RIGHT_SOURCE:
- case SO_MOVE_LEFT_TARGET:
- case SO_MOVE_RIGHT_TARGET:
- assert(false);
- case SO_DO_NOTHING:
- case SO_EQUAL:
- break;
- }
-
- recurse(dirObj); //since we model logical stats, we recurse, even if deletion variant is "recycler" or "versioning + same volume", which is a single physical operation!
-}
-
-//-----------------------------------------------------------------------------------------------------------
-
-std::vector<zen::FolderPairSyncCfg> zen::extractSyncCfg(const MainConfiguration& mainCfg)
-{
- //merge first and additional pairs
- std::vector<FolderPairEnh> allPairs;
- allPairs.push_back(mainCfg.firstPair);
- allPairs.insert(allPairs.end(),
- mainCfg.additionalPairs.begin(), //add additional pairs
- mainCfg.additionalPairs.end());
-
- std::vector<FolderPairSyncCfg> output;
-
- //process all pairs
- for (const FolderPairEnh& fp : allPairs)
- {
- SyncConfig syncCfg = fp.altSyncConfig.get() ? *fp.altSyncConfig : mainCfg.syncCfg;
-
- output.push_back(
- FolderPairSyncCfg(syncCfg.directionCfg.var == DirectionConfig::TWOWAY || detectMovedFilesEnabled(syncCfg.directionCfg),
- syncCfg.handleDeletion,
- syncCfg.versioningStyle,
- getFormattedDirectoryName(syncCfg.versioningDirectory)));
- }
- return output;
-}
-
-//------------------------------------------------------------------------------------------------------------
-
-//test if user accidentally selected the wrong folders to sync
-bool significantDifferenceDetected(const SyncStatistics& folderPairStat)
-{
- //initial file copying shall not be detected as major difference
- if ((folderPairStat.getCreate<LEFT_SIDE >() == 0 ||
- folderPairStat.getCreate<RIGHT_SIDE>() == 0) &&
- folderPairStat.getUpdate () == 0 &&
- folderPairStat.getDelete () == 0 &&
- folderPairStat.getConflict() == 0)
- return false;
-
- const int nonMatchingRows = folderPairStat.getCreate() +
- folderPairStat.getDelete();
- //folderPairStat.getUpdate() + -> not relevant when testing for "wrong folder selected"
- //folderPairStat.getConflict();
-
- return nonMatchingRows >= 10 && nonMatchingRows > 0.5 * folderPairStat.getRowCount();
-}
-
-//#################################################################################################################
-
-class DeletionHandling //abstract deletion variants: permanently, recycle bin, user-defined directory
-{
-public:
- DeletionHandling(DeletionPolicy handleDel, //nothrow!
- const Zstring& versioningDir,
- VersioningStyle versioningStyle,
- const TimeComp& timeStamp,
- const Zstring& baseDirPf, //with separator postfix
- ProcessCallback& procCallback);
- ~DeletionHandling()
- {
- //always (try to) clean up, even if synchronization is aborted!
- try
- {
- tryCleanup(false); //throw FileError, (throw X)
- }
- catch (FileError&) {}
- catch (...) { assert(false); } //what is this?
- /*
- may block heavily, but still do not allow user callback:
- -> avoid throwing user cancel exception again, leading to incomplete clean-up!
- */
- }
-
- //clean-up temporary directory (recycle bin optimization)
- void tryCleanup(bool allowUserCallback = true); //throw FileError; throw X -> call this in non-exceptional coding, i.e. somewhere after sync!
-
- template <class Function> void removeFileUpdating(const Zstring& fullName, const Zstring& relativeName, const Int64& bytesExpected, Function notifyItemDeletion); //throw FileError
- template <class Function> void removeDirUpdating (const Zstring& fullName, const Zstring& relativeName, const Int64& bytesExpected, Function notifyItemDeletion); //reports ONLY data delta via updateProcessedData()!
- template <class Function> void removeLinkUpdating(const Zstring& fullName, const Zstring& relativeName, const Int64& bytesExpected, Function notifyItemDeletion); //
-
- const std::wstring& getTxtRemovingFile () const { return txtRemovingFile; } //
- const std::wstring& getTxtRemovingSymLink() const { return txtRemovingSymlink; } //buffered status texts
- const std::wstring& getTxtRemovingDir () const { return txtRemovingDirectory; } //
-
-private:
- DeletionHandling(const DeletionHandling&);
- DeletionHandling& operator=(const DeletionHandling&);
-
- FileVersioner& getOrCreateVersioner() //throw FileError! => dont create in DeletionHandling()!!!
- {
- if (!versioner.get())
- versioner = make_unique<FileVersioner>(versioningDir_, versioningStyle_, timeStamp_); //throw FileError
- return *versioner;
- };
-
- ProcessCallback& procCallback_;
- const Zstring baseDirPf_; //ends with path separator
- const Zstring versioningDir_;
- const VersioningStyle versioningStyle_;
- const TimeComp timeStamp_;
-
-#ifdef ZEN_WIN
- Zstring getOrCreateRecyclerTempDirPf(); //throw FileError
- Zstring recyclerTmpDir; //temporary folder holding files/folders for *deferred* recycling
- std::vector<Zstring> toBeRecycled; //full path of files located in temporary folder, waiting for batch-recycling
-#endif
-
- //magage three states: allow dynamic fallback from recycler to permanent deletion
- const DeletionPolicy deletionPolicy_;
- std::unique_ptr<FileVersioner> versioner; //used for DELETE_TO_VERSIONING; throw FileError in constructor => create on demand!
-
- //buffer status texts:
- std::wstring txtRemovingFile;
- std::wstring txtRemovingSymlink;
- std::wstring txtRemovingDirectory;
-};
-
-
-DeletionHandling::DeletionHandling(DeletionPolicy handleDel, //nothrow!
- const Zstring& versioningDir,
- VersioningStyle versioningStyle,
- const TimeComp& timeStamp,
- const Zstring& baseDirPf, //with separator postfix
- ProcessCallback& procCallback) :
- procCallback_(procCallback),
- baseDirPf_(baseDirPf),
- versioningDir_(versioningDir),
- versioningStyle_(versioningStyle),
- timeStamp_(timeStamp),
- deletionPolicy_(handleDel)
-{
- switch (deletionPolicy_)
- {
- case DELETE_PERMANENTLY:
- txtRemovingFile = _("Deleting file %x" );
- txtRemovingDirectory = _("Deleting folder %x" );
- txtRemovingSymlink = _("Deleting symbolic link %x");
- break;
-
- case DELETE_TO_RECYCLER:
- txtRemovingFile = _("Moving file %x to the recycle bin" );
- txtRemovingDirectory = _("Moving folder %x to the recycle bin" );
- txtRemovingSymlink = _("Moving symbolic link %x to the recycle bin");
- break;
-
- case DELETE_TO_VERSIONING:
- txtRemovingFile = replaceCpy(_("Moving file %x to %y" ), L"%y", fmtFileName(versioningDir_));
- txtRemovingDirectory = replaceCpy(_("Moving folder %x to %y" ), L"%y", fmtFileName(versioningDir_));
- txtRemovingSymlink = replaceCpy(_("Moving symbolic link %x to %y"), L"%y", fmtFileName(versioningDir_));
- break;
- }
-}
-
-
-#ifdef ZEN_WIN
-namespace
-{
-class CallbackMassRecycling : public CallbackRecycling
-{
-public:
- CallbackMassRecycling(ProcessCallback& statusHandler) :
- statusHandler_(statusHandler),
- txtRecyclingFile(_("Moving file %x to the recycle bin")) {}
-
- //may throw: first exception is swallowed, updateStatus() is then called again where it should throw again and the exception will propagate as expected
- virtual void updateStatus(const Zstring& currentItem)
- {
- if (!currentItem.empty())
- statusHandler_.reportStatus(replaceCpy(txtRecyclingFile, L"%x", fmtFileName(currentItem))); //throw ?
- else
- statusHandler_.requestUiRefresh(); //throw ?
- }
-
-private:
- ProcessCallback& statusHandler_;
- const std::wstring txtRecyclingFile;
-};
-}
-
-//create + returns temporary directory postfixed with file name separator
-//to support later cleanup if automatic deletion fails for whatever reason
-Zstring DeletionHandling::getOrCreateRecyclerTempDirPf() //throw FileError
-{
- assert(!baseDirPf_.empty());
- if (baseDirPf_.empty())
- return Zstring();
-
- if (recyclerTmpDir.empty())
- {
- recyclerTmpDir = [&]
- {
- assert(endsWith(baseDirPf_, FILE_NAME_SEPARATOR));
- /*
- -> this naming convention is too cute and confusing for end users:
-
- //1. generate random directory name
- static std::mt19937 rng(std::time(nullptr)); //don't use std::default_random_engine and leave the choice to the STL implementer!
- //- the alternative std::random_device may not always be available and can even throw an exception!
- //- seed with second precision is sufficient: collisions are handled below
-
- const Zstring chars(Zstr("abcdefghijklmnopqrstuvwxyz")
- Zstr("1234567890"));
- std::uniform_int_distribution<size_t> distrib(0, chars.size() - 1); //takes closed range
-
- auto generatePath = [&]() -> Zstring //e.g. C:\Source\3vkf74fq.ffs_tmp
- {
- Zstring path = baseDirPf;
- for (int i = 0; i < 8; ++i)
- path += chars[distrib(rng)];
- return path + TEMP_FILE_ENDING;
- };
- */
-
- //ensure unique ownership:
- Zstring dirname = baseDirPf_ + Zstr("RecycleBin") + TEMP_FILE_ENDING;
- for (int i = 1;; ++i)
- try
- {
- makeDirectory(dirname, /*bool failIfExists*/ true); //throw FileError, ErrorTargetExisting
- return dirname;
- }
- catch (const ErrorTargetExisting&)
- {
- dirname = baseDirPf_ + Zstr("RecycleBin") + Zchar('_') + numberTo<Zstring>(i) + TEMP_FILE_ENDING;
- }
- }();
- }
- //assemble temporary recycle bin directory with random name and .ffs_tmp ending
- return appendSeparator(recyclerTmpDir);
-}
-#endif
-
-
-void DeletionHandling::tryCleanup(bool allowUserCallback) //throw FileError; throw X
-{
- switch (deletionPolicy_)
- {
- case DELETE_PERMANENTLY:
- break;
-
- case DELETE_TO_RECYCLER:
-#ifdef ZEN_WIN
- if (!toBeRecycled.empty())
- {
- //move content of temporary directory to recycle bin in a single call
- CallbackMassRecycling cbmr(procCallback_);
- recycleOrDelete(toBeRecycled, allowUserCallback ? &cbmr : nullptr); //throw FileError
- toBeRecycled.clear();
- }
-
- //clean up temp directory itself (should contain remnant empty directories only)
- if (!recyclerTmpDir.empty())
- {
- removeDirectory(recyclerTmpDir); //throw FileError
- recyclerTmpDir.clear();
- }
-#endif
- break;
-
- case DELETE_TO_VERSIONING:
- //if (versioner.get())
- //{
- // if (allowUserCallback)
- // {
- // procCallback_.reportStatus(_("Removing old versions...")); //throw ?
- // versioner->limitVersions([&] { procCallback_.requestUiRefresh(); /*throw ? */ }); //throw FileError
- // }
- // else
- // versioner->limitVersions([] {}); //throw FileError
- //}
- break;
- }
-}
-
-
-namespace
-{
-template <class Function>
-struct CallbackRemoveDirImpl : public CallbackRemoveDir
-{
- CallbackRemoveDirImpl(ProcessCallback& statusHandler,
- const DeletionHandling& delHandling,
- Function notifyItemDeletion) :
- statusHandler_(statusHandler),
- notifyItemDeletion_(notifyItemDeletion),
- txtDeletingFile (delHandling.getTxtRemovingFile()),
- txtDeletingFolder(delHandling.getTxtRemovingDir ()) {}
-
-private:
- virtual void onBeforeFileDeletion(const Zstring& filename) { notifyDeletion(txtDeletingFile, filename); }
- virtual void onBeforeDirDeletion (const Zstring& dirname ) { notifyDeletion(txtDeletingFolder, dirname ); }
-
- void notifyDeletion(const std::wstring& statusText, const Zstring& objName)
- {
- notifyItemDeletion_(); //it would be more correct to report *after* work was done!
- statusHandler_.reportStatus(replaceCpy(statusText, L"%x", fmtFileName(objName)));
- }
-
- ProcessCallback& statusHandler_;
- Function notifyItemDeletion_;
- const std::wstring txtDeletingFile;
- const std::wstring txtDeletingFolder;
-};
-
-
-template <class Function>
-class CallbackMoveDirImpl : public CallbackMoveDir
-{
-public:
- CallbackMoveDirImpl(ProcessCallback& callback,
- Int64& bytesReported,
- Function notifyItemDeletion) :
- callback_ (callback),
- bytesReported_(bytesReported),
- notifyItemDeletion_(notifyItemDeletion),
- txtMovingFile (_("Moving file %x to %y")),
- txtMovingFolder (_("Moving folder %x to %y")) {}
-
-private:
- virtual void onBeforeFileMove(const Zstring& fileFrom, const Zstring& fileTo) { notifyMove(txtMovingFile, fileFrom, fileTo); }
- virtual void onBeforeDirMove (const Zstring& dirFrom, const Zstring& dirTo ) { notifyMove(txtMovingFolder, dirFrom, dirTo); }
-
- void notifyMove(const std::wstring& statusText, const Zstring& fileFrom, const Zstring& fileTo) const
- {
- notifyItemDeletion_(); //it would be more correct to report *after* work was done!
- callback_.reportStatus(replaceCpy(replaceCpy(statusText, L"%x", L"\n" + fmtFileName(fileFrom)), L"%y", L"\n" + fmtFileName(fileTo)));
- };
-
- virtual void updateStatus(Int64 bytesDelta)
- {
- callback_.updateProcessedData(0, bytesDelta); //throw()! -> ensure client and service provider are in sync!
- bytesReported_ += bytesDelta; //
-
- callback_.requestUiRefresh(); //may throw
- }
-
- ProcessCallback& callback_;
- Int64& bytesReported_;
- Function notifyItemDeletion_;
- const std::wstring txtMovingFile;
- const std::wstring txtMovingFolder;
-};
-}
-
-
-template <class Function>
-void DeletionHandling::removeDirUpdating(const Zstring& fullName,
- const Zstring& relativeName,
- const Int64& bytesExpected, Function notifyItemDeletion) //throw FileError
-{
- Int64 bytesReported;
- ScopeGuard guardStatistics = makeGuard([&] { procCallback_.updateTotalData(0, bytesReported); }); //error = unexpected increase of total workload
-
- switch (deletionPolicy_)
- {
- case DELETE_PERMANENTLY:
- {
- CallbackRemoveDirImpl<Function> remDirCallback(procCallback_, *this, notifyItemDeletion);
- removeDirectory(fullName, &remDirCallback);
- }
- break;
-
- case DELETE_TO_RECYCLER:
- {
-#ifdef ZEN_WIN
- const Zstring targetDir = getOrCreateRecyclerTempDirPf() + relativeName; //throw FileError
- bool deleted = false;
-
- auto moveToTempDir = [&]
- {
- try
- {
- //performance optimization: Instead of moving each object into recycle bin separately,
- //we rename them one by one into a temporary directory and batch-recycle this directory after sync
- renameFile(fullName, targetDir); //throw FileError, ErrorDifferentVolume
- this->toBeRecycled.push_back(targetDir);
- deleted = true;
- }
- catch (ErrorDifferentVolume&) //MoveFileEx() returns ERROR_PATH_NOT_FOUND *before* considering ERROR_NOT_SAME_DEVICE! => we have to create targetDir in any case!
- {
- deleted = recycleOrDelete(fullName); //throw FileError
- }
- };
-
- try
- {
- moveToTempDir(); //throw FileError, ErrorDifferentVolume
- }
- catch (FileError&)
- {
- if (somethingExists(fullName))
- {
- const Zstring targetSuperDir = beforeLast(targetDir, FILE_NAME_SEPARATOR); //what if C:\ ?
- if (!dirExists(targetSuperDir))
- {
- makeDirectory(targetSuperDir); //throw FileError -> may legitimately fail on Linux if permissions are missing
- moveToTempDir(); //throw FileError -> this should work now!
- }
- else
- throw;
- }
- }
-#elif defined ZEN_LINUX || defined ZEN_MAC
- const bool deleted = recycleOrDelete(fullName); //throw FileError
-#endif
- if (deleted)
- notifyItemDeletion(); //moving to recycler is ONE logical operation, irrespective of the number of child elements!
- }
- break;
-
- case DELETE_TO_VERSIONING:
- {
- CallbackMoveDirImpl<Function> callback(procCallback_, bytesReported, notifyItemDeletion);
- getOrCreateVersioner().revisionDir(fullName, relativeName, callback); //throw FileError
- }
- break;
- }
-
- //update statistics to consider the real amount of data
- guardStatistics.dismiss();
- if (bytesReported != bytesExpected)
- procCallback_.updateTotalData(0, bytesReported - bytesExpected); //noexcept!
-}
-
-
-template <class Function>
-void DeletionHandling::removeFileUpdating(const Zstring& fullName,
- const Zstring& relativeName,
- const Int64& bytesExpected, Function notifyItemDeletion) //throw FileError
-{
- Int64 bytesReported;
- auto guardStatistics = makeGuard([&] { procCallback_.updateTotalData(0, bytesReported); }); //error = unexpected increase of total workload
- bool deleted = false;
-
- if (endsWith(relativeName, TEMP_FILE_ENDING)) //special rule for .ffs_tmp files: always delete permanently!
- deleted = zen::removeFile(fullName);
- else
- switch (deletionPolicy_)
- {
- case DELETE_PERMANENTLY:
- deleted = zen::removeFile(fullName); //[!] scope specifier resolves nameclash!
- break;
-
- case DELETE_TO_RECYCLER:
-#ifdef ZEN_WIN
- {
- const Zstring targetFile = getOrCreateRecyclerTempDirPf() + relativeName; //throw FileError
-
- auto moveToTempDir = [&]
- {
- try
- {
- //performance optimization: Instead of moving each object into recycle bin separately,
- //we rename them one by one into a temporary directory and batch-recycle this directory after sync
- renameFile(fullName, targetFile); //throw FileError, ErrorDifferentVolume
- this->toBeRecycled.push_back(targetFile);
- deleted = true;
- }
- catch (ErrorDifferentVolume&) //MoveFileEx() returns ERROR_PATH_NOT_FOUND *before* considering ERROR_NOT_SAME_DEVICE! => we have to create targetDir in any case!
- {
- deleted = recycleOrDelete(fullName); //throw FileError
- }
- };
-
- try
- {
- moveToTempDir(); //throw FileError, ErrorDifferentVolume
- }
- catch (FileError&)
- {
- if (somethingExists(fullName))
- {
- const Zstring targetDir = beforeLast(targetFile, FILE_NAME_SEPARATOR);
- if (!dirExists(targetDir))
- {
- makeDirectory(targetDir); //throw FileError -> may legitimately fail on Linux if permissions are missing
- moveToTempDir(); //throw FileError -> this should work now!
- }
- else
- throw;
- }
- }
- }
-#elif defined ZEN_LINUX || defined ZEN_MAC
- deleted = recycleOrDelete(fullName); //throw FileError
-#endif
- break;
-
- case DELETE_TO_VERSIONING:
- {
- struct CallbackMoveFileImpl : public CallbackMoveFile
- {
- CallbackMoveFileImpl(ProcessCallback& callback, Int64& bytes) : callback_(callback), bytesReported_(bytes) {}
-
- private:
- virtual void updateStatus(Int64 bytesDelta)
- {
- callback_.updateProcessedData(0, bytesDelta); //throw()! -> ensure client and service provider are in sync!
- bytesReported_ += bytesDelta; //
-
- callback_.requestUiRefresh(); //may throw
- }
- ProcessCallback& callback_;
- Int64& bytesReported_;
- } cb(procCallback_, bytesReported);
-
- deleted = getOrCreateVersioner().revisionFile(fullName, relativeName, cb); //throw FileError
- }
- break;
- }
- if (deleted)
- notifyItemDeletion();
-
- //update statistics to consider the real amount of data
- guardStatistics.dismiss();
- if (bytesReported != bytesExpected)
- procCallback_.updateTotalData(0, bytesReported - bytesExpected); //noexcept!
-}
-
-
-template <class Function> inline
-void DeletionHandling::removeLinkUpdating(const Zstring& fullName, const Zstring& relativeName, const Int64& bytesExpected, Function notifyItemDeletion) //throw FileError
-{
- if (dirExists(fullName)) //dir symlink
- return removeDirUpdating(fullName, relativeName, bytesExpected, notifyItemDeletion); //throw FileError
- else //file symlink, broken symlink
- return removeFileUpdating(fullName, relativeName, bytesExpected, notifyItemDeletion); //throw FileError
-}
-
-//------------------------------------------------------------------------------------------------------------
-
-namespace
-{
-/*
- DELETE_PERMANENTLY: deletion frees space
- DELETE_TO_RECYCLER: won't free space until recycler is full, but then frees space
- DELETE_TO_VERSIONING: depends on whether versioning folder is on a different volume
--> if deleted item is a followed symlink, no space is freed
--> created/updated/deleted item may be on a different volume than base directory: consider symlinks, junctions!
-
-=> generally assume deletion frees space; may avoid false positive disk space warnings for recycler and versioning
-*/
-class MinimumDiskSpaceNeeded
-{
-public:
- static std::pair<Int64, Int64> calculate(const BaseDirPair& baseObj)
- {
- MinimumDiskSpaceNeeded inst;
- inst.recurse(baseObj);
- return std::make_pair(inst.spaceNeededLeft, inst.spaceNeededRight);
- }
-
-private:
- MinimumDiskSpaceNeeded() {}
-
- void recurse(const HierarchyObject& hierObj)
- {
- //don't process directories
-
- //process files
- for (auto it = hierObj.refSubFiles().begin(); it != hierObj.refSubFiles().end(); ++it)
- switch (it->getSyncOperation()) //evaluate comparison result and sync direction
- {
- case SO_CREATE_NEW_LEFT:
- spaceNeededLeft += to<Int64>(it->getFileSize<RIGHT_SIDE>());
- break;
-
- case SO_CREATE_NEW_RIGHT:
- spaceNeededRight += to<Int64>(it->getFileSize<LEFT_SIDE>());
- break;
-
- case SO_DELETE_LEFT:
- //if (freeSpaceDelLeft_)
- spaceNeededLeft -= to<Int64>(it->getFileSize<LEFT_SIDE>());
- break;
-
- case SO_DELETE_RIGHT:
- //if (freeSpaceDelRight_)
- spaceNeededRight -= to<Int64>(it->getFileSize<RIGHT_SIDE>());
- break;
-
- case SO_OVERWRITE_LEFT:
- //if (freeSpaceDelLeft_)
- spaceNeededLeft -= to<Int64>(it->getFileSize<LEFT_SIDE>());
- spaceNeededLeft += to<Int64>(it->getFileSize<RIGHT_SIDE>());
- break;
-
- case SO_OVERWRITE_RIGHT:
- //if (freeSpaceDelRight_)
- spaceNeededRight -= to<Int64>(it->getFileSize<RIGHT_SIDE>());
- spaceNeededRight += to<Int64>(it->getFileSize<LEFT_SIDE>());
- break;
-
- case SO_DO_NOTHING:
- case SO_EQUAL:
- case SO_UNRESOLVED_CONFLICT:
- case SO_COPY_METADATA_TO_LEFT:
- case SO_COPY_METADATA_TO_RIGHT:
- case SO_MOVE_LEFT_SOURCE:
- case SO_MOVE_RIGHT_SOURCE:
- case SO_MOVE_LEFT_TARGET:
- case SO_MOVE_RIGHT_TARGET:
- break;
- }
-
- //symbolic links
- //[...]
-
- //recurse into sub-dirs
- for (auto& subDir : hierObj.refSubDirs())
- recurse(subDir);
- }
-
- Int64 spaceNeededLeft;
- Int64 spaceNeededRight;
-};
-
-//----------------------------------------------------------------------------------------
-
-class SynchronizeFolderPair
-{
-public:
- SynchronizeFolderPair(ProcessCallback& procCallback,
- bool verifyCopiedFiles,
- bool copyFilePermissions,
- bool transactionalFileCopy,
-#ifdef ZEN_WIN
- shadow::ShadowCopy* shadowCopyHandler,
-#endif
- DeletionHandling& delHandlingLeft,
- DeletionHandling& delHandlingRight) :
- procCallback_(procCallback),
-#ifdef ZEN_WIN
- shadowCopyHandler_(shadowCopyHandler),
-#endif
- delHandlingLeft_(delHandlingLeft),
- delHandlingRight_(delHandlingRight),
- verifyCopiedFiles_(verifyCopiedFiles),
- copyFilePermissions_(copyFilePermissions),
- transactionalFileCopy_(transactionalFileCopy),
- txtCreatingFile (_("Creating file %x" )),
- txtCreatingLink (_("Creating symbolic link %x" )),
- txtCreatingFolder (_("Creating folder %x" )),
- txtOverwritingFile (_("Overwriting file %x" )),
- txtOverwritingLink (_("Overwriting symbolic link %x")),
- txtVerifying (_("Verifying file %x" )),
- txtWritingAttributes(_("Updating attributes of %x" )),
- txtMovingFile (_("Moving file %x to %y"))
- {}
-
- void startSync(BaseDirPair& baseDirObj)
- {
- runZeroPass(baseDirObj); //first process file moves
- runPass<PASS_ONE>(baseDirObj); //delete files (or overwrite big ones with smaller ones)
- runPass<PASS_TWO>(baseDirObj); //copy rest
- }
-
-private:
- enum PassId
- {
- PASS_ONE, //delete files
- PASS_TWO, //create, modify
- PASS_NEVER //skip
- };
-
- static PassId getPass(const FilePair& fileObj);
- static PassId getPass(const SymlinkPair& linkObj);
- static PassId getPass(const DirPair& dirObj);
-
- template <SelectedSide side>
- void prepare2StepMove(FilePair& sourceObj, FilePair& targetObj); //throw FileError
- bool createParentDir(FileSystemObject& fsObj); //throw FileError
- template <SelectedSide side>
- void manageFileMove(FilePair& sourceObj, FilePair& targetObj); //throw FileError
-
- void runZeroPass(HierarchyObject& hierObj);
- template <PassId pass>
- void runPass(HierarchyObject& hierObj);
-
- void synchronizeFile(FilePair& fileObj);
- template <SelectedSide side> void synchronizeFileInt(FilePair& fileObj, SyncOperation syncOp);
-
- void synchronizeLink(SymlinkPair& linkObj);
- template <SelectedSide sideTrg> void synchronizeLinkInt(SymlinkPair& linkObj, SyncOperation syncOp);
-
- void synchronizeFolder(DirPair& dirObj);
- template <SelectedSide sideTrg> void synchronizeFolderInt(DirPair& dirObj, SyncOperation syncOp);
-
- void reportStatus(const std::wstring& rawText, const Zstring& objname) const { procCallback_.reportStatus(replaceCpy(rawText, L"%x", fmtFileName(objname))); };
- void reportInfo (const std::wstring& rawText, const Zstring& objname) const { procCallback_.reportInfo (replaceCpy(rawText, L"%x", fmtFileName(objname))); };
- void reportInfo (const std::wstring& rawText,
- const Zstring& objname1,
- const Zstring& objname2) const
- {
- procCallback_.reportInfo(replaceCpy(replaceCpy(rawText, L"%x", L"\n" + fmtFileName(objname1)), L"%y", L"\n" + fmtFileName(objname2)));
- };
-
- template <class Function>
- FileAttrib copyFileUpdating(const Zstring& sourceFile, const Zstring& targetFile, const Int64& bytesExpected, Function delTargetCommand) const; //throw FileError; reports data delta via updateProcessedData()
- void verifyFileCopy(const Zstring& source, const Zstring& target) const;
-
- template <SelectedSide side>
- DeletionHandling& getDelHandling();
-
- ProcessCallback& procCallback_;
-#ifdef ZEN_WIN
- shadow::ShadowCopy* shadowCopyHandler_; //optional!
-#endif
- DeletionHandling& delHandlingLeft_;
- DeletionHandling& delHandlingRight_;
-
- const bool verifyCopiedFiles_;
- const bool copyFilePermissions_;
- const bool transactionalFileCopy_;
-
- //preload status texts
- const std::wstring txtCreatingFile;
- const std::wstring txtCreatingLink;
- const std::wstring txtCreatingFolder;
- const std::wstring txtOverwritingFile;
- const std::wstring txtOverwritingLink;
- const std::wstring txtVerifying;
- const std::wstring txtWritingAttributes;
- const std::wstring txtMovingFile;
-};
-
-//---------------------------------------------------------------------------------------------------------------
-
-template <> inline
-DeletionHandling& SynchronizeFolderPair::getDelHandling<LEFT_SIDE>() { return delHandlingLeft_; }
-
-template <> inline
-DeletionHandling& SynchronizeFolderPair::getDelHandling<RIGHT_SIDE>() { return delHandlingRight_; }
-}
-
-/*
-__________________________
-|Move algorithm, 0th pass|
---------------------------
-1. loop over hierarchy and find "move source"
-
-2. check whether parent directory of "move source" is going to be deleted or location of "move source" may lead to name clash with other dir/symlink
- -> no: delay move until 2nd pass
-
-3. create move target's parent directory recursively + execute move
- do we have name clash?
- -> prepare a 2-step move operation: 1. move source to root and update "move target" accordingly 2. delay move until 2nd pass
-
-4. If any of the operations above did not succeed (even after retry), update statistics and revert to "copy + delete"
- Note: first pass may delete "move source"!!!
-
-__________________
-|killer-scenarios|
-------------------
-propagate the following move sequences:
-I) a -> a/a caveat sync'ing parent directory first leads to circular dependency!
-
-II) a/a -> a caveat: fixing name clash will remove source!
-
-III) c -> d caveat: move-sequence needs to be processed in correct order!
- b -> c/b
- a -> b/a
-*/
-
-namespace
-{
-template <class List> inline
-bool haveNameClash(const Zstring& shortname, List& m)
-{
- return std::any_of(m.begin(), m.end(),
- [&](const typename List::value_type& obj) { return EqualFilename()(obj.getObjShortName(), shortname); });
-}
-
-
-Zstring findUnusedTempName(const Zstring& filename)
-{
- Zstring output = filename + zen::TEMP_FILE_ENDING;
-
- //ensure uniqueness (+ minor file system race condition!)
- for (int i = 1; somethingExists(output); ++i)
- output = filename + Zchar('_') + numberTo<Zstring>(i) + zen::TEMP_FILE_ENDING;
-
- return output;
-}
-}
-
-
-template <SelectedSide side>
-void SynchronizeFolderPair::prepare2StepMove(FilePair& sourceObj,
- FilePair& targetObj) //throw FileError
-{
- const Zstring& source = sourceObj.getFullName<side>();
- const Zstring& tmpTarget = findUnusedTempName(sourceObj.getBaseDirPf<side>() + sourceObj.getShortName<side>());
- //this could still lead to a name-clash in obscure cases, if some file exists on the other side with
- //the very same (.ffs_tmp) name and is copied before the second step of the move is executed
- //good news: even in this pathologic case, this may only prevent the copy of the other file, but not the move
-
- reportInfo(txtMovingFile, source, tmpTarget);
-
- warn_static("was wenn diff volume: symlink aliasing!") //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
-
- renameFile(source, tmpTarget); //throw FileError
-
- //update file hierarchy
- const FileDescriptor descrSource(sourceObj.getLastWriteTime <side>(),
- sourceObj.getFileSize <side>(),
- sourceObj.getFileId <side>(),
- sourceObj.isFollowedSymlink<side>());
-
- FilePair& tempFile = sourceObj.root().addSubFile<side>(afterLast(tmpTarget, FILE_NAME_SEPARATOR), descrSource);
- static_assert(IsSameType<FixedList<FilePair>, HierarchyObject::SubFileVec>::value,
- "ATTENTION: we're adding to the file list WHILE looping over it! This is only working because FixedList iterators are not invalidated by insertion!");
- sourceObj.removeObject<side>(); //remove only *after* evaluating "sourceObj, side"!
-
- //prepare move in second pass
- tempFile.setSyncDir(side == LEFT_SIDE ? SyncDirection::LEFT : SyncDirection::RIGHT);
-
- targetObj.setMoveRef(tempFile .getId());
- tempFile .setMoveRef(targetObj.getId());
-
- //NO statistics update!
- procCallback_.requestUiRefresh(); //may throw
-}
-
-
-bool SynchronizeFolderPair::createParentDir(FileSystemObject& fsObj) //throw FileError, "false" on name clash
-{
- if (DirPair* parentDir = dynamic_cast<DirPair*>(&fsObj.parent()))
- {
- if (!createParentDir(*parentDir))
- return false;
-
- //detect (and try to resolve) file type conflicts: 1. symlinks 2. files
- const Zstring& shortname = parentDir->getObjShortName();
- if (haveNameClash(shortname, parentDir->parent().refSubLinks()) ||
- haveNameClash(shortname, parentDir->parent().refSubFiles()))
- return false;
-
- //in this context "parentDir" cannot be scheduled for deletion since it contains a "move target"!
- //note: if parentDir were deleted, we'd end up destroying "fsObj"!
- assert(parentDir->getSyncOperation() != SO_DELETE_LEFT &&
- parentDir->getSyncOperation() != SO_DELETE_RIGHT);
-
- synchronizeFolder(*parentDir); //throw FileError
- }
- return true;
-}
-
-
-template <SelectedSide side>
-void SynchronizeFolderPair::manageFileMove(FilePair& sourceObj,
- FilePair& targetObj) //throw FileError
-{
- assert((sourceObj.getSyncOperation() == SO_MOVE_LEFT_SOURCE && targetObj.getSyncOperation() == SO_MOVE_LEFT_TARGET && side == LEFT_SIDE) ||
- (sourceObj.getSyncOperation() == SO_MOVE_RIGHT_SOURCE && targetObj.getSyncOperation() == SO_MOVE_RIGHT_TARGET && side == RIGHT_SIDE));
-
- const bool sourceWillBeDeleted = [&]() -> bool
- {
- if (DirPair* parentDir = dynamic_cast<DirPair*>(&sourceObj.parent()))
- {
- switch (parentDir->getSyncOperation()) //evaluate comparison result and sync direction
- {
- case SO_DELETE_LEFT:
- case SO_DELETE_RIGHT:
- return true; //we need to do something about it
- case SO_MOVE_LEFT_SOURCE:
- case SO_MOVE_RIGHT_SOURCE:
- case SO_MOVE_LEFT_TARGET:
- case SO_MOVE_RIGHT_TARGET:
- case SO_OVERWRITE_LEFT:
- case SO_OVERWRITE_RIGHT:
- case SO_CREATE_NEW_LEFT:
- case SO_CREATE_NEW_RIGHT:
- case SO_DO_NOTHING:
- case SO_EQUAL:
- case SO_UNRESOLVED_CONFLICT:
- case SO_COPY_METADATA_TO_LEFT:
- case SO_COPY_METADATA_TO_RIGHT:
- break;
- }
- }
- return false;
- }();
-
- auto haveNameClash = [](const FilePair& fileObj)
- {
- return ::haveNameClash(fileObj.getObjShortName(), fileObj.parent().refSubLinks()) ||
- ::haveNameClash(fileObj.getObjShortName(), fileObj.parent().refSubDirs());
- };
-
- if (sourceWillBeDeleted || haveNameClash(sourceObj))
- {
- //prepare for move now: - revert to 2-step move on name clashes
- if (haveNameClash(targetObj) ||
- !createParentDir(targetObj)) //throw FileError
- return prepare2StepMove<side>(sourceObj, targetObj); //throw FileError
-
- //finally start move! this should work now:
- synchronizeFile(targetObj); //throw FileError
- //SynchronizeFolderPair::synchronizeFileInt() is *not* expecting SO_MOVE_LEFT_SOURCE/SO_MOVE_RIGHT_SOURCE => start move from targetObj, not sourceObj!
- }
- //else: sourceObj will not be deleted, and is not standing in the way => delay to second pass
- //note: this case may include new "move sources" from two-step sub-routine!!!
-}
-
-
-//search for file move-operations
-void SynchronizeFolderPair::runZeroPass(HierarchyObject& hierObj)
-{
- for (auto it = hierObj.refSubFiles().begin(); it != hierObj.refSubFiles().end(); ++it) //VS 2010 crashes if we use for_each + lambda here...
- {
- FilePair& fileObj = *it;
-
- const SyncOperation syncOp = fileObj.getSyncOperation();
- switch (syncOp) //evaluate comparison result and sync direction
- {
- case SO_MOVE_LEFT_SOURCE:
- case SO_MOVE_RIGHT_SOURCE:
- {
- FilePair* sourceObj = &fileObj;
- if (FilePair* targetObj = dynamic_cast<FilePair*>(FileSystemObject::retrieve(fileObj.getMoveRef())))
- {
- zen::Opt<std::wstring> errMsg = tryReportingError([&]
- {
- if (syncOp == SO_MOVE_LEFT_SOURCE)
- this->manageFileMove<LEFT_SIDE>(*sourceObj, *targetObj); //throw FileError
- else
- this->manageFileMove<RIGHT_SIDE>(*sourceObj, *targetObj); //
- }, procCallback_);
-
- if (errMsg)
- {
- //move operation has failed! We cannot allow to continue and have move source's parent directory deleted, messing up statistics!
- // => revert to ordinary "copy + delete"
-
- auto getStat = [&]() -> std::pair<int, Int64>
- {
- SyncStatistics statSrc(*sourceObj);
- SyncStatistics statTrg(*targetObj);
-
- return std::make_pair(getCUD(statSrc) + getCUD(statTrg),
- statSrc.getDataToProcess() + statTrg.getDataToProcess());
- };
-
- const auto statBefore = getStat();
- sourceObj->setMoveRef(nullptr);
- targetObj->setMoveRef(nullptr);
- const auto statAfter = getStat();
- //fix statistics to total to match "copy + delete"
- procCallback_.updateTotalData(statAfter.first - statBefore.first, statAfter.second - statBefore.second);
- }
- }
- }
- break;
- case SO_MOVE_LEFT_TARGET: //it's enough to try each move-pair *once*
- case SO_MOVE_RIGHT_TARGET: //
- case SO_DELETE_LEFT:
- case SO_DELETE_RIGHT:
- case SO_OVERWRITE_LEFT:
- case SO_OVERWRITE_RIGHT:
- case SO_CREATE_NEW_LEFT:
- case SO_CREATE_NEW_RIGHT:
- case SO_DO_NOTHING:
- case SO_EQUAL:
- case SO_UNRESOLVED_CONFLICT:
- case SO_COPY_METADATA_TO_LEFT:
- case SO_COPY_METADATA_TO_RIGHT:
- break;
- }
- }
-
- std::for_each(hierObj.refSubDirs().begin(), hierObj.refSubDirs().end(),
- [&](DirPair& dirObj) { this->runZeroPass(dirObj); /*recurse */ });
-}
-
-//---------------------------------------------------------------------------------------------------------------
-
-//1st, 2nd pass requirements:
-// - avoid disk space shortage: 1. delete files, 2. overwrite big with small files first
-// - support change in type: overwrite file by directory, symlink by file, ect.
-
-inline
-SynchronizeFolderPair::PassId SynchronizeFolderPair::getPass(const FilePair& fileObj)
-{
- switch (fileObj.getSyncOperation()) //evaluate comparison result and sync direction
- {
- case SO_DELETE_LEFT:
- case SO_DELETE_RIGHT:
- return PASS_ONE;
-
- case SO_OVERWRITE_LEFT:
- return fileObj.getFileSize<LEFT_SIDE>() > fileObj.getFileSize<RIGHT_SIDE>() ? PASS_ONE : PASS_TWO;
-
- case SO_OVERWRITE_RIGHT:
- return fileObj.getFileSize<LEFT_SIDE>() < fileObj.getFileSize<RIGHT_SIDE>() ? PASS_ONE : PASS_TWO;
-
- case SO_MOVE_LEFT_SOURCE: //
- case SO_MOVE_RIGHT_SOURCE: // [!]
- return PASS_NEVER;
- case SO_MOVE_LEFT_TARGET: //
- case SO_MOVE_RIGHT_TARGET: //make sure 2-step move is processed in second pass, after move *target* parent directory was created!
- return PASS_TWO;
-
- case SO_CREATE_NEW_LEFT:
- case SO_CREATE_NEW_RIGHT:
- case SO_COPY_METADATA_TO_LEFT:
- case SO_COPY_METADATA_TO_RIGHT:
- return PASS_TWO;
-
- case SO_DO_NOTHING:
- case SO_EQUAL:
- case SO_UNRESOLVED_CONFLICT:
- return PASS_NEVER;
- }
- assert(false);
- return PASS_TWO; //dummy
-}
-
-
-inline
-SynchronizeFolderPair::PassId SynchronizeFolderPair::getPass(const SymlinkPair& linkObj)
-{
- switch (linkObj.getSyncOperation()) //evaluate comparison result and sync direction
- {
- case SO_DELETE_LEFT:
- case SO_DELETE_RIGHT:
- return PASS_ONE; //make sure to delete symlinks in first pass, and equally named file or dir in second pass: usecase "overwrite symlink with regular file"!
-
- case SO_OVERWRITE_LEFT:
- case SO_OVERWRITE_RIGHT:
- case SO_CREATE_NEW_LEFT:
- case SO_CREATE_NEW_RIGHT:
- case SO_COPY_METADATA_TO_LEFT:
- case SO_COPY_METADATA_TO_RIGHT:
- return PASS_TWO;
-
- case SO_MOVE_LEFT_SOURCE:
- case SO_MOVE_RIGHT_SOURCE:
- case SO_MOVE_LEFT_TARGET:
- case SO_MOVE_RIGHT_TARGET:
- assert(false);
- case SO_DO_NOTHING:
- case SO_EQUAL:
- case SO_UNRESOLVED_CONFLICT:
- return PASS_NEVER;
- }
- assert(false);
- return PASS_TWO; //dummy
-}
-
-
-inline
-SynchronizeFolderPair::PassId SynchronizeFolderPair::getPass(const DirPair& dirObj)
-{
- switch (dirObj.getSyncOperation()) //evaluate comparison result and sync direction
- {
- case SO_DELETE_LEFT:
- case SO_DELETE_RIGHT:
- return PASS_ONE;
-
- case SO_CREATE_NEW_LEFT:
- case SO_CREATE_NEW_RIGHT:
- case SO_COPY_METADATA_TO_LEFT:
- case SO_COPY_METADATA_TO_RIGHT:
- return PASS_TWO;
-
- case SO_OVERWRITE_LEFT:
- case SO_OVERWRITE_RIGHT:
- case SO_MOVE_LEFT_SOURCE:
- case SO_MOVE_RIGHT_SOURCE:
- case SO_MOVE_LEFT_TARGET:
- case SO_MOVE_RIGHT_TARGET:
- assert(false);
- case SO_DO_NOTHING:
- case SO_EQUAL:
- case SO_UNRESOLVED_CONFLICT:
- return PASS_NEVER;
- }
- assert(false);
- return PASS_TWO; //dummy
-}
-
-
-template <SynchronizeFolderPair::PassId pass>
-void SynchronizeFolderPair::runPass(HierarchyObject& hierObj)
-{
- //synchronize files:
- for (FilePair& fileObj : hierObj.refSubFiles())
- if (pass == this->getPass(fileObj)) //"this->" required by two-pass lookup as enforced by GCC 4.7
- tryReportingError([&] { synchronizeFile(fileObj); }, procCallback_);
-
- //synchronize symbolic links:
- for (SymlinkPair& linkObj : hierObj.refSubLinks())
- if (pass == this->getPass(linkObj))
- tryReportingError([&] { synchronizeLink(linkObj); }, procCallback_);
-
- //synchronize folders:
- for (DirPair& dirObj : hierObj.refSubDirs())
- {
- if (pass == this->getPass(dirObj))
- tryReportingError([&] { synchronizeFolder(dirObj); }, procCallback_);
-
- this->runPass<pass>(dirObj); //recurse
- }
-}
-
-//---------------------------------------------------------------------------------------------------------------
-
-namespace
-{
-inline
-Opt<SelectedSide> getTargetDirection(SyncOperation syncOp)
-{
- switch (syncOp)
- {
- case SO_CREATE_NEW_LEFT:
- case SO_DELETE_LEFT:
- case SO_OVERWRITE_LEFT:
- case SO_COPY_METADATA_TO_LEFT:
- case SO_MOVE_LEFT_SOURCE:
- case SO_MOVE_LEFT_TARGET:
- return LEFT_SIDE;
-
- case SO_CREATE_NEW_RIGHT:
- case SO_DELETE_RIGHT:
- case SO_OVERWRITE_RIGHT:
- case SO_COPY_METADATA_TO_RIGHT:
- case SO_MOVE_RIGHT_SOURCE:
- case SO_MOVE_RIGHT_TARGET:
- return RIGHT_SIDE;
-
- case SO_DO_NOTHING:
- case SO_EQUAL:
- case SO_UNRESOLVED_CONFLICT:
- break; //nothing to do
- }
- return NoValue();
-}
-}
-
-
-inline
-void SynchronizeFolderPair::synchronizeFile(FilePair& fileObj)
-{
- const SyncOperation syncOp = fileObj.getSyncOperation();
-
- if (Opt<SelectedSide> sideTrg = getTargetDirection(syncOp))
- {
- if (*sideTrg == LEFT_SIDE)
- synchronizeFileInt<LEFT_SIDE>(fileObj, syncOp);
- else
- synchronizeFileInt<RIGHT_SIDE>(fileObj, syncOp);
- }
-}
-
-
-template <SelectedSide sideTrg>
-void SynchronizeFolderPair::synchronizeFileInt(FilePair& fileObj, SyncOperation syncOp)
-{
- static const SelectedSide sideSrc = OtherSide<sideTrg>::result;
-
- switch (syncOp)
- {
- case SO_CREATE_NEW_LEFT:
- case SO_CREATE_NEW_RIGHT:
- {
- if (const DirPair* parentDir = dynamic_cast<DirPair*>(&fileObj.parent()))
- if (parentDir->isEmpty<sideTrg>()) //BaseDirPair OTOH is always non-empty and existing in this context => else: fatal error in zen::synchronize()
- return; //if parent directory creation failed, there's no reason to show more errors!
-
- const Zstring& target = fileObj.getBaseDirPf<sideTrg>() + fileObj.getRelativeName<sideSrc>(); //can't use "getFullName" as target is not yet existing
- reportInfo(txtCreatingFile, target);
-
- try
- {
- const FileAttrib newAttr = copyFileUpdating(fileObj.getFullName<sideSrc>(),
- target,
- to<Int64>(fileObj.getFileSize<sideSrc>()), [] {} /*no target to delete*/); //throw FileError
- //update FilePair
- fileObj.setSyncedTo<sideTrg>(fileObj.getShortName<sideSrc>(), newAttr.fileSize,
- newAttr.modificationTime, //target time set from source
- newAttr.modificationTime,
- newAttr.targetFileId,
- newAttr.sourceFileId,
- false, fileObj.isFollowedSymlink<sideSrc>());
-
- procCallback_.updateProcessedData(1, 0); //processed bytes are reported in copyFileUpdating()!
- }
- catch (FileError&)
- {
- if (somethingExists(fileObj.getFullName<sideSrc>())) //do not check on type (symlink, file, folder) -> if there is a type change, FFS should error out!
- throw;
- //source deleted meanwhile...nothing was done (logical point of view!)
- procCallback_.updateTotalData(-1, -to<zen::Int64>(fileObj.getFileSize<sideSrc>()));
- fileObj.removeObject<sideSrc>(); //remove only *after* evaluating "fileObj, sideSrc"!
- }
- }
- break;
-
- case SO_DELETE_LEFT:
- case SO_DELETE_RIGHT:
- reportInfo(getDelHandling<sideTrg>().getTxtRemovingFile(), fileObj.getFullName<sideTrg>());
- {
- int objectsReported = 0;
- auto guardStatistics = makeGuard([&] { procCallback_.updateTotalData(objectsReported, 0); }); //error = unexpected increase of total workload
- const int objectsExpected = 1;
- const Int64 bytesExpected = 0;
-
- getDelHandling<sideTrg>().removeFileUpdating(fileObj.getFullName<sideTrg>(), fileObj.getObjRelativeName(), bytesExpected, [&] //throw FileError
- {
- procCallback_.updateProcessedData(1, 0); //noexcept
- ++objectsReported;
- });
-
- guardStatistics.dismiss(); //update statistics to consider the real amount of data
- if (objectsReported != objectsExpected)
- procCallback_.updateTotalData(objectsReported - objectsExpected, 0); //noexcept!
- }
- fileObj.removeObject<sideTrg>(); //update FilePair
- break;
-
- case SO_MOVE_LEFT_TARGET:
- case SO_MOVE_RIGHT_TARGET:
- if (FilePair* moveSource = dynamic_cast<FilePair*>(FileSystemObject::retrieve(fileObj.getMoveRef())))
- {
- FilePair* moveTarget = &fileObj;
-
- assert((moveSource->getSyncOperation() == SO_MOVE_LEFT_SOURCE && moveTarget->getSyncOperation() == SO_MOVE_LEFT_TARGET && sideTrg == LEFT_SIDE) ||
- (moveSource->getSyncOperation() == SO_MOVE_RIGHT_SOURCE && moveTarget->getSyncOperation() == SO_MOVE_RIGHT_TARGET && sideTrg == RIGHT_SIDE));
-
- const Zstring& oldName = moveSource->getFullName<sideTrg>();
- const Zstring& newName = moveSource->getBaseDirPf<sideTrg>() + moveTarget->getRelativeName<sideSrc>();
-
- reportInfo(txtMovingFile, oldName, newName);
- warn_static("was wenn diff volume: symlink aliasing!") //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
- renameFile(oldName, newName); //throw FileError
-
- //update FilePair
- assert(moveSource->getFileSize<sideTrg>() == moveTarget->getFileSize<sideSrc>());
- moveTarget->setSyncedTo<sideTrg>(moveTarget->getShortName<sideSrc>(), moveTarget->getFileSize<sideSrc>(),
- moveSource->getLastWriteTime<sideTrg>(), //awkward naming! moveSource is renamed on "sideTrg" side!
- moveTarget->getLastWriteTime<sideSrc>(),
- moveSource->getFileId<sideTrg>(),
- moveTarget->getFileId<sideSrc>(),
- moveSource->isFollowedSymlink<sideTrg>(),
- moveTarget->isFollowedSymlink<sideSrc>());
- moveSource->removeObject<sideTrg>(); //remove only *after* evaluating "moveSource, sideTrg"!
-
- procCallback_.updateProcessedData(1, 0);
- }
- else (assert(false));
- break;
-
- case SO_OVERWRITE_LEFT:
- case SO_OVERWRITE_RIGHT:
- {
- const Zstring targetFile = fileObj.isFollowedSymlink<sideTrg>() ? //follow link when updating file rather than delete it and replace with regular file!!!
- zen::getResolvedFilePath(fileObj.getFullName<sideTrg>()) : //throw FileError
- fileObj.getBaseDirPf<sideTrg>() + fileObj.getRelativeName<sideSrc>(); //respect differences in case of source object
-
- reportInfo(txtOverwritingFile, targetFile);
-
- if (fileObj.isFollowedSymlink<sideTrg>()) //since we follow the link, we need to handle case sensitivity of the link manually!
- if (fileObj.getShortName<sideTrg>() != fileObj.getShortName<sideSrc>()) //adapt difference in case (windows only)
- renameFile(fileObj.getFullName<sideTrg>(),
- beforeLast(fileObj.getFullName<sideTrg>(), FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR + fileObj.getShortName<sideSrc>()); //throw FileError
-
- const FileAttrib newAttr = copyFileUpdating(fileObj.getFullName<sideSrc>(),
- targetFile,
- to<Int64>(fileObj.getFileSize<sideSrc>()),
- [&] //delete target at appropriate time
- {
- reportStatus(this->getDelHandling<sideTrg>().getTxtRemovingFile(), targetFile);
-
- this->getDelHandling<sideTrg>().removeFileUpdating(targetFile, fileObj.getObjRelativeName(), 0, [] {}); //throw FileError;
- //no (logical) item count update desired - but total byte count may change, e.g. move(copy) deleted file to versioning dir
-
- //fileObj.removeObject<sideTrg>(); -> doesn't make sense for isFollowedSymlink(); "fileObj, sideTrg" evaluated below!
-
- //if fail-safe file copy is active, then the next operation will be a simple "rename"
- //=> don't risk reportStatus() throwing GuiAbortProcess() leaving the target deleted rather than updated!
- if (!transactionalFileCopy_)
- reportStatus(txtOverwritingFile, targetFile); //restore status text copy file
- }); //throw FileError
-
- //update FilePair
- fileObj.setSyncedTo<sideTrg>(fileObj.getShortName<sideSrc>(), newAttr.fileSize,
- newAttr.modificationTime, //target time set from source
- newAttr.modificationTime,
- newAttr.targetFileId,
- newAttr.sourceFileId,
- fileObj.isFollowedSymlink<sideTrg>(),
- fileObj.isFollowedSymlink<sideSrc>());
-
- procCallback_.updateProcessedData(1, 0); //we model "delete + copy" as ONE logical operation
- }
- break;
-
- case SO_COPY_METADATA_TO_LEFT:
- case SO_COPY_METADATA_TO_RIGHT:
- {
- //harmonize with file_hierarchy.cpp::getSyncOpDescription!!
-
- reportInfo(txtWritingAttributes, fileObj.getFullName<sideTrg>());
-
- if (fileObj.getShortName<sideTrg>() != fileObj.getShortName<sideSrc>()) //adapt difference in case (windows only)
- renameFile(fileObj.getFullName<sideTrg>(),
- beforeLast(fileObj.getFullName<sideTrg>(), FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR + fileObj.getShortName<sideSrc>()); //throw FileError
-
- if (!sameFileTime(fileObj.getLastWriteTime<sideTrg>(), fileObj.getLastWriteTime<sideSrc>(), 2)) //respect 2 second FAT/FAT32 precision
- setFileTime(fileObj.getFullName<sideTrg>(), fileObj.getLastWriteTime<sideSrc>(), SYMLINK_FOLLOW); //throw FileError
- //do NOT read *current* source file time, but use buffered value which corresponds to time of comparison!
-
- //-> both sides *should* be completely equal now...
- assert(fileObj.getFileSize<sideTrg>() == fileObj.getFileSize<sideSrc>());
- fileObj.setSyncedTo<sideTrg>(fileObj.getShortName<sideSrc>(), fileObj.getFileSize<sideSrc>(),
- fileObj.getLastWriteTime<sideSrc>(), //target time set from source
- fileObj.getLastWriteTime<sideSrc>(),
- fileObj.getFileId <sideTrg>(),
- fileObj.getFileId <sideSrc>(),
- fileObj.isFollowedSymlink<sideTrg>(),
- fileObj.isFollowedSymlink<sideSrc>());
-
- procCallback_.updateProcessedData(1, 0);
- }
- break;
-
- case SO_MOVE_LEFT_SOURCE:
- case SO_MOVE_RIGHT_SOURCE:
- case SO_DO_NOTHING:
- case SO_EQUAL:
- case SO_UNRESOLVED_CONFLICT:
- assert(false); //should have been filtered out by SynchronizeFolderPair::getPass()
- return; //no update on processed data!
- }
-
- procCallback_.requestUiRefresh(); //may throw
-}
-
-
-inline
-void SynchronizeFolderPair::synchronizeLink(SymlinkPair& linkObj)
-{
- const SyncOperation syncOp = linkObj.getSyncOperation();
-
- if (Opt<SelectedSide> sideTrg = getTargetDirection(syncOp))
- {
- if (*sideTrg == LEFT_SIDE)
- synchronizeLinkInt<LEFT_SIDE>(linkObj, syncOp);
- else
- synchronizeLinkInt<RIGHT_SIDE>(linkObj, syncOp);
- }
-}
-
-
-template <SelectedSide sideTrg>
-void SynchronizeFolderPair::synchronizeLinkInt(SymlinkPair& linkObj, SyncOperation syncOp)
-{
- static const SelectedSide sideSrc = OtherSide<sideTrg>::result;
-
- switch (syncOp)
- {
- case SO_CREATE_NEW_LEFT:
- case SO_CREATE_NEW_RIGHT:
- {
- if (const DirPair* parentDir = dynamic_cast<DirPair*>(&linkObj.parent()))
- if (parentDir->isEmpty<sideTrg>()) //BaseDirPair OTOH is always non-empty and existing in this context => else: fatal error in zen::synchronize()
- return; //if parent directory creation failed, there's no reason to show more errors!
-
- const Zstring& target = linkObj.getBaseDirPf<sideTrg>() + linkObj.getRelativeName<sideSrc>();
-
- reportInfo(txtCreatingLink, target);
-
- try
- {
- zen::copySymlink(linkObj.getFullName<sideSrc>(), target, copyFilePermissions_); //throw FileError
- //update SymlinkPair
- linkObj.setSyncedTo<sideTrg>(linkObj.getShortName<sideSrc>(),
- linkObj.getLastWriteTime<sideSrc>(), //target time set from source
- linkObj.getLastWriteTime<sideSrc>());
-
- procCallback_.updateProcessedData(1, 0);
- }
- catch (FileError&)
- {
- if (somethingExists(linkObj.getFullName<sideSrc>())) //do not check on type (symlink, file, folder) -> if there is a type change, FFS should not be quiet about it!
- throw;
- //source deleted meanwhile...nothing was done (logical point of view!)
- procCallback_.updateTotalData(-1, 0);
- linkObj.removeObject<sideSrc>();
- }
- }
- break;
-
- case SO_DELETE_LEFT:
- case SO_DELETE_RIGHT:
- reportInfo(getDelHandling<sideTrg>().getTxtRemovingSymLink(), linkObj.getFullName<sideTrg>());
- {
- int objectsReported = 0;
- auto guardStatistics = makeGuard([&] { procCallback_.updateTotalData(objectsReported, 0); }); //error = unexpected increase of total workload
- const int objectsExpected = 1;
- const Int64 bytesExpected = 0;
-
- getDelHandling<sideTrg>().removeLinkUpdating(linkObj.getFullName<sideTrg>(), linkObj.getObjRelativeName(), bytesExpected, [&] //throw FileError
- {
- procCallback_.updateProcessedData(1, 0); //noexcept
- ++objectsReported;
- });
-
- guardStatistics.dismiss(); //update statistics to consider the real amount of data
- if (objectsReported != objectsExpected)
- procCallback_.updateTotalData(objectsReported - objectsExpected, 0); //noexcept!
- }
- linkObj.removeObject<sideTrg>(); //update SymlinkPair
- break;
-
- case SO_OVERWRITE_LEFT:
- case SO_OVERWRITE_RIGHT:
- reportInfo(txtOverwritingLink, linkObj.getFullName<sideTrg>());
-
- //reportStatus(getDelHandling<sideTrg>().getTxtRemovingSymLink(), linkObj.getFullName<sideTrg>());
- getDelHandling<sideTrg>().removeLinkUpdating(linkObj.getFullName<sideTrg>(), linkObj.getObjRelativeName(), 0, [] {}); //throw FileError
-
- //linkObj.removeObject<sideTrg>(); -> "linkObj, sideTrg" evaluated below!
-
- //=> don't risk reportStatus() throwing GuiAbortProcess() leaving the target deleted rather than updated:
-
- //reportStatus(txtOverwritingLink, linkObj.getFullName<sideTrg>()); //restore status text
- zen::copySymlink(linkObj.getFullName<sideSrc>(),
- linkObj.getBaseDirPf<sideTrg>() + linkObj.getRelativeName<sideSrc>(), //respect differences in case of source object
- copyFilePermissions_); //throw FileError
-
- //update SymlinkPair
- linkObj.setSyncedTo<sideTrg>(linkObj.getShortName<sideSrc>(),
- linkObj.getLastWriteTime<sideSrc>(), //target time set from source
- linkObj.getLastWriteTime<sideSrc>());
-
- procCallback_.updateProcessedData(1, 0); //we model "delete + copy" as ONE logical operation
- break;
-
- case SO_COPY_METADATA_TO_LEFT:
- case SO_COPY_METADATA_TO_RIGHT:
- reportInfo(txtWritingAttributes, linkObj.getFullName<sideTrg>());
-
- if (linkObj.getShortName<sideTrg>() != linkObj.getShortName<sideSrc>()) //adapt difference in case (windows only)
- renameFile(linkObj.getFullName<sideTrg>(),
- beforeLast(linkObj.getFullName<sideTrg>(), FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR + linkObj.getShortName<sideSrc>()); //throw FileError
-
- if (!sameFileTime(linkObj.getLastWriteTime<sideTrg>(), linkObj.getLastWriteTime<sideSrc>(), 2)) //respect 2 second FAT/FAT32 precision
- setFileTime(linkObj.getFullName<sideTrg>(), linkObj.getLastWriteTime<sideSrc>(), SYMLINK_DIRECT); //throw FileError
-
- //-> both sides *should* be completely equal now...
- linkObj.setSyncedTo<sideTrg>(linkObj.getShortName<sideSrc>(),
- linkObj.getLastWriteTime<sideSrc>(), //target time set from source
- linkObj.getLastWriteTime<sideSrc>());
-
- procCallback_.updateProcessedData(1, 0);
- break;
-
- case SO_MOVE_LEFT_SOURCE:
- case SO_MOVE_RIGHT_SOURCE:
- case SO_MOVE_LEFT_TARGET:
- case SO_MOVE_RIGHT_TARGET:
- case SO_DO_NOTHING:
- case SO_EQUAL:
- case SO_UNRESOLVED_CONFLICT:
- assert(false); //should have been filtered out by SynchronizeFolderPair::getPass()
- return; //no update on processed data!
- }
-
- procCallback_.requestUiRefresh(); //may throw
-}
-
-
-inline
-void SynchronizeFolderPair::synchronizeFolder(DirPair& dirObj)
-{
- const SyncOperation syncOp = dirObj.getSyncOperation();
-
- if (Opt<SelectedSide> sideTrg = getTargetDirection(syncOp))
- {
- if (*sideTrg == LEFT_SIDE)
- synchronizeFolderInt<LEFT_SIDE>(dirObj, syncOp);
- else
- synchronizeFolderInt<RIGHT_SIDE>(dirObj, syncOp);
- }
-}
-
-
-template <SelectedSide sideTrg>
-void SynchronizeFolderPair::synchronizeFolderInt(DirPair& dirObj, SyncOperation syncOp)
-{
- static const SelectedSide sideSrc = OtherSide<sideTrg>::result;
-
- switch (syncOp)
- {
- case SO_CREATE_NEW_LEFT:
- case SO_CREATE_NEW_RIGHT:
- if (const DirPair* parentDir = dynamic_cast<DirPair*>(&dirObj.parent()))
- if (parentDir->isEmpty<sideTrg>()) //BaseDirPair OTOH is always non-empty and existing in this context => else: fatal error in zen::synchronize()
- return; //if parent directory creation failed, there's no reason to show more errors!
-
- if (somethingExists(dirObj.getFullName<sideSrc>())) //do not check on type (symlink, file, folder) -> if there is a type change, FFS should error out!
- {
- const Zstring& target = dirObj.getBaseDirPf<sideTrg>() + dirObj.getRelativeName<sideSrc>();
-
- reportInfo(txtCreatingFolder, target);
- try
- {
- makeDirectoryPlain(target, dirObj.getFullName<sideSrc>(), copyFilePermissions_); //throw FileError, ErrorTargetExisting, (ErrorTargetPathMissing)
- }
- catch (const ErrorTargetExisting&) { if (!dirExists(target)) throw; } //detect clash with file (dir-symlink OTOH is okay)
-
- //update DirPair
- dirObj.setSyncedTo(dirObj.getShortName<sideSrc>());
-
- procCallback_.updateProcessedData(1, 0);
- }
- else //source deleted meanwhile...nothing was done (logical point of view!) -> uh....what about a temporary network drop???
- {
- const SyncStatistics subStats(dirObj);
- procCallback_.updateTotalData(-getCUD(subStats) - 1, -subStats.getDataToProcess());
-
- //remove only *after* evaluating dirObj!!
- dirObj.refSubFiles().clear(); //
- dirObj.refSubLinks().clear(); //update DirPair
- dirObj.refSubDirs ().clear(); //
- dirObj.removeObject<sideSrc>(); //
- }
- break;
-
- case SO_DELETE_LEFT:
- case SO_DELETE_RIGHT:
- reportInfo(getDelHandling<sideTrg>().getTxtRemovingDir(), dirObj.getFullName<sideTrg>());
- {
- int objectsReported = 0;
- auto guardStatistics = makeGuard([&] { procCallback_.updateTotalData(objectsReported, 0); }); //error = unexpected increase of total workload
- const SyncStatistics subStats(dirObj); //counts sub-objects only!
- const int objectsExpected = 1 + getCUD(subStats);
- const Int64 bytesExpected = subStats.getDataToProcess();
- assert(bytesExpected == 0);
-
- getDelHandling<sideTrg>().removeDirUpdating(dirObj.getFullName<sideTrg>(), dirObj.getObjRelativeName(), bytesExpected, [&] //throw FileError
- {
- procCallback_.updateProcessedData(1, 0); //noexcept
- ++objectsReported;
- });
-
- guardStatistics.dismiss(); //update statistics to consider the real amount of data
- if (objectsReported != objectsExpected)
- procCallback_.updateTotalData(objectsReported - objectsExpected, 0); //noexcept!
- }
- dirObj.refSubFiles().clear(); //
- dirObj.refSubLinks().clear(); //update DirPair
- dirObj.refSubDirs ().clear(); //
- dirObj.removeObject<sideTrg>(); //
- break;
-
- case SO_COPY_METADATA_TO_LEFT:
- case SO_COPY_METADATA_TO_RIGHT:
- reportInfo(txtWritingAttributes, dirObj.getFullName<sideTrg>());
-
- if (dirObj.getShortName<sideTrg>() != dirObj.getShortName<sideSrc>()) //adapt difference in case (windows only)
- renameFile(dirObj.getFullName<sideTrg>(),
- beforeLast(dirObj.getFullName<sideTrg>(), FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR + dirObj.getShortName<sideSrc>()); //throw FileError
- //copyFileTimes -> useless: modification time changes with each child-object creation/deletion
-
- //-> both sides *should* be completely equal now...
- dirObj.setSyncedTo(dirObj.getShortName<sideSrc>());
-
- procCallback_.updateProcessedData(1, 0);
- break;
-
- case SO_OVERWRITE_LEFT:
- case SO_OVERWRITE_RIGHT:
- case SO_MOVE_LEFT_SOURCE:
- case SO_MOVE_RIGHT_SOURCE:
- case SO_MOVE_LEFT_TARGET:
- case SO_MOVE_RIGHT_TARGET:
- case SO_DO_NOTHING:
- case SO_EQUAL:
- case SO_UNRESOLVED_CONFLICT:
- assert(false); //should have been filtered out by SynchronizeFolderPair::getPass()
- return; //no update on processed data!
- }
-
- procCallback_.requestUiRefresh(); //may throw
-}
-
-
-namespace
-{
-#ifdef ZEN_WIN
-//recycleBinStatus() blocks seriously if recycle bin is really full and drive is slow
-StatusRecycler recycleBinStatusUpdating(const Zstring& dirname, ProcessCallback& procCallback)
-{
- procCallback.reportStatus(replaceCpy(_("Checking recycle bin availability for folder %x..."), L"%x", fmtFileName(dirname), false));
-
- auto ft = async([=] { return recycleBinStatus(dirname); });
-
- while (!ft.timed_wait(boost::posix_time::milliseconds(UI_UPDATE_INTERVAL / 2)))
- procCallback.requestUiRefresh(); //may throw!
- return ft.get();
-}
-#endif
-
-
-/*
-struct LessDependentDirectory : public std::binary_function<Zstring, Zstring, bool>
-{
--> a *very* bad idea: this is NOT a strict weak ordering! No transitivity of equivalence!
-
- bool operator()(const Zstring& lhs, const Zstring& rhs) const
- {
- return LessFilename()(Zstring(lhs.c_str(), std::min(lhs.length(), rhs.length())),
- Zstring(rhs.c_str(), std::min(lhs.length(), rhs.length())));
- }
-};
-*/
-
-template <SelectedSide side> //create base directories first (if not yet existing) -> no symlink or attribute copying!
-bool createBaseDirectory(BaseDirPair& baseDirObj, ProcessCallback& callback) //nothrow; return false if fatal error occurred
-{
- const Zstring dirname = beforeLast(baseDirObj.getBaseDirPf<side>(), FILE_NAME_SEPARATOR); //what about C:\ ???
- if (dirname.empty())
- return true;
-
- if (baseDirObj.isExisting<side>()) //atomicity: do NOT check directory existence again!
- {
- //just convenience: exit sync right here instead of showing tons of error messages during file copy
- zen::Opt<std::wstring> errMsg = tryReportingError([&]
- {
- if (!dirExistsUpdating(dirname, false, callback))
- throw FileError(replaceCpy(_("Cannot find %x."), L"%x", fmtFileName(dirname))); //should be logged as a "fatal error" if ignored by the user...
- }, callback); //may throw in error-callback!
-
- return !errMsg;
- }
- else //create target directory: user presumably ignored error "dir existing" in order to have it created automatically
- {
- bool temporaryNetworkDrop = false;
- zen::Opt<std::wstring> errMsg = tryReportingError([&]
- {
- try
- {
- //a nice race-free check and set operation:
- makeDirectory(dirname, /*bool failIfExists*/ true); //throw FileError, ErrorTargetExisting
- baseDirObj.setExisting<side>(true); //update our model!
- }
- catch (const ErrorTargetExisting&)
- {
- //TEMPORARY network drop: base directory not found during comparison, but reappears during synchronization
- //=> sync-directions are based on false assumptions! Abort.
- callback.reportFatalError(replaceCpy(_("Target folder %x already existing."), L"%x", fmtFileName(dirname)));
- temporaryNetworkDrop = true;
-
- //Is it possible we're catching a "false-positive" here, could FFS have created the directory indirectly after comparison?
- // 1. deletion handling: recycler -> no, temp directory created only at first deletion
- // 2. deletion handling: versioning -> "
- // 3. log file creates containing folder -> no, log only created in batch mode, and only *before* comparison
- }
- }, callback); //may throw in error-callback!
- return !errMsg && !temporaryNetworkDrop;
- }
-}
-}
-
-
-void zen::synchronize(const TimeComp& timeStamp,
- xmlAccess::OptionalDialogs& warnings,
- bool verifyCopiedFiles,
- bool copyLockedFiles,
- bool copyFilePermissions,
- bool transactionalFileCopy,
- bool runWithBackgroundPriority,
- const std::vector<FolderPairSyncCfg>& syncConfig,
- FolderComparison& folderCmp,
- ProcessCallback& callback)
-{
- //specify process and resource handling priorities
- std::unique_ptr<ScheduleForBackgroundProcessing> backgroundPrio;
- if (runWithBackgroundPriority)
- try
- {
- backgroundPrio = make_unique<ScheduleForBackgroundProcessing>(); //throw FileError
- }
- catch (const FileError& e)
- {
- //not an error in this context
- callback.reportInfo(e.toString()); //may throw!
- }
-
- //prevent operating system going into sleep state
- std::unique_ptr<PreventStandby> noStandby;
- try
- {
- noStandby = make_unique<PreventStandby>(); //throw FileError
- }
- catch (const FileError& e)
- {
- //not an error in this context
- callback.reportInfo(e.toString()); //may throw!
- }
-
- //PERF_START;
-
- if (syncConfig.size() != folderCmp.size())
- throw std::logic_error("Programming Error: Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
-
- //inform about the total amount of data that will be processed from now on
- const SyncStatistics statisticsTotal(folderCmp);
-
- //keep at beginning so that all gui elements are initialized properly
- callback.initNewPhase(getCUD(statisticsTotal),
- statisticsTotal.getDataToProcess(),
- ProcessCallback::PHASE_SYNCHRONIZING);
-
-
- std::deque<bool> skipFolderPair(folderCmp.size()); //folder pairs may be skipped after fatal errors were found
-
- //-------------------execute basic checks all at once before starting sync--------------------------------------
-
- auto dependentDir = [](const Zstring& lhs, const Zstring& rhs) //note: this is NOT an equivalence relation!
- {
- return EqualFilename()(Zstring(lhs.c_str(), std::min(lhs.length(), rhs.length())),
- Zstring(rhs.c_str(), std::min(lhs.length(), rhs.length())));
- };
-
- //aggregate information
- std::map<Zstring, std::pair<size_t, size_t>> dirReadWriteCount; //count read/write accesses
- auto incReadCount = [&](const Zstring& baseDir)
- {
- dirReadWriteCount[baseDir]; //create entry
- for (auto it = dirReadWriteCount.begin(); it != dirReadWriteCount.end(); ++it)
- {
- auto& countRef = it->second;
- if (dependentDir(baseDir, it->first))
- ++countRef.first;
- }
- };
- auto incWriteCount = [&](const Zstring& baseDir)
- {
- dirReadWriteCount[baseDir]; //create entry
- for (auto it = dirReadWriteCount.begin(); it != dirReadWriteCount.end(); ++it)
- {
- auto& countRef = it->second;
- if (dependentDir(baseDir, it->first))
- ++countRef.second;
- }
- };
-
- typedef std::vector<std::pair<Zstring, Zstring>> DirPairList;
- DirPairList significantDiff;
-
- typedef std::vector<std::pair<Zstring, std::pair<Int64, Int64>>> DirSpaceRequAvailList; //dirname / space required / space available
- DirSpaceRequAvailList diskSpaceMissing;
-
-#ifdef ZEN_WIN
- //status of base directories which are set to DELETE_TO_RECYCLER (and contain actual items to be deleted)
- std::map<Zstring, bool, LessFilename> baseDirHasRecycler; //might be expensive to determine => buffer + check recycle bin existence only once per base directory!
-#endif
-
- //start checking folder pairs
- for (auto j = begin(folderCmp); j != end(folderCmp); ++j)
- {
- const size_t folderIndex = j - begin(folderCmp);
-
- //exclude some pathological case (leftdir, rightdir are empty)
- if (EqualFilename()(j->getBaseDirPf<LEFT_SIDE>(), j->getBaseDirPf<RIGHT_SIDE>()))
- continue;
-
- const FolderPairSyncCfg& folderPairCfg = syncConfig[folderIndex];
-
- const SyncStatistics folderPairStat(*j);
-
- //aggregate basic information
- const bool writeLeft = folderPairStat.getCreate<LEFT_SIDE>() +
- folderPairStat.getUpdate<LEFT_SIDE>() +
- folderPairStat.getDelete<LEFT_SIDE>() > 0;
-
- const bool writeRight = folderPairStat.getCreate<RIGHT_SIDE>() +
- folderPairStat.getUpdate<RIGHT_SIDE>() +
- folderPairStat.getDelete<RIGHT_SIDE>() > 0;
-
- //skip folder pair if there is nothing to do (except for two-way mode and move-detection, where DB files need to be written)
- if (!writeLeft && !writeRight &&
- !folderPairCfg.saveSyncDB_)
- {
- skipFolderPair[folderIndex] = true; //skip creating (not yet existing) base directories in particular if there's no need
- continue;
- }
-
- //check empty input fields: this only makes sense if empty field is source (and no DB files need to be created)
- if ((j->getBaseDirPf<LEFT_SIDE >().empty() && (writeLeft || folderPairCfg.saveSyncDB_)) ||
- (j->getBaseDirPf<RIGHT_SIDE>().empty() && (writeRight || folderPairCfg.saveSyncDB_)))
- {
- callback.reportFatalError(_("Target folder input field must not be empty."));
- skipFolderPair[folderIndex] = true;
- continue;
- }
-
- //aggregate information of folders used by multiple pairs in read/write access
- if (!dependentDir(j->getBaseDirPf<LEFT_SIDE>(), j->getBaseDirPf<RIGHT_SIDE>())) //true in general
- {
- if (writeLeft && writeRight)
- {
- incWriteCount(j->getBaseDirPf<LEFT_SIDE >());
- incWriteCount(j->getBaseDirPf<RIGHT_SIDE>());
- }
- else if (writeLeft)
- {
- incWriteCount(j->getBaseDirPf<LEFT_SIDE>());
- incReadCount (j->getBaseDirPf<RIGHT_SIDE>());
- }
- else if (writeRight)
- {
- incReadCount (j->getBaseDirPf<LEFT_SIDE>());
- incWriteCount(j->getBaseDirPf<RIGHT_SIDE>());
- }
- }
- else //if folder pair contains two dependent folders, a warning was already issued after comparison; in this context treat as one write access at most
- {
- if (writeLeft || writeRight)
- incWriteCount(j->getBaseDirPf<LEFT_SIDE>());
- }
-
-
- if (folderPairStat.getUpdate() + folderPairStat.getDelete() > 0 &&
- folderPairCfg.handleDeletion == zen::DELETE_TO_VERSIONING)
- {
- //check if user-defined directory for deletion was specified
- if (folderPairCfg.versioningFolder.empty()) //already trimmed by getFormattedDirectoryName()
- {
- //should never arrive here: already checked in SyncCfgDialog
- callback.reportFatalError(_("Please enter a target folder for versioning."));
- skipFolderPair[folderIndex] = true;
- continue;
- }
- }
-
- //the following scenario is covered by base directory creation below in case source directory exists (accessible or not), but latter doesn't cover source created after comparison, but before sync!!!
- auto checkSourceMissing = [&](const Zstring& baseDirPf, bool wasExisting) -> bool //avoid race-condition: we need to evaluate existence status from time of comparison!
- {
- if (!baseDirPf.empty())
- {
- //PERMANENT network drop: avoid data loss when source directory is not found AND user chose to ignore errors (else we wouldn't arrive here)
- if (folderPairStat.getCreate() +
- folderPairStat.getUpdate() == 0 &&
- folderPairStat.getDelete() > 0) //deletions only... (respect filtered items!)
- //folderPairStat.getConflict() == 0 && -> there COULD be conflicts for <automatic> if directory existence check fails, but loading sync.ffs_db succeeds
- //https://sourceforge.net/tracker/?func=detail&atid=1093080&aid=3531351&group_id=234430 -> fixed, but still better not consider conflicts
- {
- if (!wasExisting) //avoid race-condition: we need to evaluate existence status from time of comparison!
- {
- callback.reportFatalError(replaceCpy(_("Source folder %x not found."), L"%x", fmtFileName(baseDirPf)));
- skipFolderPair[folderIndex] = true;
- return false;
- }
- }
- }
- return true;
- };
- if (!checkSourceMissing(j->getBaseDirPf<LEFT_SIDE >(), j->isExisting<LEFT_SIDE >()) ||
- !checkSourceMissing(j->getBaseDirPf<RIGHT_SIDE>(), j->isExisting<RIGHT_SIDE>()))
- continue;
-
- //check if more than 50% of total number of files/dirs are to be created/overwritten/deleted
- if (significantDifferenceDetected(folderPairStat))
- significantDiff.push_back(std::make_pair(j->getBaseDirPf<LEFT_SIDE>(), j->getBaseDirPf<RIGHT_SIDE>()));
-
- //check for sufficient free diskspace
- auto checkSpace = [&](const Zstring& baseDirPf, const Int64& minSpaceNeeded)
- {
- try
- {
- const Int64 freeSpace = to<Int64>(getFreeDiskSpace(baseDirPf)); //throw FileError
-
- if (0 < freeSpace && //zero disk space probably means "request not supported" (e.g. see WebDav)
- freeSpace < minSpaceNeeded)
- diskSpaceMissing.push_back(std::make_pair(baseDirPf, std::make_pair(minSpaceNeeded, freeSpace)));
- }
- catch (FileError&) {}
- };
- const std::pair<Int64, Int64> spaceNeeded = MinimumDiskSpaceNeeded::calculate(*j);
- checkSpace(j->getBaseDirPf<LEFT_SIDE >(), spaceNeeded.first);
- checkSpace(j->getBaseDirPf<RIGHT_SIDE>(), spaceNeeded.second);
-
-#ifdef ZEN_WIN
- //windows: check if recycle bin really exists; if not, Windows will silently delete, which is wrong
- auto checkRecycler = [&](const Zstring& baseDirPf)
- {
- if (!baseDirPf.empty()) //should be
- if (baseDirHasRecycler.find(baseDirPf) == baseDirHasRecycler.end()) //perf: avoid duplicate checks!
- baseDirHasRecycler[baseDirPf] = recycleBinStatusUpdating(baseDirPf, callback) == STATUS_REC_EXISTS;
- };
-
- if (folderPairCfg.handleDeletion == DELETE_TO_RECYCLER)
- {
- if (folderPairStat.getUpdate<LEFT_SIDE>() +
- folderPairStat.getDelete<LEFT_SIDE>() > 0)
- checkRecycler(j->getBaseDirPf<LEFT_SIDE>());
-
- if (folderPairStat.getUpdate<RIGHT_SIDE>() +
- folderPairStat.getDelete<RIGHT_SIDE>() > 0)
- checkRecycler(j->getBaseDirPf<RIGHT_SIDE>());
- }
-#endif
- }
-
- //check if unresolved conflicts exist
- if (statisticsTotal.getConflict() > 0)
- {
- //show the first few conflicts in warning message also:
- std::wstring msg = _("The following items have unresolved conflicts and will not be synchronized:");
-
- const auto& conflictMsgs = statisticsTotal.getConflictMessages(); //get *all* sync conflicts
- for (auto it = conflictMsgs.begin(); it != conflictMsgs.end(); ++it)
- msg += L"\n\n" + fmtFileName(it->first) + L": " + it->second;
-
- callback.reportWarning(msg, warnings.warningUnresolvedConflicts);
- }
-
-
- //check if user accidentally selected wrong directories for sync
- if (!significantDiff.empty())
- {
- std::wstring msg = _("The following folders are significantly different. Make sure you are matching the correct folders for synchronization.");
-
- for (auto it = significantDiff.begin(); it != significantDiff.end(); ++it)
- msg += std::wstring(L"\n\n") +
- it->first + L" <-> " + L"\n" +
- it->second;
-
- callback.reportWarning(msg, warnings.warningSignificantDifference);
- }
-
-
- //check for sufficient free diskspace
- if (!diskSpaceMissing.empty())
- {
- std::wstring msg = _("Not enough free disk space available in:");
-
- for (auto it = diskSpaceMissing.begin(); it != diskSpaceMissing.end(); ++it)
- msg += std::wstring(L"\n\n") +
- it->first + L"\n" +
- _("Required:") + L" " + filesizeToShortString(it->second.first) + L"\n" +
- _("Available:") + L" " + filesizeToShortString(it->second.second);
-
- callback.reportWarning(msg, warnings.warningNotEnoughDiskSpace);
- }
-
-#ifdef ZEN_WIN
- //windows: check if recycle bin really exists; if not, Windows will silently delete, which is wrong
- {
- std::wstring dirListMissingRecycler;
- for (auto it = baseDirHasRecycler.begin(); it != baseDirHasRecycler.end(); ++it)
- if (!it->second)
- dirListMissingRecycler += std::wstring(L"\n") + it->first;
-
- if (!dirListMissingRecycler.empty())
- callback.reportWarning(_("The recycle bin is not available for the following folders. Files will be deleted permanently instead:") + L"\n" + dirListMissingRecycler, warnings.warningRecyclerMissing);
- }
-#endif
-
- //check if folders are used by multiple pairs in read/write access
- std::vector<Zstring> conflictDirs;
- for (auto it = dirReadWriteCount.cbegin(); it != dirReadWriteCount.cend(); ++it)
- {
- const std::pair<size_t, size_t>& countRef = it->second; //# read/write accesses
-
- if (countRef.first + countRef.second >= 2 && countRef.second >= 1) //race condition := multiple accesses of which at least one is a write
- conflictDirs.push_back(it->first);
- }
-
- if (!conflictDirs.empty())
- {
- std::wstring msg = _("A folder will be modified which is part of multiple folder pairs. Please review synchronization settings.") + L"\n";
- std::for_each(conflictDirs.begin(), conflictDirs.end(),
- [&](const Zstring& dirname) { msg += std::wstring(L"\n") + dirname; });
-
- callback.reportWarning(msg, warnings.warningFolderPairRaceCondition);
- }
-
- //-------------------end of basic checks------------------------------------------
-
-#ifdef ZEN_WIN
- //shadow copy buffer: per sync-instance, not folder pair
- std::unique_ptr<shadow::ShadowCopy> shadowCopyHandler;
- if (copyLockedFiles)
- shadowCopyHandler = make_unique<shadow::ShadowCopy>();
-#endif
-
- try
- {
- //loop through all directory pairs
- for (auto j = begin(folderCmp); j != end(folderCmp); ++j)
- {
- //exclude pathological cases (e.g. leftdir, rightdir are empty)
- if (EqualFilename()(j->getBaseDirPf<LEFT_SIDE>(), j->getBaseDirPf<RIGHT_SIDE>()))
- continue;
-
- //------------------------------------------------------------------------------------------
- callback.reportInfo(_("Synchronizing folder pair:") + L"\n" +
- L" " + j->getBaseDirPf<LEFT_SIDE >() + L"\n" +
- L" " + j->getBaseDirPf<RIGHT_SIDE>());
- //------------------------------------------------------------------------------------------
-
- const size_t folderIndex = j - begin(folderCmp);
- const FolderPairSyncCfg& folderPairCfg = syncConfig[folderIndex];
-
- if (skipFolderPair[folderIndex]) //folder pairs may be skipped after fatal errors were found
- continue;
-
- //create base directories first (if not yet existing) -> no symlink or attribute copying!
- if (!createBaseDirectory<LEFT_SIDE >(*j, callback) ||
- !createBaseDirectory<RIGHT_SIDE>(*j, callback))
- continue; //skip this folder pair
-
- //------------------------------------------------------------------------------------------
- //execute synchronization recursively
-
- //update synchronization database (automatic sync only)
- ScopeGuard guardUpdateDb = makeGuard([&]
- {
- if (folderPairCfg.saveSyncDB_)
- try { zen::saveLastSynchronousState(*j); } //throw FileError
- catch (FileError&) {}
- });
-
- //guarantee removal of invalid entries (where element on both sides is empty)
- ZEN_ON_SCOPE_EXIT(BaseDirPair::removeEmpty(*j););
-
- bool copyPermissionsFp = false;
- tryReportingError([&]
- {
- copyPermissionsFp = copyFilePermissions && //copy permissions only if asked for and supported by *both* sides!
- !j->getBaseDirPf<LEFT_SIDE >().empty() && //scenario: directory selected on one side only
- !j->getBaseDirPf<RIGHT_SIDE>().empty() && //
- supportsPermissions(beforeLast(j->getBaseDirPf<LEFT_SIDE >(), FILE_NAME_SEPARATOR)) && //throw FileError
- supportsPermissions(beforeLast(j->getBaseDirPf<RIGHT_SIDE>(), FILE_NAME_SEPARATOR));
- }, callback); //show error dialog if necessary
-
-
- auto getEffectiveDeletionPolicy = [&](const Zstring& baseDirPf) -> DeletionPolicy
- {
-#ifdef ZEN_WIN
- if (folderPairCfg.handleDeletion == DELETE_TO_RECYCLER)
- {
- auto it = baseDirHasRecycler.find(baseDirPf);
- if (it != baseDirHasRecycler.end())
- if (!it->second)
- return DELETE_PERMANENTLY; //Windows' ::SHFileOperation() will do this anyway, but we have a better and faster deletion routine (e.g. on networks)
- }
-#endif
- return folderPairCfg.handleDeletion;
- };
-
-
- DeletionHandling delHandlerL(getEffectiveDeletionPolicy(j->getBaseDirPf<LEFT_SIDE>()),
- folderPairCfg.versioningFolder,
- folderPairCfg.versioningStyle_,
- timeStamp,
- j->getBaseDirPf<LEFT_SIDE>(),
- callback);
-
- DeletionHandling delHandlerR(getEffectiveDeletionPolicy(j->getBaseDirPf<RIGHT_SIDE>()),
- folderPairCfg.versioningFolder,
- folderPairCfg.versioningStyle_,
- timeStamp,
- j->getBaseDirPf<RIGHT_SIDE>(),
- callback);
-
-
- SynchronizeFolderPair syncFP(callback, verifyCopiedFiles, copyPermissionsFp, transactionalFileCopy,
-#ifdef ZEN_WIN
- shadowCopyHandler.get(),
-#endif
- delHandlerL, delHandlerR);
- syncFP.startSync(*j);
-
- //(try to gracefully) cleanup temporary Recycle bin folders and versioning -> will be done in ~DeletionHandling anyway...
- tryReportingError([&] { delHandlerL.tryCleanup(); }, callback); //show error dialog if necessary
- tryReportingError([&] { delHandlerR.tryCleanup(); }, callback); //
-
- //(try to gracefully) write database file
- if (folderPairCfg.saveSyncDB_)
- {
- callback.reportStatus(_("Generating database..."));
- callback.forceUiRefresh();
-
- tryReportingError([&] { zen::saveLastSynchronousState(*j); }, callback); //throw FileError
- guardUpdateDb.dismiss();
- }
- }
- }
- catch (const std::exception& e)
- {
- callback.reportFatalError(utfCvrtTo<std::wstring>(e.what()));
- }
-}
-
-//###########################################################################################
-
-template <class Function>
-class WhileCopying : public zen::CallbackCopyFile
-{
-public:
- WhileCopying(Int64& bytesReported,
- ProcessCallback& statusHandler,
- Function delTargetCmd) :
- bytesReported_(bytesReported),
- statusHandler_(statusHandler),
- delTargetCmd_(std::move(delTargetCmd)) {}
-
- virtual void deleteTargetFile(const Zstring& targetFile) { delTargetCmd_(); }
-
- virtual void updateCopyStatus(Int64 bytesDelta)
- {
- statusHandler_.updateProcessedData(0, bytesDelta); //throw()! -> ensure client and service provider are in sync!
- bytesReported_ += bytesDelta; //
-
- statusHandler_.requestUiRefresh(); //may throw
- }
-
-private:
- Int64& bytesReported_;
- ProcessCallback& statusHandler_;
- Function delTargetCmd_;
-};
-
-
-//throw FileError; reports data delta via updateProcessedData()
-template <class Function>
-FileAttrib SynchronizeFolderPair::copyFileUpdating(const Zstring& sourceFile,
- const Zstring& targetFile, const Int64& bytesExpected, Function delTargetCommand) const //returns current attributes of source file
-{
- Zstring source = sourceFile;
- FileAttrib newAttr;
- Int64 bytesReported;
-
- auto copyOperation = [&]
- {
- auto guardStatistics = makeGuard([&]
- {
- procCallback_.updateTotalData(0, bytesReported); //error = unexpected increase of total workload
- bytesReported = 0;
- });
-
- WhileCopying<Function> callback(bytesReported, procCallback_, delTargetCommand);
-
- copyFile(source, //type File implicitly means symlinks need to be dereferenced!
- targetFile,
- copyFilePermissions_,
- transactionalFileCopy_,
- &callback,
- &newAttr); //throw FileError, ErrorFileLocked
-
- //#################### Verification #############################
- if (verifyCopiedFiles_)
- {
- auto guardTarget = makeGuard([&] { removeFile(targetFile); }); //delete target if verification fails
- verifyFileCopy(source, targetFile); //throw FileError
- guardTarget.dismiss();
- }
- //#################### /Verification #############################
-
- //update statistics to consider the real amount of data, e.g. more than the "file size" for ADS streams,
- //less for sparse and compressed files, or file changed in the meantime!
- if (bytesReported != bytesExpected)
- procCallback_.updateTotalData(0, bytesReported - bytesExpected); //noexcept!
-
- guardStatistics.dismiss();
- };
-
-#ifdef ZEN_WIN
- try
- {
- copyOperation();
- }
- catch (ErrorFileLocked& e1)
- {
- //if file is locked (try to) use Windows Volume Shadow Copy Service
- if (!shadowCopyHandler_)
- throw;
- try
- {
- //contains prefix: E.g. "\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1\Program Files\FFS\sample.dat"
- source = shadowCopyHandler_->makeShadowCopy(source, //throw FileError
- [&](const Zstring& volumeName)
- {
- procCallback_.reportStatus(replaceCpy(_("Creating a Volume Shadow Copy for %x..."), L"%x", fmtFileName(volumeName)));
- procCallback_.forceUiRefresh();
- });
- }
- catch (const FileError& e2)
- {
- throw FileError(e1.toString(), e2.toString());
- }
-
- //now try again
- copyOperation();
- }
-#else
- copyOperation();
-#endif
-
- return newAttr;
-}
-
-
-//--------------------- data verification -------------------------
-struct VerifyCallback
-{
- virtual ~VerifyCallback() {}
- virtual void updateStatus() = 0;
-};
-
-void verifyFiles(const Zstring& source, const Zstring& target, VerifyCallback& callback) //throw FileError
-{
- static std::vector<char> memory1(1024 * 1024); //1024 kb seems to be a reasonable buffer size
- static std::vector<char> memory2(1024 * 1024);
-
-#ifdef ZEN_WIN
- wxFile file1(applyLongPathPrefix(source).c_str(), wxFile::read); //don't use buffered file input for verification!
-#elif defined ZEN_LINUX || defined ZEN_MAC
- wxFile file1(::open(source.c_str(), O_RDONLY)); //utilize UTF-8 filename
-#endif
- if (!file1.IsOpened())
- throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(source)) + L" (open)");
-
-#ifdef ZEN_WIN
- wxFile file2(applyLongPathPrefix(target).c_str(), wxFile::read); //don't use buffered file input for verification!
-#elif defined ZEN_LINUX || defined ZEN_MAC
- wxFile file2(::open(target.c_str(), O_RDONLY)); //utilize UTF-8 filename
-#endif
- if (!file2.IsOpened()) //NO cleanup necessary for (wxFile) file1
- throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(target)) + L" (open)");
-
- do
- {
- const size_t length1 = file1.Read(&memory1[0], memory1.size());
- if (file1.Error())
- throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(source)));
- callback.updateStatus();
-
- const size_t length2 = file2.Read(&memory2[0], memory2.size());
- if (file2.Error())
- throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(target)));
- callback.updateStatus();
-
- if (length1 != length2 || ::memcmp(&memory1[0], &memory2[0], length1) != 0)
- throw FileError(replaceCpy(replaceCpy(_("Data verification error: %x and %y have different content."), L"%x", L"\n" + fmtFileName(source)), L"%y", L"\n" + fmtFileName(target)));
- }
- while (!file1.Eof());
-
- if (!file2.Eof())
- throw FileError(replaceCpy(replaceCpy(_("Data verification error: %x and %y have different content."), L"%x", L"\n" + fmtFileName(source)), L"%y", L"\n" + fmtFileName(target)));
-}
-
-
-class VerifyStatusUpdater : public VerifyCallback
-{
-public:
- VerifyStatusUpdater(ProcessCallback& statusHandler) : statusHandler_(statusHandler) {}
-
- virtual void updateStatus() { statusHandler_.requestUiRefresh(); } //trigger display refresh
-
-private:
- ProcessCallback& statusHandler_;
-};
-
-
-void SynchronizeFolderPair::verifyFileCopy(const Zstring& source, const Zstring& target) const
-{
- procCallback_.reportInfo(replaceCpy(txtVerifying, L"%x", fmtFileName(target)));
-
- VerifyStatusUpdater callback(procCallback_);
- tryReportingError([&] { ::verifyFiles(source, target, callback); }, procCallback_);
-}
bgstack15