diff options
Diffstat (limited to 'synchronization.cpp')
-rw-r--r-- | synchronization.cpp | 179 |
1 files changed, 119 insertions, 60 deletions
diff --git a/synchronization.cpp b/synchronization.cpp index dcc119a5..a70aad30 100644 --- a/synchronization.cpp +++ b/synchronization.cpp @@ -431,10 +431,19 @@ public: size_t folderIndex, const Zstring& baseDirPf, //with separator postfix ProcessCallback& procCallback); - ~DeletionHandling() { try { tryCleanup(); } catch (...) {} /*make sure this stays non-blocking!*/ } //always (try to) clean up, even if synchronization is aborted! + ~DeletionHandling() + { + try { tryCleanup(false); } //always (try to) clean up, even if synchronization is aborted! + catch (...) {} + /* + do not allow user callback: + - make sure this stays non-blocking! + - avoid throwing user abort exception again, leading to incomplete clean-up! + */ + } //clean-up temporary directory (recycle bin optimization) - void tryCleanup(); //throw FileError -> call this in non-exceptional coding, i.e. somewhere after sync! + void tryCleanup(bool allowUserCallback = true); //throw FileError -> call this in non-exceptional coding, i.e. somewhere after sync! void removeFile (const Zstring& relativeName); //throw FileError void removeFolder(const Zstring& relativeName) { removeFolderInt(relativeName, nullptr, nullptr); }; //throw FileError @@ -470,9 +479,11 @@ private: const Zstring versioningDir_; const TimeComp timeStamp_; const int versionCountLimit_; - Zstring recyclerTmpDirPf; //temporary folder to move files to, before moving whole folder to recycler (postfixed with file name separator) #ifdef FFS_WIN + Zstring recyclerTmpDirPf; //temporary folder holding files/folders for *deferred* recycling (postfixed with file name separator) + std::vector<Zstring> toBeRecycled; //full path of files located in temporary folder, waiting to be recycled + bool recFallbackDelPermantently; #endif @@ -514,7 +525,6 @@ DeletionHandling::DeletionHandling(DeletionPolicy handleDel, //nothrow! handleDel = DELETE_PERMANENTLY; //Windows' ::SHFileOperation() will do this anyway, but we have a better and faster deletion routine (e.g. on networks) recFallbackDelPermantently = true; } -#endif //assemble temporary recycler bin directory if (!baseDirPf_.empty()) @@ -525,6 +535,7 @@ DeletionHandling::DeletionHandling(DeletionPolicy handleDel, //nothrow! recyclerTmpDirPf = appendSeparator(tempDir); } +#endif setDeletionPolicy(handleDel); } @@ -556,8 +567,33 @@ void DeletionHandling::setDeletionPolicy(DeletionPolicy newPolicy) } } +#ifdef FFS_WIN +namespace +{ +class CallbackMassRecycling : public CallbackRecycling +{ +public: + CallbackMassRecycling(ProcessCallback& statusHandler) : + statusHandler_(statusHandler), + txtRecyclingFile(_("Moving file %x to 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; +}; +} +#endif -void DeletionHandling::tryCleanup() //throw FileError +void DeletionHandling::tryCleanup(bool allowUserCallback) //throw FileError { if (!cleanedUp) { @@ -567,16 +603,29 @@ void DeletionHandling::tryCleanup() //throw FileError break; case DELETE_TO_RECYCLER: - //clean-up temporary directory (recycle bin) - if (!recyclerTmpDirPf.empty()) //folder input pair may be empty - recycleOrDelete(beforeLast(recyclerTmpDirPf, FILE_NAME_SEPARATOR)); //throw FileError +#ifdef FFS_WIN + if (!recyclerTmpDirPf.empty()) + { + //move content of temporary directory to recycle bin in a single call + CallbackMassRecycling cbmr(procCallback_); + recycleOrDelete(toBeRecycled, allowUserCallback ? &cbmr : nullptr); //throw FileError + + //clean up temp directory itself (should contain remnant empty directories only) + removeDirectory(beforeLast(recyclerTmpDirPf, FILE_NAME_SEPARATOR)); //throw FileError + } +#endif break; case DELETE_TO_VERSIONING: if (versioner.get()) { - procCallback_.reportStatus(_("Removing old versions...")); - versioner->limitVersions([&] { procCallback_.requestUiRefresh(); }); //throw FileError + if (allowUserCallback) + { + procCallback_.reportStatus(_("Removing old versions...")); //throw ? + versioner->limitVersions([&] { procCallback_.requestUiRefresh(); /*throw ? */ }); //throw FileError + } + else + versioner->limitVersions([] {}); //throw FileError } break; } @@ -674,36 +723,41 @@ void DeletionHandling::removeFile(const Zstring& relativeName) break; case DELETE_TO_RECYCLER: - { - const Zstring targetFile = recyclerTmpDirPf + relativeName; //ends with path separator - - try - { - //performance optimization: Instead of moving each object into recycle bin separately, - //we rename them one by one into a temporary directory and delete this directory only ONCE! - renameFile(fullName, targetFile); //throw FileError -> try to get away cheaply! - } - catch (FileError&) +#ifdef FFS_WIN { - if (somethingExists(fullName)) //no file at all is not an error (however a directory is *not* expected!) - try + const Zstring targetFile = recyclerTmpDirPf + relativeName; //ends with path separator + + auto moveToTempDir = [&] + { + //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 + toBeRecycled.push_back(targetFile); + }; + + try + { + moveToTempDir(); //throw FileError + } + catch (FileError&) + { + if (somethingExists(fullName)) { const Zstring targetDir = beforeLast(targetFile, FILE_NAME_SEPARATOR); - if (!dirExists(targetDir)) //no reason to update gui or overwrite status text! + if (!dirExists(targetDir)) { makeDirectory(targetDir); //throw FileError -> may legitimately fail on Linux if permissions are missing - renameFile(fullName, targetFile); //throw FileError -> this should work now! + moveToTempDir(); //throw FileError -> this should work now! } else throw; } - catch (FileError&) //if anything went wrong, move to recycle bin the standard way (single file processing: slow) - { - recycleOrDelete(fullName); //throw FileError - } + } } - } - break; +#elif defined FFS_LINUX + recycleOrDelete(fullName); //throw FileError +#endif + break; case DELETE_TO_VERSIONING: { @@ -733,42 +787,47 @@ void DeletionHandling::removeFolderInt(const Zstring& relativeName, const int* o break; case DELETE_TO_RECYCLER: - { - const Zstring targetDir = recyclerTmpDirPf + relativeName; - - try - { - //performance optimization: Instead of moving each object into recycle bin separately, - //we rename them one by one into a temporary directory and delete this directory only ONCE! - renameFile(fullName, targetDir); //throw FileError -> try to get away cheaply! - } - catch (FileError&) +#ifdef FFS_WIN { - if (somethingExists(fullName)) - try + const Zstring targetDir = recyclerTmpDirPf + relativeName; + + auto moveToTempDir = [&] + { + //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 + toBeRecycled.push_back(targetDir); + }; + + try + { + moveToTempDir(); //throw FileError + } + catch (FileError&) + { + if (somethingExists(fullName)) { const Zstring targetSuperDir = beforeLast(targetDir, FILE_NAME_SEPARATOR); - if (!dirExists(targetSuperDir)) //no reason to update gui or overwrite status text! + if (!dirExists(targetSuperDir)) { makeDirectory(targetSuperDir); //throw FileError -> may legitimately fail on Linux if permissions are missing - renameFile(fullName, targetDir); //throw FileError -> this should work now! + moveToTempDir(); //throw FileError -> this should work now! } else throw; } - catch (FileError&) //if anything went wrong, move to recycle bin the standard way (single file processing: slow) - { - recycleOrDelete(fullName); //throw FileError - } + } } - } +#elif defined FFS_LINUX + recycleOrDelete(fullName); //throw FileError +#endif - if (objectsExpected) //even though we have only one disk access, we completed "objectsExpected" logical operations! - { - procCallback_.updateProcessedData(*objectsExpected, 0); - objectsReported += *objectsExpected; - } - break; + if (objectsExpected) //even though we have only one disk access, we completed "objectsExpected" logical operations! + { + procCallback_.updateProcessedData(*objectsExpected, 0); + objectsReported += *objectsExpected; + } + break; case DELETE_TO_VERSIONING: { @@ -1460,7 +1519,7 @@ void SynchronizeFolderPair::synchronizeFileInt(FileMapping& fileObj, SyncOperati } catch (FileError&) { - if (fileExists(fileObj.getFullName<sideSrc>())) + if (somethingExists(fileObj.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, -to<zen::Int64>(fileObj.getFileSize<sideSrc>())); @@ -1624,7 +1683,7 @@ void SynchronizeFolderPair::synchronizeLinkInt(SymLinkMapping& linkObj, SyncOper } catch (FileError&) { - if (fileExists(linkObj.getFullName<sideSrc>())) + 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); @@ -1720,7 +1779,7 @@ void SynchronizeFolderPair::synchronizeFolderInt(DirMapping& dirObj, SyncOperati { case SO_CREATE_NEW_LEFT: case SO_CREATE_NEW_RIGHT: - if (dirExists(dirObj.getFullName<sideSrc>())) + if (somethingExists(dirObj.getFullName<sideSrc>())) //do not check on type (symlink, file, folder) -> if there is a type change, FFS should not be quiet about it! { const Zstring& target = dirObj.getBaseDirPf<sideTrg>() + dirObj.getRelativeName<sideSrc>(); @@ -2244,8 +2303,8 @@ void zen::synchronize(const TimeComp& timeStamp, ScopeGuard guardUpdateDb = makeGuard([&] { if (folderPairCfg.inAutomaticMode) - try { zen::saveLastSynchronousState(*j); } - catch (...) {} //throw FileError + try { zen::saveLastSynchronousState(*j); } //throw FileError + catch (...) {} }); //guarantee removal of invalid entries (where element on both sides is empty) |