From 840e906a4ddbbb32b8a5989e8a0ce10c8c374819 Mon Sep 17 00:00:00 2001 From: B Stack Date: Tue, 2 Mar 2021 17:23:41 -0500 Subject: add upstream 11.7 --- Changelog.txt | 20 + FreeFileSync/Build/Resources/Languages.zip | Bin 507047 -> 507670 bytes FreeFileSync/Source/RealTimeSync/app_icon.h | 2 +- FreeFileSync/Source/RealTimeSync/application.cpp | 14 +- FreeFileSync/Source/RealTimeSync/main_dlg.cpp | 2 +- FreeFileSync/Source/RealTimeSync/monitor.cpp | 8 +- FreeFileSync/Source/afs/abstract.cpp | 23 +- FreeFileSync/Source/afs/abstract.h | 42 +-- FreeFileSync/Source/afs/abstract_impl.h | 12 +- FreeFileSync/Source/afs/ftp.cpp | 180 ++++----- FreeFileSync/Source/afs/gdrive.cpp | 388 +++++++++++--------- FreeFileSync/Source/afs/gdrive.h | 3 + FreeFileSync/Source/afs/native.cpp | 193 ++++++---- FreeFileSync/Source/afs/native.h | 5 + FreeFileSync/Source/afs/sftp.cpp | 68 ++-- FreeFileSync/Source/application.cpp | 41 ++- FreeFileSync/Source/base/algorithm.cpp | 413 +++++++++++---------- FreeFileSync/Source/base/binary.cpp | 4 +- FreeFileSync/Source/base/binary.h | 2 +- FreeFileSync/Source/base/comparison.cpp | 141 ++++---- FreeFileSync/Source/base/db_file.cpp | 150 ++++---- FreeFileSync/Source/base/db_file.h | 6 +- FreeFileSync/Source/base/dir_exist_async.h | 3 +- FreeFileSync/Source/base/dir_lock.cpp | 22 +- FreeFileSync/Source/base/dir_lock.h | 2 +- FreeFileSync/Source/base/file_hierarchy.cpp | 31 +- FreeFileSync/Source/base/file_hierarchy.h | 222 ++++++------ FreeFileSync/Source/base/icon_loader.cpp | 70 ++-- FreeFileSync/Source/base/icon_loader.h | 2 +- FreeFileSync/Source/base/lock_holder.h | 2 +- FreeFileSync/Source/base/parallel_scan.cpp | 39 +- FreeFileSync/Source/base/parallel_scan.h | 6 +- FreeFileSync/Source/base/process_callback.h | 9 +- FreeFileSync/Source/base/status_handler_impl.h | 54 +-- FreeFileSync/Source/base/synchronization.cpp | 328 ++++++++--------- FreeFileSync/Source/base/synchronization.h | 8 +- FreeFileSync/Source/base/versioning.cpp | 20 +- FreeFileSync/Source/base/versioning.h | 8 +- FreeFileSync/Source/base_tools.cpp | 4 +- FreeFileSync/Source/config.cpp | 8 +- FreeFileSync/Source/config.h | 7 +- FreeFileSync/Source/fatal_error.h | 6 +- FreeFileSync/Source/ffs_paths.cpp | 52 +-- FreeFileSync/Source/ffs_paths.h | 6 +- FreeFileSync/Source/icon_buffer.cpp | 2 +- FreeFileSync/Source/localization.cpp | 8 +- FreeFileSync/Source/log_file.cpp | 10 +- FreeFileSync/Source/log_file.h | 4 +- FreeFileSync/Source/parse_lng.h | 122 +++---- FreeFileSync/Source/parse_plural.h | 30 +- FreeFileSync/Source/perf_check.cpp | 4 +- FreeFileSync/Source/status_handler.cpp | 45 ++- FreeFileSync/Source/status_handler.h | 24 +- FreeFileSync/Source/ui/abstract_folder_picker.cpp | 14 +- FreeFileSync/Source/ui/batch_config.cpp | 2 +- FreeFileSync/Source/ui/batch_status_handler.cpp | 17 +- FreeFileSync/Source/ui/batch_status_handler.h | 2 +- FreeFileSync/Source/ui/cfg_grid.cpp | 14 +- FreeFileSync/Source/ui/cfg_grid.h | 8 +- FreeFileSync/Source/ui/command_box.cpp | 4 +- FreeFileSync/Source/ui/file_grid.cpp | 58 +-- FreeFileSync/Source/ui/file_grid_attr.h | 8 +- FreeFileSync/Source/ui/file_view.cpp | 96 ++--- FreeFileSync/Source/ui/folder_pair.h | 6 +- FreeFileSync/Source/ui/folder_selector.cpp | 8 +- FreeFileSync/Source/ui/gui_status_handler.cpp | 18 +- FreeFileSync/Source/ui/gui_status_handler.h | 4 +- FreeFileSync/Source/ui/log_panel.cpp | 8 +- FreeFileSync/Source/ui/main_dlg.cpp | 421 +++++++++++----------- FreeFileSync/Source/ui/main_dlg.h | 9 +- FreeFileSync/Source/ui/progress_indicator.cpp | 70 ++-- FreeFileSync/Source/ui/search_grid.cpp | 2 +- FreeFileSync/Source/ui/small_dlgs.cpp | 14 +- FreeFileSync/Source/ui/status_handler_impl.h | 63 ---- FreeFileSync/Source/ui/tree_grid.cpp | 54 +-- FreeFileSync/Source/ui/tree_grid_attr.h | 6 +- FreeFileSync/Source/ui/version_check.cpp | 8 +- FreeFileSync/Source/version/version.h | 2 +- libcurl/rest.cpp | 2 +- wx+/choice_enum.h | 2 +- wx+/graph.cpp | 22 +- wx+/graph.h | 4 +- wx+/grid.cpp | 17 +- wx+/grid.h | 4 +- wx+/image_resources.cpp | 36 +- wx+/image_tools.cpp | 58 ++- wx+/image_tools.h | 3 + xBRZ/src/xbrz.cpp | 60 +-- xBRZ/src/xbrz.h | 33 +- xBRZ/src/xbrz_tools.h | 33 +- zen/basic_math.h | 22 +- zen/dir_watcher.cpp | 15 +- zen/error_log.h | 2 +- zen/file_access.cpp | 98 +++-- zen/file_access.h | 34 +- zen/file_id_def.h | 46 --- zen/file_io.cpp | 29 +- zen/file_io.h | 23 +- zen/file_traverser.cpp | 8 +- zen/format_unit.cpp | 8 +- zen/http.cpp | 12 +- zen/http.h | 6 +- zen/json.h | 2 +- zen/open_ssl.cpp | 132 +++---- zen/perf.h | 23 +- zen/process_exec.cpp | 8 +- zen/resolve_path.cpp | 9 +- zen/ring_buffer.h | 8 +- zen/serialize.h | 14 +- zen/stl_tools.h | 12 +- zen/string_tools.h | 16 +- zen/string_traits.h | 11 +- zen/symlink_target.h | 4 +- zen/sys_info.cpp | 48 +-- zen/sys_info.h | 1 + zen/thread.cpp | 2 +- zen/thread.h | 37 +- zen/time.h | 2 +- zen/type_traits.h | 60 ++- zenXml/zenxml/cvrt_text.h | 2 +- zenXml/zenxml/dom.h | 17 +- zenXml/zenxml/parser.h | 18 +- 122 files changed, 2500 insertions(+), 2294 deletions(-) delete mode 100644 FreeFileSync/Source/ui/status_handler_impl.h delete mode 100644 zen/file_id_def.h diff --git a/Changelog.txt b/Changelog.txt index dd6e9d4e..e4199c8d 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -1,3 +1,23 @@ +FreeFileSync 11.7 [2021-03-01] +------------------------------ +Detect moved files on FTP (if server supports MLSD) +Allow installation only for current or all user(s) (Linux) +Added application uninstaller: uninstall.sh (Linux) +Use login user config path when running as root (macOS, Linux) +Fixed detection of moved files with unstable device IDs (macOS, Linux) +Strict checking for duplicate file IDs +Avoid EINVAL invalid argument error when using F_PREALLOCATE (macOS) +Restore input focus after closing log panel +Double-click on file to open Google Drive web interface +Fixed alpha channel image scaling glitch +Fixed recycle bin folders being created recursively +Fixed thread count status message fluctuation +Don't quit FreeFileSync when parent terminal is closed (SIGHUP) +Fixed "Operation not supported" error when setting directory locks +Show folder picker despite SHCreateItemFromParsingName() error +Work around "OLE received a packet with an invalid header" error + + FreeFileSync 11.6 [2021-02-01] ------------------------------ New FreeFileSync installer (Linux) diff --git a/FreeFileSync/Build/Resources/Languages.zip b/FreeFileSync/Build/Resources/Languages.zip index ed898768..1329f10b 100644 Binary files a/FreeFileSync/Build/Resources/Languages.zip and b/FreeFileSync/Build/Resources/Languages.zip differ diff --git a/FreeFileSync/Source/RealTimeSync/app_icon.h b/FreeFileSync/Source/RealTimeSync/app_icon.h index 6352cc7a..55ae348b 100644 --- a/FreeFileSync/Source/RealTimeSync/app_icon.h +++ b/FreeFileSync/Source/RealTimeSync/app_icon.h @@ -16,7 +16,7 @@ inline wxIcon getRtsIcon() //see FFS/app_icon.h { assert(loadImage("RealTimeSync").GetWidth () == loadImage("RealTimeSync").GetHeight() && - loadImage("RealTimeSync").GetWidth() == 128); + loadImage("RealTimeSync").GetWidth() == fastFromDIP(128)); wxIcon icon; icon.CopyFromBitmap(loadImage("RealTimeSync", fastFromDIP(64))); return icon; diff --git a/FreeFileSync/Source/RealTimeSync/application.cpp b/FreeFileSync/Source/RealTimeSync/application.cpp index 729b0e49..87df96ba 100644 --- a/FreeFileSync/Source/RealTimeSync/application.cpp +++ b/FreeFileSync/Source/RealTimeSync/application.cpp @@ -87,6 +87,18 @@ bool Application::OnInit() #error unknown GTK version! #endif + try + { + /* we're a GUI app: ignore SIGHUP when the parent terminal quits! (or process is killed!) + => the FFS launcher will still be killed => fine + => macOS: apparently not needed! interestingly the FFS launcher does receive SIGHUP and *is* killed */ + if (sighandler_t oldHandler = ::signal(SIGHUP, SIG_IGN); + oldHandler == SIG_ERR) + THROW_LAST_SYS_ERROR("signal(SIGHUP)"); + else assert(!oldHandler); + } + catch (const SysError& e) { std::cerr << utfTo(e.toString()) << '\n'; } + //Windows User Experience Interaction Guidelines: tool tips should have 5s timeout, info tips no timeout => compromise: wxToolTip::Enable(true); //yawn, a wxWidgets screw-up: wxToolTip::SetAutoPop is no-op if global tooltip window is not yet constructed: wxToolTip::Enable creates it @@ -177,7 +189,7 @@ void Application::OnUnhandledException() //handles both wxApp::OnInit() + wxApp: } catch (const std::bad_alloc& e) //the only kind of exception we don't want crash dumps for { - fff::logFatalError(e.what()); //it's not always possible to display a message box, e.g. corrupted stack, however low-level file output works! + fff::logFatalError(utfTo(e.what())); //it's not always possible to display a message box, e.g. corrupted stack, however low-level file output works! const auto titleFmt = copyStringTo(wxTheApp->GetAppDisplayName()) + SPACED_DASH + _("An exception occurred"); std::cerr << utfTo(titleFmt + SPACED_DASH) << e.what() << '\n'; diff --git a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp index fc9e1acb..f308d0fa 100644 --- a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp +++ b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp @@ -376,7 +376,7 @@ void MainDialog::onAddFolder(wxCommandEvent& event) //clear existing top folder first firstFolderPanel_->setPath(Zstring()); - insertAddFolder({ topFolder }, 0); + insertAddFolder({topFolder}, 0); } diff --git a/FreeFileSync/Source/RealTimeSync/monitor.cpp b/FreeFileSync/Source/RealTimeSync/monitor.cpp index 1866b764..e0dabf2c 100644 --- a/FreeFileSync/Source/RealTimeSync/monitor.cpp +++ b/FreeFileSync/Source/RealTimeSync/monitor.cpp @@ -26,7 +26,7 @@ std::set waitForMissingDirs(const std::vector& const std::function& requestUiUpdate, std::chrono::milliseconds cbInterval) { //early failure! check for unsupported folder paths: - for (const std::string& protoName : { "ftp", "sftp", "mtp", "gdrive" }) + for (const std::string& protoName : {"ftp", "sftp", "mtp", "gdrive"}) for (const Zstring& phrase : folderPathPhrases) //hopefully clear enough now: https://freefilesync.org/forum/viewtopic.php?t=4302 if (startsWithAsciiNoCase(trimCpy(phrase), protoName + ':')) @@ -118,7 +118,7 @@ DirWatcher::Change waitForChanges(const std::set& folde catch (FileError&) { if (!dirAvailable(folderPath)) //folder not existing or can't access - return { DirWatcher::ChangeType::baseFolderUnavailable, folderPath }; + return {DirWatcher::ChangeType::baseFolderUnavailable, folderPath}; throw; } @@ -141,7 +141,7 @@ DirWatcher::Change waitForChanges(const std::set& folde //IMPORTANT CHECK: DirWatcher has problems detecting removal of top watched directories! if (checkDirNow) if (!dirAvailable(folderPath)) //catch errors related to directory removal, e.g. ERROR_NETNAME_DELETED - return { DirWatcher::ChangeType::baseFolderUnavailable, folderPath }; + return {DirWatcher::ChangeType::baseFolderUnavailable, folderPath}; try { std::vector changes = watcher->fetchChanges([&] { requestUiUpdate(false /*readyForSync*/); /*throw X*/ }, @@ -167,7 +167,7 @@ DirWatcher::Change waitForChanges(const std::set& folde catch (FileError&) { if (!dirAvailable(folderPath)) //a benign(?) race condition with FileError - return { DirWatcher::ChangeType::baseFolderUnavailable, folderPath }; + return {DirWatcher::ChangeType::baseFolderUnavailable, folderPath}; throw; } } diff --git a/FreeFileSync/Source/afs/abstract.cpp b/FreeFileSync/Source/afs/abstract.cpp index 08d5bda0..614f1290 100644 --- a/FreeFileSync/Source/afs/abstract.cpp +++ b/FreeFileSync/Source/afs/abstract.cpp @@ -21,7 +21,7 @@ bool fff::isValidRelPath(const Zstring& relPath) if constexpr (FILE_NAME_SEPARATOR != Zstr('/' )) if (contains(relPath, Zstr('/' ))) return false; if constexpr (FILE_NAME_SEPARATOR != Zstr('\\')) if (contains(relPath, Zstr('\\'))) return false; - const Zchar doubleSep[] = { FILE_NAME_SEPARATOR, FILE_NAME_SEPARATOR, 0 }; + const Zchar doubleSep[] = {FILE_NAME_SEPARATOR, FILE_NAME_SEPARATOR, 0}; return !startsWith(relPath, FILE_NAME_SEPARATOR)&& !endsWith(relPath, FILE_NAME_SEPARATOR)&& !contains(relPath, doubleSep); } @@ -80,7 +80,7 @@ struct FlatTraverserCallback : public AFS::TraverserCallback private: void onFile (const AFS::FileInfo& fi) override { if (onFile_) onFile_ (fi); } std::shared_ptr onFolder (const AFS::FolderInfo& fi) override { if (onFolder_) onFolder_ (fi); return nullptr; } - HandleLink onSymlink(const AFS::SymlinkInfo& si) override { if (onSymlink_) onSymlink_(si); return TraverserCallback::LINK_SKIP; } + HandleLink onSymlink(const AFS::SymlinkInfo& si) override { if (onSymlink_) onSymlink_(si); return TraverserCallback::HandleLink::skip; } HandleError reportDirError (const ErrorInfo& errorInfo) override { throw FileError(errorInfo.msg); } HandleError reportItemError(const ErrorInfo& errorInfo, const Zstring& itemName) override { throw FileError(errorInfo.msg); } @@ -98,13 +98,13 @@ void AFS::traverseFolderFlat(const AfsPath& afsPath, //throw FileError const std::function& onSymlink) const { auto ft = std::make_shared(onFile, onFolder, onSymlink); //throw FileError - traverseFolderRecursive({{ afsPath, ft }}, 1 /*parallelOps*/); //throw FileError + traverseFolderRecursive({{afsPath, ft}}, 1 /*parallelOps*/); //throw FileError } //already existing: undefined behavior! (e.g. fail/overwrite/auto-rename) AFS::FileCopyResult AFS::copyFileAsStream(const AfsPath& afsSource, const StreamAttributes& attrSource, //throw FileError, ErrorFileLocked, X - const AbstractPath& apTarget, const IOCallback& notifyUnbufferedIO /*throw X*/) const + const AbstractPath& apTarget, const IoCallback& notifyUnbufferedIO /*throw X*/) const { int64_t totalUnbufferedIO = 0; IOCallbackDivider cbd(notifyUnbufferedIO, totalUnbufferedIO); @@ -148,14 +148,13 @@ AFS::FileCopyResult AFS::copyFileAsStream(const AfsPath& afsSource, const Stream replaceCpy(replaceCpy(_("Unexpected size of data stream.\nExpected: %x bytes\nActual: %y bytes"), L"%x", numberTo(totalBytesRead)), L"%y", numberTo(totalBytesWritten)) + L" [notifyUnbufferedWrite]"); - FileCopyResult cpResult; - cpResult.fileSize = attrSourceNew.fileSize; - cpResult.modTime = attrSourceNew.modTime; - cpResult.sourceFileId = attrSourceNew.fileId; - cpResult.targetFileId = finResult.fileId; - cpResult.errorModTime = finResult.errorModTime; - /* Failing to set modification time is not a serious problem from synchronization perspective (treated like external update) + cpResult.fileSize = attrSourceNew.fileSize; + cpResult.modTime = attrSourceNew.modTime; + cpResult.sourceFilePrint = attrSourceNew.filePrint; + cpResult.targetFilePrint = finResult.filePrint; + cpResult.errorModTime = finResult.errorModTime; + /* Failing to set modification time is not a serious problem from synchronization perspective (treat like external update) => Support additional scenarios: - GVFS failing to set modTime for FTP: https://freefilesync.org/forum/viewtopic.php?t=2372 - GVFS failing to set modTime for MTP: https://freefilesync.org/forum/viewtopic.php?t=2803 @@ -171,7 +170,7 @@ AFS::FileCopyResult AFS::copyFileTransactional(const AbstractPath& apSource, con bool copyFilePermissions, bool transactionalCopy, const std::function& onDeleteTargetFile, - const IOCallback& notifyUnbufferedIO /*throw X*/) + const IoCallback& notifyUnbufferedIO /*throw X*/) { auto copyFilePlain = [&](const AbstractPath& apTargetTmp) diff --git a/FreeFileSync/Source/afs/abstract.h b/FreeFileSync/Source/afs/abstract.h index c37c5f8a..691fd464 100644 --- a/FreeFileSync/Source/afs/abstract.h +++ b/FreeFileSync/Source/afs/abstract.h @@ -69,8 +69,6 @@ struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model t static Zstring getInitPathPhrase(const AbstractPath& ap) { return ap.afsDevice.ref().getInitPathPhrase(ap.afsPath); } - static std::optional getNativeItemPath(const AbstractPath& ap) { return ap.afsDevice.ref().getNativeItemPath(ap.afsPath); } - //---------------------------------------------------------------------------------------------------------------- static void authenticateAccess(const AfsDevice& afsDevice, bool allowUserInteraction) //throw FileError { return afsDevice.ref().authenticateAccess(allowUserInteraction); } @@ -82,7 +80,7 @@ struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model t static bool hasNativeTransactionalCopy(const AbstractPath& ap) { return ap.afsDevice.ref().hasNativeTransactionalCopy(); } //---------------------------------------------------------------------------------------------------------------- - using FileId = std::string; //AfsDevice-dependent unique ID + using FingerPrint = uint64_t; //AfsDevice-dependent persistent unique ID enum class ItemType : unsigned char { @@ -135,7 +133,7 @@ struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model t { time_t modTime; //number of seconds since Jan. 1st 1970 UTC uint64_t fileSize; - FileId fileId; //optional! + FingerPrint filePrint; //optional }; //---------------------------------------------------------------------------------------------------------------- @@ -149,13 +147,13 @@ struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model t virtual std::optional getAttributesBuffered() = 0; //throw FileError }; //return value always bound: - static std::unique_ptr getInputStream(const AbstractPath& ap, const zen::IOCallback& notifyUnbufferedIO /*throw X*/) //throw FileError, ErrorFileLocked + static std::unique_ptr getInputStream(const AbstractPath& ap, const zen::IoCallback& notifyUnbufferedIO /*throw X*/) //throw FileError, ErrorFileLocked { return ap.afsDevice.ref().getInputStream(ap.afsPath, notifyUnbufferedIO); } struct FinalizeResult { - FileId fileId; + FingerPrint filePrint; //optional std::optional errorModTime; }; @@ -184,14 +182,14 @@ struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model t static std::unique_ptr getOutputStream(const AbstractPath& ap, //throw FileError std::optional streamSize, std::optional modTime, - const zen::IOCallback& notifyUnbufferedIO /*throw X*/) + const zen::IoCallback& notifyUnbufferedIO /*throw X*/) { return std::make_unique(ap.afsDevice.ref().getOutputStream(ap.afsPath, streamSize, modTime, notifyUnbufferedIO), ap, streamSize); } //---------------------------------------------------------------------------------------------------------------- struct SymlinkInfo { Zstring itemName; - time_t modTime; //number of seconds since Jan. 1st 1970 UTC + time_t modTime; }; struct FileInfo @@ -199,7 +197,7 @@ struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model t Zstring itemName; uint64_t fileSize; //unit: bytes! time_t modTime; //number of seconds since Jan. 1st 1970 UTC - FileId fileId; //optional: empty if not supported! + FingerPrint filePrint; //optional; persistent + unique (relative to device) or 0! bool isFollowedSymlink; }; @@ -213,16 +211,16 @@ struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model t { virtual ~TraverserCallback() {} - enum HandleLink + enum class HandleLink { - LINK_FOLLOW, //dereferences link, then calls "onFolder()" or "onFile()" - LINK_SKIP + follow, //follows link, then calls "onFolder()" or "onFile()" + skip }; - enum HandleError + enum class HandleError { - ON_ERROR_RETRY, - ON_ERROR_CONTINUE + retry, + ignore }; virtual void onFile (const FileInfo& fi) = 0; // @@ -264,8 +262,8 @@ struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model t { uint64_t fileSize = 0; time_t modTime = 0; //number of seconds since Jan. 1st 1970 UTC - FileId sourceFileId; - FileId targetFileId; + FingerPrint sourceFilePrint; //optional + FingerPrint targetFilePrint; // std::optional errorModTime; //failure to set modification time }; @@ -280,7 +278,7 @@ struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model t //if transactionalCopy == true, full read access on source had been proven at this point, so it's safe to delete it. const std::function& onDeleteTargetFile /*throw X*/, //accummulated delta != file size! consider ADS, sparse, compressed files - const zen::IOCallback& notifyUnbufferedIO /*throw X*/); + const zen::IoCallback& notifyUnbufferedIO /*throw X*/); //already existing: fail //symlink handling: follow @@ -331,7 +329,7 @@ protected: //already existing: undefined behavior! (e.g. fail/overwrite/auto-rename) FileCopyResult copyFileAsStream(const AfsPath& afsSource, const StreamAttributes& attrSource, //throw FileError, ErrorFileLocked, X - const AbstractPath& apTarget, const zen::IOCallback& notifyUnbufferedIO /*throw X*/) const; + const AbstractPath& apTarget, const zen::IoCallback& notifyUnbufferedIO /*throw X*/) const; private: virtual std::optional getNativeItemPath(const AfsPath& afsPath) const { return {}; }; @@ -363,13 +361,13 @@ private: virtual bool equalSymlinkContentForSameAfsType(const AfsPath& afsLhs, const AbstractPath& apRhs) const = 0; //throw FileError //---------------------------------------------------------------------------------------------------------------- - virtual std::unique_ptr getInputStream(const AfsPath& afsPath, const zen::IOCallback& notifyUnbufferedIO /*throw X*/) const = 0; //throw FileError, ErrorFileLocked + virtual std::unique_ptr getInputStream(const AfsPath& afsPath, const zen::IoCallback& notifyUnbufferedIO /*throw X*/) const = 0; //throw FileError, ErrorFileLocked //already existing: undefined behavior! (e.g. fail/overwrite/auto-rename) virtual std::unique_ptr getOutputStream(const AfsPath& afsPath, //throw FileError std::optional streamSize, std::optional modTime, - const zen::IOCallback& notifyUnbufferedIO /*throw X*/) const = 0; + const zen::IoCallback& notifyUnbufferedIO /*throw X*/) const = 0; //---------------------------------------------------------------------------------------------------------------- virtual void traverseFolderRecursive(const TraverserWorkload& workload /*throw X*/, size_t parallelOps) const = 0; //---------------------------------------------------------------------------------------------------------------- @@ -383,7 +381,7 @@ private: virtual FileCopyResult copyFileForSameAfsType(const AfsPath& afsSource, const StreamAttributes& attrSource, //throw FileError, ErrorFileLocked, X const AbstractPath& apTarget, bool copyFilePermissions, //accummulated delta != file size! consider ADS, sparse, compressed files - const zen::IOCallback& notifyUnbufferedIO /*throw X*/) const = 0; + const zen::IoCallback& notifyUnbufferedIO /*throw X*/) const = 0; //symlink handling: follow diff --git a/FreeFileSync/Source/afs/abstract_impl.h b/FreeFileSync/Source/afs/abstract_impl.h index e7ab071e..4ecc2614 100644 --- a/FreeFileSync/Source/afs/abstract_impl.h +++ b/FreeFileSync/Source/afs/abstract_impl.h @@ -28,9 +28,9 @@ std::wstring tryReportingDirError(Function cmd /*throw FileError*/, AbstractFile assert(!e.toString().empty()); switch (cb.reportDirError({e.toString(), std::chrono::steady_clock::now(), retryNumber})) //throw X { - case AbstractFileSystem::TraverserCallback::ON_ERROR_CONTINUE: + case AbstractFileSystem::TraverserCallback::HandleError::ignore: return e.toString(); - case AbstractFileSystem::TraverserCallback::ON_ERROR_RETRY: + case AbstractFileSystem::TraverserCallback::HandleError::retry: break; //continue with loop } } @@ -49,9 +49,9 @@ bool tryReportingItemError(Command cmd, AbstractFileSystem::TraverserCallback& c { switch (callback.reportItemError({e.toString(), std::chrono::steady_clock::now(), retryNumber}, itemName)) //throw X { - case AbstractFileSystem::TraverserCallback::ON_ERROR_RETRY: + case AbstractFileSystem::TraverserCallback::HandleError::retry: break; - case AbstractFileSystem::TraverserCallback::ON_ERROR_CONTINUE: + case AbstractFileSystem::TraverserCallback::HandleError::ignore: return false; } } @@ -197,8 +197,8 @@ private: std::condition_variable conditionBytesWritten_; std::condition_variable conditionBytesRead_; - std::atomic totalBytesWritten_{ 0 }; //std:atomic is uninitialized by default! - std::atomic totalBytesRead_ { 0 }; // + std::atomic totalBytesWritten_{0}; //std:atomic is uninitialized by default! + std::atomic totalBytesRead_ {0}; // }; //========================================================================================== diff --git a/FreeFileSync/Source/afs/ftp.cpp b/FreeFileSync/Source/afs/ftp.cpp index 88afa144..0685066f 100644 --- a/FreeFileSync/Source/afs/ftp.cpp +++ b/FreeFileSync/Source/afs/ftp.cpp @@ -93,7 +93,7 @@ Zstring ansiToUtfEncoding(const std::string& str) //throw SysError throw SysError(formatGlibError("g_convert(" + utfTo(str) + ')', error)); ZEN_ON_SCOPE_EXIT(::g_free(utfStr)); - return { utfStr, bytesWritten }; + return {utfStr, bytesWritten}; } @@ -117,7 +117,7 @@ std::string utfToAnsiEncoding(const Zstring& str) //throw SysError throw SysError(formatGlibError("g_convert(" + utfTo(str) + ')', error)); ZEN_ON_SCOPE_EXIT(::g_free(ansiStr)); - return { ansiStr, bytesWritten }; + return {ansiStr, bytesWritten}; } @@ -494,8 +494,8 @@ public: return perform(AfsPath(), true /*isDir*/, CURLFTPMETHOD_NOCWD /*avoid needless CWDs*/, { - { CURLOPT_NOBODY, 1L }, - { CURLOPT_QUOTE, quote }, + {CURLOPT_NOBODY, 1L}, + {CURLOPT_QUOTE, quote}, }, requiresUtf8, timeoutSec); //throw SysError } @@ -510,7 +510,7 @@ public: => are there servers supporting neither FEAT nor HELP? only time will tell... ... and it tells! FUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU https://freefilesync.org/forum/viewtopic.php?t=8041 */ - //=> * to the rescue: as long as we get an FTP response, *any* FTP response, including 550, the connection itself is fine! + //=> * to the rescue: as long as we get an FTP response - *any* FTP response (including 550) - the connection itself is fine! const std::string& featBuf = runSingleFtpCommand("*FEAT", false /*requiresUtf8*/, timeoutSec); //throw SysError for (const std::string& line : splitFtpResponse(featBuf)) @@ -563,7 +563,7 @@ public: ++it; //skip double quote else { - const std::string homePathRaw = replaceCpy({ itBegin, it }, "\"\"", '"'); + const std::string homePathRaw = replaceCpy(std::string{itBegin, it}, "\"\"", '"'); const ServerEncoding enc = getServerEncoding(timeoutSec); //throw SysError const Zstring homePathUtf = serverToUtfEncoding(homePathRaw, enc); //throw SysError return sanitizeDeviceRelativePath(homePathUtf); @@ -917,6 +917,7 @@ struct FtpItem Zstring itemName; uint64_t fileSize = 0; time_t modTime = 0; + AFS::FingerPrint filePrint = 0; //optional }; @@ -1023,8 +1024,8 @@ public: { std::vector options = { - { CURLOPT_WRITEDATA, &rawListing }, - { CURLOPT_WRITEFUNCTION, onBytesReceived }, + {CURLOPT_WRITEDATA, &rawListing}, + {CURLOPT_WRITEFUNCTION, onBytesReceived}, }; curl_ftpmethod pathMethod = CURLFTPMETHOD_SINGLECWD; @@ -1142,16 +1143,22 @@ private: } else if (startsWithAsciiNoCase(fact, "unique=")) { - warn_static("reevaluate: https://tools.ietf.org/html/rfc3659#section-7.5.2") - //note: as far as the RFC goes, the "unique" fact is not required to act like a persistent file id! - auto fileId - /*item.fileId */= afterFirst(fact, '=', IfNotFoundReturn::none); + /* https://tools.ietf.org/html/rfc3659#section-7.5.2 + "The mapping between files, and unique fact tokens should be maintained, [...] for + *at least* the lifetime of the control connection from user-PI to server-PI." + + => not necessarily *persistent* as far as the RFC goes! + BUT: practially this will be the inode ID/file index, so we can assume persistence */ + const std::string uniqueId = afterFirst(fact, '=', IfNotFoundReturn::none); + assert(!uniqueId.empty()); + item.filePrint = hashArray(uniqueId.begin(), uniqueId.end()); + //other metadata to hash e.g. create fact? => not available on Linux-hosted FTP! } if (equalAsciiNoCase(typeFact, "cdir")) - return { AFS::ItemType::folder, Zstr("."), 0, 0 }; + return {AFS::ItemType::folder, Zstr("."), 0, 0}; if (equalAsciiNoCase(typeFact, "pdir")) - return { AFS::ItemType::folder, Zstr(".."), 0, 0 }; + return {AFS::ItemType::folder, Zstr(".."), 0, 0}; if (equalAsciiNoCase(typeFact, "dir")) item.type = AFS::ItemType::folder; @@ -1238,33 +1245,33 @@ private: static FtpItem parseUnixLine(const std::string& rawLine, time_t utcTimeNow, int utcCurrentYear, bool haveGroup, ServerEncoding enc) //throw SysError { - try - { - FtpLineParser parser(rawLine); - /* total 4953 <- optional first line - drwxr-xr-x 1 root root 4096 Jan 10 11:58 version - -rwxr-xr-x 1 root root 1084 Sep 2 01:17 Unit Test.vcxproj.user - -rwxr-xr-x 1 1000 300 2217 Feb 28 2016 win32.manifest - lrwxr-xr-x 1 root root 18 Apr 26 15:17 Projects -> /mnt/hgfs/Projects + /* total 4953 <- optional first line + drwxr-xr-x 1 root root 4096 Jan 10 11:58 version + -rwxr-xr-x 1 root root 1084 Sep 2 01:17 Unit Test.vcxproj.user + -rwxr-xr-x 1 1000 300 2217 Feb 28 2016 win32.manifest + lrwxr-xr-x 1 root root 18 Apr 26 15:17 Projects -> /mnt/hgfs/Projects - file type: -:file l:symlink d:directory b:block device p:named pipe c:char device s:socket + file type: -:file l:symlink d:directory b:block device p:named pipe c:char device s:socket - permissions: (r|-)(w|-)(x|s|S|-) user - (r|-)(w|-)(x|s|S|-) group s := S + x S = Setgid - (r|-)(w|-)(x|t|T|-) others t := T + x T = sticky bit + permissions: (r|-)(w|-)(x|s|S|-) user + (r|-)(w|-)(x|s|S|-) group s := S + x S = Setgid + (r|-)(w|-)(x|t|T|-) others t := T + x T = sticky bit - Alternative formats: - Unix, no group ("ls -alG") https://freefilesync.org/forum/viewtopic.php?t=4306 - dr-xr-xr-x 2 root 512 Apr 8 1994 etc + Alternative formats: + Unix, no group ("ls -alG") https://freefilesync.org/forum/viewtopic.php?t=4306 + dr-xr-xr-x 2 root 512 Apr 8 1994 etc - Yet to be seen in the wild: - Netware: - d [R----F--] supervisor 512 Jan 16 18:53 login - - [R----F--] rhesus 214059 Oct 20 15:27 cx.exe + Yet to be seen in the wild: + Netware: + d [R----F--] supervisor 512 Jan 16 18:53 login + - [R----F--] rhesus 214059 Oct 20 15:27 cx.exe - NetPresenz for the Mac: - -------r-- 326 1391972 1392298 Nov 22 1995 MegaPhone.sit - drwxrwxr-x folder 2 May 10 1996 network */ + NetPresenz for the Mac: + -------r-- 326 1391972 1392298 Nov 22 1995 MegaPhone.sit + drwxrwxr-x folder 2 May 10 1996 network */ + try + { + FtpLineParser parser(rawLine); const std::string typeTag = parser.readRange(1, [](char c) //throw SysError { @@ -1300,7 +1307,7 @@ private: const std::string monthStr = parser.readRange(std::not_fn(isWhiteSpace)); //throw SysError parser.readRange(&isWhiteSpace); //throw SysError - const char* months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + const char* months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; auto itMonth = std::find_if(std::begin(months), std::end(months), [&](const char* name) { return equalAsciiNoCase(name, monthStr); }); if (itMonth == std::end(months)) throw SysError(L"Failed to parse month name."); @@ -1344,8 +1351,8 @@ private: else throw SysError(L"Failed to parse file time."); - warn_static("reevaluate: can't we detect the server offset via MDTM!?") //let's pretend the time listing is UTC (same behavior as FileZilla): hopefully MLSD will make this mess obsolete soon... + // => find exact offset with some MDTM hackery? yes, could do that, but this doesn't solve the bigger problem of imprecise LIST file times, so why bother? time_t utcTime = utcToTimeT(timeComp); //returns -1 on error if (utcTime == -1) { @@ -1366,7 +1373,7 @@ private: throw SysError(L"Item name not available."); if (itemName == "." || itemName == "..") //sometimes returned, e.g. by freefilesync.org - return { AFS::ItemType::folder, utfTo(itemName), 0, 0 }; + return {AFS::ItemType::folder, utfTo(itemName), 0, 0}; //------------------------------------------------------------------------------------ FtpItem item; if (typeTag == "d") @@ -1391,29 +1398,28 @@ private: //"dir" static std::vector parseWindows(const std::string& buf, ServerEncoding enc) //throw SysError { - /* - Test server: test.rebex.net username:demo pw:password useTls = true + /* Test server: test.rebex.net username:demo pw:password useTls = true + + listing supported by libcurl (US server) + 10-27-15 03:46AM pub + 04-08-14 03:09PM 11,399 readme.txt - listing supported by libcurl (US server) - 10-27-15 03:46AM pub - 04-08-14 03:09PM 11,399 readme.txt + Datalogic Windows CE 5.0 + 01-01-98 13:00 Storage Card - Datalogic Windows CE 5.0 - 01-01-98 13:00 Storage Card + IIS option "four-digit years" + 06-22-2017 04:25PM test + 06-20-2017 12:50PM 1875499 zstring.obj - IIS option "four-digit years" - 06-22-2017 04:25PM test - 06-20-2017 12:50PM 1875499 zstring.obj + Alternative formats (yet to be seen in the wild) + "dir" on Windows, US: + 10/27/2015 03:46 AM pub + 04/08/2014 03:09 PM 11,399 readme.txt - Alternative formats (yet to be seen in the wild) - "dir" on Windows, US: - 10/27/2015 03:46 AM pub - 04/08/2014 03:09 PM 11,399 readme.txt + "dir" on Windows, German: + 21.09.2016 18:31 Favorites + 12.01.2017 19:57 11.399 gsview64.ini */ - "dir" on Windows, German: - 21.09.2016 18:31 Favorites - 12.01.2017 19:57 11.399 gsview64.ini - */ const TimeComp tc = getUtcTime(); if (tc == TimeComp()) throw SysError(L"Failed to determine current time: " + numberTo(std::time(nullptr))); @@ -1473,8 +1479,8 @@ private: timeComp.day = day; timeComp.hour = hour; timeComp.minute = minute; - warn_static("reevaluate: can't we detect the server offset via MDTM!?") //let's pretend the time listing is UTC (same behavior as FileZilla): hopefully MLSD will make this mess obsolete soon... + // => find exact offset with some MDTM hackery? yes, could do that, but this doesn't solve the bigger problem of imprecise LIST file times, so why bother? time_t utcTime = utcToTimeT(timeComp); //returns -1 on error if (utcTime == -1) { @@ -1561,18 +1567,18 @@ private: switch (item.type) { case AFS::ItemType::file: - cb.onFile({ item.itemName, item.fileSize, item.modTime, AFS::FileId(), false /*isFollowedSymlink*/ }); //throw X + cb.onFile({item.itemName, item.fileSize, item.modTime, item.filePrint, false /*isFollowedSymlink*/}); //throw X break; case AFS::ItemType::folder: - if (std::shared_ptr cbSub = cb.onFolder({ item.itemName, false /*isFollowedSymlink*/ })) //throw X - workload_.push_back({ itemPath, std::move(cbSub) }); + if (std::shared_ptr cbSub = cb.onFolder({item.itemName, false /*isFollowedSymlink*/})) //throw X + workload_.push_back({itemPath, std::move(cbSub)}); break; case AFS::ItemType::symlink: - switch (cb.onSymlink({ item.itemName, item.modTime })) //throw X + switch (cb.onSymlink({item.itemName, item.modTime})) //throw X { - case AFS::TraverserCallback::LINK_FOLLOW: + case AFS::TraverserCallback::HandleLink::follow: { FtpItem target = {}; if (!tryReportingItemError([&] //throw X @@ -1583,15 +1589,15 @@ private: if (target.type == AFS::ItemType::folder) { - if (std::shared_ptr cbSub = cb.onFolder({ item.itemName, true /*isFollowedSymlink*/ })) //throw X - workload_.push_back({ itemPath, std::move(cbSub) }); + if (std::shared_ptr cbSub = cb.onFolder({item.itemName, true /*isFollowedSymlink*/})) //throw X + workload_.push_back({itemPath, std::move(cbSub)}); } else //a file or named pipe, etc. - cb.onFile({ item.itemName, target.fileSize, target.modTime, AFS::FileId(), true /*isFollowedSymlink*/ }); //throw X + cb.onFile({item.itemName, target.fileSize, target.modTime, item.filePrint, true /*isFollowedSymlink*/}); //throw X } break; - case AFS::TraverserCallback::LINK_SKIP: + case AFS::TraverserCallback::HandleLink::skip: break; } break; @@ -1643,9 +1649,9 @@ void ftpFileDownload(const FtpLogin& login, const AfsPath& afsFilePath, //throw { session.perform(afsFilePath, false /*isDir*/, CURLFTPMETHOD_NOCWD, //are there any servers that require CURLFTPMETHOD_SINGLECWD? let's find out { - { CURLOPT_WRITEDATA, &onBytesReceived }, - { CURLOPT_WRITEFUNCTION, onBytesReceivedWrapper }, - { CURLOPT_IGNORE_CONTENT_LENGTH, 1L }, //skip FTP "SIZE" command before download (=> download until actual EOF if file size changes) + {CURLOPT_WRITEDATA, &onBytesReceived}, + {CURLOPT_WRITEFUNCTION, onBytesReceivedWrapper}, + {CURLOPT_IGNORE_CONTENT_LENGTH, 1L}, //skip FTP "SIZE" command before download (=> download until actual EOF if file size changes) }, true /*requiresUtf8*/, login.timeoutSec); //throw SysError }); } @@ -1706,15 +1712,15 @@ void ftpFileUpload(const FtpLogin& login, const AfsPath& afsFilePath, //throw Fi */ session.perform(afsFilePath, false /*isDir*/, CURLFTPMETHOD_NOCWD, //are there any servers that require CURLFTPMETHOD_SINGLECWD? let's find out { - { CURLOPT_UPLOAD, 1L }, - { CURLOPT_READDATA, &getBytesToSend }, - { CURLOPT_READFUNCTION, getBytesToSendWrapper }, + {CURLOPT_UPLOAD, 1L}, + {CURLOPT_READDATA, &getBytesToSend}, + {CURLOPT_READFUNCTION, getBytesToSendWrapper}, - //{ CURLOPT_INFILESIZE_LARGE, static_cast(inputBuffer.size()) }, + //{CURLOPT_INFILESIZE_LARGE, static_cast(inputBuffer.size())}, //=> CURLOPT_INFILESIZE_LARGE does not issue a specific FTP command, but is used by libcurl only! - //{ CURLOPT_PREQUOTE, quote }, - //{ CURLOPT_POSTQUOTE, quote }, + //{CURLOPT_PREQUOTE, quote}, + //{CURLOPT_POSTQUOTE, quote}, }, true /*requiresUtf8*/, login.timeoutSec); //throw SysError }); } @@ -1733,7 +1739,7 @@ struct InputStreamFtp : public AFS::InputStream { InputStreamFtp(const FtpLogin& login, const AfsPath& afsPath, - const IOCallback& notifyUnbufferedIO /*throw X*/) : + const IoCallback& notifyUnbufferedIO /*throw X*/) : notifyUnbufferedIO_(notifyUnbufferedIO) { worker_ = InterruptibleThread([asyncStreamOut = this->asyncStreamIn_, login, afsPath] @@ -1784,7 +1790,7 @@ private: totalBytesReported_ = totalBytesDownloaded; } - const IOCallback notifyUnbufferedIO_; //throw X + const IoCallback notifyUnbufferedIO_; //throw X int64_t totalBytesReported_ = 0; std::shared_ptr asyncStreamIn_ = std::make_shared(FTP_STREAM_BUFFER_SIZE); InterruptibleThread worker_; @@ -1799,7 +1805,7 @@ struct OutputStreamFtp : public AFS::OutputStreamImpl OutputStreamFtp(const FtpLogin& login, const AfsPath& afsPath, std::optional modTime, - const IOCallback& notifyUnbufferedIO /*throw X*/) : + const IoCallback& notifyUnbufferedIO /*throw X*/) : login_(login), afsPath_(afsPath), modTime_(modTime), @@ -1861,7 +1867,7 @@ struct OutputStreamFtp : public AFS::OutputStreamImpl futUploadDone_.get(); //throw FileError AFS::FinalizeResult result; - //result.fileId = ... -> not supported by FTP + //result.filePrint = ... -> yet unknown at this point try { setModTimeIfAvailable(); //throw FileError, follows symlinks @@ -1910,7 +1916,7 @@ private: const FtpLogin login_; const AfsPath afsPath_; const std::optional modTime_; - const IOCallback notifyUnbufferedIO_; //throw X + const IoCallback notifyUnbufferedIO_; //throw X int64_t totalBytesReported_ = 0; std::shared_ptr asyncStreamOut_ = std::make_shared(FTP_STREAM_BUFFER_SIZE); InterruptibleThread worker_; @@ -2110,7 +2116,7 @@ private: //---------------------------------------------------------------------------------------------------------------- //return value always bound: - std::unique_ptr getInputStream(const AfsPath& afsPath, const IOCallback& notifyUnbufferedIO /*throw X*/) const override //throw FileError, (ErrorFileLocked) + std::unique_ptr getInputStream(const AfsPath& afsPath, const IoCallback& notifyUnbufferedIO /*throw X*/) const override //throw FileError, (ErrorFileLocked) { return std::make_unique(login_, afsPath, notifyUnbufferedIO); } @@ -2120,7 +2126,7 @@ private: std::unique_ptr getOutputStream(const AfsPath& afsPath, //throw FileError std::optional streamSize, std::optional modTime, - const IOCallback& notifyUnbufferedIO /*throw X*/) const override + const IoCallback& notifyUnbufferedIO /*throw X*/) const override { /* most FTP servers overwrite, but some (e.g. IIS) can be configured to fail, others (pureFTP) can be configured to auto-rename: https://download.pureftpd.org/pub/pure-ftpd/doc/README @@ -2140,7 +2146,7 @@ private: //symlink handling: follow //already existing: undefined behavior! (e.g. fail/overwrite/auto-rename) FileCopyResult copyFileForSameAfsType(const AfsPath& afsSource, const StreamAttributes& attrSource, //throw FileError, (ErrorFileLocked), X - const AbstractPath& apTarget, bool copyFilePermissions, const IOCallback& notifyUnbufferedIO /*throw X*/) const override + const AbstractPath& apTarget, bool copyFilePermissions, const IoCallback& notifyUnbufferedIO /*throw X*/) const override { //no native FTP file copy => use stream-based file copy: if (copyFilePermissions) @@ -2195,8 +2201,8 @@ private: session.perform(AfsPath(), true /*isDir*/, CURLFTPMETHOD_NOCWD, //avoid needless CWDs { - { CURLOPT_NOBODY, 1L }, - { CURLOPT_QUOTE, quote }, + {CURLOPT_NOBODY, 1L}, + {CURLOPT_QUOTE, quote}, }, true /*requiresUtf8*/, login_.timeoutSec); //throw SysError }); } @@ -2361,7 +2367,7 @@ AbstractPath fff::createItemPathFtp(const Zstring& itemPathPhrase) //noexcept auto it = std::find_if(fullPath.begin(), fullPath.end(), [](Zchar c) { return c == '/' || c == '\\'; }); const Zstring serverPort(fullPath.begin(), it); - const AfsPath serverRelPath = sanitizeDeviceRelativePath({ it, fullPath.end() }); + const AfsPath serverRelPath = sanitizeDeviceRelativePath({it, fullPath.end()}); login.server = beforeLast(serverPort, Zstr(':'), IfNotFoundReturn::all); const Zstring port = afterLast(serverPort, Zstr(':'), IfNotFoundReturn::none); diff --git a/FreeFileSync/Source/afs/gdrive.cpp b/FreeFileSync/Source/afs/gdrive.cpp index b731477b..863fad43 100644 --- a/FreeFileSync/Source/afs/gdrive.cpp +++ b/FreeFileSync/Source/afs/gdrive.cpp @@ -169,6 +169,14 @@ std::wstring formatGdriveErrorRaw(std::string serverResponse) return utfTo(serverResponse); } + +AFS::FingerPrint getGdriveFilePrint(const std::string& itemId) +{ + assert(!itemId.empty()); + //Google Drive item ID is persistent and globally unique! :) + return hashArray(itemId.begin(), itemId.end()); +} + //---------------------------------------------------------------------------------------------------------------- constinit2 Global httpSessionCount; @@ -180,7 +188,8 @@ UniInitializer startupInitHttp(*httpSessionCount.get()); class HttpSessionManager //reuse (healthy) HTTP sessions globally { public: - explicit HttpSessionManager(const Zstring& caCertFilePath) : caCertFilePath_(caCertFilePath), + explicit HttpSessionManager(const Zstring& caCertFilePath) : + caCertFilePath_(caCertFilePath), sessionCleaner_([this] { setCurrentThreadName(Zstr("Session Cleaner[HTTP]")); @@ -317,7 +326,7 @@ HttpSession::Result gdriveHttpsRequest(const std::string& serverRelPath, //throw { //https://developers.google.com/drive/api/v3/performance //"In order to receive a gzip-encoded response you must do two things: Set an Accept-Encoding header, ["gzip" automatically set by HttpSession] - { CURLOPT_USERAGENT, "FreeFileSync (gzip)" }, //and modify your user agent to contain the string gzip." + {CURLOPT_USERAGENT, "FreeFileSync (gzip)"}, //and modify your user agent to contain the string gzip." }; append(options, extraOptions); @@ -338,10 +347,10 @@ GdriveUser getGdriveUser(const std::string& accessToken) //throw SysError //https://developers.google.com/drive/api/v3/reference/about const std::string& queryParams = xWwwFormUrlEncode( { - { "fields", "user/displayName,user/emailAddress" }, + {"fields", "user/displayName,user/emailAddress"}, }); std::string response; - gdriveHttpsRequest("/drive/v3/about?" + queryParams, { "Authorization: Bearer " + accessToken }, {} /*extraOptions*/, //throw SysError + gdriveHttpsRequest("/drive/v3/about?" + queryParams, {"Authorization: Bearer " + accessToken}, {} /*extraOptions*/, //throw SysError [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast(buffer), bytesToWrite); }, nullptr /*readRequest*/); JsonValue jresponse; @@ -353,7 +362,7 @@ GdriveUser getGdriveUser(const std::string& accessToken) //throw SysError const std::optional displayName = getPrimitiveFromJsonObject(*user, "displayName"); const std::optional email = getPrimitiveFromJsonObject(*user, "emailAddress"); if (displayName && email) - return { utfTo(*displayName), *email }; + return {utfTo(*displayName), *email}; } throw SysError(formatGdriveErrorRaw(response)); @@ -385,15 +394,15 @@ GdriveAccessInfo gdriveExchangeAuthCode(const GdriveAuthCode& authCode) //throw //https://developers.google.com/identity/protocols/OAuth2InstalledApp#exchange-authorization-code const std::string postBuf = xWwwFormUrlEncode( { - { "code", authCode.code }, - { "client_id", getGdriveClientId() }, - { "client_secret", getGdriveClientSecret() }, - { "redirect_uri", authCode.redirectUrl }, - { "grant_type", "authorization_code" }, - { "code_verifier", authCode.codeChallenge }, + {"code", authCode.code}, + {"client_id", getGdriveClientId()}, + {"client_secret", getGdriveClientSecret()}, + {"redirect_uri", authCode.redirectUrl}, + {"grant_type", "authorization_code"}, + {"code_verifier", authCode.codeChallenge}, }); std::string response; - gdriveHttpsRequest("/oauth2/v4/token", {} /*extraHeaders*/, { { CURLOPT_POSTFIELDS, postBuf.c_str() } }, //throw SysError + gdriveHttpsRequest("/oauth2/v4/token", {} /*extraHeaders*/, {{ CURLOPT_POSTFIELDS, postBuf.c_str()}}, //throw SysError [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast(buffer), bytesToWrite); }, nullptr /*readRequest*/); JsonValue jresponse; @@ -408,7 +417,7 @@ GdriveAccessInfo gdriveExchangeAuthCode(const GdriveAuthCode& authCode) //throw const GdriveUser userInfo = getGdriveUser(*accessToken); //throw SysError - return { { *accessToken, std::time(nullptr) + stringTo(*expiresIn) }, *refreshToken, userInfo }; + return {{*accessToken, std::time(nullptr) + stringTo(*expiresIn)}, *refreshToken, userInfo}; } @@ -426,9 +435,9 @@ GdriveAccessInfo gdriveAuthorizeAccess(const std::string& gdriveLoginHint, const ZEN_ON_SCOPE_EXIT(if (servinfo) ::freeaddrinfo(servinfo)); //ServiceName == "0" => open the next best free port - const int rcGai = ::getaddrinfo(nullptr, //_In_opt_ PCSTR pNodeName, - "0", //_In_opt_ PCSTR pServiceName, - &hints, //_In_opt_ const ADDRINFOA* pHints, + const int rcGai = ::getaddrinfo(nullptr, //_In_opt_ PCSTR pNodeName + "0", //_In_opt_ PCSTR pServiceName + &hints, //_In_opt_ const ADDRINFOA* pHints &servinfo); //_Outptr_ PADDRINFOA* ppResult if (rcGai != 0) throw SysError(formatSystemError("getaddrinfo", replaceCpy(_("Error code %x"), L"%x", numberTo(rcGai)), utfTo(::gai_strerror(rcGai)))); @@ -493,13 +502,13 @@ GdriveAccessInfo gdriveAuthorizeAccess(const std::string& gdriveLoginHint, const //authenticate Google Drive via browser: https://developers.google.com/identity/protocols/OAuth2InstalledApp#step-2-send-a-request-to-googles-oauth-20-server const std::string oauthUrl = "https://accounts.google.com/o/oauth2/v2/auth?" + xWwwFormUrlEncode( { - { "client_id", getGdriveClientId() }, - { "redirect_uri", redirectUrl }, - { "response_type", "code" }, - { "scope", "https://www.googleapis.com/auth/drive" }, - { "code_challenge", codeChallenge }, - { "code_challenge_method", "plain" }, - { "login_hint", gdriveLoginHint }, + {"client_id", getGdriveClientId()}, + {"redirect_uri", redirectUrl}, + {"response_type", "code"}, + {"scope", "https://www.googleapis.com/auth/drive"}, + {"code_challenge", codeChallenge}, + {"code_challenge_method", "plain"}, + {"login_hint", gdriveLoginHint}, }); try { @@ -519,7 +528,7 @@ GdriveAccessInfo gdriveAuthorizeAccess(const std::string& gdriveLoginHint, const FD_SET(socket, &rfd); fd_set* readfds = &rfd; - struct ::timeval tv = {}; + timeval tv = {}; tv.tv_usec = static_cast(100 /*ms*/) * 1000; //WSAPoll broken, even ::poll() on OS X? https://daniel.haxx.se/blog/2012/10/10/wsapoll-is-broken/ @@ -613,7 +622,7 @@ GdriveAccessInfo gdriveAuthorizeAccess(const std::string& gdriveLoginHint, const //do as many login-related tasks as possible while we have the browser as an error output device! //see AFS::connectNetworkFolder() => errors will be lost after time out in dir_exist_async.h! - authResult = gdriveExchangeAuthCode({ code, redirectUrl, codeChallenge }); //throw SysError + authResult = gdriveExchangeAuthCode({code, redirectUrl, codeChallenge}); //throw SysError replace(htmlMsg, "TITLE_PLACEHOLDER", utfTo(_("Authentication completed."))); replace(htmlMsg, "MESSAGE_PLACEHOLDER", utfTo(_("You may close this page now and continue with FreeFileSync."))); } @@ -651,14 +660,14 @@ GdriveAccessToken gdriveRefreshAccess(const std::string& refreshToken) //throw S //https://developers.google.com/identity/protocols/OAuth2InstalledApp#offline const std::string postBuf = xWwwFormUrlEncode( { - { "refresh_token", refreshToken }, - { "client_id", getGdriveClientId() }, - { "client_secret", getGdriveClientSecret() }, - { "grant_type", "refresh_token" }, + {"refresh_token", refreshToken}, + {"client_id", getGdriveClientId()}, + {"client_secret", getGdriveClientSecret()}, + {"grant_type", "refresh_token"}, }); std::string response; - gdriveHttpsRequest("/oauth2/v4/token", {} /*extraHeaders*/, { { CURLOPT_POSTFIELDS, postBuf.c_str() } }, //throw SysError + gdriveHttpsRequest("/oauth2/v4/token", {} /*extraHeaders*/, {{CURLOPT_POSTFIELDS, postBuf.c_str()}}, //throw SysError [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast(buffer), bytesToWrite); }, nullptr /*readRequest*/); JsonValue jresponse; @@ -670,7 +679,7 @@ GdriveAccessToken gdriveRefreshAccess(const std::string& refreshToken) //throw S if (!accessToken || !expiresIn) throw SysError(formatGdriveErrorRaw(response)); - return { *accessToken, std::time(nullptr) + stringTo(*expiresIn) }; + return {*accessToken, std::time(nullptr) + stringTo(*expiresIn)}; } @@ -686,7 +695,7 @@ void gdriveRevokeAccess(const std::string& accessToken) //throw SysError mgr->access(HttpSessionId(Zstr("accounts.google.com")), [&](HttpSession& session) //throw SysError { - httpResult = session.perform("/o/oauth2/revoke?token=" + accessToken, { "Content-Type: application/x-www-form-urlencoded" }, {} /*extraOptions*/, + httpResult = session.perform("/o/oauth2/revoke?token=" + accessToken, {"Content-Type: application/x-www-form-urlencoded"}, {} /*extraOptions*/, [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast(buffer), bytesToWrite); }, nullptr /*readRequest*/); //throw SysError }); @@ -699,7 +708,7 @@ int64_t gdriveGetMyDriveFreeSpace(const std::string& accessToken) //throw SysErr { //https://developers.google.com/drive/api/v3/reference/about std::string response; - gdriveHttpsRequest("/drive/v3/about?fields=storageQuota", { "Authorization: Bearer " + accessToken }, {} /*extraOptions*/, //throw SysError + gdriveHttpsRequest("/drive/v3/about?fields=storageQuota", {"Authorization: Bearer " + accessToken}, {} /*extraOptions*/, //throw SysError [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast(buffer), bytesToWrite); }, nullptr /*readRequest*/); JsonValue jresponse; @@ -741,14 +750,14 @@ std::vector getSharedDrives(const std::string& accessToken) //thro { std::string queryParams = xWwwFormUrlEncode( { - { "pageSize", "100" }, //"[1, 100] Default: 10" - { "fields", "nextPageToken,drives(id,name)" }, + {"pageSize", "100"}, //"[1, 100] Default: 10" + {"fields", "nextPageToken,drives(id,name)"}, }); if (nextPageToken) - queryParams += '&' + xWwwFormUrlEncode({ { "pageToken", *nextPageToken } }); + queryParams += '&' + xWwwFormUrlEncode({{"pageToken", *nextPageToken}}); std::string response; - gdriveHttpsRequest("/drive/v3/drives?" + queryParams, { "Authorization: Bearer " + accessToken }, {} /*extraOptions*/, //throw SysError + gdriveHttpsRequest("/drive/v3/drives?" + queryParams, {"Authorization: Bearer " + accessToken}, {} /*extraOptions*/, //throw SysError [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast(buffer), bytesToWrite); }, nullptr /*readRequest*/); JsonValue jresponse; @@ -767,7 +776,7 @@ std::vector getSharedDrives(const std::string& accessToken) //thro if (!driveId || !driveName) throw SysError(formatGdriveErrorRaw(serializeJson(driveVal))); - sharedDrives.push_back({ std::move(*driveId), utfTo(*driveName) }); + sharedDrives.push_back({std::move(*driveId), utfTo(*driveName)}); } } while (nextPageToken); @@ -865,7 +874,7 @@ GdriveItemDetails extractItemDetails(JsonValue jvalue) //throw SysError //evaluate "targetMimeType" ? don't bother: "The MIME type of a shortcut can become stale"! } - return { utfTo(*itemName), fileSize, modTime, type, owner, std::move(targetId), std::move(parentIds) }; + return {utfTo(*itemName), fileSize, modTime, type, owner, std::move(targetId), std::move(parentIds)}; } @@ -874,11 +883,11 @@ GdriveItemDetails getItemDetails(const std::string& itemId, const std::string& a //https://developers.google.com/drive/api/v3/reference/files/get const std::string& queryParams = xWwwFormUrlEncode( { - { "fields", "trashed,name,mimeType,ownedByMe,size,modifiedTime,parents,shortcutDetails(targetId)" }, - { "supportsAllDrives", "true" }, + {"fields", "trashed,name,mimeType,ownedByMe,size,modifiedTime,parents,shortcutDetails(targetId)"}, + {"supportsAllDrives", "true"}, }); std::string response; - gdriveHttpsRequest("/drive/v3/files/" + itemId + '?' + queryParams, { "Authorization: Bearer " + accessToken }, {} /*extraOptions*/, //throw SysError + gdriveHttpsRequest("/drive/v3/files/" + itemId + '?' + queryParams, {"Authorization: Bearer " + accessToken}, {} /*extraOptions*/, //throw SysError [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast(buffer), bytesToWrite); }, nullptr /*readRequest*/); try { @@ -912,19 +921,19 @@ std::vector readFolderContent(const std::string& folderId, const std { std::string queryParams = xWwwFormUrlEncode( { - { "corpora", "allDrives" }, //"The 'user' corpus includes all files in "My Drive" and "Shared with me" https://developers.google.com/drive/api/v3/reference/files/list - { "includeItemsFromAllDrives", "true" }, - { "pageSize", "1000" }, //"[1, 1000] Default: 100" - { "q", "trashed=false and '" + folderId + "' in parents" }, - { "spaces", "drive" }, - { "supportsAllDrives", "true" }, - { "fields", "nextPageToken,incompleteSearch,files(id,name,mimeType,ownedByMe,size,modifiedTime,parents,shortcutDetails(targetId))" }, //https://developers.google.com/drive/api/v3/reference/files + {"corpora", "allDrives"}, //"The 'user' corpus includes all files in "My Drive" and "Shared with me" https://developers.google.com/drive/api/v3/reference/files/list + {"includeItemsFromAllDrives", "true"}, + {"pageSize", "1000"}, //"[1, 1000] Default: 100" + {"q", "trashed=false and '" + folderId + "' in parents"}, + {"spaces", "drive"}, + {"supportsAllDrives", "true"}, + {"fields", "nextPageToken,incompleteSearch,files(id,name,mimeType,ownedByMe,size,modifiedTime,parents,shortcutDetails(targetId))"}, //https://developers.google.com/drive/api/v3/reference/files }); if (nextPageToken) - queryParams += '&' + xWwwFormUrlEncode({ { "pageToken", *nextPageToken } }); + queryParams += '&' + xWwwFormUrlEncode({{"pageToken", *nextPageToken}}); std::string response; - gdriveHttpsRequest("/drive/v3/files?" + queryParams, { "Authorization: Bearer " + accessToken }, {} /*extraOptions*/, //throw SysError + gdriveHttpsRequest("/drive/v3/files?" + queryParams, {"Authorization: Bearer " + accessToken}, {} /*extraOptions*/, //throw SysError [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast(buffer), bytesToWrite); }, nullptr /*readRequest*/); JsonValue jresponse; @@ -946,7 +955,7 @@ std::vector readFolderContent(const std::string& folderId, const std GdriveItemDetails itemDetails(extractItemDetails(childVal)); //throw SysError assert(std::find(itemDetails.parentIds.begin(), itemDetails.parentIds.end(), folderId) != itemDetails.parentIds.end()); - childItems.push_back({ std::move(*itemId), std::move(itemDetails) }); + childItems.push_back({std::move(*itemId), std::move(itemDetails)}); } } while (nextPageToken); @@ -980,16 +989,16 @@ ChangesDelta getChangesDelta(const std::string& startPageToken, const std::strin { const std::string& queryParams = xWwwFormUrlEncode( { - { "pageToken", *nextPageToken }, - { "fields", "kind,nextPageToken,newStartPageToken,changes(kind,changeType,removed,fileId,file(trashed,name,mimeType,ownedByMe,size,modifiedTime,parents,shortcutDetails(targetId)),driveId,drive(name))" }, - { "includeItemsFromAllDrives", "true" }, - { "pageSize", "1000" }, //"[1, 1000] Default: 100" - { "spaces", "drive" }, - { "supportsAllDrives", "true" }, + {"pageToken", *nextPageToken}, + {"fields", "kind,nextPageToken,newStartPageToken,changes(kind,changeType,removed,fileId,file(trashed,name,mimeType,ownedByMe,size,modifiedTime,parents,shortcutDetails(targetId)),driveId,drive(name))"}, + {"includeItemsFromAllDrives", "true"}, + {"pageSize", "1000"}, //"[1, 1000] Default: 100" + {"spaces", "drive"}, + {"supportsAllDrives", "true"}, //do NOT "restrictToMyDrive": we're also interested in "Shared with me" items, which might be referenced by a shortcut in "My Drive" }); std::string response; - gdriveHttpsRequest("/drive/v3/changes?" + queryParams, { "Authorization: Bearer " + accessToken }, {} /*extraOptions*/, //throw SysError + gdriveHttpsRequest("/drive/v3/changes?" + queryParams, {"Authorization: Bearer " + accessToken}, {} /*extraOptions*/, //throw SysError [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast(buffer), bytesToWrite); }, nullptr /*readRequest*/); JsonValue jresponse; @@ -1076,10 +1085,10 @@ std::string /*startPageToken*/ getChangesCurrentToken(const std::string& accessT //https://developers.google.com/drive/api/v3/reference/changes/getStartPageToken const std::string& queryParams = xWwwFormUrlEncode( { - { "supportsAllDrives", "true" }, + {"supportsAllDrives", "true"}, }); std::string response; - gdriveHttpsRequest("/drive/v3/changes/startPageToken?" + queryParams, { "Authorization: Bearer " + accessToken }, {} /*extraOptions*/, //throw SysError + gdriveHttpsRequest("/drive/v3/changes/startPageToken?" + queryParams, {"Authorization: Bearer " + accessToken}, {} /*extraOptions*/, //throw SysError [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast(buffer), bytesToWrite); }, nullptr /*readRequest*/); JsonValue jresponse; @@ -1101,11 +1110,11 @@ void gdriveDeleteItem(const std::string& itemId, const std::string& accessToken) //https://developers.google.com/drive/api/v3/reference/files/delete const std::string& queryParams = xWwwFormUrlEncode( { - { "supportsAllDrives", "true" }, + {"supportsAllDrives", "true"}, }); std::string response; - const HttpSession::Result httpResult = gdriveHttpsRequest("/drive/v3/files/" + itemId + '?' + queryParams, { "Authorization: Bearer " + accessToken }, //throw SysError - { { CURLOPT_CUSTOMREQUEST, "DELETE" } }, [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast(buffer), bytesToWrite); }, nullptr /*readRequest*/); + const HttpSession::Result httpResult = gdriveHttpsRequest("/drive/v3/files/" + itemId + '?' + queryParams, {"Authorization: Bearer " + accessToken}, //throw SysError + {{CURLOPT_CUSTOMREQUEST, "DELETE"}}, [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast(buffer), bytesToWrite); }, nullptr /*readRequest*/); if (response.empty() && httpResult.statusCode == 204) return; //"If successful, this method returns an empty response body" @@ -1120,14 +1129,14 @@ void gdriveUnlinkParent(const std::string& itemId, const std::string& parentId, //https://developers.google.com/drive/api/v3/reference/files/update const std::string& queryParams = xWwwFormUrlEncode( { - { "removeParents", parentId }, - { "supportsAllDrives", "true" }, - { "fields", "id,parents" }, //for test if operation was successful + {"removeParents", parentId}, + {"supportsAllDrives", "true"}, + {"fields", "id,parents"}, //for test if operation was successful }); std::string response; const HttpSession::Result httpResult = gdriveHttpsRequest("/drive/v3/files/" + itemId + '?' + queryParams, //throw SysError - { "Authorization: Bearer " + accessToken, "Content-Type: application/json; charset=UTF-8" }, - { { CURLOPT_CUSTOMREQUEST, "PATCH" }, { CURLOPT_POSTFIELDS, "{}" } }, + {"Authorization: Bearer " + accessToken, "Content-Type: application/json; charset=UTF-8"}, + {{ CURLOPT_CUSTOMREQUEST, "PATCH"}, { CURLOPT_POSTFIELDS, "{}"}}, [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast(buffer), bytesToWrite); }, nullptr /*readRequest*/); if (response.empty() && httpResult.statusCode == 204) @@ -1157,14 +1166,14 @@ void gdriveMoveToTrash(const std::string& itemId, const std::string& accessToken //https://developers.google.com/drive/api/v3/reference/files/update const std::string& queryParams = xWwwFormUrlEncode( { - { "supportsAllDrives", "true" }, - { "fields", "trashed" }, + {"supportsAllDrives", "true"}, + {"fields", "trashed"}, }); const std::string postBuf = R"({ "trashed": true })"; std::string response; - gdriveHttpsRequest("/drive/v3/files/" + itemId + '?' + queryParams, { "Authorization: Bearer " + accessToken, "Content-Type: application/json; charset=UTF-8" }, //throw SysError - { { CURLOPT_CUSTOMREQUEST, "PATCH" }, { CURLOPT_POSTFIELDS, postBuf.c_str() } }, + gdriveHttpsRequest("/drive/v3/files/" + itemId + '?' + queryParams, {"Authorization: Bearer " + accessToken, "Content-Type: application/json; charset=UTF-8"}, //throw SysError + {{CURLOPT_CUSTOMREQUEST, "PATCH"}, {CURLOPT_POSTFIELDS, postBuf.c_str()}}, [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast(buffer), bytesToWrite); }, nullptr /*readRequest*/); JsonValue jresponse; @@ -1183,18 +1192,18 @@ std::string /*folderId*/ gdriveCreateFolderPlain(const Zstring& folderName, cons //https://developers.google.com/drive/api/v3/folder#creating_a_folder const std::string& queryParams = xWwwFormUrlEncode( { - { "supportsAllDrives", "true" }, - { "fields", "id" }, + {"supportsAllDrives", "true"}, + {"fields", "id"}, }); JsonValue postParams(JsonValue::Type::object); postParams.objectVal.emplace("mimeType", gdriveFolderMimeType); postParams.objectVal.emplace("name", utfTo(folderName)); - postParams.objectVal.emplace("parents", std::vector { JsonValue(parentId) }); + postParams.objectVal.emplace("parents", std::vector {JsonValue(parentId)}); const std::string& postBuf = serializeJson(postParams, "" /*lineBreak*/, "" /*indent*/); std::string response; - gdriveHttpsRequest("/drive/v3/files?" + queryParams, { "Authorization: Bearer " + accessToken, "Content-Type: application/json; charset=UTF-8" }, - { { CURLOPT_POSTFIELDS, postBuf.c_str() } }, + gdriveHttpsRequest("/drive/v3/files?" + queryParams, {"Authorization: Bearer " + accessToken, "Content-Type: application/json; charset=UTF-8"}, + {{CURLOPT_POSTFIELDS, postBuf.c_str()}}, [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast(buffer), bytesToWrite); }, nullptr /*readRequest*/); //throw SysError JsonValue jresponse; @@ -1216,8 +1225,8 @@ std::string /*shortcutId*/ gdriveCreateShortcutPlain(const Zstring& shortcutName - creating shortcuts to shortcuts fails with "Internal Error" */ const std::string& queryParams = xWwwFormUrlEncode( { - { "supportsAllDrives", "true" }, - { "fields", "id" }, + {"supportsAllDrives", "true"}, + {"fields", "id"}, }); JsonValue shortcutDetails(JsonValue::Type::object); shortcutDetails.objectVal.emplace("targetId", targetId); @@ -1225,13 +1234,13 @@ std::string /*shortcutId*/ gdriveCreateShortcutPlain(const Zstring& shortcutName JsonValue postParams(JsonValue::Type::object); postParams.objectVal.emplace("mimeType", gdriveShortcutMimeType); postParams.objectVal.emplace("name", utfTo(shortcutName)); - postParams.objectVal.emplace("parents", std::vector { JsonValue(parentId) }); + postParams.objectVal.emplace("parents", std::vector {JsonValue(parentId)}); postParams.objectVal.emplace("shortcutDetails", std::move(shortcutDetails)); const std::string& postBuf = serializeJson(postParams, "" /*lineBreak*/, "" /*indent*/); std::string response; - gdriveHttpsRequest("/drive/v3/files?" + queryParams, { "Authorization: Bearer " + accessToken, "Content-Type: application/json; charset=UTF-8" }, - { { CURLOPT_POSTFIELDS, postBuf.c_str() } }, + gdriveHttpsRequest("/drive/v3/files?" + queryParams, {"Authorization: Bearer " + accessToken, "Content-Type: application/json; charset=UTF-8"}, + {{CURLOPT_POSTFIELDS, postBuf.c_str()}}, [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast(buffer), bytesToWrite); }, nullptr /*readRequest*/); //throw SysError JsonValue jresponse; @@ -1252,8 +1261,8 @@ std::string /*fileId*/ gdriveCopyFile(const std::string& fileId, const std::stri //https://developers.google.com/drive/api/v3/reference/files/copy const std::string queryParams = xWwwFormUrlEncode( { - { "supportsAllDrives", "true" }, - { "fields", "id" }, + {"supportsAllDrives", "true"}, + {"fields", "id"}, }); //more Google Drive peculiarities: changing the file name changes modifiedTime!!! => workaround: @@ -1265,13 +1274,13 @@ std::string /*fileId*/ gdriveCopyFile(const std::string& fileId, const std::stri JsonValue postParams(JsonValue::Type::object); postParams.objectVal.emplace("name", utfTo(newName)); - postParams.objectVal.emplace("parents", std::vector { JsonValue(parentIdTo) }); + postParams.objectVal.emplace("parents", std::vector {JsonValue(parentIdTo)}); postParams.objectVal.emplace("modifiedTime", modTimeRfc); const std::string& postBuf = serializeJson(postParams, "" /*lineBreak*/, "" /*indent*/); std::string response; gdriveHttpsRequest("/drive/v3/files/" + fileId + "/copy?" + queryParams, //throw SysError - { "Authorization: Bearer " + accessToken, "Content-Type: application/json; charset=UTF-8" }, { { CURLOPT_POSTFIELDS, postBuf.c_str() } }, + {"Authorization: Bearer " + accessToken, "Content-Type: application/json; charset=UTF-8"}, {{CURLOPT_POSTFIELDS, postBuf.c_str()}}, [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast(buffer), bytesToWrite); }, nullptr /*readRequest*/); JsonValue jresponse; @@ -1294,15 +1303,15 @@ void gdriveMoveAndRenameItem(const std::string& itemId, const std::string& paren //https://developers.google.com/drive/api/v3/folder#moving_files_between_folders std::string queryParams = xWwwFormUrlEncode( { - { "supportsAllDrives", "true" }, - { "fields", "name,parents" }, //for test if operation was successful + {"supportsAllDrives", "true"}, + {"fields", "name,parents"}, //for test if operation was successful }); if (parentIdFrom != parentIdTo) queryParams += '&' + xWwwFormUrlEncode( { - { "removeParents", parentIdFrom }, - { "addParents", parentIdTo }, + {"removeParents", parentIdFrom}, + {"addParents", parentIdTo}, }); //more Google Drive peculiarities: changing the file name changes modifiedTime!!! => workaround: @@ -1319,8 +1328,8 @@ void gdriveMoveAndRenameItem(const std::string& itemId, const std::string& paren std::string response; gdriveHttpsRequest("/drive/v3/files/" + itemId + '?' + queryParams, //throw SysError - { "Authorization: Bearer " + accessToken, "Content-Type: application/json; charset=UTF-8" }, - { { CURLOPT_CUSTOMREQUEST, "PATCH" }, { CURLOPT_POSTFIELDS, postBuf.c_str() } }, + {"Authorization: Bearer " + accessToken, "Content-Type: application/json; charset=UTF-8"}, + {{CURLOPT_CUSTOMREQUEST, "PATCH"}, {CURLOPT_POSTFIELDS, postBuf.c_str()}}, [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast(buffer), bytesToWrite); }, nullptr /*readRequest*/); JsonValue jresponse; @@ -1350,14 +1359,14 @@ void setModTime(const std::string& itemId, time_t modTime, const std::string& ac const std::string& queryParams = xWwwFormUrlEncode( { - { "supportsAllDrives", "true" }, - { "fields", "modifiedTime" }, + {"supportsAllDrives", "true"}, + {"fields", "modifiedTime"}, }); const std::string postBuf = R"({ "modifiedTime": ")" + modTimeRfc + "\" }"; std::string response; - gdriveHttpsRequest("/drive/v3/files/" + itemId + '?' + queryParams, { "Authorization: Bearer " + accessToken, "Content-Type: application/json; charset=UTF-8" }, //throw SysError - { { CURLOPT_CUSTOMREQUEST, "PATCH" }, { CURLOPT_POSTFIELDS, postBuf.c_str() } }, + gdriveHttpsRequest("/drive/v3/files/" + itemId + '?' + queryParams, {"Authorization: Bearer " + accessToken, "Content-Type: application/json; charset=UTF-8"}, //throw SysError + {{CURLOPT_CUSTOMREQUEST, "PATCH"}, {CURLOPT_POSTFIELDS, postBuf.c_str()}}, [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast(buffer), bytesToWrite); }, nullptr /*readRequest*/); JsonValue jresponse; @@ -1382,17 +1391,17 @@ void gdriveDownloadFileImpl(const std::string& fileId, const std::function support acknowledgeAbuse retry handling) @@ -1456,7 +1465,7 @@ std::string /*itemId*/ gdriveUploadSmallFile(const Zstring& fileName, const std: JsonValue postParams(JsonValue::Type::object); postParams.objectVal.emplace("name", utfTo(fileName)); - postParams.objectVal.emplace("parents", std::vector { JsonValue(parentId) }); + postParams.objectVal.emplace("parents", std::vector {JsonValue(parentId)}); if (modTime) //convert to RFC 3339 date-time: e.g. "2018-09-29T08:39:12.053Z" { const std::string& modTimeRfc = utfTo(formatTime(Zstr("%Y-%m-%dT%H:%M:%S.000Z"), getUtcTime(*modTime))); //returns empty string on failure @@ -1521,8 +1530,8 @@ TODO: const std::string& queryParams = xWwwFormUrlEncode( { - { "supportsAllDrives", "true" }, - { "uploadType", "multipart" }, + {"supportsAllDrives", "true"}, + {"uploadType", "multipart"}, }); std::string response; const HttpSession::Result httpResult = gdriveHttpsRequest("/upload/drive/v3/files?" + queryParams, //throw SysError, X @@ -1531,7 +1540,7 @@ TODO: "Content-Type: multipart/related; boundary=" + boundaryString, "Content-Length: " + numberTo(postBufHead.size() + streamSize + postBufTail.size()) }, - { { CURLOPT_POST, 1 } }, //otherwise HttpSession::perform() will PUT + {{CURLOPT_POST, 1}}, //otherwise HttpSession::perform() will PUT [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast(buffer), bytesToWrite); }, readMultipartBlock ); JsonValue jresponse; @@ -1561,12 +1570,12 @@ std::string /*itemId*/ gdriveUploadFile(const Zstring& fileName, const std::stri { const std::string& queryParams = xWwwFormUrlEncode( { - { "supportsAllDrives", "true" }, - { "uploadType", "resumable" }, + {"supportsAllDrives", "true"}, + {"uploadType", "resumable"}, }); JsonValue postParams(JsonValue::Type::object); postParams.objectVal.emplace("name", utfTo(fileName)); - postParams.objectVal.emplace("parents", std::vector { JsonValue(parentId) }); + postParams.objectVal.emplace("parents", std::vector {JsonValue(parentId)}); if (modTime) //convert to RFC 3339 date-time: e.g. "2018-09-29T08:39:12.053Z" { const std::string& modTimeRfc = utfTo(formatTime(Zstr("%Y-%m-%dT%H:%M:%S.000Z"), getUtcTime(*modTime))); //returns empty string on failure @@ -1601,8 +1610,8 @@ std::string /*itemId*/ gdriveUploadFile(const Zstring& fileName, const std::stri std::string response; const HttpSession::Result httpResult = gdriveHttpsRequest("/upload/drive/v3/files?" + queryParams, //throw SysError - { "Authorization: Bearer " + accessToken, "Content-Type: application/json; charset=UTF-8" }, - { { CURLOPT_POSTFIELDS, postBuf.c_str() }, { CURLOPT_HEADERDATA, &onBytesReceived }, { CURLOPT_HEADERFUNCTION, onBytesReceivedWrapper } }, + {"Authorization: Bearer " + accessToken, "Content-Type: application/json; charset=UTF-8"}, + {{CURLOPT_POSTFIELDS, postBuf.c_str()}, {CURLOPT_HEADERDATA, &onBytesReceived}, {CURLOPT_HEADERFUNCTION, onBytesReceivedWrapper}}, [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast(buffer), bytesToWrite); }, nullptr /*readRequest*/); if (httpResult.statusCode != 200) @@ -1644,11 +1653,11 @@ std::string /*itemId*/ getMyDriveId(const std::string& accessToken) //throw SysE //https://developers.google.com/drive/api/v3/reference/files/get const std::string& queryParams = xWwwFormUrlEncode( { - { "supportsAllDrives", "true" }, - { "fields", "id" }, + {"supportsAllDrives", "true"}, + {"fields", "id"}, }); std::string response; - gdriveHttpsRequest("/drive/v3/files/root?" + queryParams, { "Authorization: Bearer " + accessToken }, {} /*extraOptions*/, //throw SysError + gdriveHttpsRequest("/drive/v3/files/root?" + queryParams, {"Authorization: Bearer " + accessToken}, {} /*extraOptions*/, //throw SysError [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast(buffer), bytesToWrite); }, nullptr /*readRequest*/); JsonValue jresponse; @@ -1721,7 +1730,7 @@ class GdriveFileState //per-user-session! => serialize access (perf: amortized f { public: GdriveFileState(GdriveAccessBuffer& accessBuf) : //throw SysError - /* issue getChangesCurrentToken() as the very first Google Drive query!*/ + /* issue getChangesCurrentToken() as the very first Google Drive query! */ lastSyncToken_(getChangesCurrentToken(accessBuf.getAccessToken())), //throw SysError myDriveId_ (getMyDriveId (accessBuf.getAccessToken())), // accessBuf_(accessBuf) @@ -1863,20 +1872,20 @@ public: { if (itFound != sharedDrives_.end()) throw SysError(replaceCpy(_("Cannot find %x."), L"%x", - fmtPath(getGdriveDisplayPath({{ accessBuf_.getUserEmail(), sharedDriveName}, AfsPath() }))) + L' ' + + fmtPath(getGdriveDisplayPath({{accessBuf_.getUserEmail(), sharedDriveName}, AfsPath()}))) + L' ' + replaceCpy(_("The name %x is used by more than one item in the folder."), L"%x", fmtPath(sharedDriveName))); itFound = it; } if (itFound == sharedDrives_.end()) throw SysError(replaceCpy(_("Cannot find %x."), L"%x", - fmtPath(getGdriveDisplayPath({{ accessBuf_.getUserEmail(), sharedDriveName}, AfsPath() })))); + fmtPath(getGdriveDisplayPath({{accessBuf_.getUserEmail(), sharedDriveName}, AfsPath()})))); return itFound->first; }(); const std::vector relPath = split(afsPath.value, FILE_NAME_SEPARATOR, SplitOnEmpty::skip); if (relPath.empty()) - return { driveId, GdriveItemType::folder, AfsPath(), {} }; + return {driveId, GdriveItemType::folder, AfsPath(), {}}; else return getPathStatusSub(driveId, sharedDriveName, AfsPath(), relPath, followLeafShortcut); //throw SysError } @@ -1888,7 +1897,7 @@ public: return ps.existingItemId; const AfsPath afsPathMissingChild(nativeAppendPaths(ps.existingPath.value, ps.relPath.front())); - throw SysError(replaceCpy(_("Cannot find %x."), L"%x", fmtPath(getGdriveDisplayPath({ { accessBuf_.getUserEmail(), sharedDriveName }, afsPathMissingChild })))); + throw SysError(replaceCpy(_("Cannot find %x."), L"%x", fmtPath(getGdriveDisplayPath({{accessBuf_.getUserEmail(), sharedDriveName}, afsPathMissingChild})))); } std::pair getFileAttributes(const Zstring& sharedDriveName, const AfsPath& afsPath, bool followLeafShortcut) //throw SysError @@ -1903,7 +1912,7 @@ public: { rootDetails.itemName = Zstr("My Drive"); rootDetails.owner = FileOwner::me; - return { itemId, std::move(rootDetails) }; + return {itemId, std::move(rootDetails)}; } if (auto it = sharedDrives_.find(itemId); @@ -1911,7 +1920,7 @@ public: { rootDetails.itemName = it->second; rootDetails.owner = FileOwner::none; - return { itemId, std::move(rootDetails) }; + return {itemId, std::move(rootDetails)}; } } else if (auto it = itemDetails_.find(itemId); @@ -1940,7 +1949,7 @@ public: for (auto itChild : it->second.childItems) { const auto& [childId, childDetails] = *itChild; - childItems.push_back({ childId, childDetails }); + childItems.push_back({childId, childDetails}); } return std::move(childItems); //[!] need std::move! } @@ -2116,14 +2125,14 @@ private: { if (itFound != itemDetails_.end()) throw SysError(replaceCpy(_("Cannot find %x."), L"%x", - fmtPath(getGdriveDisplayPath({{ accessBuf_.getUserEmail(), sharedDriveName}, AfsPath(nativeAppendPaths(folderPath.value, relPath.front())) }))) + L' ' + + fmtPath(getGdriveDisplayPath({{accessBuf_.getUserEmail(), sharedDriveName}, AfsPath(nativeAppendPaths(folderPath.value, relPath.front()))}))) + L' ' + replaceCpy(_("The name %x is used by more than one item in the folder."), L"%x", fmtPath(relPath.front()))); itFound = itChild; } if (itFound == itemDetails_.end()) - return { folderId, GdriveItemType::folder, folderPath, relPath }; //always a folder, see check before recursion above + return {folderId, GdriveItemType::folder, folderPath, relPath}; //always a folder, see check before recursion above else { auto getItemDetailsBuffered = [&](const std::string& itemId) -> const GdriveItemDetails& @@ -2131,7 +2140,7 @@ private: auto it = itemDetails_.find(itemId); if (it == itemDetails_.end()) { - notifyItemUpdated(registerFileStateDelta(), { itemId, getItemDetails(itemId, accessBuf_.getAccessToken()) }); //throw SysError + notifyItemUpdated(registerFileStateDelta(), {itemId, getItemDetails(itemId, accessBuf_.getAccessToken())}); //throw SysError //perf: always buffered, except for direct, first-time folder access! it = itemDetails_.find(itemId); assert(it != itemDetails_.end()); @@ -2146,15 +2155,15 @@ private: if (childRelPath.empty()) { if (childDetails.type == GdriveItemType::shortcut && followLeafShortcut) - return { childDetails.targetId, getItemDetailsBuffered(childDetails.targetId).type, childItemPath, childRelPath }; + return {childDetails.targetId, getItemDetailsBuffered(childDetails.targetId).type, childItemPath, childRelPath}; else - return { childId, childDetails.type, childItemPath, childRelPath }; + return {childId, childDetails.type, childItemPath, childRelPath}; } switch (childDetails.type) { case GdriveItemType::file: //parent/file/child-rel-path... => obscure, but possible (and not an error) - return { childId, childDetails.type, childItemPath, childRelPath }; + return {childId, childDetails.type, childItemPath, childRelPath}; case GdriveItemType::folder: return getPathStatusSub(childId, sharedDriveName, childItemPath, childRelPath, followLeafShortcut); //throw SysError @@ -2163,14 +2172,14 @@ private: switch (getItemDetailsBuffered(childDetails.targetId).type) { case GdriveItemType::file: //parent/file-symlink/child-rel-path... => obscure, but possible (and not an error) - return { childDetails.targetId, GdriveItemType::file, childItemPath, childRelPath }; //resolve symlinks if in the *middle* of a path! + return {childDetails.targetId, GdriveItemType::file, childItemPath, childRelPath}; //resolve symlinks if in the *middle* of a path! case GdriveItemType::folder: //parent/folder-symlink/child-rel-path... => always follow return getPathStatusSub(childDetails.targetId, sharedDriveName, childItemPath, childRelPath, followLeafShortcut); //throw SysError case GdriveItemType::shortcut: //should never happen: creating shortcuts to shortcuts fails with "Internal Error" throw SysError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", - fmtPath(getGdriveDisplayPath({{ accessBuf_.getUserEmail(), sharedDriveName}, AfsPath(nativeAppendPaths(folderPath.value, relPath.front())) }))) + L' ' + + fmtPath(getGdriveDisplayPath({{accessBuf_.getUserEmail(), sharedDriveName}, AfsPath(nativeAppendPaths(folderPath.value, relPath.front()))}))) + L' ' + L"Google Drive Shortcut points to another Shortcut."); } break; @@ -2343,7 +2352,7 @@ public: { auto accessBuf = makeSharedRef(accessInfo); auto fileState = makeSharedRef(accessBuf.ref()); //throw SysError - userSession = { accessBuf, fileState }; + userSession = {accessBuf, fileState}; } }); @@ -2449,7 +2458,7 @@ public: useFileState(userSession->fileState.ref()); //throw X }); - return { accessToken, stateDelta }; + return {accessToken, stateDelta}; } private: @@ -2550,7 +2559,7 @@ private: makeSharedRef( accessBuf.ref()) : //throw SysError makeSharedRef(streamIn2, accessBuf.ref()); // - return UserSession{ accessBuf, fileState }; + return UserSession{accessBuf, fileState}; } else { @@ -2566,7 +2575,7 @@ private: auto accessBuf = makeSharedRef(streamInBody); //throw SysError auto fileState = makeSharedRef(streamInBody, accessBuf.ref()); //throw SysError - return UserSession{ accessBuf, fileState }; + return UserSession{accessBuf, fileState}; } } catch (const SysError& e) @@ -2647,7 +2656,7 @@ struct GetDirDetails if (item.details.itemName.empty()) throw SysError(L"Folder contains an item without name."); //mostly an issue for FFS's folder traversal, but NOT for globalGdriveSessions! - return { std::move(*childItemsBuf), folderPath_ }; + return {std::move(*childItemsBuf), folderPath_}; } catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(getGdriveDisplayPath(folderPath_))), e.toString()); } } @@ -2683,14 +2692,15 @@ struct GetShortcutTargetDetails //buffer new file state ASAP accessGlobalFileState(shortcutPath_.gdriveLogin.email, [&](GdriveFileState& fileState) //throw SysError { - fileState.notifyItemUpdated(aai.stateDelta, { shortcutDetails_.targetId, *targetDetailsBuf }); + fileState.notifyItemUpdated(aai.stateDelta, {shortcutDetails_.targetId, *targetDetailsBuf}); }); } + assert(targetDetailsBuf->targetId.empty()); if (targetDetailsBuf->type == GdriveItemType::shortcut) //should never happen: creating shortcuts to shortcuts fails with "Internal Error" throw SysError(L"Google Drive Shortcut points to another Shortcut."); - return { std::move(*targetDetailsBuf), shortcutDetails_, shortcutPath_ }; + return {std::move(*targetDetailsBuf), shortcutDetails_, shortcutPath_}; } catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(getGdriveDisplayPath(shortcutPath_))), e.toString()); } } @@ -2726,7 +2736,7 @@ private: void traverseWithException(const AfsPath& folderPath, AFS::TraverserCallback& cb) //throw FileError, X { - const std::vector& childItems = GetDirDetails({ gdriveLogin_, folderPath })().childItems; //throw FileError + const std::vector& childItems = GetDirDetails({gdriveLogin_, folderPath})().childItems; //throw FileError for (const GdriveItem& item : childItems) { @@ -2735,42 +2745,42 @@ private: switch (item.details.type) { case GdriveItemType::file: - cb.onFile({ itemName, item.details.fileSize, item.details.modTime, item.itemId, false /*isFollowedSymlink*/ }); //throw X + cb.onFile({itemName, item.details.fileSize, item.details.modTime, getGdriveFilePrint(item.itemId), false /*isFollowedSymlink*/}); //throw X break; case GdriveItemType::folder: - if (std::shared_ptr cbSub = cb.onFolder({ itemName, false /*isFollowedSymlink*/ })) //throw X + if (std::shared_ptr cbSub = cb.onFolder({itemName, false /*isFollowedSymlink*/})) //throw X { const AfsPath afsItemPath(nativeAppendPaths(folderPath.value, itemName)); - workload_.push_back({ afsItemPath, std::move(cbSub) }); + workload_.push_back({afsItemPath, std::move(cbSub)}); } break; case GdriveItemType::shortcut: - switch (cb.onSymlink({ itemName, item.details.modTime })) //throw X + switch (cb.onSymlink({itemName, item.details.modTime})) //throw X { - case AFS::TraverserCallback::LINK_FOLLOW: + case AFS::TraverserCallback::HandleLink::follow: { const AfsPath afsItemPath(nativeAppendPaths(folderPath.value, itemName)); GdriveItemDetails targetDetails = {}; if (!tryReportingItemError([&] //throw X { - targetDetails = GetShortcutTargetDetails({ gdriveLogin_, afsItemPath }, item.details)().target; //throw FileError + targetDetails = GetShortcutTargetDetails({gdriveLogin_, afsItemPath}, item.details)().target; //throw FileError }, cb, itemName)) continue; if (targetDetails.type == GdriveItemType::folder) { - if (std::shared_ptr cbSub = cb.onFolder({ itemName, true /*isFollowedSymlink*/ })) //throw X - workload_.push_back({ afsItemPath, std::move(cbSub) }); + if (std::shared_ptr cbSub = cb.onFolder({itemName, true /*isFollowedSymlink*/})) //throw X + workload_.push_back({afsItemPath, std::move(cbSub)}); } else //a file or named pipe, etc. - cb.onFile({ itemName, targetDetails.fileSize, targetDetails.modTime, item.details.targetId, true /*isFollowedSymlink*/ }); //throw X + cb.onFile({itemName, targetDetails.fileSize, targetDetails.modTime, getGdriveFilePrint(item.details.targetId), true /*isFollowedSymlink*/}); //throw X } break; - case AFS::TraverserCallback::LINK_SKIP: + case AFS::TraverserCallback::HandleLink::skip: break; } break; @@ -2792,7 +2802,7 @@ void gdriveTraverseFolderRecursive(const GdriveLogin& gdriveLogin, const std::ve struct InputStreamGdrive : public AFS::InputStream { - InputStreamGdrive(const GdrivePath& gdrivePath, const IOCallback& notifyUnbufferedIO /*throw X*/) : + InputStreamGdrive(const GdrivePath& gdrivePath, const IoCallback& notifyUnbufferedIO /*throw X*/) : gdrivePath_(gdrivePath), notifyUnbufferedIO_(notifyUnbufferedIO) { @@ -2854,7 +2864,7 @@ struct InputStreamGdrive : public AFS::InputStream const auto& [itemId, itemDetails] = fileState.getFileAttributes(gdrivePath_.gdriveLogin.sharedDriveName, gdrivePath_.itemPath, true /*followLeafShortcut*/); //throw SysError attr.modTime = itemDetails.modTime; attr.fileSize = itemDetails.fileSize; - attr.fileId = itemId; + attr.filePrint = getGdriveFilePrint(itemId); }); } catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(getGdriveDisplayPath(gdrivePath_))), e.toString()); } @@ -2870,7 +2880,7 @@ private: } const GdrivePath gdrivePath_; - const IOCallback notifyUnbufferedIO_; //throw X + const IoCallback notifyUnbufferedIO_; //throw X int64_t totalBytesReported_ = 0; std::shared_ptr asyncStreamIn_ = std::make_shared(GDRIVE_STREAM_BUFFER_SIZE); InterruptibleThread worker_; @@ -2884,12 +2894,12 @@ struct OutputStreamGdrive : public AFS::OutputStreamImpl OutputStreamGdrive(const GdrivePath& gdrivePath, //throw SysError std::optional /*streamSize*/, std::optional modTime, - const IOCallback& notifyUnbufferedIO /*throw X*/, + const IoCallback& notifyUnbufferedIO /*throw X*/, std::unique_ptr&& pal) : notifyUnbufferedIO_(notifyUnbufferedIO) { - std::promise pFileId; - futFileId_ = pFileId.get_future(); + std::promise pFilePrint; + futFilePrint_ = pFilePrint.get_future(); //CAVEAT: if file is already existing, OutputStreamGdrive *constructor* must fail, not OutputStreamGdrive::write(), // otherwise ~OutputStreamImpl() will delete the already existing file! => don't check asynchronously! @@ -2908,10 +2918,10 @@ struct OutputStreamGdrive : public AFS::OutputStreamImpl }); worker_ = InterruptibleThread([gdrivePath, modTime, fileName, asyncStreamIn = this->asyncStreamOut_, - pFileId = std::move(pFileId), - parentId = std::move(parentId), - aai = std::move(aai), - pal = std::move(pal)]() mutable + pFilePrint = std::move(pFilePrint), + parentId = std::move(parentId), + aai = std::move(aai), + pal = std::move(pal)]() mutable { assert(pal); //bind life time to worker thread! setCurrentThreadName(Zstr("Ostream[Gdrive] ") + utfTo(getGdriveDisplayPath(gdrivePath))); @@ -2946,14 +2956,14 @@ struct OutputStreamGdrive : public AFS::OutputStreamImpl fileState.notifyItemCreated(aai.stateDelta, newFileItem); }); - pFileId.set_value(fileIdNew); + pFilePrint.set_value(getGdriveFilePrint(fileIdNew)); } catch (const SysError& e) { FileError fe(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getGdriveDisplayPath(gdrivePath))), e.toString()); const std::exception_ptr exptr = std::make_exception_ptr(std::move(fe)); asyncStreamIn->setReadError(exptr); //set both! - pFileId.set_exception(exptr); // + pFilePrint.set_exception(exptr); // } //let ThreadStopRequest pass through! }); @@ -2974,7 +2984,7 @@ struct OutputStreamGdrive : public AFS::OutputStreamImpl { asyncStreamOut_->closeStream(); - while (futFileId_.wait_for(std::chrono::milliseconds(50)) == std::future_status::timeout) + while (futFilePrint_.wait_for(std::chrono::milliseconds(50)) == std::future_status::timeout) reportBytesProcessed(); //throw X reportBytesProcessed(); //[!] once more, now that *all* bytes were written @@ -2982,8 +2992,8 @@ struct OutputStreamGdrive : public AFS::OutputStreamImpl //-------------------------------------------------------------------- AFS::FinalizeResult result; - assert(isReady(futFileId_)); - result.fileId = futFileId_.get(); //throw FileError + assert(isReady(futFilePrint_)); + result.filePrint = futFilePrint_.get(); //throw FileError //result.errorModTime -> already (successfully) set during file creation return result; } @@ -2996,11 +3006,11 @@ private: totalBytesReported_ = totalBytesUploaded; } - const IOCallback notifyUnbufferedIO_; //throw X + const IoCallback notifyUnbufferedIO_; //throw X int64_t totalBytesReported_ = 0; std::shared_ptr asyncStreamOut_ = std::make_shared(GDRIVE_STREAM_BUFFER_SIZE); InterruptibleThread worker_; - std::future futFileId_; + std::future futFilePrint_; }; //========================================================================================== @@ -3012,8 +3022,29 @@ public: const GdriveLogin& getGdriveLogin() const { return gdriveLogin_; } + Zstring getFolderUrl(const AfsPath& folderPath) const //throw FileError + { + try + { + GdriveFileState::PathStatus ps; + accessGlobalFileState(gdriveLogin_.email, [&](GdriveFileState& fileState) //throw SysError + { + ps = fileState.getPathStatus(gdriveLogin_.sharedDriveName, folderPath, true /*followLeafShortcut*/); //throw SysError + }); + + if (!ps.relPath.empty()) + throw FileError(replaceCpy(_("Cannot find %x."), L"%x", fmtPath(getDisplayPath(folderPath)))); + + if (ps.existingType != GdriveItemType::folder) + throw SysError(replaceCpy(L"%x is not a folder.", L"%x", fmtPath(getItemName(folderPath)))); + + return Zstr("https://drive.google.com/drive/folders/") + utfTo(ps.existingItemId); + } + catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(getDisplayPath(folderPath))), e.toString()); } + } + private: - GdrivePath getGdrivePath(const AfsPath& afsPath) const { return { gdriveLogin_, afsPath }; } + GdrivePath getGdrivePath(const AfsPath& afsPath) const { return {gdriveLogin_, afsPath}; } GdriveRawPath getGdriveRawPath(const AfsPath& afsPath) const //throw SysError { @@ -3224,7 +3255,7 @@ private: //---------------------------------------------------------------------------------------------------------------- //return value always bound: - std::unique_ptr getInputStream(const AfsPath& afsPath, const IOCallback& notifyUnbufferedIO /*throw X*/) const override //throw FileError, (ErrorFileLocked) + std::unique_ptr getInputStream(const AfsPath& afsPath, const IoCallback& notifyUnbufferedIO /*throw X*/) const override //throw FileError, (ErrorFileLocked) { return std::make_unique(getGdrivePath(afsPath), notifyUnbufferedIO); } @@ -3234,7 +3265,7 @@ private: std::unique_ptr getOutputStream(const AfsPath& afsPath, //throw FileError std::optional streamSize, std::optional modTime, - const IOCallback& notifyUnbufferedIO /*throw X*/) const override + const IoCallback& notifyUnbufferedIO /*throw X*/) const override { try { @@ -3262,7 +3293,7 @@ private: //already existing: undefined behavior! (e.g. fail/overwrite/auto-rename) //=> actual behavior: 1. fails or 2. creates duplicate (unlikely) FileCopyResult copyFileForSameAfsType(const AfsPath& afsSource, const StreamAttributes& attrSource, //throw FileError, (ErrorFileLocked), (X) - const AbstractPath& apTarget, bool copyFilePermissions, const IOCallback& notifyUnbufferedIO /*throw X*/) const override + const AbstractPath& apTarget, bool copyFilePermissions, const IoCallback& notifyUnbufferedIO /*throw X*/) const override { //no native Google Drive file copy => use stream-based file copy: if (copyFilePermissions) @@ -3325,10 +3356,10 @@ private: }); FileCopyResult result; - result.fileSize = fileSize; - result.modTime = modTime; - result.sourceFileId = itemIdSrc; - result.targetFileId = fileIdTrg; + result.fileSize = fileSize; + result.modTime = modTime; + result.sourceFilePrint = getGdriveFilePrint(itemIdSrc); + result.targetFilePrint = getGdriveFilePrint(fileIdTrg); /*result.errorModTime = */ return result; } @@ -3597,7 +3628,7 @@ void fff::gdriveRemoveUser(const std::string& accountEmail) //throw FileError throw SysError(formatSystemError("gdriveRemoveUser", L"", L"Function call not allowed during init/shutdown.")); } - catch (const SysError& e) { throw FileError(replaceCpy(_("Unable to disconnect from %x."), L"%x", fmtPath(getGdriveDisplayPath({{ accountEmail, Zstr("")}, AfsPath() }))), e.toString()); } + catch (const SysError& e) { throw FileError(replaceCpy(_("Unable to disconnect from %x."), L"%x", fmtPath(getGdriveDisplayPath({{accountEmail, Zstr("")}, AfsPath()}))), e.toString()); } } @@ -3625,7 +3656,7 @@ std::vector fff::gdriveListSharedDrives(const std:: }); return sharedDriveNames; } - catch (const SysError& e) { throw FileError(replaceCpy(_("Unable to access %x."), L"%x", fmtPath(getGdriveDisplayPath({{ accountEmail, Zstr("")}, AfsPath() }))), e.toString()); } + catch (const SysError& e) { throw FileError(replaceCpy(_("Unable to access %x."), L"%x", fmtPath(getGdriveDisplayPath({{accountEmail, Zstr("")}, AfsPath()}))), e.toString()); } } @@ -3649,6 +3680,15 @@ GdriveLogin fff::extractGdriveLogin(const AfsDevice& afsDevice) //noexcept } +Zstring fff::getGoogleDriveFolderUrl(const AbstractPath& folderPath) //throw FileError +{ + if (const auto gdriveDevice = dynamic_cast(&folderPath.afsDevice.ref())) + return gdriveDevice->getFolderUrl(folderPath.afsPath); //throw FileError + + return {}; +} + + bool fff::acceptsItemPathPhraseGdrive(const Zstring& itemPathPhrase) //noexcept { Zstring path = expandMacros(itemPathPhrase); //expand before trimming! diff --git a/FreeFileSync/Source/afs/gdrive.h b/FreeFileSync/Source/afs/gdrive.h index abca18ad..75bb4a80 100644 --- a/FreeFileSync/Source/afs/gdrive.h +++ b/FreeFileSync/Source/afs/gdrive.h @@ -33,6 +33,9 @@ struct GdriveLogin }; AfsDevice condenseToGdriveDevice(const GdriveLogin& login); //noexcept; potentially messy user input GdriveLogin extractGdriveLogin(const AfsDevice& afsDevice); //noexcept + +//return empty, if not a Google Drive path +Zstring getGoogleDriveFolderUrl(const AbstractPath& folderPath); //throw FileError } #endif //FS_GDRIVE_9238425018342701356 diff --git a/FreeFileSync/Source/afs/native.cpp b/FreeFileSync/Source/afs/native.cpp index 2e078378..9f677d36 100644 --- a/FreeFileSync/Source/afs/native.cpp +++ b/FreeFileSync/Source/afs/native.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include #include @@ -18,6 +17,7 @@ #include "abstract_impl.h" #include "../base/icon_loader.h" + #include //statfs #include #include @@ -30,6 +30,7 @@ using AFS = AbstractFileSystem; namespace { + void initComForThread() //throw FileError { } @@ -37,34 +38,59 @@ void initComForThread() //throw FileError //==================================================================================================== //==================================================================================================== +//persistent + unique (relative to volume) or 0! inline -AFS::FileId convertToAbstractFileId(const zen::FileId& fid) +AFS::FingerPrint getFileFingerprint(FileIndex fileIndex) { - if (fid == zen::FileId()) - return AFS::FileId(); - - AFS::FileId out(reinterpret_cast(&fid.volumeId), sizeof(fid.volumeId)); - return out.append(reinterpret_cast(&fid.fileIndex), sizeof(fid.fileIndex)); + static_assert(sizeof(fileIndex) == sizeof(AFS::FingerPrint)); + return fileIndex; //== 0 if not supported + /* File details + ------------ + st_mtim (Linux) + st_mtimespec (macOS): nanosecond-precision for improved uniqueness? + => essentially unknown after file copy (e.g. to FAT) without extra directory traversal :( + + macOS st_birthtimespec: "if not supported by file system, holds the ctime instead" + ctime: inode modification time => changed on* rename*! => FU... + + Volume details + -------------- + st_dev: "st_dev value is not necessarily consistent across reboots or system crashes" https://freefilesync.org/forum/viewtopic.php?t=8054 + only locally unique and depends on device mount point! => FU... + + f_fsid: "Some operating systems use the device number..." => fuck! + "Several OSes restrict giving out the f_fsid field to the superuser only" + + f_bsize macOS: "fundamental file system block size" + Linux: "optimal transfer block size" -> no! for all intents and purposes this *is* the "fundamental file system block size": https://stackoverflow.com/a/54835515 + f_blocks => meh... + + f_type Linux: documented values, nice! https://linux.die.net/man/2/statfs + macOS: - not stable between macOS releases: https://developer.apple.com/forums/thread/87745 + - Apple docs say: "generally not a useful value" + - f_fstypename can be used as alternative + + DADiskGetBSDName(): macOS only */ } struct NativeFileInfo { - time_t modTime; + FileTimeNative modTime; uint64_t fileSize; - FileId fileId; //optional + FileIndex fileIndex; }; NativeFileInfo getFileAttributes(FileBase::FileHandle fh) //throw SysError { - struct ::stat fileAttr = {}; - if (::fstat(fh, &fileAttr) != 0) + struct stat fileInfo = {}; + if (::fstat(fh, &fileInfo) != 0) THROW_LAST_SYS_ERROR("fstat"); return { - fileAttr.st_mtime, - makeUnsigned(fileAttr.st_size), - generateFileId(fileAttr) + fileInfo.st_mtim, + fileInfo.st_ino, + makeUnsigned(fileInfo.st_size) }; } @@ -94,7 +120,7 @@ std::vector getDirContentFlat(const Zstring& dirPath) //throw FileError macOS: - libc: readdir thread-safe already in code from 2000: https://opensource.apple.com/source/Libc/Libc-166/gen.subproj/readdir.c.auto.html - and in the latest version from 2017: https://opensource.apple.com/source/Libc/Libc-1244.30.3/gen/FreeBSD/readdir.c.auto.html */ errno = 0; - const struct ::dirent* dirEntry = ::readdir(folder); + const dirent* dirEntry = ::readdir(folder); if (!dirEntry) { if (errno == 0) //errno left unchanged => no more items @@ -114,7 +140,7 @@ std::vector getDirContentFlat(const Zstring& dirPath) //throw FileError if (itemNameRaw[0] == 0) //show error instead of endless recursion!!! throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), formatSystemError("readdir", L"", L"Folder contains an item without name.")); - output.push_back({ itemNameRaw }); + output.push_back({itemNameRaw}); /* Unicode normalization is file-system-dependent: @@ -150,32 +176,42 @@ struct FsItemDetails ItemType type; time_t modTime; //number of seconds since Jan. 1st 1970 UTC uint64_t fileSize; //unit: bytes! - FileId fileId; + AFS::FingerPrint filePrint; }; FsItemDetails getItemDetails(const Zstring& itemPath) //throw FileError { - struct ::stat statData = {}; - if (::lstat(itemPath.c_str(), &statData) != 0) //lstat() does not resolve symlinks + struct stat itemInfo = {}; + if (::lstat(itemPath.c_str(), &itemInfo) != 0) //lstat() does not resolve symlinks THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), "lstat"); - return { S_ISLNK(statData.st_mode) ? ItemType::symlink : //on Linux there is no distinction between file and directory symlinks! - /**/ (S_ISDIR(statData.st_mode) ? ItemType::folder : ItemType::file), //a file or named pipe, etc. => dont't check using S_ISREG(): see comment in file_traverser.cpp - statData.st_mtime, - makeUnsigned(statData.st_size), - generateFileId(statData) }; + return {S_ISLNK(itemInfo.st_mode) ? ItemType::symlink : //on Linux there is no distinction between file and directory symlinks! + /**/ (S_ISDIR(itemInfo.st_mode) ? ItemType::folder : ItemType::file), //a file or named pipe, etc. => dont't check using S_ISREG(): see comment in file_traverser.cpp + itemInfo.st_mtime, + makeUnsigned(itemInfo.st_size), + getFileFingerprint(itemInfo.st_ino)}; } FsItemDetails getSymlinkTargetDetails(const Zstring& linkPath) //throw FileError { - struct ::stat statData = {}; - if (::stat(linkPath.c_str(), &statData) != 0) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(linkPath)), "stat"); - - return { S_ISDIR(statData.st_mode) ? ItemType::folder : ItemType::file, - statData.st_mtime, - makeUnsigned(statData.st_size), - generateFileId(statData) }; + try + { + struct stat itemInfo = {}; + if (::stat(linkPath.c_str(), &itemInfo) != 0) + THROW_LAST_SYS_ERROR("stat"); + + const ItemType targetType = S_ISDIR(itemInfo.st_mode) ? ItemType::folder : ItemType::file; + + const AFS::FingerPrint filePrint = targetType == ItemType::folder ? 0 : getFileFingerprint(itemInfo.st_ino); + return {targetType, + itemInfo.st_mtime, + makeUnsigned(itemInfo.st_size), + filePrint}; + } + catch (const SysError& e) + { + throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(linkPath)), e.toString()); + } } @@ -185,7 +221,7 @@ public: SingleFolderTraverser(const std::vector>>& workload /*throw X*/) { for (const auto& [folderPath, cb] : workload) - workload_.push_back({ folderPath, cb }); + workload_.push_back({folderPath, cb}); while (!workload_.empty()) { @@ -219,18 +255,18 @@ private: switch (itemDetails.type) { case ItemType::file: - cb.onFile({ itemName, itemDetails.fileSize, itemDetails.modTime, convertToAbstractFileId(itemDetails.fileId), false /*isFollowedSymlink*/ }); //throw X + cb.onFile({itemName, itemDetails.fileSize, itemDetails.modTime, itemDetails.filePrint, false /*isFollowedSymlink*/}); //throw X break; case ItemType::folder: - if (std::shared_ptr cbSub = cb.onFolder({ itemName, false /*isFollowedSymlink*/ })) //throw X - workload_.push_back({ itemPath, std::move(cbSub) }); + if (std::shared_ptr cbSub = cb.onFolder({itemName, false /*isFollowedSymlink*/})) //throw X + workload_.push_back({itemPath, std::move(cbSub)}); break; case ItemType::symlink: - switch (cb.onSymlink({ itemName, itemDetails.modTime })) //throw X + switch (cb.onSymlink({itemName, itemDetails.modTime})) //throw X { - case AFS::TraverserCallback::LINK_FOLLOW: + case AFS::TraverserCallback::HandleLink::follow: { FsItemDetails targetDetails = {}; if (!tryReportingItemError([&] //throw X @@ -241,15 +277,15 @@ private: if (targetDetails.type == ItemType::folder) { - if (std::shared_ptr cbSub = cb.onFolder({ itemName, true /*isFollowedSymlink*/ })) //throw X - workload_.push_back({ itemPath, std::move(cbSub) }); + if (std::shared_ptr cbSub = cb.onFolder({itemName, true /*isFollowedSymlink*/})) //throw X + workload_.push_back({itemPath, std::move(cbSub)}); //symlink may link to different volume! } else //a file or named pipe, etc. - cb.onFile({ itemName, targetDetails.fileSize, targetDetails.modTime, convertToAbstractFileId(targetDetails.fileId), true /*isFollowedSymlink*/ }); //throw X + cb.onFile({itemName, targetDetails.fileSize, targetDetails.modTime, targetDetails.filePrint, true /*isFollowedSymlink*/}); //throw X } break; - case AFS::TraverserCallback::LINK_SKIP: + case AFS::TraverserCallback::HandleLink::skip: break; } break; @@ -289,7 +325,7 @@ private: struct InputStreamNative : public AFS::InputStream { - InputStreamNative(const Zstring& filePath, const IOCallback& notifyUnbufferedIO /*throw X*/) : fi_(filePath, notifyUnbufferedIO) {} //throw FileError, ErrorFileLocked + InputStreamNative(const Zstring& filePath, const IoCallback& notifyUnbufferedIO /*throw X*/) : fi_(filePath, notifyUnbufferedIO) {} //throw FileError, ErrorFileLocked size_t read(void* buffer, size_t bytesToRead) override { return fi_.read(buffer, bytesToRead); } //throw FileError, ErrorFileLocked, X; return "bytesToRead" bytes unless end of stream! size_t getBlockSize() const override { return fi_.getBlockSize(); } //non-zero block size is AFS contract! @@ -297,13 +333,11 @@ struct InputStreamNative : public AFS::InputStream { try { - const NativeFileInfo fileInfo = getFileAttributes(fi_.getHandle()); //throw SysError - return AFS::StreamAttributes( - { - fileInfo.modTime, - fileInfo.fileSize, - convertToAbstractFileId(fileInfo.fileId) - }); + const NativeFileInfo& fileInfo = getFileAttributes(fi_.getHandle()); //throw SysError + + return AFS::StreamAttributes({nativeFileTimeToTimeT(fileInfo.modTime), + fileInfo.fileSize, + getFileFingerprint(fileInfo.fileIndex)}); } catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(fi_.getFilePath())), e.toString()); } } @@ -319,7 +353,7 @@ struct OutputStreamNative : public AFS::OutputStreamImpl OutputStreamNative(const Zstring& filePath, std::optional streamSize, std::optional modTime, - const IOCallback& notifyUnbufferedIO /*throw X*/) : + const IoCallback& notifyUnbufferedIO /*throw X*/) : fo_(filePath, notifyUnbufferedIO), //throw FileError, ErrorTargetExisting modTime_(modTime) { @@ -332,20 +366,22 @@ struct OutputStreamNative : public AFS::OutputStreamImpl AFS::FinalizeResult finalize() override //throw FileError, X { AFS::FinalizeResult result; - try - { - result.fileId = convertToAbstractFileId(getFileAttributes(fo_.getHandle()).fileId); //throw SysError - } - catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(fo_.getFilePath())), e.toString()); } + if (modTime_) + try + { + const FileIndex fileIndex = getFileAttributes(fo_.getHandle()).fileIndex; //throw SysError + result.filePrint = getFileFingerprint(fileIndex); + } + catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(fo_.getFilePath())), e.toString()); } fo_.finalize(); //throw FileError, X try { if (modTime_) - setFileTime(fo_.getFilePath(), *modTime_, ProcSymlink::FOLLOW); //throw FileError + setFileTime(fo_.getFilePath(), *modTime_, ProcSymlink::follow); //throw FileError /* is setting modtime after closing the file handle a pessimization? - Native: no, needed for functional correctness, see file_access.cpp */ + no, needed for functional correctness, see file_access.cpp */ } catch (const FileError& e) { result.errorModTime = FileError(e.toString()); /*avoid slicing*/ } @@ -364,11 +400,9 @@ class NativeFileSystem : public AbstractFileSystem public: NativeFileSystem(const Zstring& rootPath) : rootPath_(rootPath) {} -private: - Zstring getNativePath(const AfsPath& afsPath) const { return nativeAppendPaths(rootPath_, afsPath.value); } - - std::optional getNativeItemPath(const AfsPath& afsPath) const override { return getNativePath(afsPath); } + Zstring getNativePath(const AfsPath& afsPath) const { return isNullFileSystem() ? Zstring() : nativeAppendPaths(rootPath_, afsPath.value); } +private: Zstring getInitPathPhrase(const AfsPath& afsPath) const override { Zstring initPathPhrase = getNativePath(afsPath); @@ -475,7 +509,7 @@ private: //---------------------------------------------------------------------------------------------------------------- //return value always bound: - std::unique_ptr getInputStream(const AfsPath& afsPath, const IOCallback& notifyUnbufferedIO /*throw X*/) const override //throw FileError, ErrorFileLocked + std::unique_ptr getInputStream(const AfsPath& afsPath, const IoCallback& notifyUnbufferedIO /*throw X*/) const override //throw FileError, ErrorFileLocked { initComForThread(); //throw FileError return std::make_unique(getNativePath(afsPath), notifyUnbufferedIO); //throw FileError, ErrorFileLocked @@ -486,7 +520,7 @@ private: std::unique_ptr getOutputStream(const AfsPath& afsPath, //throw FileError std::optional streamSize, std::optional modTime, - const IOCallback& notifyUnbufferedIO /*throw X*/) const override + const IoCallback& notifyUnbufferedIO /*throw X*/) const override { initComForThread(); //throw FileError return std::make_unique(getNativePath(afsPath), streamSize, modTime, notifyUnbufferedIO); //throw FileError @@ -509,7 +543,7 @@ private: //already existing: undefined behavior! (e.g. fail/overwrite/auto-rename) //=> actual behavior: fail with clear error message FileCopyResult copyFileForSameAfsType(const AfsPath& afsSource, const StreamAttributes& attrSource, //throw FileError, ErrorFileLocked, X - const AbstractPath& apTarget, bool copyFilePermissions, const IOCallback& notifyUnbufferedIO /*throw X*/) const override + const AbstractPath& apTarget, bool copyFilePermissions, const IoCallback& notifyUnbufferedIO /*throw X*/) const override { const Zstring nativePathTarget = static_cast(apTarget.afsDevice.ref()).getNativePath(apTarget.afsPath); @@ -522,13 +556,14 @@ private: catch (FileError&) {}); if (copyFilePermissions) - copyItemPermissions(getNativePath(afsSource), nativePathTarget, ProcSymlink::FOLLOW); //throw FileError + copyItemPermissions(getNativePath(afsSource), nativePathTarget, ProcSymlink::follow); //throw FileError FileCopyResult result; - result.fileSize = nativeResult.fileSize; - result.modTime = nativeResult.modTime; - result.sourceFileId = convertToAbstractFileId(nativeResult.sourceFileId); - result.targetFileId = convertToAbstractFileId(nativeResult.targetFileId); + result.fileSize = nativeResult.fileSize; + //caveat: modTime will be incorrect for file systems with imprecise file times, e.g. see FAT_FILE_TIME_PRECISION_SEC + result.modTime = nativeFileTimeToTimeT(nativeResult.sourceModTime); + result.sourceFilePrint = getFileFingerprint(nativeResult.sourceFileIdx); + result.targetFilePrint = getFileFingerprint(nativeResult.targetFileIdx); result.errorModTime = nativeResult.errorModTime; return result; } @@ -553,7 +588,7 @@ private: tryCopyDirectoryAttributes(sourcePath, targetPath); //throw FileError if (copyFilePermissions) - copyItemPermissions(sourcePath, targetPath, ProcSymlink::FOLLOW); //throw FileError + copyItemPermissions(sourcePath, targetPath, ProcSymlink::follow); //throw FileError } //already existing: fail @@ -568,7 +603,7 @@ private: catch (FileError&) {}); if (copyFilePermissions) - copyItemPermissions(getNativePath(afsSource), nativePathTarget, ProcSymlink::DIRECT); //throw FileError + copyItemPermissions(getNativePath(afsSource), nativePathTarget, ProcSymlink::direct); //throw FileError } //already existing: undefined behavior! (e.g. fail/overwrite) @@ -662,11 +697,11 @@ void RecycleSessionNative::recycleItemIfExists(const AbstractPath& itemPath, con { assert(!startsWith(logicalRelPath, FILE_NAME_SEPARATOR)); - std::optional itemPathNative = AFS::getNativeItemPath(itemPath); - if (!itemPathNative) + const Zstring& itemPathNative = getNativeItemPath(itemPath); + if (itemPathNative.empty()) throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); - recycleOrDeleteIfExists(*itemPathNative); //throw FileError + recycleOrDeleteIfExists(itemPathNative); //throw FileError } @@ -710,3 +745,11 @@ AbstractPath fff::createItemPathNativeNoFormatting(const Zstring& nativePath) // else //broken path syntax return AbstractPath(makeSharedRef(nativePath), AfsPath()); } + + +Zstring fff::getNativeItemPath(const AbstractPath& ap) +{ + if (const auto nativeDevice = dynamic_cast(&ap.afsDevice.ref())) + return nativeDevice->getNativePath(ap.afsPath); + return {}; +} diff --git a/FreeFileSync/Source/afs/native.h b/FreeFileSync/Source/afs/native.h index 77896e91..24270e9f 100644 --- a/FreeFileSync/Source/afs/native.h +++ b/FreeFileSync/Source/afs/native.h @@ -14,7 +14,12 @@ namespace fff bool acceptsItemPathPhraseNative(const Zstring& itemPathPhrase); //noexcept AbstractPath createItemPathNative(const Zstring& itemPathPhrase); //noexcept +//------------------------------------------------------- + AbstractPath createItemPathNativeNoFormatting(const Zstring& nativePath); //noexcept + +//return empty, if not a native path +Zstring getNativeItemPath(const AbstractPath& ap); } #endif //FS_NATIVE_183247018532434563465 diff --git a/FreeFileSync/Source/afs/sftp.cpp b/FreeFileSync/Source/afs/sftp.cpp index 325aa412..74dc1657 100644 --- a/FreeFileSync/Source/afs/sftp.cpp +++ b/FreeFileSync/Source/afs/sftp.cpp @@ -478,7 +478,7 @@ public: int rc = LIBSSH2_ERROR_NONE; try { - rc = sftpCommand({ sshSession_, sftpChannel }); //noexcept + rc = sftpCommand({sshSession_, sftpChannel}); //noexcept } catch (...) { assert(false); rc = LIBSSH2_ERROR_BAD_USE; } @@ -563,16 +563,16 @@ public: return; //time-out! => let next tryNonBlocking() call fail with detailed error! const auto waitTimeMs = std::chrono::duration_cast(endTime - now).count(); - struct ::timeval tv = {}; + timeval tv = {}; tv.tv_sec = static_cast(waitTimeMs / 1000); tv.tv_usec = static_cast(waitTimeMs - tv.tv_sec * 1000) * 1000; //WSAPoll is broken, ::poll() on macOS even worse: https://daniel.haxx.se/blog/2012/10/10/wsapoll-is-broken/ //perf: no significant difference compared to ::WSAPoll() - const int rc = ::select(nfds + 1, //int nfds, - readCount > 0 ? &readfds : nullptr, //fd_set* readfds, - writeCount > 0 ? &writefds : nullptr, //fd_set* writefds, - nullptr, //fd_set* exceptfds, + const int rc = ::select(nfds + 1, //int nfds + readCount > 0 ? &readfds : nullptr, //fd_set* readfds + writeCount > 0 ? &writefds : nullptr, //fd_set* writefds + nullptr, //fd_set* exceptfds &tv); //struct timeval* timeout if (rc == 0) return; //time-out! => let next tryNonBlocking() call fail with detailed error! @@ -738,7 +738,7 @@ public: void init() //throw SysError, FatalSshError { if (session_->getSftpChannelCount() == 0) //make sure the SSH session contains at least one SFTP channel - SshSession::addSftpChannel({ session_.get() }, timeoutSec_); //throw SysError, FatalSshError + SshSession::addSftpChannel({session_.get()}, timeoutSec_); //throw SysError, FatalSshError } //bool isHealthy() const { return session_->isHealthy(); } @@ -753,7 +753,7 @@ public: if (session_->tryNonBlocking(0, sftpCommandStartTime, functionName, sftpCommand, timeoutSec_)) //throw SysError, FatalSshError return; else //pending - SshSession::waitForTraffic({ session_.get() }, timeoutSec_); //throw FatalSshError + SshSession::waitForTraffic({session_.get()}, timeoutSec_); //throw FatalSshError } private: @@ -783,7 +783,7 @@ public: if (session_->tryNonBlocking(channelNo, commandStartTime, functionName, sftpCommand, timeoutSec_)) //throw SysError, FatalSshError return; else //pending - SshSession::waitForTraffic({ session_.get() }, timeoutSec_); //throw FatalSshError + SshSession::waitForTraffic({session_.get()}, timeoutSec_); //throw FatalSshError } catch (const SysError& ) { return; } catch (const FatalSshError&) { return; } @@ -1087,17 +1087,17 @@ std::vector getDirContentFlat(const SftpLogin& login, const AfsPath& d { if ((attribs.flags & LIBSSH2_SFTP_ATTR_ACMODTIME) == 0) //server probably does not support these attributes => fail at folder level throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(getSftpDisplayPath(login.server, itemPath))), L"Modification time not supported."); - output.push_back({ itemName, { AFS::ItemType::symlink, 0, static_cast(attribs.mtime) }}); + output.push_back({itemName, {AFS::ItemType::symlink, 0, static_cast(attribs.mtime)}}); } else if (LIBSSH2_SFTP_S_ISDIR(attribs.permissions)) - output.push_back({ itemName, { AFS::ItemType::folder, 0, static_cast(attribs.mtime) }}); + output.push_back({itemName, {AFS::ItemType::folder, 0, static_cast(attribs.mtime)}}); else //a file or named pipe, ect: LIBSSH2_SFTP_S_ISREG, LIBSSH2_SFTP_S_ISCHR, LIBSSH2_SFTP_S_ISBLK, LIBSSH2_SFTP_S_ISFIFO, LIBSSH2_SFTP_S_ISSOCK { if ((attribs.flags & LIBSSH2_SFTP_ATTR_ACMODTIME) == 0) //server probably does not support these attributes => fail at folder level throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(getSftpDisplayPath(login.server, itemPath))), L"Modification time not supported."); if ((attribs.flags & LIBSSH2_SFTP_ATTR_SIZE) == 0) throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(getSftpDisplayPath(login.server, itemPath))), L"File size not supported."); - output.push_back({ itemName, { AFS::ItemType::file, attribs.filesize, static_cast(attribs.mtime) }}); + output.push_back({itemName, {AFS::ItemType::file, attribs.filesize, static_cast(attribs.mtime)}}); } } } @@ -1117,7 +1117,7 @@ SftpItemDetails getSymlinkTargetDetails(const SftpLogin& login, const AfsPath& l throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(getSftpDisplayPath(login.server, linkPath))), L"File attributes not available."); if (LIBSSH2_SFTP_S_ISDIR(attribsTrg.permissions)) - return { AFS::ItemType::folder, 0, static_cast(attribsTrg.mtime) }; + return {AFS::ItemType::folder, 0, static_cast(attribsTrg.mtime)}; else { if ((attribsTrg.flags & LIBSSH2_SFTP_ATTR_ACMODTIME) == 0) //server probably does not support these attributes => should fail at folder level! @@ -1125,7 +1125,7 @@ SftpItemDetails getSymlinkTargetDetails(const SftpLogin& login, const AfsPath& l if ((attribsTrg.flags & LIBSSH2_SFTP_ATTR_SIZE) == 0) throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(getSftpDisplayPath(login.server, linkPath))), L"File size not supported."); - return { AFS::ItemType::file, attribsTrg.filesize, static_cast(attribsTrg.mtime) }; + return {AFS::ItemType::file, attribsTrg.filesize, static_cast(attribsTrg.mtime)}; } } @@ -1167,18 +1167,18 @@ private: switch (item.details.type) { case AFS::ItemType::file: - cb.onFile({ item.itemName, item.details.fileSize, item.details.modTime, AFS::FileId(), false /*isFollowedSymlink*/ }); //throw X + cb.onFile({item.itemName, item.details.fileSize, item.details.modTime, AFS::FingerPrint() /*not supported by SFTP*/, false /*isFollowedSymlink*/}); //throw X break; case AFS::ItemType::folder: - if (std::shared_ptr cbSub = cb.onFolder({ item.itemName, false /*isFollowedSymlink*/ })) //throw X - workload_.push_back(WorkItem{ itemPath, std::move(cbSub) }); + if (std::shared_ptr cbSub = cb.onFolder({item.itemName, false /*isFollowedSymlink*/})) //throw X + workload_.push_back(WorkItem{itemPath, std::move(cbSub)}); break; case AFS::ItemType::symlink: - switch (cb.onSymlink({ item.itemName, item.details.modTime })) //throw X + switch (cb.onSymlink({item.itemName, item.details.modTime})) //throw X { - case AFS::TraverserCallback::LINK_FOLLOW: + case AFS::TraverserCallback::HandleLink::follow: { SftpItemDetails targetDetails = {}; if (!tryReportingItemError([&] //throw X @@ -1189,15 +1189,15 @@ private: if (targetDetails.type == AFS::ItemType::folder) { - if (std::shared_ptr cbSub = cb.onFolder({ item.itemName, true /*isFollowedSymlink*/ })) //throw X - workload_.push_back(WorkItem{ itemPath, std::move(cbSub) }); + if (std::shared_ptr cbSub = cb.onFolder({item.itemName, true /*isFollowedSymlink*/})) //throw X + workload_.push_back(WorkItem{itemPath, std::move(cbSub)}); } else //a file or named pipe, etc. - cb.onFile({ item.itemName, targetDetails.fileSize, targetDetails.modTime, AFS::FileId(), true /*isFollowedSymlink*/ }); //throw X + cb.onFile({item.itemName, targetDetails.fileSize, targetDetails.modTime, AFS::FingerPrint() /*not supported by SFTP*/, true /*isFollowedSymlink*/}); //throw X } break; - case AFS::TraverserCallback::LINK_SKIP: + case AFS::TraverserCallback::HandleLink::skip: break; } break; @@ -1219,7 +1219,7 @@ void traverseFolderRecursiveSftp(const SftpLogin& login, const std::vector session_; std::vector memBuf_ = std::vector(getBlockSize()); @@ -1334,7 +1334,7 @@ struct OutputStreamSftp : public AFS::OutputStreamImpl OutputStreamSftp(const SftpLogin& login, //throw FileError const AfsPath& filePath, std::optional modTime, - const IOCallback& notifyUnbufferedIO /*throw X*/) : + const IoCallback& notifyUnbufferedIO /*throw X*/) : filePath_(filePath), displayPath_(getSftpDisplayPath(login.server, filePath)), modTime_(modTime), @@ -1422,12 +1422,12 @@ struct OutputStreamSftp : public AFS::OutputStreamImpl //it seems libssh2_sftp_fsetstat() triggers bugs on synology server => set mtime by path! https://freefilesync.org/forum/viewtopic.php?t=1281 AFS::FinalizeResult result; - //result.fileId = ... -> not supported by SFTP + //result.filePrint = ... -> not supported by SFTP try { setModTimeIfAvailable(); //throw FileError, follows symlinks /* is setting modtime after closing the file handle a pessimization? - SFTP: no, needed for functional correctness (synology server), just as for Native */ + SFTP: no, needed for functional correctness (synology server), same as for Native */ } catch (const FileError& e) { result.errorModTime = e; /*slicing?*/ } @@ -1502,7 +1502,7 @@ private: const std::wstring displayPath_; LIBSSH2_SFTP_HANDLE* fileHandle_ = nullptr; const std::optional modTime_; - const IOCallback notifyUnbufferedIO_; //throw X + const IoCallback notifyUnbufferedIO_; //throw X std::shared_ptr session_; std::vector memBuf_ = std::vector(getBlockSize()); @@ -1702,7 +1702,7 @@ private: //---------------------------------------------------------------------------------------------------------------- //return value always bound: - std::unique_ptr getInputStream(const AfsPath& afsPath, const IOCallback& notifyUnbufferedIO /*throw X*/) const override //throw FileError, (ErrorFileLocked) + std::unique_ptr getInputStream(const AfsPath& afsPath, const IoCallback& notifyUnbufferedIO /*throw X*/) const override //throw FileError, (ErrorFileLocked) { return std::make_unique(login_, afsPath, notifyUnbufferedIO); //throw FileError } @@ -1712,7 +1712,7 @@ private: std::unique_ptr getOutputStream(const AfsPath& afsPath, //throw FileError std::optional streamSize, std::optional modTime, - const IOCallback& notifyUnbufferedIO /*throw X*/) const override + const IoCallback& notifyUnbufferedIO /*throw X*/) const override { return std::make_unique(login_, afsPath, modTime, notifyUnbufferedIO); //throw FileError } @@ -1727,7 +1727,7 @@ private: //symlink handling: follow //already existing: undefined behavior! (e.g. fail/overwrite/auto-rename) FileCopyResult copyFileForSameAfsType(const AfsPath& afsSource, const StreamAttributes& attrSource, //throw FileError, (ErrorFileLocked), X - const AbstractPath& apTarget, bool copyFilePermissions, const IOCallback& notifyUnbufferedIO /*throw X*/) const override + const AbstractPath& apTarget, bool copyFilePermissions, const IoCallback& notifyUnbufferedIO /*throw X*/) const override { //no native SFTP file copy => use stream-based file copy: if (copyFilePermissions) @@ -1961,7 +1961,7 @@ int fff::getServerMaxChannelsPerConnection(const SftpLogin& login) //throw FileE { try { - SftpSessionManager::SshSessionExclusive::addSftpChannel({ exSession.get() }); //throw SysError, FatalSshError + SftpSessionManager::SshSessionExclusive::addSftpChannel({exSession.get()}); //throw SysError, FatalSshError } catch (const SysError& ) { if (exSession->getSftpChannelCount() == 0) throw; return static_cast(exSession->getSftpChannelCount()); } catch (const FatalSshError& e) { if (exSession->getSftpChannelCount() == 0) throw SysError(e.toString()); return static_cast(exSession->getSftpChannelCount()); } @@ -2012,7 +2012,7 @@ AbstractPath fff::createItemPathSftp(const Zstring& itemPathPhrase) //noexcept auto it = std::find_if(fullPath.begin(), fullPath.end(), [](Zchar c) { return c == '/' || c == '\\'; }); const Zstring serverPort(fullPath.begin(), it); - const AfsPath serverRelPath = sanitizeDeviceRelativePath({ it, fullPath.end() }); + const AfsPath serverRelPath = sanitizeDeviceRelativePath({it, fullPath.end()}); login.server = beforeLast(serverPort, Zstr(':'), IfNotFoundReturn::all); const Zstring port = afterLast(serverPort, Zstr(':'), IfNotFoundReturn::none); diff --git a/FreeFileSync/Source/application.cpp b/FreeFileSync/Source/application.cpp index c1ccd1d2..dc24ad19 100644 --- a/FreeFileSync/Source/application.cpp +++ b/FreeFileSync/Source/application.cpp @@ -92,8 +92,8 @@ bool Application::OnInit() if (error) throw SysError(formatGlibError("gtk_css_provider_load_from_path", error)); - ::gtk_style_context_add_provider_for_screen(::gdk_screen_get_default(), //GdkScreen* screen, - GTK_STYLE_PROVIDER(provider), //GtkStyleProvider* provider, + ::gtk_style_context_add_provider_for_screen(::gdk_screen_get_default(), //GdkScreen* screen + GTK_STYLE_PROVIDER(provider), //GtkStyleProvider* provider GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); //guint priority }; try @@ -113,6 +113,18 @@ bool Application::OnInit() #error unknown GTK version! #endif + try + { + /* we're a GUI app: ignore SIGHUP when the parent terminal quits! (or process is killed!) + => the FFS launcher will still be killed => fine + => macOS: apparently not needed! interestingly the FFS launcher does receive SIGHUP and *is* killed */ + if (sighandler_t oldHandler = ::signal(SIGHUP, SIG_IGN); + oldHandler == SIG_ERR) + THROW_LAST_SYS_ERROR("signal(SIGHUP)"); + else assert(!oldHandler); + } + catch (const SysError& e) { std::cerr << utfTo(e.toString()) << '\n'; } + //Windows User Experience Interaction Guidelines: tool tips should have 5s timeout, info tips no timeout => compromise: wxToolTip::Enable(true); //yawn, a wxWidgets screw-up: wxToolTip::SetAutoPop is no-op if global tooltip window is not yet constructed: wxToolTip::Enable creates it @@ -127,7 +139,7 @@ bool Application::OnInit() } catch (FileError&) { assert(false); } - initAfs({ getResourceDirPf(), getConfigDirPathPf() }); //bonus: using FTP Gdrive implicitly inits OpenSSL (used in runSanityChecks() on Linux) alredy during globals init + initAfs({getResourceDirPf(), getConfigDirPathPf()}); //bonus: using FTP Gdrive implicitly inits OpenSSL (used in runSanityChecks() on Linux) alredy during globals init Bind(wxEVT_QUERY_END_SESSION, [this](wxCloseEvent& event) { onQueryEndSession(event); }); //can veto @@ -178,7 +190,7 @@ void Application::OnUnhandledException() //handles both wxApp::OnInit() + wxApp: } catch (const std::bad_alloc& e) //the only kind of exception we don't want crash dumps for { - logFatalError(e.what()); //it's not always possible to display a message box, e.g. corrupted stack, however low-level file output works! + logFatalError(utfTo(e.what())); //it's not always possible to display a message box, e.g. corrupted stack, however low-level file output works! const auto& titleFmt = copyStringTo(wxTheApp->GetAppDisplayName()) + SPACED_DASH + _("An exception occurred"); std::cerr << utfTo(titleFmt + SPACED_DASH) << e.what() << '\n'; @@ -212,7 +224,7 @@ void Application::launch(const std::vector& commandArgs) auto notifyFatalError = [&](const std::wstring& msg, const std::wstring& title) { - logFatalError(utfTo(msg)); + logFatalError(msg); //error handling strategy unknown and no sync log output available at this point! auto titleFmt = copyStringTo(wxTheApp->GetAppDisplayName()) + SPACED_DASH + title; @@ -347,9 +359,9 @@ void Application::launch(const std::vector& commandArgs) auto hasNonDefaultConfig = [](const LocalPairConfig& lpc) { - return lpc != LocalPairConfig{ lpc.folderPathPhraseLeft, - lpc.folderPathPhraseRight, - std::nullopt, std::nullopt, FilterConfig() }; + return lpc != LocalPairConfig{lpc.folderPathPhraseLeft, + lpc.folderPathPhraseRight, + std::nullopt, std::nullopt, FilterConfig()}; }; auto replaceDirectories = [&](MainConfiguration& mainCfg) @@ -371,8 +383,8 @@ void Application::launch(const std::vector& commandArgs) mainCfg.firstPair.folderPathPhraseRight = dirPathPhrasePairs[0].second; } else - mainCfg.additionalPairs.push_back({ dirPathPhrasePairs[i].first, dirPathPhrasePairs[i].second, - std::nullopt, std::nullopt, FilterConfig() }); + mainCfg.additionalPairs.push_back({dirPathPhrasePairs[i].first, dirPathPhrasePairs[i].second, + std::nullopt, std::nullopt, FilterConfig()}); } return true; }; @@ -516,8 +528,11 @@ void runBatchMode(const Zstring& globalConfigFilePath, const XmlBatchConfig& bat { if (showPopupAllowed) showNotificationDialog(nullptr, DialogInfoType::error, PopupDialogCfg().setDetailInstructions(msg)); - else //"exit" or "ignore" - logFatalError(utfTo(msg)); + else //"ignoreErrors" or BatchErrorHandling::cancel + { + logFatalError(msg); + std::cerr << utfTo(wxTheApp->GetAppDisplayName() + SPACED_DASH + msg) << '\n'; //Linux, macOS + } raiseExitCode(exitCode, rc); }; @@ -669,7 +684,7 @@ void runBatchMode(const Zstring& globalConfigFilePath, const XmlBatchConfig& bat case FinalRequest::none: break; case FinalRequest::switchGui: //open new top-level window *after* progress dialog is gone => run on main event loop - MainDialog::create(globalConfigFilePath, &globalCfg, convertBatchToGui(batchCfg), { cfgFilePath }, true /*startComparison*/); + MainDialog::create(globalConfigFilePath, &globalCfg, convertBatchToGui(batchCfg), {cfgFilePath}, true /*startComparison*/); break; case FinalRequest::shutdown: //run *after* last sync stats were updated and saved! https://freefilesync.org/forum/viewtopic.php?t=5761 try diff --git a/FreeFileSync/Source/base/algorithm.cpp b/FreeFileSync/Source/base/algorithm.cpp index bcbba259..cf4a0ce9 100644 --- a/FreeFileSync/Source/base/algorithm.cpp +++ b/FreeFileSync/Source/base/algorithm.cpp @@ -120,9 +120,9 @@ private: const CompareFileResult cat = file.getCategory(); //##################### schedule old temporary files for deletion #################### - if (cat == FILE_LEFT_SIDE_ONLY && endsWith(file.getItemName(), AFS::TEMP_FILE_ENDING)) + if (cat == FILE_LEFT_SIDE_ONLY && endsWith(file.getItemName(), AFS::TEMP_FILE_ENDING)) return file.setSyncDir(SyncDirection::left); - else if (cat == FILE_RIGHT_SIDE_ONLY && endsWith(file.getItemName(), AFS::TEMP_FILE_ENDING)) + else if (cat == FILE_RIGHT_SIDE_ONLY && endsWith(file.getItemName(), AFS::TEMP_FILE_ENDING)) return file.setSyncDir(SyncDirection::right); //#################################################################################### @@ -193,9 +193,9 @@ private: const CompareDirResult cat = folder.getDirCategory(); //########### schedule abandoned temporary recycle bin directory for deletion ########## - if (cat == DIR_LEFT_SIDE_ONLY && endsWith(folder.getItemName(), AFS::TEMP_FILE_ENDING)) + if (cat == DIR_LEFT_SIDE_ONLY && endsWith(folder.getItemName(), AFS::TEMP_FILE_ENDING)) return setSyncDirectionRec(SyncDirection::left, folder); // - else if (cat == DIR_RIGHT_SIDE_ONLY && endsWith(folder.getItemName(), AFS::TEMP_FILE_ENDING)) + else if (cat == DIR_RIGHT_SIDE_ONLY && endsWith(folder.getItemName(), AFS::TEMP_FILE_ENDING)) return setSyncDirectionRec(SyncDirection::right, folder); //don't recurse below! //####################################################################################### @@ -252,7 +252,7 @@ bool fff::allElementsEqual(const FolderComparison& folderCmp) namespace { -template inline +template inline bool matchesDbEntry(const FilePair& file, const InSyncFile* dbFile, const std::vector& ignoreTimeShiftMinutes) { if (file.isEmpty()) @@ -262,11 +262,10 @@ bool matchesDbEntry(const FilePair& file, const InSyncFile* dbFile, const std::v const InSyncDescrFile& descrDb = SelectParam::ref(dbFile->left, dbFile->right); - //respect 2 second FAT/FAT32 precision! copying a file to a FAT32 drive changes it's modification date by up to 2 seconds return //we're not interested in "fileTimeTolerance" here! - sameFileTime(file.getLastWriteTime(), descrDb.modTime, 2, ignoreTimeShiftMinutes) && + sameFileTime(file.getLastWriteTime(), descrDb.modTime, FAT_FILE_TIME_PRECISION_SEC, ignoreTimeShiftMinutes) && file.getFileSize() == dbFile->fileSize; - //note: we do *not* consider FileId here, but are only interested in *visual* changes. Consider user moving data to some other medium, this is not a change! + //note: we do *not* consider file ID here, but are only interested in *visual* changes. Consider user moving data to some other medium, this is not a change! } @@ -297,7 +296,7 @@ bool stillInSync(const InSyncFile& dbFile, CompareVariant compareVar, int fileTi //-------------------------------------------------------------------- //check whether database entry and current item match: *irrespective* of current comparison settings -template inline +template inline bool matchesDbEntry(const SymlinkPair& symlink, const InSyncSymlink* dbSymlink, const std::vector& ignoreTimeShiftMinutes) { if (symlink.isEmpty()) @@ -307,8 +306,7 @@ bool matchesDbEntry(const SymlinkPair& symlink, const InSyncSymlink* dbSymlink, const InSyncDescrLink& descrDb = SelectParam::ref(dbSymlink->left, dbSymlink->right); - //respect 2 second FAT/FAT32 precision! copying a file to a FAT32 drive changes its modification date by up to 2 seconds - return sameFileTime(symlink.getLastWriteTime(), descrDb.modTime, 2, ignoreTimeShiftMinutes); + return sameFileTime(symlink.getLastWriteTime(), descrDb.modTime, FAT_FILE_TIME_PRECISION_SEC, ignoreTimeShiftMinutes); } @@ -337,7 +335,7 @@ bool stillInSync(const InSyncSymlink& dbLink, CompareVariant compareVar, int fil //-------------------------------------------------------------------- //check whether database entry and current item match: *irrespective* of current comparison settings -template inline +template inline bool matchesDbEntry(const FolderPair& folder, const InSyncFolder* dbFolder) { const bool haveDbEntry = dbFolder && dbFolder->status != InSyncFolder::DIR_STATUS_STRAW_MAN; @@ -368,6 +366,9 @@ private: { recurse(baseFolder, &dbFolder, &dbFolder); + purgeDuplicates< SelectSide::left>(filesL_, exLeftOnlyById_); + purgeDuplicates(filesR_, exRightOnlyById_); + if ((!exLeftOnlyById_ .empty() || !exLeftOnlyByPath_ .empty()) && (!exRightOnlyById_.empty() || !exRightOnlyByPath_.empty())) detectMovePairs(dbFolder); @@ -377,40 +378,31 @@ private: { for (FilePair& file : hierObj.refSubFiles()) { + const AFS::FingerPrint filePrintL = file.getFilePrint< SelectSide::left>(); + const AFS::FingerPrint filePrintR = file.getFilePrint(); + + if (filePrintL != 0) filesL_.push_back(&file); //collect *all* prints for uniqueness check! + if (filePrintR != 0) filesR_.push_back(&file); // + auto getDbEntry = [](const InSyncFolder* dbFolder, const Zstring& fileName) -> const InSyncFile* { if (dbFolder) - { - auto it = dbFolder->files.find(fileName); - if (it != dbFolder->files.end()) + if (const auto it = dbFolder->files.find(fileName); + it != dbFolder->files.end()) return &it->second; - } return nullptr; }; - const CompareFileResult cat = file.getCategory(); - - if (cat == FILE_LEFT_SIDE_ONLY) + if (const CompareFileResult cat = file.getCategory(); + cat == FILE_LEFT_SIDE_ONLY) { - if (const InSyncFile* dbEntry = getDbEntry(dbFolderL, file.getItemName())) + if (const InSyncFile* dbEntry = getDbEntry(dbFolderL, file.getItemName())) exLeftOnlyByPath_.emplace(dbEntry, &file); - else if (!file.getFileId().empty()) - { - const auto [it, inserted] = exLeftOnlyById_.emplace(file.getFileId(), &file); - if (!inserted) //duplicate file ID! NTFS hard link/symlink? - it->second = nullptr; - } } else if (cat == FILE_RIGHT_SIDE_ONLY) { - if (const InSyncFile* dbEntry = getDbEntry(dbFolderR, file.getItemName())) + if (const InSyncFile* dbEntry = getDbEntry(dbFolderR, file.getItemName())) exRightOnlyByPath_.emplace(dbEntry, &file); - else if (!file.getFileId().empty()) - { - const auto [it, inserted] = exRightOnlyById_.emplace(file.getFileId(), &file); - if (!inserted) //duplicate file ID! NTFS hard link/symlink? - it->second = nullptr; - } } } @@ -419,22 +411,59 @@ private: auto getDbEntry = [](const InSyncFolder* dbFolder, const Zstring& folderName) -> const InSyncFolder* { if (dbFolder) - { - auto it = dbFolder->folders.find(folderName); - if (it != dbFolder->folders.end()) + if (const auto it = dbFolder->folders.find(folderName); + it != dbFolder->folders.end()) return &it->second; - } return nullptr; }; - const InSyncFolder* dbEntryL = getDbEntry(dbFolderL, folder.getItemName()); + const InSyncFolder* dbEntryL = getDbEntry(dbFolderL, folder.getItemName()); const InSyncFolder* dbEntryR = dbEntryL; - if (dbFolderL != dbFolderR || getUnicodeNormalForm(folder.getItemName()) != getUnicodeNormalForm(folder.getItemName())) - dbEntryR = getDbEntry(dbFolderR, folder.getItemName()); + if (dbFolderL != dbFolderR || + getUnicodeNormalForm(folder.getItemName< SelectSide::left>()) != + getUnicodeNormalForm(folder.getItemName())) + dbEntryR = getDbEntry(dbFolderR, folder.getItemName()); recurse(folder, dbEntryL, dbEntryR); } } + template + static void purgeDuplicates(std::vector& files, + std::unordered_map& exOneSideById) + { + if (!files.empty()) + { + std::sort(files.begin(), files.end(), [](const FilePair* lhs, const FilePair* rhs) + { return lhs->getFilePrint() < rhs->getFilePrint(); }); + + AFS::FingerPrint prevPrint = files[0]->getFilePrint(); + + for (auto it = files.begin() + 1; it != files.end(); ++it) + if (const AFS::FingerPrint filePrint = (*it)->getFilePrint(); + prevPrint != filePrint) + prevPrint = filePrint; + else //duplicate file ID! NTFS hard link/symlink? + { + const auto dupFirst = it - 1; + const auto dupLast = std::find_if(it + 1, files.end(), [prevPrint](const FilePair* file) + { return file->getFilePrint() != prevPrint; }); + + //remove from model: do *not* store invalid file prints in sync.ffs_db! + std::for_each(dupFirst, dupLast, [](FilePair* file) { file->clearFilePrint(); }); + it = dupLast - 1; + } + + //collect unique file prints for files existing on one side only: + constexpr CompareFileResult oneSideOnlyTag = side == SelectSide::left ? FILE_LEFT_SIDE_ONLY : FILE_RIGHT_SIDE_ONLY; + + for (FilePair* file : files) + if (file->getCategory() == oneSideOnlyTag) + if (const AFS::FingerPrint filePrint = file->getFilePrint(); + filePrint != 0) //skip duplicates marked by clearFilePrint() + exOneSideById.emplace(filePrint, file); + } + } + void detectMovePairs(const InSyncFolder& container) const { for (const auto& [fileName, dbAttrib] : container.files) @@ -444,93 +473,99 @@ private: detectMovePairs(subFolder); } - template + template static bool sameSizeAndDate(const FilePair& file, const InSyncFile& dbFile) { return file.getFileSize() == dbFile.fileSize && - sameFileTime(file.getLastWriteTime(), SelectParam::ref(dbFile.left, dbFile.right).modTime, 2, {}); - //- respect 2 second FAT/FAT32 precision! not user-configurable! - //- "ignoreTimeShiftMinutes" may lead to false positive move detections => let's be conservative and not allow it - // (time shift is only ever required during FAT DST switches) - - //PS: *never* allow 2 sec tolerance as container predicate!! - // => no strict weak ordering relation! reason: no transitivity of equivalence! + file.getLastWriteTime() == SelectParam::ref(dbFile.left, dbFile.right).modTime; + /* do NOT consider FAT_FILE_TIME_PRECISION_SEC: + 1. if DB contains file metadata collected during folder comparison we can be as precise as we want here + 2. if DB contains file metadata *estimated* directly after file copy: + - most file systems store file times with sub-second precision... + - ...except for FAT, but FAT does not have stable file IDs after file copy anyway (see comment below) + => file time comparison with seconds precision is fine! + + PS: *never* allow a tolerance as container predicate!! + => no strict weak ordering relation! reason: no transitivity of equivalence! */ } - template + template FilePair* getAssocFilePair(const InSyncFile& dbFile) const { - const std::unordered_map& exOneSideById = SelectParam::ref(exLeftOnlyById_, exRightOnlyById_); const std::unordered_map& exOneSideByPath = SelectParam::ref(exLeftOnlyByPath_, exRightOnlyByPath_); - { - auto it = exOneSideByPath.find(&dbFile); - if (it != exOneSideByPath.end()) - return it->second; //if there is an association by path, don't care if there is also an association by id, - //even if the association by path doesn't match time and size while the association by id does! - //- there doesn't seem to be (any?) value in allowing this! - //- note: exOneSideById isn't filled in this case, see recurse() - } + const std::unordered_map& exOneSideById = SelectParam::ref(exLeftOnlyById_, exRightOnlyById_); + + if (const auto it = exOneSideByPath.find(&dbFile); + it != exOneSideByPath.end()) + return it->second; //if there is an association by path, don't care if there is also an association by ID, + //even if the association by path doesn't match time and size while the association by ID does! + //there doesn't seem to be (any?) value in allowing this! + + if (const AFS::FingerPrint filePrint = SelectParam::ref(dbFile.left, dbFile.right).filePrint; + filePrint != 0) + if (const auto it = exOneSideById.find(filePrint); + it != exOneSideById.end()) + return it->second; - const AFS::FileId fileId = SelectParam::ref(dbFile.left, dbFile.right).fileId; - if (!fileId.empty()) - { - auto it = exOneSideById.find(fileId); - if (it != exOneSideById.end()) - return it->second; //= nullptr, if duplicate ID! - } return nullptr; } void findAndSetMovePair(const InSyncFile& dbFile) const { if (stillInSync(dbFile, cmpVar_, fileTimeTolerance_, ignoreTimeShiftMinutes_)) - if (FilePair* fileLeftOnly = getAssocFilePair(dbFile)) - if (sameSizeAndDate(*fileLeftOnly, dbFile)) - if (FilePair* fileRightOnly = getAssocFilePair(dbFile)) - if (sameSizeAndDate(*fileRightOnly, dbFile)) - if (fileLeftOnly ->getMoveRef() == nullptr && //don't let a row participate in two move pairs! + if (FilePair* fileLeftOnly = getAssocFilePair(dbFile)) + if (sameSizeAndDate(*fileLeftOnly, dbFile)) + if (FilePair* fileRightOnly = getAssocFilePair(dbFile)) + if (sameSizeAndDate(*fileRightOnly, dbFile)) + { + assert((!fileLeftOnly ->getMoveRef() && + !fileRightOnly->getMoveRef()) || + (fileLeftOnly ->getMoveRef() == fileRightOnly->getId() && + fileRightOnly->getMoveRef() == fileLeftOnly ->getId())); + + if (fileLeftOnly ->getMoveRef() == nullptr && //needless check!? file prints are unique in this context! fileRightOnly->getMoveRef() == nullptr) // { fileLeftOnly ->setMoveRef(fileRightOnly->getId()); //found a pair, mark it! fileRightOnly->setMoveRef(fileLeftOnly ->getId()); // } + } } const CompareVariant cmpVar_; const int fileTimeTolerance_; const std::vector ignoreTimeShiftMinutes_; - std::unordered_map exLeftOnlyById_; //FilePair* == nullptr for duplicate ids! => consider aliasing through symlinks! - std::unordered_map exRightOnlyById_; //=> avoid ambiguity for mixtures of files/symlinks on one side and allow 1-1 mapping only! - //MSVC: std::unordered_map: about twice as fast as std::map for 1 million items! + std::vector filesL_; //collection of *all* file items (with non-null filePrint) + std::vector filesR_; // => detect duplicate file IDs + + std::unordered_map exLeftOnlyById_; //MSVC: twice as fast as std::map for 1 million items! + std::unordered_map exRightOnlyById_; - std::unordered_map exLeftOnlyByPath_; //MSVC: only 4% faster than std::map for 1 million items! + std::unordered_map exLeftOnlyByPath_; //MSVC: only 4% faster than std::map for 1 million items! std::unordered_map exRightOnlyByPath_; - /* - detect renamed files: - - X -> |_| Create right - |_| -> Y Delete right - - is detected as: - - Rename Y to X on right - - Algorithm: - ---------- - DB-file left <--- (name, size, date) ---> DB-file right - | | - | (file ID, size, date) | (file ID, size, date) - | or | or - | (file path, size, date) | (file path, size, date) - \|/ \|/ - file left only file right only - - FAT caveat: File Ids are generally not stable when file is either moved or renamed! - => 1. Move/rename operations on FAT cannot be detected reliably. - => 2. database generally contains wrong file ID on FAT after renaming from .ffs_tmp files => correct file IDs in database only after next sync - => 3. even exFAT screws up (but less than FAT) and changes IDs after file move. Did they learn nothing from the past? - */ + + /* Detect Renamed Files: + + X -> |_| Create right + |_| -> Y Delete right + + resolve as: Rename Y to X on right + + Algorithm: + ---------- + DB-file left <--- (name, size, date) ---> DB-file right + | | + | (file ID, size, date) | (file ID, size, date) + | or | or + | (file path, size, date) | (file path, size, date) + \|/ \|/ + file left only file right only + + FAT caveat: file IDs are generally not stable when file is either moved or renamed! + 1. Move/rename operations on FAT cannot be detected reliably. + 2. database generally contains wrong file ID on FAT after renaming from .ffs_tmp files => correct file IDs in database only after next sync + 3. even exFAT screws up (but less than FAT) and changes IDs after file move. Did they learn nothing from the past? */ }; //---------------------------------------------------------------------------------------------- @@ -569,9 +604,9 @@ private: return; //##################### schedule old temporary files for deletion #################### - if (cat == FILE_LEFT_SIDE_ONLY && endsWith(file.getItemName(), AFS::TEMP_FILE_ENDING)) + if (cat == FILE_LEFT_SIDE_ONLY && endsWith(file.getItemName(), AFS::TEMP_FILE_ENDING)) return file.setSyncDir(SyncDirection::left); - else if (cat == FILE_RIGHT_SIDE_ONLY && endsWith(file.getItemName(), AFS::TEMP_FILE_ENDING)) + else if (cat == FILE_RIGHT_SIDE_ONLY && endsWith(file.getItemName(), AFS::TEMP_FILE_ENDING)) return file.setSyncDir(SyncDirection::right); //#################################################################################### @@ -586,14 +621,14 @@ private: } return nullptr; }; - const InSyncFile* dbEntryL = getDbEntry(dbFolderL, file.getItemName()); + const InSyncFile* dbEntryL = getDbEntry(dbFolderL, file.getItemName()); const InSyncFile* dbEntryR = dbEntryL; - if (dbFolderL != dbFolderR || getUnicodeNormalForm(file.getItemName()) != getUnicodeNormalForm(file.getItemName())) - dbEntryR = getDbEntry(dbFolderR, file.getItemName()); + if (dbFolderL != dbFolderR || getUnicodeNormalForm(file.getItemName()) != getUnicodeNormalForm(file.getItemName())) + dbEntryR = getDbEntry(dbFolderR, file.getItemName()); //evaluation - const bool changeOnLeft = !matchesDbEntry< LEFT_SIDE>(file, dbEntryL, ignoreTimeShiftMinutes_); - const bool changeOnRight = !matchesDbEntry(file, dbEntryR, ignoreTimeShiftMinutes_); + const bool changeOnLeft = !matchesDbEntry< SelectSide::left>(file, dbEntryL, ignoreTimeShiftMinutes_); + const bool changeOnRight = !matchesDbEntry(file, dbEntryR, ignoreTimeShiftMinutes_); if (changeOnLeft != changeOnRight) { @@ -630,14 +665,14 @@ private: } return nullptr; }; - const InSyncSymlink* dbEntryL = getDbEntry(dbFolderL, symlink.getItemName()); + const InSyncSymlink* dbEntryL = getDbEntry(dbFolderL, symlink.getItemName()); const InSyncSymlink* dbEntryR = dbEntryL; - if (dbFolderL != dbFolderR || getUnicodeNormalForm(symlink.getItemName()) != getUnicodeNormalForm(symlink.getItemName())) - dbEntryR = getDbEntry(dbFolderR, symlink.getItemName()); + if (dbFolderL != dbFolderR || getUnicodeNormalForm(symlink.getItemName()) != getUnicodeNormalForm(symlink.getItemName())) + dbEntryR = getDbEntry(dbFolderR, symlink.getItemName()); //evaluation - const bool changeOnLeft = !matchesDbEntry< LEFT_SIDE>(symlink, dbEntryL, ignoreTimeShiftMinutes_); - const bool changeOnRight = !matchesDbEntry(symlink, dbEntryR, ignoreTimeShiftMinutes_); + const bool changeOnLeft = !matchesDbEntry< SelectSide::left>(symlink, dbEntryL, ignoreTimeShiftMinutes_); + const bool changeOnRight = !matchesDbEntry(symlink, dbEntryR, ignoreTimeShiftMinutes_); if (changeOnLeft != changeOnRight) { @@ -662,9 +697,9 @@ private: const CompareDirResult cat = folder.getDirCategory(); //########### schedule abandoned temporary recycle bin directory for deletion ########## - if (cat == DIR_LEFT_SIDE_ONLY && endsWith(folder.getItemName(), AFS::TEMP_FILE_ENDING)) + if (cat == DIR_LEFT_SIDE_ONLY && endsWith(folder.getItemName(), AFS::TEMP_FILE_ENDING)) return setSyncDirectionRec(SyncDirection::left, folder); // - else if (cat == DIR_RIGHT_SIDE_ONLY && endsWith(folder.getItemName(), AFS::TEMP_FILE_ENDING)) + else if (cat == DIR_RIGHT_SIDE_ONLY && endsWith(folder.getItemName(), AFS::TEMP_FILE_ENDING)) return setSyncDirectionRec(SyncDirection::right, folder); //don't recurse below! //####################################################################################### @@ -679,16 +714,16 @@ private: } return nullptr; }; - const InSyncFolder* dbEntryL = getDbEntry(dbFolderL, folder.getItemName()); + const InSyncFolder* dbEntryL = getDbEntry(dbFolderL, folder.getItemName()); const InSyncFolder* dbEntryR = dbEntryL; - if (dbFolderL != dbFolderR || getUnicodeNormalForm(folder.getItemName()) != getUnicodeNormalForm(folder.getItemName())) - dbEntryR = getDbEntry(dbFolderR, folder.getItemName()); + if (dbFolderL != dbFolderR || getUnicodeNormalForm(folder.getItemName()) != getUnicodeNormalForm(folder.getItemName())) + dbEntryR = getDbEntry(dbFolderR, folder.getItemName()); if (cat != DIR_EQUAL) { //evaluation - const bool changeOnLeft = !matchesDbEntry< LEFT_SIDE>(folder, dbEntryL); - const bool changeOnRight = !matchesDbEntry(folder, dbEntryR); + const bool changeOnLeft = !matchesDbEntry< SelectSide::left>(folder, dbEntryL); + const bool changeOnRight = !matchesDbEntry(folder, dbEntryR); if (changeOnLeft != changeOnRight) { @@ -778,10 +813,10 @@ void fff::redetermineSyncDirection(const std::vector 1) - msg += L'\n' + AFS::getDisplayPath(baseFolder->getAbstractPath< LEFT_SIDE>()) + L' ' + getVariantNameWithSymbol(dirCfg.var) + L' ' + - AFS::getDisplayPath(baseFolder->getAbstractPath()); + msg += L'\n' + AFS::getDisplayPath(baseFolder->getAbstractPath< SelectSide::left>()) + L' ' + getVariantNameWithSymbol(dirCfg.var) + L' ' + + AFS::getDisplayPath(baseFolder->getAbstractPath()); - try { callback.reportInfo(msg); /*throw X*/} catch (...) {}; + try { callback.logInfo(msg); /*throw X*/} catch (...) {}; Redetermine::execute(getTwoWayUpdateSet(), *baseFolder); } @@ -1000,12 +1035,12 @@ private: { if (Eval::process(file)) { - if (file.isEmpty()) - file.setActive(matchSize(file) && - matchTime(file)); - else if (file.isEmpty()) - file.setActive(matchSize(file) && - matchTime(file)); + if (file.isEmpty()) + file.setActive(matchSize(file) && + matchTime(file)); + else if (file.isEmpty()) + file.setActive(matchSize(file) && + matchTime(file)); else { //the only case with partially unclear semantics: @@ -1023,10 +1058,10 @@ private: ------------ */ //let's set ? := E - file.setActive((matchSize(file) && - matchTime(file)) || - (matchSize(file) && - matchTime(file))); + file.setActive((matchSize(file) && + matchTime(file)) || + (matchSize(file) && + matchTime(file))); } } } @@ -1035,13 +1070,13 @@ private: { if (Eval::process(symlink)) { - if (symlink.isEmpty()) - symlink.setActive(matchTime(symlink)); - else if (symlink.isEmpty()) - symlink.setActive(matchTime(symlink)); + if (symlink.isEmpty()) + symlink.setActive(matchTime(symlink)); + else if (symlink.isEmpty()) + symlink.setActive(matchTime(symlink)); else - symlink.setActive(matchTime(symlink) || - matchTime (symlink)); + symlink.setActive(matchTime(symlink) || + matchTime (symlink)); } } @@ -1053,13 +1088,13 @@ private: recurse(folder); } - template + template bool matchTime(const T& obj) const { return timeSizeFilter_.matchTime(obj.template getLastWriteTime()); } - template + template bool matchSize(const T& obj) const { return timeSizeFilter_.matchSize(obj.template getFileSize()); @@ -1136,24 +1171,24 @@ private: void processFile(FilePair& file) const { - if (file.isEmpty()) - file.setActive(matchTime(file)); - else if (file.isEmpty()) - file.setActive(matchTime(file)); + if (file.isEmpty()) + file.setActive(matchTime(file)); + else if (file.isEmpty()) + file.setActive(matchTime(file)); else - file.setActive(matchTime(file) || - matchTime(file)); + file.setActive(matchTime(file) || + matchTime(file)); } void processLink(SymlinkPair& link) const { - if (link.isEmpty()) - link.setActive(matchTime(link)); - else if (link.isEmpty()) - link.setActive(matchTime(link)); + if (link.isEmpty()) + link.setActive(matchTime(link)); + else if (link.isEmpty()) + link.setActive(matchTime(link)); else - link.setActive(matchTime(link) || - matchTime (link)); + link.setActive(matchTime(link) || + matchTime (link)); } void processDir(FolderPair& folder) const @@ -1162,7 +1197,7 @@ private: recurse(folder); } - template + template bool matchTime(const T& obj) const { return timeFrom_ <= obj.template getLastWriteTime() && @@ -1212,7 +1247,7 @@ std::optional fff::getPathDependency(const AbstractPath& basePat // - user may have manually excluded the conflicting items or changed the filter settings without running a re-compare bool childItemMightMatch = true; if (relDirPath.empty() || filterP.passDirFilter(relDirPath, &childItemMightMatch) || childItemMightMatch) - return PathDependency({ basePathP, basePathC, relDirPath }); + return PathDependency({basePathP, basePathC, relDirPath}); } } } @@ -1229,26 +1264,26 @@ std::pair fff::getSelectedItemsAsString(std::spanisEmpty()) + if (!fsObj->isEmpty()) { - fileList += AFS::getDisplayPath(fsObj->getAbstractPath()) + L'\n'; + fileList += AFS::getDisplayPath(fsObj->getAbstractPath()) + L'\n'; ++totalDelCount; } for (const FileSystemObject* fsObj : selectionRight) - if (!fsObj->isEmpty()) + if (!fsObj->isEmpty()) { - fileList += AFS::getDisplayPath(fsObj->getAbstractPath()) + L'\n'; + fileList += AFS::getDisplayPath(fsObj->getAbstractPath()) + L'\n'; ++totalDelCount; } - return { fileList, totalDelCount }; + return {fileList, totalDelCount}; } namespace { -template +template void copyToAlternateFolderFrom(const std::vector& rowsToCopy, const AbstractPath& targetFolderPath, bool keepRelPaths, @@ -1257,7 +1292,9 @@ void copyToAlternateFolderFrom(const std::vector& rowsT { auto notifyItemCopy = [&](const std::wstring& statusText, const std::wstring& displayPath) { - callback.reportInfo(replaceCpy(statusText, L"%x", fmtPath(displayPath))); //throw X + std::wstring msg = replaceCpy(statusText, L"%x", fmtPath(displayPath)); + callback.logInfo(msg); //throw X + callback.updateStatus(std::move(msg)); // }; const std::wstring txtCreatingFile (_("Creating file %x" )); const std::wstring txtCreatingFolder(_("Creating folder %x" )); @@ -1319,7 +1356,7 @@ void copyToAlternateFolderFrom(const std::vector& rowsT visitFSObject(*fsObj, [&](const FolderPair& folder) { - ItemStatReporter<> statReporter(1, 0, callback); + ItemStatReporter statReporter(1, 0, callback); notifyItemCopy(txtCreatingFolder, AFS::getDisplayPath(targetPath)); AFS::createFolderIfMissingRecursion(targetPath); //throw FileError @@ -1333,7 +1370,7 @@ void copyToAlternateFolderFrom(const std::vector& rowsT notifyItemCopy(txtCreatingFile, AFS::getDisplayPath(targetPath)); const FileAttributes attr = file.getAttributes(); - const AFS::StreamAttributes sourceAttr{ attr.modTime, attr.fileSize, attr.fileId }; + const AFS::StreamAttributes sourceAttr{attr.modTime, attr.fileSize, attr.filePrint}; copyItem(targetPath, statReporter, [&](const std::function& deleteTargetItem) //throw FileError { @@ -1380,19 +1417,19 @@ void fff::copyToAlternateFolder(std::span rowsToC { std::vector itemSelectionLeft (rowsToCopyOnLeft .begin(), rowsToCopyOnLeft .end()); std::vector itemSelectionRight(rowsToCopyOnRight.begin(), rowsToCopyOnRight.end()); - std::erase_if(itemSelectionLeft, [](const FileSystemObject* fsObj) { return fsObj->isEmpty< LEFT_SIDE>(); }); //needed for correct stats! - std::erase_if(itemSelectionRight, [](const FileSystemObject* fsObj) { return fsObj->isEmpty(); }); // + std::erase_if(itemSelectionLeft, [](const FileSystemObject* fsObj) { return fsObj->isEmpty< SelectSide::left>(); }); //needed for correct stats! + std::erase_if(itemSelectionRight, [](const FileSystemObject* fsObj) { return fsObj->isEmpty(); }); // const int itemTotal = static_cast(itemSelectionLeft.size() + itemSelectionRight.size()); int64_t bytesTotal = 0; for (const FileSystemObject* fsObj : itemSelectionLeft) visitFSObject(*fsObj, [](const FolderPair& folder) {}, - [&](const FilePair& file) { bytesTotal += static_cast(file.getFileSize()); }, [](const SymlinkPair& symlink) {}); + [&](const FilePair& file) { bytesTotal += static_cast(file.getFileSize()); }, [](const SymlinkPair& symlink) {}); for (const FileSystemObject* fsObj : itemSelectionRight) visitFSObject(*fsObj, [](const FolderPair& folder) {}, - [&](const FilePair& file) { bytesTotal += static_cast(file.getFileSize()); }, [](const SymlinkPair& symlink) {}); + [&](const FilePair& file) { bytesTotal += static_cast(file.getFileSize()); }, [](const SymlinkPair& symlink) {}); callback.initNewPhase(itemTotal, bytesTotal, ProcessPhase::none); //throw X @@ -1400,22 +1437,24 @@ void fff::copyToAlternateFolder(std::span rowsToC const AbstractPath targetFolderPath = createAbstractPath(targetFolderPathPhrase); - copyToAlternateFolderFrom< LEFT_SIDE>(itemSelectionLeft, targetFolderPath, keepRelPaths, overwriteIfExists, callback); - copyToAlternateFolderFrom(itemSelectionRight, targetFolderPath, keepRelPaths, overwriteIfExists, callback); + copyToAlternateFolderFrom< SelectSide::left>(itemSelectionLeft, targetFolderPath, keepRelPaths, overwriteIfExists, callback); + copyToAlternateFolderFrom(itemSelectionRight, targetFolderPath, keepRelPaths, overwriteIfExists, callback); } //############################################################################################################ namespace { -template +template void deleteFromGridAndHDOneSide(std::vector& rowsToDelete, bool useRecycleBin, PhaseCallback& callback) { auto notifyItemDeletion = [&](const std::wstring& statusText, const std::wstring& displayPath) { - callback.reportInfo(replaceCpy(statusText, L"%x", fmtPath(displayPath))); //throw X + std::wstring msg = replaceCpy(statusText, L"%x", fmtPath(displayPath)); + callback.logInfo(msg); //throw X + callback.updateStatus(std::move(msg)); // }; std::wstring txtRemovingFile; @@ -1501,7 +1540,7 @@ void deleteFromGridAndHDOneSide(std::vector& rowsToDelete, } -template +template void categorize(const std::vector& rows, std::vector& deletePermanent, std::vector& deleteRecyler, @@ -1556,8 +1595,8 @@ void fff::deleteFromGridAndHD(const std::vector& rowsToDelete std::vector deleteLeft = rowsToDeleteOnLeft; std::vector deleteRight = rowsToDeleteOnRight; - std::erase_if(deleteLeft, [](const FileSystemObject* fsObj) { return fsObj->isEmpty< LEFT_SIDE>(); }); //needed? - std::erase_if(deleteRight, [](const FileSystemObject* fsObj) { return fsObj->isEmpty(); }); //yes, for correct stats: + std::erase_if(deleteLeft, [](const FileSystemObject* fsObj) { return fsObj->isEmpty< SelectSide::left>(); }); //needed? + std::erase_if(deleteRight, [](const FileSystemObject* fsObj) { return fsObj->isEmpty(); }); //yes, for correct stats: const int itemCount = static_cast(deleteLeft.size() + deleteRight.size()); callback.initNewPhase(itemCount, 0, ProcessPhase::none); //throw X @@ -1577,7 +1616,7 @@ void fff::deleteFromGridAndHD(const std::vector& rowsToDelete { FileSystemObject& fsObj = **it; //all pointers are required(!) to be bound - if (fsObj.isEmpty() != fsObj.isEmpty()) //make sure objects exists on one side only + if (fsObj.isEmpty() != fsObj.isEmpty()) //make sure objects exists on one side only { auto cfgIter = baseFolderCfgs.find(&fsObj.base()); assert(cfgIter != baseFolderCfgs.end()); @@ -1586,11 +1625,11 @@ void fff::deleteFromGridAndHD(const std::vector& rowsToDelete SyncDirection newDir = SyncDirection::none; if (cfgIter->second.var == SyncVariant::twoWay) - newDir = fsObj.isEmpty() ? SyncDirection::right : SyncDirection::left; + newDir = fsObj.isEmpty() ? SyncDirection::right : SyncDirection::left; else { const DirectionSet& dirCfg = extractDirections(cfgIter->second); - newDir = fsObj.isEmpty() ? dirCfg.exRightSideOnly : dirCfg.exLeftSideOnly; + newDir = fsObj.isEmpty() ? dirCfg.exRightSideOnly : dirCfg.exLeftSideOnly; } setSyncDirectionRec(newDir, fsObj); //set new direction (recursively) @@ -1611,8 +1650,8 @@ void fff::deleteFromGridAndHD(const std::vector& rowsToDelete std::vector deleteRecylerRight; std::map recyclerSupported; - categorize< LEFT_SIDE>(deleteLeft, deletePermanentLeft, deleteRecylerLeft, useRecycleBin, recyclerSupported, callback); //throw X - categorize(deleteRight, deletePermanentRight, deleteRecylerRight, useRecycleBin, recyclerSupported, callback); // + categorize< SelectSide::left>(deleteLeft, deletePermanentLeft, deleteRecylerLeft, useRecycleBin, recyclerSupported, callback); //throw X + categorize(deleteRight, deletePermanentRight, deleteRecylerRight, useRecycleBin, recyclerSupported, callback); // //windows: check if recycle bin really exists; if not, Windows will silently delete, which is wrong if (useRecycleBin && @@ -1627,11 +1666,11 @@ void fff::deleteFromGridAndHD(const std::vector& rowsToDelete callback.reportWarning(msg, warnRecyclerMissing); //throw? } - deleteFromGridAndHDOneSide(deleteRecylerLeft, true, callback); - deleteFromGridAndHDOneSide(deletePermanentLeft, false, callback); + deleteFromGridAndHDOneSide(deleteRecylerLeft, true, callback); + deleteFromGridAndHDOneSide(deletePermanentLeft, false, callback); - deleteFromGridAndHDOneSide(deleteRecylerRight, true, callback); - deleteFromGridAndHDOneSide(deletePermanentRight, false, callback); + deleteFromGridAndHDOneSide(deleteRecylerRight, true, callback); + deleteFromGridAndHDOneSide(deletePermanentRight, false, callback); } //############################################################################################################ @@ -1693,7 +1732,7 @@ void TempFileBuffer::createTempFiles(const std::set& workLoad, P MemoryStreamOut cookie; //create hash to distinguish different versions and file locations writeNumber (cookie, descr.attr.modTime); writeNumber (cookie, descr.attr.fileSize); - writeContainer(cookie, descr.attr.fileId); + writeNumber (cookie, descr.attr.filePrint); writeNumber (cookie, descr.attr.isFollowedSymlink); writeContainer(cookie, AFS::getInitPathPhrase(descr.path)); @@ -1706,13 +1745,15 @@ void TempFileBuffer::createTempFiles(const std::set& workLoad, P const Zstring tempFileName = Zstring(fileName.begin(), it) + Zstr('~') + descrHash + Zstring(it, fileName.end()); const Zstring tempFilePath = appendSeparator(tempFolderPath_) + tempFileName; - const AFS::StreamAttributes sourceAttr{ descr.attr.modTime, descr.attr.fileSize, descr.attr.fileId }; + const AFS::StreamAttributes sourceAttr{descr.attr.modTime, descr.attr.fileSize, descr.attr.filePrint}; tryReportingError([&] { - ItemStatReporter<> statReporter(1, descr.attr.fileSize, callback); + ItemStatReporter statReporter(1, descr.attr.fileSize, callback); - callback.reportInfo(replaceCpy(_("Creating file %x"), L"%x", fmtPath(tempFilePath))); //throw X + std::wstring msg = replaceCpy(_("Creating file %x"), L"%x", fmtPath(tempFilePath)); //throw X + callback.logInfo(msg); //throw X + callback.updateStatus(std::move(msg)); // auto notifyUnbufferedIO = [&](int64_t bytesDelta) { diff --git a/FreeFileSync/Source/base/binary.cpp b/FreeFileSync/Source/base/binary.cpp index f3199f67..49bdd6eb 100644 --- a/FreeFileSync/Source/base/binary.cpp +++ b/FreeFileSync/Source/base/binary.cpp @@ -39,7 +39,7 @@ const size_t BLOCK_SIZE_MAX = 16 * 1024 * 1024; struct StreamReader { - StreamReader(const AbstractPath& filePath, const IOCallback& notifyUnbufferedIO) : //throw FileError + StreamReader(const AbstractPath& filePath, const IoCallback& notifyUnbufferedIO) : //throw FileError stream_(AFS::getInputStream(filePath, notifyUnbufferedIO)), //throw FileError, ErrorFileLocked defaultBlockSize_(stream_->getBlockSize()), dynamicBlockSize_(defaultBlockSize_) { assert(defaultBlockSize_ > 0); } @@ -94,7 +94,7 @@ private: } -bool fff::filesHaveSameContent(const AbstractPath& filePath1, const AbstractPath& filePath2, const IOCallback& notifyUnbufferedIO /*throw X*/) //throw FileError, X +bool fff::filesHaveSameContent(const AbstractPath& filePath1, const AbstractPath& filePath2, const IoCallback& notifyUnbufferedIO /*throw X*/) //throw FileError, X { int64_t totalUnbufferedIO = 0; diff --git a/FreeFileSync/Source/base/binary.h b/FreeFileSync/Source/base/binary.h index 3c2db11a..1223cb05 100644 --- a/FreeFileSync/Source/base/binary.h +++ b/FreeFileSync/Source/base/binary.h @@ -14,7 +14,7 @@ namespace fff { bool filesHaveSameContent(const AbstractPath& filePath1, //throw FileError, X const AbstractPath& filePath2, - const zen::IOCallback& notifyUnbufferedIO /*throw X*/); + const zen::IoCallback& notifyUnbufferedIO /*throw X*/); } #endif //BINARY_H_3941281398513241134 diff --git a/FreeFileSync/Source/base/comparison.cpp b/FreeFileSync/Source/base/comparison.cpp index 87055154..bf1afafa 100644 --- a/FreeFileSync/Source/base/comparison.cpp +++ b/FreeFileSync/Source/base/comparison.cpp @@ -17,6 +17,7 @@ #include "cmp_filetime.h" #include "status_handler_impl.h" #include "../afs/concrete.h" +#include "../afs/native.h" using namespace zen; using namespace fff; @@ -25,7 +26,7 @@ using namespace fff; std::vector fff::extractCompareCfg(const MainConfiguration& mainCfg) { //merge first and additional pairs - std::vector localCfgs = { mainCfg.firstPair }; + std::vector localCfgs = {mainCfg.firstPair}; append(localCfgs, mainCfg.additionalPairs); std::vector output; @@ -206,9 +207,9 @@ ComparisonBuffer::ComparisonBuffer(const std::set& foldersToRead, const int64_t totalTimeSec = std::chrono::duration_cast(std::chrono::steady_clock::now() - compareStartTime).count(); - callback.reportInfo(_("Comparison finished:") + L' ' + - _P("1 item found", "%x items found", itemsReported) + L" | " + - _("Time elapsed:") + L' ' + copyStringTo(wxTimeSpan::Seconds(totalTimeSec).Format())); //throw X + callback.logInfo(_("Comparison finished:") + L' ' + + _P("1 item found", "%x items found", itemsReported) + L" | " + + _("Time elapsed:") + L' ' + copyStringTo(wxTimeSpan::Seconds(totalTimeSec).Format())); //throw X } @@ -222,7 +223,7 @@ const wchar_t arrowRight[] = L"->"; //NOTE: conflict texts are NOT expected to contain additional path info (already implicit through associated item!) // => only add path info if information is relevant, e.g. conflict is specific to left/right side only -template inline +template inline Zstringc getConflictInvalidDate(const FileOrLinkPair& file) { return utfTo(replaceCpy(_("File %x has an invalid date."), L"%x", fmtPath(AFS::getDisplayPath(file.template getAbstractPath()))) + L'\n' + @@ -233,8 +234,8 @@ Zstringc getConflictInvalidDate(const FileOrLinkPair& file) Zstringc getConflictSameDateDiffSize(const FilePair& file) { return utfTo(_("Files have the same date but a different size.") + L'\n' + - arrowLeft + L' ' + _("Date:") + L' ' + formatUtcToLocalTime(file.getLastWriteTime< LEFT_SIDE>()) + L" " + _("Size:") + L' ' + formatNumber(file.getFileSize()) + L'\n' + - arrowRight + L' ' + _("Date:") + L' ' + formatUtcToLocalTime(file.getLastWriteTime()) + L" " + _("Size:") + L' ' + formatNumber(file.getFileSize())); + arrowLeft + L' ' + _("Date:") + L' ' + formatUtcToLocalTime(file.getLastWriteTime< SelectSide::left>()) + L" " + _("Size:") + L' ' + formatNumber(file.getFileSize()) + L'\n' + + arrowRight + L' ' + _("Date:") + L' ' + formatUtcToLocalTime(file.getLastWriteTime()) + L" " + _("Size:") + L' ' + formatNumber(file.getFileSize())); } @@ -247,8 +248,8 @@ Zstringc getConflictSkippedBinaryComparison() Zstringc getDescrDiffMetaShortnameCase(const FileSystemObject& fsObj) { return utfTo(_("Items differ in attributes only") + L'\n' + - arrowLeft + L' ' + fmtPath(fsObj.getItemName< LEFT_SIDE>()) + L'\n' + - arrowRight + L' ' + fmtPath(fsObj.getItemName())); + arrowLeft + L' ' + fmtPath(fsObj.getItemName< SelectSide::left>()) + L'\n' + + arrowRight + L' ' + fmtPath(fsObj.getItemName())); } @@ -257,8 +258,8 @@ template Zstringc getDescrDiffMetaData(const FileOrLinkPair& file) { return utfTo(_("Items differ in attributes only") + L'\n' + - arrowLeft + L' ' + _("Date:") + L' ' + formatUtcToLocalTime(file.template getLastWriteTime< LEFT_SIDE>()) + L'\n' + - arrowRight + L' ' + _("Date:") + L' ' + formatUtcToLocalTime(file.template getLastWriteTime())); + arrowLeft + L' ' + _("Date:") + L' ' + formatUtcToLocalTime(file.template getLastWriteTime< SelectSide::left>()) + L'\n' + + arrowRight + L' ' + _("Date:") + L' ' + formatUtcToLocalTime(file.template getLastWriteTime())); } #endif @@ -273,16 +274,16 @@ Zstringc getConflictAmbiguousItemName(const Zstring& itemName) void categorizeSymlinkByTime(SymlinkPair& symlink) { //categorize symlinks that exist on both sides - switch (compareFileTime(symlink.getLastWriteTime(), - symlink.getLastWriteTime(), symlink.base().getFileTimeTolerance(), symlink.base().getIgnoredTimeShift())) + switch (compareFileTime(symlink.getLastWriteTime(), + symlink.getLastWriteTime(), symlink.base().getFileTimeTolerance(), symlink.base().getIgnoredTimeShift())) { case TimeResult::equal: //Caveat: //1. SYMLINK_EQUAL may only be set if short names match in case: InSyncFolder's mapping tables use short name as a key! see db_file.cpp //2. harmonize with "bool stillInSync()" in algorithm.cpp - if (getUnicodeNormalForm(symlink.getItemName< LEFT_SIDE>()) == - getUnicodeNormalForm(symlink.getItemName())) + if (getUnicodeNormalForm(symlink.getItemName< SelectSide::left>()) == + getUnicodeNormalForm(symlink.getItemName())) symlink.setCategory(); else symlink.setCategoryDiffMetadata(getDescrDiffMetaShortnameCase(symlink)); @@ -297,11 +298,11 @@ void categorizeSymlinkByTime(SymlinkPair& symlink) break; case TimeResult::leftInvalid: - symlink.setCategoryConflict(getConflictInvalidDate(symlink)); + symlink.setCategoryConflict(getConflictInvalidDate(symlink)); break; case TimeResult::rightInvalid: - symlink.setCategoryConflict(getConflictInvalidDate(symlink)); + symlink.setCategoryConflict(getConflictInvalidDate(symlink)); break; } } @@ -321,18 +322,18 @@ std::shared_ptr ComparisonBuffer::compareByTimeSize(const Resolv //categorize files that exist on both sides for (FilePair* file : uncategorizedFiles) { - switch (compareFileTime(file->getLastWriteTime(), - file->getLastWriteTime(), fileTimeTolerance_, fpConfig.ignoreTimeShiftMinutes)) + switch (compareFileTime(file->getLastWriteTime(), + file->getLastWriteTime(), fileTimeTolerance_, fpConfig.ignoreTimeShiftMinutes)) { case TimeResult::equal: //Caveat: //1. FILE_EQUAL may only be set if short names match in case: InSyncFolder's mapping tables use short name as a key! see db_file.cpp //2. FILE_EQUAL is expected to mean identical file sizes! See InSyncFile //3. harmonize with "bool stillInSync()" in algorithm.cpp, FilePair::setSyncedTo() in file_hierarchy.h - if (file->getFileSize() == file->getFileSize()) + if (file->getFileSize() == file->getFileSize()) { - if (getUnicodeNormalForm(file->getItemName< LEFT_SIDE>()) == - getUnicodeNormalForm(file->getItemName())) + if (getUnicodeNormalForm(file->getItemName< SelectSide::left>()) == + getUnicodeNormalForm(file->getItemName())) file->setCategory(); else file->setCategoryDiffMetadata(getDescrDiffMetaShortnameCase(*file)); @@ -350,11 +351,11 @@ std::shared_ptr ComparisonBuffer::compareByTimeSize(const Resolv break; case TimeResult::leftInvalid: - file->setCategoryConflict(getConflictInvalidDate(*file)); + file->setCategoryConflict(getConflictInvalidDate(*file)); break; case TimeResult::rightInvalid: - file->setCategoryConflict(getConflictInvalidDate(*file)); + file->setCategoryConflict(getConflictInvalidDate(*file)); break; } } @@ -367,14 +368,14 @@ namespace void categorizeSymlinkByContent(SymlinkPair& symlink, PhaseCallback& callback) { //categorize symlinks that exist on both sides - callback.updateStatus(replaceCpy(_("Resolving symbolic link %x"), L"%x", fmtPath(AFS::getDisplayPath(symlink.getAbstractPath< LEFT_SIDE>())))); //throw X - callback.updateStatus(replaceCpy(_("Resolving symbolic link %x"), L"%x", fmtPath(AFS::getDisplayPath(symlink.getAbstractPath())))); //throw X + callback.updateStatus(replaceCpy(_("Resolving symbolic link %x"), L"%x", fmtPath(AFS::getDisplayPath(symlink.getAbstractPath< SelectSide::left>())))); //throw X + callback.updateStatus(replaceCpy(_("Resolving symbolic link %x"), L"%x", fmtPath(AFS::getDisplayPath(symlink.getAbstractPath())))); //throw X bool equalContent = false; const std::wstring errMsg = tryReportingError([&] { - equalContent = AFS::equalSymlinkContent(symlink.getAbstractPath< LEFT_SIDE>(), - symlink.getAbstractPath()); //throw FileError + equalContent = AFS::equalSymlinkContent(symlink.getAbstractPath< SelectSide::left>(), + symlink.getAbstractPath()); //throw FileError }, callback); //throw X if (!errMsg.empty()) @@ -387,11 +388,11 @@ void categorizeSymlinkByContent(SymlinkPair& symlink, PhaseCallback& callback) //1. SYMLINK_EQUAL may only be set if short names match in case: InSyncFolder's mapping tables use short name as a key! see db_file.cpp //2. harmonize with "bool stillInSync()" in algorithm.cpp, FilePair::setSyncedTo() in file_hierarchy.h - if (getUnicodeNormalForm(symlink.getItemName< LEFT_SIDE>()) != - getUnicodeNormalForm(symlink.getItemName())) + if (getUnicodeNormalForm(symlink.getItemName< SelectSide::left>()) != + getUnicodeNormalForm(symlink.getItemName())) symlink.setCategoryDiffMetadata(getDescrDiffMetaShortnameCase(symlink)); - //else if (!sameFileTime(symlink.getLastWriteTime(), - // symlink.getLastWriteTime(), symlink.base().getFileTimeTolerance(), symlink.base().getIgnoredTimeShift())) + //else if (!sameFileTime(symlink.getLastWriteTime(), + // symlink.getLastWriteTime(), symlink.base().getFileTimeTolerance(), symlink.base().getIgnoredTimeShift())) // symlink.setCategoryDiffMetadata(getDescrDiffMetaData(symlink)); else symlink.setCategory(); @@ -422,10 +423,10 @@ std::shared_ptr ComparisonBuffer::compareBySize(const ResolvedFo //1. FILE_EQUAL may only be set if short names match in case: InSyncFolder's mapping tables use short name as a key! see db_file.cpp //2. FILE_EQUAL is expected to mean identical file sizes! See InSyncFile //3. harmonize with "bool stillInSync()" in algorithm.cpp, FilePair::setSyncedTo() in file_hierarchy.h - if (file->getFileSize() == file->getFileSize()) + if (file->getFileSize() == file->getFileSize()) { - if (getUnicodeNormalForm(file->getItemName< LEFT_SIDE>()) == - getUnicodeNormalForm(file->getItemName())) + if (getUnicodeNormalForm(file->getItemName< SelectSide::left>()) == + getUnicodeNormalForm(file->getItemName())) file->setCategory(); else file->setCategoryDiffMetadata(getDescrDiffMetaShortnameCase(*file)); @@ -444,7 +445,7 @@ namespace parallel //-------------------------------------------------------------- inline bool filesHaveSameContent(const AbstractPath& filePath1, const AbstractPath& filePath2, //throw FileError, X - const IOCallback& notifyUnbufferedIO /*throw X*/, + const IoCallback& notifyUnbufferedIO /*throw X*/, std::mutex& singleThread) { return parallelScope([=] { return filesHaveSameContent(filePath1, filePath2, notifyUnbufferedIO); /*throw FileError, X*/ }, singleThread); } } @@ -459,7 +460,7 @@ void categorizeFileByContent(FilePair& file, const std::wstring& txtComparingCon bool haveSameContent = false; const std::wstring errMsg = tryReportingError([&] { - AsyncItemStatReporter statReporter(1, file.getFileSize(), acb); + AsyncItemStatReporter statReporter(1, file.getFileSize(), acb); //callbacks run *outside* singleThread_ lock! => fine auto notifyUnbufferedIO = [&statReporter](int64_t bytesDelta) @@ -468,8 +469,8 @@ void categorizeFileByContent(FilePair& file, const std::wstring& txtComparingCon interruptionPoint(); //throw ThreadStopRequest }; - haveSameContent = parallel::filesHaveSameContent(file.getAbstractPath< LEFT_SIDE>(), - file.getAbstractPath(), notifyUnbufferedIO, singleThread); //throw FileError, ThreadStopRequest + haveSameContent = parallel::filesHaveSameContent(file.getAbstractPath< SelectSide::left>(), + file.getAbstractPath(), notifyUnbufferedIO, singleThread); //throw FileError, ThreadStopRequest statReporter.reportDelta(1, 0); }, acb); //throw ThreadStopRequest @@ -483,12 +484,12 @@ void categorizeFileByContent(FilePair& file, const std::wstring& txtComparingCon //1. FILE_EQUAL may only be set if short names match in case: InSyncFolder's mapping tables use short name as a key! see db_file.cpp //2. FILE_EQUAL is expected to mean identical file sizes! See InSyncFile //3. harmonize with "bool stillInSync()" in algorithm.cpp, FilePair::setSyncedTo() in file_hierarchy.h - if (getUnicodeNormalForm(file.getItemName< LEFT_SIDE>()) != - getUnicodeNormalForm(file.getItemName())) + if (getUnicodeNormalForm(file.getItemName< SelectSide::left>()) != + getUnicodeNormalForm(file.getItemName())) file.setCategoryDiffMetadata(getDescrDiffMetaShortnameCase(file)); #if 0 //don't synchronize modtime only see FolderPairSyncer::synchronizeFileInt(), SO_COPY_METADATA_TO_* - else if (!sameFileTime(file.getLastWriteTime(), - file.getLastWriteTime(), file.base().getFileTimeTolerance(), file.base().getIgnoredTimeShift())) + else if (!sameFileTime(file.getLastWriteTime(), + file.getLastWriteTime(), file.base().getFileTimeTolerance(), file.base().getIgnoredTimeShift())) file.setCategoryDiffMetadata(getDescrDiffMetaData(file)); #endif else @@ -521,7 +522,7 @@ std::list> ComparisonBuffer::compareByContent(co { ParallelOps& posL = parallelOpsStatus[basePathL.afsDevice]; ParallelOps& posR = parallelOpsStatus[basePathR.afsDevice]; - fpWorkload.push_back({ posL, posR, std::move(filesToCompareBytewise) }); + fpWorkload.push_back({posL, posR, std::move(filesToCompareBytewise)}); }; //PERF_START; @@ -541,7 +542,7 @@ std::list> ComparisonBuffer::compareByContent(co //in order to separate into two processes (scanning and comparing) for (FilePair* file : undefinedFiles) //pre-check: files have different content if they have a different file size (must not be FILE_EQUAL: see InSyncFile) - if (file->getFileSize() != file->getFileSize()) + if (file->getFileSize() != file->getFileSize()) file->setCategory(); else { @@ -553,8 +554,8 @@ std::list> ComparisonBuffer::compareByContent(co filesToCompareBytewise.push_back(file); } if (!filesToCompareBytewise.empty()) - addToBinaryWorkload(output.back()->getAbstractPath< LEFT_SIDE>(), - output.back()->getAbstractPath(), std::move(filesToCompareBytewise)); + addToBinaryWorkload(output.back()->getAbstractPath< SelectSide::left>(), + output.back()->getAbstractPath(), std::move(filesToCompareBytewise)); //finish symlink categorization for (SymlinkPair* symlink : uncategorizedLinks) @@ -571,7 +572,7 @@ std::list> ComparisonBuffer::compareByContent(co itemsTotal += bwl.filesToCompareBytewise.size(); for (const FilePair* file : bwl.filesToCompareBytewise) - bytesTotal += file->getFileSize(); //left and right file sizes are equal + bytesTotal += file->getFileSize(); //left and right file sizes are equal } cb_.initNewPhase(itemsTotal, bytesTotal, ProcessPhase::comparingContent); //throw X @@ -594,7 +595,7 @@ std::list> ComparisonBuffer::compareByContent(co BinaryWorkload& bwl = fpWorkload[j]; ParallelOps& posL = bwl.parallelOpsL; ParallelOps& posR = bwl.parallelOpsR; - const size_t newTaskCount = std::min({ 1 - posL.current, 1 - posR.current, bwl.filesToCompareBytewise.size() }); + const size_t newTaskCount = std::min({1 - posL.current, 1 - posR.current, bwl.filesToCompareBytewise.size()}); if (&posL != &posR) posL.current += newTaskCount; // posR.current += newTaskCount; //consider aliasing! @@ -659,7 +660,7 @@ public: private: void mergeTwoSides(const FolderContainer& lhs, const FolderContainer& rhs, const Zstringc* errorMsg, ContainerObject& output); - template + template void fillOneSide(const FolderContainer& folderCont, const Zstringc* errorMsg, ContainerObject& output); const Zstringc* checkFailedRead(FileSystemObject& fsObj, const Zstringc* errorMsg); @@ -690,7 +691,7 @@ const Zstringc* MergeSides::checkFailedRead(FileSystemObject& fsObj, const Zstri } -template +template void MergeSides::fillOneSide(const FolderContainer& folderCont, const Zstringc* errorMsg, ContainerObject& output) { for (const auto& [fileName, attrib] : folderCont.files) @@ -726,8 +727,8 @@ void matchFolders(const MapType& mapLeft, const MapType& mapRight, ProcessLeftOn std::vector fileList; fileList.reserve(mapLeft.size() + mapRight.size()); //perf: ~5% shorter runtime - for (const auto& item : mapLeft ) fileList.push_back({ getUpperCase(item.first), &item, true }); - for (const auto& item : mapRight) fileList.push_back({ getUpperCase(item.first), &item, false }); + for (const auto& item : mapLeft ) fileList.push_back({getUpperCase(item.first), &item, true}); + for (const auto& item : mapRight) fileList.push_back({getUpperCase(item.first), &item, false}); //primary sort: ignore unicode normal form and case //bonus: natural default sequence on file guid UI @@ -792,12 +793,12 @@ void MergeSides::mergeTwoSides(const FolderContainer& lhs, const FolderContainer matchFolders(lhs.files, rhs.files, [&](const FileData& fileLeft, const Zstringc* conflictMsg) { - FilePair& newItem = output.addSubFile< LEFT_SIDE>(fileLeft .first, fileLeft .second); + FilePair& newItem = output.addSubFile< SelectSide::left>(fileLeft .first, fileLeft .second); checkFailedRead(newItem, conflictMsg ? conflictMsg : errorMsg); }, [&](const FileData& fileRight, const Zstringc* conflictMsg) { - FilePair& newItem = output.addSubFile(fileRight.first, fileRight.second); + FilePair& newItem = output.addSubFile(fileRight.first, fileRight.second); checkFailedRead(newItem, conflictMsg ? conflictMsg : errorMsg); }, [&](const FileData& fileLeft, const FileData& fileRight) @@ -817,12 +818,12 @@ void MergeSides::mergeTwoSides(const FolderContainer& lhs, const FolderContainer matchFolders(lhs.symlinks, rhs.symlinks, [&](const SymlinkData& symlinkLeft, const Zstringc* conflictMsg) { - SymlinkPair& newItem = output.addSubLink< LEFT_SIDE>(symlinkLeft .first, symlinkLeft .second); + SymlinkPair& newItem = output.addSubLink< SelectSide::left>(symlinkLeft .first, symlinkLeft .second); checkFailedRead(newItem, conflictMsg ? conflictMsg : errorMsg); }, [&](const SymlinkData& symlinkRight, const Zstringc* conflictMsg) { - SymlinkPair& newItem = output.addSubLink(symlinkRight.first, symlinkRight.second); + SymlinkPair& newItem = output.addSubLink(symlinkRight.first, symlinkRight.second); checkFailedRead(newItem, conflictMsg ? conflictMsg : errorMsg); }, [&](const SymlinkData& symlinkLeft, const SymlinkData& symlinkRight) //both sides @@ -841,15 +842,15 @@ void MergeSides::mergeTwoSides(const FolderContainer& lhs, const FolderContainer matchFolders(lhs.folders, rhs.folders, [&](const FolderData& dirLeft, const Zstringc* conflictMsg) { - FolderPair& newFolder = output.addSubFolder(dirLeft.first, dirLeft.second.first); + FolderPair& newFolder = output.addSubFolder(dirLeft.first, dirLeft.second.first); const Zstringc* errorMsgNew = checkFailedRead(newFolder, conflictMsg ? conflictMsg : errorMsg); - this->fillOneSide(dirLeft.second.second, errorMsgNew, newFolder); //recurse + this->fillOneSide(dirLeft.second.second, errorMsgNew, newFolder); //recurse }, [&](const FolderData& dirRight, const Zstringc* conflictMsg) { - FolderPair& newFolder = output.addSubFolder(dirRight.first, dirRight.second.first); + FolderPair& newFolder = output.addSubFolder(dirRight.first, dirRight.second.first); const Zstringc* errorMsgNew = checkFailedRead(newFolder, conflictMsg ? conflictMsg : errorMsg); - this->fillOneSide(dirRight.second.second, errorMsgNew, newFolder); //recurse + this->fillOneSide(dirRight.second.second, errorMsgNew, newFolder); //recurse }, [&](const FolderData& dirLeft, const FolderData& dirRight) { @@ -904,7 +905,7 @@ std::shared_ptr ComparisonBuffer::performComparison(const Resolv auto getDirValue = [&](const AbstractPath& folderPath) -> const DirectoryValue* { - auto it = directoryBuffer_.find({ folderPath, fpCfg.filter.nameFilter, fpCfg.handleSymlinks }); + auto it = directoryBuffer_.find({folderPath, fpCfg.filter.nameFilter, fpCfg.handleSymlinks}); return it != directoryBuffer_.end() ? &it->second : nullptr; }; @@ -986,7 +987,7 @@ FolderComparison fff::compare(WarningDialogs& warnings, //indicator at the very beginning of the log to make sense of "total time" //init process: keep at beginning so that all gui elements are initialized properly callback.initNewPhase(-1, -1, ProcessPhase::scanning); //throw X; it's unknown how many files will be scanned => -1 objects - //callback.reportInfo(Comparison started")); -> still useful? + //callback.logInfo(Comparison started")); -> still useful? //------------------------------------------------------------------------------- @@ -1006,7 +1007,7 @@ FolderComparison fff::compare(WarningDialogs& warnings, } catch (const FileError& e) //failure is not critical => log only { - callback.reportInfo(e.toString()); //throw X + callback.logInfo(e.toString()); //throw X } const ResolvedBaseFolders& resInfo = initializeBaseFolders(fpCfgList, @@ -1067,8 +1068,10 @@ FolderComparison fff::compare(WarningDialogs& warnings, { std::set folderPathsToLock; for (const AbstractPath& folderPath : resInfo.existingBaseFolders) - if (std::optional nativePath = AFS::getNativeItemPath(folderPath)) //restrict directory locking to native paths until further - folderPathsToLock.insert(*nativePath); + + if (const Zstring& nativePath = getNativeItemPath(folderPath); //restrict directory locking to native paths until further + !nativePath.empty()) + folderPathsToLock.insert(nativePath); dirLocks = std::make_unique(folderPathsToLock, warnings.warnDirectoryLockFailed, callback); } @@ -1081,9 +1084,9 @@ FolderComparison fff::compare(WarningDialogs& warnings, for (const auto& [folderPair, fpCfg] : workLoad) { if (basefolderExisting(folderPair.folderPathLeft)) //only traverse *currently existing* folders: at this point user is aware that non-ex + empty string are seen as empty folder! - foldersToRead.emplace(DirectoryKey({ folderPair.folderPathLeft, fpCfg.filter.nameFilter, fpCfg.handleSymlinks })); + foldersToRead.emplace(DirectoryKey({folderPair.folderPathLeft, fpCfg.filter.nameFilter, fpCfg.handleSymlinks})); if (basefolderExisting(folderPair.folderPathRight)) - foldersToRead.emplace(DirectoryKey({ folderPair.folderPathRight, fpCfg.filter.nameFilter, fpCfg.handleSymlinks })); + foldersToRead.emplace(DirectoryKey({folderPair.folderPathRight, fpCfg.filter.nameFilter, fpCfg.handleSymlinks})); } FolderComparison output; @@ -1100,7 +1103,7 @@ FolderComparison fff::compare(WarningDialogs& warnings, std::vector> workLoadByContent; for (const auto& [folderPair, fpCfg] : workLoad) if (fpCfg.compareVar == CompareVariant::content) - workLoadByContent.push_back({ folderPair, fpCfg }); + workLoadByContent.push_back({folderPair, fpCfg}); std::list> outputByContent = cmpBuff.compareByContent(workLoadByContent); diff --git a/FreeFileSync/Source/base/db_file.cpp b/FreeFileSync/Source/base/db_file.cpp index ed32e884..27f47565 100644 --- a/FreeFileSync/Source/base/db_file.cpp +++ b/FreeFileSync/Source/base/db_file.cpp @@ -11,6 +11,7 @@ #include #include #include "../afs/concrete.h" +#include "../afs/native.h" #include "status_handler_impl.h" @@ -23,7 +24,7 @@ namespace //------------------------------------------------------------------------------------------------------------------------------- const char DB_FILE_DESCR[] = "FreeFileSync"; const int DB_FILE_VERSION = 11; //2020-02-07 -const int DB_STREAM_VERSION = 3; //2017-02-01 +const int DB_STREAM_VERSION = 4; //2021-02-14 //------------------------------------------------------------------------------------------------------------------------------- DEFINE_NEW_FILE_ERROR(FileErrorDatabaseNotExisting) @@ -44,7 +45,7 @@ using DbStreams = std::unordered_map; //list of streams b | ensure 32/64 bit portability: use fixed size data types only e.g. uint32_t | ------------------------------------------------------------------------------*/ -template inline +template inline AbstractPath getDatabaseFilePath(const BaseFolderPair& baseFolder) { static_assert(std::endian::native == std::endian::little); @@ -57,13 +58,13 @@ AbstractPath getDatabaseFilePath(const BaseFolderPair& baseFolder) - 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 + const Zstring dbName = Zstr(".sync"); //files beginning with dots are usually hidden return AFS::appendRelPath(baseFolder.getAbstractPath(), dbName + SYNC_DB_FILE_ENDING); } //####################################################################################################################################### -void saveStreams(const DbStreams& streamList, const AbstractPath& dbPath, const IOCallback& notifyUnbufferedIO /*throw X*/) //throw FileError, X +void saveStreams(const DbStreams& streamList, const AbstractPath& dbPath, const IoCallback& notifyUnbufferedIO /*throw X*/) //throw FileError, X { MemoryStreamOut memStreamOut; @@ -98,7 +99,7 @@ void saveStreams(const DbStreams& streamList, const AbstractPath& dbPath, const } -DbStreams loadStreams(const AbstractPath& dbPath, const IOCallback& notifyUnbufferedIO /*throw X*/) //throw FileError, FileErrorDatabaseNotExisting, X +DbStreams loadStreams(const AbstractPath& dbPath, const IoCallback& notifyUnbufferedIO /*throw X*/) //throw FileError, FileErrorDatabaseNotExisting, X { std::string byteStream; try @@ -133,7 +134,7 @@ DbStreams loadStreams(const AbstractPath& dbPath, const IOCallback& notifyUnbuff if (version == 9 || //TODO: remove migration code at some time! v9 used until 2017-02-01 version == 10) //TODO: remove migration code at some time! v10 used until 2020-02-07 ; - else if (version == DB_FILE_VERSION)//catch data corruption ASAP + don't rely on std::bad_alloc for consistency checking + else if (version == DB_FILE_VERSION) //catch data corruption ASAP + don't rely on std::bad_alloc for consistency checking // => only "partially" useful for container/stream metadata since the streams data is zlib-compressed { assert(byteStream.size() >= sizeof(uint32_t)); //obviously in this context! @@ -273,8 +274,8 @@ private: writeItemName(itemName); writeNumber(streamOutSmallNum_, static_cast(inSyncData.cmpVar)); - writeLinkDescr(inSyncData.left); - writeLinkDescr(inSyncData.right); + writeNumber(streamOutBigNum_, inSyncData.left .modTime); + writeNumber(streamOutBigNum_, inSyncData.right.modTime); } writeNumber(streamOutSmallNum_, static_cast(container.folders.size())); @@ -291,13 +292,11 @@ private: void writeFileDescr(const InSyncDescrFile& descr) { - writeNumber(streamOutBigNum_, descr.modTime); - writeContainer(streamOutBigNum_, descr.fileId); + writeNumber(streamOutBigNum_, descr.modTime); + writeNumber(streamOutBigNum_, descr.filePrint); static_assert(sizeof(descr.modTime) <= sizeof(int64_t)); //ensure cross-platform compatibility! } - void writeLinkDescr(const InSyncDescrLink& descr) { writeNumber(streamOutBigNum_, descr.modTime); } - /* maximize zlib compression by grouping similar data (=> 20% size reduction!) -> further ~5% reduction possible by having one container per data type @@ -364,7 +363,8 @@ public: parser.recurse(output.ref()); //throw SysError return output; } - else if (streamVersion == DB_STREAM_VERSION) + else if (streamVersion == 3 || //TODO: remove migration code at some time! 2021-02-14 + streamVersion == DB_STREAM_VERSION) { MemoryStreamIn& streamInPart1 = leadStreamLeft ? streamInL : streamInR; MemoryStreamIn& streamInPart2 = leadStreamLeft ? streamInR : streamInL; @@ -387,9 +387,9 @@ public: decompress(bufSmallNum), //throw SysError decompress(bufBigNum)); // if (leadStreamLeft) - parser.recurse(output.ref()); //throw SysError + parser.recurse(output.ref()); //throw SysError else - parser.recurse(output.ref()); //throw SysError + parser.recurse(output.ref()); //throw SysError return output; } else @@ -406,23 +406,20 @@ private: streamVersion_(streamVersion), streamInText_(bufText), streamInSmallNum_(bufSmallNumbers), - streamInBigNum_(bufBigNumbers) - { - (void)streamVersion_; //clang: -Wunused-private-field - } + streamInBigNum_(bufBigNumbers) {} - template + template void recurse(InSyncFolder& container) //throw SysError { - size_t fileCount = readNumber(streamInSmallNum_); + size_t fileCount = readNumber(streamInSmallNum_); //throw SysErrorUnexpectedEos while (fileCount-- != 0) { - const Zstring itemName = readItemName(); - const auto cmpVar = static_cast(readNumber(streamInSmallNum_)); - const uint64_t fileSize = readNumber(streamInSmallNum_); + const Zstring itemName = readItemName(); // + const auto cmpVar = static_cast(readNumber(streamInSmallNum_)); // + const uint64_t fileSize = readNumber(streamInSmallNum_); // - const InSyncDescrFile dataL = readFileDescr(); - const InSyncDescrFile dataT = readFileDescr(); + const InSyncDescrFile dataL = readFileDescr(); //throw SysErrorUnexpectedEos + const InSyncDescrFile dataT = readFileDescr(); // container.addFile(itemName, SelectParam::ref(dataL, dataT), @@ -432,22 +429,22 @@ private: size_t linkCount = readNumber(streamInSmallNum_); while (linkCount-- != 0) { - const Zstring itemName = readItemName(); - const auto cmpVar = static_cast(readNumber(streamInSmallNum_)); + const Zstring itemName = readItemName(); // + const auto cmpVar = static_cast(readNumber(streamInSmallNum_)); // - const InSyncDescrLink dataL = readLinkDescr(); - const InSyncDescrLink dataT = readLinkDescr(); + const InSyncDescrLink dataL(readNumber(streamInBigNum_)); //throw SysErrorUnexpectedEos + const InSyncDescrLink dataT(readNumber(streamInBigNum_)); // container.addSymlink(itemName, SelectParam::ref(dataL, dataT), SelectParam::ref(dataT, dataL), cmpVar); } - size_t dirCount = readNumber(streamInSmallNum_); + size_t dirCount = readNumber(streamInSmallNum_); // while (dirCount-- != 0) { - const Zstring itemName = readItemName(); - const auto status = static_cast(readNumber(streamInSmallNum_)); + const Zstring itemName = readItemName(); // + const auto status = static_cast(readNumber(streamInSmallNum_)); // InSyncFolder& dbFolder = container.addFolder(itemName, status); recurse(dbFolder); @@ -458,17 +455,24 @@ private: InSyncDescrFile readFileDescr() //throw SysErrorUnexpectedEos { - //attention: order of function argument evaluation is undefined! So do it one after the other... - const auto modTime = readNumber(streamInBigNum_); //throw SysErrorUnexpectedEos - const auto fileId = readContainer(streamInBigNum_); // + const auto modTime = readNumber(streamInBigNum_); //throw SysErrorUnexpectedEos - return InSyncDescrFile(modTime, fileId); - } + AFS::FingerPrint filePrint = 0; + if (streamVersion_ == 3) //TODO: remove migration code at some time! 2021-02-14 + { + const auto& devFileId = readContainer(streamInBigNum_); //throw SysErrorUnexpectedEos + ino_t fileIndex = 0; + if (devFileId.size() == sizeof(dev_t) + sizeof(fileIndex)) + { + std::memcpy(&fileIndex, &devFileId[devFileId.size() - sizeof(fileIndex)], sizeof(fileIndex)); + filePrint = fileIndex; + } + else assert(devFileId.empty()); + } + else + filePrint = readNumber(streamInBigNum_); //throw SysErrorUnexpectedEos - InSyncDescrLink readLinkDescr() //throw SysErrorUnexpectedEos - { - const auto modTime = readNumber(streamInBigNum_); //throw SysErrorUnexpectedEos - return InSyncDescrLink(modTime); + return InSyncDescrFile(modTime, filePrint); } //TODO: remove migration code at some time! 2017-02-01 @@ -491,10 +495,10 @@ private: const auto cmpVar = static_cast(readNumber(inputBoth_)); const uint64_t fileSize = readNumber(inputBoth_); const auto modTimeL = readNumber(inputLeft_); - const auto fileIdL = readContainer(inputLeft_); + /*const auto fileIdL =*/ readContainer(inputLeft_); const auto modTimeR = readNumber(inputRight_); - const auto fileIdR = readContainer(inputRight_); - container.addFile(itemName, InSyncDescrFile(modTimeL, fileIdL), InSyncDescrFile(modTimeR, fileIdR), cmpVar, fileSize); + /*const auto fileIdR =*/ readContainer(inputRight_); + container.addFile(itemName, InSyncDescrFile(modTimeL, AFS::FingerPrint()), InSyncDescrFile(modTimeR, AFS::FingerPrint()), cmpVar, fileSize); } size_t linkCount = readNumber(inputBoth_); @@ -568,23 +572,23 @@ private: { //Caveat: If FILE_EQUAL, we *implicitly* assume equal left and right short names matching case: InSyncFolder's mapping tables use short name as a key! //This makes us silently dependent from code in algorithm.h!!! - assert(getUnicodeNormalForm(file.getItemName()) == getUnicodeNormalForm(file.getItemName())); - assert(file.getFileSize() == file.getFileSize()); + assert(getUnicodeNormalForm(file.getItemName()) == getUnicodeNormalForm(file.getItemName())); + assert(file.getFileSize() == file.getFileSize()); //create or update new "in-sync" state dbFiles.insert_or_assign(file.getItemNameAny(), - InSyncFile(InSyncDescrFile(file.getLastWriteTime< LEFT_SIDE>(), - file.getFileId < LEFT_SIDE>()), - InSyncDescrFile(file.getLastWriteTime(), - file.getFileId ()), + InSyncFile(InSyncDescrFile(file.getLastWriteTime< SelectSide::left>(), + file.getFilePrint < SelectSide::left>()), + InSyncDescrFile(file.getLastWriteTime(), + file.getFilePrint ()), activeCmpVar_, - file.getFileSize())); + file.getFileSize())); toPreserve.insert(file.getItemNameAny()); } else //not in sync: preserve last synchronous state { - toPreserve.insert(file.getItemName< LEFT_SIDE>()); //left/right may differ in case! - toPreserve.insert(file.getItemName()); // + toPreserve.insert(file.getItemName< SelectSide::left>()); //left/right may differ in case! + toPreserve.insert(file.getItemName()); // } } @@ -609,19 +613,19 @@ private: { if (symlink.getLinkCategory() == SYMLINK_EQUAL) //data in sync: write current state { - assert(getUnicodeNormalForm(symlink.getItemName()) == getUnicodeNormalForm(symlink.getItemName())); + assert(getUnicodeNormalForm(symlink.getItemName()) == getUnicodeNormalForm(symlink.getItemName())); //create or update new "in-sync" state dbSymlinks.insert_or_assign(symlink.getItemNameAny(), - InSyncSymlink(InSyncDescrLink(symlink.getLastWriteTime< LEFT_SIDE>()), - InSyncDescrLink(symlink.getLastWriteTime()), + InSyncSymlink(InSyncDescrLink(symlink.getLastWriteTime< SelectSide::left>()), + InSyncDescrLink(symlink.getLastWriteTime()), activeCmpVar_)); toPreserve.insert(symlink.getItemNameAny()); } else //not in sync: preserve last synchronous state { - toPreserve.insert(symlink.getItemName< LEFT_SIDE>()); //left/right may differ in case! - toPreserve.insert(symlink.getItemName()); // + toPreserve.insert(symlink.getItemName< SelectSide::left>()); //left/right may differ in case! + toPreserve.insert(symlink.getItemName()); // } } @@ -645,7 +649,7 @@ private: { if (folder.getDirCategory() == DIR_EQUAL) { - assert(getUnicodeNormalForm(folder.getItemName()) == getUnicodeNormalForm(folder.getItemName())); + assert(getUnicodeNormalForm(folder.getItemName()) == getUnicodeNormalForm(folder.getItemName())); //update directory entry only (shallow), but do *not touch* existing child elements!!! InSyncFolder& dbFolder = dbFolders.emplace(folder.getItemNameAny(), InSyncFolder(InSyncFolder::DIR_STATUS_IN_SYNC)).first->second; //get or create @@ -655,8 +659,8 @@ private: } else //not in sync: preserve last synchronous state { - toPreserve.emplace(folder.getItemName< LEFT_SIDE>(), &folder); //names differing in case? => treat like any other folder rename - toPreserve.emplace(folder.getItemName(), &folder); //=> no *new* database entries even if child items are in sync + toPreserve.emplace(folder.getItemName< SelectSide::left>(), &folder); //names differing in case? => treat like any other folder rename + toPreserve.emplace(folder.getItemName(), &folder); //=> no *new* database entries even if child items are in sync } } @@ -749,7 +753,7 @@ std::pair> fff::lo 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! - if (baseFolder->isAvailable< LEFT_SIDE>() && - baseFolder->isAvailable()) + if (baseFolder->isAvailable< SelectSide::left>() && + baseFolder->isAvailable()) { - dbFilePaths.insert(getDatabaseFilePath< LEFT_SIDE>(*baseFolder)); - dbFilePaths.insert(getDatabaseFilePath(*baseFolder)); + dbFilePaths.insert(getDatabaseFilePath< SelectSide::left>(*baseFolder)); + dbFilePaths.insert(getDatabaseFilePath(*baseFolder)); } //else: ignore; there's no value in reporting it other than to confuse users @@ -801,11 +805,11 @@ std::unordered_map> fff::lo std::unordered_map> output; for (const BaseFolderPair* baseFolder : baseFolders) - if (baseFolder->isAvailable< LEFT_SIDE>() && - baseFolder->isAvailable()) + if (baseFolder->isAvailable< SelectSide::left>() && + baseFolder->isAvailable()) { - const AbstractPath dbPathL = getDatabaseFilePath< LEFT_SIDE>(*baseFolder); - const AbstractPath dbPathR = getDatabaseFilePath(*baseFolder); + const AbstractPath dbPathL = getDatabaseFilePath< SelectSide::left>(*baseFolder); + const AbstractPath dbPathR = getDatabaseFilePath(*baseFolder); auto itL = dbStreamsByPath.find(dbPathL); auto itR = dbStreamsByPath.find(dbPathR); @@ -842,8 +846,8 @@ std::unordered_map> fff::lo void fff::saveLastSynchronousState(const BaseFolderPair& baseFolder, bool transactionalCopy, PhaseCallback& callback /*throw X*/) //throw X { - const AbstractPath dbPathL = getDatabaseFilePath< LEFT_SIDE>(baseFolder); - const AbstractPath dbPathR = getDatabaseFilePath(baseFolder); + const AbstractPath dbPathL = getDatabaseFilePath< SelectSide::left>(baseFolder); + const AbstractPath dbPathR = getDatabaseFilePath(baseFolder); //------------ (try to) load DB files in parallel ------------------------- DbStreams streamsL; //list of session ID + DirInfo-stream diff --git a/FreeFileSync/Source/base/db_file.h b/FreeFileSync/Source/base/db_file.h index f9e0dabb..49d14813 100644 --- a/FreeFileSync/Source/base/db_file.h +++ b/FreeFileSync/Source/base/db_file.h @@ -19,12 +19,12 @@ const Zchar SYNC_DB_FILE_ENDING[] = Zstr(".ffs_db"); //don't use Zstring as glob struct InSyncDescrFile //subset of FileAttributes { - InSyncDescrFile(time_t modTimeIn, const AFS::FileId& idIn) : + InSyncDescrFile(time_t modTimeIn, AFS::FingerPrint filePrintIn) : modTime(modTimeIn), - fileId(idIn) {} + filePrint(filePrintIn) {} time_t modTime = 0; - AFS::FileId fileId; // == file id: optional! (however, always set on Linux, and *generally* available on Windows) + AFS::FingerPrint filePrint = 0; //optional! }; struct InSyncDescrLink diff --git a/FreeFileSync/Source/base/dir_exist_async.h b/FreeFileSync/Source/base/dir_exist_async.h index 98430b2e..593dc3b9 100644 --- a/FreeFileSync/Source/base/dir_exist_async.h +++ b/FreeFileSync/Source/base/dir_exist_async.h @@ -53,7 +53,8 @@ FolderStatus getFolderStatusNonBlocking(const std::set& folderPath threadGroup.detach(); //don't wait on threads hanging longer than "folderAccessTimeout" //1. login to network share, connect with Google Drive, etc. - std::shared_future ftAuth = runAsync([afsDevice /*clang bug*/= afsDevice, allowUserInteraction] { AFS::authenticateAccess(afsDevice, allowUserInteraction); /*throw FileError*/ }); + std::shared_future ftAuth = runAsync([afsDevice /*clang bug*/= afsDevice, allowUserInteraction] + { AFS::authenticateAccess(afsDevice, allowUserInteraction); /*throw FileError*/ }); for (const AbstractPath& folderPath : deviceFolderPaths) { diff --git a/FreeFileSync/Source/base/dir_lock.cpp b/FreeFileSync/Source/base/dir_lock.cpp index de392fe9..f70cf513 100644 --- a/FreeFileSync/Source/base/dir_lock.cpp +++ b/FreeFileSync/Source/base/dir_lock.cpp @@ -96,12 +96,13 @@ private: { try { +#if 1 const int fdLockFile = ::open(lockFilePath_.c_str(), O_WRONLY | O_APPEND | O_CLOEXEC); if (fdLockFile == -1) THROW_LAST_SYS_ERROR("open"); ZEN_ON_SCOPE_EXIT(::close(fdLockFile)); -#if 0//alternative using lseek => no apparent benefit https://freefilesync.org/forum/viewtopic.php?t=7553#p25505 +#else //alternative using lseek => no apparent benefit https://freefilesync.org/forum/viewtopic.php?t=7553#p25505 const int fdLockFile = ::open(lockFilePath_.c_str(), O_WRONLY | O_CLOEXEC); if (fdLockFile == -1) THROW_LAST_SYS_ERROR("open"); @@ -302,7 +303,7 @@ ProcessStatus getProcessStatus(const LockInformation& lockInfo) //throw FileErro DEFINE_NEW_FILE_ERROR(ErrorFileNotExisting) uint64_t getLockFileSize(const Zstring& filePath) //throw FileError, ErrorFileNotExisting { - struct ::stat fileInfo = {}; + struct stat fileInfo = {}; if (::stat(filePath.c_str(), &fileInfo) == 0) return fileInfo.st_size; @@ -316,7 +317,7 @@ void waitOnDirLock(const Zstring& lockFilePath, const DirLockCallback& notifySta { std::wstring infoMsg = _("Waiting while directory is in use:") + L' ' + fmtPath(lockFilePath); - if (notifyStatus) notifyStatus(infoMsg); //throw X + if (notifyStatus) notifyStatus(std::wstring(infoMsg)); //throw X //convenience optimization only: if we know the owning process crashed, we needn't wait DETECT_ABANDONED_INTERVAL sec bool lockOwnderDead = false; @@ -399,7 +400,7 @@ void waitOnDirLock(const Zstring& lockFilePath, const DirLockCallback& notifySta notifyStatus(infoMsg + L" | " + _("Detecting abandoned lock...") + L' ' + _P("1 sec", "%x sec", remainingSeconds)); //throw X } else - notifyStatus(infoMsg); //throw X; emit a message in any case (might clear other one) + notifyStatus(std::wstring(infoMsg)); //throw X; emit a message in any case (might clear other one) } std::this_thread::sleep_for(cbInterval); } @@ -419,10 +420,17 @@ void releaseLock(const Zstring& lockFilePath) //noexcept bool tryLock(const Zstring& lockFilePath) //throw FileError { + //important: we want the lock file to have exactly the permissions specified + //=> yes, disabling umask() is messy (per-process!), but fchmod() may not be supported: https://freefilesync.org/forum/viewtopic.php?t=8096 + const mode_t oldMask = ::umask(0); //always succeeds + ZEN_ON_SCOPE_EXIT(::umask(oldMask)); + const mode_t lockFileMode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; //0666 //O_EXCL contains a race condition on NFS file systems: https://linux.die.net/man/2/open - const int hFile = ::open(lockFilePath.c_str(), O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, lockFileMode); + const int hFile = ::open(lockFilePath.c_str(), //const char* pathname + O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, //int flags + lockFileMode); //mode_t mode if (hFile == -1) { if (errno == EEXIST) @@ -432,10 +440,6 @@ bool tryLock(const Zstring& lockFilePath) //throw FileError } FileOutput fileOut(hFile, lockFilePath, nullptr /*notifyUnbufferedIO*/); //pass handle ownership - //consider umask! we want the lock file to have exactly the permissions specified - if (::fchmod(hFile, lockFileMode) != 0) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(lockFilePath)), "fchmod"); - //write housekeeping info: user, process info, lock GUID const std::string byteStream = serialize(getLockInfoFromCurrentProcess()); //throw FileError diff --git a/FreeFileSync/Source/base/dir_lock.h b/FreeFileSync/Source/base/dir_lock.h index 269dc7d1..87b3a7e6 100644 --- a/FreeFileSync/Source/base/dir_lock.h +++ b/FreeFileSync/Source/base/dir_lock.h @@ -25,7 +25,7 @@ namespace fff - NOT thread-safe! (1. global LockAdmin 2. locks for directory aliases should be created sequentially to detect duplicate locks!) */ //while waiting for the lock -using DirLockCallback = std::function; //throw X +using DirLockCallback = std::function; //throw X class DirLock { diff --git a/FreeFileSync/Source/base/file_hierarchy.cpp b/FreeFileSync/Source/base/file_hierarchy.cpp index 6e184b97..0aa176cb 100644 --- a/FreeFileSync/Source/base/file_hierarchy.cpp +++ b/FreeFileSync/Source/base/file_hierarchy.cpp @@ -186,13 +186,13 @@ bool hasDirectChild(const ContainerObject& hierObj, Predicate p) SyncOperation FileSystemObject::testSyncOperation(SyncDirection testSyncDir) const //semantics: "what if"! assumes "active, no conflict, no recursion (directory)! { - return getIsolatedSyncOperation(!isEmpty(), !isEmpty(), getCategory(), true, testSyncDir, false); + return getIsolatedSyncOperation(!isEmpty(), !isEmpty(), getCategory(), true, testSyncDir, false); } SyncOperation FileSystemObject::getSyncOperation() const { - return getIsolatedSyncOperation(!isEmpty(), !isEmpty(), getCategory(), selectedForSync_, getSyncDir(), !syncDirectionConflict_.empty()); + return getIsolatedSyncOperation(!isEmpty(), !isEmpty(), getCategory(), selectedForSync_, getSyncDir(), !syncDirectionConflict_.empty()); //do *not* make a virtual call to testSyncOperation()! See FilePair::testSyncOperation()! <- better not implement one in terms of the other!!! } @@ -228,7 +228,7 @@ SyncOperation FolderPair::getSyncOperation() const case SO_DELETE_RIGHT: case SO_DO_NOTHING: case SO_UNRESOLVED_CONFLICT: - if (isEmpty()) + if (isEmpty()) { //1. if at least one child-element is to be created, make sure parent folder is created also //note: this automatically fulfills "create parent folders even if excluded" @@ -253,7 +253,7 @@ SyncOperation FolderPair::getSyncOperation() const })) syncOpBuffered_ = SO_DO_NOTHING; } - else if (isEmpty()) + else if (isEmpty()) { if (hasDirectChild(*this, [](const FileSystemObject& fsObj) @@ -285,10 +285,8 @@ SyncOperation FolderPair::getSyncOperation() const inline //called by private only! SyncOperation FilePair::applyMoveOptimization(SyncOperation op) const { - /* - check whether we can optimize "create + delete" via "move": - note: as long as we consider "create + delete" cases only, detection of renamed files, should be fine even for "binary" comparison variant! - */ + /* check whether we can optimize "create + delete" via "move": + note: as long as we consider "create + delete" cases only, detection of renamed files, should be fine even for "binary" comparison variant! */ if (moveFileRef_) if (auto refFile = dynamic_cast(FileSystemObject::retrieve(moveFileRef_))) //we expect a "FilePair", but only need a "FileSystemObject" here if (refFile->moveFileRef_ == getId()) //both ends should agree... @@ -379,14 +377,14 @@ std::wstring fff::getCategoryDescription(const FileSystemObject& fsObj) [&](const FilePair& file) { descr += std::wstring(L"\n") + - arrowLeft + L' ' + formatUtcToLocalTime(file.getLastWriteTime< LEFT_SIDE>()) + L'\n' + - arrowRight + L' ' + formatUtcToLocalTime(file.getLastWriteTime()); + arrowLeft + L' ' + formatUtcToLocalTime(file.getLastWriteTime< SelectSide::left>()) + L'\n' + + arrowRight + L' ' + formatUtcToLocalTime(file.getLastWriteTime()); }, [&](const SymlinkPair& symlink) { descr += std::wstring(L"\n") + - arrowLeft + L' ' + formatUtcToLocalTime(symlink.getLastWriteTime< LEFT_SIDE>()) + L'\n' + - arrowRight + L' ' + formatUtcToLocalTime(symlink.getLastWriteTime()); + arrowLeft + L' ' + formatUtcToLocalTime(symlink.getLastWriteTime< SelectSide::left>()) + L'\n' + + arrowRight + L' ' + formatUtcToLocalTime(symlink.getLastWriteTime()); }); return descr + footer; } @@ -459,8 +457,8 @@ std::wstring fff::getSyncOpDescription(const FileSystemObject& fsObj) case SO_COPY_METADATA_TO_RIGHT: //harmonize with synchronization.cpp::FolderPairSyncer::synchronizeFileInt, ect!! { - Zstring itemNameOld = fsObj.getItemName(); - Zstring itemNameNew = fsObj.getItemName< LEFT_SIDE>(); + Zstring itemNameOld = fsObj.getItemName(); + Zstring itemNameNew = fsObj.getItemName< SelectSide::left>(); if (op == SO_COPY_METADATA_TO_LEFT) std::swap(itemNameOld, itemNameNew); @@ -486,7 +484,7 @@ std::wstring fff::getSyncOpDescription(const FileSystemObject& fsObj) if (!isMoveSource) std::swap(fileFrom, fileTo); - auto getRelName = [&](const FileSystemObject& fso, bool leftSide) { return leftSide ? fso.getRelativePath() : fso.getRelativePath(); }; + auto getRelName = [&](const FileSystemObject& fso, bool leftSide) { return leftSide ? fso.getRelativePath() : fso.getRelativePath(); }; const Zstring relPathFrom = getRelName(*fileFrom, onLeft); const Zstring relPathTo = getRelName(*fileTo, onLeft); @@ -511,6 +509,3 @@ std::wstring fff::getSyncOpDescription(const FileSystemObject& fsObj) assert(false); return std::wstring(); } - - -warn_static(" FileSystemObject::isEmpty => rename: exists()!?") diff --git a/FreeFileSync/Source/base/file_hierarchy.h b/FreeFileSync/Source/base/file_hierarchy.h index 52000203..45c672f6 100644 --- a/FreeFileSync/Source/base/file_hierarchy.h +++ b/FreeFileSync/Source/base/file_hierarchy.h @@ -15,7 +15,6 @@ #include #include #include -#include #include "structures.h" #include "path_filter.h" #include "../afs/abstract.h" @@ -28,19 +27,19 @@ struct FileAttributes FileAttributes() {} FileAttributes(time_t modTimeIn, uint64_t fileSizeIn, - const AFS::FileId& idIn, - bool isSymlink) : + AFS::FingerPrint filePrintIn, + bool followedSymlink) : modTime(modTimeIn), fileSize(fileSizeIn), - fileId(idIn), - isFollowedSymlink(isSymlink) + filePrint(filePrintIn), + isFollowedSymlink(followedSymlink) { static_assert(std::is_signed_v, "... and signed!"); } time_t modTime = 0; //number of seconds since Jan. 1st 1970 UTC uint64_t fileSize = 0; - AFS::FileId fileId; //optional! + AFS::FingerPrint filePrint = 0; //optional bool isFollowedSymlink = false; std::strong_ordering operator<=>(const FileAttributes&) const = default; @@ -66,34 +65,34 @@ struct FolderAttributes }; -enum SelectedSide +enum class SelectSide { - LEFT_SIDE, - RIGHT_SIDE + left, + right }; -template +template struct OtherSide; template <> -struct OtherSide { static const SelectedSide value = RIGHT_SIDE; }; +struct OtherSide { static const SelectSide value = SelectSide::right; }; template <> -struct OtherSide { static const SelectedSide value = LEFT_SIDE; }; +struct OtherSide { static const SelectSide value = SelectSide::left; }; -template +template struct SelectParam; template <> -struct SelectParam +struct SelectParam { template static T& ref(T& left, T& right) { return left; } }; template <> -struct SelectParam +struct SelectParam { template static T& ref(T& left, T& right) { return right; } @@ -173,8 +172,8 @@ struct PathInformation //diamond-shaped inheritence! { virtual ~PathInformation() {} - template AbstractPath getAbstractPath() const; - template Zstring getRelativePath() const; //get path relative to base sync dir (without leading/trailing FILE_NAME_SEPARATOR) + template AbstractPath getAbstractPath() const; + template Zstring getRelativePath() const; //get path relative to base sync dir (without leading/trailing FILE_NAME_SEPARATOR) Zstring getRelativePathAny() const { return getRelativePathL(); } //side doesn't matter private: @@ -185,11 +184,11 @@ private: virtual Zstring getRelativePathR() const = 0; // }; -template <> inline AbstractPath PathInformation::getAbstractPath< LEFT_SIDE>() const { return getAbstractPathL(); } -template <> inline AbstractPath PathInformation::getAbstractPath() const { return getAbstractPathR(); } +template <> inline AbstractPath PathInformation::getAbstractPath< SelectSide::left>() const { return getAbstractPathL(); } +template <> inline AbstractPath PathInformation::getAbstractPath() const { return getAbstractPathR(); } -template <> inline Zstring PathInformation::getRelativePath< LEFT_SIDE>() const { return getRelativePathL(); } -template <> inline Zstring PathInformation::getRelativePath() const { return getRelativePathR(); } +template <> inline Zstring PathInformation::getRelativePath< SelectSide::left>() const { return getRelativePathL(); } +template <> inline Zstring PathInformation::getRelativePath() const { return getRelativePathR(); } //------------------------------------------------------------------ @@ -209,7 +208,7 @@ public: const Zstring& itemNameR, const FolderAttributes& right); - template + template FolderPair& addSubFolder(const Zstring& itemName, //dir exists on one side only const FolderAttributes& attr); @@ -219,7 +218,7 @@ public: const Zstring& itemNameR, const FileAttributes& right); - template + template FilePair& addSubFile(const Zstring& itemName, //file exists on one side only const FileAttributes& attr); @@ -229,7 +228,7 @@ public: const Zstring& itemNameR, const LinkAttributes& right); - template + template SymlinkPair& addSubLink(const Zstring& itemName, //link exists on one side only const LinkAttributes& attr); @@ -256,7 +255,7 @@ protected: void removeEmptyRec(); - template + template void updateRelPathsRecursion(const FileSystemObject& fsAlias); private: @@ -300,8 +299,8 @@ public: static void removeEmpty(BaseFolderPair& baseFolder) { baseFolder.removeEmptyRec(); } //physically remove all invalid entries (where both sides are empty) recursively - template bool isAvailable() const; //base folder status at the time of comparison! - template void setAvailable(bool value); //update after creating the directory in FFS + template bool isAvailable() const; //base folder status at the time of comparison! + template void setAvailable(bool value); //update after creating the directory in FFS //get settings which were used while creating BaseFolderPair const PathFilter& getFilter() const { return filter_.ref(); } @@ -400,7 +399,6 @@ private: ObjectMgr (const ObjectMgr& rhs) = delete; ObjectMgr& operator=(const ObjectMgr& rhs) = delete; //it's not well-defined what copying an objects means regarding object-identity in this context - //our global ObjectMgr is not thread-safe (and currently does not need to be!) //assert(runningOnMainThread()); -> still, may be accessed by synchronization worker threads, one thread at a time static inline std::unordered_set activeObjects_; //external linkage! @@ -414,11 +412,11 @@ public: virtual void accept(FSObjectVisitor& visitor) const = 0; bool isPairEmpty() const; //true, if both sides are empty - template bool isEmpty() const; + template bool isEmpty() const; //path getters always return valid values, even if isEmpty()! Zstring getItemNameAny() const; //like getItemName() but without bias to which side is returned - template Zstring getItemName() const; //case sensitive! + template Zstring getItemName() const; //case sensitive! //comparison result CompareFileResult getCategory() const { return cmpResult_; } @@ -437,7 +435,7 @@ public: virtual SyncOperation getSyncOperation() const; std::wstring getSyncOpConflict() const; //return conflict when determining sync direction or (still unresolved) conflict during categorization - template void removeObject(); //removes file or directory (recursively!) without physically removing the element: used by manual deletion + template void removeObject(); //removes file or directory (recursively!) without physically removing the element: used by manual deletion const ContainerObject& parent() const { return parent_; } /**/ ContainerObject& parent() { return parent_; } @@ -475,13 +473,13 @@ private: FileSystemObject (const FileSystemObject&) = delete; FileSystemObject& operator=(const FileSystemObject&) = delete; - AbstractPath getAbstractPathL() const override { return AFS::appendRelPath(base().getAbstractPath< LEFT_SIDE>(), getRelativePath< LEFT_SIDE>()); } - AbstractPath getAbstractPathR() const override { return AFS::appendRelPath(base().getAbstractPath(), getRelativePath()); } + AbstractPath getAbstractPathL() const override { return AFS::appendRelPath(base().getAbstractPath< SelectSide::left>(), getRelativePath< SelectSide::left>()); } + AbstractPath getAbstractPathR() const override { return AFS::appendRelPath(base().getAbstractPath(), getRelativePath()); } virtual void removeObjectL() = 0; virtual void removeObjectR() = 0; - template + template void propagateChangedItemName(const Zstring& itemNameOld); //required after any itemName changes //categorization @@ -525,11 +523,11 @@ public: attrL_(attrL), attrR_(attrR) {} - template bool isFollowedSymlink() const; + template bool isFollowedSymlink() const; SyncOperation getSyncOperation() const override; - template + template void setSyncedTo(const Zstring& itemName, bool isSymlinkTrg, bool isSymlinkSrc); //call after sync, sets DIR_EQUAL private: @@ -564,11 +562,12 @@ public: attrL_(attrL), attrR_(attrR) {} - template time_t getLastWriteTime() const; - template uint64_t getFileSize() const; - template AFS::FileId getFileId() const; - template bool isFollowedSymlink() const; - template FileAttributes getAttributes() const; + template time_t getLastWriteTime() const; + template uint64_t getFileSize() const; + template bool isFollowedSymlink() const; + template FileAttributes getAttributes() const; + template AFS::FingerPrint getFilePrint() const; + template void clearFilePrint(); void setMoveRef(ObjectId refId) { moveFileRef_ = refId; } //reference to corresponding renamed file ObjectId getMoveRef() const { return moveFileRef_; } //may be nullptr @@ -578,19 +577,19 @@ public: SyncOperation testSyncOperation(SyncDirection testSyncDir) const override; //semantics: "what if"! assumes "active, no conflict, no recursion (directory)! SyncOperation getSyncOperation() const override; - template + template void setSyncedTo(const Zstring& itemName, //call after sync, sets FILE_EQUAL uint64_t fileSize, int64_t lastWriteTimeTrg, int64_t lastWriteTimeSrc, - const AFS::FileId& fileIdTrg, - const AFS::FileId& fileIdSrc, + AFS::FingerPrint filePrintTrg, + AFS::FingerPrint filePrintSrc, bool isSymlinkTrg, bool isSymlinkSrc); private: - Zstring getRelativePathL() const override { return nativeAppendPaths(parent().getRelativePath< LEFT_SIDE>(), getItemName< LEFT_SIDE>()); } - Zstring getRelativePathR() const override { return nativeAppendPaths(parent().getRelativePath(), getItemName()); } + Zstring getRelativePathL() const override { return nativeAppendPaths(parent().getRelativePath< SelectSide::left>(), getItemName< SelectSide::left>()); } + Zstring getRelativePathR() const override { return nativeAppendPaths(parent().getRelativePath(), getItemName()); } SyncOperation applyMoveOptimization(SyncOperation op) const; @@ -613,7 +612,7 @@ class SymlinkPair : public FileSystemObject //this class models a TRUE symbolic public: void accept(FSObjectVisitor& visitor) const override; - template time_t getLastWriteTime() const; //write time of the link, NOT target! + template time_t getLastWriteTime() const; //write time of the link, NOT target! CompareSymlinkResult getLinkCategory() const; //returns actually used subset of CompareFileResult @@ -627,14 +626,14 @@ public: attrL_(attrL), attrR_(attrR) {} - template + template void setSyncedTo(const Zstring& itemName, //call after sync, sets SYMLINK_EQUAL int64_t lastWriteTimeTrg, int64_t lastWriteTimeSrc); private: - Zstring getRelativePathL() const override { return nativeAppendPaths(parent().getRelativePath< LEFT_SIDE>(), getItemName< LEFT_SIDE>()); } - Zstring getRelativePathR() const override { return nativeAppendPaths(parent().getRelativePath(), getItemName()); } + Zstring getRelativePathL() const override { return nativeAppendPaths(parent().getRelativePath< SelectSide::left>(), getItemName< SelectSide::left>()); } + Zstring getRelativePathR() const override { return nativeAppendPaths(parent().getRelativePath(), getItemName()); } void flip() override; void removeObjectL() override { attrL_ = LinkAttributes(); } @@ -765,7 +764,7 @@ void FileSystemObject::setActive(bool active) } -template inline +template inline bool FileSystemObject::isEmpty() const { return SelectParam::ref(itemNameL_, itemNameR_).empty(); @@ -775,11 +774,11 @@ bool FileSystemObject::isEmpty() const inline bool FileSystemObject::isPairEmpty() const { - return isEmpty() && isEmpty(); + return isEmpty() && isEmpty(); } -template inline +template inline Zstring FileSystemObject::getItemName() const { //assert(!itemNameL_.empty() || !itemNameR_.empty()); -> file pair might be empty (until removed after sync) @@ -794,51 +793,51 @@ Zstring FileSystemObject::getItemName() const inline Zstring FileSystemObject::getItemNameAny() const { - return getItemName(); //side doesn't matter + return getItemName(); //side doesn't matter } template <> inline -void FileSystemObject::removeObject() +void FileSystemObject::removeObject() { - const Zstring itemNameOld = getItemName(); + const Zstring itemNameOld = getItemName(); - cmpResult_ = isEmpty() ? FILE_EQUAL : FILE_RIGHT_SIDE_ONLY; + cmpResult_ = isEmpty() ? FILE_EQUAL : FILE_RIGHT_SIDE_ONLY; itemNameL_.clear(); removeObjectL(); setSyncDir(SyncDirection::none); //calls notifySyncCfgChanged() - propagateChangedItemName(itemNameOld); + propagateChangedItemName(itemNameOld); } template <> inline -void FileSystemObject::removeObject() +void FileSystemObject::removeObject() { - const Zstring itemNameOld = getItemName(); + const Zstring itemNameOld = getItemName(); - cmpResult_ = isEmpty() ? FILE_EQUAL : FILE_LEFT_SIDE_ONLY; + cmpResult_ = isEmpty() ? FILE_EQUAL : FILE_LEFT_SIDE_ONLY; itemNameR_.clear(); removeObjectR(); setSyncDir(SyncDirection::none); //calls notifySyncCfgChanged() - propagateChangedItemName(itemNameOld); + propagateChangedItemName(itemNameOld); } inline void FileSystemObject::setSynced(const Zstring& itemName) { - const Zstring itemNameOldL = getItemName(); - const Zstring itemNameOldR = getItemName(); + const Zstring itemNameOldL = getItemName(); + const Zstring itemNameOldR = getItemName(); assert(!isPairEmpty()); itemNameR_ = itemNameL_ = itemName; cmpResult_ = FILE_EQUAL; setSyncDir(SyncDirection::none); - propagateChangedItemName< LEFT_SIDE>(itemNameOldL); - propagateChangedItemName(itemNameOldR); + propagateChangedItemName< SelectSide::left>(itemNameOldL); + propagateChangedItemName(itemNameOldR); } @@ -898,7 +897,7 @@ void FileSystemObject::flip() } -template inline +template inline void FileSystemObject::propagateChangedItemName(const Zstring& itemNameOld) { if (itemNameL_.empty() && itemNameR_.empty()) return; //both sides might just have been deleted by removeObject<> @@ -909,7 +908,7 @@ void FileSystemObject::propagateChangedItemName(const Zstring& itemNameOld) } -template inline +template inline void ContainerObject::updateRelPathsRecursion(const FileSystemObject& fsAlias) { assert(SelectParam::ref(relPathL_, relPathR_) != //perf: only call if actual item name changed! @@ -924,14 +923,14 @@ void ContainerObject::updateRelPathsRecursion(const FileSystemObject& fsAlias) inline ContainerObject::ContainerObject(const FileSystemObject& fsAlias) : - relPathL_(nativeAppendPaths(fsAlias.parent().relPathL_, fsAlias.getItemName())), + relPathL_(nativeAppendPaths(fsAlias.parent().relPathL_, fsAlias.getItemName())), relPathR_( fsAlias.parent().relPathL_.c_str() == // fsAlias.parent().relPathR_.c_str() && //take advantage of FileSystemObject's Zstring reuse: - fsAlias.getItemName< LEFT_SIDE>().c_str() == //=> perf: 12% faster merge phase; -4% peak memory - fsAlias.getItemName().c_str() ? // + fsAlias.getItemName< SelectSide::left>().c_str() == //=> perf: 12% faster merge phase; -4% peak memory + fsAlias.getItemName().c_str() ? // relPathL_ : //ternary-WTF! (implicit copy-constructor call!!) => no big deal for a Zstring - nativeAppendPaths(fsAlias.parent().relPathR_, fsAlias.getItemName())), + nativeAppendPaths(fsAlias.parent().relPathR_, fsAlias.getItemName())), base_(fsAlias.parent().base_) { assert(relPathL_.c_str() == relPathR_.c_str() || relPathL_ != relPathR_); @@ -965,7 +964,7 @@ FolderPair& ContainerObject::addSubFolder(const Zstring& itemNameL, template <> inline -FolderPair& ContainerObject::addSubFolder(const Zstring& itemName, const FolderAttributes& attr) +FolderPair& ContainerObject::addSubFolder(const Zstring& itemName, const FolderAttributes& attr) { subFolders_.emplace_back(itemName, attr, DIR_LEFT_SIDE_ONLY, Zstring(), FolderAttributes(), *this); return subFolders_.back(); @@ -973,7 +972,7 @@ FolderPair& ContainerObject::addSubFolder(const Zstring& itemName, co template <> inline -FolderPair& ContainerObject::addSubFolder(const Zstring& itemName, const FolderAttributes& attr) +FolderPair& ContainerObject::addSubFolder(const Zstring& itemName, const FolderAttributes& attr) { subFolders_.emplace_back(Zstring(), FolderAttributes(), DIR_RIGHT_SIDE_ONLY, itemName, attr, *this); return subFolders_.back(); @@ -993,7 +992,7 @@ FilePair& ContainerObject::addSubFile(const Zstring& itemNameL, template <> inline -FilePair& ContainerObject::addSubFile(const Zstring& itemName, const FileAttributes& attr) +FilePair& ContainerObject::addSubFile(const Zstring& itemName, const FileAttributes& attr) { subFiles_.emplace_back(itemName, attr, FILE_LEFT_SIDE_ONLY, Zstring(), FileAttributes(), *this); return subFiles_.back(); @@ -1001,7 +1000,7 @@ FilePair& ContainerObject::addSubFile(const Zstring& itemName, const template <> inline -FilePair& ContainerObject::addSubFile(const Zstring& itemName, const FileAttributes& attr) +FilePair& ContainerObject::addSubFile(const Zstring& itemName, const FileAttributes& attr) { subFiles_.emplace_back(Zstring(), FileAttributes(), FILE_RIGHT_SIDE_ONLY, itemName, attr, *this); return subFiles_.back(); @@ -1021,7 +1020,7 @@ SymlinkPair& ContainerObject::addSubLink(const Zstring& itemNameL, template <> inline -SymlinkPair& ContainerObject::addSubLink(const Zstring& itemName, const LinkAttributes& attr) +SymlinkPair& ContainerObject::addSubLink(const Zstring& itemName, const LinkAttributes& attr) { subLinks_.emplace_back(itemName, attr, SYMLINK_LEFT_SIDE_ONLY, Zstring(), LinkAttributes(), *this); return subLinks_.back(); @@ -1029,7 +1028,7 @@ SymlinkPair& ContainerObject::addSubLink(const Zstring& itemName, con template <> inline -SymlinkPair& ContainerObject::addSubLink(const Zstring& itemName, const LinkAttributes& attr) +SymlinkPair& ContainerObject::addSubLink(const Zstring& itemName, const LinkAttributes& attr) { subLinks_.emplace_back(Zstring(), LinkAttributes(), SYMLINK_RIGHT_SIDE_ONLY, itemName, attr, *this); return subLinks_.back(); @@ -1058,11 +1057,11 @@ inline void FolderPair::removeObjectL() { for (FilePair& file : refSubFiles()) - file.removeObject(); + file.removeObject(); for (SymlinkPair& link : refSubLinks()) - link.removeObject(); + link.removeObject(); for (FolderPair& folder : refSubFolders()) - folder.removeObject(); + folder.removeObject(); attrL_ = FolderAttributes(); } @@ -1072,24 +1071,24 @@ inline void FolderPair::removeObjectR() { for (FilePair& file : refSubFiles()) - file.removeObject(); + file.removeObject(); for (SymlinkPair& link : refSubLinks()) - link.removeObject(); + link.removeObject(); for (FolderPair& folder : refSubFolders()) - folder.removeObject(); + folder.removeObject(); attrR_ = FolderAttributes(); } -template inline +template inline bool BaseFolderPair::isAvailable() const { return SelectParam::ref(folderAvailableLeft_, folderAvailableRight_); } -template inline +template inline void BaseFolderPair::setAvailable(bool value) { SelectParam::ref(folderAvailableLeft_, folderAvailableRight_) = value; @@ -1104,75 +1103,82 @@ void FilePair::flip() } -template inline +template inline FileAttributes FilePair::getAttributes() const { return SelectParam::ref(attrL_, attrR_); } -template inline +template inline time_t FilePair::getLastWriteTime() const { return SelectParam::ref(attrL_, attrR_).modTime; } -template inline +template inline uint64_t FilePair::getFileSize() const { return SelectParam::ref(attrL_, attrR_).fileSize; } -template inline -AFS::FileId FilePair::getFileId() const +template inline +bool FilePair::isFollowedSymlink() const { - return SelectParam::ref(attrL_, attrR_).fileId; + return SelectParam::ref(attrL_, attrR_).isFollowedSymlink; } -template inline -bool FilePair::isFollowedSymlink() const +template inline +bool FolderPair::isFollowedSymlink() const { return SelectParam::ref(attrL_, attrR_).isFollowedSymlink; } -template inline -bool FolderPair::isFollowedSymlink() const +template inline +AFS::FingerPrint FilePair::getFilePrint() const { - return SelectParam::ref(attrL_, attrR_).isFollowedSymlink; + return SelectParam::ref(attrL_, attrR_).filePrint; +} + + +template inline +void FilePair::clearFilePrint() +{ + SelectParam::ref(attrL_, attrR_).filePrint = 0; } -template inline +template inline void FilePair::setSyncedTo(const Zstring& itemName, uint64_t fileSize, int64_t lastWriteTimeTrg, int64_t lastWriteTimeSrc, - const AFS::FileId& fileIdTrg, - const AFS::FileId& fileIdSrc, + AFS::FingerPrint filePrintTrg, + AFS::FingerPrint filePrintSrc, bool isSymlinkTrg, bool isSymlinkSrc) { //FILE_EQUAL is only allowed for same short name and file size: enforced by this method! - constexpr SelectedSide sideSrc = OtherSide::value; + constexpr SelectSide sideSrc = OtherSide::value; - SelectParam::ref(attrL_, attrR_) = FileAttributes(lastWriteTimeTrg, fileSize, fileIdTrg, isSymlinkTrg); - SelectParam::ref(attrL_, attrR_) = FileAttributes(lastWriteTimeSrc, fileSize, fileIdSrc, isSymlinkSrc); + SelectParam::ref(attrL_, attrR_) = FileAttributes(lastWriteTimeTrg, fileSize, filePrintTrg, isSymlinkTrg); + SelectParam::ref(attrL_, attrR_) = FileAttributes(lastWriteTimeSrc, fileSize, filePrintSrc, isSymlinkSrc); moveFileRef_ = nullptr; FileSystemObject::setSynced(itemName); //set FileSystemObject specific part } -template inline +template inline void SymlinkPair::setSyncedTo(const Zstring& itemName, int64_t lastWriteTimeTrg, int64_t lastWriteTimeSrc) { - constexpr SelectedSide sideSrc = OtherSide::value; + constexpr SelectSide sideSrc = OtherSide::value; SelectParam::ref(attrL_, attrR_) = LinkAttributes(lastWriteTimeTrg); SelectParam::ref(attrL_, attrR_) = LinkAttributes(lastWriteTimeSrc); @@ -1181,12 +1187,12 @@ void SymlinkPair::setSyncedTo(const Zstring& itemName, } -template inline +template inline void FolderPair::setSyncedTo(const Zstring& itemName, bool isSymlinkTrg, bool isSymlinkSrc) { - constexpr SelectedSide sideSrc = OtherSide::value; + constexpr SelectSide sideSrc = OtherSide::value; SelectParam::ref(attrL_, attrR_) = FolderAttributes(isSymlinkTrg); SelectParam::ref(attrL_, attrR_) = FolderAttributes(isSymlinkSrc); @@ -1195,7 +1201,7 @@ void FolderPair::setSyncedTo(const Zstring& itemName, } -template inline +template inline time_t SymlinkPair::getLastWriteTime() const { return SelectParam::ref(attrL_, attrR_).modTime; diff --git a/FreeFileSync/Source/base/icon_loader.cpp b/FreeFileSync/Source/base/icon_loader.cpp index 684569ed..495793be 100644 --- a/FreeFileSync/Source/base/icon_loader.cpp +++ b/FreeFileSync/Source/base/icon_loader.cpp @@ -36,8 +36,7 @@ ImageHolder copyToImageHolder(const GdkPixbuf& pixBuf, int maxSize) //throw SysE if (channels != 3 && channels != 4) throw SysError(formatSystemError("gdk_pixbuf_get_n_channels", L"", L"Unexpected number of channels: " + numberTo(channels))); - const bool withAlpha = channels == 4; - assert(::gdk_pixbuf_get_has_alpha(&pixBuf) == withAlpha); + assert(::gdk_pixbuf_get_has_alpha(&pixBuf) == (channels == 4)); const unsigned char* srcBytes = ::gdk_pixbuf_read_pixels(&pixBuf); const int srcWidth = ::gdk_pixbuf_get_width (&pixBuf); @@ -54,10 +53,10 @@ ImageHolder copyToImageHolder(const GdkPixbuf& pixBuf, int maxSize) //throw SysE targetWidth = numeric::intDivRound(targetWidth * maxSize, maxExtent); targetHeight = numeric::intDivRound(targetHeight * maxSize, maxExtent); -#if 0 //alternative to xbrz::bilinearScale() - GdkPixbuf* pixBufShrinked = ::gdk_pixbuf_scale_simple(pixBuf, //const GdkPixbuf* src, - targetWidth, //int dest_width, - targetHeight, //int dest_height, +#if 0 //alternative to xbrz::bilinearScaleSimple()? does it support alpha-channel? + GdkPixbuf* pixBufShrinked = ::gdk_pixbuf_scale_simple(pixBuf, //const GdkPixbuf* src + targetWidth, //int dest_width + targetHeight, //int dest_height GDK_INTERP_BILINEAR); //GdkInterpType interp_type if (!pixBufShrinked) throw SysError(formatSystemError("gdk_pixbuf_scale_simple", L"", L"Not enough memory.")); @@ -67,32 +66,38 @@ ImageHolder copyToImageHolder(const GdkPixbuf& pixBuf, int maxSize) //throw SysE const auto imgReader = [srcBytes, srcStride, channels](int x, int y, xbrz::BytePixel& pix) { - std::memcpy(pix, srcBytes + y * srcStride + channels * x, channels); + const unsigned char* const ptr = srcBytes + y * srcStride + channels * x; + + const unsigned char a = channels == 4 ? ptr[3] : 255; + pix[0] = a; + pix[1] = xbrz::premultiply(ptr[0], a); //r + pix[2] = xbrz::premultiply(ptr[1], a); //g + pix[3] = xbrz::premultiply(ptr[2], a); //b }; - ImageHolder imgOut(targetWidth, targetHeight, withAlpha); + ImageHolder imgOut(targetWidth, targetHeight, true /*withAlpha*/); - const auto imgWriter = [rgbPtr = imgOut.getRgb(), alphaPtr = imgOut.getAlpha()](const xbrz::BytePixel& pix) mutable + const auto imgWriter = [rgb = imgOut.getRgb(), alpha = imgOut.getAlpha()](const xbrz::BytePixel& pix) mutable { - *rgbPtr++ = pix[0]; //r - *rgbPtr++ = pix[1]; //g - *rgbPtr++ = pix[2]; //b - if (alphaPtr) - * alphaPtr++ = pix[3]; //a + const unsigned char a = pix[0]; + *alpha++ = a; + *rgb++ = xbrz::demultiply(pix[1], a); //r + *rgb++ = xbrz::demultiply(pix[2], a); //g + *rgb++ = xbrz::demultiply(pix[3], a); //b }; if (srcWidth == targetWidth && srcHeight == targetHeight) xbrz::unscaledCopy(imgReader, imgWriter, srcWidth, srcHeight); //perf: going overboard? else - xbrz::bilinearScale(imgReader, //PixReader srcReader, - srcWidth, //int srcWidth, - srcHeight, //int srcHeight, - imgWriter, //PixWriter trgWriter - targetWidth, //int trgWidth, - targetHeight, //int trgHeight, - 0, //int yFirst, - targetHeight); //int yLast, + xbrz::bilinearScaleSimple(imgReader, //PixReader srcReader + srcWidth, //int srcWidth + srcHeight, //int srcHeight + imgWriter, //PixWriter trgWriter + targetWidth, //int trgWidth + targetHeight, //int trgHeight + 0, //int yFirst + targetHeight); //int yLast return imgOut; } @@ -105,9 +110,9 @@ ImageHolder imageHolderFromGicon(GIcon& gicon, int maxSize) //throw SysError GtkIconTheme* const defaultTheme = ::gtk_icon_theme_get_default(); //not owned! ASSERT_SYSERROR(defaultTheme); //no more error details - GtkIconInfo* const iconInfo = ::gtk_icon_theme_lookup_by_gicon(defaultTheme, //GtkIconTheme* icon_theme, - &gicon, //GIcon* icon, - maxSize, //gint size, + GtkIconInfo* const iconInfo = ::gtk_icon_theme_lookup_by_gicon(defaultTheme, //GtkIconTheme* icon_theme + &gicon, //GIcon* icon + maxSize, //gint size GTK_ICON_LOOKUP_USE_BUILTIN); //GtkIconLookupFlags flags if (!iconInfo) throw SysError(formatSystemError("gtk_icon_theme_lookup_by_gicon", L"", L"Icon not available.")); @@ -135,9 +140,9 @@ ImageHolder imageHolderFromGicon(GIcon& gicon, int maxSize) //throw SysError FileIconHolder fff::getIconByTemplatePath(const Zstring& templatePath, int maxSize) //throw SysError { //uses full file name, e.g. "AUTHORS" has own mime type on Linux: - gchar* const contentType = ::g_content_type_guess(templatePath.c_str(), //const gchar* filename, - nullptr, //const guchar* data, - 0, //gsize data_size, + gchar* const contentType = ::g_content_type_guess(templatePath.c_str(), //const gchar* filename + nullptr, //const guchar* data + 0, //gsize data_size nullptr); //gboolean* result_uncertain if (!contentType) throw SysError(formatSystemError("g_content_type_guess(" + copyStringTo(templatePath) + ')', L"", L"Unknown content type.")); @@ -218,7 +223,7 @@ FileIconHolder fff::getFileIcon(const Zstring& filePath, int maxSize) //throw Sy ImageHolder fff::getThumbnailImage(const Zstring& filePath, int maxSize) //throw SysError { - struct ::stat fileInfo = {}; + struct stat fileInfo = {}; if (::stat(filePath.c_str(), &fileInfo) != 0) THROW_LAST_SYS_ERROR("stat"); @@ -241,13 +246,18 @@ ImageHolder fff::getThumbnailImage(const Zstring& filePath, int maxSize) //throw wxImage fff::extractWxImage(ImageHolder&& ih) { assert(runningOnMainThread()); - if (!ih.getRgb()) return wxNullImage; wxImage img(ih.getWidth(), ih.getHeight(), ih.releaseRgb(), false /*static_data*/); //pass ownership if (ih.getAlpha()) img.SetAlpha(ih.releaseAlpha(), false /*static_data*/); + else + { + assert(false); + img.SetAlpha(); + ::memset(img.GetAlpha(), wxIMAGE_ALPHA_OPAQUE, ih.getWidth() * ih.getHeight()); + } return img; } diff --git a/FreeFileSync/Source/base/icon_loader.h b/FreeFileSync/Source/base/icon_loader.h index ae4b7b43..754cffbc 100644 --- a/FreeFileSync/Source/base/icon_loader.h +++ b/FreeFileSync/Source/base/icon_loader.h @@ -16,7 +16,7 @@ namespace fff { //=> all functions are safe to call from multiple threads! //COM needs to be initialized before calling any of these functions! CoInitializeEx/CoUninitialize -//=> don't call from WM_PAINT handler! https://blogs.msdn.microsoft.com/yvesdolc/2009/08/06/do-you-receive-wm_paint-when-waiting-for-a-com-call-to-return/ +//=> don't call from WM_PAINT handler! https://docs.microsoft.com/en-us/archive/blogs/yvesdolc/do-you-receive-wm_paint-when-waiting-for-a-com-call-to-return zen::FileIconHolder getIconByTemplatePath(const Zstring& templatePath, int maxSize); //throw SysError zen::FileIconHolder genericFileIcon(int maxSize); //throw SysError diff --git a/FreeFileSync/Source/base/lock_holder.h b/FreeFileSync/Source/base/lock_holder.h index 87c075f9..68d8215e 100644 --- a/FreeFileSync/Source/base/lock_holder.h +++ b/FreeFileSync/Source/base/lock_holder.h @@ -31,7 +31,7 @@ public: { //lock file creation is synchronous and may block noticeably for very slow devices (USB sticks, mapped cloud storage) lockHolder_.emplace_back(appendSeparator(folderPath) + Zstr("sync") + LOCK_FILE_ENDING, - [&](const std::wstring& msg) { pcb.updateStatus(msg); /*throw X*/ }, + [&](std::wstring&& msg) { pcb.updateStatus(std::move(msg)); /*throw X*/ }, UI_UPDATE_INTERVAL / 2); //throw FileError } catch (const FileError& e) { failedLocks.emplace_back(folderPath, e); } diff --git a/FreeFileSync/Source/base/parallel_scan.cpp b/FreeFileSync/Source/base/parallel_scan.cpp index 340bf0d8..d61ec28b 100644 --- a/FreeFileSync/Source/base/parallel_scan.cpp +++ b/FreeFileSync/Source/base/parallel_scan.cpp @@ -64,7 +64,7 @@ public: errorResponse_ = std::nullopt; dummy.unlock(); //optimization for condition_variable::notify_all() - conditionReadyForNewRequest_.notify_all(); //instead of notify_one(); workaround bug: https://svn.boost.org/trac/boost/ticket/7796 + conditionReadyForNewRequest_.notify_all(); //instead of notify_one(); work around bug: https://svn.boost.org/trac/boost/ticket/7796 return rv; } @@ -89,14 +89,14 @@ public: switch (onError({errorRequest_->msg, errorRequest_->failTime, errorRequest_->retryNumber})) //throw X { case PhaseCallback::ignore: - errorResponse_ = AFS::TraverserCallback::ON_ERROR_CONTINUE; + errorResponse_ = AFS::TraverserCallback::HandleError::ignore; break; case PhaseCallback::retry: - errorResponse_ = AFS::TraverserCallback::ON_ERROR_RETRY; + errorResponse_ = AFS::TraverserCallback::HandleError::retry; break; } - conditionHaveResponse_.notify_all(); //instead of notify_one(); workaround bug: https://svn.boost.org/trac/boost/ticket/7796 + conditionHaveResponse_.notify_all(); //instead of notify_one(); work around bug: https://svn.boost.org/trac/boost/ticket/7796 } if (threadsToFinish_ == 0) { @@ -303,7 +303,7 @@ void DirCallback::onFile(const AFS::FileInfo& fi) //throw ThreadStopRequest Linux: retrieveFileID takes about 50% longer in VM! (avoidable because of redundant stat() call!) */ - output_.addSubFile(fi.itemName, FileAttributes(fi.modTime, fi.fileSize, fi.fileId, fi.isFollowedSymlink )); + output_.addSubFile(fi.itemName, FileAttributes(fi.modTime, fi.fileSize, fi.filePrint, fi.isFollowedSymlink)); cfg_.acb.incItemsScanned(); //add 1 element to the progress indicator } @@ -332,16 +332,15 @@ std::shared_ptr DirCallback::onFolder(const AFS::FolderI cfg_.acb.incItemsScanned(); //add 1 element to the progress indicator //------------------------------------------------------------------------------------ - warn_static("FIX: this error cannot be retried!") if (level_ > FOLDER_TRAVERSAL_LEVEL_MAX) //Win32 traverser: stack overflow approximately at level 1000 //check after FolderContainer::addSubFolder() for (size_t retryNumber = 0;; ++retryNumber) switch (reportItemError({replaceCpy(_("Cannot read directory %x."), L"%x", AFS::getDisplayPath(AFS::appendRelPath(cfg_.baseFolderPath, relPath))) + L"\n\n" L"Endless recursion.", std::chrono::steady_clock::now(), retryNumber}, fi.itemName)) //throw ThreadStopRequest { - case AFS::TraverserCallback::ON_ERROR_RETRY: + case AFS::TraverserCallback::HandleError::retry: break; - case AFS::TraverserCallback::ON_ERROR_CONTINUE: + case AFS::TraverserCallback::HandleError::ignore: return nullptr; } @@ -362,7 +361,7 @@ DirCallback::HandleLink DirCallback::onSymlink(const AFS::SymlinkInfo& si) //thr switch (cfg_.handleSymlinks) { case SymLinkHandling::exclude: - return LINK_SKIP; + return HandleLink::skip; case SymLinkHandling::direct: if (cfg_.filter.ref().passFileFilter(relPath)) //always use file filter: Link type may not be "stable" on Linux! @@ -370,7 +369,7 @@ DirCallback::HandleLink DirCallback::onSymlink(const AFS::SymlinkInfo& si) //thr output_.addSubLink(si.itemName, LinkAttributes(si.modTime)); cfg_.acb.incItemsScanned(); //add 1 element to the progress indicator } - return LINK_SKIP; + return HandleLink::skip; case SymLinkHandling::follow: //filter symlinks before trying to follow them: handle user-excluded broken symlinks! @@ -380,32 +379,32 @@ DirCallback::HandleLink DirCallback::onSymlink(const AFS::SymlinkInfo& si) //thr bool childItemMightMatch = true; if (!cfg_.filter.ref().passDirFilter(relPath, &childItemMightMatch)) if (!childItemMightMatch) - return LINK_SKIP; + return HandleLink::skip; } - return LINK_FOLLOW; + return HandleLink::follow; } assert(false); - return LINK_SKIP; + return HandleLink::skip; } DirCallback::HandleError DirCallback::reportError(const ErrorInfo& errorInfo, const Zstring& itemName /*optional*/) //throw ThreadStopRequest { - switch (cfg_.acb.reportError(errorInfo)) //throw ThreadStopRequest + const HandleError handleErr = cfg_.acb.reportError(errorInfo); //throw ThreadStopRequest + switch (handleErr) { - case ON_ERROR_CONTINUE: + case HandleError::ignore: if (itemName.empty()) cfg_.failedDirReads.emplace(beforeLast(parentRelPathPf_, FILE_NAME_SEPARATOR, IfNotFoundReturn::none), utfTo(errorInfo.msg)); else cfg_.failedItemReads.emplace(parentRelPathPf_ + itemName, utfTo(errorInfo.msg)); - return ON_ERROR_CONTINUE; + break; - case ON_ERROR_RETRY: - return ON_ERROR_RETRY; + case HandleError::retry: + break; } - assert(false); - return ON_ERROR_CONTINUE; + return handleErr; } } diff --git a/FreeFileSync/Source/base/parallel_scan.h b/FreeFileSync/Source/base/parallel_scan.h index 4675bbc4..4fe46643 100644 --- a/FreeFileSync/Source/base/parallel_scan.h +++ b/FreeFileSync/Source/base/parallel_scan.h @@ -33,10 +33,10 @@ struct DirectoryValue FolderContainer folderCont; //relative paths (or empty string for root) for directories that could not be read (completely), e.g. access denied, or temporary network drop - std::map failedFolderReads; //with corresponding error message + std::map failedFolderReads; - //relative paths (never empty) for failure to read single file/dir/symlink with corresponding error message - std::map failedItemReads; + //relative paths (never empty) for failure to read single file/dir/symlink + std::map failedItemReads; }; diff --git a/FreeFileSync/Source/base/process_callback.h b/FreeFileSync/Source/base/process_callback.h index b5b015d6..8540f872 100644 --- a/FreeFileSync/Source/base/process_callback.h +++ b/FreeFileSync/Source/base/process_callback.h @@ -37,11 +37,11 @@ struct PhaseCallback //opportunity to abort must be implemented in a frequently-executed method like requestUiUpdate() virtual void requestUiUpdate(bool force = false) = 0; //throw X - //UI info only, should not be logged: called periodically after data was processed: expected(!) to request GUI update - virtual void updateStatus(const std::wstring& msg) = 0; //throw X + //UI info only, should *not* be logged: called periodically after data was processed: expected(!) to request GUI update + virtual void updateStatus(std::wstring&& msg) = 0; //throw X - //like updateStatus() but should be logged: - virtual void reportInfo(const std::wstring& msg) = 0; //throw X + //log only; must *not* call updateStatus()! + virtual void logInfo(const std::wstring& msg) = 0; //throw X virtual void reportWarning(const std::wstring& msg, bool& warningActive) = 0; //throw X @@ -51,7 +51,6 @@ struct PhaseCallback std::chrono::steady_clock::time_point failTime; size_t retryNumber = 0; }; - enum Response { ignore, diff --git a/FreeFileSync/Source/base/status_handler_impl.h b/FreeFileSync/Source/base/status_handler_impl.h index 0841a752..611c45ee 100644 --- a/FreeFileSync/Source/base/status_handler_impl.h +++ b/FreeFileSync/Source/base/status_handler_impl.h @@ -32,35 +32,39 @@ public: } //context of worker thread - void updateStatus(const std::wstring& msg) //throw ThreadStopRequest + void updateStatus(std::wstring&& msg) //throw ThreadStopRequest { assert(!zen::runningOnMainThread()); { std::lock_guard dummy(lockCurrentStatus_); if (ThreadStatus* ts = getThreadStatus()) //call while holding "lockCurrentStatus_" lock!! - ts->statusMsg = msg; + ts->statusMsg = std::move(msg); else assert(false); } zen::interruptionPoint(); //throw ThreadStopRequest } //blocking call: context of worker thread - //=> indirect support for "pause": reportInfo() is called under singleThread lock, + //=> indirect support for "pause": logInfo() is called under singleThread lock, // so all other worker threads will wait when coming out of parallel I/O (trying to lock singleThread) - void reportInfo(const std::wstring& msg) //throw ThreadStopRequest + void logInfo(const std::wstring& msg) //throw ThreadStopRequest { - updateStatus(msg); //throw ThreadStopRequest - assert(!zen::runningOnMainThread()); std::unique_lock dummy(lockRequest_); - zen::interruptibleWait(conditionReadyForNewRequest_, dummy, [this] { return !reportInfoRequest_; }); //throw ThreadStopRequest + zen::interruptibleWait(conditionReadyForNewRequest_, dummy, [this] { return !logInfoRequest_; }); //throw ThreadStopRequest - reportInfoRequest_ = /*std::move(taskPrefix) + */ msg; + logInfoRequest_ = /*std::move(taskPrefix) + */ msg; dummy.unlock(); //optimization for condition_variable::notify_all() conditionNewRequest.notify_all(); } + void reportInfo(std::wstring&& msg) //throw ThreadStopRequest + { + logInfo(msg); //throw ThreadStopRequest + updateStatus(std::move(msg)); // + } + //blocking call: context of worker thread PhaseCallback::Response reportError(const PhaseCallback::ErrorInfo& errorInfo) //throw ThreadStopRequest { @@ -79,21 +83,21 @@ public: errorResponse_ = std::nullopt; dummy.unlock(); //optimization for condition_variable::notify_all() - conditionReadyForNewRequest_.notify_all(); //=> spurious wake-up for AsyncCallback::reportInfo() + conditionReadyForNewRequest_.notify_all(); //=> spurious wake-up for AsyncCallback::logInfo() return rv; } //context of main thread - void waitUntilDone(std::chrono::milliseconds duration, PhaseCallback& cb) //throw X + void waitUntilDone(std::chrono::milliseconds cbInterval, PhaseCallback& cb) //throw X { assert(zen::runningOnMainThread()); for (;;) { - const std::chrono::steady_clock::time_point callbackTime = std::chrono::steady_clock::now() + duration; + const std::chrono::steady_clock::time_point callbackTime = std::chrono::steady_clock::now() + cbInterval; for (std::unique_lock dummy(lockRequest_);;) //process all errors without delay { - const bool rv = conditionNewRequest.wait_until(dummy, callbackTime, [this] { return (errorRequest_ && !errorResponse_) || reportInfoRequest_ || finishNowRequest_; }); + const bool rv = conditionNewRequest.wait_until(dummy, callbackTime, [this] { return (errorRequest_ && !errorResponse_) || logInfoRequest_ || finishNowRequest_; }); if (!rv) //time-out + condition not met break; @@ -101,12 +105,12 @@ public: { assert(!finishNowRequest_); errorResponse_ = cb.reportError(*errorRequest_); //throw X - conditionHaveResponse_.notify_all(); //instead of notify_one(); workaround bug: https://svn.boost.org/trac/boost/ticket/7796 + conditionHaveResponse_.notify_all(); //instead of notify_one(); work around bug: https://svn.boost.org/trac/boost/ticket/7796 } - if (reportInfoRequest_) + if (logInfoRequest_) { - cb.reportInfo(*reportInfoRequest_); //throw X - reportInfoRequest_ = {}; + cb.logInfo(*logInfoRequest_); //throw X + logInfoRequest_ = {}; conditionReadyForNewRequest_.notify_all(); //=> spurious wake-up for AsyncCallback::reportError() } if (finishNowRequest_) @@ -117,7 +121,7 @@ public: } } - //call member functions outside of mutex scope: + //call back outside of mutex scope: cb.updateStatus(getCurrentStatus()); //throw X reportStats(cb); } @@ -146,7 +150,7 @@ public: if (statusByPriority_.size() < prio + 1) statusByPriority_.resize(prio + 1); - statusByPriority_[prio].push_back({ threadId, /*taskIdx,*/ std::wstring() }); + statusByPriority_[prio].push_back({threadId, /*taskIdx,*/ std::wstring()}); } void notifyTaskEnd() //noexcept @@ -264,7 +268,7 @@ private: std::condition_variable conditionHaveResponse_; std::optional errorRequest_; std::optional errorResponse_; - std::optional reportInfoRequest_; + std::optional logInfoRequest_; bool finishNowRequest_ = false; //---- status updates ---- @@ -275,10 +279,10 @@ private: //std::vector usedIndexNums_; //keep info for human-readable task index numbers //---- status updates II (lock-free) ---- - std::atomic itemsDeltaProcessed_{ 0 }; // - std::atomic bytesDeltaProcessed_{ 0 }; //std:atomic is uninitialized by default! - std::atomic itemsDeltaTotal_ { 0 }; // - std::atomic bytesDeltaTotal_ { 0 }; // + std::atomic itemsDeltaProcessed_{0}; // + std::atomic bytesDeltaProcessed_{0}; //std:atomic is uninitialized by default! + std::atomic itemsDeltaTotal_ {0}; // + std::atomic bytesDeltaTotal_ {0}; // }; @@ -303,7 +307,7 @@ public: cb_.updateDataTotal(itemsReported_ - itemsExpected_, bytesReported_ - bytesExpected_); //noexcept! } - void updateStatus(const std::wstring& msg) { cb_.updateStatus(msg); } //throw ThreadStopRequest + void updateStatus(std::wstring&& msg) { cb_.updateStatus(std::move(msg)); } //throw ThreadStopRequest void reportDelta(int itemsDelta, int64_t bytesDelta) //noexcept! { @@ -404,7 +408,7 @@ void massParallelExecute(const std::vector(file.getFileSize()); + bytesToProcess_ += static_cast(file.getFileSize()); break; case SO_CREATE_NEW_RIGHT: ++createRight_; - bytesToProcess_ += static_cast(file.getFileSize()); + bytesToProcess_ += static_cast(file.getFileSize()); break; case SO_DELETE_LEFT: @@ -117,20 +117,20 @@ void SyncStatistics::processFile(const FilePair& file) case SO_OVERWRITE_LEFT: ++updateLeft_; - bytesToProcess_ += static_cast(file.getFileSize()); + bytesToProcess_ += static_cast(file.getFileSize()); physicalDeleteLeft_ = true; break; case SO_OVERWRITE_RIGHT: ++updateRight_; - bytesToProcess_ += static_cast(file.getFileSize()); + bytesToProcess_ += static_cast(file.getFileSize()); physicalDeleteRight_ = true; break; case SO_UNRESOLVED_CONFLICT: ++conflictCount_; if (conflictsPreview_.size() < CONFLICTS_PREVIEW_MAX) - conflictsPreview_.push_back({ file.getRelativePathAny(), file.getSyncOpConflict() }); + conflictsPreview_.push_back({file.getRelativePathAny(), file.getSyncOpConflict()}); break; case SO_COPY_METADATA_TO_LEFT: @@ -186,7 +186,7 @@ void SyncStatistics::processLink(const SymlinkPair& link) case SO_UNRESOLVED_CONFLICT: ++conflictCount_; if (conflictsPreview_.size() < CONFLICTS_PREVIEW_MAX) - conflictsPreview_.push_back({ link.getRelativePathAny(), link.getSyncOpConflict() }); + conflictsPreview_.push_back({link.getRelativePathAny(), link.getSyncOpConflict()}); break; case SO_MOVE_LEFT_FROM: @@ -228,7 +228,7 @@ void SyncStatistics::processFolder(const FolderPair& folder) case SO_UNRESOLVED_CONFLICT: ++conflictCount_; if (conflictsPreview_.size() < CONFLICTS_PREVIEW_MAX) - conflictsPreview_.push_back({ folder.getRelativePathAny(), folder.getSyncOpConflict() }); + conflictsPreview_.push_back({folder.getRelativePathAny(), folder.getSyncOpConflict()}); break; case SO_OVERWRITE_LEFT: @@ -272,7 +272,7 @@ public: { MinimumDiskSpaceNeeded inst; inst.recurse(baseFolder); - return { inst.spaceNeededLeft_, inst.spaceNeededRight_ }; + return {inst.spaceNeededLeft_, inst.spaceNeededRight_}; } private: @@ -283,33 +283,33 @@ private: switch (file.getSyncOperation()) //evaluate comparison result and sync direction { case SO_CREATE_NEW_LEFT: - spaceNeededLeft_ += static_cast(file.getFileSize()); + spaceNeededLeft_ += static_cast(file.getFileSize()); break; case SO_CREATE_NEW_RIGHT: - spaceNeededRight_ += static_cast(file.getFileSize()); + spaceNeededRight_ += static_cast(file.getFileSize()); break; case SO_DELETE_LEFT: - if (!file.isFollowedSymlink()) - spaceNeededLeft_ -= static_cast(file.getFileSize()); + if (!file.isFollowedSymlink()) + spaceNeededLeft_ -= static_cast(file.getFileSize()); break; case SO_DELETE_RIGHT: - if (!file.isFollowedSymlink()) - spaceNeededRight_ -= static_cast(file.getFileSize()); + if (!file.isFollowedSymlink()) + spaceNeededRight_ -= static_cast(file.getFileSize()); break; case SO_OVERWRITE_LEFT: - if (!file.isFollowedSymlink()) - spaceNeededLeft_ -= static_cast(file.getFileSize()); - spaceNeededLeft_ += static_cast(file.getFileSize()); + if (!file.isFollowedSymlink()) + spaceNeededLeft_ -= static_cast(file.getFileSize()); + spaceNeededLeft_ += static_cast(file.getFileSize()); break; case SO_OVERWRITE_RIGHT: - if (!file.isFollowedSymlink()) - spaceNeededRight_ -= static_cast(file.getFileSize()); - spaceNeededRight_ += static_cast(file.getFileSize()); + if (!file.isFollowedSymlink()) + spaceNeededRight_ -= static_cast(file.getFileSize()); + spaceNeededRight_ += static_cast(file.getFileSize()); break; case SO_DO_NOTHING: @@ -332,11 +332,11 @@ private: switch (folder.getSyncOperation()) { case SO_DELETE_LEFT: - if (!folder.isFollowedSymlink()) + if (!folder.isFollowedSymlink()) recurse(folder); //not 100% correct: in fact more that what our model contains may be deleted (consider file filter!) break; case SO_DELETE_RIGHT: - if (!folder.isFollowedSymlink()) + if (!folder.isFollowedSymlink()) recurse(folder); break; @@ -369,7 +369,7 @@ private: std::vector fff::extractSyncCfg(const MainConfiguration& mainCfg) { //merge first and additional pairs - std::vector localCfgs = { mainCfg.firstPair }; + std::vector localCfgs = {mainCfg.firstPair}; append(localCfgs, mainCfg.additionalPairs); std::vector output; @@ -400,7 +400,7 @@ std::vector fff::extractSyncCfg(const MainConfiguration& main namespace { inline -std::optional getTargetDirection(SyncOperation syncOp) +std::optional getTargetDirection(SyncOperation syncOp) { switch (syncOp) { @@ -410,7 +410,7 @@ std::optional getTargetDirection(SyncOperation syncOp) case SO_COPY_METADATA_TO_LEFT: case SO_MOVE_LEFT_FROM: case SO_MOVE_LEFT_TO: - return LEFT_SIDE; + return SelectSide::left; case SO_CREATE_NEW_RIGHT: case SO_DELETE_RIGHT: @@ -418,7 +418,7 @@ std::optional getTargetDirection(SyncOperation syncOp) case SO_COPY_METADATA_TO_RIGHT: case SO_MOVE_RIGHT_FROM: case SO_MOVE_RIGHT_TO: - return RIGHT_SIDE; + return SelectSide::right; case SO_DO_NOTHING: case SO_EQUAL: @@ -433,8 +433,8 @@ std::optional getTargetDirection(SyncOperation syncOp) bool significantDifferenceDetected(const SyncStatistics& folderPairStat) { //initial file copying shall not be detected as major difference - if ((folderPairStat.createCount< LEFT_SIDE>() == 0 || - folderPairStat.createCount() == 0) && + if ((folderPairStat.createCount< SelectSide::left>() == 0 || + folderPairStat.createCount() == 0) && folderPairStat.updateCount () == 0 && folderPairStat.deleteCount () == 0 && folderPairStat.conflictCount() == 0) @@ -463,14 +463,15 @@ void flushFileBuffers(const Zstring& nativeFilePath) //throw FileError } -void verifyFiles(const AbstractPath& sourcePath, const AbstractPath& targetPath, const IOCallback& notifyUnbufferedIO /*throw X*/) //throw FileError, X +void verifyFiles(const AbstractPath& sourcePath, const AbstractPath& targetPath, const IoCallback& notifyUnbufferedIO /*throw X*/) //throw FileError, X { try { //do like "copy /v": 1. flush target file buffers, 2. read again as usual (using OS buffers) // => it seems OS buffers are not invalidated by this: snake oil??? - if (std::optional nativeTargetPath = AFS::getNativeItemPath(targetPath)) - flushFileBuffers(*nativeTargetPath); //throw FileError + if (const Zstring& targetPathNative = getNativeItemPath(targetPath); + !targetPathNative.empty()) + flushFileBuffers(targetPathNative); //throw FileError if (!filesHaveSameContent(sourcePath, targetPath, notifyUnbufferedIO)) //throw FileError, X throw FileError(replaceCpy(replaceCpy(_("%x and %y have different content."), @@ -546,7 +547,7 @@ AFS::FileCopyResult copyFileTransactional(const AbstractPath& apSource, const AF bool copyFilePermissions, bool transactionalCopy, const std::function& onDeleteTargetFile /*throw X*/, - const IOCallback& notifyUnbufferedIO /*throw X*/, + const IoCallback& notifyUnbufferedIO /*throw X*/, std::mutex& singleThread) { return parallelScope([=] @@ -560,7 +561,7 @@ void recycleItemIfExists(AFS::RecycleSession& recyclerSession, const AbstractPat { parallelScope([=, &recyclerSession] { return recyclerSession.recycleItemIfExists(ap, logicalRelPath); /*throw FileError*/ }, singleThread); } inline //FileVersioner::revisionFile() is internally synchronized! -void revisionFile(FileVersioner& versioner, const FileDescriptor& fileDescr, const Zstring& relativePath, const IOCallback& notifyUnbufferedIO /*throw X*/, std::mutex& singleThread) //throw FileError, X +void revisionFile(FileVersioner& versioner, const FileDescriptor& fileDescr, const Zstring& relativePath, const IoCallback& notifyUnbufferedIO /*throw X*/, std::mutex& singleThread) //throw FileError, X { parallelScope([=, &versioner] { return versioner.revisionFile(fileDescr, relativePath, notifyUnbufferedIO); /*throw FileError, X*/ }, singleThread); } inline //FileVersioner::revisionSymlink() is internally synchronized! @@ -572,12 +573,12 @@ void revisionFolder(FileVersioner& versioner, const AbstractPath& folderPath, const Zstring& relativePath, const std::function& onBeforeFileMove /*throw X*/, const std::function& onBeforeFolderMove /*throw X*/, - const IOCallback& notifyUnbufferedIO /*throw X*/, + const IoCallback& notifyUnbufferedIO /*throw X*/, std::mutex& singleThread) //throw FileError, X { parallelScope([=, &versioner] { versioner.revisionFolder(folderPath, relativePath, onBeforeFileMove, onBeforeFolderMove, notifyUnbufferedIO); /*throw FileError, X*/ }, singleThread); } inline -void verifyFiles(const AbstractPath& apSource, const AbstractPath& apTarget, const IOCallback& notifyUnbufferedIO /*throw X*/, std::mutex& singleThread) //throw FileError, X +void verifyFiles(const AbstractPath& apSource, const AbstractPath& apTarget, const IoCallback& notifyUnbufferedIO /*throw X*/, std::mutex& singleThread) //throw FileError, X { parallelScope([=] { ::verifyFiles(apSource, apTarget, notifyUnbufferedIO); /*throw FileError, X*/ }, singleThread); } } @@ -970,18 +971,24 @@ private: static bool containsMoveTarget(const FolderPair& parent); void executeFileMove(FilePair& file); //throw ThreadStopRequest - template void executeFileMoveImpl(FilePair& fileFrom, FilePair& fileTo); //throw ThreadStopRequest + template void executeFileMoveImpl(FilePair& fileFrom, FilePair& fileTo); //throw ThreadStopRequest - void synchronizeFile(FilePair& file); // - template void synchronizeFileInt(FilePair& file, SyncOperation syncOp); //throw FileError, ErrorMoveUnsupported, ThreadStopRequest + void synchronizeFile(FilePair& file); // + template void synchronizeFileInt(FilePair& file, SyncOperation syncOp); //throw FileError, ErrorMoveUnsupported, ThreadStopRequest - void synchronizeLink(SymlinkPair& link); // - template void synchronizeLinkInt(SymlinkPair& link, SyncOperation syncOp); //throw FileError, ThreadStopRequest + void synchronizeLink(SymlinkPair& link); // + template void synchronizeLinkInt(SymlinkPair& link, SyncOperation syncOp); //throw FileError, ThreadStopRequest - void synchronizeFolder(FolderPair& folder); // - template void synchronizeFolderInt(FolderPair& folder, SyncOperation syncOp); //throw FileError, ThreadStopRequest + void synchronizeFolder(FolderPair& folder); // + template void synchronizeFolderInt(FolderPair& folder, SyncOperation syncOp); //throw FileError, ThreadStopRequest + void logInfo(const std::wstring& rawText, const std::wstring& displayPath) { acb_.logInfo (replaceCpy(rawText, L"%x", fmtPath(displayPath))); } void reportInfo(const std::wstring& rawText, const std::wstring& displayPath) { acb_.reportInfo(replaceCpy(rawText, L"%x", fmtPath(displayPath))); } + + void logInfo(const std::wstring& rawText, const std::wstring& displayPath1, const std::wstring& displayPath2) //throw ThreadStopRequest + { + acb_.logInfo(replaceCpy(replaceCpy(rawText, L"%x", L'\n' + fmtPath(displayPath1)), L"%y", L'\n' + fmtPath(displayPath2))); //throw ThreadStopRequest + } void reportInfo(const std::wstring& rawText, const std::wstring& displayPath1, const std::wstring& displayPath2) //throw ThreadStopRequest { acb_.reportInfo(replaceCpy(replaceCpy(rawText, L"%x", L'\n' + fmtPath(displayPath1)), L"%y", L'\n' + fmtPath(displayPath2))); //throw ThreadStopRequest @@ -1146,10 +1153,9 @@ RingBuffer FolderPairSyncer::getFolderLevelWorkItems(PassNo } -/* - __________________________ - |Move algorithm, 0th pass| - -------------------------- +/* __________________________ + |Move algorithm, 0th pass| + -------------------------- 1. loop over hierarchy and find "move targets" => remember required parent folders 2. create required folders hierarchically: @@ -1174,7 +1180,7 @@ RingBuffer FolderPairSyncer::getFolderLevelWorkItems(PassNo b -> c/b a -> b/a */ -template +template void FolderPairSyncer::executeFileMoveImpl(FilePair& fileFrom, FilePair& fileTo) //throw ThreadStopRequest { const bool fallBackCopyDelete = [&] @@ -1186,10 +1192,10 @@ void FolderPairSyncer::executeFileMoveImpl(FilePair& fileFrom, FilePair& fileTo) if (parentMissing) { - reportInfo(_("Cannot move file %x to %y.") + L"\n\n" + - replaceCpy(_("Parent folder %x is not existing."), L"%x", fmtPath(AFS::getDisplayPath(parentMissing->getAbstractPath()))), - AFS::getDisplayPath(fileFrom.getAbstractPath()), - AFS::getDisplayPath(fileTo .getAbstractPath())); //throw ThreadStopRequest + logInfo(_("Cannot move file %x to %y.") + L"\n\n" + + replaceCpy(_("Parent folder %x is not existing."), L"%x", fmtPath(AFS::getDisplayPath(parentMissing->getAbstractPath()))), + AFS::getDisplayPath(fileFrom.getAbstractPath()), + AFS::getDisplayPath(fileTo .getAbstractPath())); //throw ThreadStopRequest return true; } @@ -1197,9 +1203,9 @@ void FolderPairSyncer::executeFileMoveImpl(FilePair& fileFrom, FilePair& fileTo) if (haveNameClash(fileTo.getItemNameAny(), fileTo.parent().refSubFolders()) || haveNameClash(fileTo.getItemNameAny(), fileTo.parent().refSubLinks ())) { - reportInfo(_("Cannot move file %x to %y.") + L"\n\n" + replaceCpy(_("The name %x is already used by another item."), L"%x", fmtPath(fileTo.getItemNameAny())), - AFS::getDisplayPath(fileFrom.getAbstractPath()), - AFS::getDisplayPath(fileTo .getAbstractPath())); //throw ThreadStopRequest + logInfo(_("Cannot move file %x to %y.") + L"\n\n" + replaceCpy(_("The name %x is already used by another item."), L"%x", fmtPath(fileTo.getItemNameAny())), + AFS::getDisplayPath(fileFrom.getAbstractPath()), + AFS::getDisplayPath(fileTo .getAbstractPath())); //throw ThreadStopRequest return true; } @@ -1212,7 +1218,7 @@ void FolderPairSyncer::executeFileMoveImpl(FilePair& fileFrom, FilePair& fileTo) } catch (const ErrorMoveUnsupported& e) { - acb_.reportInfo(e.toString()); //let user know that move operation is not supported, then fall back: + acb_.logInfo(e.toString()); //let user know that move operation is not supported, then fall back: moveSupported = false; } }, acb_); @@ -1226,7 +1232,7 @@ void FolderPairSyncer::executeFileMoveImpl(FilePair& fileFrom, FilePair& fileTo) { SyncStatistics statSrc(fileFrom); SyncStatistics statTrg(fileTo); - return { getCUD(statSrc) + getCUD(statTrg), statSrc.getBytesToProcess() + statTrg.getBytesToProcess() }; + return {getCUD(statSrc) + getCUD(statTrg), statSrc.getBytesToProcess() + statTrg.getBytesToProcess()}; }; const auto [itemsBefore, bytesBefore] = getStats(); fileFrom.setMoveRef(nullptr); @@ -1251,9 +1257,9 @@ void FolderPairSyncer::executeFileMove(FilePair& file) //throw ThreadStopRequest assert(fileFrom->getMoveRef() == file.getId()); if (syncOp == SO_MOVE_LEFT_TO) - executeFileMoveImpl(*fileFrom, file); //throw ThreadStopRequest + executeFileMoveImpl(*fileFrom, file); //throw ThreadStopRequest else - executeFileMoveImpl(*fileFrom, file); //throw ThreadStopRequest + executeFileMoveImpl(*fileFrom, file); //throw ThreadStopRequest } else assert(false); break; @@ -1307,7 +1313,7 @@ bool FolderPairSyncer::needZeroPass(const FolderPair& folder) case SO_OVERWRITE_RIGHT: // case SO_COPY_METADATA_TO_LEFT: case SO_COPY_METADATA_TO_RIGHT: - assert((!folder.isEmpty() && !folder.isEmpty()) || !containsMoveTarget(folder)); + assert((!folder.isEmpty() && !folder.isEmpty()) || !containsMoveTarget(folder)); //we're good to move contained items break; case SO_DELETE_LEFT: //not possible in the context of planning to move a child item, see FolderPair::getSyncOperation() @@ -1367,10 +1373,10 @@ FolderPairSyncer::PassNo FolderPairSyncer::getPass(const FilePair& file) return PassNo::one; case SO_OVERWRITE_LEFT: - return file.getFileSize() > file.getFileSize() ? PassNo::one : PassNo::two; + return file.getFileSize() > file.getFileSize() ? PassNo::one : PassNo::two; case SO_OVERWRITE_RIGHT: - return file.getFileSize() < file.getFileSize() ? PassNo::one : PassNo::two; + return file.getFileSize() < file.getFileSize() ? PassNo::one : PassNo::two; case SO_MOVE_LEFT_FROM: // case SO_MOVE_RIGHT_FROM: // [!] @@ -1467,20 +1473,20 @@ void FolderPairSyncer::synchronizeFile(FilePair& file) //throw FileError, ErrorM { const SyncOperation syncOp = file.getSyncOperation(); - if (std::optional sideTrg = getTargetDirection(syncOp)) + if (std::optional sideTrg = getTargetDirection(syncOp)) { - if (*sideTrg == LEFT_SIDE) - synchronizeFileInt(file, syncOp); + if (*sideTrg == SelectSide::left) + synchronizeFileInt(file, syncOp); else - synchronizeFileInt(file, syncOp); + synchronizeFileInt(file, syncOp); } } -template +template void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) //throw FileError, ErrorMoveUnsupported, ThreadStopRequest { - constexpr SelectedSide sideSrc = OtherSide::value; + constexpr SelectSide sideSrc = OtherSide::value; DeletionHandler& delHandlerTrg = SelectParam::ref(delHandlerLeft_, delHandlerRight_); switch (syncOp) @@ -1498,7 +1504,7 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) AsyncItemStatReporter statReporter(1, file.getFileSize(), acb_); try { - const AFS::FileCopyResult result = copyFileWithCallback({ file.getAbstractPath(), file.getAttributes() }, + const AFS::FileCopyResult result = copyFileWithCallback({file.getAbstractPath(), file.getAttributes()}, targetPath, nullptr, //onDeleteTargetFile: nothing to delete //if existing: undefined behavior! (e.g. fail/overwrite/auto-rename) @@ -1509,8 +1515,8 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) file.setSyncedTo(file.getItemName(), result.fileSize, result.modTime, //target time set from source result.modTime, - result.targetFileId, - result.sourceFileId, + result.targetFilePrint, + result.sourceFilePrint, false, file.isFollowedSymlink()); if (result.errorModTime) @@ -1521,7 +1527,7 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) break; case CompareVariant::content: //just log, no warning: case CompareVariant::size: //e.g. FTP server not supporting MFMT command - acb_.reportInfo(result.errorModTime->toString()); + acb_.logInfo(result.errorModTime->toString()); break; } } @@ -1539,7 +1545,7 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) statReporter.reportDelta(1, 0); //even if the source item does not exist anymore, significant I/O work was done => report file.removeObject(); //source deleted meanwhile...nothing was done (logical point of view!) - reportInfo(txtSourceItemNotFound_, AFS::getDisplayPath(file.getAbstractPath())); //throw ThreadStopRequest + logInfo(txtSourceItemNotFound_, AFS::getDisplayPath(file.getAbstractPath())); //throw ThreadStopRequest } else throw; @@ -1553,7 +1559,7 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) { AsyncItemStatReporter statReporter(1, 0, acb_); - delHandlerTrg.removeFileWithCallback({ file.getAbstractPath(), file.getAttributes() }, + delHandlerTrg.removeFileWithCallback({file.getAbstractPath(), file.getAttributes()}, file.getRelativePath(), statReporter, singleThread_); //throw FileError, X file.removeObject(); //update FilePair } @@ -1566,8 +1572,8 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) FilePair* fileTo = &file; assert(fileFrom->getMoveRef() == fileTo->getId()); - assert((fileFrom->getSyncOperation() == SO_MOVE_LEFT_FROM && fileTo->getSyncOperation() == SO_MOVE_LEFT_TO && sideTrg == LEFT_SIDE) || - (fileFrom->getSyncOperation() == SO_MOVE_RIGHT_FROM && fileTo->getSyncOperation() == SO_MOVE_RIGHT_TO && sideTrg == RIGHT_SIDE)); + assert((fileFrom->getSyncOperation() == SO_MOVE_LEFT_FROM && fileTo->getSyncOperation() == SO_MOVE_LEFT_TO && sideTrg == SelectSide::left) || + (fileFrom->getSyncOperation() == SO_MOVE_RIGHT_FROM && fileTo->getSyncOperation() == SO_MOVE_RIGHT_TO && sideTrg == SelectSide::right)); const AbstractPath pathFrom = fileFrom->getAbstractPath(); const AbstractPath pathTo = fileTo ->getAbstractPath(); @@ -1587,8 +1593,8 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) fileTo ->getFileSize(), fileFrom->getLastWriteTime(), fileTo ->getLastWriteTime(), - fileFrom->getFileId(), - fileTo ->getFileId(), + fileFrom->getFilePrint(), + fileTo ->getFilePrint(), fileFrom->isFollowedSymlink(), fileTo ->isFollowedSymlink()); fileFrom->removeObject(); //remove only *after* evaluating "fileFrom, sideTrg"! @@ -1624,7 +1630,7 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) FileAttributes followedTargetAttr = file.getAttributes(); followedTargetAttr.isFollowedSymlink = false; - delHandlerTrg.removeFileWithCallback({ targetPathResolvedOld, followedTargetAttr }, file.getRelativePath(), statReporter, singleThread_); //throw FileError, X + delHandlerTrg.removeFileWithCallback({targetPathResolvedOld, followedTargetAttr}, file.getRelativePath(), statReporter, singleThread_); //throw FileError, X //no (logical) item count update desired - but total byte count may change, e.g. move(copy) old file to versioning dir statReporter.reportDelta(-1, 0); //undo item stats reporting within DeletionHandler::removeFileWithCallback() @@ -1635,7 +1641,7 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) //=> if failSafeFileCopy_ : don't run callbacks that could throw }; - const AFS::FileCopyResult result = copyFileWithCallback({ file.getAbstractPath(), file.getAttributes() }, + const AFS::FileCopyResult result = copyFileWithCallback({file.getAbstractPath(), file.getAttributes()}, targetPathResolvedNew, onDeleteTargetFile, statReporter); //throw FileError, ThreadStopRequest, X @@ -1645,8 +1651,8 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) file.setSyncedTo(file.getItemName(), result.fileSize, result.modTime, //target time set from source result.modTime, - result.targetFileId, - result.sourceFileId, + result.targetFilePrint, + result.sourceFilePrint, file.isFollowedSymlink(), file.isFollowedSymlink()); @@ -1658,7 +1664,7 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) break; case CompareVariant::content: //just log, no warning: case CompareVariant::size: //e.g. FTP server not supporting MFMT command - acb_.reportInfo(result.errorModTime->toString()); + acb_.logInfo(result.errorModTime->toString()); break; } } @@ -1691,10 +1697,10 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) //-> both sides *should* be completely equal now... assert(file.getFileSize() == file.getFileSize()); file.setSyncedTo(file.getItemName(), file.getFileSize(), - file.getLastWriteTime(), - file.getLastWriteTime(), - file.getFileId (), - file.getFileId (), + file.getLastWriteTime (), + file.getLastWriteTime (), + file.getFilePrint (), + file.getFilePrint (), file.isFollowedSymlink(), file.isFollowedSymlink()); } @@ -1716,20 +1722,20 @@ void FolderPairSyncer::synchronizeLink(SymlinkPair& link) //throw FileError, Thr { const SyncOperation syncOp = link.getSyncOperation(); - if (std::optional sideTrg = getTargetDirection(syncOp)) + if (std::optional sideTrg = getTargetDirection(syncOp)) { - if (*sideTrg == LEFT_SIDE) - synchronizeLinkInt(link, syncOp); + if (*sideTrg == SelectSide::left) + synchronizeLinkInt(link, syncOp); else - synchronizeLinkInt(link, syncOp); + synchronizeLinkInt(link, syncOp); } } -template +template void FolderPairSyncer::synchronizeLinkInt(SymlinkPair& symlink, SyncOperation syncOp) //throw FileError, ThreadStopRequest { - constexpr SelectedSide sideSrc = OtherSide::value; + constexpr SelectSide sideSrc = OtherSide::value; DeletionHandler& delHandlerTrg = SelectParam::ref(delHandlerLeft_, delHandlerRight_); switch (syncOp) @@ -1771,7 +1777,7 @@ void FolderPairSyncer::synchronizeLinkInt(SymlinkPair& symlink, SyncOperation sy statReporter.reportDelta(1, 0); symlink.removeObject(); //source deleted meanwhile...nothing was done (logical point of view!) - reportInfo(txtSourceItemNotFound_, AFS::getDisplayPath(symlink.getAbstractPath())); //throw ThreadStopRequest + logInfo(txtSourceItemNotFound_, AFS::getDisplayPath(symlink.getAbstractPath())); //throw ThreadStopRequest } else throw; @@ -1865,20 +1871,20 @@ void FolderPairSyncer::synchronizeFolder(FolderPair& folder) //throw FileError, { const SyncOperation syncOp = folder.getSyncOperation(); - if (std::optional sideTrg = getTargetDirection(syncOp)) + if (std::optional sideTrg = getTargetDirection(syncOp)) { - if (*sideTrg == LEFT_SIDE) - synchronizeFolderInt(folder, syncOp); + if (*sideTrg == SelectSide::left) + synchronizeFolderInt(folder, syncOp); else - synchronizeFolderInt(folder, syncOp); + synchronizeFolderInt(folder, syncOp); } } -template +template void FolderPairSyncer::synchronizeFolderInt(FolderPair& folder, SyncOperation syncOp) //throw FileError, ThreadStopRequest { - constexpr SelectedSide sideSrc = OtherSide::value; + constexpr SelectSide sideSrc = OtherSide::value; DeletionHandler& delHandlerTrg = SelectParam::ref(delHandlerLeft_, delHandlerRight_); switch (syncOp) @@ -1932,7 +1938,7 @@ void FolderPairSyncer::synchronizeFolderInt(FolderPair& folder, SyncOperation sy acb_.updateDataProcessed(1, 0); //even if the source item does not exist anymore, significant I/O work was done => report acb_.updateDataTotal(getCUD(statsAfter) - getCUD(statsBefore) + 1, statsAfter.getBytesToProcess() - statsBefore.getBytesToProcess()); //noexcept - reportInfo(txtSourceItemNotFound_, AFS::getDisplayPath(folder.getAbstractPath())); //throw ThreadStopRequest + logInfo(txtSourceItemNotFound_, AFS::getDisplayPath(folder.getAbstractPath())); //throw ThreadStopRequest } } break; @@ -2002,7 +2008,7 @@ AFS::FileCopyResult FolderPairSyncer::copyFileWithCallback(const FileDescriptor& AsyncItemStatReporter& statReporter) { const AbstractPath& sourcePath = sourceDescr.path; - const AFS::StreamAttributes sourceAttr{ sourceDescr.attr.modTime, sourceDescr.attr.fileSize, sourceDescr.attr.fileId }; + const AFS::StreamAttributes sourceAttr{sourceDescr.attr.modTime, sourceDescr.attr.fileSize, sourceDescr.attr.filePrint}; auto copyOperation = [this, &sourceAttr, &targetPath, &onDeleteTargetFile, &statReporter](const AbstractPath& sourcePathTmp) { @@ -2048,7 +2054,7 @@ AFS::FileCopyResult FolderPairSyncer::copyFileWithCallback(const FileDescriptor& //########################################################################################### -template +template bool baseFolderDrop(BaseFolderPair& baseFolder, PhaseCallback& callback) { const AbstractPath folderPath = baseFolder.getAbstractPath(); @@ -2057,7 +2063,7 @@ bool baseFolderDrop(BaseFolderPair& baseFolder, PhaseCallback& callback) { const std::wstring errMsg = tryReportingError([&] { - const FolderStatus status = getFolderStatusNonBlocking({ folderPath }, + const FolderStatus status = getFolderStatusNonBlocking({folderPath}, false /*allowUserInteraction*/, callback); static_assert(std::is_same_vsecond), FileError>); @@ -2076,10 +2082,10 @@ bool baseFolderDrop(BaseFolderPair& baseFolder, PhaseCallback& callback) } -template //create base directories first (if not yet existing) -> no symlink or attribute copying! +template //create base directories first (if not yet existing) -> no symlink or attribute copying! bool createBaseFolder(BaseFolderPair& baseFolder, bool copyFilePermissions, PhaseCallback& callback) //return false if fatal error occurred { - static const SelectedSide sideSrc = OtherSide::value; + static const SelectSide sideSrc = OtherSide::value; const AbstractPath baseFolderPath = baseFolder.getAbstractPath(); if (AFS::isNullPath(baseFolderPath)) @@ -2090,7 +2096,7 @@ bool createBaseFolder(BaseFolderPair& baseFolder, bool copyFilePermissions, Phas bool temporaryNetworkDrop = false; const std::wstring errMsg = tryReportingError([&] { - const FolderStatus status = getFolderStatusNonBlocking({ baseFolderPath }, + const FolderStatus status = getFolderStatusNonBlocking({baseFolderPath}, false /*allowUserInteraction*/, callback); static_assert(std::is_same_vsecond), FileError>); @@ -2196,7 +2202,7 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime } catch (const FileError& e) //failure is not critical => log only { - callback.reportInfo(e.toString()); //throw X + callback.logInfo(e.toString()); //throw X } //-------------------execute basic checks all at once BEFORE starting sync-------------------------------------- @@ -2243,8 +2249,8 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime //=============================================================================== //exclude a few pathological cases (including empty left, right folders) - if (baseFolder.getAbstractPath< LEFT_SIDE>() == - baseFolder.getAbstractPath()) + if (baseFolder.getAbstractPath< SelectSide::left>() == + baseFolder.getAbstractPath()) { jobType[folderIndex] = FolderPairJobType::SKIP; continue; @@ -2258,17 +2264,17 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime continue; } - const bool writeLeft = folderPairStat.createCount() + - folderPairStat.updateCount() + - folderPairStat.deleteCount() > 0; + const bool writeLeft = folderPairStat.createCount() + + folderPairStat.updateCount() + + folderPairStat.deleteCount() > 0; - const bool writeRight = folderPairStat.createCount() + - folderPairStat.updateCount() + - folderPairStat.deleteCount() > 0; + const bool writeRight = folderPairStat.createCount() + + folderPairStat.updateCount() + + folderPairStat.deleteCount() > 0; //check for empty target folder paths: this only makes sense if empty field is source (and no DB files need to be created) - if ((AFS::isNullPath(baseFolder.getAbstractPath< LEFT_SIDE>()) && (writeLeft || folderPairCfg.saveSyncDB)) || - (AFS::isNullPath(baseFolder.getAbstractPath()) && (writeRight || folderPairCfg.saveSyncDB))) + if ((AFS::isNullPath(baseFolder.getAbstractPath< SelectSide::left>()) && (writeLeft || folderPairCfg.saveSyncDB)) || + (AFS::isNullPath(baseFolder.getAbstractPath()) && (writeRight || folderPairCfg.saveSyncDB))) { callback.reportFatalError(_("Target folder input field must not be empty.")); jobType[folderIndex] = FolderPairJobType::SKIP; @@ -2278,8 +2284,8 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime //check for network drops after comparison // - convenience: exit sync right here instead of showing tons of errors during file copy // - early failure! there's no point in evaluating subsequent warnings - if (baseFolderDrop< LEFT_SIDE>(baseFolder, callback) || - baseFolderDrop(baseFolder, callback)) + if (baseFolderDrop< SelectSide::left>(baseFolder, callback) || + baseFolderDrop(baseFolder, callback)) { jobType[folderIndex] = FolderPairJobType::SKIP; continue; @@ -2300,8 +2306,8 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime } return false; }; - if (sourceFolderMissing(baseFolder.getAbstractPath< LEFT_SIDE>(), baseFolder.isAvailable< LEFT_SIDE>()) || - sourceFolderMissing(baseFolder.getAbstractPath(), baseFolder.isAvailable())) + if (sourceFolderMissing(baseFolder.getAbstractPath< SelectSide::left>(), baseFolder.isAvailable< SelectSide::left>()) || + sourceFolderMissing(baseFolder.getAbstractPath(), baseFolder.isAvailable())) { jobType[folderIndex] = FolderPairJobType::SKIP; continue; @@ -2323,20 +2329,20 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime //prepare: check if versioning path itself will be synchronized (and was not excluded via filter) checkVersioningPaths.insert(versioningFolderPath); - checkVersioningBasePaths.emplace_back(baseFolder.getAbstractPath< LEFT_SIDE>(), &baseFolder.getFilter()); - checkVersioningBasePaths.emplace_back(baseFolder.getAbstractPath(), &baseFolder.getFilter()); + checkVersioningBasePaths.emplace_back(baseFolder.getAbstractPath< SelectSide::left>(), &baseFolder.getFilter()); + checkVersioningBasePaths.emplace_back(baseFolder.getAbstractPath(), &baseFolder.getFilter()); } //prepare: check if folders are used by multiple pairs in read/write access - checkReadWriteBaseFolders.emplace_back(baseFolder.getAbstractPath< LEFT_SIDE>(), &baseFolder.getFilter(), writeLeft); - checkReadWriteBaseFolders.emplace_back(baseFolder.getAbstractPath(), &baseFolder.getFilter(), writeRight); + checkReadWriteBaseFolders.emplace_back(baseFolder.getAbstractPath< SelectSide::left>(), &baseFolder.getFilter(), writeLeft); + checkReadWriteBaseFolders.emplace_back(baseFolder.getAbstractPath(), &baseFolder.getFilter(), writeRight); //check if more than 50% of total number of files/dirs are to be created/overwritten/deleted - if (!AFS::isNullPath(baseFolder.getAbstractPath< LEFT_SIDE>()) && - !AFS::isNullPath(baseFolder.getAbstractPath())) + if (!AFS::isNullPath(baseFolder.getAbstractPath< SelectSide::left>()) && + !AFS::isNullPath(baseFolder.getAbstractPath())) if (significantDifferenceDetected(folderPairStat)) - checkSignificantDiffPairs.emplace_back(baseFolder.getAbstractPath< LEFT_SIDE>(), - baseFolder.getAbstractPath()); + checkSignificantDiffPairs.emplace_back(baseFolder.getAbstractPath< SelectSide::left>(), + baseFolder.getAbstractPath()); //check for sufficient free diskspace auto checkSpace = [&](const AbstractPath& baseFolderPath, int64_t minSpaceNeeded) @@ -2348,16 +2354,16 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime if (0 <= freeSpace && freeSpace < minSpaceNeeded) - checkDiskSpaceMissing.push_back({ baseFolderPath, { minSpaceNeeded, freeSpace } }); + checkDiskSpaceMissing.push_back({baseFolderPath, {minSpaceNeeded, freeSpace}}); } catch (const FileError& e) //failure is not critical => log only { - callback.reportInfo(e.toString()); //throw X + callback.logInfo(e.toString()); //throw X } }; const std::pair spaceNeeded = MinimumDiskSpaceNeeded::calculate(baseFolder); - checkSpace(baseFolder.getAbstractPath< LEFT_SIDE>(), spaceNeeded.first); - checkSpace(baseFolder.getAbstractPath(), spaceNeeded.second); + checkSpace(baseFolder.getAbstractPath< SelectSide::left>(), spaceNeeded.first); + checkSpace(baseFolder.getAbstractPath(), spaceNeeded.second); //Windows: check if recycle bin really exists; if not, Windows will silently delete, which is just wrong auto checkRecycler = [&](const AbstractPath& baseFolderPath) @@ -2379,11 +2385,11 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime }; if (folderPairCfg.handleDeletion == DeletionPolicy::recycler) { - if (folderPairStat.expectPhysicalDeletion()) - checkRecycler(baseFolder.getAbstractPath()); + if (folderPairStat.expectPhysicalDeletion()) + checkRecycler(baseFolder.getAbstractPath()); - if (folderPairStat.expectPhysicalDeletion()) - checkRecycler(baseFolder.getAbstractPath()); + if (folderPairStat.expectPhysicalDeletion()) + checkRecycler(baseFolder.getAbstractPath()); } } //----------------------------------------------------------------- @@ -2399,8 +2405,8 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime if (conflictCount > 0) { msg += L"\n\n" + _("Folder pair:") + L' ' + - AFS::getDisplayPath(baseFolder->getAbstractPath< LEFT_SIDE>()) + L" <-> " + - AFS::getDisplayPath(baseFolder->getAbstractPath()); + AFS::getDisplayPath(baseFolder->getAbstractPath< SelectSide::left>()) + L" <-> " + + AFS::getDisplayPath(baseFolder->getAbstractPath()); for (const SyncStatistics::ConflictInfo& item : conflictPreview) msg += L'\n' + utfTo(item.relPath) + L": " + item.msg; @@ -2567,7 +2573,7 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime if (!scopeFail) callback.reportWarning(msg, warnings.warnModificationTimeError); //throw X else //at least log warnings when sync is cancelled - try { callback.reportInfo(msg); /*throw X*/} catch (...) {}; + try { callback.logInfo(msg); /*throw X*/} catch (...) {}; } //*INDENT-ON* ); @@ -2575,19 +2581,19 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime class PcbNoThrow : public PhaseCallback { public: - PcbNoThrow(ProcessCallback& cb) : cb_(cb) {} + explicit PcbNoThrow(ProcessCallback& cb) : cb_(cb) {} void updateDataProcessed(int itemsDelta, int64_t bytesDelta) override {} //sync DB/del-handler: logically not part of sync data, so let's ignore void updateDataTotal (int itemsDelta, int64_t bytesDelta) override {} // void requestUiUpdate(bool force) override { try { cb_.requestUiUpdate(force); /*throw X*/} catch (...) {}; } - void updateStatus(const std::wstring& msg) override { try { cb_.updateStatus(msg); /*throw X*/} catch (...) {}; } - void reportInfo (const std::wstring& msg) override { try { cb_.reportInfo (msg); /*throw X*/} catch (...) {}; } + void updateStatus(std::wstring&& msg) override { try { cb_.updateStatus(std::move(msg)); /*throw X*/} catch (...) {}; } + void logInfo(const std::wstring& msg) override { try { cb_.logInfo(msg); /*throw X*/} catch (...) {}; } - void reportWarning(const std::wstring& msg, bool& warningActive) override { reportInfo(msg); /*ignore*/ } - Response reportError (const ErrorInfo& errorInfo) override { reportInfo(errorInfo.msg); return Response::ignore; } - void reportFatalError(const std::wstring& msg) override { reportInfo(msg); /*ignore*/ } + void reportWarning(const std::wstring& msg, bool& warningActive) override { logInfo(msg); /*ignore*/ } + Response reportError (const ErrorInfo& errorInfo) override { logInfo(errorInfo.msg); return Response::ignore; } + void reportFatalError(const std::wstring& msg) override { logInfo(msg); /*ignore*/ } private: ProcessCallback& cb_; @@ -2608,20 +2614,20 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime //------------------------------------------------------------------------------------------ if (folderCmp.size() > 1) - callback.reportInfo(_("Synchronizing folder pair:") + L' ' + getVariantNameWithSymbol(folderPairCfg.syncVar) + L'\n' + //throw X - L" " + AFS::getDisplayPath(baseFolder.getAbstractPath< LEFT_SIDE>()) + L'\n' + - L" " + AFS::getDisplayPath(baseFolder.getAbstractPath())); + callback.logInfo(_("Synchronizing folder pair:") + L' ' + getVariantNameWithSymbol(folderPairCfg.syncVar) + L'\n' + //throw X + L" " + AFS::getDisplayPath(baseFolder.getAbstractPath< SelectSide::left>()) + L'\n' + + L" " + AFS::getDisplayPath(baseFolder.getAbstractPath())); //------------------------------------------------------------------------------------------ //checking a second time: (a long time may have passed since syncing the previous folder pairs!) - if (baseFolderDrop< LEFT_SIDE>(baseFolder, callback) || - baseFolderDrop(baseFolder, callback)) + if (baseFolderDrop< SelectSide::left>(baseFolder, callback) || + baseFolderDrop(baseFolder, callback)) continue; //create base folders if not yet existing if (folderPairStat.createCount() > 0 || folderPairCfg.saveSyncDB) //else: temporary network drop leading to deletions already caught by "sourceFolderMissing" check! - if (!createBaseFolder< LEFT_SIDE>(baseFolder, copyFilePermissions, callback) || //+ detect temporary network drop!! - !createBaseFolder(baseFolder, copyFilePermissions, callback)) // + if (!createBaseFolder< SelectSide::left>(baseFolder, copyFilePermissions, callback) || //+ detect temporary network drop!! + !createBaseFolder(baseFolder, copyFilePermissions, callback)) // continue; //------------------------------------------------------------------------------------------ @@ -2644,10 +2650,10 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime tryReportingError([&] { copyPermissionsFp = copyFilePermissions && //copy permissions only if asked for and supported by *both* sides! - !AFS::isNullPath(baseFolder.getAbstractPath< LEFT_SIDE>()) && //scenario: directory selected on one side only - !AFS::isNullPath(baseFolder.getAbstractPath()) && // - AFS::supportPermissionCopy(baseFolder.getAbstractPath(), - baseFolder.getAbstractPath()); //throw FileError + !AFS::isNullPath(baseFolder.getAbstractPath< SelectSide::left>()) && //scenario: directory selected on one side only + !AFS::isNullPath(baseFolder.getAbstractPath()) && // + AFS::supportPermissionCopy(baseFolder.getAbstractPath(), + baseFolder.getAbstractPath()); //throw FileError }, callback); //throw X @@ -2664,14 +2670,14 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime }; const AbstractPath versioningFolderPath = createAbstractPath(folderPairCfg.versioningFolderPhrase); - DeletionHandler delHandlerL(baseFolder.getAbstractPath(), - getEffectiveDeletionPolicy(baseFolder.getAbstractPath()), + DeletionHandler delHandlerL(baseFolder.getAbstractPath(), + getEffectiveDeletionPolicy(baseFolder.getAbstractPath()), versioningFolderPath, folderPairCfg.versioningStyle, std::chrono::system_clock::to_time_t(syncStartTime)); - DeletionHandler delHandlerR(baseFolder.getAbstractPath(), - getEffectiveDeletionPolicy(baseFolder.getAbstractPath()), + DeletionHandler delHandlerR(baseFolder.getAbstractPath(), + getEffectiveDeletionPolicy(baseFolder.getAbstractPath()), versioningFolderPath, folderPairCfg.versioningStyle, std::chrono::system_clock::to_time_t(syncStartTime)); diff --git a/FreeFileSync/Source/base/synchronization.h b/FreeFileSync/Source/base/synchronization.h index cb66a4ad..592760ce 100644 --- a/FreeFileSync/Source/base/synchronization.h +++ b/FreeFileSync/Source/base/synchronization.h @@ -23,19 +23,19 @@ public: SyncStatistics(const ContainerObject& hierObj); SyncStatistics(const FilePair& file); - template + template int createCount() const { return SelectParam::ref(createLeft_, createRight_); } int createCount() const { return createLeft_ + createRight_; } - template + template int updateCount() const { return SelectParam::ref(updateLeft_, updateRight_); } int updateCount() const { return updateLeft_ + updateRight_; } - template + template int deleteCount() const { return SelectParam::ref(deleteLeft_, deleteRight_); } int deleteCount() const { return deleteLeft_ + deleteRight_; } - template + template bool expectPhysicalDeletion() const { return SelectParam::ref(physicalDeleteLeft_, physicalDeleteRight_); } int64_t getBytesToProcess() const { return bytesToProcess_; } diff --git a/FreeFileSync/Source/base/versioning.cpp b/FreeFileSync/Source/base/versioning.cpp index ffd98261..adb8f106 100644 --- a/FreeFileSync/Source/base/versioning.cpp +++ b/FreeFileSync/Source/base/versioning.cpp @@ -55,7 +55,7 @@ std::pair fff::impl::parseVersionedFileName(const Zstring& file if (fileNameOrig.empty()) return {}; - return { t, std::move(fileNameOrig) }; + return {t, std::move(fileNameOrig)}; } @@ -179,7 +179,7 @@ void moveExistingItemToVersioning(const AbstractPath& sourcePath, const Abstract } -void FileVersioner::revisionFile(const FileDescriptor& fileDescr, const Zstring& relativePath, const IOCallback& notifyUnbufferedIO /*throw X*/) const //throw FileError, X +void FileVersioner::revisionFile(const FileDescriptor& fileDescr, const Zstring& relativePath, const IoCallback& notifyUnbufferedIO /*throw X*/) const //throw FileError, X { if (std::optional type = AFS::itemStillExists(fileDescr.path)) //throw FileError { @@ -194,12 +194,12 @@ void FileVersioner::revisionFile(const FileDescriptor& fileDescr, const Zstring& void FileVersioner::revisionFileImpl(const FileDescriptor& fileDescr, const Zstring& relativePath, //throw FileError, X const std::function& onBeforeMove, - const IOCallback& notifyUnbufferedIO /*throw X*/) const + const IoCallback& notifyUnbufferedIO /*throw X*/) const { const AbstractPath& filePath = fileDescr.path; const AbstractPath targetPath = generateVersionedPath(relativePath); - const AFS::StreamAttributes fileAttr{ fileDescr.attr.modTime, fileDescr.attr.fileSize, fileDescr.attr.fileId }; + const AFS::StreamAttributes fileAttr{fileDescr.attr.modTime, fileDescr.attr.fileSize, fileDescr.attr.filePrint}; if (onBeforeMove) onBeforeMove(AFS::getDisplayPath(filePath), AFS::getDisplayPath(targetPath)); @@ -242,7 +242,7 @@ void FileVersioner::revisionSymlinkImpl(const AbstractPath& linkPath, const Zstr void FileVersioner::revisionFolder(const AbstractPath& folderPath, const Zstring& relativePath, //throw FileError, X const std::function& onBeforeFileMove /*throw X*/, const std::function& onBeforeFolderMove /*throw X*/, - const IOCallback& notifyUnbufferedIO /*throw X*/) const + const IoCallback& notifyUnbufferedIO /*throw X*/) const { //no error situation if directory is not existing! manual deletion relies on it! if (std::optional type = AFS::itemStillExists(folderPath)) //throw FileError @@ -260,7 +260,7 @@ void FileVersioner::revisionFolder(const AbstractPath& folderPath, const Zstring void FileVersioner::revisionFolderImpl(const AbstractPath& folderPath, const Zstring& relativePath, //throw FileError, X const std::function& onBeforeFileMove, const std::function& onBeforeFolderMove, - const IOCallback& notifyUnbufferedIO /*throw X*/) const + const IoCallback& notifyUnbufferedIO /*throw X*/) const { //create target directories only when needed in moveFileToVersioning(): avoid empty directories! @@ -277,8 +277,8 @@ void FileVersioner::revisionFolderImpl(const AbstractPath& folderPath, const Zst for (const AFS::FileInfo& fileInfo : files) { - const FileDescriptor fileDescr{ AFS::appendRelPath(folderPath, fileInfo.itemName), - FileAttributes(fileInfo.modTime, fileInfo.fileSize, fileInfo.fileId, false /*isSymlink*/)}; + const FileDescriptor fileDescr{AFS::appendRelPath(folderPath, fileInfo.itemName), + FileAttributes(fileInfo.modTime, fileInfo.fileSize, fileInfo.filePrint, false /*isFollowedSymlink*/)}; revisionFileImpl(fileDescr, relPathPf + fileInfo.itemName, onBeforeFileMove, notifyUnbufferedIO); //throw FileError, X } @@ -325,7 +325,7 @@ void findFileVersions(VersionInfoMap& versions, const Zstring& relPathOrig = nativeAppendPaths(relPathOrigParent, fileNameOrig); const AbstractPath& filePath = AFS::appendRelPath(parentFolderPath, fileName); - versions[relPathOrig].push_back(VersionInfo{ versionTime, filePath, isSymlink }); + versions[relPathOrig].push_back(VersionInfo{versionTime, filePath, isSymlink}); }; auto extractFileVersion = [&](const Zstring& fileName, bool isSymlink) @@ -423,7 +423,7 @@ void fff::applyVersioningLimit(const std::set& folderLimi false /*allowUserInteraction*/, callback); //throw X foldersToRead.clear(); for (const AbstractPath& folderPath : status.existing) - foldersToRead.insert(DirectoryKey({ folderPath, makeSharedRef(), SymLinkHandling::direct })); + foldersToRead.insert(DirectoryKey({folderPath, makeSharedRef(), SymLinkHandling::direct})); if (!status.failedChecks.empty()) { diff --git a/FreeFileSync/Source/base/versioning.h b/FreeFileSync/Source/base/versioning.h index 7e707300..6c5d667b 100644 --- a/FreeFileSync/Source/base/versioning.h +++ b/FreeFileSync/Source/base/versioning.h @@ -53,7 +53,7 @@ public: void revisionFile(const FileDescriptor& fileDescr, //throw FileError, X const Zstring& relativePath, //called frequently if move has to revert to copy + delete => see zen::copyFile for limitations when throwing exceptions! - const zen::IOCallback& notifyUnbufferedIO /*throw X*/) const; + const zen::IoCallback& notifyUnbufferedIO /*throw X*/) const; void revisionSymlink(const AbstractPath& linkPath, const Zstring& relativePath) const; //throw FileError @@ -61,7 +61,7 @@ public: const std::function& onBeforeFileMove, /*throw X*/ const std::function& onBeforeFolderMove, /*throw X*/ //called frequently if move has to revert to copy + delete => see zen::copyFile for limitations when throwing exceptions! - const zen::IOCallback& notifyUnbufferedIO /*throw X*/) const; + const zen::IoCallback& notifyUnbufferedIO /*throw X*/) const; private: FileVersioner (const FileVersioner&) = delete; @@ -69,7 +69,7 @@ private: void revisionFileImpl(const FileDescriptor& fileDescr, const Zstring& relativePath, //throw FileError, X const std::function& onBeforeMove, - const zen::IOCallback& notifyUnbufferedIO) const; + const zen::IoCallback& notifyUnbufferedIO) const; void revisionSymlinkImpl(const AbstractPath& linkPath, const Zstring& relativePath, //throw FileError const std::function& onBeforeMove) const; @@ -77,7 +77,7 @@ private: void revisionFolderImpl(const AbstractPath& folderPath, const Zstring& relativePath, const std::function& onBeforeFileMove, const std::function& onBeforeFolderMove, - const zen::IOCallback& notifyUnbufferedIO) const; //throw FileError, X + const zen::IoCallback& notifyUnbufferedIO) const; //throw FileError, X AbstractPath generateVersionedPath(const Zstring& relativePath) const; diff --git a/FreeFileSync/Source/base_tools.cpp b/FreeFileSync/Source/base_tools.cpp index 8860ecef..73246dde 100644 --- a/FreeFileSync/Source/base_tools.cpp +++ b/FreeFileSync/Source/base_tools.cpp @@ -29,7 +29,7 @@ std::vector fff::fromTimeShiftPhrase(const std::wstring& timeShift } minutes.erase(0); - return { minutes.begin(), minutes.end() }; + return {minutes.begin(), minutes.end()}; } @@ -76,7 +76,7 @@ void fff::logNonDefaultSettings(const XmlGlobalSettings& activeSettings, PhaseCa changedSettingsMsg += L"\n " + _("Verify copied files") + L" - " + (activeSettings.verifyFileCopy ? _("Enabled") : _("Disabled")); if (!changedSettingsMsg.empty()) - callback.reportInfo(_("Using non-default global settings:") + changedSettingsMsg); //throw X + callback.logInfo(_("Using non-default global settings:") + changedSettingsMsg); //throw X } diff --git a/FreeFileSync/Source/config.cpp b/FreeFileSync/Source/config.cpp index 06f4abb6..f4d2850b 100644 --- a/FreeFileSync/Source/config.cpp +++ b/FreeFileSync/Source/config.cpp @@ -13,6 +13,7 @@ #include #include "ffs_paths.h" #include "base_tools.h" +#include "afs/native.h" using namespace zen; @@ -1099,8 +1100,9 @@ void writeStruc(const ConfigFileItem& value, XmlElement& output) out.attribute("Config", substituteFreeFileSyncDriveLetter(value.cfgFilePath)); out.attribute("LastSync", value.lastSyncTime); - if (std::optional nativePath = AFS::getNativeItemPath(value.logFilePath)) - out.attribute("Log", substituteFreeFileSyncDriveLetter(*nativePath)); + if (const Zstring& nativePath = getNativeItemPath(value.logFilePath); + !nativePath.empty()) + out.attribute("Log", substituteFreeFileSyncDriveLetter(nativePath)); else out.attribute("Log", AFS::getInitPathPhrase(value.logFilePath)); @@ -1109,7 +1111,7 @@ void writeStruc(const ConfigFileItem& value, XmlElement& output) const auto& [highR, lowR] = hexify(value.backColor.Red ()); const auto& [highG, lowG] = hexify(value.backColor.Green()); const auto& [highB, lowB] = hexify(value.backColor.Blue ()); - out.attribute("Color", std::string({ highR, lowR, highG, lowG, highB, lowB })); + out.attribute("Color", std::string({highR, lowR, highG, lowG, highB, lowB})); } } diff --git a/FreeFileSync/Source/config.h b/FreeFileSync/Source/config.h index b5995318..dc2c6bd6 100644 --- a/FreeFileSync/Source/config.h +++ b/FreeFileSync/Source/config.h @@ -8,6 +8,7 @@ #define PROCESS_XML_H_28345825704254262435 #include +#include #include "localization.h" #include "base/structures.h" #include "ui/file_grid_attr.h" @@ -130,7 +131,7 @@ struct XmlGlobalSettings bool copyLockedFiles = false; //safer default: avoid copies of partially written files bool copyFilePermissions = false; - int fileTimeTolerance = 2; //max. allowed file time deviation; < 0 means unlimited tolerance; default 2s: FAT vs NTFS + int fileTimeTolerance = zen::FAT_FILE_TIME_PRECISION_SEC; //max. allowed file time deviation; < 0 means unlimited tolerance; default 2s: FAT vs NTFS bool runWithBackgroundPriority = false; bool createLockFile = true; bool verifyFileCopy = false; @@ -227,8 +228,8 @@ struct XmlGlobalSettings /* CONTRACT: first entry: show item in file browser default external app descriptions will be translated "on the fly"!!! */ //"xdg-open \"%parent_path%\"" -> not good enough: we need %local_path% for proper MTP/Google Drive handling - { L"Browse directory", "xdg-open \"$(dirname \"%local_path%\")\"" }, - { L"Open with default application", "xdg-open \"%local_path%\"" }, + {L"Browse directory", "xdg-open \"$(dirname \"%local_path%\")\""}, + {L"Open with default application", "xdg-open \"%local_path%\"" }, //mark for extraction: _("Browse directory") Linux doesn't use the term "folder" }; diff --git a/FreeFileSync/Source/fatal_error.h b/FreeFileSync/Source/fatal_error.h index eb025472..4749ac75 100644 --- a/FreeFileSync/Source/fatal_error.h +++ b/FreeFileSync/Source/fatal_error.h @@ -16,7 +16,7 @@ namespace fff { //write error message to a file (even with corrupted stack)- call in desperate situations when no other means of error handling is available -void logFatalError(const std::string& msg); //noexcept +void logFatalError(const std::wstring& msg); //noexcept @@ -28,12 +28,12 @@ void logFatalError(const std::string& msg); //noexcept //##################### implementation ############################ inline -void logFatalError(const std::string& msg) //noexcept +void logFatalError(const std::wstring& msg) //noexcept { using namespace zen; assert(false); //this is stuff we like to debug - const std::string logEntry = '[' + utfTo(formatTime(formatDateTimeTag)) + "] " + msg; + const std::string logEntry = '[' + utfTo(formatTime(formatDateTimeTag)) + "] " + utfTo(msg); try { setFileContent(getConfigDirPathPf() + Zstr("LastError.log"), logEntry, nullptr /*notifyUnbufferedIO*/); //throw FileError diff --git a/FreeFileSync/Source/ffs_paths.cpp b/FreeFileSync/Source/ffs_paths.cpp index 6a81257e..44b80f6f 100644 --- a/FreeFileSync/Source/ffs_paths.cpp +++ b/FreeFileSync/Source/ffs_paths.cpp @@ -8,8 +8,6 @@ #include #include #include -#include -#include #include //std::cerr @@ -48,21 +46,6 @@ Zstring fff::getInstallDirPath() } -//getFfsVolumeId() might be called during static destruction, e.g. async update check -VolumeId fff::getFfsVolumeId() //throw FileError -{ - static VolumeId volumeId; //POD => no "magic static" code gen - static constinit2 std::once_flag onceFlagGetFfsVolumeId; //=> no "magic static" code gen - std::call_once(onceFlagGetFfsVolumeId, [] { volumeId = getVolumeId(getRealProcessPath()); }); //throw FileError - return volumeId; -} - - -bool fff::isPortableVersion() -{ - return false; //users want local installation type: https://freefilesync.org/forum/viewtopic.php?t=5750 - -} Zstring fff::getResourceDirPf() @@ -73,34 +56,37 @@ Zstring fff::getResourceDirPf() Zstring fff::getConfigDirPathPf() { - warn_static("Linux/macOS TODO: consider getuid() == 0 as request for elevation, NOT impersonation") - //note: compiler generates magic-statics code => fine, we don't expect accesses during shutdown - static const Zstring cfgFolderPathPf = [] + static const Zstring ffsConfigPathPf = [] { - //make independent from wxWidgets global variable "appname"; support being called by RealTimeSync - auto appName = wxTheApp->GetAppName(); - wxTheApp->SetAppName(L"FreeFileSync"); - ZEN_ON_SCOPE_EXIT(wxTheApp->SetAppName(appName)); - - //OS standard path (XDG layout): ~/.config/FreeFileSync - //wxBug: wxStandardPaths::GetUserDataDir() does not honor FileLayout_XDG flag - wxStandardPaths::Get().SetFileLayout(wxStandardPaths::FileLayout_XDG); - const Zstring cfgFolderPath = appendSeparator(utfTo(wxStandardPaths::Get().GetUserConfigDir())) + "FreeFileSync"; + /* Windows: %AppData%\FreeFileSync + macOS: ~/Library/Application Support/FreeFileSync + Linux (XDG layout): ~/.config/FreeFileSync */ + const Zstring& ffsConfigPath = [] + { + try + { + return + appendSeparator(getUserDataPath()) + Zstr("FreeFileSync"); //throw FileError + } + catch (const FileError& e) + { + throw std::runtime_error(std::string(__FILE__) + '[' + numberTo(__LINE__) + "] " + utfTo(e.toString())); + } + }(); try //create the config folder if not existing + create "Logs" subfolder while we're at it { - createDirectoryIfMissingRecursion(appendSeparator(cfgFolderPath) + Zstr("Logs")); //throw FileError + createDirectoryIfMissingRecursion(appendSeparator(ffsConfigPath) + Zstr("Logs")); //throw FileError } catch (const FileError& e) { assert(false); std::cerr << utfTo(e.toString()) << '\n'; } - - return appendSeparator(cfgFolderPath); + return appendSeparator(ffsConfigPath); }(); - return cfgFolderPathPf; + return ffsConfigPathPf; } diff --git a/FreeFileSync/Source/ffs_paths.h b/FreeFileSync/Source/ffs_paths.h index cbf4edb4..a2310a14 100644 --- a/FreeFileSync/Source/ffs_paths.h +++ b/FreeFileSync/Source/ffs_paths.h @@ -8,7 +8,6 @@ #define FFS_PATHS_H_842759083425342534253 #include -#include namespace fff @@ -20,11 +19,8 @@ Zstring getResourceDirPf (); //resource directory WITH trailing path separator Zstring getConfigDirPathPf(); // config directory WITH trailing path separator //------------------------------------------------------------------------------ -bool isPortableVersion(); - Zstring getInstallDirPath(); - -zen::VolumeId getFfsVolumeId(); //throw FileError +Zstring getInstallDirPath(); Zstring getFreeFileSyncLauncherPath(); //full path to application launcher C:\...\FreeFileSync.exe } diff --git a/FreeFileSync/Source/icon_buffer.cpp b/FreeFileSync/Source/icon_buffer.cpp index bdd5eab2..30ad3173 100644 --- a/FreeFileSync/Source/icon_buffer.cpp +++ b/FreeFileSync/Source/icon_buffer.cpp @@ -76,7 +76,7 @@ public: std::lock_guard dummy(lockFiles_); workLoad_ = newLoad; } - conditionNewWork_.notify_all(); //instead of notify_one(); workaround bug: https://svn.boost.org/trac/boost/ticket/7796 + conditionNewWork_.notify_all(); //instead of notify_one(); work around bug: https://svn.boost.org/trac/boost/ticket/7796 //condition handling, see: https://www.boost.org/doc/libs/1_43_0/doc/html/thread/synchronization.html#thread.synchronization.condvar_ref } diff --git a/FreeFileSync/Source/localization.cpp b/FreeFileSync/Source/localization.cpp index 40c7b890..b04bc3b5 100644 --- a/FreeFileSync/Source/localization.cpp +++ b/FreeFileSync/Source/localization.cpp @@ -49,7 +49,7 @@ public: std::wstring translate(const std::wstring& singular, const std::wstring& plural, int64_t n) const override { - auto it = transMappingPl_.find({ singular, plural }); + auto it = transMappingPl_.find({singular, plural}); if (it != transMappingPl_.end()) { const size_t formNo = pluralParser_->getForm(n); @@ -89,13 +89,11 @@ FFSTranslation::FFSTranslation(const std::string& lngStream) //throw lng::Parsin for (const std::string& pf : pluralForms) transPluralForms.push_back(utfTo(pf)); - transMappingPl_.insert( - { - { + transMappingPl_.insert({{ utfTo(singAndPlural.first), utfTo(singAndPlural.second) }, - std::move(transPluralForms) }); + std::move(transPluralForms)}); } } diff --git a/FreeFileSync/Source/log_file.cpp b/FreeFileSync/Source/log_file.cpp index 9ab69203..90eddeb9 100644 --- a/FreeFileSync/Source/log_file.cpp +++ b/FreeFileSync/Source/log_file.cpp @@ -399,7 +399,7 @@ void saveNewLogFile(const AbstractPath& logFilePath, //throw FileError, X LogFileFormat logFormat, const ProcessSummary& summary, const ErrorLog& log, - const std::function& notifyStatus /*throw X*/) + const std::function& notifyStatus /*throw X*/) { //create logfile folder if required if (const std::optional parentPath = AFS::getParentPath(logFilePath)) @@ -479,7 +479,7 @@ std::vector getLogFiles(const AbstractPath& logFolderPath) //throw jobNames.pop_back(); } - logfiles.push_back({ AFS::appendRelPath(logFolderPath, fi.itemName), t, utfTo(jobNames) }); + logfiles.push_back({AFS::appendRelPath(logFolderPath, fi.itemName), t, utfTo(jobNames)}); } } } @@ -494,7 +494,7 @@ std::vector getLogFiles(const AbstractPath& logFolderPath) //throw void limitLogfileCount(const AbstractPath& logFolderPath, //throw FileError, X int logfilesMaxAgeDays, //<= 0 := no limit const std::set& logFilePathsToKeep, - const std::function& notifyStatus /*throw X*/) + const std::function& notifyStatus /*throw X*/) { if (logfilesMaxAgeDays > 0) { @@ -613,7 +613,7 @@ void fff::saveLogFile(const AbstractPath& logFilePath, //throw FileError, X int logfilesMaxAgeDays, LogFileFormat logFormat, const std::set& logFilePathsToKeep, - const std::function& notifyStatus /*throw X*/) + const std::function& notifyStatus /*throw X*/) { std::exception_ptr firstError; try @@ -642,7 +642,7 @@ void fff::sendLogAsEmail(const std::string& email, //throw FileError, X const ProcessSummary& summary, const ErrorLog& log, const AbstractPath& logFilePath, - const std::function& notifyStatus /*throw X*/) + const std::function& notifyStatus /*throw X*/) { try { diff --git a/FreeFileSync/Source/log_file.h b/FreeFileSync/Source/log_file.h index 031be320..50dcd4c7 100644 --- a/FreeFileSync/Source/log_file.h +++ b/FreeFileSync/Source/log_file.h @@ -32,13 +32,13 @@ void saveLogFile(const AbstractPath& logFilePath, //throw FileError, X int logfilesMaxAgeDays, LogFileFormat logFormat, const std::set& logFilePathsToKeep, - const std::function& notifyStatus /*throw X*/); + const std::function& notifyStatus /*throw X*/); void sendLogAsEmail(const std::string& email, //throw FileError, X const ProcessSummary& summary, const zen::ErrorLog& log, const AbstractPath& logFilePath, - const std::function& notifyStatus /*throw X*/); + const std::function& notifyStatus /*throw X*/); } #endif //GENERATE_LOGFILE_H_931726432167489732164 diff --git a/FreeFileSync/Source/parse_lng.h b/FreeFileSync/Source/parse_lng.h index 7b8e09ec..4ab4e0f7 100644 --- a/FreeFileSync/Source/parse_lng.h +++ b/FreeFileSync/Source/parse_lng.h @@ -211,28 +211,28 @@ private: const TokenMap tokens_ = { //header information - { Token::TK_HEADER_BEGIN, "
" }, - { Token::TK_HEADER_END, "
" }, - { Token::TK_LANG_NAME_BEGIN, "" }, - { Token::TK_LANG_NAME_END, "" }, - { Token::TK_TRANS_NAME_BEGIN, "" }, - { Token::TK_TRANS_NAME_END, "" }, - { Token::TK_LOCALE_NAME_BEGIN, "" }, - { Token::TK_LOCALE_NAME_END, "" }, - { Token::TK_FLAG_FILE_BEGIN, "" }, - { Token::TK_FLAG_FILE_END, "" }, - { Token::TK_PLURAL_COUNT_BEGIN, "" }, - { Token::TK_PLURAL_COUNT_END, "" }, - { Token::TK_PLURAL_DEF_BEGIN, "" }, - { Token::TK_PLURAL_DEF_END, "" }, + {Token::TK_HEADER_BEGIN, "
"}, + {Token::TK_HEADER_END, "
"}, + {Token::TK_LANG_NAME_BEGIN, ""}, + {Token::TK_LANG_NAME_END, ""}, + {Token::TK_TRANS_NAME_BEGIN, ""}, + {Token::TK_TRANS_NAME_END, ""}, + {Token::TK_LOCALE_NAME_BEGIN, ""}, + {Token::TK_LOCALE_NAME_END, ""}, + {Token::TK_FLAG_FILE_BEGIN, ""}, + {Token::TK_FLAG_FILE_END, ""}, + {Token::TK_PLURAL_COUNT_BEGIN, ""}, + {Token::TK_PLURAL_COUNT_END, ""}, + {Token::TK_PLURAL_DEF_BEGIN, ""}, + {Token::TK_PLURAL_DEF_END, ""}, //item level - { Token::TK_SRC_BEGIN, "" }, - { Token::TK_SRC_END, "" }, - { Token::TK_TRG_BEGIN, "" }, - { Token::TK_TRG_END, "" }, - { Token::TK_PLURAL_BEGIN, "" }, - { Token::TK_PLURAL_END, "" }, + {Token::TK_SRC_BEGIN, ""}, + {Token::TK_SRC_END, ""}, + {Token::TK_TRG_BEGIN, ""}, + {Token::TK_TRG_END, ""}, + {Token::TK_PLURAL_BEGIN, ""}, + {Token::TK_PLURAL_END, ""}, }; }; @@ -349,7 +349,7 @@ public: } catch (const plural::InvalidPluralForm&) { - throw ParsingError({ L"Invalid plural form definition", scn_.posRow(), scn_.posCol() }); + throw ParsingError({L"Invalid plural form definition", scn_.posRow(), scn_.posCol()}); } } @@ -454,12 +454,12 @@ private: using namespace zen; if (original.empty()) - throw ParsingError({ L"Translation source text is empty", scn_.posRow(), scn_.posCol() }); + throw ParsingError({L"Translation source text is empty", scn_.posRow(), scn_.posCol()}); if (!isValidUtf(original)) - throw ParsingError({ L"Translation source text contains UTF-8 encoding error", scn_.posRow(), scn_.posCol() }); + throw ParsingError({L"Translation source text contains UTF-8 encoding error", scn_.posRow(), scn_.posCol()}); if (!isValidUtf(translation)) - throw ParsingError({ L"Translation text contains UTF-8 encoding error", scn_.posRow(), scn_.posCol() }); + throw ParsingError({L"Translation text contains UTF-8 encoding error", scn_.posRow(), scn_.posCol()}); if (!translation.empty()) { @@ -468,7 +468,7 @@ private: { if (contains(original, placeholder) && !contains(translation, placeholder)) - throw ParsingError({ replaceCpy(L"Placeholder %x missing in translation", L"%x", utfTo(placeholder)), scn_.posRow(), scn_.posCol() }); + throw ParsingError({replaceCpy(L"Placeholder %x missing in translation", L"%x", utfTo(placeholder)), scn_.posRow(), scn_.posCol()}); }; checkPlaceholder("%x"); checkPlaceholder("%y"); @@ -476,41 +476,41 @@ private: //if source is a one-liner, so should be the translation if (!contains(original, '\n') && contains(translation, '\n')) - throw ParsingError({ L"Source text is a one-liner, but translation consists of multiple lines", scn_.posRow(), scn_.posCol() }); + throw ParsingError({L"Source text is a one-liner, but translation consists of multiple lines", scn_.posRow(), scn_.posCol()}); //if source contains ampersand to mark menu accellerator key, so must translation const size_t ampCount = ampersandTokenCount(original); if (ampCount > 1 || ampCount != ampersandTokenCount(translation)) - throw ParsingError({ L"Source and translation both need exactly one & character to mark a menu item access key or none at all", scn_.posRow(), scn_.posCol() }); + throw ParsingError({L"Source and translation both need exactly one & character to mark a menu item access key or none at all", scn_.posRow(), scn_.posCol()}); //ampersand at the end makes buggy wxWidgets crash miserably if (endsWithSingleAmp(original) || endsWithSingleAmp(translation)) - throw ParsingError({ L"The & character to mark a menu item access key must not occur at the end of a string", scn_.posRow(), scn_.posCol() }); + throw ParsingError({L"The & character to mark a menu item access key must not occur at the end of a string", scn_.posRow(), scn_.posCol()}); //if source ends with colon, so must translation (note: character seems to be universally used, even for asian and arabic languages) if (endsWith(original, ':') && !endsWithColon(translation)) - throw ParsingError({ L"Source text ends with a colon character \":\", but translation does not", scn_.posRow(), scn_.posCol() }); + throw ParsingError({L"Source text ends with a colon character \":\", but translation does not", scn_.posRow(), scn_.posCol()}); //if source ends with a period, so must translation (note: character seems to be universally used, even for asian and arabic languages) if (endsWithSingleDot(original) && !endsWithSingleDot(translation)) - throw ParsingError({ L"Source text ends with a punctuation mark character \".\", but translation does not", scn_.posRow(), scn_.posCol() }); + throw ParsingError({L"Source text ends with a punctuation mark character \".\", but translation does not", scn_.posRow(), scn_.posCol()}); //if source ends with an ellipsis, so must translation (note: character seems to be universally used, even for asian and arabic languages) if (endsWithEllipsis(original) && !endsWithEllipsis(translation)) - throw ParsingError({ L"Source text ends with an ellipsis \"...\", but translation does not", scn_.posRow(), scn_.posCol() }); + throw ParsingError({L"Source text ends with an ellipsis \"...\", but translation does not", scn_.posRow(), scn_.posCol()}); //check for not-to-be-translated texts - for (const char* fixedStr : { "FreeFileSync", "RealTimeSync", "ffs_gui", "ffs_batch", "ffs_tmp", "GlobalSettings.xml" }) + for (const char* fixedStr : {"FreeFileSync", "RealTimeSync", "ffs_gui", "ffs_batch", "ffs_tmp", "GlobalSettings.xml"}) if (contains(original, fixedStr) && !contains(translation, fixedStr)) - throw ParsingError({ replaceCpy(L"Misspelled \"%x\" in translation", L"%x", utfTo(fixedStr)), scn_.posRow(), scn_.posCol() }); + throw ParsingError({replaceCpy(L"Misspelled \"%x\" in translation", L"%x", utfTo(fixedStr)), scn_.posRow(), scn_.posCol()}); //some languages (French!) put a space before punctuation mark => must be a no-brake space! for (const char punctChar : std::string(".!?:;$#")) if (contains(original, std::string(" ") + punctChar) || contains(translation, std::string(" ") + punctChar)) - throw ParsingError({ replaceCpy(L"Text contains a space before the \"%x\" character. Are line-breaks really allowed here?" - " Maybe this should be a \"non-breaking space\" (Windows: Alt 0160 UTF8: 0xC2 0xA0)?", - L"%x", utfTo(punctChar)), scn_.posRow(), scn_.posCol() }); + throw ParsingError({replaceCpy(L"Text contains a space before the \"%x\" character. Are line-breaks really allowed here?" + " Maybe this should be a \"non-breaking space\" (Windows: Alt 0160 UTF8: 0xC2 0xA0)?", + L"%x", utfTo(punctChar)), scn_.posRow(), scn_.posCol()}); } } @@ -519,30 +519,30 @@ private: using namespace zen; if (original.first.empty() || original.second.empty()) - throw ParsingError({ L"Translation source text is empty", scn_.posRow(), scn_.posCol() }); + throw ParsingError({L"Translation source text is empty", scn_.posRow(), scn_.posCol()}); const std::vector allTexts = [&] { - std::vector at{ original.first, original.second }; + std::vector at{original.first, original.second}; at.insert(at.end(), translation.begin(), translation.end()); return at; }(); for (const std::string& str : allTexts) if (!isValidUtf(str)) - throw ParsingError({ L"Text contains UTF-8 encoding error", scn_.posRow(), scn_.posCol() }); + throw ParsingError({L"Text contains UTF-8 encoding error", scn_.posRow(), scn_.posCol()}); //check the primary placeholder is existing at least for the second english text if (!contains(original.second, "%x")) - throw ParsingError({ L"Plural form source text does not contain %x placeholder", scn_.posRow(), scn_.posCol() }); + throw ParsingError({L"Plural form source text does not contain %x placeholder", scn_.posRow(), scn_.posCol()}); if (!translation.empty()) { //check for invalid number of plural forms if (pluralInfo.getCount() != translation.size()) - throw ParsingError({ replaceCpy(replaceCpy(L"Invalid number of plural forms; actual: %x, expected: %y", - L"%x", numberTo(translation.size())), - L"%y", numberTo(pluralInfo.getCount())), scn_.posRow(), scn_.posCol() }); + throw ParsingError({replaceCpy(replaceCpy(L"Invalid number of plural forms; actual: %x, expected: %y", + L"%x", numberTo(translation.size())), + L"%y", numberTo(pluralInfo.getCount())), scn_.posRow(), scn_.posCol()}); //check for duplicate plural form translations (catch copy & paste errors for single-number form translations) for (auto it = translation.begin(); it != translation.end(); ++it) @@ -550,8 +550,8 @@ private: { auto it2 = std::find(it + 1, translation.end(), *it); if (it2 != translation.end()) - throw ParsingError({ replaceCpy(L"Duplicate plural form translation at index position %x", - L"%x", numberTo(it2 - translation.begin())), scn_.posRow(), scn_.posCol() }); + throw ParsingError({replaceCpy(L"Duplicate plural form translation at index position %x", + L"%x", numberTo(it2 - translation.begin())), scn_.posRow(), scn_.posCol()}); } for (size_t pos = 0; pos < translation.size(); ++pos) @@ -564,15 +564,15 @@ private: const int firstNumber = pluralInfo.getFirstNumber(pos); if (!contains(translation[pos], "%x") && !contains(translation[pos], numberTo(firstNumber))) - throw ParsingError({ replaceCpy(replaceCpy(L"Plural form translation at index position %y needs to use the decimal number %z or the %x placeholder", - L"%y", numberTo(pos)), L"%z", numberTo(firstNumber)), scn_.posRow(), scn_.posCol() }); + throw ParsingError({replaceCpy(replaceCpy(L"Plural form translation at index position %y needs to use the decimal number %z or the %x placeholder", + L"%y", numberTo(pos)), L"%z", numberTo(firstNumber)), scn_.posRow(), scn_.posCol()}); } } else { //ensure the placeholder is used when needed if (!contains(translation[pos], "%x")) - throw ParsingError({ replaceCpy(L"Plural form at index position %y is missing the %x placeholder", L"%y", numberTo(pos)), scn_.posRow(), scn_.posCol() }); + throw ParsingError({replaceCpy(L"Plural form at index position %y is missing the %x placeholder", L"%y", numberTo(pos)), scn_.posRow(), scn_.posCol()}); } auto checkSecondaryPlaceholder = [&](const std::string& placeholder) @@ -582,7 +582,7 @@ private: contains(original.second, placeholder)) for (const std::string& str : allTexts) if (!contains(str, placeholder)) - throw ParsingError({ zen::replaceCpy(L"Placeholder %x missing in text", L"%x", zen::utfTo(placeholder)), scn_.posRow(), scn_.posCol() }); + throw ParsingError({zen::replaceCpy(L"Placeholder %x missing in text", L"%x", zen::utfTo(placeholder)), scn_.posRow(), scn_.posCol()}); }; checkSecondaryPlaceholder("%y"); checkSecondaryPlaceholder("%z"); @@ -590,51 +590,51 @@ private: //if source is a one-liner, so should be the translation if (!contains(original.first, '\n') && !contains(original.second, '\n') && /**/std::any_of(translation.begin(), translation.end(), [](const std::string& pform) { return contains(pform, '\n'); })) - /**/throw ParsingError({ L"Source text is a one-liner, but at least one plural form translation consists of multiple lines", scn_.posRow(), scn_.posCol() }); + /**/throw ParsingError({L"Source text is a one-liner, but at least one plural form translation consists of multiple lines", scn_.posRow(), scn_.posCol()}); //if source contains ampersand to mark menu accellerator key, so must translation const size_t ampCount = ampersandTokenCount(original.first); for (const std::string& str : allTexts) if (ampCount > 1 || ampersandTokenCount(str) != ampCount) - throw ParsingError({ L"Source and translation both need exactly one & character to mark a menu item access key or none at all", scn_.posRow(), scn_.posCol() }); + throw ParsingError({L"Source and translation both need exactly one & character to mark a menu item access key or none at all", scn_.posRow(), scn_.posCol()}); //ampersand at the end makes buggy wxWidgets crash miserably for (const std::string& str : allTexts) if (endsWithSingleAmp(str)) - throw ParsingError({ L"The & character to mark a menu item access key must not occur at the end of a string", scn_.posRow(), scn_.posCol() }); + throw ParsingError({L"The & character to mark a menu item access key must not occur at the end of a string", scn_.posRow(), scn_.posCol()}); //if source ends with colon, so must translation (note: character seems to be universally used, even for asian and arabic languages) if (endsWith(original.first, ':') || endsWith(original.second, ':')) for (const std::string& str : allTexts) if (!endsWithColon(str)) - throw ParsingError({ L"Source text ends with a colon character \":\", but translation does not", scn_.posRow(), scn_.posCol() }); + throw ParsingError({L"Source text ends with a colon character \":\", but translation does not", scn_.posRow(), scn_.posCol()}); //if source ends with a period, so must translation (note: character seems to be universally used, even for asian and arabic languages) if (endsWithSingleDot(original.first) || endsWithSingleDot(original.second)) for (const std::string& str : allTexts) if (!endsWithSingleDot(str)) - throw ParsingError({ L"Source text ends with a punctuation mark character \".\", but translation does not", scn_.posRow(), scn_.posCol() }); + throw ParsingError({L"Source text ends with a punctuation mark character \".\", but translation does not", scn_.posRow(), scn_.posCol()}); //if source ends with an ellipsis, so must translation (note: character seems to be universally used, even for asian and arabic languages) if (endsWithEllipsis(original.first) || endsWithEllipsis(original.second)) for (const std::string& str : allTexts) if (!endsWithEllipsis(str)) - throw ParsingError({ L"Source text ends with an ellipsis \"...\", but translation does not", scn_.posRow(), scn_.posCol() }); + throw ParsingError({L"Source text ends with an ellipsis \"...\", but translation does not", scn_.posRow(), scn_.posCol()}); //check for not-to-be-translated texts - for (const char* fixedStr : { "FreeFileSync", "RealTimeSync", "ffs_gui", "ffs_batch", "ffs_tmp", "GlobalSettings.xml" }) + for (const char* fixedStr : {"FreeFileSync", "RealTimeSync", "ffs_gui", "ffs_batch", "ffs_tmp", "GlobalSettings.xml"}) if (contains(original.first, fixedStr) || contains(original.second, fixedStr)) for (const std::string& str : allTexts) if (!contains(str, fixedStr)) - throw ParsingError({ replaceCpy(L"Misspelled \"%x\" in translation", L"%x", utfTo(fixedStr)), scn_.posRow(), scn_.posCol() }); + throw ParsingError({replaceCpy(L"Misspelled \"%x\" in translation", L"%x", utfTo(fixedStr)), scn_.posRow(), scn_.posCol()}); //some languages (French!) put a space before punctuation mark => must be a no-brake space! for (const char punctChar : std::string(".!?:;$#")) for (const std::string& str : allTexts) if (contains(str, std::string(" ") + punctChar)) - throw ParsingError({ replaceCpy(L"Text contains a space before the \"%x\" character. Are line-breaks really allowed here?" - " Maybe this should be a \"non-breaking space\" (Windows: Alt 0160 UTF8: 0xC2 0xA0)?", - L"%x", utfTo(punctChar)), scn_.posRow(), scn_.posCol() }); + throw ParsingError({replaceCpy(L"Text contains a space before the \"%x\" character. Are line-breaks really allowed here?" + " Maybe this should be a \"non-breaking space\" (Windows: Alt 0160 UTF8: 0xC2 0xA0)?", + L"%x", utfTo(punctChar)), scn_.posRow(), scn_.posCol()}); } } @@ -686,7 +686,7 @@ private: void expectToken(Token::Type t) //throw ParsingError { if (token().type != t) - throw ParsingError({ L"Unexpected token", scn_.posRow(), scn_.posCol() }); + throw ParsingError({L"Unexpected token", scn_.posRow(), scn_.posCol()}); } void consumeToken(Token::Type t) //throw ParsingError diff --git a/FreeFileSync/Source/parse_plural.h b/FreeFileSync/Source/parse_plural.h index bbf635f1..67f70b27 100644 --- a/FreeFileSync/Source/parse_plural.h +++ b/FreeFileSync/Source/parse_plural.h @@ -241,21 +241,21 @@ private: using TokenList = std::vector>; const TokenList tokens_ { - { "?", Token::TK_TERNARY_QUEST }, - { ":", Token::TK_TERNARY_COLON }, - { "||", Token::TK_OR }, - { "&&", Token::TK_AND }, - { "==", Token::TK_EQUAL }, - { "!=", Token::TK_NOT_EQUAL }, - { "<=", Token::TK_LESS_EQUAL }, - { "<", Token::TK_LESS }, - { ">=", Token::TK_GREATER_EQUAL }, - { ">", Token::TK_GREATER }, - { "%", Token::TK_MODULUS }, - { "n", Token::TK_VARIABLE_N }, - { "N", Token::TK_VARIABLE_N }, - { "(", Token::TK_BRACKET_LEFT }, - { ")", Token::TK_BRACKET_RIGHT }, + {"?", Token::TK_TERNARY_QUEST}, + {":", Token::TK_TERNARY_COLON}, + {"||", Token::TK_OR }, + {"&&", Token::TK_AND }, + {"==", Token::TK_EQUAL }, + {"!=", Token::TK_NOT_EQUAL }, + {"<=", Token::TK_LESS_EQUAL }, + {"<", Token::TK_LESS }, + {">=", Token::TK_GREATER_EQUAL}, + {">", Token::TK_GREATER }, + {"%", Token::TK_MODULUS }, + {"n", Token::TK_VARIABLE_N }, + {"N", Token::TK_VARIABLE_N }, + {"(", Token::TK_BRACKET_LEFT }, + {")", Token::TK_BRACKET_RIGHT}, }; const std::string stream_; diff --git a/FreeFileSync/Source/perf_check.cpp b/FreeFileSync/Source/perf_check.cpp index 9905cc7d..7a0f43ab 100644 --- a/FreeFileSync/Source/perf_check.cpp +++ b/FreeFileSync/Source/perf_check.cpp @@ -22,7 +22,7 @@ PerfCheck::PerfCheck(std::chrono::milliseconds windowSizeRemTime, void PerfCheck::addSample(std::chrono::nanoseconds timeElapsed, int itemsCurrent, int64_t bytesCurrent) { - samples_.insert(samples_.end(), { timeElapsed, { itemsCurrent, bytesCurrent }}); //use fact that time is monotonously ascending + samples_.insert(samples_.end(), {timeElapsed, { itemsCurrent, bytesCurrent}}); //use fact that time is monotonously ascending //remove all records earlier than "now - windowMax" auto it = samples_.upper_bound(timeElapsed - windowMax_); @@ -45,7 +45,7 @@ std::tuple Per const int itemsDelta = itBack->second.items - itFront->second.items; const int64_t bytesDelta = itBack->second.bytes - itFront->second.bytes; - return { timeDelta, itemsDelta, bytesDelta }; + return {timeDelta, itemsDelta, bytesDelta}; } diff --git a/FreeFileSync/Source/status_handler.cpp b/FreeFileSync/Source/status_handler.cpp index b86c104d..38c488f8 100644 --- a/FreeFileSync/Source/status_handler.cpp +++ b/FreeFileSync/Source/status_handler.cpp @@ -5,8 +5,10 @@ // ***************************************************************************** #include "status_handler.h" -#include -//#include +#include +#include + +using namespace zen; namespace @@ -26,3 +28,42 @@ bool fff::uiUpdateDue(bool force) } return false; } + + +void fff::runCommandAndLogErrors(const Zstring& cmdLine, ErrorLog& errorLog) +{ + try + { + //give consoleExecute() some "time to fail", but not too long to hang our process + const int DEFAULT_APP_TIMEOUT_MS = 100; + + if (const auto& [exitCode, output] = consoleExecute(cmdLine, DEFAULT_APP_TIMEOUT_MS); //throw SysError, SysErrorTimeOut + exitCode != 0) + throw SysError(formatSystemError("", replaceCpy(_("Exit code %x"), L"%x", numberTo(exitCode)), utfTo(output))); + + errorLog.logMsg(_("Executing command:") + L' ' + utfTo(cmdLine) + L" [" + replaceCpy(_("Exit code %x"), L"%x", L"0") + L']', MSG_TYPE_INFO); + } + catch (SysErrorTimeOut&) //child process not failed yet => probably fine :> + { + errorLog.logMsg(_("Executing command:") + L' ' + utfTo(cmdLine), MSG_TYPE_INFO); + } + catch (const SysError& e) + { + errorLog.logMsg(replaceCpy(_("Command %x failed."), L"%x", fmtPath(cmdLine)) + L"\n\n" + e.toString(), MSG_TYPE_ERROR); + } +} + + +void fff::delayAndCountDown(std::chrono::steady_clock::time_point delayUntil, const std::function& notifyStatus) +{ + for (auto now = std::chrono::steady_clock::now(); now < delayUntil; now = std::chrono::steady_clock::now()) + { + if (notifyStatus) + { + const auto timeRemMs = std::chrono::duration_cast(delayUntil - now).count(); + notifyStatus(_P("1 sec", "%x sec", numeric::intDivCeil(timeRemMs, 1000))); + } + + std::this_thread::sleep_for(UI_UPDATE_INTERVAL / 2); + } +} diff --git a/FreeFileSync/Source/status_handler.h b/FreeFileSync/Source/status_handler.h index faac4e99..36a74a2e 100644 --- a/FreeFileSync/Source/status_handler.h +++ b/FreeFileSync/Source/status_handler.h @@ -7,21 +7,19 @@ #ifndef STATUS_HANDLER_H_81704805908341534 #define STATUS_HANDLER_H_81704805908341534 -#include +#include +#include +#include #include "base/process_callback.h" #include "return_codes.h" - namespace fff { bool uiUpdateDue(bool force = false); //test if a specific amount of time is over -/* -Updating GUI is fast! - time per single call to ProcessCallback::forceUiRefresh() +/* Updating GUI is fast! time per call to ProcessCallback::forceUiRefresh() - Comparison 0.025 ms - - Synchronization 0.74 ms (despite complex graph control!) -*/ + - Synchronization 0.74 ms (despite complex graph control!) */ //Exception class used to abort the "compare" and "sync" process class AbortProcess {}; @@ -88,7 +86,7 @@ public: assert((itemsTotal < 0) == (bytesTotal < 0)); currentPhase_ = phase; statsCurrent_ = {}; - statsTotal_ = { itemsTotal, bytesTotal }; + statsTotal_ = {itemsTotal, bytesTotal}; } void updateDataProcessed(int itemsDelta, int64_t bytesDelta) override { updateData(statsCurrent_, itemsDelta, bytesDelta); } //note: these methods MUST NOT throw in order @@ -116,10 +114,10 @@ public: virtual void forceUiUpdateNoThrow() = 0; //noexcept - void updateStatus(const std::wstring& msg) final //throw AbortProcess + void updateStatus(std::wstring&& msg) final //throw AbortProcess { //assert(!msg.empty()); -> possible, e.g. start of parallel scan - statusText_ = msg; //update *before* running operations that can throw + statusText_ = std::move(msg); //update *before* running operations that can throw requestUiUpdate(false /*force*/); //throw AbortProcess } @@ -160,11 +158,15 @@ private: ProcessPhase currentPhase_ = ProcessPhase::none; ProgressStats statsCurrent_; - ProgressStats statsTotal_ { -1, -1 }; + ProgressStats statsTotal_ {-1, -1}; std::wstring statusText_; std::optional abortRequested_; }; + + +void delayAndCountDown(std::chrono::steady_clock::time_point delayUntil, const std::function& notifyStatus); +void runCommandAndLogErrors(const Zstring& cmdLine, zen::ErrorLog& errorLog); } #endif //STATUS_HANDLER_H_81704805908341534 diff --git a/FreeFileSync/Source/ui/abstract_folder_picker.cpp b/FreeFileSync/Source/ui/abstract_folder_picker.cpp index 1465d8b1..914b2358 100644 --- a/FreeFileSync/Source/ui/abstract_folder_picker.cpp +++ b/FreeFileSync/Source/ui/abstract_folder_picker.cpp @@ -74,7 +74,7 @@ private: error }; - AsyncGuiQueue guiQueue_{ 25 /*polling [ms]*/ }; //schedule and run long-running tasks asynchronously, but process results on GUI queue + AsyncGuiQueue guiQueue_{25 /*polling [ms]*/}; //schedule and run long-running tasks asynchronously, but process results on GUI queue //output-only parameters: AbstractPath& folderPathOut_; @@ -157,9 +157,9 @@ struct FlatTraverserCallback : public AFS::TraverserCallback private: void onFile (const AFS::FileInfo& fi) override {} std::shared_ptr onFolder (const AFS::FolderInfo& fi) override { result_.folderNames.emplace(fi.itemName, fi.isFollowedSymlink); return nullptr; } - HandleLink onSymlink(const AFS::SymlinkInfo& si) override { return LINK_FOLLOW; } - HandleError reportDirError (const ErrorInfo& errorInfo) override { logError(errorInfo.msg); return ON_ERROR_CONTINUE; } - HandleError reportItemError(const ErrorInfo& errorInfo, const Zstring& itemName) override { logError(errorInfo.msg); return ON_ERROR_CONTINUE; } + HandleLink onSymlink(const AFS::SymlinkInfo& si) override { return HandleLink::follow; } + HandleError reportDirError (const ErrorInfo& errorInfo) override { logError(errorInfo.msg); return HandleError::ignore; } + HandleError reportItemError(const ErrorInfo& errorInfo, const Zstring& itemName) override { logError(errorInfo.msg); return HandleError::ignore; } void logError(const std::wstring& msg) { @@ -189,7 +189,7 @@ void AbstractFolderPickerDlg::populateNodeThen(const wxTreeItemId& itemId, const guiQueue_.processAsync([folderPath = itemData->folderPath] //AbstractPath is thread-safe like an int! { auto ft = std::make_shared(); //noexcept, traverse directory one level deep - AFS::traverseFolderRecursive(folderPath.afsDevice, {{ folderPath.afsPath, ft }}, 1 /*parallelOps*/); + AFS::traverseFolderRecursive(folderPath.afsDevice, {{folderPath.afsPath, ft}}, 1 /*parallelOps*/); return ft->getResult(); }, @@ -290,8 +290,8 @@ void AbstractFolderPickerDlg::navigateToExistingPath(const wxTreeItemId& itemId, populateNodeThen(itemId, [this, itemId, nodeRelPath, leafType] { - const Zstring childFolderName = nodeRelPath.front(); - const std::vector childFolderRelPath{ nodeRelPath.begin() + 1, nodeRelPath.end() }; + const Zstring childFolderName = nodeRelPath.front(); + const std::vector childFolderRelPath{nodeRelPath.begin() + 1, nodeRelPath.end()}; wxTreeItemId childIdMatch; size_t insertPos = 0; //let's not use the wxTreeCtrl::OnCompareItems() abomination to implement sorting diff --git a/FreeFileSync/Source/ui/batch_config.cpp b/FreeFileSync/Source/ui/batch_config.cpp index 77ce2f9e..a2351cc9 100644 --- a/FreeFileSync/Source/ui/batch_config.cpp +++ b/FreeFileSync/Source/ui/batch_config.cpp @@ -167,7 +167,7 @@ ConfirmationButton fff::showBatchConfigDialog(wxWindow* parent, BatchExclusiveConfig& batchExCfg, bool& ignoreErrors) { - BatchDialogConfig dlgCfg = { batchExCfg, ignoreErrors }; + BatchDialogConfig dlgCfg = {batchExCfg, ignoreErrors}; BatchDialog batchDlg(parent, dlgCfg); diff --git a/FreeFileSync/Source/ui/batch_status_handler.cpp b/FreeFileSync/Source/ui/batch_status_handler.cpp index a79b894e..7950f76d 100644 --- a/FreeFileSync/Source/ui/batch_status_handler.cpp +++ b/FreeFileSync/Source/ui/batch_status_handler.cpp @@ -5,7 +5,6 @@ // ***************************************************************************** #include "batch_status_handler.h" -#include #include #include #include @@ -13,7 +12,7 @@ #include #include "../afs/concrete.h" #include "../log_file.h" -#include "status_handler_impl.h" +#include "../fatal_error.h" using namespace zen; using namespace fff; @@ -36,7 +35,7 @@ BatchStatusHandler::BatchStatusHandler(bool showProgress, autoRetryDelay_(autoRetryDelay), soundFileSyncComplete_(soundFileSyncComplete), progressDlg_(SyncProgressDialog::create(progressDlgSize, dlgMaximize, [this] { userRequestAbort(); }, *this, nullptr /*parentWindow*/, showProgress, autoCloseDialog, -{ jobName }, startTime, ignoreErrors, autoRetryCount, [&] +{jobName}, startTime, ignoreErrors, autoRetryCount, [&] { switch (postSyncAction) { @@ -96,7 +95,7 @@ BatchStatusHandler::Result BatchStatusHandler::reportResults(const Zstring& post const ProcessSummary summary { - startTime_, syncResult, { jobName_ }, + startTime_, syncResult, {jobName_}, getStatsCurrent(), getStatsTotal (), totalTime @@ -105,7 +104,7 @@ BatchStatusHandler::Result BatchStatusHandler::reportResults(const Zstring& post const AbstractPath logFilePath = generateLogFilePath(logFormat, summary, altLogFolderPathPhrase); //e.g. %AppData%\FreeFileSync\Logs\Backup FreeFileSync 2013-09-15 015052.123 [Error].log - auto notifyStatusNoThrow = [&](const std::wstring& msg) { try { updateStatus(msg); /*throw AbortProcess*/ } catch (AbortProcess&) {} }; + auto notifyStatusNoThrow = [&](std::wstring&& msg) { try { updateStatus(std::move(msg)); /*throw AbortProcess*/ } catch (AbortProcess&) {} }; bool autoClose = false; FinalRequest finalRequest = FinalRequest::none; @@ -212,7 +211,7 @@ BatchStatusHandler::Result BatchStatusHandler::reportResults(const Zstring& post //do NOT use tryReportingError()! saving log files should not be cancellable! saveLogFile(logFilePath, summary, errorLog_, logfilesMaxAgeDays, logFormat, logFilePathsToKeep, notifyStatusNoThrow); //throw FileError } - catch (const FileError& e) { errorLog_.logMsg(e.toString(), MSG_TYPE_ERROR); } + catch (const FileError& e) { errorLog_.logMsg(e.toString(), MSG_TYPE_ERROR); logFatalError(e.toString()); } //---------------------------------------------------------- @@ -236,7 +235,7 @@ BatchStatusHandler::Result BatchStatusHandler::reportResults(const Zstring& post syncResult, errorLogFinal); progressDlg_ = nullptr; - return { syncResult, errorLogFinal.ref().getStats(), finalRequest, logFilePath, dlgSize, dlgIsMaximized }; + return {syncResult, errorLogFinal.ref().getStats(), finalRequest, logFilePath, dlgSize, dlgIsMaximized}; } @@ -260,10 +259,10 @@ void BatchStatusHandler::updateDataProcessed(int itemsDelta, int64_t bytesDelta) } -void BatchStatusHandler::reportInfo(const std::wstring& msg) +void BatchStatusHandler::logInfo(const std::wstring& msg) { errorLog_.logMsg(msg, MSG_TYPE_INFO); - updateStatus(msg); //throw AbortProcess + requestUiUpdate(false /*force*/); //throw AbortProcess } diff --git a/FreeFileSync/Source/ui/batch_status_handler.h b/FreeFileSync/Source/ui/batch_status_handler.h index edea7480..91c1d78d 100644 --- a/FreeFileSync/Source/ui/batch_status_handler.h +++ b/FreeFileSync/Source/ui/batch_status_handler.h @@ -34,7 +34,7 @@ public: ~BatchStatusHandler(); void initNewPhase (int itemsTotal, int64_t bytesTotal, ProcessPhase phaseID) override; // - void reportInfo (const std::wstring& msg) override; // + void logInfo (const std::wstring& msg) override; // void reportWarning (const std::wstring& msg, bool& warningActive) override; //throw AbortProcess Response reportError (const ErrorInfo& errorInfo) override; // void reportFatalError(const std::wstring& msg) override; // diff --git a/FreeFileSync/Source/ui/cfg_grid.cpp b/FreeFileSync/Source/ui/cfg_grid.cpp index c0d153c1..98a8d50b 100644 --- a/FreeFileSync/Source/ui/cfg_grid.cpp +++ b/FreeFileSync/Source/ui/cfg_grid.cpp @@ -16,6 +16,7 @@ #include #include "../icon_buffer.h" #include "../ffs_paths.h" +#include "../afs/native.h" using namespace zen; using namespace fff; @@ -199,7 +200,7 @@ void ConfigView::sortListViewImpl() if (lhs->second.isLastRunCfg != rhs->second.isLastRunCfg) return lhs->second.isLastRunCfg < rhs->second.isLastRunCfg; //"last session" label should be (always) last - return makeSortDirection(std::greater<>(), std::bool_constant())(lhs->second.cfgItem.lastSyncTime, rhs->second.cfgItem.lastSyncTime); + return makeSortDirection(std::greater(), std::bool_constant())(lhs->second.cfgItem.lastSyncTime, rhs->second.cfgItem.lastSyncTime); //[!] ascending lastSync shows lowest "days past" first <=> highest lastSyncTime first }; @@ -215,7 +216,7 @@ void ConfigView::sortListViewImpl() //primary sort order if (hasLogL && lhs->second.cfgItem.logResult != rhs->second.cfgItem.logResult) - return makeSortDirection(std::greater<>(), std::bool_constant())(lhs->second.cfgItem.logResult, rhs->second.cfgItem.logResult); + return makeSortDirection(std::greater(), std::bool_constant())(lhs->second.cfgItem.logResult, rhs->second.cfgItem.logResult); //secondary sort order return LessNaturalSort()(lhs->second.name, rhs->second.name); @@ -480,9 +481,7 @@ private: break; case ColumnTypeCfg::lastLog: - if (!item->isLastRunCfg && - !AFS::isNullPath(item->cfgItem.logFilePath) && - AFS::getNativeItemPath(item->cfgItem.logFilePath)) + if (!item->isLastRunCfg && !getNativeItemPath(item->cfgItem.logFilePath).empty()) return static_cast(HoverAreaLog::link); break; } @@ -588,8 +587,9 @@ private: case HoverAreaLog::link: try { - if (std::optional nativePath = AFS::getNativeItemPath(item->cfgItem.logFilePath)) - openWithDefaultApp(*nativePath); //throw FileError + if (const Zstring& nativePath = getNativeItemPath(item->cfgItem.logFilePath); + !nativePath.empty()) + openWithDefaultApp(nativePath); //throw FileError else assert(false); assert(!AFS::isNullPath(item->cfgItem.logFilePath)); //see getMouseHover() diff --git a/FreeFileSync/Source/ui/cfg_grid.h b/FreeFileSync/Source/ui/cfg_grid.h index 19a7b427..84364584 100644 --- a/FreeFileSync/Source/ui/cfg_grid.h +++ b/FreeFileSync/Source/ui/cfg_grid.h @@ -59,9 +59,9 @@ std::vector getCfgGridDefaultColAttribs() using namespace zen; return { - { ColumnTypeCfg::name, -fastFromDIP(75) - fastFromDIP(42), 1, true }, - { ColumnTypeCfg::lastSync, fastFromDIP(75), 0, true }, - { ColumnTypeCfg::lastLog, fastFromDIP(42), 0, true }, //leave some room for the sort direction indicator + {ColumnTypeCfg::name, -fastFromDIP(75) - fastFromDIP(42), 1, true}, + {ColumnTypeCfg::lastSync, fastFromDIP(75), 0, true}, + {ColumnTypeCfg::lastLog, fastFromDIP(42), 0, true}, //leave some room for the sort direction indicator }; } @@ -126,7 +126,7 @@ public: size_t getRowCount() const { assert(cfgList_.size() == cfgListView_.size()); return cfgListView_.size(); } void setSortDirection(ColumnTypeCfg colType, bool ascending); - std::pair getSortDirection() { return { sortColumn_, sortAscending_ }; } + std::pair getSortDirection() { return {sortColumn_, sortAscending_}; } private: ConfigView (const ConfigView&) = delete; diff --git a/FreeFileSync/Source/ui/command_box.cpp b/FreeFileSync/Source/ui/command_box.cpp index 4c1b177f..c0637b10 100644 --- a/FreeFileSync/Source/ui/command_box.cpp +++ b/FreeFileSync/Source/ui/command_box.cpp @@ -112,9 +112,9 @@ void CommandBox::setValueAndUpdateList(const wxString& value) if (std::find(items.begin(), items.end(), value) == items.end()) { if (!items.empty() && !value.empty()) - items.insert(items.begin(), { value, getSeparationLine() }); + items.insert(items.begin(), {value, getSeparationLine()}); else - items.insert(items.begin(), { value }); + items.insert(items.begin(), {value}); } //this->Clear(); -> NO! emits yet another wxEVT_COMMAND_TEXT_UPDATED!!! diff --git a/FreeFileSync/Source/ui/file_grid.cpp b/FreeFileSync/Source/ui/file_grid.cpp index b65786d1..d54a9368 100644 --- a/FreeFileSync/Source/ui/file_grid.cpp +++ b/FreeFileSync/Source/ui/file_grid.cpp @@ -34,17 +34,17 @@ wxDEFINE_EVENT(EVENT_GRID_SYNC_DIRECTION, SyncDirectionEvent); namespace { //let's NOT create wxWidgets objects statically: -inline wxColor getColorSyncBlue (bool faint) { if (faint) return { 0xed, 0xee, 0xff }; return { 185, 188, 255 }; } -inline wxColor getColorSyncGreen(bool faint) { if (faint) return { 0xf1, 0xff, 0xed }; return { 196, 255, 185 }; } +inline wxColor getColorSyncBlue (bool faint) { if (faint) return {0xed, 0xee, 0xff}; return {185, 188, 255}; } +inline wxColor getColorSyncGreen(bool faint) { if (faint) return {0xf1, 0xff, 0xed}; return {196, 255, 185}; } -inline wxColor getColorConflictBackground (bool faint) { if (faint) return { 0xfe, 0xfe, 0xda }; return { 247, 252, 62 }; } //yellow -inline wxColor getColorDifferentBackground(bool faint) { if (faint) return { 0xff, 0xed, 0xee }; return { 255, 185, 187 }; } //red +inline wxColor getColorConflictBackground (bool faint) { if (faint) return {0xfe, 0xfe, 0xda}; return {247, 252, 62}; } //yellow +inline wxColor getColorDifferentBackground(bool faint) { if (faint) return {0xff, 0xed, 0xee}; return {255, 185, 187}; } //red -inline wxColor getColorSymlinkBackground() { return { 238, 201, 0 }; } //orange -//inline wxColor getColorItemMissing() { return { 212, 208, 200 }; } //medium grey +inline wxColor getColorSymlinkBackground() { return {238, 201, 0}; } //orange +//inline wxColor getColorItemMissing() { return {212, 208, 200}; } //medium grey -inline wxColor getColorInactiveBack(bool faint) { if (faint) return { 0xf6, 0xf6, 0xf6}; return { 0xe4, 0xe4, 0xe4 }; } //light grey -inline wxColor getColorInactiveText() { return { 0x40, 0x40, 0x40 }; } //dark grey +inline wxColor getColorInactiveBack(bool faint) { if (faint) return {0xf6, 0xf6, 0xf6}; return {0xe4, 0xe4, 0xe4}; } //light grey +inline wxColor getColorInactiveText() { return {0x40, 0x40, 0x40}; } //dark grey inline wxColor getColorGridLine() { return wxSystemSettings::GetColour(wxSYS_COLOUR_BTNSHADOW); } @@ -73,7 +73,7 @@ std::pair getVisibleRows(const Grid& grid) //returns range const ptrdiff_t rowFrom = grid.getRowAtPos(topLeft.y); //return -1 for invalid position, rowCount if out of range const ptrdiff_t rowTo = grid.getRowAtPos(bottom.y); if (rowFrom >= 0 && rowTo >= 0) - return { rowFrom, std::min(rowTo + 1, rowCount) }; + return {rowFrom, std::min(rowTo + 1, rowCount)}; } return {}; } @@ -125,36 +125,36 @@ enum class CudAction update, destroy, }; -std::pair getCudAction(SyncOperation so) +std::pair getCudAction(SyncOperation so) { switch (so) { //*INDENT-OFF* case SO_CREATE_NEW_LEFT: - case SO_MOVE_LEFT_TO: return {CudAction::create, LEFT_SIDE}; + case SO_MOVE_LEFT_TO: return {CudAction::create, SelectSide::left}; case SO_CREATE_NEW_RIGHT: - case SO_MOVE_RIGHT_TO: return {CudAction::create, RIGHT_SIDE}; + case SO_MOVE_RIGHT_TO: return {CudAction::create, SelectSide::right}; case SO_DELETE_LEFT: - case SO_MOVE_LEFT_FROM: return {CudAction::destroy, LEFT_SIDE}; + case SO_MOVE_LEFT_FROM: return {CudAction::destroy, SelectSide::left}; case SO_DELETE_RIGHT: - case SO_MOVE_RIGHT_FROM: return {CudAction::destroy, RIGHT_SIDE}; + case SO_MOVE_RIGHT_FROM: return {CudAction::destroy, SelectSide::right}; case SO_OVERWRITE_LEFT: - case SO_COPY_METADATA_TO_LEFT: return {CudAction::update, LEFT_SIDE}; + case SO_COPY_METADATA_TO_LEFT: return {CudAction::update, SelectSide::left}; case SO_OVERWRITE_RIGHT: - case SO_COPY_METADATA_TO_RIGHT: return {CudAction::update, RIGHT_SIDE}; + case SO_COPY_METADATA_TO_RIGHT: return {CudAction::update, SelectSide::right}; case SO_DO_NOTHING: case SO_EQUAL: - case SO_UNRESOLVED_CONFLICT: return {CudAction::doNothing, LEFT_SIDE}; + case SO_UNRESOLVED_CONFLICT: return {CudAction::doNothing, SelectSide::left}; //*INDENT-ON* } assert(false); - return {CudAction::doNothing, LEFT_SIDE}; + return {CudAction::doNothing, SelectSide::left}; } @@ -375,7 +375,7 @@ private: //######################################################################################################## -template +template class GridDataRim : public GridDataBase { public: @@ -503,7 +503,7 @@ private: case ColumnTypeRim::size: visitFSObject(*fsObj, [&](const FolderPair& folder) { value = L'<' + _("Folder") + L'>'; }, [&](const FilePair& file) { value = formatNumber(file.getFileSize()); }, - //[&](const FilePair& file) { value = utfTo(formatAsHexString(file.getFileId())); }, // -> test file id + //[&](const FilePair& file) { value = numberTo(file.getFilePrint()); }, // -> test file id [&](const SymlinkPair& symlink) { value = L'<' + _("Symlink") + L'>'; }); break; @@ -1194,7 +1194,7 @@ private: //draw sort marker if (auto sortInfo = getDataView().getSortConfig()) if (const ColumnTypeRim* sortType = std::get_if(&sortInfo->sortCol)) - if (*sortType == static_cast(colType) && sortInfo->onLeft == (side == LEFT_SIDE)) + if (*sortType == static_cast(colType) && sortInfo->onLeft == (side == SelectSide::left)) { const wxImage sortMarker = loadImage(sortInfo->ascending ? "sort_ascending" : "sort_descending"); drawBitmapRtlNoMirror(dc, enabled ? sortMarker : sortMarker.ConvertToDisabled(), rectInner, wxALIGN_CENTER_HORIZONTAL); @@ -1293,16 +1293,16 @@ private: }; -class GridDataLeft : public GridDataRim +class GridDataLeft : public GridDataRim { public: - GridDataLeft(Grid& grid, const SharedRef& sharedComp) : GridDataRim(grid, sharedComp) {} + GridDataLeft(Grid& grid, const SharedRef& sharedComp) : GridDataRim(grid, sharedComp) {} }; -class GridDataRight : public GridDataRim +class GridDataRight : public GridDataRim { public: - GridDataRight(Grid& grid, const SharedRef& sharedComp) : GridDataRim(grid, sharedComp) {} + GridDataRight(Grid& grid, const SharedRef& sharedComp) : GridDataRim(grid, sharedComp) {} }; //######################################################################################################## @@ -1480,7 +1480,7 @@ private: { //draw notch on left side if (notch_.GetHeight() != rectTmp.height) - notch_.Rescale(notch_.GetWidth(), rectTmp.height); + notch_ = notch_.Scale(notch_.GetWidth(), rectTmp.height); //wxWidgets screws up again and has wxALIGN_RIGHT off by one pixel! -> use wxALIGN_LEFT instead const wxRect rectNotch(rectTmp.x + rectTmp.width - notch_.GetWidth(), rectTmp.y, notch_.GetWidth(), rectTmp.height); @@ -2008,9 +2008,9 @@ void filegrid::init(Grid& gridLeft, Grid& gridCenter, Grid& gridRight) gridCenter.setColumnConfig( { - { static_cast(ColumnTypeCenter::checkbox), widthCheckbox, 0, true }, - { static_cast(ColumnTypeCenter::difference), widthDifference, 0, true }, - { static_cast(ColumnTypeCenter::action), widthAction, 0, true }, + {static_cast(ColumnTypeCenter::checkbox), widthCheckbox, 0, true}, + {static_cast(ColumnTypeCenter::difference), widthDifference, 0, true}, + {static_cast(ColumnTypeCenter::action), widthAction, 0, true}, }); } diff --git a/FreeFileSync/Source/ui/file_grid_attr.h b/FreeFileSync/Source/ui/file_grid_attr.h index 13c4dab9..012724ad 100644 --- a/FreeFileSync/Source/ui/file_grid_attr.h +++ b/FreeFileSync/Source/ui/file_grid_attr.h @@ -42,10 +42,10 @@ std::vector getFileGridDefaultColAttribsLeft() using namespace zen; return //harmonize with main_dlg.cpp::onGridLabelContextRim() => expects stretched path and non-stretched other columns! { - { ColumnTypeRim::path, -fastFromDIP(100), 1, true }, - { ColumnTypeRim::extension, fastFromDIP( 60), 0, false }, - { ColumnTypeRim::date, fastFromDIP( 140), 0, false }, - { ColumnTypeRim::size, fastFromDIP( 100), 0, true }, + {ColumnTypeRim::path, -fastFromDIP(100), 1, true }, + {ColumnTypeRim::extension, fastFromDIP( 60), 0, false}, + {ColumnTypeRim::date, fastFromDIP( 140), 0, false}, + {ColumnTypeRim::size, fastFromDIP( 100), 0, true }, }; } diff --git a/FreeFileSync/Source/ui/file_view.cpp b/FreeFileSync/Source/ui/file_view.cpp index 8d06b266..fbea502e 100644 --- a/FreeFileSync/Source/ui/file_view.cpp +++ b/FreeFileSync/Source/ui/file_view.cpp @@ -62,8 +62,8 @@ FileView::FileView(FolderComparison& folderCmp) serializeHierarchy(baseObj, sortedRef_); folderPairs_.emplace_back(&baseObj, - baseObj.getAbstractPath< LEFT_SIDE>(), - baseObj.getAbstractPath()); + baseObj.getAbstractPath< SelectSide::left>(), + baseObj.getAbstractPath()); }); } @@ -123,7 +123,7 @@ void FileView::updateView(Predicate pred) assert(!groupDetails_.empty()); const size_t groupIdx = groupDetails_.size() - 1; //----------------------------------------------------------- - viewRef_.push_back({ objId, groupIdx }); + viewRef_.push_back({objId, groupIdx}); } } @@ -149,33 +149,33 @@ void addNumbers(const FileSystemObject& fsObj, ViewStats& stats) { visitFSObject(fsObj, [&](const FolderPair& folder) { - if (!folder.isEmpty()) + if (!folder.isEmpty()) ++stats.fileStatsLeft.folderCount; - if (!folder.isEmpty()) + if (!folder.isEmpty()) ++stats.fileStatsRight.folderCount; }, [&](const FilePair& file) { - if (!file.isEmpty()) + if (!file.isEmpty()) { - stats.fileStatsLeft.bytes += file.getFileSize(); + stats.fileStatsLeft.bytes += file.getFileSize(); ++stats.fileStatsLeft.fileCount; } - if (!file.isEmpty()) + if (!file.isEmpty()) { - stats.fileStatsRight.bytes += file.getFileSize(); + stats.fileStatsRight.bytes += file.getFileSize(); ++stats.fileStatsRight.fileCount; } }, [&](const SymlinkPair& symlink) { - if (!symlink.isEmpty()) + if (!symlink.isEmpty()) ++stats.fileStatsLeft.fileCount; - if (!symlink.isEmpty()) + if (!symlink.isEmpty()) ++stats.fileStatsRight.fileCount; }); } @@ -345,7 +345,7 @@ FileView::PathDrawInfo FileView::getDrawInfo(size_t row) if (fsObj && !folderGroupObj) folderGroupObj = dynamic_cast(&fsObj->parent()); - return { groupFirstRow, groupLastRow, groupIdx, viewUpdateId_, folderGroupObj, fsObj }; + return {groupFirstRow, groupLastRow, groupIdx, viewUpdateId_, folderGroupObj, fsObj}; } assert(false); //unexpected: check rowsOnView()! return {}; @@ -394,7 +394,7 @@ bool isDirectoryPair(const FileSystemObject& fsObj) } -template inline +template inline bool lessFileName(const FileSystemObject& lhs, const FileSystemObject& rhs) { //sort order: first files/symlinks, then directories then empty rows @@ -445,7 +445,7 @@ bool lessFilePath(const FileSystemObject::ObjectId& lhs, const FileSystemObject: const size_t basePosR = itR->second; if (basePosL != basePosR) - return zen::makeSortDirection(std::less<>(), std::bool_constant())(basePosL, basePosR); + return zen::makeSortDirection(std::less(), std::bool_constant())(basePosL, basePosR); } //------- sort component-wise ---------- @@ -507,7 +507,7 @@ bool lessFilePath(const FileSystemObject::ObjectId& lhs, const FileSystemObject: else return std::is_gt(cmp); } - //return zen::makeSortDirection(std::less<>(), std::bool_constant())(rv, 0); + //return zen::makeSortDirection(std::less(), std::bool_constant())(rv, 0); /*...with equivalent names: 1. functional correctness => must not compare equal! e.g. a/a/x and a/A/y @@ -516,7 +516,7 @@ bool lessFilePath(const FileSystemObject::ObjectId& lhs, const FileSystemObject: } -template inline +template inline bool lessFilesize(const FileSystemObject& lhs, const FileSystemObject& rhs) { //empty rows always last @@ -541,11 +541,11 @@ bool lessFilesize(const FileSystemObject& lhs, const FileSystemObject& rhs) return true; //return list beginning with largest files first - return zen::makeSortDirection(std::less<>(), std::bool_constant())(fileL->getFileSize(), fileR->getFileSize()); + return zen::makeSortDirection(std::less(), std::bool_constant())(fileL->getFileSize(), fileR->getFileSize()); } -template inline +template inline bool lessFiletime(const FileSystemObject& lhs, const FileSystemObject& rhs) { if (lhs.isEmpty()) @@ -568,11 +568,11 @@ bool lessFiletime(const FileSystemObject& lhs, const FileSystemObject& rhs) const int64_t dateR = fileR ? fileR->getLastWriteTime() : symlinkR->getLastWriteTime(); //return list beginning with newest files first - return zen::makeSortDirection(std::less<>(), std::bool_constant())(dateL, dateR); + return zen::makeSortDirection(std::less(), std::bool_constant())(dateL, dateR); } -template inline +template inline bool lessExtension(const FileSystemObject& lhs, const FileSystemObject& rhs) { if (lhs.isEmpty()) @@ -613,11 +613,11 @@ bool lessCmpResult(const FileSystemObject& lhs, const FileSystemObject& rhs) template inline bool lessSyncDirection(const FileSystemObject& lhs, const FileSystemObject& rhs) { - return zen::makeSortDirection(std::less<>(), std::bool_constant())(lhs.getSyncOperation(), rhs.getSyncOperation()); + return zen::makeSortDirection(std::less(), std::bool_constant())(lhs.getSyncOperation(), rhs.getSyncOperation()); } -template +template struct LessFullPath { LessFullPath(std::vector> folderPairs) @@ -673,7 +673,7 @@ private: }; -template +template struct LessFileName { bool operator()(const FileSystemObject::ObjectId& lhs, const FileSystemObject::ObjectId& rhs) const @@ -690,7 +690,7 @@ struct LessFileName }; -template +template struct LessFilesize { bool operator()(const FileSystemObject::ObjectId& lhs, const FileSystemObject::ObjectId& rhs) const @@ -707,7 +707,7 @@ struct LessFilesize }; -template +template struct LessFiletime { bool operator()(const FileSystemObject::ObjectId& lhs, const FileSystemObject::ObjectId& rhs) const @@ -724,7 +724,7 @@ struct LessFiletime }; -template +template struct LessExtension { bool operator()(const FileSystemObject::ObjectId& lhs, const FileSystemObject::ObjectId& rhs) const @@ -783,7 +783,7 @@ void FileView::sortView(ColumnTypeRim type, ItemPathFormat pathFmt, bool onLeft, groupDetails_ .clear(); rowPositions_ .clear(); rowPositionsFirstChild_.clear(); - currentSort_ = SortInfo({ type, onLeft, ascending }); + currentSort_ = SortInfo({type, onLeft, ascending}); switch (type) { @@ -791,10 +791,10 @@ void FileView::sortView(ColumnTypeRim type, ItemPathFormat pathFmt, bool onLeft, switch (pathFmt) { case ItemPathFormat::name: - if ( ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFileName()); - else if ( ascending && !onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFileName()); - else if (!ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFileName()); - else if (!ascending && !onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFileName()); + if ( ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFileName()); + else if ( ascending && !onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFileName()); + else if (!ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFileName()); + else if (!ascending && !onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFileName()); break; case ItemPathFormat::relative: @@ -803,31 +803,31 @@ void FileView::sortView(ColumnTypeRim type, ItemPathFormat pathFmt, bool onLeft, break; case ItemPathFormat::full: - if ( ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFullPath(folderPairs_)); - else if ( ascending && !onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFullPath(folderPairs_)); - else if (!ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFullPath(folderPairs_)); - else if (!ascending && !onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFullPath(folderPairs_)); + if ( ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFullPath(folderPairs_)); + else if ( ascending && !onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFullPath(folderPairs_)); + else if (!ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFullPath(folderPairs_)); + else if (!ascending && !onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFullPath(folderPairs_)); break; } break; case ColumnTypeRim::size: - if ( ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFilesize()); - else if ( ascending && !onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFilesize()); - else if (!ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFilesize()); - else if (!ascending && !onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFilesize()); + if ( ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFilesize()); + else if ( ascending && !onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFilesize()); + else if (!ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFilesize()); + else if (!ascending && !onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFilesize()); break; case ColumnTypeRim::date: - if ( ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFiletime()); - else if ( ascending && !onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFiletime()); - else if (!ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFiletime()); - else if (!ascending && !onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFiletime()); + if ( ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFiletime()); + else if ( ascending && !onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFiletime()); + else if (!ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFiletime()); + else if (!ascending && !onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFiletime()); break; case ColumnTypeRim::extension: - if ( ascending && onLeft) std::stable_sort(sortedRef_.begin(), sortedRef_.end(), LessExtension()); - else if ( ascending && !onLeft) std::stable_sort(sortedRef_.begin(), sortedRef_.end(), LessExtension()); - else if (!ascending && onLeft) std::stable_sort(sortedRef_.begin(), sortedRef_.end(), LessExtension()); - else if (!ascending && !onLeft) std::stable_sort(sortedRef_.begin(), sortedRef_.end(), LessExtension()); + if ( ascending && onLeft) std::stable_sort(sortedRef_.begin(), sortedRef_.end(), LessExtension()); + else if ( ascending && !onLeft) std::stable_sort(sortedRef_.begin(), sortedRef_.end(), LessExtension()); + else if (!ascending && onLeft) std::stable_sort(sortedRef_.begin(), sortedRef_.end(), LessExtension()); + else if (!ascending && !onLeft) std::stable_sort(sortedRef_.begin(), sortedRef_.end(), LessExtension()); break; } } @@ -839,7 +839,7 @@ void FileView::sortView(ColumnTypeCenter type, bool ascending) groupDetails_ .clear(); rowPositions_ .clear(); rowPositionsFirstChild_.clear(); - currentSort_ = SortInfo({ type, false, ascending }); + currentSort_ = SortInfo({type, false, ascending}); switch (type) { diff --git a/FreeFileSync/Source/ui/folder_pair.h b/FreeFileSync/Source/ui/folder_pair.h index 5940e25e..0357ae4c 100644 --- a/FreeFileSync/Source/ui/folder_pair.h +++ b/FreeFileSync/Source/ui/folder_pair.h @@ -84,7 +84,7 @@ private: zen::ContextMenu menu; menu.addItem(_("Remove local settings"), removeLocalCompCfg, wxNullImage, static_cast(localCmpCfg_)); - menu.popup(*basicPanel_.m_bpButtonLocalCompCfg, { basicPanel_.m_bpButtonLocalCompCfg->GetSize().x, 0 }); + menu.popup(*basicPanel_.m_bpButtonLocalCompCfg, {basicPanel_.m_bpButtonLocalCompCfg->GetSize().x, 0}); } void onLocalSyncCfgContext(wxEvent& event) @@ -98,7 +98,7 @@ private: zen::ContextMenu menu; menu.addItem(_("Remove local settings"), removeLocalSyncCfg, wxNullImage, static_cast(localSyncCfg_)); - menu.popup(*basicPanel_.m_bpButtonLocalSyncCfg, { basicPanel_.m_bpButtonLocalSyncCfg->GetSize().x, 0 }); + menu.popup(*basicPanel_.m_bpButtonLocalSyncCfg, {basicPanel_.m_bpButtonLocalSyncCfg->GetSize().x, 0}); } void onLocalFilterCfgContext(wxEvent& event) @@ -128,7 +128,7 @@ private: menu.addSeparator(); menu.addItem( _("Copy"), copyFilter, wxNullImage, !isNullFilter(localFilter_)); menu.addItem( _("Paste"), pasteFilter, wxNullImage, filterCfgOnClipboard.get() != nullptr); - menu.popup(*basicPanel_.m_bpButtonLocalFilter, { basicPanel_.m_bpButtonLocalFilter->GetSize().x, 0 }); + menu.popup(*basicPanel_.m_bpButtonLocalFilter, {basicPanel_.m_bpButtonLocalFilter->GetSize().x, 0}); } diff --git a/FreeFileSync/Source/ui/folder_selector.cpp b/FreeFileSync/Source/ui/folder_selector.cpp index 061b603f..99c0bbe4 100644 --- a/FreeFileSync/Source/ui/folder_selector.cpp +++ b/FreeFileSync/Source/ui/folder_selector.cpp @@ -201,6 +201,9 @@ void FolderSelector::onSelectFolder(wxCommandEvent& event) //make sure default folder exists: don't let folder picker hang on non-existing network share! auto folderExistsTimed = [waitEndTime = std::chrono::steady_clock::now() + FOLDER_SELECTED_EXISTENCE_CHECK_TIME_MAX](const AbstractPath& folderPath) { + if (AFS::isNullPath(folderPath)) + return false; + auto ft = runAsync([folderPath] { try @@ -218,8 +221,9 @@ void FolderSelector::onSelectFolder(wxCommandEvent& event) { const AbstractPath folderPath = createItemPathNative(folderPathPhrase); if (folderExistsTimed(folderPath)) - if (std::optional nativeFolderPath = AFS::getNativeItemPath(folderPath)) - defaultFolderNative = *nativeFolderPath; + if (const Zstring& nativePath = getNativeItemPath(folderPath); + !nativePath.empty()) + defaultFolderNative = nativePath; } }; const Zstring& currentFolderPath = getPath(); diff --git a/FreeFileSync/Source/ui/gui_status_handler.cpp b/FreeFileSync/Source/ui/gui_status_handler.cpp index ef82679f..861ea810 100644 --- a/FreeFileSync/Source/ui/gui_status_handler.cpp +++ b/FreeFileSync/Source/ui/gui_status_handler.cpp @@ -15,7 +15,7 @@ #include "main_dlg.h" #include "../afs/concrete.h" #include "../log_file.h" -#include "status_handler_impl.h" +#include "../fatal_error.h" using namespace zen; using namespace fff; @@ -172,7 +172,7 @@ StatusHandlerTemporaryPanel::Result StatusHandlerTemporaryPanel::reportResults() auto errorLogFinal = std::make_shared(std::move(errorLog_)); errorLog_ = ErrorLog(); //see check in ~StatusHandlerTemporaryPanel() - return { summary, errorLogFinal }; + return {summary, errorLogFinal}; } @@ -187,10 +187,10 @@ void StatusHandlerTemporaryPanel::initNewPhase(int itemsTotal, int64_t bytesTota } -void StatusHandlerTemporaryPanel::reportInfo(const std::wstring& msg) +void StatusHandlerTemporaryPanel::logInfo(const std::wstring& msg) { errorLog_.logMsg(msg, MSG_TYPE_INFO); - updateStatus(msg); //throw AbortProcess + requestUiUpdate(false /*force*/); //throw AbortProcess } @@ -405,7 +405,7 @@ StatusHandlerFloatingDialog::Result StatusHandlerFloatingDialog::reportResults(c const AbstractPath logFilePath = generateLogFilePath(logFormat, summary, altLogFolderPathPhrase); //e.g. %AppData%\FreeFileSync\Logs\Backup FreeFileSync 2013-09-15 015052.123 [Error].log - auto notifyStatusNoThrow = [&](const std::wstring& msg) { try { updateStatus(msg); /*throw AbortProcess*/ } catch (AbortProcess&) {} }; + auto notifyStatusNoThrow = [&](std::wstring&& msg) { try { updateStatus(std::move(msg)); /*throw AbortProcess*/ } catch (AbortProcess&) {} }; bool autoClose = false; FinalRequest finalRequest = FinalRequest::none; @@ -513,7 +513,7 @@ StatusHandlerFloatingDialog::Result StatusHandlerFloatingDialog::reportResults(c //do NOT use tryReportingError()! saving log files should not be cancellable! saveLogFile(logFilePath, summary, errorLog_, logfilesMaxAgeDays, logFormat, logFilePathsToKeep, notifyStatusNoThrow); //throw FileError } - catch (const FileError& e) { errorLog_.logMsg(e.toString(), MSG_TYPE_ERROR); } + catch (const FileError& e) { errorLog_.logMsg(e.toString(), MSG_TYPE_ERROR); logFatalError(e.toString()); } //---------------------------------------------------------- @@ -532,7 +532,7 @@ StatusHandlerFloatingDialog::Result StatusHandlerFloatingDialog::reportResults(c syncResult, errorLogFinal); progressDlg_ = nullptr; - return { summary, errorLogFinal, finalRequest, logFilePath, dlgSize, dlgIsMaximized, autoCloseFinal }; + return {summary, errorLogFinal, finalRequest, logFilePath, dlgSize, dlgIsMaximized, autoCloseFinal}; } @@ -547,10 +547,10 @@ void StatusHandlerFloatingDialog::initNewPhase(int itemsTotal, int64_t bytesTota } -void StatusHandlerFloatingDialog::reportInfo(const std::wstring& msg) +void StatusHandlerFloatingDialog::logInfo(const std::wstring& msg) { errorLog_.logMsg(msg, MSG_TYPE_INFO); - updateStatus(msg); //throw AbortProcess + requestUiUpdate(false /*force*/); //throw AbortProcess } diff --git a/FreeFileSync/Source/ui/gui_status_handler.h b/FreeFileSync/Source/ui/gui_status_handler.h index 643714a2..8f74e77d 100644 --- a/FreeFileSync/Source/ui/gui_status_handler.h +++ b/FreeFileSync/Source/ui/gui_status_handler.h @@ -26,7 +26,7 @@ public: ~StatusHandlerTemporaryPanel(); void initNewPhase (int itemsTotal, int64_t bytesTotal, ProcessPhase phaseID) override; // - void reportInfo (const std::wstring& msg) override; // + void logInfo (const std::wstring& msg) override; // void reportWarning (const std::wstring& msg, bool& warningActive) override; //throw AbortProcess Response reportError (const ErrorInfo& errorInfo) override; // void reportFatalError(const std::wstring& msg) override; // @@ -71,7 +71,7 @@ public: ~StatusHandlerFloatingDialog(); void initNewPhase (int itemsTotal, int64_t bytesTotal, ProcessPhase phaseID) override; // - void reportInfo (const std::wstring& msg) override; // + void logInfo (const std::wstring& msg) override; // void reportWarning (const std::wstring& msg, bool& warningActive) override; //throw AbortProcess Response reportError (const ErrorInfo& errorInfo) override; // void reportFatalError(const std::wstring& msg) override; // diff --git a/FreeFileSync/Source/ui/log_panel.cpp b/FreeFileSync/Source/ui/log_panel.cpp index 3bea6506..cd61b9bd 100644 --- a/FreeFileSync/Source/ui/log_panel.cpp +++ b/FreeFileSync/Source/ui/log_panel.cpp @@ -18,7 +18,7 @@ using namespace fff; namespace { -inline wxColor getColorGridLine() { return { 192, 192, 192 }; } //light grey +inline wxColor getColorGridLine() { return {192, 192, 192}; } //light grey inline @@ -320,9 +320,9 @@ LogPanel::LogPanel(wxWindow* parent) : LogPanelGenerated(parent) m_gridMessages->setRowHeight(rowHeight); m_gridMessages->setColumnConfig( { - { static_cast(ColumnTypeLog::time ), colMsgTimeWidth, 0, true }, - { static_cast(ColumnTypeLog::severity), colMsgSeverityWidth, 0, true }, - { static_cast(ColumnTypeLog::text ), -colMsgTimeWidth - colMsgSeverityWidth, 1, true }, + {static_cast(ColumnTypeLog::time ), colMsgTimeWidth, 0, true}, + {static_cast(ColumnTypeLog::severity), colMsgSeverityWidth, 0, true}, + {static_cast(ColumnTypeLog::text ), -colMsgTimeWidth - colMsgSeverityWidth, 1, true}, }); //support for CTRL + C diff --git a/FreeFileSync/Source/ui/main_dlg.cpp b/FreeFileSync/Source/ui/main_dlg.cpp index be302be0..3fe046fb 100644 --- a/FreeFileSync/Source/ui/main_dlg.cpp +++ b/FreeFileSync/Source/ui/main_dlg.cpp @@ -49,6 +49,7 @@ #include "../ffs_paths.h" #include "../localization.h" #include "../version/version.h" +#include "../afs/gdrive.h" using namespace zen; using namespace fff; @@ -1350,8 +1351,8 @@ std::vector MainDialog::getTreeSelection() const void MainDialog::copyToAlternateFolder(const std::vector& selectionLeft, const std::vector& selectionRight) { - if (std::all_of(selectionLeft .begin(), selectionLeft .end(), [](const FileSystemObject* fsObj) { return fsObj->isEmpty< LEFT_SIDE>(); }) && - /**/std::all_of(selectionRight.begin(), selectionRight.end(), [](const FileSystemObject* fsObj) { return fsObj->isEmpty(); })) + if (std::all_of(selectionLeft .begin(), selectionLeft .end(), [](const FileSystemObject* fsObj) { return fsObj->isEmpty< SelectSide::left>(); }) && + /**/std::all_of(selectionRight.begin(), selectionRight.end(), [](const FileSystemObject* fsObj) { return fsObj->isEmpty(); })) /**/return; //harmonize with onGridContextRim(): this function should be a no-op iff context menu option is disabled! FocusPreserver fp; @@ -1399,8 +1400,8 @@ void MainDialog::copyToAlternateFolder(const std::vector& sel void MainDialog::deleteSelectedFiles(const std::vector& selectionLeft, const std::vector& selectionRight, bool moveToRecycler) { - if (std::all_of(selectionLeft .begin(), selectionLeft .end(), [](const FileSystemObject* fsObj) { return fsObj->isEmpty< LEFT_SIDE>(); }) && - /**/std::all_of(selectionRight.begin(), selectionRight.end(), [](const FileSystemObject* fsObj) { return fsObj->isEmpty(); })) + if (std::all_of(selectionLeft .begin(), selectionLeft .end(), [](const FileSystemObject* fsObj) { return fsObj->isEmpty< SelectSide::left>(); }) && + /**/std::all_of(selectionRight.begin(), selectionRight.end(), [](const FileSystemObject* fsObj) { return fsObj->isEmpty(); })) /**/return; //harmonize with onGridContextRim(): this function should be a no-op iff context menu option is disabled! FocusPreserver fp; @@ -1444,7 +1445,7 @@ void MainDialog::deleteSelectedFiles(const std::vector& selec namespace { -template +template AbstractPath getExistingParentFolder(const FileSystemObject& fsObj) { auto folder = dynamic_cast(&fsObj); @@ -1462,41 +1463,40 @@ AbstractPath getExistingParentFolder(const FileSystemObject& fsObj) } -template +template void extractFileDescriptor(const FileSystemObject& fsObj, Function onDescriptor) { if (!fsObj.isEmpty()) visitFSObject(fsObj, [](const FolderPair& folder) {}, [&](const FilePair& file) { - const FileDescriptor descr = { file.getAbstractPath(), file.getAttributes() }; - onDescriptor(descr); + onDescriptor(FileDescriptor{file.getAbstractPath(), file.getAttributes()}); }, [](const SymlinkPair& symlink) {}); } -template +template void collectNonNativeFiles(const std::vector& selectedRows, const TempFileBuffer& tempFileBuf, std::set& workLoad) { for (const FileSystemObject* fsObj : selectedRows) extractFileDescriptor(*fsObj, [&](const FileDescriptor& descr) { - if (!AFS::getNativeItemPath(descr.path)) - if (tempFileBuf.getTempPath(descr).empty()) //TempFileBuffer::createTempFiles() contract! - workLoad.insert(descr); + if (getNativeItemPath(descr.path).empty() && + tempFileBuf.getTempPath(descr).empty()) //TempFileBuffer::createTempFiles() contract! + workLoad.insert(descr); }); } -template +template void invokeCommandLine(const Zstring& commandLinePhrase, //throw FileError bool openWithDefaultAppRequested, const std::vector& selection, const TempFileBuffer& tempFileBuf) { - constexpr SelectedSide side2 = OtherSide::value; + constexpr SelectSide side2 = OtherSide::value; for (const FileSystemObject* fsObj : selection) //context menu calls this function only if selection is not empty! { @@ -1514,13 +1514,15 @@ void invokeCommandLine(const Zstring& commandLinePhrase, //throw FileError Zstring localPath; Zstring localPath2; - if (AFS::getNativeItemPath(basePath)) - localPath = itemPath; //no matter if item exists or not + if (const Zstring& nativePath = getNativeItemPath(fsObj->getAbstractPath()); + !nativePath.empty()) + localPath = nativePath; //no matter if item exists or not else //returns empty if not available (item not existing, error during copy): extractFileDescriptor(*fsObj, [&](const FileDescriptor& descr) { localPath = tempFileBuf.getTempPath(descr); }); - if (AFS::getNativeItemPath(basePath2)) - localPath2 = itemPath2; + if (const Zstring& nativePath = getNativeItemPath(fsObj->getAbstractPath()); + !nativePath.empty()) + localPath2 = nativePath; else extractFileDescriptor(*fsObj, [&](const FileDescriptor& descr) { localPath2 = tempFileBuf.getTempPath(descr); }); @@ -1568,113 +1570,124 @@ void MainDialog::openExternalApplication(const Zstring& commandLinePhrase, bool const bool showInFileBrowserRequested = commandLinePhrase == defaultCfg.externalApps[0].cmdLine; const bool openWithDefaultAppRequested = commandLinePhrase == defaultCfg.externalApps[1].cmdLine; - auto openFolderInFileBrowser = [this](const AbstractPath& folderPath) + try { - try + auto openFolderInFileBrowser = [](const AbstractPath& folderPath) //throw FileError { - openWithDefaultApp(utfTo(AFS::getDisplayPath(folderPath))); //throw FileError - } - catch (const FileError& e) { showNotificationDialog(this, DialogInfoType::error, PopupDialogCfg().setDetailInstructions(e.toString())); } - }; + if (const Zstring& gdriveUrl = getGoogleDriveFolderUrl(folderPath); //throw FileError + !gdriveUrl.empty()) + return openWithDefaultApp(gdriveUrl); //throw FileError + else + openWithDefaultApp(utfTo(AFS::getDisplayPath(folderPath))); //throw FileError + }; - //support fallback instead of an error in this special case - if (showInFileBrowserRequested) - { - if (selectionLeft.size() + selectionRight.size() > 1) //do not open more than one Explorer instance! + //support fallback instead of an error in this special case + if (showInFileBrowserRequested) { - if ((leftSide && !selectionLeft .empty()) || - (!leftSide && selectionRight.empty())) - return openExternalApplication(commandLinePhrase, leftSide, { selectionLeft[0] }, {}); + if (selectionLeft.size() + selectionRight.size() > 1) //do not open more than one Explorer instance! + { + if ((leftSide && !selectionLeft .empty()) || + (!leftSide && selectionRight.empty())) + return openExternalApplication(commandLinePhrase, leftSide, {selectionLeft[0]}, {}); + else + return openExternalApplication(commandLinePhrase, leftSide, {}, {selectionRight[0]}); + } + + //either left or right selection is filled with exactly one item (or no selection at all) + AbstractPath itemPath = getNullPath(); + if (!selectionLeft.empty()) + { + if (selectionLeft[0]->isEmpty()) + return openFolderInFileBrowser(getExistingParentFolder(*selectionLeft[0])); //throw FileError + + itemPath = selectionLeft[0]->getAbstractPath(); + } + else if (!selectionRight.empty()) + { + if (selectionRight[0]->isEmpty()) + return openFolderInFileBrowser(getExistingParentFolder(*selectionRight[0])); //throw FileError + + itemPath = selectionRight[0]->getAbstractPath(); + } else - return openExternalApplication(commandLinePhrase, leftSide, {}, { selectionRight[0] }); + return openFolderInFileBrowser(leftSide ? //throw FileError + createAbstractPath(firstFolderPair_->getValues().folderPathPhraseLeft) : + createAbstractPath(firstFolderPair_->getValues().folderPathPhraseRight)); + + //itemPath != base folder in this context + if (const Zstring& gdriveUrl = getGoogleDriveFolderUrl(*AFS::getParentPath(itemPath)); //throw FileError + !gdriveUrl.empty()) + return openWithDefaultApp(gdriveUrl); //throw FileError } - if (selectionLeft.empty() && selectionRight.empty()) - return openFolderInFileBrowser(leftSide ? - createAbstractPath(firstFolderPair_->getValues().folderPathPhraseLeft) : - createAbstractPath(firstFolderPair_->getValues().folderPathPhraseRight)); - //in this context either left or right selection is filled with exactly one item - if (!selectionLeft.empty()) + //regular command evaluation: + const size_t invokeCount = selectionLeft.size() + selectionRight.size(); + if (invokeCount > EXT_APP_MASS_INVOKE_THRESHOLD) + if (globalCfg_.confirmDlgs.confirmCommandMassInvoke) + { + bool dontAskAgain = false; + switch (showConfirmationDialog(this, DialogInfoType::warning, PopupDialogCfg(). + setTitle(_("Confirm")). + setMainInstructions(replaceCpy(_P("Do you really want to execute the command %y for one item?", + "Do you really want to execute the command %y for %x items?", invokeCount), + L"%y", fmtPath(commandLinePhrase))). + setCheckBox(dontAskAgain, _("&Don't show this warning again")), + _("&Execute"))) + { + case ConfirmationButton::accept: + globalCfg_.confirmDlgs.confirmCommandMassInvoke = !dontAskAgain; + break; + case ConfirmationButton::cancel: + return; + } + } + + std::set nonNativeFiles; + if (contains(commandLinePhrase, Zstr("%local_path%"))) { - if (selectionLeft[0]->isEmpty()) - return openFolderInFileBrowser(getExistingParentFolder(*selectionLeft[0])); + collectNonNativeFiles< SelectSide::left>(selectionLeft, tempFileBuf_, nonNativeFiles); + collectNonNativeFiles(selectionRight, tempFileBuf_, nonNativeFiles); } - else + if (contains(commandLinePhrase, Zstr("%local_path2%"))) { - if (selectionRight[0]->isEmpty()) - return openFolderInFileBrowser(getExistingParentFolder(*selectionRight[0])); + collectNonNativeFiles(selectionLeft, tempFileBuf_, nonNativeFiles); + collectNonNativeFiles< SelectSide::left>(selectionRight, tempFileBuf_, nonNativeFiles); } - } - //regular command evaluation: - const size_t invokeCount = selectionLeft.size() + selectionRight.size(); - if (invokeCount > EXT_APP_MASS_INVOKE_THRESHOLD) - if (globalCfg_.confirmDlgs.confirmCommandMassInvoke) + //##################### create temporary files for non-native paths ###################### + if (!nonNativeFiles.empty()) { - bool dontAskAgain = false; - switch (showConfirmationDialog(this, DialogInfoType::warning, PopupDialogCfg(). - setTitle(_("Confirm")). - setMainInstructions(replaceCpy(_P("Do you really want to execute the command %y for one item?", - "Do you really want to execute the command %y for %x items?", invokeCount), - L"%y", fmtPath(commandLinePhrase))). - setCheckBox(dontAskAgain, _("&Don't show this warning again")), - _("&Execute"))) - { - case ConfirmationButton::accept: - globalCfg_.confirmDlgs.confirmCommandMassInvoke = !dontAskAgain; - break; - case ConfirmationButton::cancel: - return; - } - } + FocusPreserver fp; - std::set nonNativeFiles; - if (contains(commandLinePhrase, Zstr("%local_path%"))) - { - collectNonNativeFiles< LEFT_SIDE>(selectionLeft, tempFileBuf_, nonNativeFiles); - collectNonNativeFiles(selectionRight, tempFileBuf_, nonNativeFiles); - } - if (contains(commandLinePhrase, Zstr("%local_path2%"))) - { - collectNonNativeFiles(selectionLeft, tempFileBuf_, nonNativeFiles); - collectNonNativeFiles< LEFT_SIDE>(selectionRight, tempFileBuf_, nonNativeFiles); - } + disableGuiElements(true /*enableAbort*/); //StatusHandlerTemporaryPanel will internally process Window messages, so avoid unexpected callbacks! + auto app = wxTheApp; //fix lambda/wxWigets/VC fuck up + ZEN_ON_SCOPE_EXIT(app->Yield(); enableGuiElements()); //ui update before enabling buttons again: prevent strange behaviour of delayed button clicks - //##################### create temporary files for non-native paths ###################### - if (!nonNativeFiles.empty()) - { - FocusPreserver fp; + const auto& guiCfg = getConfig(); - disableGuiElements(true /*enableAbort*/); //StatusHandlerTemporaryPanel will internally process Window messages, so avoid unexpected callbacks! - auto app = wxTheApp; //fix lambda/wxWigets/VC fuck up - ZEN_ON_SCOPE_EXIT(app->Yield(); enableGuiElements()); //ui update before enabling buttons again: prevent strange behaviour of delayed button clicks + StatusHandlerTemporaryPanel statusHandler(*this, std::chrono::system_clock::now() /*startTime*/, + false /*ignoreErrors*/, + guiCfg.mainCfg.autoRetryCount, + guiCfg.mainCfg.autoRetryDelay); //handle status display and error messages + try + { + tempFileBuf_.createTempFiles(nonNativeFiles, statusHandler); //throw AbortProcess + //"clearSelection" not needed/desired + } + catch (AbortProcess&) {} - const auto& guiCfg = getConfig(); + const StatusHandlerTemporaryPanel::Result r = statusHandler.reportResults(); //noexcept + setLastOperationLog(r.summary, r.errorLog); - StatusHandlerTemporaryPanel statusHandler(*this, std::chrono::system_clock::now() /*startTime*/, - false /*ignoreErrors*/, - guiCfg.mainCfg.autoRetryCount, - guiCfg.mainCfg.autoRetryDelay); //handle status display and error messages - try - { - tempFileBuf_.createTempFiles(nonNativeFiles, statusHandler); //throw AbortProcess - //"clearSelection" not needed/desired - } - catch (AbortProcess&) {} - - const StatusHandlerTemporaryPanel::Result r = statusHandler.reportResults(); //noexcept - setLastOperationLog(r.summary, r.errorLog); + if (r.summary.syncResult == SyncResult::aborted) + return; - if (r.summary.syncResult == SyncResult::aborted) - return; + //updateGui(); -> not needed + } + //######################################################################################## - //updateGui(); -> not needed - } - //######################################################################################## - try - { - invokeCommandLine< LEFT_SIDE>(commandLinePhrase, openWithDefaultAppRequested, selectionLeft, tempFileBuf_); //throw FileError - invokeCommandLine(commandLinePhrase, openWithDefaultAppRequested, selectionRight, tempFileBuf_); // + invokeCommandLine< SelectSide::left>(commandLinePhrase, openWithDefaultAppRequested, selectionLeft, tempFileBuf_); //throw FileError + invokeCommandLine(commandLinePhrase, openWithDefaultAppRequested, selectionRight, tempFileBuf_); // } catch (const FileError& e) { showNotificationDialog(this, DialogInfoType::error, PopupDialogCfg().setDetailInstructions(e.toString())); } } @@ -1898,7 +1911,7 @@ void MainDialog::onTreeKeyEvent(wxKeyEvent& event) { case 'C': case WXK_INSERT: //CTRL + C || CTRL + INS - copySelectionToClipboard({ m_gridOverview }); + copySelectionToClipboard({m_gridOverview}); return; } @@ -2142,16 +2155,13 @@ void MainDialog::onLocalKeyEvent(wxKeyEvent& event) //process key events without break; case WXK_ESCAPE: //let's do something useful and hide the log panel - { - const wxWindow* focus = wxWindow::FindFocus(); - if (!isComponentOf(focus, m_panelSearch) && //search panel also handles ESC! + if (!isComponentOf(wxWindow::FindFocus(), m_panelSearch) && //search panel also handles ESC! m_panelLog->IsEnabled()) { if (auiMgr_.GetPane(m_panelLog).IsShown()) //else: let it "ding" return showLogPanel(false /*show*/); } - } - break; + break; } event.Skip(); @@ -2308,7 +2318,7 @@ void MainDialog::onTreeGridContext(GridContextMenuEvent& event) menu.addSeparator(); menu.addItem(_("&Synchronize selection") + L"\tEnter", [&] { startSyncForSelecction(selection); }, loadImage("start_sync_selection_sicon"), selectionContainsItemsToSync); //---------------------------------------------------------------------------------------------------- - const bool haveNonEmptyItems = std::any_of(selection.begin(), selection.end(), [](const FileSystemObject* fsObj) { return !fsObj->isEmpty() || !fsObj->isEmpty(); }); + const bool haveNonEmptyItems = std::any_of(selection.begin(), selection.end(), [](const FileSystemObject* fsObj) { return !fsObj->isEmpty() || !fsObj->isEmpty(); }); //menu.addSeparator(); //menu.addItem(_("&Copy to...") + L"\tCtrl+T", [&] { copyToAlternateFolder(selection, selection); }, wxNullImage, haveNonEmptyItems); //---------------------------------------------------------------------------------------------------- @@ -2485,8 +2495,8 @@ void MainDialog::onGridContextRim(const std::vector& selectio } } //---------------------------------------------------------------------------------------------------- - const bool haveNonEmptyItemsL = std::any_of(selectionLeft .begin(), selectionLeft .end(), [](const FileSystemObject* fsObj) { return !fsObj->isEmpty(); }); - const bool haveNonEmptyItemsR = std::any_of(selectionRight.begin(), selectionRight.end(), [](const FileSystemObject* fsObj) { return !fsObj->isEmpty(); }); + const bool haveNonEmptyItemsL = std::any_of(selectionLeft .begin(), selectionLeft .end(), [](const FileSystemObject* fsObj) { return !fsObj->isEmpty< SelectSide::left>(); }); + const bool haveNonEmptyItemsR = std::any_of(selectionRight.begin(), selectionRight.end(), [](const FileSystemObject* fsObj) { return !fsObj->isEmpty(); }); menu.addSeparator(); menu.addItem(_("&Copy to...") + L"\tCtrl+T", [&] { copyToAlternateFolder(selectionLeft, selectionRight); }, wxNullImage, haveNonEmptyItemsL || haveNonEmptyItemsR); @@ -2594,7 +2604,7 @@ void MainDialog::onGridLabelContextC(GridLabelClickEvent& event) const GridViewType viewType = m_bpButtonViewType->isActive() ? GridViewType::action : GridViewType::difference; menu.addItem(_("Difference") + (viewType != GridViewType::difference ? L"\tF11" : L""), [&] { setGridViewType(GridViewType::difference); }, greyScaleIfDisabled(loadImage("compare_sicon" ), viewType == GridViewType::difference)); menu.addItem(_("Action") + (viewType != GridViewType::action ? L"\tF11" : L""), [&] { setGridViewType(GridViewType::action ); }, greyScaleIfDisabled(loadImage("start_sync_sicon"), viewType == GridViewType::action)); - menu.popup(*m_gridMainC, { m_gridMainC->GetSize().x, 0 }); + menu.popup(*m_gridMainC, {m_gridMainC->GetSize().x, 0}); } @@ -2703,7 +2713,7 @@ void MainDialog::onGridLabelContextRim(GridLabelClickEvent& event, bool leftSide menu.addItem(_("Select time span..."), selectTimeSpan); } //-------------------------------------------------------------------------------------------------------- - menu.popup(grid, { event.mousePos_.x, grid.getColumnLabelHeight() }); + menu.popup(grid, {event.mousePos_.x, grid.getColumnLabelHeight()}); //event.Skip(); } @@ -2800,7 +2810,7 @@ void MainDialog::onCompSettingsContext(wxEvent& event) addVariantItem(CompareVariant::content, "cmp_content"); addVariantItem(CompareVariant::size, "cmp_size"); - menu.popup(*m_bpButtonCmpContext, { m_bpButtonCmpContext->GetSize().x, 0 }); + menu.popup(*m_bpButtonCmpContext, {m_bpButtonCmpContext->GetSize().x, 0}); } @@ -2827,7 +2837,7 @@ void MainDialog::onSyncSettingsContext(wxEvent& event) addVariantItem(SyncVariant::update, "sync_update"); addVariantItem(SyncVariant::custom, "sync_custom"); - menu.popup(*m_bpButtonSyncContext, { m_bpButtonSyncContext->GetSize().x, 0 }); + menu.popup(*m_bpButtonSyncContext, {m_bpButtonSyncContext->GetSize().x, 0}); } @@ -3004,7 +3014,7 @@ bool MainDialog::trySaveConfig(const Zstring* guiCfgPath) //return true if saved try { writeConfig(guiCfg, cfgFilePath); //throw FileError - setLastUsedConfig(guiCfg, { cfgFilePath }); + setLastUsedConfig(guiCfg, {cfgFilePath}); flashStatusInformation(_("Configuration saved")); return true; @@ -3086,7 +3096,7 @@ bool MainDialog::trySaveBatchConfig(const Zstring* batchCfgPath) try { writeConfig(batchCfg, cfgFilePath); //throw FileError - setLastUsedConfig(guiCfg, { cfgFilePath }); //[!] behave as if we had saved guiCfg + setLastUsedConfig(guiCfg, {cfgFilePath}); //[!] behave as if we had saved guiCfg flashStatusInformation(_("Configuration saved")); return true; @@ -3351,7 +3361,7 @@ void MainDialog::renameSelectedCfgHistoryItem() //FIRST: 1. consolidate unsaved changes using the *old* config file name, if any! //2. get rid of multiple-selection if exists 3. load cfg to allow non-failing(!) setLastUsedConfig() below - if (!loadConfiguration({ cfgPathOld })) + if (!loadConfiguration({cfgPathOld})) return; //error/cancel const Zstring fileName = afterLast(cfgPathOld, FILE_NAME_SEPARATOR, IfNotFoundReturn::all); @@ -3394,11 +3404,11 @@ void MainDialog::renameSelectedCfgHistoryItem() continue; } - cfggrid::getDataView(*m_gridCfgHistory).removeItems({ cfgPathOld }); + cfggrid::getDataView(*m_gridCfgHistory).removeItems({cfgPathOld}); m_gridCfgHistory->Refresh(); //grid size changed => clears selection! - //keep current cfg and just swap the file name: see previous "loadConfiguration({ cfgPathOld }"! - setLastUsedConfig(lastSavedCfg_, { cfgPathNew }); + //keep current cfg and just swap the file name: see previous "loadConfiguration({cfgPathOld}"! + setLastUsedConfig(lastSavedCfg_, {cfgPathNew}); return; } } @@ -3478,13 +3488,13 @@ void MainDialog::onCfgGridContext(GridContextMenuEvent& event) submenu.addItem(name, applyBackColor, bmpSquare.ConvertToImage(), !selectedRows.empty()); }; addColorOption(wxNullColour, L'(' + _("&Default") + L')'); //meta options should be enclosed in parentheses - addColorOption({ 0xff, 0xd8, 0xcb }, _("Red")); - addColorOption({ 0xff, 0xf9, 0x99 }, _("Yellow")); - addColorOption({ 0xcc, 0xff, 0x99 }, _("Green")); - addColorOption({ 0xcc, 0xff, 0xff }, _("Cyan")); - addColorOption({ 0xcc, 0xcc, 0xff }, _("Blue")); - addColorOption({ 0xf2, 0xcb, 0xff }, _("Purple")); - addColorOption({ 0xdd, 0xdd, 0xdd }, _("Grey")); + addColorOption({0xff, 0xd8, 0xcb}, _("Red")); + addColorOption({0xff, 0xf9, 0x99}, _("Yellow")); + addColorOption({0xcc, 0xff, 0x99}, _("Green")); + addColorOption({0xcc, 0xff, 0xff}, _("Cyan")); + addColorOption({0xcc, 0xcc, 0xff}, _("Blue")); + addColorOption({0xf2, 0xcb, 0xff}, _("Purple")); + addColorOption({0xdd, 0xdd, 0xdd}, _("Grey")); menu.addSubmenu(_("Background color"), submenu, loadImage("color_sicon"), !selectedRows.empty()); menu.addSeparator(); @@ -3586,7 +3596,7 @@ void MainDialog::onCfgGridLabelContext(GridLabelClickEvent& event) menu.addItem(_("Highlight..."), setCfgHighlight); //-------------------------------------------------------------------------------------------------------- - menu.popup(*m_gridCfgHistory, { event.mousePos_.x, m_gridCfgHistory->getColumnLabelHeight() }); + menu.popup(*m_gridCfgHistory, {event.mousePos_.x, m_gridCfgHistory->getColumnLabelHeight()}); //event.Skip(); } @@ -3867,7 +3877,7 @@ void MainDialog::onGlobalFilterContext(wxEvent& event) menu.addItem( _("Copy"), copyFilter, wxNullImage, !isNullFilter(currentCfg_.mainCfg.globalFilter)); menu.addItem( _("Paste"), pasteFilter, wxNullImage, filterCfgOnClipboard_.get() != nullptr); - menu.popup(*m_bpButtonFilterContext, { m_bpButtonFilterContext->GetSize().x, 0 }); + menu.popup(*m_bpButtonFilterContext, {m_bpButtonFilterContext->GetSize().x, 0}); } @@ -3922,7 +3932,7 @@ void MainDialog::onViewTypeContextMouse(wxMouseEvent& event) menu.addItem(_("Difference") + (viewType != GridViewType::difference ? L"\tF11" : L""), [&] { setGridViewType(GridViewType::difference); }, greyScaleIfDisabled(loadImage("compare_sicon" ), viewType == GridViewType::difference)); menu.addItem(_("Action") + (viewType != GridViewType::action ? L"\tF11" : L""), [&] { setGridViewType(GridViewType::action ); }, greyScaleIfDisabled(loadImage("start_sync_sicon"), viewType == GridViewType::action)); - menu.popup(*m_bpButtonViewType, { m_bpButtonViewType->GetSize().x, 0 }); + menu.popup(*m_bpButtonViewType, {m_bpButtonViewType->GetSize().x, 0}); } @@ -3961,7 +3971,7 @@ void MainDialog::onViewFilterContext(wxEvent& event) }; menu.addItem( _("Save as default"), saveDefault, loadImage("cfg_save_sicon")); - menu.popup(*m_bpButtonViewFilterContext, { m_bpButtonViewFilterContext->GetSize().x, 0 }); + menu.popup(*m_bpButtonViewFilterContext, {m_bpButtonViewFilterContext->GetSize().x, 0}); } @@ -4166,12 +4176,12 @@ void MainDialog::updateStatistics() const SyncStatistics st(folderCmp_); setValue(*m_staticTextData, st.getBytesToProcess() == 0, formatFilesizeShort(st.getBytesToProcess()), *m_bitmapData, "data"); - setIntValue(*m_staticTextCreateLeft, st.createCount< LEFT_SIDE>(), *m_bitmapCreateLeft, "so_create_left_sicon"); - setIntValue(*m_staticTextUpdateLeft, st.updateCount< LEFT_SIDE>(), *m_bitmapUpdateLeft, "so_update_left_sicon"); - setIntValue(*m_staticTextDeleteLeft, st.deleteCount< LEFT_SIDE>(), *m_bitmapDeleteLeft, "so_delete_left_sicon"); - setIntValue(*m_staticTextCreateRight, st.createCount(), *m_bitmapCreateRight, "so_create_right_sicon"); - setIntValue(*m_staticTextUpdateRight, st.updateCount(), *m_bitmapUpdateRight, "so_update_right_sicon"); - setIntValue(*m_staticTextDeleteRight, st.deleteCount(), *m_bitmapDeleteRight, "so_delete_right_sicon"); + setIntValue(*m_staticTextCreateLeft, st.createCount< SelectSide::left>(), *m_bitmapCreateLeft, "so_create_left_sicon"); + setIntValue(*m_staticTextUpdateLeft, st.updateCount< SelectSide::left>(), *m_bitmapUpdateLeft, "so_update_left_sicon"); + setIntValue(*m_staticTextDeleteLeft, st.deleteCount< SelectSide::left>(), *m_bitmapDeleteLeft, "so_delete_left_sicon"); + setIntValue(*m_staticTextCreateRight, st.createCount(), *m_bitmapCreateRight, "so_create_right_sicon"); + setIntValue(*m_staticTextUpdateRight, st.updateCount(), *m_bitmapUpdateRight, "so_update_right_sicon"); + setIntValue(*m_staticTextDeleteRight, st.deleteCount(), *m_bitmapDeleteRight, "so_delete_right_sicon"); m_panelStatistics->Layout(); m_panelStatistics->Refresh(); //fix small mess up on RTL layout @@ -4270,13 +4280,15 @@ void MainDialog::onStartSync(wxCommandEvent& event) std::set folderPathsToLock; for (auto it = begin(folderCmp_); it != end(folderCmp_); ++it) { - if (it->isAvailable()) //do NOT check directory existence again! - if (std::optional nativeFolderPath = AFS::getNativeItemPath(it->getAbstractPath())) //restrict directory locking to native paths until further - folderPathsToLock.insert(*nativeFolderPath); - - if (it->isAvailable()) - if (std::optional nativeFolderPath = AFS::getNativeItemPath(it->getAbstractPath())) - folderPathsToLock.insert(*nativeFolderPath); + if (it->isAvailable()) //do NOT check directory existence again! + if (const Zstring& nativePath = getNativeItemPath(it->getAbstractPath()); //restrict directory locking to native paths until further + !nativePath.empty()) + folderPathsToLock.insert(nativePath); + + if (it->isAvailable()) + if (const Zstring& nativePath = getNativeItemPath(it->getAbstractPath()); + !nativePath.empty()) + folderPathsToLock.insert(nativePath); } dirLocks = std::make_unique(folderPathsToLock, globalCfg_.warnDlgs.warnDirectoryLockFailed, statusHandler); //throw AbortProcess } @@ -4497,7 +4509,7 @@ void MainDialog::startSyncForSelecction(const std::vector& se void MainDialog::updateConfigLastRunStats(time_t lastRunTime, SyncResult result, const AbstractPath& logFilePath) { - cfggrid::getDataView(*m_gridCfgHistory).setLastRunStats(activeConfigFiles_, { lastRunTime, result, logFilePath }); + cfggrid::getDataView(*m_gridCfgHistory).setLastRunStats(activeConfigFiles_, {lastRunTime, result, logFilePath}); //re-apply selection: sort order changed if sorted by last sync time cfggrid::addAndSelect(*m_gridCfgHistory, activeConfigFiles_, false /*scrollToSelection*/); @@ -4576,21 +4588,14 @@ void MainDialog::setLastOperationLog(const ProcessSummary& summary, const std::s void MainDialog::onToggleLog(wxCommandEvent& event) { - const bool show = !auiMgr_.GetPane(m_panelLog).IsShown(); - showLogPanel(show); - if (show) - logPanel_->SetFocus(); + showLogPanel(!auiMgr_.GetPane(m_panelLog).IsShown()); } void MainDialog::showLogPanel(bool show) { - warn_static("add 'FocusPreserver fp' when showing andclosing log!? similar implementation like focusIdAfterSearch_") - - - - wxAuiPaneInfo& logPane = auiMgr_.GetPane(m_panelLog); - if (show != logPane.IsShown()) + if (wxAuiPaneInfo& logPane = auiMgr_.GetPane(m_panelLog); + logPane.IsShown() != show) { if (!show) { @@ -4602,7 +4607,23 @@ void MainDialog::showLogPanel(bool show) logPane.Show(show); auiMgr_.Update(); - m_panelLog->Refresh(); //macOS: fix background corruption for the statistics boxes (call *after* wxAuiManager::Update() + m_panelLog->Refresh(); //macOS: fix background corruption for the statistics boxes; call *after* wxAuiManager::Update() + } + + if (show) + { + if (wxWindow* focus = wxWindow::FindFocus()) //restore when closing panel! + if (!isComponentOf(focus, m_panelLog)) + focusAfterCloseLog_ = focus->GetId(); + + logPanel_->SetFocus(); + } + else + { + if (isComponentOf(wxWindow::FindFocus(), m_panelLog)) + if (wxWindow* oldFocusWin = wxWindow::FindWindowById(focusAfterCloseLog_)) + oldFocusWin->SetFocus(); + focusAfterCloseLog_ = wxID_ANY; } } @@ -4614,7 +4635,7 @@ void MainDialog::onGridDoubleClickRim(GridClickEvent& event, bool leftSide) std::vector selectionLeft; std::vector selectionRight; if (FileSystemObject* fsObj = filegrid::getDataView(*m_gridMainC).getFsObject(event.row_)) //selection must be a list of BOUND pointers! - (leftSide ? selectionLeft : selectionRight) = { fsObj }; + (leftSide ? selectionLeft : selectionRight) = {fsObj}; openExternalApplication(globalCfg_.externalApps[0].cmdLine, leftSide, selectionLeft, selectionRight); } @@ -5007,7 +5028,7 @@ void MainDialog::onSearchGridEnter(wxCommandEvent& event) void MainDialog::onHideSearchPanel(wxCommandEvent& event) { - hideFindPanel(); + showFindPanel(false /*show*/); } @@ -5020,43 +5041,39 @@ void MainDialog::onSearchPanelKeyPressed(wxKeyEvent& event) startFindNext(true /*searchAscending*/); return; case WXK_ESCAPE: - hideFindPanel(); + showFindPanel(false /*show*/); return; } event.Skip(); } -void MainDialog::showFindPanel() //CTRL + F or F3 with empty search phrase +void MainDialog::showFindPanel(bool show) //CTRL + F or F3 with empty search phrase { - if (!auiMgr_.GetPane(m_panelSearch).IsShown()) + if (auiMgr_.GetPane(m_panelSearch).IsShown() != show) { - auiMgr_.GetPane(m_panelSearch).Show(); + auiMgr_.GetPane(m_panelSearch).Show(show); auiMgr_.Update(); } - m_textCtrlSearchTxt->SelectAll(); - - if (wxWindow* focus = wxWindow::FindFocus()) //restore when closing panel! - if (!isComponentOf(focus, m_panelSearch)) - focusIdAfterSearch_ = focus->GetId(); - //don't save wxWindow* to arbitrary window: it might not exist anymore when hideFindPanel() uses it!!! (e.g. some folder pair panel) - - m_textCtrlSearchTxt->SetFocus(); -} + if (show) + { + m_textCtrlSearchTxt->SelectAll(); + if (wxWindow* focus = wxWindow::FindFocus()) //restore when closing panel! + if (!isComponentOf(focus, m_panelSearch)) + focusAfterCloseSearch_ = focus->GetId(); -void MainDialog::hideFindPanel() -{ - if (auiMgr_.GetPane(m_panelSearch).IsShown()) - { - auiMgr_.GetPane(m_panelSearch).Hide(); - auiMgr_.Update(); + m_textCtrlSearchTxt->SetFocus(); } + else + { + if (isComponentOf(wxWindow::FindFocus(), m_panelSearch)) + if (wxWindow* oldFocusWin = wxWindow::FindWindowById(focusAfterCloseSearch_)) + oldFocusWin->SetFocus(); - if (wxWindow* oldFocusWin = wxWindow::FindWindowById(focusIdAfterSearch_)) - oldFocusWin->SetFocus(); - focusIdAfterSearch_ = wxID_ANY; + focusAfterCloseSearch_ = wxID_ANY; + } } @@ -5065,14 +5082,14 @@ void MainDialog::startFindNext(bool searchAscending) //F3 or ENTER in m_textCtrl const std::wstring& searchString = utfTo(trimCpy(m_textCtrlSearchTxt->GetValue())); if (searchString.empty()) - showFindPanel(); + showFindPanel(true /*show*/); else { Grid* grid1 = m_gridMainL; Grid* grid2 = m_gridMainR; wxWindow* focus = wxWindow::FindFocus(); - if ((isComponentOf(focus, m_panelSearch) ? focusIdAfterSearch_ : focus->GetId()) == m_gridMainR->getMainWin().GetId()) + if ((isComponentOf(focus, m_panelSearch) ? focusAfterCloseSearch_ : focus->GetId()) == m_gridMainR->getMainWin().GetId()) std::swap(grid1, grid2); //select side to start search at grid cursor position wxBeginBusyCursor(wxHOURGLASS_CURSOR); @@ -5080,21 +5097,21 @@ void MainDialog::startFindNext(bool searchAscending) //F3 or ENTER in m_textCtrl m_checkBoxMatchCase->GetValue(), searchAscending); //parameter owned by GUI, *not* globalCfg structure! => we should better implement a getGlocalCfg()! wxEndBusyCursor(); - if (Grid* grid = const_cast(result.first)) //grid wasn't const when passing to findAndSelectNext(), so this is safe + if (Grid* grid = const_cast(result.first)) //grid wasn't const when passing to findAndSelectNext(), so this is legal { assert(result.second >= 0); filegrid::setScrollMaster(*grid); grid->setGridCursor(result.second, GridEventPolicy::allow); - focusIdAfterSearch_ = grid->getMainWin().GetId(); + focusAfterCloseSearch_ = grid->getMainWin().GetId(); if (!isComponentOf(wxWindow::FindFocus(), m_panelSearch)) grid->getMainWin().SetFocus(); } else { - showFindPanel(); + showFindPanel(true /*show*/); showNotificationDialog(this, DialogInfoType::info, PopupDialogCfg(). setTitle(_("Find")). setMainInstructions(replaceCpy(_("Cannot find %x"), L"%x", fmtPath(searchString)))); @@ -5105,7 +5122,7 @@ void MainDialog::startFindNext(bool searchAscending) //F3 or ENTER in m_textCtrl void MainDialog::onTopFolderPairAdd(wxCommandEvent& event) { - insertAddFolderPair({ LocalPairConfig() }, 0); + insertAddFolderPair({LocalPairConfig()}, 0); moveAddFolderPairUp(0); } @@ -5178,12 +5195,12 @@ void MainDialog::onShowFolderPairOptions(wxEvent& event) const ptrdiff_t pos = it - additionalFolderPairs_.begin(); ContextMenu menu; - menu.addItem(_("Add folder pair"), [this, pos] { insertAddFolderPair({ LocalPairConfig() }, pos); }, loadImage("item_add_sicon")); + menu.addItem(_("Add folder pair"), [this, pos] { insertAddFolderPair({LocalPairConfig()}, pos); }, loadImage("item_add_sicon")); menu.addSeparator(); menu.addItem(_("Move up" ) + L"\tAlt+Page Up", [this, pos] { moveAddFolderPairUp(pos); }, loadImage("move_up_sicon")); menu.addItem(_("Move down") + L"\tAlt+Page Down", [this, pos] { moveAddFolderPairUp(pos + 1); }, loadImage("move_down_sicon"), pos + 1 < makeSigned(additionalFolderPairs_.size())); - menu.popup(*(*it)->m_bpButtonFolderPairOptions, { (*it)->m_bpButtonFolderPairOptions->GetSize().x, 0 }); + menu.popup(*(*it)->m_bpButtonFolderPairOptions, {(*it)->m_bpButtonFolderPairOptions->GetSize().x, 0}); break; } } @@ -5228,27 +5245,23 @@ void MainDialog::onAddFolderPairKeyEvent(wxKeyEvent& event) { case WXK_PAGEUP: //Alt + Page Up case WXK_NUMPAD_PAGEUP: - { - const ptrdiff_t pos = getAddFolderPairPos(); - if (pos >= 0) + if (const ptrdiff_t pos = getAddFolderPairPos(); + pos >= 0) { moveAddFolderPairUp(pos); (pos == 0 ? m_folderPathLeft : additionalFolderPairs_[pos - 1]->m_folderPathLeft)->SetFocus(); } - } - return; + return; case WXK_PAGEDOWN: //Alt + Page Down case WXK_NUMPAD_PAGEDOWN: - { - const ptrdiff_t pos = getAddFolderPairPos(); - if (0 <= pos && pos + 1 < makeSigned(additionalFolderPairs_.size())) + if (const ptrdiff_t pos = getAddFolderPairPos(); + 0 <= pos && pos + 1 < makeSigned(additionalFolderPairs_.size())) { moveAddFolderPairUp(pos + 1); additionalFolderPairs_[pos + 1]->m_folderPathLeft->SetFocus(); } - } - return; + return; } event.Skip(); @@ -5507,8 +5520,8 @@ void MainDialog::onMenuExportFileList(wxCommandEvent& event) header += fmtValue(_("Folder Pairs")) + LINE_BREAK; std::for_each(begin(folderCmp_), end(folderCmp_), [&](BaseFolderPair& baseFolder) { - header += fmtValue(AFS::getDisplayPath(baseFolder.getAbstractPath< LEFT_SIDE>())) + CSV_SEP; - header += fmtValue(AFS::getDisplayPath(baseFolder.getAbstractPath())) + LINE_BREAK; + header += fmtValue(AFS::getDisplayPath(baseFolder.getAbstractPath< SelectSide::left>())) + CSV_SEP; + header += fmtValue(AFS::getDisplayPath(baseFolder.getAbstractPath())) + LINE_BREAK; }); header += LINE_BREAK; diff --git a/FreeFileSync/Source/ui/main_dlg.h b/FreeFileSync/Source/ui/main_dlg.h index e22bad6f..dc0ea8d1 100644 --- a/FreeFileSync/Source/ui/main_dlg.h +++ b/FreeFileSync/Source/ui/main_dlg.h @@ -251,8 +251,7 @@ private: void applyFilterConfig(); void applySyncDirections(); - void showFindPanel(); //CTRL + F - void hideFindPanel(); + void showFindPanel(bool show); //CTRL + F void startFindNext(bool searchAscending); //F3 void resetLayout(); @@ -266,7 +265,7 @@ private: void onMenuOptions (wxCommandEvent& event) override; void onMenuExportFileList (wxCommandEvent& event) override; void onMenuResetLayout (wxCommandEvent& event) override { resetLayout(); } - void onMenuFindItem (wxCommandEvent& event) override { showFindPanel(); } //CTRL + F + void onMenuFindItem (wxCommandEvent& event) override { showFindPanel(true /*show*/); } //CTRL + F void onMenuCheckVersion (wxCommandEvent& event) override; void onMenuCheckVersionAutomatically(wxCommandEvent& event) override; void onMenuAbout (wxCommandEvent& event) override; @@ -347,7 +346,9 @@ private: std::unique_ptr filterCfgOnClipboard_; //copy/paste of filter config - wxWindowID focusIdAfterSearch_ = wxID_ANY; //used to restore focus after search panel is closed + wxWindowID focusAfterCloseLog_ = wxID_ANY; // + wxWindowID focusAfterCloseSearch_ = wxID_ANY; //restore focus after panel is closed + //don't save wxWindow* to arbitrary window: might not exist anymore when hideFindPanel() uses it!!! (e.g. some folder pair panel) //mitigate reentrancy: bool localKeyEventsEnabled_ = true; diff --git a/FreeFileSync/Source/ui/progress_indicator.cpp b/FreeFileSync/Source/ui/progress_indicator.cpp index 0ab39dab..fc97858e 100644 --- a/FreeFileSync/Source/ui/progress_indicator.cpp +++ b/FreeFileSync/Source/ui/progress_indicator.cpp @@ -47,17 +47,17 @@ constexpr std::chrono::seconds GRAPH_TOTAL_TIME_UPDATE_INTERVAL(2); const size_t PROGRESS_GRAPH_SAMPLE_SIZE_MAX = 2'500'000; //sizeof(single node) worst case ~ 3 * 8 byte ptr + 16 byte key/value = 40 byte -inline wxColor getColorBytes() { return { 111, 255, 99 }; } //light green -inline wxColor getColorItems() { return { 127, 147, 255 }; } //light blue +inline wxColor getColorBytes() { return {111, 255, 99}; } //light green +inline wxColor getColorItems() { return {127, 147, 255}; } //light blue -inline wxColor getColorBytesRim() { return { 20, 200, 0 }; } //medium green -inline wxColor getColorItemsRim() { return { 90, 120, 255 }; } //medium blue +inline wxColor getColorBytesRim() { return {20, 200, 0}; } //medium green +inline wxColor getColorItemsRim() { return {90, 120, 255}; } //medium blue -//inline wxColor getColorBytesFaint() { return { 205, 255, 202 }; } //faint green -//inline wxColor getColorItemsFaint() { return { 198, 206, 255 }; } //faint blue +//inline wxColor getColorBytesFaint() { return {205, 255, 202}; } //faint green +//inline wxColor getColorItemsFaint() { return {198, 206, 255}; } //faint blue -inline wxColor getColorBytesDark() { return { 12, 128, 0 }; } //dark green -inline wxColor getColorItemsDark() { return { 53, 25, 255 }; } //dark blue +inline wxColor getColorBytesDark() { return {12, 128, 0}; } //dark green +inline wxColor getColorItemsDark() { return {53, 25, 255}; } //dark blue inline wxColor getColorLightGrey() { return {0xf2, 0xf2, 0xf2}; } inline wxColor getColorDarkGrey () { return {0x8f, 0x8f, 0x8f}; } @@ -95,7 +95,7 @@ public: void setFraction(double fraction) { fraction_ = fraction; } //value between [0, 1] private: - std::pair getRangeX() const override { return { 0, 1 }; } + std::pair getRangeX() const override { return {0, 1}; } std::vector getPoints(double minX, double maxX, const wxSize& areaSizePx) const override { @@ -104,10 +104,10 @@ private: return { - { 0, yHigh }, - { fraction_, yHigh }, - { fraction_, yLow }, - { 0, yLow }, + {0, yHigh}, + {fraction_, yHigh}, + {fraction_, yLow }, + {0, yLow }, }; } @@ -117,14 +117,14 @@ private: class CurveDataProgressSeparatorLine : public CurveData { - std::pair getRangeX() const override { return { 0, 1 }; } + std::pair getRangeX() const override { return {0, 1}; } std::vector getPoints(double minX, double maxX, const wxSize& areaSizePx) const override { return { - { 0, 1 }, - { 1, 1 }, + {0, 1}, + {1, 1}, }; } }; @@ -168,13 +168,13 @@ private: const Statistics* syncStat_ = nullptr; //only bound while sync is running std::unique_ptr taskbar_; - PerfCheck perf_{ WINDOW_REMAINING_TIME, WINDOW_BYTES_PER_SEC }; //estimate remaining time + PerfCheck perf_{WINDOW_REMAINING_TIME, WINDOW_BYTES_PER_SEC}; //estimate remaining time std::chrono::nanoseconds timeLastSpeedEstimate_ = std::chrono::seconds(-100); //used for calculating intervals between showing and collecting perf samples //initial value: just some big number - std::shared_ptr curveDataBytes_{ std::make_shared(true /*drawTop*/) }; - std::shared_ptr curveDataItems_{ std::make_shared(false /*drawTop*/) }; + std::shared_ptr curveDataBytes_{std::make_shared(true /*drawTop*/)}; + std::shared_ptr curveDataItems_{std::make_shared(false /*drawTop*/)}; bool ignoreErrors_ = false; }; @@ -422,14 +422,14 @@ public: { assert(!samples_.empty() || (lastSample_ == std::pair())); - lastSample_ = { timeElapsed, value }; + lastSample_ = {timeElapsed, value}; //allow for at most one sample per 100ms (handles duplicate inserts, too!) => this is unrelated to UI_UPDATE_INTERVAL! if (!samples_.empty()) //always unconditionally insert first sample! if (numeric::dist(timeElapsed, samples_.rbegin()->first) < std::chrono::milliseconds(100)) return; - samples_.insert(samples_.end(), { timeElapsed, value }); //time is "expected" to be monotonously ascending + samples_.insert(samples_.end(), {timeElapsed, value}); //time is "expected" to be monotonously ascending //documentation differs about whether "hint" should be before or after the to be inserted element! //however "std::map<>::end()" is interpreted correctly by GCC and VS2010 @@ -452,8 +452,8 @@ private: upperEndMs += 0.05 *(upperEndMs - samples.begin()->first); */ - return { std::chrono::duration(samples_.begin()->first).count(), //need not start with 0, e.g. "binary comparison, graph reset, followed by sync" - std::chrono::duration(upperEnd).count() }; + return {std::chrono::duration(samples_.begin()->first).count(), //need not start with 0, e.g. "binary comparison, graph reset, followed by sync" + std::chrono::duration(upperEnd).count()}; } std::optional getLessEq(double x) const override //x: seconds since begin @@ -504,14 +504,14 @@ public: double getTotalTime() const { return x2_; } private: - std::pair getRangeX() const override { return { x1_, x2_ }; } + std::pair getRangeX() const override { return {x1_, x2_}; } std::vector getPoints(double minX, double maxX, const wxSize& areaSizePx) const override { return { - { x1_, y1_ }, - { x2_, y2_ }, + {x1_, y1_}, + {x2_, y2_}, }; } @@ -529,14 +529,14 @@ public: void setTime(double x) { x_ = x; } private: - std::pair getRangeX() const override { return { x_, x_ }; } + std::pair getRangeX() const override { return {x_, x_}; } std::vector getPoints(double minX, double maxX, const wxSize& areaSizePx) const override { return { - { x_, y_ }, - { x_, 0 }, + {x_, y_}, + {x_, 0 }, }; } @@ -564,7 +564,7 @@ struct LabelFormatterBytes : public LabelFormatter return 0; const double a = bytesProposed / e; //bytesProposed = a * 2^k with a in [1, 2) assert(1 <= a && a < 2); - const double steps[] = { 1, 2 }; + const double steps[] = {1, 2}; return e * numeric::nearMatch(a, std::begin(steps), std::end(steps)); } @@ -578,7 +578,7 @@ struct LabelFormatterItemCount : public LabelFormatter { itemsProposed *= stretchDefaultBlockSize; //enlarge block default size - const double steps[] = { 1, 2, 5, 10 }; + const double steps[] = {1, 2, 5, 10}; if (itemsProposed <= 10) return numeric::nearMatch(itemsProposed, std::begin(steps), std::end(steps)); //like nextNiceNumber(), but without the 2.5 step! return nextNiceNumber(itemsProposed); @@ -598,11 +598,11 @@ struct LabelFormatterTimeElapsed : public LabelFormatter double getOptimalBlockSize(double secProposed) const override { //5 sec minimum block size - const double stepsSec[] = { 5, 10, 20, 30, 60 }; //nice numbers for seconds + const double stepsSec[] = {5, 10, 20, 30, 60}; //nice numbers for seconds if (secProposed <= 60) return numeric::nearMatch(secProposed, std::begin(stepsSec), std::end(stepsSec)); - const double stepsMin[] = { 1, 2, 5, 10, 15, 20, 30, 60 }; //nice numbers for minutes + const double stepsMin[] = {1, 2, 5, 10, 15, 20, 30, 60}; //nice numbers for minutes if (secProposed <= 3600) return 60 * numeric::nearMatch(secProposed / 60, std::begin(stepsMin), std::end(stepsMin)); @@ -716,7 +716,7 @@ private: bool closePressed_ = false; //remaining time - PerfCheck perf_{ WINDOW_REMAINING_TIME, WINDOW_BYTES_PER_SEC }; + PerfCheck perf_{WINDOW_REMAINING_TIME, WINDOW_BYTES_PER_SEC}; std::chrono::nanoseconds timeLastSpeedEstimate_ = std::chrono::seconds(-100); //used for calculating intervals between collecting perf samples std::chrono::nanoseconds timeLastGraphTotalUpdate_ = std::chrono::seconds(-100); @@ -1513,7 +1513,7 @@ auto SyncProgressDialogImpl::destroy(bool autoClose, bool restor this->Destroy(); //wxWidgets macOS: simple "delete"!!!!!!! - return { autoCloseDialog, dlgSizeBuf_, isMaximized }; + return {autoCloseDialog, dlgSizeBuf_, isMaximized}; } diff --git a/FreeFileSync/Source/ui/search_grid.cpp b/FreeFileSync/Source/ui/search_grid.cpp index 8c102d97..c3f459f4 100644 --- a/FreeFileSync/Source/ui/search_grid.cpp +++ b/FreeFileSync/Source/ui/search_grid.cpp @@ -123,7 +123,7 @@ std::pair fff::findGridMatch(const Grid& grid1, const Gr findRow(grid, searchString, searchAscending, rowFirst, rowLast); if (targetRow >= 0) { - result = { &grid, targetRow }; + result = {&grid, targetRow}; return true; } return false; diff --git a/FreeFileSync/Source/ui/small_dlgs.cpp b/FreeFileSync/Source/ui/small_dlgs.cpp index fbd0cbf9..1f569467 100644 --- a/FreeFileSync/Source/ui/small_dlgs.cpp +++ b/FreeFileSync/Source/ui/small_dlgs.cpp @@ -1082,12 +1082,12 @@ SyncConfirmationDlg::SyncConfirmationDlg(wxWindow* parent, }; setValue(*m_staticTextData, st.getBytesToProcess() == 0, formatFilesizeShort(st.getBytesToProcess()), *m_bitmapData, "data"); - setIntValue(*m_staticTextCreateLeft, st.createCount< LEFT_SIDE>(), *m_bitmapCreateLeft, "so_create_left_sicon"); - setIntValue(*m_staticTextUpdateLeft, st.updateCount< LEFT_SIDE>(), *m_bitmapUpdateLeft, "so_update_left_sicon"); - setIntValue(*m_staticTextDeleteLeft, st.deleteCount< LEFT_SIDE>(), *m_bitmapDeleteLeft, "so_delete_left_sicon"); - setIntValue(*m_staticTextCreateRight, st.createCount(), *m_bitmapCreateRight, "so_create_right_sicon"); - setIntValue(*m_staticTextUpdateRight, st.updateCount(), *m_bitmapUpdateRight, "so_update_right_sicon"); - setIntValue(*m_staticTextDeleteRight, st.deleteCount(), *m_bitmapDeleteRight, "so_delete_right_sicon"); + setIntValue(*m_staticTextCreateLeft, st.createCount< SelectSide::left>(), *m_bitmapCreateLeft, "so_create_left_sicon"); + setIntValue(*m_staticTextUpdateLeft, st.updateCount< SelectSide::left>(), *m_bitmapUpdateLeft, "so_update_left_sicon"); + setIntValue(*m_staticTextDeleteLeft, st.deleteCount< SelectSide::left>(), *m_bitmapDeleteLeft, "so_delete_left_sicon"); + setIntValue(*m_staticTextCreateRight, st.createCount(), *m_bitmapCreateRight, "so_create_right_sicon"); + setIntValue(*m_staticTextUpdateRight, st.updateCount(), *m_bitmapUpdateRight, "so_update_right_sicon"); + setIntValue(*m_staticTextDeleteRight, st.deleteCount(), *m_bitmapDeleteRight, "so_delete_right_sicon"); GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize() //=> works like a charm for GTK2 with window resizing problems and title bar corruption; e.g. Debian!!! @@ -1426,7 +1426,7 @@ std::vector OptionsDlg::getExtApp() const description = it->second; if (!description.empty() || !commandline.empty()) - output.push_back({ description, commandline }); + output.push_back({description, commandline}); } return output; } diff --git a/FreeFileSync/Source/ui/status_handler_impl.h b/FreeFileSync/Source/ui/status_handler_impl.h deleted file mode 100644 index c52df76e..00000000 --- a/FreeFileSync/Source/ui/status_handler_impl.h +++ /dev/null @@ -1,63 +0,0 @@ -// ***************************************************************************** -// * This file is part of the FreeFileSync project. It is distributed under * -// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * -// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * -// ***************************************************************************** - -#ifndef STATUS_HANDLER_IMPL_H_145234543248059083415565 -#define STATUS_HANDLER_IMPL_H_145234543248059083415565 - -#include -#include -#include -#include -#include - - -namespace fff -{ -namespace -{ -void delayAndCountDown(std::chrono::steady_clock::time_point delayUntil, const std::function& notifyStatus) -{ - for (auto now = std::chrono::steady_clock::now(); now < delayUntil; now = std::chrono::steady_clock::now()) - { - if (notifyStatus) - { - const auto timeRemMs = std::chrono::duration_cast(delayUntil - now).count(); - notifyStatus(_P("1 sec", "%x sec", numeric::intDivCeil(timeRemMs, 1000))); - } - - std::this_thread::sleep_for(UI_UPDATE_INTERVAL / 2); - } -} - - -void runCommandAndLogErrors(const Zstring& cmdLine, zen::ErrorLog& errorLog) -{ - using namespace zen; - - try - { - //give consoleExecute() some "time to fail", but not too long to hang our process - const int DEFAULT_APP_TIMEOUT_MS = 100; - - if (const auto& [exitCode, output] = consoleExecute(cmdLine, DEFAULT_APP_TIMEOUT_MS); //throw SysError, SysErrorTimeOut - exitCode != 0) - throw SysError(formatSystemError("", replaceCpy(_("Exit code %x"), L"%x", numberTo(exitCode)), utfTo(output))); - - errorLog.logMsg(_("Executing command:") + L' ' + utfTo(cmdLine) + L" [" + replaceCpy(_("Exit code %x"), L"%x", L"0") + L']', MSG_TYPE_INFO); - } - catch (SysErrorTimeOut&) //child process not failed yet => probably fine :> - { - errorLog.logMsg(_("Executing command:") + L' ' + utfTo(cmdLine), MSG_TYPE_INFO); - } - catch (const SysError& e) - { - errorLog.logMsg(replaceCpy(_("Command %x failed."), L"%x", fmtPath(cmdLine)) + L"\n\n" + e.toString(), MSG_TYPE_ERROR); - } -} -} -} - -#endif //STATUS_HANDLER_IMPL_H_145234543248059083415565 diff --git a/FreeFileSync/Source/ui/tree_grid.cpp b/FreeFileSync/Source/ui/tree_grid.cpp index 9e6ec23c..ff261ef4 100644 --- a/FreeFileSync/Source/ui/tree_grid.cpp +++ b/FreeFileSync/Source/ui/tree_grid.cpp @@ -29,8 +29,8 @@ namespace const int PERCENTAGE_BAR_WIDTH_DIP = 60; const int TREE_GRID_GAP_SIZE_DIP = 2; -inline wxColor getColorPercentBorder () { return { 198, 198, 198 }; } -inline wxColor getColorPercentBackground() { return { 0xf8, 0xf8, 0xf8 }; } +inline wxColor getColorPercentBorder () { return {198, 198, 198}; } +inline wxColor getColorPercentBackground() { return {0xf8, 0xf8, 0xf8}; } } @@ -39,8 +39,8 @@ TreeView::TreeView(FolderComparison& folderCmp, const SortInfo& si) : folderCmp_ //remove truly empty folder pairs as early as this: we want to distinguish single/multiple folder pair cases by looking at "folderCmp" std::erase_if(folderCmp_, [](const std::shared_ptr& baseObj) { - return AFS::isNullPath(baseObj->getAbstractPath< LEFT_SIDE>()) && - AFS::isNullPath(baseObj->getAbstractPath()); + return AFS::isNullPath(baseObj->getAbstractPath< SelectSide::left>()) && + AFS::isNullPath(baseObj->getAbstractPath()); }); } @@ -73,16 +73,16 @@ void TreeView::extractVisibleSubtree(ContainerObject& hierObj, //in // switch (file.getSyncDir()) // { // case SyncDirection::left: - // return file.getFileSize(); + // return file.getFileSize(); // case SyncDirection::right: - // return file.getFileSize(); + // return file.getFileSize(); // case SyncDirection::none: // break; // } //prefer file-browser semantics over sync preview (=> always show useful numbers, even for SyncDirection::none) //discussion: https://freefilesync.org/forum/viewtopic.php?t=1595 - return std::max(file.getFileSize(), file.getFileSize()); + return std::max(file.getFileSize(), file.getFileSize()); }; cont.firstFileId = nullptr; @@ -275,13 +275,13 @@ void TreeView::getChildren(const Container& cont, unsigned int level, std::vecto for (const DirNodeImpl& subDir : cont.subDirs) { - output.push_back({ level, 0, &subDir, NodeType::folder}); + output.push_back({level, 0, &subDir, NodeType::folder}); workList.emplace_back(subDir.bytesGross, &output.back().percent); } if (cont.firstFileId) { - output.push_back({ level, 0, &cont, NodeType::files }); + output.push_back({level, 0, &cont, NodeType::files}); workList.emplace_back(cont.bytesNet, &output.back().percent); } calcPercentage(workList); @@ -345,7 +345,7 @@ void TreeView::applySubView(std::vector&& newView) for (const RootNodeImpl& root : folderCmpView_) { - flatTree_.push_back({ 0, 0, &root, NodeType::root }); + flatTree_.push_back({0, 0, &root, NodeType::root}); workList.emplace_back(root.bytesGross, &flatTree_.back().percent); } @@ -394,8 +394,8 @@ void TreeView::updateView(Predicate pred) else { root.baseFolder = baseObj; - root.displayName = getShortDisplayNameForFolderPair(baseObj->getAbstractPath< LEFT_SIDE>(), - baseObj->getAbstractPath()); + root.displayName = getShortDisplayNameForFolderPair(baseObj->getAbstractPath< SelectSide::left>(), + baseObj->getAbstractPath()); this->compressNode(root); //"this->" required by two-pass lookup as enforced by GCC 4.7 } @@ -663,18 +663,18 @@ wxColor getColorForLevel(size_t level) switch (level % 12) { //*INDENT-OFF* - case 0: return { 0xcc, 0xcc, 0xff }; - case 1: return { 0xcc, 0xff, 0xcc }; - case 2: return { 0xff, 0xff, 0x99 }; - case 3: return { 0xdd, 0xdd, 0xdd }; - case 4: return { 0xff, 0xcc, 0xff }; - case 5: return { 0x99, 0xff, 0xcc }; - case 6: return { 0xcc, 0xcc, 0x99 }; - case 7: return { 0xff, 0xcc, 0xcc }; - case 8: return { 0xcc, 0xff, 0x99 }; - case 9: return { 0xff, 0xff, 0xcc }; - case 10: return { 0xcc, 0xff, 0xff }; - case 11: return { 0xff, 0xcc, 0x99 }; + case 0: return {0xcc, 0xcc, 0xff}; + case 1: return {0xcc, 0xff, 0xcc}; + case 2: return {0xff, 0xff, 0x99}; + case 3: return {0xdd, 0xdd, 0xdd}; + case 4: return {0xff, 0xcc, 0xff}; + case 5: return {0x99, 0xff, 0xcc}; + case 6: return {0xcc, 0xcc, 0x99}; + case 7: return {0xff, 0xcc, 0xcc}; + case 8: return {0xcc, 0xff, 0x99}; + case 9: return {0xff, 0xff, 0xcc}; + case 10: return {0xcc, 0xff, 0xff}; + case 11: return {0xff, 0xcc, 0x99}; //*INDENT-ON* } assert(false); @@ -724,8 +724,8 @@ private: if (std::unique_ptr node = getDataView().getLine(row)) if (const TreeView::RootNode* root = dynamic_cast(node.get())) { - const std::wstring& dirLeft = AFS::getDisplayPath(root->baseFolder.getAbstractPath< LEFT_SIDE>()); - const std::wstring& dirRight = AFS::getDisplayPath(root->baseFolder.getAbstractPath()); + const std::wstring& dirLeft = AFS::getDisplayPath(root->baseFolder.getAbstractPath< SelectSide::left>()); + const std::wstring& dirRight = AFS::getDisplayPath(root->baseFolder.getAbstractPath()); if (dirLeft.empty()) return dirRight; else if (dirRight.empty()) @@ -1131,7 +1131,7 @@ private: menu.addItem(_("&Default"), setDefaultColumns); //'&' -> reuse text from "default" buttons elsewhere //-------------------------------------------------------------------------------------------------------- - menu.popup(grid_, { event.mousePos_.x, grid_.getColumnLabelHeight() }); + menu.popup(grid_, {event.mousePos_.x, grid_.getColumnLabelHeight()}); //event.Skip(); } diff --git a/FreeFileSync/Source/ui/tree_grid_attr.h b/FreeFileSync/Source/ui/tree_grid_attr.h index eff14c4e..4cd0f2f1 100644 --- a/FreeFileSync/Source/ui/tree_grid_attr.h +++ b/FreeFileSync/Source/ui/tree_grid_attr.h @@ -36,9 +36,9 @@ std::vector getTreeGridDefaultColAttribs() using namespace zen; return //harmonize with tree_view.cpp::onGridLabelContext() => expects stretched folder and non-stretched other columns! { - { ColumnTypeTree::folder, - 2 * fastFromDIP(60), 1, true }, //stretch to full width and substract sum of fixed size widths - { ColumnTypeTree::itemCount, fastFromDIP(60), 0, true }, - { ColumnTypeTree::bytes, fastFromDIP(60), 0, true }, //GTK needs a few pixels more width + {ColumnTypeTree::folder, - 2 * fastFromDIP(60), 1, true}, //stretch to full width and substract sum of fixed size widths + {ColumnTypeTree::itemCount, fastFromDIP(60), 0, true}, + {ColumnTypeTree::bytes, fastFromDIP(60), 0, true}, //GTK needs a few pixels more width }; } diff --git a/FreeFileSync/Source/ui/version_check.cpp b/FreeFileSync/Source/ui/version_check.cpp index c81c0582..087582ee 100644 --- a/FreeFileSync/Source/ui/version_check.cpp +++ b/FreeFileSync/Source/ui/version_check.cpp @@ -10,7 +10,7 @@ #include #include #include -#include +//#include #include #include #include @@ -27,6 +27,7 @@ #include "../version/version.h" #include "small_dlgs.h" + #include using namespace zen; @@ -117,11 +118,10 @@ std::wstring getIso3166Country() //coordinate with get_latest_version_number.php std::vector> geHttpPostParameters(wxWindow& parent) //throw SysError { - assert(runningOnMainThread()); //this function is not thread-safe, e.g. consider wxWidgets usage in isPortableVersion() + assert(runningOnMainThread()); //this function is not thread-safe, e.g. consider wxWidgets usage in getIso639Language() std::vector> params; params.emplace_back("ffs_version", ffsVersion); - params.emplace_back("installation_type", isPortableVersion() ? "Portable" : "Local"); params.emplace_back("os_name", "Linux"); @@ -159,7 +159,7 @@ void showUpdateAvailableDialog(wxWindow* parent, const std::string& onlineVersio std::wstring updateDetailsMsg; try { - updateDetailsMsg = utfTo(sendHttpGet(utfTo("https://api.freefilesync.org/latest_changes?" + xWwwFormUrlEncode({ { "since", ffsVersion } })), + updateDetailsMsg = utfTo(sendHttpGet(utfTo("https://api.freefilesync.org/latest_changes?" + xWwwFormUrlEncode({{"since", ffsVersion}})), ffsUpdateCheckUserAgent, nullptr /*caCertFilePath*/, nullptr /*notifyUnbufferedIO*/).readAll()); //throw SysError } catch (const SysError& e) { updateDetailsMsg = _("Failed to retrieve update information.") + + L"\n\n" + e.toString(); } diff --git a/FreeFileSync/Source/version/version.h b/FreeFileSync/Source/version/version.h index e75db779..c621b68c 100644 --- a/FreeFileSync/Source/version/version.h +++ b/FreeFileSync/Source/version/version.h @@ -3,7 +3,7 @@ namespace fff { -const char ffsVersion[] = "11.6"; //internal linkage! +const char ffsVersion[] = "11.7"; //internal linkage! const char FFS_VERSION_SEPARATOR = '.'; } diff --git a/libcurl/rest.cpp b/libcurl/rest.cpp index 60fcb8eb..0964f63a 100644 --- a/libcurl/rest.cpp +++ b/libcurl/rest.cpp @@ -181,5 +181,5 @@ HttpSession::Result HttpSession::perform(const std::string& serverRelPath, } lastSuccessfulUseTime_ = std::chrono::steady_clock::now(); - return { static_cast(httpStatus) /*, contentType ? contentType : ""*/ }; + return {static_cast(httpStatus) /*, contentType ? contentType : ""*/}; } diff --git a/wx+/choice_enum.h b/wx+/choice_enum.h index 626aa39a..a590861a 100644 --- a/wx+/choice_enum.h +++ b/wx+/choice_enum.h @@ -41,7 +41,7 @@ struct EnumDescrList { EnumDescrList& add(Enum value, const wxString& text, const wxString& tooltip = {}) { - descrList.push_back({ value, { text, tooltip } }); + descrList.push_back({value, {text, tooltip}}); return *this; } diff --git a/wx+/graph.cpp b/wx+/graph.cpp index ba87299e..be75ac4d 100644 --- a/wx+/graph.cpp +++ b/wx+/graph.cpp @@ -36,7 +36,7 @@ double zen::nextNiceNumber(double blockSize) //round to next number which is a c assert(1 <= a && a < 10); //have a look at leading two digits: "nice" numbers start with 1, 2, 2.5 and 5 - const double steps[] = { 1, 2, 2.5, 5, 10 }; + const double steps[] = {1, 2, 2.5, 5, 10}; return e * numeric::nearMatch(a, std::begin(steps), std::end(steps)); } @@ -48,16 +48,16 @@ wxColor getDefaultColor(size_t pos) switch (pos % 10) { //*INDENT-OFF* - case 0: return { 0, 69, 134 }; //blue - case 1: return { 255, 66, 14 }; //red - case 2: return { 255, 211, 32 }; //yellow - case 3: return { 87, 157, 28 }; //green - case 4: return { 126, 0, 33 }; //royal - case 5: return { 131, 202, 255 }; //light blue - case 6: return { 49, 64, 4 }; //dark green - case 7: return { 174, 207, 0 }; //light green - case 8: return { 75, 31, 111 }; //purple - case 9: return { 255, 149, 14 }; //orange + case 0: return { 0, 69, 134}; //blue + case 1: return {255, 66, 14}; //red + case 2: return {255, 211, 32}; //yellow + case 3: return { 87, 157, 28}; //green + case 4: return {126, 0, 33}; //royal + case 5: return {131, 202, 255}; //light blue + case 6: return { 49, 64, 4}; //dark green + case 7: return {174, 207, 0}; //light green + case 8: return { 75, 31, 111}; //purple + case 9: return {255, 149, 14}; //orange //*INDENT-ON* } assert(false); diff --git a/wx+/graph.h b/wx+/graph.h index c843b09b..288ef9e7 100644 --- a/wx+/graph.h +++ b/wx+/graph.h @@ -218,7 +218,7 @@ public: void addCurve(const std::shared_ptr& data, const CurveAttributes& ca = CurveAttributes()); void clearCurves() { curves_.clear(); } - static wxColor getBorderColor() { return { 130, 135, 144 }; } //medium grey, the same Win7 uses for other frame borders => not accessible! but no big deal... + static wxColor getBorderColor() { return {130, 135, 144}; } //medium grey, the same Win7 uses for other frame borders => not accessible! but no big deal... class MainAttributes { @@ -330,7 +330,7 @@ private: CurveList curves_; //perf!!! generating the font is *very* expensive! => buffer for Graph2D::render()! - const wxFont labelFont_ { wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, L"Arial" }; + const wxFont labelFont_{wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, L"Arial"}; }; } diff --git a/wx+/grid.cpp b/wx+/grid.cpp index cd91b1af..0111ccf7 100644 --- a/wx+/grid.cpp +++ b/wx+/grid.cpp @@ -27,8 +27,8 @@ using namespace zen; //let's NOT create wxWidgets objects statically: -wxColor GridData::getColorSelectionGradientFrom() { return { 137, 172, 255 }; } //blue: HSL: 158, 255, 196 HSV: 222, 0.46, 1 -wxColor GridData::getColorSelectionGradientTo () { return { 225, 234, 255 }; } // HSL: 158, 255, 240 HSV: 222, 0.12, 1 +wxColor GridData::getColorSelectionGradientFrom() { return {137, 172, 255}; } //blue: HSL: 158, 255, 196 HSV: 222, 0.46, 1 +wxColor GridData::getColorSelectionGradientTo () { return {225, 234, 255}; } // HSL: 158, 255, 240 HSV: 222, 0.12, 1 int GridData::getColumnGapLeft() { return fastFromDIP(4); } @@ -493,8 +493,8 @@ public: const int yFrom = refParent().CalcUnscrolledPosition(clientRect.GetTopLeft ()).y; const int yTo = refParent().CalcUnscrolledPosition(clientRect.GetBottomRight()).y; - return { std::max(yFrom / rowHeight_, 0), - std::min((yTo / rowHeight_) + 1, refParent().getRowCount()) }; + return {std::max(yFrom / rowHeight_, 0), + std::min((yTo / rowHeight_) + 1, refParent().getRowCount())}; } private: @@ -1351,6 +1351,7 @@ private: return; const double mouseDragSpeedIncScrollU = MOUSE_DRAG_ACCELERATION_DIP * wnd_.rowLabelWin_.getRowHeight() / pixelsPerUnitY; //unit: [scroll units / (DIP * sec)] + //design alternative: "Dynamic autoscroll based on escape velocity": https://devblogs.microsoft.com/oldnewthing/20210128-00/?p=104768 auto autoScroll = [&](int overlapPix, double& toScroll) { @@ -1986,7 +1987,7 @@ void Grid::setColumnConfig(const std::vector& attr) assert(ca.type != ColumnType::none); if (ca.visible) - visCols.push_back({ ca.type, ca.offset, std::max(ca.stretch, 0) }); + visCols.push_back({ca.type, ca.offset, std::max(ca.stretch, 0)}); } //"ownership" of visible columns is now within Grid @@ -2101,10 +2102,10 @@ Grid::ColumnPosInfo Grid::getColumnAtPos(int posX) const { accWidth += cw.width; if (posX < accWidth) - return { cw.type, posX + cw.width - accWidth, cw.width }; + return {cw.type, posX + cw.width - accWidth, cw.width}; } } - return { ColumnType::none, 0, 0 }; + return {ColumnType::none, 0, 0}; } @@ -2386,7 +2387,7 @@ std::vector Grid::getColWidths(int mainWinWidth) const //eval else width = std::max(width, 0); //support smaller width than COLUMN_MIN_WIDTH_DIP if set via configuration - output.push_back({ vc.type, width }); + output.push_back({vc.type, width}); } return output; } diff --git a/wx+/grid.h b/wx+/grid.h index 8d68ffd7..4de76b66 100644 --- a/wx+/grid.h +++ b/wx+/grid.h @@ -387,7 +387,7 @@ std::vector convertColAttributes(const std::vector output; for (const ColAttrReal& ca : makeConsistent(attribs, defaults)) - output.push_back({ static_cast(ca.type), ca.offset, ca.stretch, ca.visible }); + output.push_back({static_cast(ca.type), ca.offset, ca.stretch, ca.visible}); return output; } @@ -399,7 +399,7 @@ std::vector convertColAttributes(const std::vector output; for (const Grid::ColAttributes& ca : attribs) - output.push_back({ static_cast(ca.type), ca.offset, ca.stretch, ca.visible }); + output.push_back({static_cast(ca.type), ca.offset, ca.stretch, ca.visible}); return output; } } diff --git a/wx+/image_resources.cpp b/wx+/image_resources.cpp index 42114ebb..fbfeb0d8 100644 --- a/wx+/image_resources.cpp +++ b/wx+/image_resources.cpp @@ -50,27 +50,23 @@ ImageHolder xbrzScale(int width, int height, const unsigned char* imageRgb, cons *out++ = xbrz::makePixel(*alpha++, rgb[0], rgb[1], rgb[2]); } //----------------------------------------------------- - xbrz::scale(hqScale, //size_t factor, //valid range: 2 - SCALE_FACTOR_MAX - argbSrc, //const uint32_t* src, - xbrTrg, //uint32_t* trg, - width, height, //int srcWidth, int srcHeight, - xbrz::ColorFormat::ARGB_UNBUFFERED); //ColorFormat colFmt, - //test: total xBRZ scaling time with ARGB: 300ms, ARGB_UNBUFFERED: 50ms + xbrz::scale(hqScale, //size_t factor - valid range: 2 - SCALE_FACTOR_MAX + argbSrc, //const uint32_t* src + xbrTrg, //uint32_t* trg + width, height, //int srcWidth, int srcHeight + xbrz::ColorFormat::argbUnbuffered); //ColorFormat colFmt + //test: total xBRZ scaling time with ARGB: 300ms, ARGB unbuffered: 50ms //----------------------------------------------------- //convert BGRA to RGB + alpha ImageHolder trgImg(hqWidth, hqHeight, true /*withAlpha*/); - { - unsigned char* rgb = trgImg.getRgb(); - unsigned char* alpha = trgImg.getAlpha(); - std::for_each(xbrTrg, xbrTrg + hqWidth * hqHeight, [&](uint32_t col) - { - *alpha++ = xbrz::getAlpha(col); - *rgb++ = xbrz::getRed (col); - *rgb++ = xbrz::getGreen(col); - *rgb++ = xbrz::getBlue (col); - }); - } + std::for_each(xbrTrg, xbrTrg + hqWidth * hqHeight, [rgb = trgImg.getRgb(), alpha = trgImg.getAlpha()](uint32_t col) mutable + { + *alpha++ = xbrz::getAlpha(col); + *rgb++ = xbrz::getRed (col); + *rgb++ = xbrz::getGreen(col); + *rgb++ = xbrz::getBlue (col); + }); return trgImg; } @@ -128,7 +124,7 @@ private: Protected>> result_; using TaskType = FunctionReturnTypeT; - std::optional> threadGroup_{ ThreadGroup(std::max(std::thread::hardware_concurrency(), 1), Zstr("xBRZ Scaler")) }; + std::optional> threadGroup_{ThreadGroup(std::max(std::thread::hardware_concurrency(), 1), Zstr("xBRZ Scaler"))}; //hardware_concurrency() == 0 if "not computable or well defined" }; @@ -296,8 +292,8 @@ const wxImage& ImageBuffer::getImage(const std::string& name, int maxWidth /*opt if (rawImg.GetHeight() >= outHeight) //=> skip needless xBRZ upscaling it = imagesOut_.emplace(imkey, shrinkImage(rawImg, -1 /*maxWidth*/, outHeight)).first; else if (rawImg.GetHeight() >= 0.9 * outHeight) //almost there: also no need for xBRZ-scale - //however: for 125% DPI scaling, "2xBRZ + bilinear downscale" gives a better result than mere "125% bilinear upscale"! - it = imagesOut_.emplace(imkey, rawImg.Scale(numeric::intDivRound(outHeight * rawImg.GetWidth(), rawImg.GetHeight()), outHeight, wxIMAGE_QUALITY_BILINEAR)).first; + it = imagesOut_.emplace(imkey, bilinearScale(rawImg, numeric::intDivRound(outHeight * rawImg.GetWidth(), rawImg.GetHeight()), outHeight)).first; + //however: for 125% DPI scaling, "2xBRZ + bilinear downscale" gives a better result than mere "125% bilinear upscale" else it = imagesOut_.emplace(imkey, shrinkImage(getScaledImage(name), -1 /*maxWidth*/, outHeight)).first; } diff --git a/wx+/image_tools.cpp b/wx+/image_tools.cpp index 6ba95c5e..bbc8cee7 100644 --- a/wx+/image_tools.cpp +++ b/wx+/image_tools.cpp @@ -8,6 +8,7 @@ #include #include #include +#include using namespace zen; @@ -91,12 +92,12 @@ void copyImageLayover(const wxImage& src, for (int x = 0; x < srcWidth; ++x) { const int w1 = *srcAlpha; //alpha-composition interpreted as weighted average - const int w2 = *trgAlpha * (255 - w1) / 255; + const int w2 = numeric::intDivRound(*trgAlpha * (255 - w1), 255); const int wSum = w1 + w2; auto calcColor = [w1, w2, wSum](unsigned char colsrc, unsigned char colTrg) { - return static_cast(wSum == 0 ? 0 : (colsrc * w1 + colTrg * w2) / wSum); + return static_cast(wSum == 0 ? 0 : numeric::intDivRound(colsrc * w1 + colTrg * w2, wSum)); }; trgRgb[0] = calcColor(srcRgb[0], trgRgb[0]); trgRgb[1] = calcColor(srcRgb[1], trgRgb[1]); @@ -138,7 +139,7 @@ wxImage zen::stackImages(const wxImage& img1, const wxImage& img2, ImageStackLay const int img2Height = img2.GetHeight(); const wxSize newSize = dir == ImageStackLayout::horizontal ? - wxSize(img1Width + gap + img2Width, std::max(img1Height, img2Height)) : + wxSize(img1Width + gap + img2Width, std::max(img1Height, img2Height)) : wxSize(std::max(img1Width, img2Width), img1Height + gap + img2Height); wxImage output(newSize); @@ -181,7 +182,7 @@ wxImage zen::createImageFromText(const wxString& text, const wxFont& font, const //assert(!contains(text, L"&")); //accelerator keys not supported here wxString textFmt = replaceCpy(text, L"&", L"", false); - const std::vector> lineInfo = getTextExtentInfo(textFmt, font); + const std::vector> lineInfo = getTextExtentInfo(textFmt, font); int maxWidth = 0; int lineHeight = 0; @@ -197,8 +198,8 @@ wxImage zen::createImageFromText(const wxString& text, const wxFont& font, const { wxMemoryDC dc(newBitmap); - if (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft) - dc.SetLayoutDirection(wxLayout_RightToLeft); //handle e.g. "weak" bidi characters: -> arrows in hebrew/arabic + if (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft) + dc.SetLayoutDirection(wxLayout_RightToLeft); //handle e.g. "weak" bidi characters: -> arrows in hebrew/arabic dc.SetBackground(*wxWHITE_BRUSH); dc.Clear(); @@ -238,7 +239,7 @@ wxImage zen::createImageFromText(const wxString& text, const wxFont& font, const for (int i = 0; i < pixelCount; ++i) { //black(0,0,0) becomes wxIMAGE_ALPHA_OPAQUE(255), while white(255,255,255) becomes wxIMAGE_ALPHA_TRANSPARENT(0) - *alpha++ = static_cast((255 - rgb[0] + 255 - rgb[1] + 255 - rgb[2]) / 3); //mixed-mode arithmetics! + *alpha++ = static_cast(numeric::intDivRound(3 * 255 - rgb[0] - rgb[1] - rgb[2], 3)); //mixed-mode arithmetics! *rgb++ = col.Red (); // *rgb++ = col.Green(); //apply actual text color @@ -312,6 +313,46 @@ wxImage zen::resizeCanvas(const wxImage& img, wxSize newSize, int alignment) } +wxImage zen::bilinearScale(const wxImage& img, int width, int height) +{ + assert(img.HasAlpha()); + const auto imgReader = [rgb = img.GetData(), alpha = img.GetAlpha(), srcWidth = img.GetSize().x](int x, int y, xbrz::BytePixel& pix) + { + const int idx = y * srcWidth + x; + const unsigned char* const ptr = rgb + idx * 3; + + const unsigned char a = alpha[idx]; + pix[0] = a; + pix[1] = xbrz::premultiply(ptr[0], a); //r + pix[2] = xbrz::premultiply(ptr[1], a); //g + pix[3] = xbrz::premultiply(ptr[2], a); //b + }; + + wxImage imgOut(width, height); + imgOut.SetAlpha(); + + const auto imgWriter = [rgb = imgOut.GetData(), alpha = imgOut.GetAlpha()](const xbrz::BytePixel& pix) mutable + { + const unsigned char a = pix[0]; + * alpha++ = a; + * rgb++ = xbrz::demultiply(pix[1], a); //r + *rgb++ = xbrz::demultiply(pix[2], a); //g + *rgb++ = xbrz::demultiply(pix[3], a); //b + }; + + xbrz::bilinearScaleSimple(imgReader, //PixReader srcReader + img.GetSize().x, //int srcWidth + img.GetSize().y, //int srcHeight + imgWriter, //PixWriter trgWriter + width, //int trgWidth + height, //int trgHeight + 0, //int yFirst + height); //int yLast + return imgOut; + //return img.Scale(width, height, wxIMAGE_QUALITY_BILINEAR); +} + + wxImage zen::shrinkImage(const wxImage& img, int maxWidth /*optional*/, int maxHeight /*optional*/) { wxSize newSize = img.GetSize(); @@ -330,8 +371,7 @@ wxImage zen::shrinkImage(const wxImage& img, int maxWidth /*optional*/, int maxH if (newSize == img.GetSize()) return img; - return img.Scale(newSize.x, newSize.y, wxIMAGE_QUALITY_BILINEAR); //looks sharper than wxIMAGE_QUALITY_HIGH! - //perf: use xbrz::bilinearScale instead? only about 10% shorter runtime + return bilinearScale(img, newSize.x, newSize.y); //looks sharper than wxIMAGE_QUALITY_HIGH! } diff --git a/wx+/image_tools.h b/wx+/image_tools.h index 0f0fb9c2..c2fed4c1 100644 --- a/wx+/image_tools.h +++ b/wx+/image_tools.h @@ -50,6 +50,9 @@ void convertToVanillaImage(wxImage& img); //add alpha channel if missing + remov //wxColor hsvColor(double h, double s, double v); //h within [0, 360), s, v within [0, 1] +//does *not* fuck up alpha channel like naive bilinear implementations, e.g. wxImage::Scale() +wxImage bilinearScale(const wxImage& img, int width, int height); + wxImage shrinkImage(const wxImage& img, int maxWidth /*optional*/, int maxHeight /*optional*/); inline wxImage shrinkImage(const wxImage& img, int maxSize) { return shrinkImage(img, maxSize, maxSize); } diff --git a/xBRZ/src/xbrz.cpp b/xBRZ/src/xbrz.cpp index 50660b84..6c015aa1 100644 --- a/xBRZ/src/xbrz.cpp +++ b/xBRZ/src/xbrz.cpp @@ -32,7 +32,10 @@ uint32_t gradientRGB(uint32_t pixFront, uint32_t pixBack) //blend front color wi { static_assert(0 < M && M < N && N <= 1000); - auto calcColor = [](unsigned char colFront, unsigned char colBack) -> unsigned char { return (colFront * M + colBack * (N - M)) / N; }; + auto calcColor = [](unsigned char colFront, unsigned char colBack) + { + return static_cast(uintDivRound(colFront * M + colBack * (N - M), N)); + }; return makePixel(calcColor(getRed (pixFront), getRed (pixBack)), calcColor(getGreen(pixFront), getGreen(pixBack)), @@ -53,10 +56,10 @@ uint32_t gradientARGB(uint32_t pixFront, uint32_t pixBack) //find intermediate c auto calcColor = [=](unsigned char colFront, unsigned char colBack) { - return static_cast((colFront * weightFront + colBack * weightBack) / weightSum); + return static_cast(uintDivRound(colFront * weightFront + colBack * weightBack, weightSum)); }; - return makePixel(static_cast(weightSum / N), + return makePixel(static_cast(uintDivRound(weightSum, N)), calcColor(getRed (pixFront), getRed (pixBack)), calcColor(getGreen(pixFront), getGreen(pixBack)), calcColor(getBlue (pixFront), getBlue (pixBack))); @@ -154,7 +157,7 @@ double distYCbCr(uint32_t pix1, uint32_t pix2, double lumaWeight) { //https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion //YCbCr conversion is a matrix multiplication => take advantage of linearity by subtracting first! - const int r_diff = static_cast(getRed (pix1)) - getRed (pix2); //we may delay division by 255 to after matrix multiplication + const int r_diff = static_cast(getRed (pix1)) - getRed (pix2); //defer division by 255 to after matrix multiplication const int g_diff = static_cast(getGreen(pix1)) - getGreen(pix2); // const int b_diff = static_cast(getBlue (pix1)) - getBlue (pix2); //substraction for int is noticeable faster than for double! @@ -1094,23 +1097,23 @@ struct ColorDistanceARGB { const double a1 = getAlpha(pix1) / 255.0 ; const double a2 = getAlpha(pix2) / 255.0 ; - /* - Requirements for a color distance handling alpha channel: with a1, a2 in [0, 1] + + /* Requirements for a color distance handling alpha channel: with a1, a2 in [0, 1] 1. if a1 = a2, distance should be: a1 * distYCbCr() 2. if a1 = 0, distance should be: a2 * distYCbCr(black, white) = a2 * 255 3. if a1 = 1, ??? maybe: 255 * (1 - a2) + a2 * distYCbCr() - */ - //return std::min(a1, a2) * distYCbCrBuffered(pix1, pix2) + 255 * abs(a1 - a2); + std::min(a1, a2) * distYCbCrBuffered(pix1, pix2) + 255 * abs(a1 - a2); + + alternative? std::sqrt(a1 * a2 * square(distYCbCrBuffered(pix1, pix2)) + square(255 * (a1 - a2))); */ + //=> following code is 15% faster: const double d = distYCbCrBuffered(pix1, pix2); if (a1 < a2) return a1 * d + 255 * (a2 - a1); else return a2 * d + 255 * (a1 - a2); - - //alternative? return std::sqrt(a1 * a2 * square(distYCbCrBuffered(pix1, pix2)) + square(255 * (a1 - a2))); } }; @@ -1163,7 +1166,7 @@ void xbrz::scale(size_t factor, const uint32_t* src, uint32_t* trg, int srcWidth switch (colFmt) { //*INDENT-OFF* - case ColorFormat::RGB: + case ColorFormat::rgb: switch (factor) { case 2: return scaleImage, ColorDistanceRGB, OobReaderDuplicate>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); @@ -1174,7 +1177,7 @@ void xbrz::scale(size_t factor, const uint32_t* src, uint32_t* trg, int srcWidth } break; - case ColorFormat::ARGB: + case ColorFormat::argb: switch (factor) { case 2: return scaleImage, ColorDistanceARGB, OobReaderTransparent>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); @@ -1185,7 +1188,7 @@ void xbrz::scale(size_t factor, const uint32_t* src, uint32_t* trg, int srcWidth } break; - case ColorFormat::ARGB_UNBUFFERED: + case ColorFormat::argbUnbuffered: switch (factor) { case 2: return scaleImage, ColorDistanceUnbufferedARGB, OobReaderTransparent>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); @@ -1205,11 +1208,11 @@ bool xbrz::equalColorTest(uint32_t col1, uint32_t col2, ColorFormat colFmt, doub { switch (colFmt) { - case ColorFormat::RGB: + case ColorFormat::rgb: return ColorDistanceRGB::dist(col1, col2, luminanceWeight) < equalColorTolerance; - case ColorFormat::ARGB: + case ColorFormat::argb: return ColorDistanceARGB::dist(col1, col2, luminanceWeight) < equalColorTolerance; - case ColorFormat::ARGB_UNBUFFERED: + case ColorFormat::argbUnbuffered: return ColorDistanceUnbufferedARGB::dist(col1, col2, luminanceWeight) < equalColorTolerance; } assert(false); @@ -1223,13 +1226,26 @@ void xbrz::bilinearScale(const uint32_t* src, int srcWidth, int srcHeight, const auto imgReader = [src, srcWidth](int x, int y, BytePixel& pix) { static_assert(sizeof(pix) == sizeof(uint32_t)); - std::memcpy(pix, src + y * srcWidth + x, sizeof(pix)); + const uint32_t pixSrc = src[y * srcWidth + x]; + + const unsigned char a = getAlpha(pixSrc); + pix[0] = a; + pix[1] = xbrz::premultiply(getRed (pixSrc), a); //r + pix[2] = xbrz::premultiply(getGreen(pixSrc), a); //g + pix[3] = xbrz::premultiply(getBlue (pixSrc), a); //b }; - const auto imgWriter = [trg](const xbrz::BytePixel& pix) mutable { std::memcpy(trg++, pix, sizeof(pix)); }; + const auto imgWriter = [trg](const xbrz::BytePixel& pix) mutable + { + const unsigned char a = pix[0]; + * trg++ = makePixel(a, + xbrz::demultiply(pix[1], a), //r + xbrz::demultiply(pix[2], a), //g + xbrz::demultiply(pix[3], a)); //b + }; - bilinearScale(imgReader, srcWidth, srcHeight, - imgWriter, trgWidth, trgHeight, 0, trgHeight); + bilinearScaleSimple(imgReader, srcWidth, srcHeight, + imgWriter, trgWidth, trgHeight, 0, trgHeight); } @@ -1262,8 +1278,8 @@ void bilinearScaleCpu(const uint32_t* src, int srcWidth, int srcHeight, tg.run([=] { const int iLast = std::min(i + TASK_GRANULARITY, trgHeight); - xbrz::bilinearScale(src, srcWidth, srcHeight, srcWidth * sizeof(uint32_t), - trg, trgWidth, trgHeight, trgWidth * sizeof(uint32_t), + xbrz::bilinearScaleSimple(src, srcWidth, srcHeight, srcWidth * sizeof(uint32_t), + trg, trgWidth, trgHeight, trgWidth * sizeof(uint32_t), i, iLast, [](uint32_t pix) { return pix; }); }); tg.wait(); diff --git a/xBRZ/src/xbrz.h b/xBRZ/src/xbrz.h index c0778cf1..b3a496ba 100644 --- a/xBRZ/src/xbrz.h +++ b/xBRZ/src/xbrz.h @@ -26,26 +26,24 @@ namespace xbrz { -/* -------------------------------------------------------------------------- -| xBRZ: "Scale by rules" - high quality image upscaling filter by Zenju | -------------------------------------------------------------------------- -using a modified approach of xBR: -http://board.byuu.org/viewtopic.php?f=10&t=2248 -- new rule set preserving small image features -- highly optimized for performance -- support alpha channel -- support multithreading -- support 64-bit architectures -- support processing image slices -- support scaling up to 6xBRZ -*/ +/* ------------------------------------------------------------------------- + | xBRZ: "Scale by rules" - high quality image upscaling filter by Zenju | + ------------------------------------------------------------------------- + using a modified approach of xBR: + http://board.byuu.org/viewtopic.php?f=10&t=2248 + - new rule set preserving small image features + - highly optimized for performance + - support alpha channel + - support multithreading + - support 64-bit architectures + - support processing image slices + - support scaling up to 6xBRZ */ enum class ColorFormat //from high bits -> low bits, 8 bit per channel { - RGB, //8 bit for each red, green, blue, upper 8 bits unused - ARGB, //including alpha channel, BGRA byte order on little-endian machines - ARGB_UNBUFFERED, //like ARGB, but without the one-time buffer creation overhead (ca. 100 - 300 ms) at the expense of a slightly slower scaling time + rgb, //8 bit for each red, green, blue, upper 8 bits unused + argb, //including alpha channel, BGRA byte order on little-endian machines + argbUnbuffered, //like ARGB, but without the one-time buffer creation overhead (ca. 100 - 300 ms) at the expense of a slightly slower scaling time }; const int SCALE_FACTOR_MAX = 6; @@ -66,6 +64,7 @@ void scale(size_t factor, //valid range: 2 - SCALE_FACTOR_MAX const ScalerCfg& cfg = ScalerCfg(), int yFirst = 0, int yLast = std::numeric_limits::max()); //slice of source image +//BGRA byte order void bilinearScale(const uint32_t* src, int srcWidth, int srcHeight, /**/ uint32_t* trg, int trgWidth, int trgHeight); diff --git a/xBRZ/src/xbrz_tools.h b/xBRZ/src/xbrz_tools.h index 98164678..d6e48c0c 100644 --- a/xBRZ/src/xbrz_tools.h +++ b/xBRZ/src/xbrz_tools.h @@ -87,10 +87,35 @@ void nearestNeighborScale(PixReader srcReader /* (int x, int y, BytePixel& pix) } +inline +unsigned int uintDivRound(unsigned int num, unsigned int den) +{ + assert(den != 0); + return (num + den / 2) / den; +} + + +inline +unsigned char premultiply(unsigned char c, unsigned char alpha) +{ + return static_cast(uintDivRound(static_cast(c) * alpha, 255)); + //premultiply/demultiply using int div round is more accurate than int div floor/ceil pair +} + + +inline +unsigned char demultiply(unsigned char c, unsigned char alpha) +{ + return static_cast(alpha == 0 ? 0 : + std::clamp(uintDivRound(static_cast(c) * 255, alpha), 0U, 255U)); +} + + +//caveat: treats alpha channel like regular color! => caller needs to pre/de-multiply alpha! template -void bilinearScale(PixReader srcReader /* (int x, int y, BytePixel& pix) */, int srcWidth, int srcHeight, - PixWriter trgWriter /* (const BytePixel& pix) */, int trgWidth, int trgHeight, - int yFirst, int yLast) +void bilinearScaleSimple(PixReader srcReader /* (int x, int y, BytePixel& pix) */, int srcWidth, int srcHeight, + PixWriter trgWriter /* (const BytePixel& pix) */, int trgWidth, int trgHeight, + int yFirst, int yLast) { yFirst = std::max(yFirst, 0); yLast = std::min(yLast, trgHeight); @@ -121,7 +146,7 @@ void bilinearScale(PixReader srcReader /* (int x, int y, BytePixel& pix) */, int const double xx1 = x / scaleX - x1; const double x2x = 1 - xx1; - buf[x] = { x1, x2, xx1, x2x }; + buf[x] = {x1, x2, xx1, x2x}; } for (int y = yFirst; y < yLast; ++y) diff --git a/zen/basic_math.h b/zen/basic_math.h index a4feb83e..944a0f53 100644 --- a/zen/basic_math.h +++ b/zen/basic_math.h @@ -111,14 +111,14 @@ std::pair minMaxElement(InputIterator first, Input } } } - return { lowest, largest }; + return {lowest, largest}; } template inline std::pair minMaxElement(InputIterator first, InputIterator last) { - return minMaxElement(first, last, std::less::value_type>()); + return minMaxElement(first, last, std::less()); } */ @@ -152,10 +152,10 @@ template inline auto intDivRound(N num, D den) { using namespace zen; - static_assert(IsInteger::value && IsInteger::value); - static_assert(IsSignedInt::value == IsSignedInt::value); //until further + static_assert(IsIntegerV&& IsIntegerV); + static_assert(IsSignedIntV == IsSignedIntV); //until further assert(den != 0); - if constexpr (IsSignedInt::value) + if constexpr (IsSignedIntV) { if ((num < 0) != (den < 0)) return (num - den / 2) / den; @@ -168,10 +168,10 @@ template inline auto intDivCeil(N num, D den) { using namespace zen; - static_assert(IsInteger::value && IsInteger::value); - static_assert(IsSignedInt::value == IsSignedInt::value); //until further + static_assert(IsIntegerV&& IsIntegerV); + static_assert(IsSignedIntV == IsSignedIntV); //until further assert(den != 0); - if constexpr (IsSignedInt::value) + if constexpr (IsSignedIntV) { if ((num < 0) != (den < 0)) return num / den; @@ -187,10 +187,10 @@ template inline auto intDivFloor(N num, D den) { using namespace zen; - static_assert(IsInteger::value && IsInteger::value); - static_assert(IsSignedInt::value == IsSignedInt::value); //until further + static_assert(IsIntegerV&& IsIntegerV); + static_assert(IsSignedIntV == IsSignedIntV); //until further assert(den != 0); - if constexpr (IsSignedInt::value) + if constexpr (IsSignedIntV) { if ((num < 0) != (den < 0)) { diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp index dc416b34..191ffd64 100644 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -9,7 +9,6 @@ #include #include "thread.h" #include "scope_guard.h" -//#include "basic_math.h" #include #include @@ -34,7 +33,7 @@ DirWatcher::DirWatcher(const Zstring& dirPath) : //throw FileError pimpl_(std::make_unique()) { //get all subdirectories - std::vector fullFolderList { baseDirPath_ }; + std::vector fullFolderList {baseDirPath_}; { std::function traverse; @@ -102,7 +101,7 @@ DirWatcher::~DirWatcher() std::vector DirWatcher::fetchChanges(const std::function& requestUiUpdate, std::chrono::milliseconds cbInterval) //throw FileError { - std::vector buffer(512 * (sizeof(struct ::inotify_event) + NAME_MAX + 1)); + std::vector buffer(512 * (sizeof(inotify_event) + NAME_MAX + 1)); ssize_t bytesRead = 0; do @@ -125,7 +124,7 @@ std::vector DirWatcher::fetchChanges(const std::function(buffer[bytePos]); + inotify_event& evt = reinterpret_cast(buffer[bytePos]); if (evt.len != 0) //exclude case: deletion of "self", already reported by parent directory watch { @@ -138,18 +137,18 @@ std::vector DirWatcher::fetchChanges(const std::function(msg) }); + entries_.push_back({std::time(nullptr), type, utfTo(msg)}); } diff --git a/zen/file_access.cpp b/zen/file_access.cpp index 4c9af652..fb770f19 100644 --- a/zen/file_access.cpp +++ b/zen/file_access.cpp @@ -12,7 +12,6 @@ #include "file_traverser.h" #include "scope_guard.h" #include "symlink_target.h" -#include "file_id_def.h" #include "file_io.h" #include "crc.h" #include "guid.h" @@ -45,7 +44,7 @@ std::optional zen::parsePathComponents(const Zstring& itemPath) Zstring relPath(it + 1, itemPathFmt.end()); trim(relPath, true, true, [](Zchar c) { return c == FILE_NAME_SEPARATOR; }); - return PathComponents({ rootPath, relPath }); + return PathComponents({rootPath, relPath}); } return {}; }; @@ -100,7 +99,7 @@ std::optional zen::getParentFolderPath(const Zstring& itemPath) ItemType zen::getItemType(const Zstring& itemPath) //throw FileError { - struct ::stat itemInfo = {}; + struct stat itemInfo = {}; if (::lstat(itemPath.c_str(), &itemInfo) != 0) THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), "lstat"); @@ -153,7 +152,7 @@ std::optional zen::itemStillExists(const Zstring& itemPath) //throw Fi bool zen::fileAvailable(const Zstring& filePath) //noexcept { //symbolic links (broken or not) are also treated as existing files! - struct ::stat fileInfo = {}; + struct stat fileInfo = {}; if (::stat(filePath.c_str(), &fileInfo) == 0) //follow symlinks! return S_ISREG(fileInfo.st_mode); return false; @@ -163,7 +162,7 @@ bool zen::fileAvailable(const Zstring& filePath) //noexcept bool zen::dirAvailable(const Zstring& dirPath) //noexcept { //symbolic links (broken or not) are also treated as existing directories! - struct ::stat dirInfo = {}; + struct stat dirInfo = {}; if (::stat(dirPath.c_str(), &dirInfo) == 0) //follow symlinks! return S_ISDIR(dirInfo.st_mode); return false; @@ -177,29 +176,22 @@ namespace int64_t zen::getFreeDiskSpace(const Zstring& path) //throw FileError, returns < 0 if not available { - struct ::statfs info = {}; - if (::statfs(path.c_str(), &info) != 0) + struct statfs info = {}; + if (::statfs(path.c_str(), &info) != 0) //follows symlinks! THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot determine free disk space for %x."), L"%x", fmtPath(path)), "statfs"); + //Linux: "Fields that are undefined for a particular file system are set to 0." + //macOS: "Fields that are undefined for a particular file system are set to -1." - mkay :> + if (makeSigned(info.f_bsize) <= 0 || + makeSigned(info.f_bavail) <= 0) + return -1; return static_cast(info.f_bsize) * info.f_bavail; } -VolumeId zen::getVolumeId(const Zstring& itemPath) //throw FileError -{ - struct ::stat fileInfo = {}; - if (::stat(itemPath.c_str(), &fileInfo) != 0) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), "stat"); - - warn_static("NOT STABLE!") - - return fileInfo.st_dev; -} - - uint64_t zen::getFileSize(const Zstring& filePath) //throw FileError { - struct ::stat fileInfo = {}; + struct stat fileInfo = {}; if (::stat(filePath.c_str(), &fileInfo) != 0) THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), "stat"); @@ -207,6 +199,8 @@ uint64_t zen::getFileSize(const Zstring& filePath) //throw FileError } + + Zstring zen::getTempFolderPath() //throw FileError { if (const char* tempPath = ::getenv("TMPDIR")) //no extended error reporting @@ -336,15 +330,15 @@ void moveAndRenameFileSub(const Zstring& pathFrom, const Zstring& pathTo, bool r //macOS: no solution https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man2/rename.2.html if (!replaceExisting) { - struct ::stat infoSrc = {}; - if (::lstat(pathFrom.c_str(), &infoSrc) != 0) + struct stat sourceInfo = {}; + if (::lstat(pathFrom.c_str(), &sourceInfo) != 0) THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(pathFrom)), "stat"); - struct ::stat infoTrg = {}; - if (::lstat(pathTo.c_str(), &infoTrg) == 0) + struct stat targetInfo = {}; + if (::lstat(pathTo.c_str(), &targetInfo) == 0) { - if (infoSrc.st_dev != infoTrg.st_dev || - infoSrc.st_ino != infoTrg.st_ino) + if (sourceInfo.st_dev != targetInfo.st_dev || + sourceInfo.st_ino != targetInfo.st_ino) throwException(EEXIST); //that's what we're really here for //else: continue with a rename in case //caveat: if we have a hardlink referenced by two different paths, the source one will be unlinked => fine, but not exactly a "rename"... @@ -376,7 +370,7 @@ void zen::moveAndRenameItem(const Zstring& pathFrom, const Zstring& pathTo, bool namespace { -void setWriteTimeNative(const Zstring& itemPath, const struct ::timespec& modTime, ProcSymlink procSl) //throw FileError +void setWriteTimeNative(const Zstring& itemPath, const timespec& modTime, ProcSymlink procSl) //throw FileError { /* [2013-05-01] sigh, we can't use utimensat() on NTFS volumes on Ubuntu: silent failure!!! what morons are programming this shit??? @@ -388,12 +382,12 @@ void setWriteTimeNative(const Zstring& itemPath, const struct ::timespec& modTim => let's give utimensat another chance: using open()/futimens() for regular files and utimensat(AT_SYMLINK_NOFOLLOW) for symlinks is consistent with "cp" and "touch"! */ - struct ::timespec newTimes[2] = {}; + timespec newTimes[2] = {}; newTimes[0].tv_sec = ::time(nullptr); //access time; using UTIME_OMIT for tv_nsec would trigger even more bugs: https://freefilesync.org/forum/viewtopic.php?t=1701 newTimes[1] = modTime; //modification time //test: even modTime == 0 is correctly applied (no NOOP!) test2: same behavior for "utime()" - if (procSl == ProcSymlink::FOLLOW) + if (procSl == ProcSymlink::follow) { //hell knows why files on gvfs-mounted Samba shares fail to open(O_WRONLY) returning EOPNOTSUPP: //https://freefilesync.org/forum/viewtopic.php?t=2803 => utimensat() works (but not for gvfs SFTP) @@ -422,10 +416,8 @@ void setWriteTimeNative(const Zstring& itemPath, const struct ::timespec& modTim void zen::setFileTime(const Zstring& filePath, time_t modTime, ProcSymlink procSl) //throw FileError { - struct ::timespec writeTime = {}; - writeTime.tv_sec = modTime; - setWriteTimeNative(filePath, writeTime, procSl); //throw FileError - + setWriteTimeNative(filePath, timetToNativeFileTime(modTime), + procSl); //throw FileError } @@ -442,7 +434,7 @@ namespace void copySecurityContext(const Zstring& source, const Zstring& target, ProcSymlink procSl) //throw FileError { security_context_t contextSource = nullptr; - const int rv = procSl == ProcSymlink::FOLLOW ? + const int rv = procSl == ProcSymlink::follow ? ::getfilecon (source.c_str(), &contextSource) : ::lgetfilecon(source.c_str(), &contextSource); if (rv < 0) @@ -457,7 +449,7 @@ void copySecurityContext(const Zstring& source, const Zstring& target, ProcSymli { security_context_t contextTarget = nullptr; - const int rv2 = procSl == ProcSymlink::FOLLOW ? + const int rv2 = procSl == ProcSymlink::follow ? ::getfilecon(target.c_str(), &contextTarget) : ::lgetfilecon(target.c_str(), &contextTarget); if (rv2 < 0) @@ -475,7 +467,7 @@ void copySecurityContext(const Zstring& source, const Zstring& target, ProcSymli } } - const int rv3 = procSl == ProcSymlink::FOLLOW ? + const int rv3 = procSl == ProcSymlink::follow ? ::setfilecon(target.c_str(), contextSource) : ::lsetfilecon(target.c_str(), contextSource); if (rv3 < 0) @@ -493,8 +485,8 @@ void zen::copyItemPermissions(const Zstring& sourcePath, const Zstring& targetPa copySecurityContext(sourcePath, targetPath, procSl); //throw FileError #endif - struct ::stat fileInfo = {}; - if (procSl == ProcSymlink::FOLLOW) + struct stat fileInfo = {}; + if (procSl == ProcSymlink::follow) { if (::stat(sourcePath.c_str(), &fileInfo) != 0) THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtPath(sourcePath)), "stat"); @@ -614,22 +606,22 @@ void zen::copySymlink(const Zstring& sourcePath, const Zstring& targetPath) //th catch (FileError&) {}); //file times: essential for syncing a symlink: enforce this! (don't just try!) - struct ::stat sourceInfo = {}; + struct stat sourceInfo = {}; if (::lstat(sourcePath.c_str(), &sourceInfo) != 0) THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourcePath)), "lstat"); - setWriteTimeNative(targetPath, sourceInfo.st_mtim, ProcSymlink::DIRECT); //throw FileError + setWriteTimeNative(targetPath, sourceInfo.st_mtim, ProcSymlink::direct); //throw FileError } FileCopyResult zen::copyNewFile(const Zstring& sourceFile, const Zstring& targetFile, //throw FileError, ErrorTargetExisting, (ErrorFileLocked), X - const IOCallback& notifyUnbufferedIO /*throw X*/) + const IoCallback& notifyUnbufferedIO /*throw X*/) { int64_t totalUnbufferedIO = 0; FileInput fileIn(sourceFile, IOCallbackDivider(notifyUnbufferedIO, totalUnbufferedIO)); //throw FileError, (ErrorFileLocked -> Windows-only) - struct ::stat sourceInfo = {}; + struct stat sourceInfo = {}; if (::fstat(fileIn.getHandle(), &sourceInfo) != 0) THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourceFile)), "fstat"); @@ -637,7 +629,7 @@ FileCopyResult zen::copyNewFile(const Zstring& sourceFile, const Zstring& target //it seems we don't need S_IWUSR, not even for the setFileTime() below! (tested with source file having different user/group!) //=> need copyItemPermissions() only for "chown" and umask-agnostic permissions - const int fdTarget = ::open(targetFile.c_str(), O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, mode); + const int fdTarget = ::open(targetFile.c_str(), O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, mode); if (fdTarget == -1) { const int ec = errno; //copy before making other system calls! @@ -659,7 +651,7 @@ FileCopyResult zen::copyNewFile(const Zstring& sourceFile, const Zstring& target //flush intermediate buffers before fiddling with the raw file handle fileOut.flushBuffers(); //throw FileError, X - struct ::stat targetInfo = {}; + struct stat targetInfo = {}; if (::fstat(fileOut.getHandle(), &targetInfo) != 0) THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(targetFile)), "fstat"); @@ -673,12 +665,12 @@ FileCopyResult zen::copyNewFile(const Zstring& sourceFile, const Zstring& target std::optional errorModTime; try { - //we cannot set the target file times (::futimes) while the file descriptor is still open after a write operation: - //this triggers bugs on samba shares where the modification time is set to current time instead. - //Linux: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=340236 - // http://comments.gmane.org/gmane.linux.file-systems.cifs/2854 - //OS X: https://freefilesync.org/forum/viewtopic.php?t=356 - setWriteTimeNative(targetFile, sourceInfo.st_mtim, ProcSymlink::FOLLOW); //throw FileError + /* we cannot set the target file times (::futimes) while the file descriptor is still open after a write operation: + this triggers bugs on Samba shares where the modification time is set to current time instead. + Linux: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=340236 + http://comments.gmane.org/gmane.linux.file-systems.cifs/2854 + macOS: https://freefilesync.org/forum/viewtopic.php?t=356 */ + setWriteTimeNative(targetFile, sourceInfo.st_mtim, ProcSymlink::follow); //throw FileError } catch (const FileError& e) { @@ -687,9 +679,9 @@ FileCopyResult zen::copyNewFile(const Zstring& sourceFile, const Zstring& target FileCopyResult result; result.fileSize = sourceInfo.st_size; - result.modTime = sourceInfo.st_mtim.tv_sec; // - result.sourceFileId = generateFileId(sourceInfo); - result.targetFileId = generateFileId(targetInfo); + result.sourceModTime = sourceInfo.st_mtim; + result.sourceFileIdx = sourceInfo.st_ino; + result.targetFileIdx = targetInfo.st_ino; result.errorModTime = errorModTime; return result; } diff --git a/zen/file_access.h b/zen/file_access.h index a3fa56d7..f3ea6c00 100644 --- a/zen/file_access.h +++ b/zen/file_access.h @@ -10,9 +10,8 @@ #include #include "zstring.h" #include "file_error.h" -#include "file_id_def.h" -#include "serialize.h" - +#include "serialize.h" //IoCallback + #include namespace zen { @@ -31,6 +30,21 @@ std::optional getParentFolderPath(const Zstring& itemPath); bool fileAvailable(const Zstring& filePath); //noexcept bool dirAvailable (const Zstring& dirPath ); // +//FAT/FAT32: "Why does the timestamp of a file *increase* by up to 2 seconds when I copy it to a USB thumb drive?" +const int FAT_FILE_TIME_PRECISION_SEC = 2; //https://devblogs.microsoft.com/oldnewthing/?p=83 +//https://web.archive.org/web/20141127143832/http://support.microsoft.com/kb/127830 + +using FileIndex = ino_t; +using FileTimeNative = timespec; + +inline time_t nativeFileTimeToTimeT(const timespec& ft) { return ft.tv_sec; } //follow Windows Explorer and always round down! +inline timespec timetToNativeFileTime(time_t utcTime) +{ + timespec natTime = {}; + natTime.tv_sec = utcTime; + return natTime; +} + enum class ItemType { file, @@ -47,14 +61,13 @@ std::optional itemStillExists(const Zstring& itemPath); //throw FileEr enum class ProcSymlink { - DIRECT, - FOLLOW + direct, + follow }; void setFileTime(const Zstring& filePath, time_t modTime, ProcSymlink procSl); //throw FileError //symlink handling: follow int64_t getFreeDiskSpace(const Zstring& path); //throw FileError, returns < 0 if not available -VolumeId getVolumeId(const Zstring& itemPath); //throw FileError uint64_t getFileSize(const Zstring& filePath); //throw FileError //get per-user directory designated for temporary files: @@ -87,16 +100,15 @@ void copySymlink(const Zstring& sourcePath, const Zstring& targetPath); //throw struct FileCopyResult { uint64_t fileSize = 0; - time_t modTime = 0; //number of seconds since Jan. 1st 1970 UTC - FileId sourceFileId; - FileId targetFileId; + FileTimeNative sourceModTime = {}; + FileIndex sourceFileIdx = 0; + FileIndex targetFileIdx = 0; std::optional errorModTime; //failure to set modification time }; FileCopyResult copyNewFile(const Zstring& sourceFile, const Zstring& targetFile, //throw FileError, ErrorTargetExisting, ErrorFileLocked, X //accummulated delta != file size! consider ADS, sparse, compressed files - const IOCallback& notifyUnbufferedIO /*throw X*/); - + const IoCallback& notifyUnbufferedIO /*throw X*/); } #endif //FILE_ACCESS_H_8017341345614857 diff --git a/zen/file_id_def.h b/zen/file_id_def.h deleted file mode 100644 index d2d104d5..00000000 --- a/zen/file_id_def.h +++ /dev/null @@ -1,46 +0,0 @@ -// ***************************************************************************** -// * This file is part of the FreeFileSync project. It is distributed under * -// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * -// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * -// ***************************************************************************** - -#ifndef FILE_ID_DEF_H_013287632486321493 -#define FILE_ID_DEF_H_013287632486321493 - - #include - - -namespace zen -{ -namespace impl { typedef struct ::stat StatDummy; } //sigh... - -using VolumeId = decltype(impl::StatDummy::st_dev); -using FileIndex = decltype(impl::StatDummy::st_ino); - - -struct FileId //always available on Linux, and *generally* available on Windows) -{ - FileId() {} - FileId(VolumeId volId, FileIndex fIdx) : volumeId(volId), fileIndex(fIdx) - { - if (volId == 0 || fIdx == 0) - { - volumeId = 0; - fileIndex = 0; - } - } - VolumeId volumeId = 0; - FileIndex fileIndex = 0; - - bool operator==(const FileId&) const = default; -}; - - -inline -FileId generateFileId(const struct ::stat& fileInfo) -{ - return FileId(fileInfo.st_dev, fileInfo.st_ino); -} -} - -#endif //FILE_ID_DEF_H_013287632486321493 diff --git a/zen/file_io.cpp b/zen/file_io.cpp index e081335d..f575a366 100644 --- a/zen/file_io.cpp +++ b/zen/file_io.cpp @@ -41,7 +41,7 @@ namespace FileBase::FileHandle openHandleForRead(const Zstring& filePath) //throw FileError, ErrorFileLocked { //caveat: check for file types that block during open(): character device, block device, named pipe - struct ::stat fileInfo = {}; + struct stat fileInfo = {}; if (::stat(filePath.c_str(), &fileInfo) == 0) //follows symlinks { if (!S_ISREG(fileInfo.st_mode) && @@ -74,11 +74,11 @@ FileBase::FileHandle openHandleForRead(const Zstring& filePath) //throw FileErro } -FileInput::FileInput(FileHandle handle, const Zstring& filePath, const IOCallback& notifyUnbufferedIO) : +FileInput::FileInput(FileHandle handle, const Zstring& filePath, const IoCallback& notifyUnbufferedIO) : FileBase(handle, filePath), notifyUnbufferedIO_(notifyUnbufferedIO) {} -FileInput::FileInput(const Zstring& filePath, const IOCallback& notifyUnbufferedIO) : +FileInput::FileInput(const Zstring& filePath, const IoCallback& notifyUnbufferedIO) : FileBase(openHandleForRead(filePath), filePath), //throw FileError, ErrorFileLocked notifyUnbufferedIO_(notifyUnbufferedIO) { @@ -166,8 +166,13 @@ FileBase::FileHandle openHandleForWrite(const Zstring& filePath) //throw FileErr { //checkForUnsupportedType(filePath); -> not needed, open() + O_WRONLY should fail fast - const int fdFile = ::open(filePath.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC | /*access == FileOutput::ACC_OVERWRITE ? O_TRUNC : */ O_EXCL, - S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); //0666 => umask will be applied implicitly! + const mode_t lockFileMode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; //0666 => umask will be applied implicitly! + + //O_EXCL contains a race condition on NFS file systems: https://linux.die.net/man/2/open + const int fdFile = ::open(filePath.c_str(), //const char* pathname + O_CREAT | //int flags + /*access == FileOutput::ACC_OVERWRITE ? O_TRUNC : */ O_EXCL | O_WRONLY | O_CLOEXEC, + lockFileMode); //mode_t mode if (fdFile == -1) { const int ec = errno; //copy before making other system calls! @@ -185,13 +190,13 @@ FileBase::FileHandle openHandleForWrite(const Zstring& filePath) //throw FileErr } -FileOutput::FileOutput(FileHandle handle, const Zstring& filePath, const IOCallback& notifyUnbufferedIO) : +FileOutput::FileOutput(FileHandle handle, const Zstring& filePath, const IoCallback& notifyUnbufferedIO) : FileBase(handle, filePath), notifyUnbufferedIO_(notifyUnbufferedIO) { } -FileOutput::FileOutput(const Zstring& filePath, const IOCallback& notifyUnbufferedIO) : +FileOutput::FileOutput(const Zstring& filePath, const IoCallback& notifyUnbufferedIO) : FileBase(openHandleForWrite(filePath), filePath), notifyUnbufferedIO_(notifyUnbufferedIO) {} //throw FileError, ErrorTargetExisting @@ -298,8 +303,8 @@ void FileOutput::reserveSpace(uint64_t expectedSize) //throw FileError //don't use ::posix_fallocate which uses horribly inefficient fallback if FS doesn't support it (EOPNOTSUPP) and changes files size! //FALLOC_FL_KEEP_SIZE => allocate only, file size is NOT changed! - if (::fallocate(getHandle(), //int fd, - FALLOC_FL_KEEP_SIZE, //int mode, + if (::fallocate(getHandle(), //int fd + FALLOC_FL_KEEP_SIZE, //int mode 0, //off_t offset expectedSize) != 0) //off_t len if (errno != EOPNOTSUPP) //possible, unlike with posix_fallocate() @@ -308,19 +313,19 @@ void FileOutput::reserveSpace(uint64_t expectedSize) //throw FileError } -std::string zen::getFileContent(const Zstring& filePath, const IOCallback& notifyUnbufferedIO /*throw X*/) //throw FileError, X +std::string zen::getFileContent(const Zstring& filePath, const IoCallback& notifyUnbufferedIO /*throw X*/) //throw FileError, X { FileInput streamIn(filePath, notifyUnbufferedIO); //throw FileError, ErrorFileLocked return bufferedLoad(streamIn); //throw FileError, X } -void zen::setFileContent(const Zstring& filePath, const std::string& byteStream, const IOCallback& notifyUnbufferedIO /*throw X*/) //throw FileError, X +void zen::setFileContent(const Zstring& filePath, const std::string& byteStream, const IoCallback& notifyUnbufferedIO /*throw X*/) //throw FileError, X { TempFileOutput fileOut(filePath, notifyUnbufferedIO); //throw FileError if (!byteStream.empty()) { - //preallocate disk space + reduce fragmentation + //preallocate disk space & reduce fragmentation fileOut.reserveSpace(byteStream.size()); //throw FileError fileOut.write(&byteStream[0], byteStream.size()); //throw FileError, X } diff --git a/zen/file_io.h b/zen/file_io.h index a7385241..3d1dfee7 100644 --- a/zen/file_io.h +++ b/zen/file_io.h @@ -32,8 +32,7 @@ public: FileHandle getHandle() { return hFile_; } //Windows: use 64kB ?? https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-2000-server/cc938632%28v=technet.10%29 - //Linux: use st_blksize? - //macOS: use f_iosize? + //macOS, Linux: use st_blksize? static size_t getBlockSize() { return 128 * 1024; }; const Zstring& getFilePath() const { return filePath_; } @@ -57,15 +56,15 @@ private: class FileInput : public FileBase { public: - FileInput( const Zstring& filePath, const IOCallback& notifyUnbufferedIO /*throw X*/); //throw FileError, ErrorFileLocked - FileInput(FileHandle handle, const Zstring& filePath, const IOCallback& notifyUnbufferedIO /*throw X*/); //takes ownership! + FileInput( const Zstring& filePath, const IoCallback& notifyUnbufferedIO /*throw X*/); //throw FileError, ErrorFileLocked + FileInput(FileHandle handle, const Zstring& filePath, const IoCallback& notifyUnbufferedIO /*throw X*/); //takes ownership! size_t read(void* buffer, size_t bytesToRead); //throw FileError, ErrorFileLocked, X; return "bytesToRead" bytes unless end of stream! private: size_t tryRead(void* buffer, size_t bytesToRead); //throw FileError, ErrorFileLocked; may return short, only 0 means EOF! => CONTRACT: bytesToRead > 0! - const IOCallback notifyUnbufferedIO_; //throw X + const IoCallback notifyUnbufferedIO_; //throw X std::vector memBuf_ = std::vector(getBlockSize()); size_t bufPos_ = 0; @@ -76,8 +75,8 @@ private: class FileOutput : public FileBase { public: - FileOutput( const Zstring& filePath, const IOCallback& notifyUnbufferedIO /*throw X*/); //throw FileError, ErrorTargetExisting - FileOutput(FileHandle handle, const Zstring& filePath, const IOCallback& notifyUnbufferedIO /*throw X*/); //takes ownership! + FileOutput( const Zstring& filePath, const IoCallback& notifyUnbufferedIO /*throw X*/); //throw FileError, ErrorTargetExisting + FileOutput(FileHandle handle, const Zstring& filePath, const IoCallback& notifyUnbufferedIO /*throw X*/); //takes ownership! ~FileOutput(); void reserveSpace(uint64_t expectedSize); //throw FileError @@ -91,7 +90,7 @@ public: private: size_t tryWrite(const void* buffer, size_t bytesToWrite); //throw FileError; may return short! CONTRACT: bytesToWrite > 0 - IOCallback notifyUnbufferedIO_; //throw X + IoCallback notifyUnbufferedIO_; //throw X std::vector memBuf_ = std::vector(getBlockSize()); size_t bufPos_ = 0; size_t bufPosEnd_ = 0; @@ -102,7 +101,7 @@ private: class TempFileOutput { public: - TempFileOutput( const Zstring& filePath, const IOCallback& notifyUnbufferedIO /*throw X*/) : //throw FileError + TempFileOutput( const Zstring& filePath, const IoCallback& notifyUnbufferedIO /*throw X*/) : //throw FileError filePath_(filePath), tmpFile_(tmpFilePath_, notifyUnbufferedIO) {} //throw FileError, (ErrorTargetExisting) @@ -110,7 +109,7 @@ public: void write(const void* buffer, size_t bytesToWrite) { tmpFile_.write(buffer, bytesToWrite); } //throw FileError, X - FileOutput& refTempFile() { return tmpFile_; } + FileOutput& refTempFile() { return tmpFile_; } void commit() //throw FileError, X { @@ -133,10 +132,10 @@ private: }; -[[nodiscard]] std::string getFileContent(const Zstring& filePath, const IOCallback& notifyUnbufferedIO /*throw X*/); //throw FileError, X +[[nodiscard]] std::string getFileContent(const Zstring& filePath, const IoCallback& notifyUnbufferedIO /*throw X*/); //throw FileError, X //overwrites if existing + transactional! :) -void setFileContent(const Zstring& filePath, const std::string& bytes, const IOCallback& notifyUnbufferedIO /*throw X*/); //throw FileError, X +void setFileContent(const Zstring& filePath, const std::string& bytes, const IoCallback& notifyUnbufferedIO /*throw X*/); //throw FileError, X } #endif //FILE_IO_H_89578342758342572345 diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp index aa48cb85..f1b5519b 100644 --- a/zen/file_traverser.cpp +++ b/zen/file_traverser.cpp @@ -31,7 +31,7 @@ void zen::traverseFolder(const Zstring& dirPath, for (;;) { errno = 0; - const struct ::dirent* dirEntry = ::readdir(folder); //don't use readdir_r(), see comment in native.cpp + const dirent* dirEntry = ::readdir(folder); //don't use readdir_r(), see comment in native.cpp if (!dirEntry) { if (errno == 0) //errno left unchanged => no more items @@ -54,7 +54,7 @@ void zen::traverseFolder(const Zstring& dirPath, const Zstring& itemPath = appendSeparator(dirPath) + itemName; - struct ::stat statData = {}; + struct stat statData = {}; try { if (::lstat(itemPath.c_str(), &statData) != 0) //lstat() does not resolve symlinks @@ -75,12 +75,12 @@ void zen::traverseFolder(const Zstring& dirPath, else if (S_ISDIR(statData.st_mode)) //a directory { if (onFolder) - onFolder({ itemName, itemPath }); + onFolder({itemName, itemPath}); } else //a file or named pipe, etc. { if (onFile) - onFile({ itemName, itemPath, makeUnsigned(statData.st_size), statData.st_mtime }); + onFile({itemName, itemPath, makeUnsigned(statData.st_size), statData.st_mtime}); } /* It may be a good idea to not check "S_ISREG(statData.st_mode)" explicitly and to not issue an error message on other types to support these scenarios: diff --git a/zen/format_unit.cpp b/zen/format_unit.cpp index 28943de7..b2d1b59a 100644 --- a/zen/format_unit.cpp +++ b/zen/format_unit.cpp @@ -128,9 +128,9 @@ std::wstring roundToBlock(double timeInHigh, std::wstring zen::formatRemainingTime(double timeInSec) { - const int steps10[] = { 1, 2, 5, 10 }; - const int steps24[] = { 1, 2, 3, 4, 6, 8, 12, 24 }; - const int steps60[] = { 1, 2, 5, 10, 15, 20, 30, 60 }; + const int steps10[] = {1, 2, 5, 10}; + const int steps24[] = {1, 2, 3, 4, 6, 8, 12, 24}; + const int steps60[] = {1, 2, 5, 10, 15, 20, 30, 60}; //determine preferred unit double timeInUnit = timeInSec; @@ -178,7 +178,7 @@ std::wstring zen::formatUtcToLocalTime(time_t utcTime) { auto errorMsg = [&] { return _("Error") + L" (time_t: " + numberTo(utcTime) + L')'; }; - TimeComp loc = getLocalTime(utcTime); + const TimeComp& loc = getLocalTime(utcTime); std::wstring dateString = utfTo(formatTime(Zstr("%x %X"), loc)); return !dateString.empty() ? dateString : errorMsg(); diff --git a/zen/http.cpp b/zen/http.cpp index 5d389719..f8fb24a3 100644 --- a/zen/http.cpp +++ b/zen/http.cpp @@ -23,7 +23,7 @@ public: bool disableGetCache /*not relevant for POST (= never cached)*/, const Zstring& userAgent, const Zstring* caCertFilePath /*optional: enable certificate validation*/, - const IOCallback& notifyUnbufferedIO) : //throw SysError, X + const IoCallback& notifyUnbufferedIO) : //throw SysError, X notifyUnbufferedIO_(notifyUnbufferedIO) { ZEN_ON_SCOPE_FAIL(cleanup(); /*destructor call would lead to member double clean-up!!!*/); @@ -214,7 +214,7 @@ private: int64_t contentRemaining_ = -1; //consider "Content-Length" if available - const IOCallback notifyUnbufferedIO_; //throw X + const IoCallback notifyUnbufferedIO_; //throw X std::vector memBuf_ = std::vector(getBlockSize()); size_t bufPos_ = 0; //buffered I/O; see file_io.cpp @@ -240,7 +240,7 @@ std::unique_ptr sendHttpRequestImpl(const Zstring& url, const std::string& contentType, //required for POST const Zstring& userAgent, const Zstring* caCertFilePath /*optional: enable certificate validation*/, - const IOCallback& notifyUnbufferedIO) //throw SysError, X + const IoCallback& notifyUnbufferedIO) //throw SysError, X { Zstring urlRed = url; //"A user agent should not automatically redirect a request more than five times, since such redirections usually indicate an infinite loop." @@ -339,14 +339,14 @@ std::vector> zen::xWwwFormUrlDecode(const st } -HttpInputStream zen::sendHttpGet(const Zstring& url, const Zstring& userAgent, const Zstring* caCertFilePath, const IOCallback& notifyUnbufferedIO) //throw SysError, X +HttpInputStream zen::sendHttpGet(const Zstring& url, const Zstring& userAgent, const Zstring* caCertFilePath, const IoCallback& notifyUnbufferedIO) //throw SysError, X { return sendHttpRequestImpl(url, nullptr /*postBuf*/, "" /*contentType*/, userAgent, caCertFilePath, notifyUnbufferedIO); //throw SysError, X, X } HttpInputStream zen::sendHttpPost(const Zstring& url, const std::vector>& postParams, - const Zstring& userAgent, const Zstring* caCertFilePath, const IOCallback& notifyUnbufferedIO) //throw SysError, X + const Zstring& userAgent, const Zstring* caCertFilePath, const IoCallback& notifyUnbufferedIO) //throw SysError, X { return sendHttpPost(url, xWwwFormUrlEncode(postParams), "application/x-www-form-urlencoded", userAgent, caCertFilePath, notifyUnbufferedIO); //throw SysError, X } @@ -354,7 +354,7 @@ HttpInputStream zen::sendHttpPost(const Zstring& url, const std::vector>& postParams, const Zstring& userAgent, const Zstring* caCertFilePath /*optional: enable certificate validation*/, - const IOCallback& notifyUnbufferedIO /*throw X*/); //throw SysError, X + const IoCallback& notifyUnbufferedIO /*throw X*/); //throw SysError, X HttpInputStream sendHttpPost(const Zstring& url, const std::string& postBuf, const std::string& contentType, const Zstring& userAgent, const Zstring* caCertFilePath /*optional: enable certificate validation*/, - const IOCallback& notifyUnbufferedIO /*throw X*/); //throw SysError, X + const IoCallback& notifyUnbufferedIO /*throw X*/); //throw SysError, X bool internetIsAlive(); //noexcept std::wstring formatHttpError(int httpStatus); diff --git a/zen/json.h b/zen/json.h index a3740664..f6458d6a 100644 --- a/zen/json.h +++ b/zen/json.h @@ -372,7 +372,7 @@ public: if (*it == '"') { Token tk(Token::Type::string); - tk.primVal = jsonUnescape({ pos_, it }); + tk.primVal = jsonUnescape({pos_, it}); pos_ = ++it; return tk; } diff --git a/zen/open_ssl.cpp b/zen/open_ssl.cpp index ea77db43..7c94263a 100644 --- a/zen/open_ssl.cpp +++ b/zen/open_ssl.cpp @@ -79,7 +79,7 @@ std::wstring formatLastOpenSSLError(const char* functionName) std::shared_ptr generateRsaKeyPair(int bits) //throw SysError { - EVP_PKEY_CTX* keyCtx = ::EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, //int id, + EVP_PKEY_CTX* keyCtx = ::EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, //int id nullptr); //ENGINE* e if (!keyCtx) throw SysError(formatLastOpenSSLError("EVP_PKEY_CTX_new_id")); @@ -110,9 +110,9 @@ std::shared_ptr streamToEvpKey(const std::string& keyStream, BioToEvpF throw SysError(formatLastOpenSSLError("BIO_new_mem_buf")); ZEN_ON_SCOPE_EXIT(::BIO_free_all(bio)); - if (EVP_PKEY* evp = bioToEvp(bio, //BIO* bp, - nullptr, //EVP_PKEY** x, - nullptr, //pem_password_cb* cb, + if (EVP_PKEY* evp = bioToEvp(bio, //BIO* bp + nullptr, //EVP_PKEY** x + nullptr, //pem_password_cb* cb nullptr)) //void* u return std::shared_ptr(evp, ::EVP_PKEY_free); throw SysError(formatLastOpenSSLError(functionName)); @@ -128,9 +128,9 @@ std::shared_ptr streamToEvpKey(const std::string& keyStream, BioToRsaF throw SysError(formatLastOpenSSLError("BIO_new_mem_buf")); ZEN_ON_SCOPE_EXIT(::BIO_free_all(bio)); - RSA* rsa = bioToRsa(bio, //BIO* bp, - nullptr, //RSA** x, - nullptr, //pem_password_cb* cb, + RSA* rsa = bioToRsa(bio, //BIO* bp + nullptr, //RSA** x + nullptr, //pem_password_cb* cb nullptr); //void* u if (!rsa) throw SysError(formatLastOpenSSLError(functionName)); @@ -168,9 +168,9 @@ std::shared_ptr streamToKey(const std::string& keyStream, RsaStreamTyp } auto tmp = reinterpret_cast(keyStream.c_str()); - EVP_PKEY* evp = (publicKey ? ::d2i_PublicKey : ::d2i_PrivateKey)(EVP_PKEY_RSA, //int type, - nullptr, //EVP_PKEY** a, - &tmp, /*changes tmp pointer itself!*/ //const unsigned char** pp, + EVP_PKEY* evp = (publicKey ? ::d2i_PublicKey : ::d2i_PrivateKey)(EVP_PKEY_RSA, //int type + nullptr, //EVP_PKEY** a + &tmp, /*changes tmp pointer itself!*/ //const unsigned char** pp static_cast(keyStream.size())); //long length if (!evp) throw SysError(formatLastOpenSSLError(publicKey ? "d2i_PublicKey" : "d2i_PrivateKey")); @@ -238,23 +238,23 @@ std::string evpKeyToStream(EVP_PKEY* evp, RsaToBioFunc rsaToBio, const char* fun //fix OpenSSL API inconsistencies: int PEM_write_bio_PrivateKey2(BIO* bio, EVP_PKEY* key) { - return ::PEM_write_bio_PrivateKey(bio, //BIO* bp, - key, //EVP_PKEY* x, - nullptr, //const EVP_CIPHER* enc, - nullptr, //unsigned char* kstr, - 0, //int klen, - nullptr, //pem_password_cb* cb, + return ::PEM_write_bio_PrivateKey(bio, //BIO* bp + key, //EVP_PKEY* x + nullptr, //const EVP_CIPHER* enc + nullptr, //unsigned char* kstr + 0, //int klen + nullptr, //pem_password_cb* cb nullptr); //void* u } int PEM_write_bio_RSAPrivateKey2(BIO* bio, RSA* rsa) { - return ::PEM_write_bio_RSAPrivateKey(bio, //BIO* bp, - rsa, //RSA* x, - nullptr, //const EVP_CIPHER* enc, - nullptr, //unsigned char* kstr, - 0, //int klen, - nullptr, //pem_password_cb* cb, + return ::PEM_write_bio_RSAPrivateKey(bio, //BIO* bp + rsa, //RSA* x + nullptr, //const EVP_CIPHER* enc + nullptr, //unsigned char* kstr + 0, //int klen + nullptr, //pem_password_cb* cb nullptr); //void* u } @@ -286,7 +286,7 @@ std::string keyToStream(EVP_PKEY* evp, RsaStreamType streamType, bool publicKey) throw SysError(formatLastOpenSSLError(publicKey ? "i2d_PublicKey" : "i2d_PrivateKey")); ZEN_ON_SCOPE_EXIT(::OPENSSL_free(buf)); //memory is only allocated for bufSize > 0 - return { reinterpret_cast(buf), static_cast(bufSize) }; + return {reinterpret_cast(buf), static_cast(bufSize)}; } //================================================================================ @@ -299,29 +299,29 @@ std::string createSignature(const std::string& message, EVP_PKEY* privateKey) // throw SysError(formatSystemError("EVP_MD_CTX_create", L"", L"Unexpected failure.")); //no more error details ZEN_ON_SCOPE_EXIT(::EVP_MD_CTX_destroy(mdctx)); - if (::EVP_DigestSignInit(mdctx, //EVP_MD_CTX* ctx, - nullptr, //EVP_PKEY_CTX** pctx, - EVP_sha256(), //const EVP_MD* type, - nullptr, //ENGINE* e, + if (::EVP_DigestSignInit(mdctx, //EVP_MD_CTX* ctx + nullptr, //EVP_PKEY_CTX** pctx + EVP_sha256(), //const EVP_MD* type + nullptr, //ENGINE* e privateKey) != 1) //EVP_PKEY* pkey throw SysError(formatLastOpenSSLError("EVP_DigestSignInit")); - if (::EVP_DigestSignUpdate(mdctx, //EVP_MD_CTX* ctx, - message.c_str(), //const void* d, + if (::EVP_DigestSignUpdate(mdctx, //EVP_MD_CTX* ctx + message.c_str(), //const void* d message.size()) != 1) //size_t cnt throw SysError(formatLastOpenSSLError("EVP_DigestSignUpdate")); size_t sigLenMax = 0; //"first call to EVP_DigestSignFinal returns the maximum buffer size required" - if (::EVP_DigestSignFinal(mdctx, //EVP_MD_CTX* ctx, - nullptr, //unsigned char* sigret, + if (::EVP_DigestSignFinal(mdctx, //EVP_MD_CTX* ctx + nullptr, //unsigned char* sigret &sigLenMax) != 1) //size_t* siglen throw SysError(formatLastOpenSSLError("EVP_DigestSignFinal")); std::string signature(sigLenMax, '\0'); size_t sigLen = sigLenMax; - if (::EVP_DigestSignFinal(mdctx, //EVP_MD_CTX* ctx, - reinterpret_cast(&signature[0]), //unsigned char* sigret, + if (::EVP_DigestSignFinal(mdctx, //EVP_MD_CTX* ctx + reinterpret_cast(&signature[0]), //unsigned char* sigret &sigLen) != 1) //size_t* siglen throw SysError(formatLastOpenSSLError("EVP_DigestSignFinal")); @@ -338,20 +338,20 @@ void verifySignature(const std::string& message, const std::string& signature, E throw SysError(formatSystemError("EVP_MD_CTX_create", L"", L"Unexpected failure.")); //no more error details ZEN_ON_SCOPE_EXIT(::EVP_MD_CTX_destroy(mdctx)); - if (::EVP_DigestVerifyInit(mdctx, //EVP_MD_CTX* ctx, - nullptr, //EVP_PKEY_CTX** pctx, - EVP_sha256(), //const EVP_MD* type, - nullptr, //ENGINE* e, + if (::EVP_DigestVerifyInit(mdctx, //EVP_MD_CTX* ctx + nullptr, //EVP_PKEY_CTX** pctx + EVP_sha256(), //const EVP_MD* type + nullptr, //ENGINE* e publicKey) != 1) //EVP_PKEY* pkey throw SysError(formatLastOpenSSLError("EVP_DigestVerifyInit")); - if (::EVP_DigestVerifyUpdate(mdctx, //EVP_MD_CTX* ctx, - message.c_str(), //const void* d, + if (::EVP_DigestVerifyUpdate(mdctx, //EVP_MD_CTX* ctx + message.c_str(), //const void* d message.size()) != 1) //size_t cnt throw SysError(formatLastOpenSSLError("EVP_DigestVerifyUpdate")); - if (::EVP_DigestVerifyFinal(mdctx, //EVP_MD_CTX* ctx, - reinterpret_cast(signature.c_str()), //const unsigned char* sig, + if (::EVP_DigestVerifyFinal(mdctx, //EVP_MD_CTX* ctx + reinterpret_cast(signature.c_str()), //const unsigned char* sig signature.size()) != 1) //size_t siglen throw SysError(formatLastOpenSSLError("EVP_DigestVerifyFinal")); } @@ -735,10 +735,10 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: throw SysError(formatSystemError("EVP_CIPHER_CTX_new", L"", L"Unexpected failure.")); //no more error details ZEN_ON_SCOPE_EXIT(::EVP_CIPHER_CTX_free(cipCtx)); - if (::EVP_DecryptInit_ex(cipCtx, //EVP_CIPHER_CTX* ctx, - EVP_aes_256_cbc(), //const EVP_CIPHER* type, - nullptr, //ENGINE* impl, - key, //const unsigned char* key, => implied length of 256 bit! + if (::EVP_DecryptInit_ex(cipCtx, //EVP_CIPHER_CTX* ctx + EVP_aes_256_cbc(), //const EVP_CIPHER* type + nullptr, //ENGINE* impl + key, //const unsigned char* key => implied length of 256 bit! nullptr) != 1) //const unsigned char* iv throw SysError(formatLastOpenSSLError("EVP_DecryptInit_ex")); @@ -749,16 +749,16 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: //"EVP_DecryptUpdate() should have room for (inl + cipher_block_size) bytes" int decLen1 = 0; - if (::EVP_DecryptUpdate(cipCtx, //EVP_CIPHER_CTX* ctx, - reinterpret_cast(&privateBlob[0]), //unsigned char* out, - &decLen1, //int* outl, - reinterpret_cast(privateBlobEnc.c_str()), //const unsigned char* in, + if (::EVP_DecryptUpdate(cipCtx, //EVP_CIPHER_CTX* ctx + reinterpret_cast(&privateBlob[0]), //unsigned char* out + &decLen1, //int* outl + reinterpret_cast(privateBlobEnc.c_str()), //const unsigned char* in static_cast(privateBlobEnc.size())) != 1) //int inl throw SysError(formatLastOpenSSLError("EVP_DecryptUpdate")); int decLen2 = 0; - if (::EVP_DecryptFinal_ex(cipCtx, //EVP_CIPHER_CTX* ctx, - reinterpret_cast(&privateBlob[decLen1]), //unsigned char* outm, + if (::EVP_DecryptFinal_ex(cipCtx, //EVP_CIPHER_CTX* ctx + reinterpret_cast(&privateBlob[decLen1]), //unsigned char* outm &decLen2) != 1) //int* outl throw SysError(formatLastOpenSSLError("EVP_DecryptFinal_ex")); @@ -777,7 +777,7 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: { static_assert(std::endian::native == std::endian::little&& sizeof(n) >= 4); const char* numStr = reinterpret_cast(&n); - return { numStr[3], numStr[2], numStr[1], numStr[0] }; //big endian! + return {numStr[3], numStr[2], numStr[1], numStr[0]}; //big endian! }; const std::string macData = numToBeString(algorithm .size()) + algorithm + @@ -787,13 +787,13 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: numToBeString(privateBlob .size()) + privateBlob; char md[EVP_MAX_MD_SIZE] = {}; unsigned int mdLen = 0; - if (!::HMAC(EVP_sha1(), //const EVP_MD* evp_md, - macKey, //const void* key, - sizeof(macKey), //int key_len, - reinterpret_cast(macData.c_str()), //const unsigned char* d, - static_cast(macData.size()), //int n, - reinterpret_cast(md), //unsigned char* md, - &mdLen)) //unsigned int* md_len + if (!::HMAC(EVP_sha1(), //const EVP_MD* evp_md + macKey, //const void* key + sizeof(macKey), //int key_len + reinterpret_cast(macData.c_str()), //const unsigned char* d + static_cast(macData.size()), //int n + reinterpret_cast(md), //unsigned char* md + &mdLen)) //unsigned int* md_len throw SysError(formatSystemError("HMAC", L"", L"Unexpected failure.")); //no more error details const bool hashValid = mac == std::string_view(md, mdLen); @@ -979,10 +979,10 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: throw SysError(formatLastOpenSSLError("EC_POINT_new")); ZEN_ON_SCOPE_EXIT(::EC_POINT_free(ecPoint)); - if (::EC_POINT_oct2point(ecGroup, //const EC_GROUP* group, - ecPoint, //EC_POINT* p, - reinterpret_cast(&pointStream[0]), //const unsigned char* buf, - pointStream.size(), //size_t len, + if (::EC_POINT_oct2point(ecGroup, //const EC_GROUP* group + ecPoint, //EC_POINT* p + reinterpret_cast(&pointStream[0]), //const unsigned char* buf + pointStream.size(), //size_t len nullptr) != 1) //BN_CTX* ctx throw SysError(formatLastOpenSSLError("EC_POINT_oct2point")); @@ -1008,9 +1008,9 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: //const std::string pubStream = extractStringPub(); -> we don't need the public key const std::string priStream = extractStringPriv(); - EVP_PKEY* evpPriv = ::EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, //int type, - nullptr, //ENGINE* e, - reinterpret_cast(&priStream[0]), //const unsigned char* priv, + EVP_PKEY* evpPriv = ::EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, //int type + nullptr, //ENGINE* e + reinterpret_cast(&priStream[0]), //const unsigned char* priv priStream.size()); //size_t len if (!evpPriv) throw SysError(formatLastOpenSSLError("EVP_PKEY_new_raw_private_key")); diff --git a/zen/perf.h b/zen/perf.h index 6bc328bb..2ebf1955 100644 --- a/zen/perf.h +++ b/zen/perf.h @@ -23,21 +23,20 @@ static zen::PerfTimer perfTest(true); //startPaused perfTest.resume(); - ZEN_ON_SCOPE_EXIT(perfTest.pause()); -*/ + ZEN_ON_SCOPE_EXIT(perfTest.pause()); */ namespace zen { -//issue with wxStopWatch? https://freefilesync.org/forum/viewtopic.php?t=1426 -// => wxStopWatch implementation uses QueryPerformanceCounter: https://github.com/wxWidgets/wxWidgets/blob/17d72a48ffd4d8ff42eed070ac48ee2de50ceabd/src/common/stopwatch.cpp -// => whatever the problem was, it's almost certainly not caused by QueryPerformanceCounter(): -// MSDN: "How often does QPC roll over? Not less than 100 years from the most recent system boot" -// https://docs.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps#general-faq-about-qpc-and-tsc -// -// => using the system clock is problematic: https://freefilesync.org/forum/viewtopic.php?t=5280 -// -// std::chrono::system_clock wraps ::GetSystemTimePreciseAsFileTime() -// std::chrono::steady_clock wraps ::QueryPerformanceCounter() +/* issue with wxStopWatch? https://freefilesync.org/forum/viewtopic.php?t=1426 + - wxStopWatch implementation uses QueryPerformanceCounter: https://github.com/wxWidgets/wxWidgets/blob/17d72a48ffd4d8ff42eed070ac48ee2de50ceabd/src/common/stopwatch.cpp + - whatever the problem was, it's almost certainly not caused by QueryPerformanceCounter(): + MSDN: "How often does QPC roll over? Not less than 100 years from the most recent system boot" + https://docs.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps#general-faq-about-qpc-and-tsc + + - using the system clock is problematic: https://freefilesync.org/forum/viewtopic.php?t=5280 + + std::chrono::system_clock wraps ::GetSystemTimePreciseAsFileTime() + std::chrono::steady_clock wraps ::QueryPerformanceCounter() */ class StopWatch { public: diff --git a/zen/process_exec.cpp b/zen/process_exec.cpp index bbc87c51..b82c2565 100644 --- a/zen/process_exec.cpp +++ b/zen/process_exec.cpp @@ -117,7 +117,7 @@ std::pair processExecuteImpl(const Zstring& file if (::dup(fdLifeSignW) == -1) //O_CLOEXEC does NOT propagate with dup() THROW_LAST_SYS_ERROR("dup(fdLifeSignW)"); - std::vector argv{ filePath.c_str() }; + std::vector argv{filePath.c_str()}; for (const Zstring& arg : arguments) argv.push_back(arg.c_str()); argv.push_back(nullptr); @@ -147,6 +147,8 @@ std::pair processExecuteImpl(const Zstring& file if (flags == -1) THROW_LAST_SYS_ERROR("fcntl(F_GETFL)"); + //fcntl() success: Linux: 0 + // macOS: "Value other than -1." if (::fcntl(fdLifeSignR, F_SETFL, flags | O_NONBLOCK) == -1) THROW_LAST_SYS_ERROR("fcntl(F_SETFL, O_NONBLOCK)"); @@ -174,7 +176,7 @@ std::pair processExecuteImpl(const Zstring& file const auto waitTimeMs = std::chrono::duration_cast(endTime - now).count(); - struct ::timeval tv = {}; + timeval tv = {}; tv.tv_sec = static_cast(waitTimeMs / 1000); tv.tv_usec = static_cast(waitTimeMs - tv.tv_sec * 1000) * 1000; @@ -219,7 +221,7 @@ std::pair processExecuteImpl(const Zstring& file exitCode == 127) //details should have been streamed to STDERR: used by /bin/sh, e.g. failure to execute due to missing .so file throw SysError(utfTo(trimCpy(output))); - return { exitCode, output }; + return {exitCode, output}; } } diff --git a/zen/resolve_path.cpp b/zen/resolve_path.cpp index 76999500..0e714528 100644 --- a/zen/resolve_path.cpp +++ b/zen/resolve_path.cpp @@ -5,17 +5,12 @@ // ***************************************************************************** #include "resolve_path.h" -//#include //not necessarily included by ! -//#include #include "time.h" #include "thread.h" -//#include "utf.h" -//#include "scope_guard.h" -//#include "globals.h" #include "file_access.h" #include //getenv() - #include //getcwd + #include //getcwd() using namespace zen; @@ -251,7 +246,7 @@ std::vector zen::getFolderPathAliases(const Zstring& folderPathPhrase) tmp.erase(dirPath); tmp.erase(Zstring()); - return { tmp.begin(), tmp.end() }; + return {tmp.begin(), tmp.end()}; } diff --git a/zen/ring_buffer.h b/zen/ring_buffer.h index 240262fa..ae2377d8 100644 --- a/zen/ring_buffer.h +++ b/zen/ring_buffer.h @@ -196,11 +196,11 @@ public: using iterator = Iterator< RingBuffer, T>; using const_iterator = Iterator; - iterator begin() { return { *this, 0 }; } - iterator end () { return { *this, size_ }; } + iterator begin() { return {*this, 0 }; } + iterator end () { return {*this, size_}; } - const_iterator begin() const { return { *this, 0 }; } - const_iterator end () const { return { *this, size_ }; } + const_iterator begin() const { return {*this, 0 }; } + const_iterator end () const { return {*this, size_}; } const_iterator cbegin() const { return begin(); } const_iterator cend () const { return end (); } diff --git a/zen/serialize.h b/zen/serialize.h index 6c57e4ee..f9677630 100644 --- a/zen/serialize.h +++ b/zen/serialize.h @@ -35,7 +35,7 @@ struct BufferedInputStream Optional: support stream-copying -------------------------------- size_t getBlockSize() const; - const IOCallback& notifyUnbufferedIO + const IoCallback& notifyUnbufferedIO }; -------------------------------- @@ -47,9 +47,9 @@ struct BufferedOutputStream Optional: support stream-copying -------------------------------- - const IOCallback& notifyUnbufferedIO + const IoCallback& notifyUnbufferedIO }; */ -using IOCallback = std::function; //throw X +using IoCallback = std::function; //throw X //functions based on buffered stream abstraction @@ -75,7 +75,7 @@ template < class BufferedInputStream> void readArray (BufferedInputSt struct IOCallbackDivider { - IOCallbackDivider(const IOCallback& notifyUnbufferedIO, int64_t& totalUnbufferedIO) : totalUnbufferedIO_(totalUnbufferedIO), notifyUnbufferedIO_(notifyUnbufferedIO) {} + IOCallbackDivider(const IoCallback& notifyUnbufferedIO, int64_t& totalUnbufferedIO) : totalUnbufferedIO_(totalUnbufferedIO), notifyUnbufferedIO_(notifyUnbufferedIO) {} void operator()(int64_t bytesDelta) { @@ -85,7 +85,7 @@ struct IOCallbackDivider private: int64_t& totalUnbufferedIO_; - const IOCallback& notifyUnbufferedIO_; + const IoCallback& notifyUnbufferedIO_; }; @@ -206,7 +206,7 @@ void writeArray(BufferedOutputStream& stream, const void* buffer, size_t len) template inline void writeNumber(BufferedOutputStream& stream, const N& num) { - static_assert(IsArithmetic::value || std::is_same_v || std::is_enum_v); + static_assert(IsArithmeticV || std::is_same_v || std::is_enum_v); writeArray(stream, &num, sizeof(N)); } @@ -234,7 +234,7 @@ void readArray(BufferedInputStream& stream, void* buffer, size_t len) //throw Sy template inline N readNumber(BufferedInputStream& stream) //throw SysErrorUnexpectedEos { - static_assert(IsArithmetic::value || std::is_same_v || std::is_enum_v); + static_assert(IsArithmeticV || std::is_same_v || std::is_enum_v); N num{}; readArray(stream, &num, sizeof(N)); //throw SysErrorUnexpectedEos return num; diff --git a/zen/stl_tools.h b/zen/stl_tools.h index 495ff8d1..53b95241 100644 --- a/zen/stl_tools.h +++ b/zen/stl_tools.h @@ -128,7 +128,7 @@ void removeDuplicates(std::vector& v, CompLess less) template inline void removeDuplicates(std::vector& v) { - removeDuplicates(v, std::less(), std::equal_to()); + removeDuplicates(v, std::less(), std::equal_to()); } @@ -233,6 +233,7 @@ class FNV1aHash { public: FNV1aHash() {} + explicit FNV1aHash(Num startVal) : hashVal_(startVal) {} void add(Num n) { @@ -243,8 +244,8 @@ public: Num get() const { return hashVal_; } private: - static_assert(IsUnsignedInt::value); - static_assert(sizeof(Num) == 4 || sizeof(Num) == 8); //macOS: size_t is "unsigned long" + static_assert(IsUnsignedIntV); + static_assert(sizeof(Num) == 4 || sizeof(Num) == 8); static constexpr Num base_ = sizeof(Num) == 4 ? 2166136261U : 14695981039346656037ULL; static constexpr Num prime_ = sizeof(Num) == 4 ? 16777619U : 1099511628211ULL; @@ -257,7 +258,7 @@ Num hashArray(ByteIterator first, ByteIterator last) { using ValType = typename std::iterator_traits::value_type; static_assert(sizeof(ValType) <= sizeof(Num)); - static_assert(IsInteger::value || std::is_same_v || std::is_same_v); + static_assert(IsIntegerV || std::is_same_v || std::is_same_v); FNV1aHash hash; std::for_each(first, last, [&hash](ValType v) { hash.add(v); }); @@ -265,8 +266,7 @@ Num hashArray(ByteIterator first, ByteIterator last) } -//support for custom string classes in std::unordered_set/map -struct StringHash +struct StringHash //support for custom string classes with std::unordered_set/map { template size_t operator()(const String& str) const diff --git a/zen/string_tools.h b/zen/string_tools.h index 883c45b8..8150df05 100644 --- a/zen/string_tools.h +++ b/zen/string_tools.h @@ -395,7 +395,7 @@ std::vector split(const S& str, const T& delimiter, SplitOnEmpty soe) { if (str.empty() && soe == SplitOnEmpty::skip) return {}; - return { str }; + return {str}; } const auto* const delimFirst = strBegin(delimiter); @@ -800,9 +800,9 @@ template inline S numberTo(const Num& number) { using TypeTag = std::integral_constant::value ? impl::NumberType::signedInt : - IsUnsignedInt::value ? impl::NumberType::unsignedInt : - IsFloat ::value ? impl::NumberType::floatingPoint : + IsSignedIntV ? impl::NumberType::signedInt : + IsUnsignedIntV ? impl::NumberType::unsignedInt : + IsFloatV ? impl::NumberType::floatingPoint : impl::NumberType::other>; return impl::numberTo(number, TypeTag()); @@ -813,9 +813,9 @@ template inline Num stringTo(const S& str) { using TypeTag = std::integral_constant::value ? impl::NumberType::signedInt : - IsUnsignedInt::value ? impl::NumberType::unsignedInt : - IsFloat ::value ? impl::NumberType::floatingPoint : + IsSignedIntV ? impl::NumberType::signedInt : + IsUnsignedIntV ? impl::NumberType::unsignedInt : + IsFloatV ? impl::NumberType::floatingPoint : impl::NumberType::other>; return impl::stringTo(str, TypeTag()); @@ -836,7 +836,7 @@ std::pair hexify(unsigned char c, bool upperCase) else return static_cast('a' + (num - 10)); }; - return { hexifyDigit(c / 16), hexifyDigit(c % 16) }; + return {hexifyDigit(c / 16), hexifyDigit(c % 16)}; } diff --git a/zen/string_traits.h b/zen/string_traits.h index d9ce589c..ca40f7d6 100644 --- a/zen/string_traits.h +++ b/zen/string_traits.h @@ -120,19 +120,12 @@ public: }; } -template -struct IsStringLike : std::bool_constant::isStringLike> {}; -template -struct GetCharType { using Type = typename impl::StringTraits::CharType; }; - - -//template alias helpers: template -constexpr bool IsStringLikeV = IsStringLike::value; +constexpr bool IsStringLikeV = impl::StringTraits::isStringLike; template -using GetCharTypeT = typename GetCharType::Type; +using GetCharTypeT = typename impl::StringTraits::CharType; namespace impl diff --git a/zen/symlink_target.h b/zen/symlink_target.h index 42010fd2..32b1211d 100644 --- a/zen/symlink_target.h +++ b/zen/symlink_target.h @@ -70,11 +70,11 @@ Zstring getResolvedSymlinkPath_impl(const Zstring& linkPath) //throw FileError namespace zen { inline -SymlinkRawContent getSymlinkRawContent(const Zstring& linkPath) { return getSymlinkRawContent_impl(linkPath); } +SymlinkRawContent getSymlinkRawContent(const Zstring& linkPath) { return getSymlinkRawContent_impl(linkPath); } //throw FileError inline -Zstring getSymlinkResolvedPath(const Zstring& linkPath) { return getResolvedSymlinkPath_impl(linkPath); } +Zstring getSymlinkResolvedPath(const Zstring& linkPath) { return getResolvedSymlinkPath_impl(linkPath); } //throw FileError } diff --git a/zen/sys_info.cpp b/zen/sys_info.cpp index f6045f7e..1a0d18f5 100644 --- a/zen/sys_info.cpp +++ b/zen/sys_info.cpp @@ -25,17 +25,14 @@ using namespace zen; std::wstring zen::getUserName() //throw FileError { - const uid_t userIdNo = ::getuid(); //"real user ID"; never fails - - std::vector buffer(std::max(10000, ::sysconf(_SC_GETPW_R_SIZE_MAX))); //::sysconf may return long(-1) - struct passwd buffer2 = {}; - struct passwd* pwsEntry = nullptr; - if (::getpwuid_r(userIdNo, &buffer2, &buffer[0], buffer.size(), &pwsEntry) != 0) //getlogin() is deprecated and not working on Ubuntu at all!!! - THROW_LAST_FILE_ERROR(_("Cannot get process information."), "getpwuid_r"); - if (!pwsEntry) - throw FileError(_("Cannot get process information."), L"no login found"); //should not happen? - - return utfTo(pwsEntry->pw_name); + //https://linux.die.net/man/3/getlogin + //https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/getlogin.2.html + const char* loginUser = ::getlogin(); + if (!loginUser) + THROW_LAST_FILE_ERROR(_("Cannot get process information."), "getlogin"); + //getlogin() is smarter than simply evaluating $LOGNAME! even in contexts without + //$LOGNAME, e.g. "sudo su" on Ubuntu, it returns the correct non-root user! + return utfTo(loginUser); } @@ -64,7 +61,7 @@ ComputerModel zen::getComputerModel() //throw FileError cm.vendor = tryGetInfo("/sys/devices/virtual/dmi/id/sys_vendor"); // //clean up: - cm.model = beforeFirst(cm.model, L'\u00ff', IfNotFoundReturn::all); //fix broken BIOS entries: + cm.model = beforeFirst(cm.model, L'\u00ff', IfNotFoundReturn::all); //fix broken BIOS entries: cm.vendor = beforeFirst(cm.vendor, L'\u00ff', IfNotFoundReturn::all); //0xff can be considered 0 for (const char* dummyModel : @@ -119,21 +116,25 @@ Zstring zen::getRealProcessPath() //throw FileError } +Zstring zen::getUserDataPath() //throw FileError +{ + if (::getuid() != 0) //nofail; root(0) => consider as request for elevation, NOT impersonation + if (const char* xdgCfgPath = ::getenv("XDG_CONFIG_HOME"); //no extended error reporting + xdgCfgPath && xdgCfgPath[0] != 0) + return xdgCfgPath; + + return Zstring("/home/") + utfTo(getUserName()) + "/.config"; //throw FileError +} + + Zstring zen::getUserDownloadsPath() //throw FileError { try { - Zstring cmdLine; - if (getuid() == 0) //nofail; root(0) => consider as request for elevation, NOT impersonation - { - const char* loginUser = getlogin(); //https://linux.die.net/man/3/getlogin - if (!loginUser) - THROW_LAST_SYS_ERROR("getlogin"); - - cmdLine = Zstring("sudo -u ") + loginUser + " xdg-user-dir DOWNLOAD"; //sudo better be installed :> - } - else - cmdLine = "xdg-user-dir DOWNLOAD"; + const Zstring cmdLine = ::getuid() == 0 ? //nofail; root(0) => consider as request for elevation, NOT impersonation + //sudo better be installed :> + "sudo -u " + utfTo(getUserName()) + " xdg-user-dir DOWNLOAD" : //throw FileError + "xdg-user-dir DOWNLOAD"; const auto& [exitCode, output] = consoleExecute(cmdLine, std::nullopt /*timeoutMs*/); //throw SysError if (exitCode != 0) @@ -145,4 +146,3 @@ Zstring zen::getUserDownloadsPath() //throw FileError } catch (const SysError& e) { throw FileError(_("Cannot get process information."), e.toString()); } } - diff --git a/zen/sys_info.h b/zen/sys_info.h index 1b046fb6..4f83a9a3 100644 --- a/zen/sys_info.h +++ b/zen/sys_info.h @@ -31,6 +31,7 @@ std::wstring getOsDescription(); //throw FileError Zstring getRealProcessPath(); //throw FileError Zstring getUserDownloadsPath(); //throw FileError +Zstring getUserDataPath(); //throw FileError } diff --git a/zen/thread.cpp b/zen/thread.cpp index 89fa0233..e14afac7 100644 --- a/zen/thread.cpp +++ b/zen/thread.cpp @@ -28,7 +28,7 @@ const std::thread::id globalMainThreadId = std::this_thread::get_id(); bool zen::runningOnMainThread() { - if (globalMainThreadId == std::thread::id()) //called during static initialization! + if (globalMainThreadId == std::thread::id()) //if called during static initialization! return true; return std::this_thread::get_id() == globalMainThreadId; diff --git a/zen/thread.h b/zen/thread.h index 1bea95ea..136c7a5c 100644 --- a/zen/thread.h +++ b/zen/thread.h @@ -173,18 +173,18 @@ public: void run(Function&& wi /*should throw ThreadStopRequest when needed*/, bool insertFront = false) { { - std::lock_guard dummy(workLoad_->lock); + std::lock_guard dummy(workLoad_.ref().lock); if (insertFront) - workLoad_->tasks.push_front(std::move(wi)); + workLoad_.ref().tasks.push_front(std::move(wi)); else - workLoad_->tasks.push_back(std::move(wi)); - const size_t tasksPending = ++(workLoad_->tasksPending); + workLoad_.ref().tasks.push_back(std::move(wi)); + const size_t tasksPending = ++(workLoad_.ref().tasksPending); if (worker_.size() < std::min(tasksPending, threadCountMax_)) addWorkerThread(); } - workLoad_->conditionNewTask.notify_all(); + workLoad_.ref().conditionNewTask.notify_all(); } //context of controlling thread, blocking: @@ -203,12 +203,12 @@ public: //non-blocking wait()-alternative: context of controlling thread: void notifyWhenDone(const std::function& onCompletion /*noexcept! runs on worker thread!*/) { - std::lock_guard dummy(workLoad_->lock); + std::lock_guard dummy(workLoad_.ref().lock); - if (workLoad_->tasksPending == 0) + if (workLoad_.ref().tasksPending == 0) onCompletion(); else - workLoad_->onCompletionCallbacks.push_back(onCompletion); + workLoad_.ref().onCompletionCallbacks.push_back(onCompletion); } //context of controlling thread: @@ -222,27 +222,28 @@ private: { Zstring threadName = groupName_ + Zstr('[') + numberTo(worker_.size() + 1) + Zstr('/') + numberTo(threadCountMax_) + Zstr(']'); - worker_.emplace_back([wl = workLoad_, threadName = std::move(threadName)] //don't capture "this"! consider detach() and move operations + worker_.emplace_back([workLoad_ /*clang bug*/= workLoad_ /*share ownership!*/, threadName = std::move(threadName)]() mutable //don't capture "this"! consider detach() and move operations { setCurrentThreadName(threadName); + WorkLoad& workLoad = workLoad_.ref(); - std::unique_lock dummy(wl->lock); + std::unique_lock dummy(workLoad.lock); for (;;) { - interruptibleWait(wl->conditionNewTask, dummy, [&tasks = wl->tasks] { return !tasks.empty(); }); //throw ThreadStopRequest + interruptibleWait(workLoad.conditionNewTask, dummy, [&tasks = workLoad.tasks] { return !tasks.empty(); }); //throw ThreadStopRequest - Function task = std::move(wl->tasks. front()); //noexcept thanks to move - /**/ wl->tasks.pop_front(); // + Function task = std::move(workLoad.tasks. front()); //noexcept thanks to move + /**/ workLoad.tasks.pop_front(); // dummy.unlock(); task(); //throw ThreadStopRequest? dummy.lock(); - if (--(wl->tasksPending) == 0) - if (!wl->onCompletionCallbacks.empty()) + if (--(workLoad.tasksPending) == 0) + if (!workLoad.onCompletionCallbacks.empty()) { std::vector> callbacks; - callbacks.swap(wl->onCompletionCallbacks); + callbacks.swap(workLoad.onCompletionCallbacks); dummy.unlock(); for (const auto& cb : callbacks) @@ -263,7 +264,7 @@ private: }; std::vector worker_; - std::shared_ptr workLoad_ = std::make_shared(); + SharedRef workLoad_ = makeSharedRef(); bool detach_ = false; size_t threadCountMax_; Zstring groupName_; @@ -446,7 +447,7 @@ private: activeCondition_ = cv; } - std::atomic stopRequested_{ false }; //std:atomic is uninitialized by default!!! + std::atomic stopRequested_{false}; //std:atomic is uninitialized by default!!! //"The default constructor is trivial: no initialization takes place other than zero initialization of static and thread-local objects." std::condition_variable* activeCondition_ = nullptr; diff --git a/zen/time.h b/zen/time.h index aaf36983..903b2e87 100644 --- a/zen/time.h +++ b/zen/time.h @@ -271,7 +271,7 @@ TimeComp parseTime(const String& format, const String2& str) if (strLast - itStr < 3) return TimeComp(); - const char* months[] = { "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec" }; + const char* months[] = {"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"}; auto itMonth = std::find_if(std::begin(months), std::end(months), [&](const char* month) { return equalAsciiNoCase(makeStringView(itStr, 3), month); diff --git a/zen/type_traits.h b/zen/type_traits.h index 03fbd768..a4194c05 100644 --- a/zen/type_traits.h +++ b/zen/type_traits.h @@ -20,6 +20,7 @@ struct GetFirstOf }; template using GetFirstOfT = typename GetFirstOf::Type; + template class FunctionReturnType { @@ -48,40 +49,40 @@ template inline auto makeSigned (T t) { return static_cast inline auto makeUnsigned(T t) { return static_cast>(t); } //################# Built-in Types ######################## -//Example: "IsSignedInt::value" evaluates to "true" - //unfortunate standardized nonsense: std::is_integral<> includes bool, char, wchar_t! => roll our own: -template struct IsUnsignedInt; -template struct IsSignedInt; - -template using IsFloat = std::is_floating_point; -template using IsInteger = std::bool_constant::value || IsSignedInt::value>; -template using IsArithmetic = std::bool_constant::value || IsFloat ::value>; - -//remaining non-arithmetic types: bool, char, wchar_t - - -//optional: specialize new types like: -//template <> struct IsUnsignedInt : std::true_type {}; +template constexpr bool IsUnsignedIntV = std::is_same_v, unsigned char> || + std::is_same_v, unsigned short int> || + std::is_same_v, unsigned int> || + std::is_same_v, unsigned long int> || + std::is_same_v, unsigned long long int>; + +template constexpr bool IsSignedIntV = std::is_same_v, signed char> || + std::is_same_v, short int> || + std::is_same_v, int> || + std::is_same_v, long int> || + std::is_same_v, long long int>; + +template constexpr bool IsIntegerV = IsUnsignedIntV || IsSignedIntV; +template constexpr bool IsFloatV = std::is_floating_point_v; +template constexpr bool IsArithmeticV = IsIntegerV || IsFloatV; //################# Class Members ######################## /* Detect data or function members of a class by name: ZEN_INIT_DETECT_MEMBER + HasMember_ Example: 1. ZEN_INIT_DETECT_MEMBER(c_str); 2. HasMemberV_c_str -> use boolean -*/ -/* Detect data or function members of a class by name *and* type: ZEN_INIT_DETECT_MEMBER2 + HasMember_ + + Detect data or function members of a class by name *and* type: ZEN_INIT_DETECT_MEMBER2 + HasMember_ Example: 1. ZEN_INIT_DETECT_MEMBER2(size, size_t (T::*)() const); 2. HasMember_size::value -> use as boolean -*/ -/* Detect member type of a class: ZEN_INIT_DETECT_MEMBER_TYPE + HasMemberType_ + + Detect member type of a class: ZEN_INIT_DETECT_MEMBER_TYPE + HasMemberType_ Example: 1. ZEN_INIT_DETECT_MEMBER_TYPE(value_type); - 2. HasMemberTypeV_value_type -> use as boolean -*/ + 2. HasMemberTypeV_value_type -> use as boolean */ //########## Sorting ############################## /* @@ -114,25 +115,6 @@ LessDescending makeSortDirection(Predicate pred, std::false_type) { r //################ implementation ###################### -template -struct IsUnsignedInt : std::false_type {}; - -template <> struct IsUnsignedInt : std::true_type {}; -template <> struct IsUnsignedInt : std::true_type {}; -template <> struct IsUnsignedInt : std::true_type {}; -template <> struct IsUnsignedInt : std::true_type {}; -template <> struct IsUnsignedInt : std::true_type {}; - -template -struct IsSignedInt : std::false_type {}; - -template <> struct IsSignedInt : std::true_type {}; -template <> struct IsSignedInt : std::true_type {}; -template <> struct IsSignedInt : std::true_type {}; -template <> struct IsSignedInt : std::true_type {}; -template <> struct IsSignedInt : std::true_type {}; -//#################################################################### - #define ZEN_INIT_DETECT_MEMBER(NAME) \ \ template \ diff --git a/zenXml/zenxml/cvrt_text.h b/zenXml/zenxml/cvrt_text.h index 51b23173..058ffa30 100644 --- a/zenXml/zenxml/cvrt_text.h +++ b/zenXml/zenxml/cvrt_text.h @@ -131,7 +131,7 @@ template struct GetTextType : std::integral_constant ? TEXT_TYPE_BOOL : IsStringLikeV ? TEXT_TYPE_STRING : //string before number to correctly handle char/wchar_t -> this was an issue with Loki only! - IsArithmetic::value ? TEXT_TYPE_NUMBER : // + IsArithmeticV ? TEXT_TYPE_NUMBER : // IsChronoDuration::value ? TEXT_TYPE_CHRONO : TEXT_TYPE_OTHER> {}; diff --git a/zenXml/zenxml/dom.h b/zenXml/zenxml/dom.h index cfbd14c9..e77509bf 100644 --- a/zenXml/zenxml/dom.h +++ b/zenXml/zenxml/dom.h @@ -85,7 +85,7 @@ public: it->second->value = std::move(attrValue); else { - auto itBack = attributes_.insert(attributes_.end(), { attrName, std::move(attrValue) }); + auto itBack = attributes_.insert(attributes_.end(), {attrName, std::move(attrValue)}); attributesSorted_.emplace(std::move(attrName), itBack); } } @@ -207,10 +207,10 @@ public: \endcode \return A pair of STL begin/end iterators to access all child elements sequentially. */ - std::pair getChildren() const { return { childElements_.begin(), childElements_.end() }; } + std::pair getChildren() const { return {childElements_.begin(), childElements_.end()}; } ///\sa getChildren - std::pair getChildren() { return { childElements_.begin(), childElements_.end() }; } + std::pair getChildren() { return {childElements_.begin(), childElements_.end()}; } ///Get parent XML element, may be nullptr for root element XmlElement* parent() { return parent_; } @@ -231,9 +231,8 @@ public: for (auto it = iterPair.first; it != iterPair.second; ++it) std::cout << "name: " << it->name << " value: " << it->value << '\n'; \endcode - \return A pair of STL begin/end iterators to access all attributes sequentially as a list of name/value pairs of std::string. - */ - std::pair getAttributes() const { return { attributes_.begin(), attributes_.end() }; } + \return A pair of STL begin/end iterators to access all attributes sequentially as a list of name/value pairs of std::string. */ + std::pair getAttributes() const { return {attributes_.begin(), attributes_.end()}; } //swap two elements while keeping references to parent. -> disabled documentation extraction void swapSubtree(XmlElement& other) noexcept @@ -329,11 +328,11 @@ private: XmlDoc (const XmlDoc&) = delete; //not implemented, thanks to XmlElement::parent_ XmlDoc& operator=(const XmlDoc&) = delete; - std::string version_ { "1.0" }; - std::string encoding_{ "utf-8" }; + std::string version_ {"1.0"}; + std::string encoding_{"utf-8"}; std::string standalone_; - XmlElement root_{ "Root" }; + XmlElement root_{"Root"}; }; } diff --git a/zenXml/zenxml/parser.h b/zenXml/zenxml/parser.h index 7ec2433b..a4800ab3 100644 --- a/zenXml/zenxml/parser.h +++ b/zenXml/zenxml/parser.h @@ -423,15 +423,15 @@ private: using TokenList = std::vector>; const TokenList tokens_ { - { "", Token::TK_DECL_END }, - { "", Token::TK_SLASH_GREATER }, - { "<", Token::TK_LESS }, //evaluate after TK_DECL_BEGIN! - { ">", Token::TK_GREATER }, - { "=", Token::TK_EQUAL }, - { "\"", Token::TK_QUOTE }, - { "\'", Token::TK_QUOTE }, + {"", Token::TK_DECL_END }, + {"", Token::TK_SLASH_GREATER}, + {"<", Token::TK_LESS }, //evaluate after TK_DECL_BEGIN! + {">", Token::TK_GREATER }, + {"=", Token::TK_EQUAL }, + {"\"", Token::TK_QUOTE }, + {"\'", Token::TK_QUOTE }, }; const std::string xmlCommentBegin_ = "