diff options
Diffstat (limited to 'FreeFileSync/Source/base/db_file.cpp')
-rw-r--r-- | FreeFileSync/Source/base/db_file.cpp | 381 |
1 files changed, 203 insertions, 178 deletions
diff --git a/FreeFileSync/Source/base/db_file.cpp b/FreeFileSync/Source/base/db_file.cpp index 4e16c885..c7ad54d5 100644 --- a/FreeFileSync/Source/base/db_file.cpp +++ b/FreeFileSync/Source/base/db_file.cpp @@ -7,8 +7,10 @@ #include "db_file.h" #include <zen/guid.h> #include <zen/crc.h> +#include <zen/build_info.h> #include <zen/zlib_wrap.h> #include "../afs/concrete.h" +#include "status_handler_impl.h" using namespace zen; @@ -23,6 +25,8 @@ const int DB_FORMAT_CONTAINER = 10; //since 2017-02-01 const int DB_FORMAT_STREAM = 3; // //------------------------------------------------------------------------------------------------------------------------------- +DEFINE_NEW_FILE_ERROR(FileErrorDatabaseNotExisting) + struct SessionData { bool isLeadStream = false; @@ -40,17 +44,23 @@ using DbStreams = std::map<UniqueId, SessionData>; //list of streams ordered by template <SelectedSide side> inline AbstractPath getDatabaseFilePath(const BaseFolderPair& baseFolder) { - //Linux and Windows builds are binary incompatible: different file id?, problem with case sensitivity? - //precomposed/decomposed UTF? are UTC file times really compatible? what about endianess!? - //however 32 and 64-bit FreeFileSync are designed to produce binary-identical db files! - //=> give db files different names: + static_assert(usingLittleEndian()); + /* Windows, Linux, macOS considerations for uniform database format: + - different file IDs: no, but the volume IDs are different! + - problem with case sensitivity: no + - are UTC file times identical: yes (at least with 1 sec precision) + - endianess: FFS currently not running on any big-endian platform + - precomposed/decomposed UTF: differences already ignored + - 32 vs 64-bit: already handled + + => give db files different names: */ const Zstring dbName = Zstr(".sync"); //files beginning with dots are hidden e.g. in Nautilus return AFS::appendRelPath(baseFolder.getAbstractPath<side>(), dbName + SYNC_DB_FILE_ENDING); } //####################################################################################################################################### -void saveStreams(const DbStreams& streamList, const AbstractPath& dbPath, const IOCallback& notifyUnbufferedIO) //throw FileError +void saveStreams(const DbStreams& streamList, const AbstractPath& dbPath, const IOCallback& notifyUnbufferedIO /*throw X*/) //throw FileError, X { const std::unique_ptr<AFS::OutputStream> fileStreamOut = AFS::getOutputStream(dbPath, //throw FileError std::nullopt /*streamSize*/, @@ -105,7 +115,6 @@ DbStreams loadStreams(const AbstractPath& dbPath, const IOCallback& notifyUnbuff size_t streamCount = readNumber<uint32_t>(*fileStreamIn); //throw FileError, ErrorFileLocked, X, UnexpectedEndOfStreamError while (streamCount-- != 0) { - //DB id of partner databases std::string sessionID = readContainer<std::string>(*fileStreamIn); //throw FileError, ErrorFileLocked, X, UnexpectedEndOfStreamError SessionData sessionData = {}; @@ -284,11 +293,11 @@ private: class StreamParser { public: - static std::shared_ptr<InSyncFolder> execute(bool leadStreamLeft, //throw FileError - const ByteArray& streamL, - const ByteArray& streamR, - const std::wstring& displayFilePathL, //for diagnostics only - const std::wstring& displayFilePathR) + static SharedRef<InSyncFolder> execute(bool leadStreamLeft, //throw FileError + const ByteArray& streamL, + const ByteArray& streamR, + const std::wstring& displayFilePathL, //for diagnostics only + const std::wstring& displayFilePathR) { auto decompStream = [&](const ByteArray& stream) -> ByteArray //throw FileError { @@ -343,11 +352,11 @@ public: const ByteArray tmpL = readContainer<ByteArray>(streamInL); const ByteArray tmpR = readContainer<ByteArray>(streamInR); - auto output = std::make_shared<InSyncFolder>(InSyncFolder::DIR_STATUS_IN_SYNC); + auto output = makeSharedRef<InSyncFolder>(InSyncFolder::DIR_STATUS_IN_SYNC); StreamParserV2 parser(decompStream(tmpL), decompStream(tmpR), decompStream(tmpB)); - parser.recurse(*output); //throw UnexpectedEndOfStreamError + parser.recurse(output.ref()); //throw UnexpectedEndOfStreamError return output; } else @@ -369,15 +378,15 @@ public: const ByteArray bufSmallNum = readContainer<ByteArray>(streamIn); //throw UnexpectedEndOfStreamError const ByteArray bufBigNum = readContainer<ByteArray>(streamIn); // - auto output = std::make_shared<InSyncFolder>(InSyncFolder::DIR_STATUS_IN_SYNC); + auto output = makeSharedRef<InSyncFolder>(InSyncFolder::DIR_STATUS_IN_SYNC); StreamParser parser(streamVersion, decompStream(bufText), decompStream(bufSmallNum), decompStream(bufBigNum)); //throw FileError if (leadStreamLeft) - parser.recurse<LEFT_SIDE>(*output); //throw UnexpectedEndOfStreamError + parser.recurse<LEFT_SIDE>(output.ref()); //throw UnexpectedEndOfStreamError else - parser.recurse<RIGHT_SIDE>(*output); //throw UnexpectedEndOfStreamError + parser.recurse<RIGHT_SIDE>(output.ref()); //throw UnexpectedEndOfStreamError return output; } } @@ -446,7 +455,7 @@ private: } static Zstring readUtf8(MemoryStreamIn<ByteArray>& streamIn) { return utfTo<Zstring>(readContainer<Zbase<char>>(streamIn)); } //throw UnexpectedEndOfStreamError - //optional: use null-termiation: 5% overall size reduction + //optional: use null-termination: 5% overall size reduction //optional: split into streamInText_/streamInSmallNum_: overall size increase! (why?) static InSyncDescrFile readFileDescr(MemoryStreamIn<ByteArray>& streamIn) //throw UnexpectedEndOfStreamError @@ -582,9 +591,9 @@ private: } //delete removed items (= "in-sync") from database - eraseIf(dbFiles, [&](const InSyncFolder::FileList::value_type& v) + std::erase_if(dbFiles, [&](const InSyncFolder::FileList::value_type& v) { - if (toPreserve.find(v.first) != toPreserve.end()) + if (contains(toPreserve, v.first)) return false; //all items not existing in "currentFiles" have either been deleted meanwhile or been excluded via filter: const Zstring& itemRelPath = nativeAppendPaths(parentRelPath, v.first); @@ -619,9 +628,9 @@ private: } //delete removed items (= "in-sync") from database - eraseIf(dbSymlinks, [&](const InSyncFolder::SymlinkList::value_type& v) + std::erase_if(dbSymlinks, [&](const InSyncFolder::SymlinkList::value_type& v) { - if (toPreserve.find(v.first) != toPreserve.end()) + if (contains(toPreserve, v.first)) return false; //all items not existing in "currentSymlinks" have either been deleted meanwhile or been excluded via filter: const Zstring& itemRelPath = nativeAppendPaths(parentRelPath, v.first); @@ -654,7 +663,7 @@ private: } //delete removed items (= "in-sync") from database - eraseIf(dbFolders, [&](InSyncFolder::FolderList::value_type& v) + std::erase_if(dbFolders, [&](InSyncFolder::FolderList::value_type& v) { if (auto it = toPreserve.find(v.first); it != toPreserve.end()) { @@ -678,10 +687,10 @@ private: //delete all entries for removed folder (= "in-sync") from database void dbSetEmptyState(InSyncFolder& dbFolder, const Zstring& parentRelPathPf) { - eraseIf(dbFolder.files, [&](const InSyncFolder::FileList ::value_type& v) { return filter_.passFileFilter(parentRelPathPf + v.first); }); - eraseIf(dbFolder.symlinks, [&](const InSyncFolder::SymlinkList::value_type& v) { return filter_.passFileFilter(parentRelPathPf + v.first); }); + std::erase_if(dbFolder.files, [&](const InSyncFolder::FileList ::value_type& v) { return filter_.passFileFilter(parentRelPathPf + v.first); }); + std::erase_if(dbFolder.symlinks, [&](const InSyncFolder::SymlinkList::value_type& v) { return filter_.passFileFilter(parentRelPathPf + v.first); }); - eraseIf(dbFolder.folders, [&](InSyncFolder::FolderList::value_type& v) + std::erase_if(dbFolder.folders, [&](InSyncFolder::FolderList::value_type& v) { const Zstring& itemRelPath = parentRelPathPf + v.first; @@ -700,19 +709,19 @@ private: struct StreamStatusNotifier { - StreamStatusNotifier(const std::wstring& msgPrefix, const std::function<void(const std::wstring& statusMsg)>& notifyStatus) : - msgPrefix_(msgPrefix), notifyStatus_(notifyStatus) {} + StreamStatusNotifier(const std::wstring& msgPrefix, AsyncCallback& acb /*throw ThreadInterruption*/) : + msgPrefix_(msgPrefix), acb_(acb) {} - void operator()(int64_t bytesDelta) //throw X + void operator()(int64_t bytesDelta) //throw ThreadInterruption { bytesTotal_ += bytesDelta; - if (notifyStatus_) notifyStatus_(msgPrefix_ + L" (" + formatFilesizeShort(bytesTotal_) + L")"); //throw X + acb_.updateStatus(msgPrefix_ + L" (" + formatFilesizeShort(bytesTotal_) + L")"); //throw ThreadInterruption } private: const std::wstring msgPrefix_; int64_t bytesTotal_ = 0; - const std::function<void(const std::wstring& statusMsg)> notifyStatus_; + AsyncCallback& acb_; }; @@ -748,90 +757,146 @@ std::pair<DbStreams::const_iterator, //####################################################################################################################################### -std::shared_ptr<InSyncFolder> fff::loadLastSynchronousState(const BaseFolderPair& baseFolder, //throw FileError, FileErrorDatabaseNotExisting -> return value always bound! - const std::function<void(const std::wstring& statusMsg)>& notifyStatus) +std::unordered_map<const BaseFolderPair*, SharedRef<const InSyncFolder>> fff::loadLastSynchronousState(const std::vector<const BaseFolderPair*>& baseFolders, + PhaseCallback& callback /*throw X*/) //throw X { - const AbstractPath dbPathLeft = getDatabaseFilePath< LEFT_SIDE>(baseFolder); - const AbstractPath dbPathRight = getDatabaseFilePath<RIGHT_SIDE>(baseFolder); + std::set<AbstractPath> dbFilePaths; - if (!baseFolder.isAvailable< LEFT_SIDE>() || - !baseFolder.isAvailable<RIGHT_SIDE>()) - { + for (const BaseFolderPair* baseFolder : baseFolders) //avoid race condition with directory existence check: reading sync.ffs_db may succeed although first dir check had failed => conflicts! - //https://sourceforge.net/tracker/?func=detail&atid=1093080&aid=3531351&group_id=234430 - const AbstractPath filePath = !baseFolder.isAvailable<LEFT_SIDE>() ? dbPathLeft : dbPathRight; - throw FileErrorDatabaseNotExisting(_("Initial synchronization:") + L" \n" + //it could be due to a to-be-created target directory not yet existing => FileErrorDatabaseNotExisting - replaceCpy(_("Database file %x does not yet exist."), L"%x", fmtPath(AFS::getDisplayPath(filePath)))); + if (baseFolder->isAvailable< LEFT_SIDE>() && + baseFolder->isAvailable<RIGHT_SIDE>()) + { + dbFilePaths.insert(getDatabaseFilePath< LEFT_SIDE>(*baseFolder)); + dbFilePaths.insert(getDatabaseFilePath<RIGHT_SIDE>(*baseFolder)); + } + //else: ignore; there's no value in reporting it other than to confuse users + + std::map<AbstractPath, DbStreams> dbStreamsByPath; + //------------ (try to) load DB files in parallel ------------------------- + { + Protected<std::map<AbstractPath, DbStreams>&> dbStreamsByPathShared(dbStreamsByPath); + std::vector<std::pair<AbstractPath, ParallelWorkItem>> parallelWorkload; + + for (const AbstractPath& dbPath : dbFilePaths) + parallelWorkload.emplace_back(dbPath, [&dbStreamsByPathShared](ParallelContext& ctx) //throw ThreadInterruption + { + StreamStatusNotifier notifyLoad(replaceCpy(_("Loading file %x..."), L"%x", fmtPath(AFS::getDisplayPath(ctx.itemPath))), ctx.acb); + + tryReportingError([&] //throw ThreadInterruption + { + try + { + DbStreams dbStreams = ::loadStreams(ctx.itemPath, notifyLoad); //throw FileError, FileErrorDatabaseNotExisting, ThreadInterruption + + dbStreamsByPathShared.access([&](auto& dbStreamsByPath2) { dbStreamsByPath2.emplace(ctx.itemPath, std::move(dbStreams)); }); + } + catch (FileErrorDatabaseNotExisting&) {} + }, ctx.acb); + }); + + massParallelExecute(parallelWorkload, + "Load sync.ffs_db:", callback /*throw X*/); //throw X } + //---------------------------------------------------------------- - StreamStatusNotifier notifyLoadL(replaceCpy(_("Loading file %x..."), L"%x", fmtPath(AFS::getDisplayPath(dbPathLeft) )), notifyStatus); - StreamStatusNotifier notifyLoadR(replaceCpy(_("Loading file %x..."), L"%x", fmtPath(AFS::getDisplayPath(dbPathRight))), notifyStatus); - - //read file data: list of session ID + DirInfo-stream - const DbStreams streamsLeft = ::loadStreams(dbPathLeft, notifyLoadL); //throw FileError, FileErrorDatabaseNotExisting, X - const DbStreams streamsRight = ::loadStreams(dbPathRight, notifyLoadR); // - - //find associated session: there can be at most one session within intersection of left and right IDs - const auto [itStreamL, itStreamR] = findCommonSession(streamsLeft, streamsRight, //throw FileError - AFS::getDisplayPath(dbPathLeft), - AFS::getDisplayPath(dbPathRight)); - if (itStreamL == streamsLeft.end()) - throw FileErrorDatabaseNotExisting(_("Initial synchronization:") + L" \n" + - _("The database files do not yet contain information about the last synchronization.")); - - const bool leadStreamLeft = itStreamL->second.isLeadStream; - assert(itStreamL->second.isLeadStream != itStreamR->second.isLeadStream); - const ByteArray& streamL = itStreamL ->second.rawStream; - const ByteArray& streamR = itStreamR->second.rawStream; - - return StreamParser::execute(leadStreamLeft, streamL, streamR, //throw FileError - AFS::getDisplayPath(dbPathLeft), - AFS::getDisplayPath(dbPathRight)); + std::unordered_map<const BaseFolderPair*, SharedRef<const InSyncFolder>> output; + + for (const BaseFolderPair* baseFolder : baseFolders) + if (baseFolder->isAvailable< LEFT_SIDE>() && + baseFolder->isAvailable<RIGHT_SIDE>()) + { + const AbstractPath dbPathL = getDatabaseFilePath< LEFT_SIDE>(*baseFolder); + const AbstractPath dbPathR = getDatabaseFilePath<RIGHT_SIDE>(*baseFolder); + + auto itL = dbStreamsByPath.find(dbPathL); + auto itR = dbStreamsByPath.find(dbPathR); + + if (itL != dbStreamsByPath.end() && + itR != dbStreamsByPath.end()) + { + const DbStreams& streamsL = itL->second; + const DbStreams& streamsR = itR->second; + + tryReportingError([&] //throw X + { + //find associated session: there can be at most one session within intersection of left and right IDs + const auto [itStreamL, itStreamR] = findCommonSession(streamsL, streamsR, + AFS::getDisplayPath(dbPathL), + AFS::getDisplayPath(dbPathR)); //throw FileError + if (itStreamL != streamsL.end()) + { + assert(itStreamL->second.isLeadStream != itStreamR->second.isLeadStream); + SharedRef<InSyncFolder> lastSyncState = StreamParser::execute(itStreamL->second.isLeadStream, + itStreamL->second.rawStream, + itStreamR->second.rawStream, + AFS::getDisplayPath(dbPathL), + AFS::getDisplayPath(dbPathR)); //throw FileError + output.emplace(baseFolder, lastSyncState); + } + }, callback /*throw X*/); + } + } + + return output; } void fff::saveLastSynchronousState(const BaseFolderPair& baseFolder, bool transactionalCopy, - const std::function<void(const std::wstring& statusMsg)>& notifyStatus /*throw X*/) //throw FileError, X + PhaseCallback& callback /*throw X*/) //throw X { - //transactional behaviour! write to tmp files first const AbstractPath dbPathL = getDatabaseFilePath< LEFT_SIDE>(baseFolder); const AbstractPath dbPathR = getDatabaseFilePath<RIGHT_SIDE>(baseFolder); - StreamStatusNotifier notifyLoadL(replaceCpy(_("Loading file %x..."), L"%x", fmtPath(AFS::getDisplayPath(dbPathL))), notifyStatus); - StreamStatusNotifier notifyLoadR(replaceCpy(_("Loading file %x..."), L"%x", fmtPath(AFS::getDisplayPath(dbPathR))), notifyStatus); - - StreamStatusNotifier notifySaveL(replaceCpy(_("Saving file %x..."), L"%x", fmtPath(AFS::getDisplayPath(dbPathL))), notifyStatus); - StreamStatusNotifier notifySaveR(replaceCpy(_("Saving file %x..."), L"%x", fmtPath(AFS::getDisplayPath(dbPathR))), notifyStatus); - - //(try to) load old database files... + //------------ (try to) load DB files in parallel ------------------------- DbStreams streamsL; //list of session ID + DirInfo-stream - DbStreams streamsR; + DbStreams streamsR; // + { + std::vector<std::pair<AbstractPath, ParallelWorkItem>> parallelWorkload; + + for (const auto& [dbPath, streamsOut] : + { + std::pair(dbPathL, &streamsL), + std::pair(dbPathR, &streamsR) + }) + parallelWorkload.emplace_back(dbPath, [streamsOut /*clang bug*/= streamsOut](ParallelContext& ctx) //throw ThreadInterruption + { + StreamStatusNotifier notifyLoad(replaceCpy(_("Loading file %x..."), L"%x", fmtPath(AFS::getDisplayPath(ctx.itemPath))), ctx.acb); - try { streamsL = ::loadStreams(dbPathL, notifyLoadL); } //throw FileError, FileErrorDatabaseNotExisting, X - catch (FileError&) {} - try { streamsR = ::loadStreams(dbPathR, notifyLoadR); } //throw FileError, FileErrorDatabaseNotExisting, X - catch (FileError&) {} - //if error occurs: just overwrite old file! User is already informed about issues right after comparing! + tryReportingError([&] //throw ThreadInterruption + { + try { *streamsOut = ::loadStreams(ctx.itemPath, notifyLoad); } //throw FileError, FileErrorDatabaseNotExisting, ThreadInterruption + catch (FileErrorDatabaseNotExisting&) {} + }, ctx.acb); + }); - auto lastSyncState = std::make_shared<InSyncFolder>(InSyncFolder::DIR_STATUS_IN_SYNC); + massParallelExecute(parallelWorkload, + "Load sync.ffs_db:", callback /*throw X*/); //throw X + } + //---------------------------------------------------------------- - //find associated session: there can be at most one session within intersection of left and right IDs - const auto [itStreamOldL, itStreamOldR] = findCommonSession(streamsL, streamsR, //throw FileError - AFS::getDisplayPath(dbPathL), - AFS::getDisplayPath(dbPathR)); - if (itStreamOldL != streamsL.end()) - try //load last synchrounous state - { - lastSyncState = StreamParser::execute(itStreamOldL->second.isLeadStream /*leadStreamLeft*/, - itStreamOldL->second.rawStream, //throw FileError - itStreamOldR->second.rawStream, - AFS::getDisplayPath(dbPathL), - AFS::getDisplayPath(dbPathR)); - } - catch (FileError&) {} //if error occurs: just overwrite old file! User is already informed about errors right after comparing! + //load last synchrounous state + auto itStreamOldL = streamsL.cend(); + auto itStreamOldR = streamsR.cend(); + InSyncFolder lastSyncState(InSyncFolder::DIR_STATUS_IN_SYNC); + try + { + //find associated session: there can be at most one session within intersection of left and right IDs + std::tie(itStreamOldL, itStreamOldR) = findCommonSession(streamsL, streamsR, //throw FileError + AFS::getDisplayPath(dbPathL), + AFS::getDisplayPath(dbPathR)); + if (itStreamOldL != streamsL.end()) + lastSyncState = std::move(StreamParser::execute(itStreamOldL->second.isLeadStream /*leadStreamLeft*/, + itStreamOldL->second.rawStream, + itStreamOldR->second.rawStream, + AFS::getDisplayPath(dbPathL), + AFS::getDisplayPath(dbPathR)).ref()); //throw FileError + } + catch (const FileError& e) { callback.reportInfo(e.toString()); } //throw X + //if error occurs: just overwrite old file! User is already informed about errors right after comparing! //update last synchrounous state - LastSynchronousStateUpdater::execute(baseFolder, *lastSyncState); + LastSynchronousStateUpdater::execute(baseFolder, lastSyncState); //serialize again SessionData sessionDataL = {}; @@ -839,11 +904,15 @@ void fff::saveLastSynchronousState(const BaseFolderPair& baseFolder, bool transa sessionDataL.isLeadStream = true; sessionDataR.isLeadStream = false; - StreamGenerator::execute(*lastSyncState, //throw FileError + if (const std::wstring errMsg = tryReportingError([&] //throw X +{ + StreamGenerator::execute(lastSyncState, //throw FileError AFS::getDisplayPath(dbPathL), AFS::getDisplayPath(dbPathR), sessionDataL.rawStream, sessionDataR.rawStream); + }, callback /*throw X*/); !errMsg.empty()) + return; //check if there is some work to do at all if (itStreamOldL != streamsL.end() && itStreamOldL->second == sessionDataL && @@ -862,90 +931,46 @@ void fff::saveLastSynchronousState(const BaseFolderPair& baseFolder, bool transa streamsL[sessionID] = std::move(sessionDataL); streamsR[sessionID] = std::move(sessionDataR); - warn_static("finish: support massParallelExecute!?") - if (transactionalCopy && - (!AFS::hasNativeTransactionalCopy(dbPathL) || - !AFS::hasNativeTransactionalCopy(dbPathR))) - { - //write (temp-) files as a transaction - const Zstring shortGuid = printNumber<Zstring>(Zstr("%04x"), static_cast<unsigned int>(getCrc16(generateGUID()))); - - const AbstractPath dbPathTmpL = AFS::appendRelPath(*AFS::getParentPath(dbPathL), AFS::getItemName(dbPathL) + Zstr('.') + shortGuid + AFS::TEMP_FILE_ENDING); - const AbstractPath dbPathRTmp = AFS::appendRelPath(*AFS::getParentPath(dbPathR), AFS::getItemName(dbPathR) + Zstr('.') + shortGuid + AFS::TEMP_FILE_ENDING); - - saveStreams(streamsL, dbPathTmpL, notifySaveL); //throw FileError, X - auto guardTmpL = makeGuard<ScopeGuardRunMode::ON_FAIL>([&] { try { AFS::removeFilePlain(dbPathTmpL); } catch (FileError&) {} }); - saveStreams(streamsR, dbPathRTmp, notifySaveR); //throw FileError, X - auto guardTmpR = makeGuard<ScopeGuardRunMode::ON_FAIL>([&] { try { AFS::removeFilePlain(dbPathRTmp); } catch (FileError&) {} }); - - //operation finished: rename temp files -> this should work (almost) transactionally: - //if there were no write access, creation of temp files would have failed - AFS::removeFileIfExists(dbPathL); //throw FileError - AFS::moveAndRenameItem(dbPathTmpL, dbPathL); //throw FileError, (ErrorMoveUnsupported) - guardTmpL.dismiss(); - - AFS::removeFileIfExists(dbPathR); //throw FileError - AFS::moveAndRenameItem(dbPathRTmp, dbPathR); //throw FileError, (ErrorMoveUnsupported) - guardTmpR.dismiss(); - } - else //some MTP devices don't even allow renaming files: https://freefilesync.org/forum/viewtopic.php?t=6531 - { - warn_static("caveat: throw X leaves db file as deleted!") - AFS::removeFileIfExists(dbPathL); //throw FileError - saveStreams(streamsL, dbPathL, notifySaveL); //throw FileError, X - - AFS::removeFileIfExists(dbPathR); //throw FileError - saveStreams(streamsR, dbPathR, notifySaveR); //throw FileError, X - } - -#if 0 - warn_static("remove after test") - - //write (temp-) files as a transaction - const Zstring shortGuid = printNumber<Zstring>(Zstr("%04x"), static_cast<unsigned int>(getCrc16(generateGUID()))); - - AbstractPath dbPathTmpL = getNullPath(); - AbstractPath dbPathTmpR = getNullPath(); - ZEN_ON_SCOPE_EXIT( - try { if (!AFS::isNullPath(dbPathTmpL)) AFS::removeFilePlain(dbPathTmpL); } - catch (FileError&) {} - - try { if (!AFS::isNullPath(dbPathTmpR)) AFS::removeFilePlain(dbPathTmpR); } - catch (FileError&) {} - ); - - if (transactionalCopy && !AFS::hasNativeTransactionalCopy(dbPathL)) - { - AbstractPath dbPathTmpL2 = AFS::appendRelPath(*AFS::getParentPath(dbPathL), AFS::getItemName(dbPathL) + Zstr('.') + shortGuid + AFS::TEMP_FILE_ENDING); - saveStreams(streamsL, dbPathTmpL2, notifySaveL); //throw FileError, X - dbPathTmpL = dbPathTmpL2; - } - - if (transactionalCopy && !AFS::hasNativeTransactionalCopy(dbPathR)) + //------------ save DB files in parallel ------------------------- { - AbstractPath dbPathTmpR2 = AFS::appendRelPath(*AFS::getParentPath(dbPathR), AFS::getItemName(dbPathR) + Zstr('.') + shortGuid + AFS::TEMP_FILE_ENDING); - saveStreams(streamsR, dbPathTmpR2, notifySaveR); //throw FileError, X - dbPathTmpR = dbPathTmpR2; - } + std::vector<std::pair<AbstractPath, ParallelWorkItem>> parallelWorkload; + + for (const auto& [dbPath, streams] : + { + std::pair(dbPathL, &streamsL), + std::pair(dbPathR, &streamsR) + }) + parallelWorkload.emplace_back(dbPath, [streams /*clang bug*/= streams, transactionalCopy](ParallelContext& ctx) //throw ThreadInterruption + { + tryReportingError([&] //throw ThreadInterruption + { + StreamStatusNotifier notifySave(replaceCpy(_("Saving file %x..."), L"%x", fmtPath(AFS::getDisplayPath(ctx.itemPath))), ctx.acb); - AFS::removeFileIfExists(dbPathL); //throw FileError - if (!AFS::isNullPath(dbPathTmpL)) - { - //operation finished: rename temp files -> this should work (almost) transactionally: - //if there were no write access, creation of temp files would have failed - AFS::moveAndRenameItem(dbPathTmpL, dbPathL); //throw FileError, (ErrorMoveUnsupported) - dbPathTmpL = getNullPath(); - } - else //some MTP devices don't even allow renaming files: https://freefilesync.org/forum/viewtopic.php?t=6531 - saveStreams(streamsL, dbPathL, notifySaveL); //throw FileError, X + if (transactionalCopy && !AFS::hasNativeTransactionalCopy(ctx.itemPath)) + { + //write (temp-) files as a transaction + const Zstring shortGuid = printNumber<Zstring>(Zstr("%04x"), static_cast<unsigned int>(getCrc16(generateGUID()))); + const AbstractPath dbPathTmp = AFS::appendRelPath(*AFS::getParentPath(ctx.itemPath), AFS::getItemName(ctx.itemPath) + Zstr('.') + shortGuid + AFS::TEMP_FILE_ENDING); + + saveStreams(*streams, dbPathTmp, notifySave); //throw FileError, ThreadInterruption + ZEN_ON_SCOPE_FAIL(try { AFS::removeFilePlain(dbPathTmp); } + catch (FileError&) {}); + + //operation finished: rename temp file -> this should work (almost) transactionally: + //if there were no write access, creation of temp file would have failed + AFS::removeFileIfExists(ctx.itemPath); //throw FileError + AFS::moveAndRenameItem(dbPathTmp, ctx.itemPath); //throw FileError, (ErrorMoveUnsupported) + } + else //some MTP devices don't even allow renaming files: https://freefilesync.org/forum/viewtopic.php?t=6531 + { + AFS::removeFileIfExists(ctx.itemPath); //throw FileError + saveStreams(*streams, ctx.itemPath, notifySave); //throw FileError, ThreadInterruption + } + }, ctx.acb); + }); - AFS::removeFileIfExists(dbPathR); //throw FileError - if (!AFS::isNullPath(dbPathTmpR)) - { - AFS::moveAndRenameItem(dbPathTmpR, dbPathR); //throw FileError, (ErrorMoveUnsupported) - dbPathTmpR = getNullPath(); + massParallelExecute(parallelWorkload, + "Save sync.ffs_db:", callback /*throw X*/); //throw X } - else - saveStreams(streamsR, dbPathR, notifySaveR); //throw FileError, X -#endif + //---------------------------------------------------------------- } |