diff options
172 files changed, 2904 insertions, 2830 deletions
diff --git a/Changelog.txt b/Changelog.txt index 44d49196..54ba2ebd 100755 --- a/Changelog.txt +++ b/Changelog.txt @@ -1,5 +1,23 @@ -FreeFileSync 10.0 ------------------ +FreeFileSync 10.1 [2018-06-03] +------------------------------ +Binary-compare multiple files in parallel +Copy file permissions when creating base folders +Fixed hang when scrolling file list (Windows) +Fixed file list mismatch when cancelling sync +Fixed delay when cancelling folder existence check +Fixed comparison and sync processing order to honor FIFO +Fixed startup delay when internet is offline (Linux, macOS) +Fixed crash when closing FreeFileSync via the macOS Dock +Support installation without admin rights (macOS) +Fixed bcrypt.dll not found on startup (Windows XP) +Respect Content-Length header for HTTP requests +Support parallel folder traversal on Ubuntu 16.4 +Fixed missing shared library dependencies (Linux) +Unified precompiled Linux binary packages + + +FreeFileSync 10.0 [2018-04-27] +------------------------------ The installer is now ad-free! Sync multiple files in parallel (Donation Edition) Compare multiple files in parallel within a single folder tree @@ -640,7 +658,7 @@ Keep user interface responsive while creating a volume shadow copy Fixed error when starting asynchronously from a batch script Show progress of writing log files Fixed updated file being left deleted when copying permissions failed -New Project website: http://www.freefilesync.org/ +New Project website: https://freefilesync.org/ FreeFileSync 6.10 [2014-10-01] diff --git a/FreeFileSync/Build/Help/html/performance.html b/FreeFileSync/Build/Help/html/performance.html index 37098ed3..69922c12 100755 --- a/FreeFileSync/Build/Help/html/performance.html +++ b/FreeFileSync/Build/Help/html/performance.html @@ -16,18 +16,19 @@ cases where single I/O operations have significant latency (e.g. long response times on a slow network connection) or they cannot use the full bandwidth available - (e.g. a SFTP server that has a speed limit for each connection).<br> + (e.g. an SFTP server that has a speed limit for each connection).<br> <br> The number of parallel file operations that FreeFileSync should use can be set up for each device individually in the <b>Comparison Settings</b> dialog. - It is considered for all folder pairs of a configuration as follows: + It is evaluated for all folder pairs of a configuration as follows: </p> <ul> - <li><b>During comparison</b> FreeFileSync first groups all folders by their root devices.<br> + <li><b>During comparison</b> FreeFileSync groups all folders by their root devices.<br> <div class="half-line"> </div> For example, consider a configuration with two folder pairs and parallel file operations set up: - <div> + + <div style="background-color: #eee; border: 1px solid #ccc; padding: 5px 10px; margin: .5em 0;"> <table style="border-spacing:0; display: inline-block; vertical-align: middle;"> <tr><td><span class="file-path">C:\Source </span></td> <td>↔</td> <td><span class="file-path">D:\Target</span></td></tr> <tr><td><span class="file-path">C:\Source2</span></td> <td>↔</td> <td><span class="file-path">E:\Target</span></td></tr> @@ -42,13 +43,13 @@ <tr><td>3</td> <td><span class="file-path">E:\</span></td></tr> </table> </div> - <div class="half-line"> </div> + FreeFileSync will put the folders <span class="file-path">C:\Source</span> and <span class="file-path">C:\Source2</span> into the same group and allow only 1 file operation at a time. Folder <span class="file-path">D:\Target</span> will be traversed using 2 operations, and <span class="file-path">E:\Target</span> using 3 operations at a time. - In total FreeFileSync will be scanning all folders + In total FreeFileSync will be scanning all four folders employing 6 file operations in parallel.<br> <br> diff --git a/FreeFileSync/Build/Help/html/realtimesync.html b/FreeFileSync/Build/Help/html/realtimesync.html index 6abbdbb2..325316b3 100755 --- a/FreeFileSync/Build/Help/html/realtimesync.html +++ b/FreeFileSync/Build/Help/html/realtimesync.html @@ -13,7 +13,7 @@ </h1> <p> - The primary purpose of RealTimeSync is to execute a command line each time it <b>detects changes</b> in one of the monitored directories, + The primary function of RealTimeSync is to execute a command line each time it <b>detects changes</b> in one of the monitored directories, or when a <b>directory becomes available</b> (e. g. insert of a USB-stick). Usually this command line will trigger a FreeFileSync batch job.<br> <br> @@ -51,7 +51,7 @@ <div class="command-line"> "C:\Program Files\FreeFileSync\RealTimeSync.exe" "D:\Backup Projects.ffs_batch"</div> <br> - <li>RealTimeSync is not required to start FreeFileSync. It can also be used in other scenarios, like sending an email whenever a certain directory is modified. + <li>RealTimeSync does not require to start FreeFileSync. It can also be used in other scenarios, like sending an email whenever a certain directory is modified. </ul> </div> <br> diff --git a/FreeFileSync/Build/Help/images/basic-step-compare.png b/FreeFileSync/Build/Help/images/basic-step-compare.png Binary files differindex 704fd4fa..8963f9fa 100755 --- a/FreeFileSync/Build/Help/images/basic-step-compare.png +++ b/FreeFileSync/Build/Help/images/basic-step-compare.png diff --git a/FreeFileSync/Build/Help/images/command-line-syntax.png b/FreeFileSync/Build/Help/images/command-line-syntax.png Binary files differindex f0d9878d..ebb2d895 100755 --- a/FreeFileSync/Build/Help/images/command-line-syntax.png +++ b/FreeFileSync/Build/Help/images/command-line-syntax.png diff --git a/FreeFileSync/Build/Help/images/comparison-settings.png b/FreeFileSync/Build/Help/images/comparison-settings.png Binary files differindex 5ffa2f48..242f558a 100755 --- a/FreeFileSync/Build/Help/images/comparison-settings.png +++ b/FreeFileSync/Build/Help/images/comparison-settings.png diff --git a/FreeFileSync/Build/Help/images/comparison-variant-double-click.png b/FreeFileSync/Build/Help/images/comparison-variant-double-click.png Binary files differindex 1d998755..5ad6f256 100755 --- a/FreeFileSync/Build/Help/images/comparison-variant-double-click.png +++ b/FreeFileSync/Build/Help/images/comparison-variant-double-click.png diff --git a/FreeFileSync/Build/Help/images/copy-alternative-path.png b/FreeFileSync/Build/Help/images/copy-alternative-path.png Binary files differindex bded2b56..1baeadb4 100755 --- a/FreeFileSync/Build/Help/images/copy-alternative-path.png +++ b/FreeFileSync/Build/Help/images/copy-alternative-path.png diff --git a/FreeFileSync/Build/Help/images/filter.png b/FreeFileSync/Build/Help/images/filter.png Binary files differindex a4d3a0f8..33d7264c 100755 --- a/FreeFileSync/Build/Help/images/filter.png +++ b/FreeFileSync/Build/Help/images/filter.png diff --git a/FreeFileSync/Build/Help/images/gnome-scheduler.png b/FreeFileSync/Build/Help/images/gnome-scheduler.png Binary files differindex 05d0f1f7..0cd5ef12 100755 --- a/FreeFileSync/Build/Help/images/gnome-scheduler.png +++ b/FreeFileSync/Build/Help/images/gnome-scheduler.png diff --git a/FreeFileSync/Build/Help/images/ignore-time-shift.png b/FreeFileSync/Build/Help/images/ignore-time-shift.png Binary files differindex ceadf60a..cec19888 100755 --- a/FreeFileSync/Build/Help/images/ignore-time-shift.png +++ b/FreeFileSync/Build/Help/images/ignore-time-shift.png diff --git a/FreeFileSync/Build/Help/images/performance.png b/FreeFileSync/Build/Help/images/performance.png Binary files differindex 70bed081..0c189c5c 100755 --- a/FreeFileSync/Build/Help/images/performance.png +++ b/FreeFileSync/Build/Help/images/performance.png diff --git a/FreeFileSync/Build/Help/images/realtimesync-create-shortcut.png b/FreeFileSync/Build/Help/images/realtimesync-create-shortcut.png Binary files differindex c0910bf7..fcf0f7f1 100755 --- a/FreeFileSync/Build/Help/images/realtimesync-create-shortcut.png +++ b/FreeFileSync/Build/Help/images/realtimesync-create-shortcut.png diff --git a/FreeFileSync/Build/Help/images/realtimesync-schedule.png b/FreeFileSync/Build/Help/images/realtimesync-schedule.png Binary files differindex 34ba5b88..416e32f8 100755 --- a/FreeFileSync/Build/Help/images/realtimesync-schedule.png +++ b/FreeFileSync/Build/Help/images/realtimesync-schedule.png diff --git a/FreeFileSync/Build/Help/images/save-automator.png b/FreeFileSync/Build/Help/images/save-automator.png Binary files differindex d685d8c6..738af1de 100755 --- a/FreeFileSync/Build/Help/images/save-automator.png +++ b/FreeFileSync/Build/Help/images/save-automator.png diff --git a/FreeFileSync/Build/Help/images/setup-batch-job.png b/FreeFileSync/Build/Help/images/setup-batch-job.png Binary files differindex 1982a66b..cc38e85c 100755 --- a/FreeFileSync/Build/Help/images/setup-batch-job.png +++ b/FreeFileSync/Build/Help/images/setup-batch-job.png diff --git a/FreeFileSync/Build/Help/images/sftp-cloud-picker.png b/FreeFileSync/Build/Help/images/sftp-cloud-picker.png Binary files differindex a2f24f72..588d102c 100755 --- a/FreeFileSync/Build/Help/images/sftp-cloud-picker.png +++ b/FreeFileSync/Build/Help/images/sftp-cloud-picker.png diff --git a/FreeFileSync/Build/Help/images/sftp-login.png b/FreeFileSync/Build/Help/images/sftp-login.png Binary files differindex d28b12a9..0a3f3b7a 100755 --- a/FreeFileSync/Build/Help/images/sftp-login.png +++ b/FreeFileSync/Build/Help/images/sftp-login.png diff --git a/FreeFileSync/Build/Help/images/sftp-performance.png b/FreeFileSync/Build/Help/images/sftp-performance.png Binary files differindex 6125aa5e..f982277f 100755 --- a/FreeFileSync/Build/Help/images/sftp-performance.png +++ b/FreeFileSync/Build/Help/images/sftp-performance.png diff --git a/FreeFileSync/Build/Help/images/synchronization-settings.png b/FreeFileSync/Build/Help/images/synchronization-settings.png Binary files differindex 4eab9306..501b3db1 100755 --- a/FreeFileSync/Build/Help/images/synchronization-settings.png +++ b/FreeFileSync/Build/Help/images/synchronization-settings.png diff --git a/FreeFileSync/Build/Help/images/synchronization-variant-double-click.png b/FreeFileSync/Build/Help/images/synchronization-variant-double-click.png Binary files differindex cb6cd370..0cd13905 100755 --- a/FreeFileSync/Build/Help/images/synchronization-variant-double-click.png +++ b/FreeFileSync/Build/Help/images/synchronization-variant-double-click.png diff --git a/FreeFileSync/Build/Help/images/windows-scheduler.png b/FreeFileSync/Build/Help/images/windows-scheduler.png Binary files differindex bf214a8f..14788118 100755 --- a/FreeFileSync/Build/Help/images/windows-scheduler.png +++ b/FreeFileSync/Build/Help/images/windows-scheduler.png diff --git a/FreeFileSync/Build/Help/images/xp-scheduler.png b/FreeFileSync/Build/Help/images/xp-scheduler.png Binary files differindex cfb74050..4d2f5907 100755 --- a/FreeFileSync/Build/Help/images/xp-scheduler.png +++ b/FreeFileSync/Build/Help/images/xp-scheduler.png diff --git a/FreeFileSync/Build/Languages/german.lng b/FreeFileSync/Build/Languages/german.lng index 2039e816..31d74476 100755 --- a/FreeFileSync/Build/Languages/german.lng +++ b/FreeFileSync/Build/Languages/german.lng @@ -7,6 +7,12 @@ <plural_definition>n == 1 ? 0 : 1</plural_definition> </header> +<source>Donation Edition</source> +<target>Spendenversion</target> + +<source>Defined by context of use</source> +<target></target> + <source>Both sides have changed since last synchronization.</source> <target>Beide Seiten wurden seit der letzten Synchronisation verändert.</target> @@ -887,9 +893,6 @@ Die Befehlszeile wird ausgelöst, wenn: <source>Please select a folder on a local file system, network or an MTP device.</source> <target>Bitte wählen Sie einen Ordner auf einem lokalen Dateisystem, Netzwerk oder MTP Gerät.</target> -<source>Defined by context of use</source> -<target></target> - <source>Requires FreeFileSync Donation Edition</source> <target>Benötigt FreeFileSync Spendenversion</target> @@ -1061,8 +1064,8 @@ Die Befehlszeile wird ausgelöst, wenn: <source>Show examples</source> <target>Beispiele zeigen</target> -<source>Time span:</source> -<target>Zeitspanne:</target> +<source>Select filter rules to exclude certain files from synchronization. Enter file paths relative to their corresponding folder pair.</source> +<target>Wählen Sie Filterregeln aus, um einzelne Dateien von der Synchronisation auszuschließen. Geben Sie Dateipfade relativ zum zugehörigen Ordnerpaar an.</target> <source>File size:</source> <target>Dateigröße:</target> @@ -1073,8 +1076,8 @@ Die Befehlszeile wird ausgelöst, wenn: <source>Maximum:</source> <target>Maximum:</target> -<source>Select filter rules to exclude certain files from synchronization. Enter file paths relative to their corresponding folder pair.</source> -<target>Wählen Sie Filterregeln aus, um einzelne Dateien von der Synchronisation auszuschließen. Geben Sie Dateipfade relativ zum zugehörigen Ordnerpaar an.</target> +<source>Time span:</source> +<target>Zeitspanne:</target> <source>C&lear</source> <target>&Löschen</target> @@ -1381,9 +1384,6 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. <source>Select Time Span</source> <target>Zeitspanne auswählen</target> -<source>FreeFileSync Donation Edition</source> -<target>FreeFileSync Spendenversion</target> - <source>Highlight Configurations</source> <target>Konfigurationen hervorheben</target> diff --git a/FreeFileSync/Build/Resources.zip b/FreeFileSync/Build/Resources.zip Binary files differindex a1d93d01..d020de7c 100755 --- a/FreeFileSync/Build/Resources.zip +++ b/FreeFileSync/Build/Resources.zip diff --git a/FreeFileSync/Source/Makefile b/FreeFileSync/Source/Makefile index dffa2308..89983ac8 100755 --- a/FreeFileSync/Source/Makefile +++ b/FreeFileSync/Source/Makefile @@ -5,8 +5,8 @@ SHAREDIR = $(DESTDIR)$(prefix)/share APPSHAREDIR = $(SHAREDIR)/$(APPNAME) DOCSHAREDIR = $(SHAREDIR)/doc/$(APPNAME) -CXXFLAGS = -std=c++17 -pipe -DWXINTL_NO_GETTEXT_MACRO -I../.. -I../../zenXml -I../../boost -include "zen/i18n.h" -include "zen/warn_static.h" \ --Wall -Wfatal-errors -Winit-self -Wmissing-include-dirs -Wswitch-enum -Wmain -Wnon-virtual-dtor -Wcast-align -Wshadow -Wno-deprecated-declarations \ +CXXFLAGS = -std=c++17 -pipe -DWXINTL_NO_GETTEXT_MACRO -I../.. -I../../zenXml -isystem../../boost -include "zen/i18n.h" -include "zen/warn_static.h" \ +-Wall -Wfatal-errors -Wmissing-include-dirs -Wswitch-enum -Wcast-align -Wshadow -Wnon-virtual-dtor \ -O3 -DNDEBUG `wx-config --cxxflags --debug=no` -pthread LINKFLAGS = -s -no-pie `wx-config --libs std, aui --debug=no` -pthread @@ -30,15 +30,30 @@ LINKFLAGS += `pkg-config --libs unity` endif CPP_FILES= -CPP_FILES+=algorithm.cpp -CPP_FILES+=application.cpp -CPP_FILES+=comparison.cpp -CPP_FILES+=structures.cpp -CPP_FILES+=synchronization.cpp +CPP_FILES+=base/algorithm.cpp +CPP_FILES+=base/application.cpp +CPP_FILES+=base/binary.cpp +CPP_FILES+=base/comparison.cpp +CPP_FILES+=base/db_file.cpp +CPP_FILES+=base/dir_lock.cpp +CPP_FILES+=base/ffs_paths.cpp +CPP_FILES+=base/file_hierarchy.cpp +CPP_FILES+=base/generate_logfile.cpp +CPP_FILES+=base/hard_filter.cpp +CPP_FILES+=base/icon_buffer.cpp +CPP_FILES+=base/icon_loader.cpp +CPP_FILES+=base/localization.cpp +CPP_FILES+=base/parallel_scan.cpp +CPP_FILES+=base/process_xml.cpp +CPP_FILES+=base/perf_check.cpp +CPP_FILES+=base/resolve_path.cpp +CPP_FILES+=base/status_handler.cpp +CPP_FILES+=base/structures.cpp +CPP_FILES+=base/synchronization.cpp +CPP_FILES+=base/versioning.cpp CPP_FILES+=fs/abstract.cpp CPP_FILES+=fs/concrete.cpp CPP_FILES+=fs/native.cpp -CPP_FILES+=file_hierarchy.cpp CPP_FILES+=ui/batch_config.cpp CPP_FILES+=ui/batch_status_handler.cpp CPP_FILES+=ui/cfg_grid.cpp @@ -59,35 +74,21 @@ CPP_FILES+=ui/taskbar.cpp CPP_FILES+=ui/tray_icon.cpp CPP_FILES+=ui/triple_splitter.cpp CPP_FILES+=ui/version_check.cpp -CPP_FILES+=lib/binary.cpp -CPP_FILES+=lib/db_file.cpp -CPP_FILES+=lib/dir_lock.cpp -CPP_FILES+=lib/ffs_paths.cpp -CPP_FILES+=lib/generate_logfile.cpp -CPP_FILES+=lib/hard_filter.cpp -CPP_FILES+=lib/icon_buffer.cpp -CPP_FILES+=lib/icon_loader.cpp -CPP_FILES+=lib/localization.cpp -CPP_FILES+=lib/parallel_scan.cpp -CPP_FILES+=lib/process_xml.cpp -CPP_FILES+=lib/resolve_path.cpp -CPP_FILES+=lib/perf_check.cpp -CPP_FILES+=lib/status_handler.cpp -CPP_FILES+=lib/versioning.cpp CPP_FILES+=../../zen/xml_io.cpp CPP_FILES+=../../zen/recycler.cpp CPP_FILES+=../../zen/file_access.cpp CPP_FILES+=../../zen/file_io.cpp CPP_FILES+=../../zen/file_traverser.cpp +CPP_FILES+=../../zen/http.cpp CPP_FILES+=../../zen/zstring.cpp CPP_FILES+=../../zen/format_unit.cpp CPP_FILES+=../../zen/process_priority.cpp CPP_FILES+=../../zen/shutdown.cpp +CPP_FILES+=../../zen/thread.cpp CPP_FILES+=../../wx+/file_drop.cpp CPP_FILES+=../../wx+/grid.cpp CPP_FILES+=../../wx+/image_tools.cpp CPP_FILES+=../../wx+/graph.cpp -CPP_FILES+=../../wx+/http.cpp CPP_FILES+=../../wx+/tooltip.cpp CPP_FILES+=../../wx+/image_resources.cpp CPP_FILES+=../../wx+/popup_dlg.cpp diff --git a/FreeFileSync/Source/RealTimeSync/Makefile b/FreeFileSync/Source/RealTimeSync/Makefile index e4915afa..161055f4 100755 --- a/FreeFileSync/Source/RealTimeSync/Makefile +++ b/FreeFileSync/Source/RealTimeSync/Makefile @@ -2,8 +2,8 @@ APPNAME = RealTimeSync prefix = /usr BINDIR = $(DESTDIR)$(prefix)/bin -CXXFLAGS = -std=c++17 -pipe -DWXINTL_NO_GETTEXT_MACRO -I../../.. -I../../../zenXml -I../../../boost -include "zen/i18n.h" -include "zen/warn_static.h" \ --Wall -Wfatal-errors -Winit-self -Wmissing-include-dirs -Wswitch-enum -Wmain -Wnon-virtual-dtor -Wcast-align -Wshadow -Wno-deprecated-declarations \ +CXXFLAGS = -std=c++17 -pipe -DWXINTL_NO_GETTEXT_MACRO -I../../.. -I../../../zenXml -isystem../../../boost -include "zen/i18n.h" -include "zen/warn_static.h" \ +-Wall -Wfatal-errors -Wmissing-include-dirs -Wswitch-enum -Wcast-align -Wshadow -Wnon-virtual-dtor \ -O3 -DNDEBUG `wx-config --cxxflags --debug=no` -pthread LINKFLAGS = -s -no-pie `wx-config --libs std, aui --debug=no` -pthread @@ -20,9 +20,9 @@ CPP_FILES+=tray_menu.cpp CPP_FILES+=monitor.cpp CPP_FILES+=xml_proc.cpp CPP_FILES+=folder_selector2.cpp -CPP_FILES+=../lib/localization.cpp -CPP_FILES+=../lib/resolve_path.cpp -CPP_FILES+=../lib/ffs_paths.cpp +CPP_FILES+=../base/localization.cpp +CPP_FILES+=../base/resolve_path.cpp +CPP_FILES+=../base/ffs_paths.cpp CPP_FILES+=../../../zen/xml_io.cpp CPP_FILES+=../../../zen/dir_watcher.cpp CPP_FILES+=../../../zen/file_access.cpp @@ -30,6 +30,7 @@ CPP_FILES+=../../../zen/file_io.cpp CPP_FILES+=../../../zen/file_traverser.cpp CPP_FILES+=../../../zen/zstring.cpp CPP_FILES+=../../../zen/format_unit.cpp +CPP_FILES+=../../../zen/thread.cpp CPP_FILES+=../../../wx+/file_drop.cpp CPP_FILES+=../../../wx+/image_tools.cpp CPP_FILES+=../../../wx+/image_resources.cpp diff --git a/FreeFileSync/Source/RealTimeSync/application.cpp b/FreeFileSync/Source/RealTimeSync/application.cpp index 5559fcf9..69f7475f 100755 --- a/FreeFileSync/Source/RealTimeSync/application.cpp +++ b/FreeFileSync/Source/RealTimeSync/application.cpp @@ -14,12 +14,12 @@ #include <wx+/popup_dlg.h> #include <wx+/image_resources.h> #include "xml_proc.h" -#include "../lib/localization.h" -#include "../lib/ffs_paths.h" -#include "../lib/return_codes.h" -#include "../lib/error_log.h" -#include "../lib/help_provider.h" -#include "../lib/resolve_path.h" +#include "../base/localization.h" +#include "../base/ffs_paths.h" +#include "../base/return_codes.h" +#include "../base/error_log.h" +#include "../base/help_provider.h" +#include "../base/resolve_path.h" #include <gtk/gtk.h> @@ -142,5 +142,6 @@ void Application::onQueryEndSession(wxEvent& event) if (auto mainWin = dynamic_cast<MainDialog*>(GetTopWindow())) mainWin->onQueryEndSession(); //it's futile to try and clean up while the process is in full swing (CRASH!) => just terminate! - std::abort(); //on Windows calls ::ExitProcess() which can still internally process Window messages and crash! + std::exit(fff::FFS_RC_ABORTED); + //don't use std::abort() => crashes process with "EXC_CRASH (SIGABRT)" on macOS } diff --git a/FreeFileSync/Source/RealTimeSync/application.h b/FreeFileSync/Source/RealTimeSync/application.h index 338a15e1..bdfc2027 100755 --- a/FreeFileSync/Source/RealTimeSync/application.h +++ b/FreeFileSync/Source/RealTimeSync/application.h @@ -16,8 +16,8 @@ class Application : public wxApp { public: bool OnInit() override; - int OnExit() override; int OnRun () override; + int OnExit() override; bool OnExceptionInMainLoop() override { throw; } //just re-throw and avoid display of additional messagebox: it will be caught in OnRun() void OnUnhandledException () override { throw; } //just re-throw and avoid display of additional messagebox void onQueryEndSession(wxEvent& event); diff --git a/FreeFileSync/Source/RealTimeSync/folder_selector2.cpp b/FreeFileSync/Source/RealTimeSync/folder_selector2.cpp index 64d582dc..97314b4d 100755 --- a/FreeFileSync/Source/RealTimeSync/folder_selector2.cpp +++ b/FreeFileSync/Source/RealTimeSync/folder_selector2.cpp @@ -11,7 +11,7 @@ #include <wx/dirdlg.h> #include <wx/scrolwin.h> #include <wx+/popup_dlg.h> -#include "../lib/resolve_path.h" +#include "../base/resolve_path.h" #include <gtk/gtk.h> diff --git a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp index 67fdde0a..3fb69547 100755 --- a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp +++ b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp @@ -17,9 +17,9 @@ #include "xml_proc.h" #include "tray_menu.h" #include "app_icon.h" -#include "../lib/help_provider.h" -#include "../lib/process_xml.h" -#include "../lib/ffs_paths.h" +#include "../base/help_provider.h" +//#include "../base/process_xml.h" +#include "../base/ffs_paths.h" #include "../version/version.h" #include <gtk/gtk.h> diff --git a/FreeFileSync/Source/RealTimeSync/monitor.cpp b/FreeFileSync/Source/RealTimeSync/monitor.cpp index 8f0f5c18..3b5a4321 100755 --- a/FreeFileSync/Source/RealTimeSync/monitor.cpp +++ b/FreeFileSync/Source/RealTimeSync/monitor.cpp @@ -12,7 +12,7 @@ #include <zen/thread.h> #include <zen/basic_math.h> #include <wx/utils.h> -#include "../lib/resolve_path.h" +#include "../base/resolve_path.h" //#include "../library/db_file.h" //SYNC_DB_FILE_ENDING -> complete file too much of a dependency; file ending too little to decouple into single header //#include "../library/lock_holder.h" //LOCK_FILE_ENDING //TEMP_FILE_ENDING @@ -30,7 +30,7 @@ std::vector<Zstring> getFormattedDirs(const std::vector<Zstring>& folderPathPhra std::set<Zstring, LessFilePath> folderPaths; //make unique for (const Zstring& phrase : std::set<Zstring, LessFilePath>(folderPathPhrases.begin(), folderPathPhrases.end())) { - //hopefully clear enough now: https://www.freefilesync.org/forum/viewtopic.php?t=4302 + //hopefully clear enough now: https://freefilesync.org/forum/viewtopic.php?t=4302 auto checkProtocol = [&](const Zstring& protoName) { if (startsWith(trimCpy(phrase), protoName + Zstr(":"), CmpAsciiNoCase())) diff --git a/FreeFileSync/Source/RealTimeSync/tray_menu.cpp b/FreeFileSync/Source/RealTimeSync/tray_menu.cpp index dd697d1f..fb152755 100755 --- a/FreeFileSync/Source/RealTimeSync/tray_menu.cpp +++ b/FreeFileSync/Source/RealTimeSync/tray_menu.cpp @@ -17,7 +17,7 @@ #include <wx+/popup_dlg.h> #include <wx+/image_resources.h> #include "monitor.h" -#include "../lib/resolve_path.h" +#include "../base/resolve_path.h" using namespace zen; using namespace rts; diff --git a/FreeFileSync/Source/RealTimeSync/xml_proc.cpp b/FreeFileSync/Source/RealTimeSync/xml_proc.cpp index f4762540..dd933e0d 100755 --- a/FreeFileSync/Source/RealTimeSync/xml_proc.cpp +++ b/FreeFileSync/Source/RealTimeSync/xml_proc.cpp @@ -7,8 +7,8 @@ #include "xml_proc.h" #include <zen/file_access.h> #include <wx/intl.h> -#include "../lib/ffs_paths.h" -#include "../lib/localization.h" +#include "../base/ffs_paths.h" +#include "../base/localization.h" using namespace zen; using namespace rts; diff --git a/FreeFileSync/Source/algorithm.cpp b/FreeFileSync/Source/base/algorithm.cpp index a7d21ccf..8b017923 100755 --- a/FreeFileSync/Source/algorithm.cpp +++ b/FreeFileSync/Source/base/algorithm.cpp @@ -12,12 +12,12 @@ #include <zen/guid.h> #include <zen/file_access.h> //needed for TempFileBuffer only #include <zen/serialize.h> -#include "lib/norm_filter.h" -#include "lib/db_file.h" -#include "lib/cmp_filetime.h" -#include "lib/status_handler_impl.h" -#include "fs/concrete.h" -#include "fs/native.h" +#include "norm_filter.h" +#include "db_file.h" +#include "cmp_filetime.h" +#include "status_handler_impl.h" +#include "../fs/concrete.h" +#include "../fs/native.h" using namespace zen; @@ -1199,14 +1199,14 @@ void copyToAlternateFolderFrom(const std::vector<const FileSystemObject*>& rowsT { auto notifyItemCopy = [&](const std::wstring& statusText, const std::wstring& displayPath) { - callback.reportInfo(replaceCpy(statusText, L"%x", fmtPath(displayPath))); + callback.reportInfo(replaceCpy(statusText, L"%x", fmtPath(displayPath))); //throw X }; const std::wstring txtCreatingFile (_("Creating file %x" )); const std::wstring txtCreatingFolder(_("Creating folder %x" )); const std::wstring txtCreatingLink (_("Creating symbolic link %x")); - auto copyItem = [overwriteIfExists](const AbstractPath& targetPath, ItemStatReporter& statReporter, //throw FileError - const std::function<void(const std::function<void()>& deleteTargetItem)>& copyItemPlain) //throw FileError + auto copyItem = [&callback, overwriteIfExists](const AbstractPath& targetPath, ItemStatReporter<>& statReporter, //throw FileError + const std::function<void(const std::function<void()>& deleteTargetItem)>& copyItemPlain) //throw FileError { //start deleting existing target as required by copyFileTransactional(): //best amortized performance if "target existing" is the most common case @@ -1239,6 +1239,7 @@ void copyToAlternateFolderFrom(const std::vector<const FileSystemObject*>& rowsT { AFS::createFolderPlain(intermediatePath = AFS::appendRelPath(intermediatePath, itemName)); //throw FileError statReporter.reportDelta(1, 0); + callback.requestUiRefresh(); //throw X } //potential future issue when adding multithreading support: intermediate folders might already exist //potential future issue 2: folder created by parallel thread just after failure => ps->relPath.size() == 1, but need retry! @@ -1261,7 +1262,7 @@ void copyToAlternateFolderFrom(const std::vector<const FileSystemObject*>& rowsT visitFSObject(*fsObj, [&](const FolderPair& folder) { - ItemStatReporter statReporter(1, 0, callback); + ItemStatReporter<> statReporter(1, 0, callback); notifyItemCopy(txtCreatingFolder, AFS::getDisplayPath(targetPath)); try { @@ -1285,6 +1286,7 @@ void copyToAlternateFolderFrom(const std::vector<const FileSystemObject*>& rowsT { AFS::createFolderPlain(intermediatePath = AFS::appendRelPath(intermediatePath, itemName)); //throw FileError statReporter.reportDelta(1, 0); + callback.requestUiRefresh(); //throw X } //potential future issue when adding multithreading support: intermediate folders might already exist //potential future issue 2: parent folder created by parallel thread just after failure => ps->relPath.size() == 1, but need retry! @@ -1294,7 +1296,7 @@ void copyToAlternateFolderFrom(const std::vector<const FileSystemObject*>& rowsT [&](const FilePair& file) { - ItemStatReporter statReporter(1, file.getFileSize<side>(), callback); + ItemStatReporter<> statReporter(1, file.getFileSize<side>(), callback); notifyItemCopy(txtCreatingFile, AFS::getDisplayPath(targetPath)); const FileAttributes attr = file.getAttributes<side>(); @@ -1302,7 +1304,12 @@ void copyToAlternateFolderFrom(const std::vector<const FileSystemObject*>& rowsT copyItem(targetPath, statReporter, [&](const std::function<void()>& deleteTargetItem) //throw FileError { - auto notifyUnbufferedIO = [&](int64_t bytesDelta) { statReporter.reportDelta(0, bytesDelta); }; + auto notifyUnbufferedIO = [&](int64_t bytesDelta) + + { + statReporter.reportDelta(0, bytesDelta); + callback.requestUiRefresh(); //throw X + }; /*const AFS::FileCopyResult result =*/ AFS::copyFileTransactional(sourcePath, sourceAttr, targetPath, //throw FileError, ErrorFileLocked false /*copyFilePermissions*/, true /*transactionalCopy*/, deleteTargetItem, notifyUnbufferedIO); //result.errorModTime? => probably irrelevant (behave like Windows Explorer) @@ -1312,7 +1319,7 @@ void copyToAlternateFolderFrom(const std::vector<const FileSystemObject*>& rowsT [&](const SymlinkPair& symlink) { - ItemStatReporter statReporter(1, 0, callback); + ItemStatReporter<> statReporter(1, 0, callback); notifyItemCopy(txtCreatingLink, AFS::getDisplayPath(targetPath)); copyItem(targetPath, statReporter, [&](const std::function<void()>& deleteTargetItem) //throw FileError @@ -1322,6 +1329,8 @@ void copyToAlternateFolderFrom(const std::vector<const FileSystemObject*>& rowsT }); statReporter.reportDelta(1, 0); }); + + callback.requestUiRefresh(); //throw X }, callback); //throw X } } @@ -1372,7 +1381,7 @@ void deleteFromGridAndHDOneSide(std::vector<FileSystemObject*>& rowsToDelete, { auto notifyItemDeletion = [&](const std::wstring& statusText, const std::wstring& displayPath) { - callback.reportInfo(replaceCpy(statusText, L"%x", fmtPath(displayPath))); + callback.reportInfo(replaceCpy(statusText, L"%x", fmtPath(displayPath))); //throw X }; std::wstring txtRemovingFile; @@ -1396,7 +1405,7 @@ void deleteFromGridAndHDOneSide(std::vector<FileSystemObject*>& rowsToDelete, for (FileSystemObject* fsObj : rowsToDelete) //all pointers are required(!) to be bound tryReportingError([&] { - ItemStatReporter statReporter(1, 0, callback); + ItemStatReporter<> statReporter(1, 0, callback); if (!fsObj->isEmpty<side>()) //element may be implicitly deleted, e.g. if parent folder was deleted first { @@ -1405,7 +1414,8 @@ void deleteFromGridAndHDOneSide(std::vector<FileSystemObject*>& rowsToDelete, { if (useRecycleBin) { - notifyItemDeletion(txtRemovingDirectory, AFS::getDisplayPath(folder.getAbstractPath<side>())); + notifyItemDeletion(txtRemovingDirectory, AFS::getDisplayPath(folder.getAbstractPath<side>())); //throw X + AFS::recycleItemIfExists(folder.getAbstractPath<side>()); //throw FileError statReporter.reportDelta(1, 0); } @@ -1413,13 +1423,13 @@ void deleteFromGridAndHDOneSide(std::vector<FileSystemObject*>& rowsToDelete, { auto onBeforeFileDeletion = [&](const std::wstring& displayPath) { + notifyItemDeletion(txtRemovingFile, displayPath); //throw X statReporter.reportDelta(1, 0); - notifyItemDeletion(txtRemovingFile, displayPath); }; auto onBeforeDirDeletion = [&](const std::wstring& displayPath) { + notifyItemDeletion(txtRemovingDirectory, displayPath); //throw X statReporter.reportDelta(1, 0); - notifyItemDeletion(txtRemovingDirectory, displayPath); }; AFS::removeFolderIfExistsRecursion(folder.getAbstractPath<side>(), onBeforeFileDeletion, onBeforeDirDeletion); //throw FileError @@ -1428,7 +1438,7 @@ void deleteFromGridAndHDOneSide(std::vector<FileSystemObject*>& rowsToDelete, [&](const FilePair& file) { - notifyItemDeletion(txtRemovingFile, AFS::getDisplayPath(file.getAbstractPath<side>())); + notifyItemDeletion(txtRemovingFile, AFS::getDisplayPath(file.getAbstractPath<side>())); //throw X if (useRecycleBin) AFS::recycleItemIfExists(file.getAbstractPath<side>()); //throw FileError @@ -1439,7 +1449,7 @@ void deleteFromGridAndHDOneSide(std::vector<FileSystemObject*>& rowsToDelete, [&](const SymlinkPair& symlink) { - notifyItemDeletion(txtRemovingSymlink, AFS::getDisplayPath(symlink.getAbstractPath<side>())); + notifyItemDeletion(txtRemovingSymlink, AFS::getDisplayPath(symlink.getAbstractPath<side>())); //throw X if (useRecycleBin) AFS::recycleItemIfExists(symlink.getAbstractPath<side>()); //throw FileError @@ -1450,6 +1460,9 @@ void deleteFromGridAndHDOneSide(std::vector<FileSystemObject*>& rowsToDelete, fsObj->removeObject<side>(); //if directory: removes recursively! } + + //remain transactional as much as possible => allow for abort only *after* updating file model + callback.requestUiRefresh(); //throw X }, callback); //throw X } @@ -1460,7 +1473,7 @@ void categorize(const std::vector<FileSystemObject*>& rows, std::vector<FileSystemObject*>& deleteRecyler, bool useRecycleBin, std::map<AbstractPath, bool>& recyclerSupported, - ProcessCallback& callback) + ProcessCallback& callback) //throw X { auto hasRecycler = [&](const AbstractPath& baseFolderPath) -> bool { @@ -1472,7 +1485,7 @@ void categorize(const std::vector<FileSystemObject*>& rows, bool recSupported = false; tryReportingError([&]{ - recSupported = AFS::supportsRecycleBin(baseFolderPath, [&] { callback.reportStatus(msg); /*may throw*/ }); //throw FileError + recSupported = AFS::supportsRecycleBin(baseFolderPath, [&] { callback.reportStatus(msg); /*throw X*/ }); //throw FileError }, callback); //throw X recyclerSupported.emplace(baseFolderPath, recSupported); @@ -1566,8 +1579,8 @@ void fff::deleteFromGridAndHD(const std::vector<FileSystemObject*>& rowsToDelete std::vector<FileSystemObject*> deleteRecylerRight; std::map<AbstractPath, bool> recyclerSupported; - categorize< LEFT_SIDE>(deleteLeft, deletePermanentLeft, deleteRecylerLeft, useRecycleBin, recyclerSupported, callback); - categorize<RIGHT_SIDE>(deleteRight, deletePermanentRight, deleteRecylerRight, useRecycleBin, recyclerSupported, callback); + categorize< LEFT_SIDE>(deleteLeft, deletePermanentLeft, deleteRecylerLeft, useRecycleBin, recyclerSupported, callback); //throw X + categorize<RIGHT_SIDE>(deleteRight, deletePermanentRight, deleteRecylerRight, useRecycleBin, recyclerSupported, callback); // //windows: check if recycle bin really exists; if not, Windows will silently delete, which is wrong if (useRecycleBin && @@ -1644,7 +1657,7 @@ void TempFileBuffer::createTempFiles(const std::set<FileDescriptor>& workLoad, P if (tempFolderPath_.empty()) { - Opt<std::wstring> errMsg = tryReportingError([&] + const std::wstring errMsg = tryReportingError([&] { //generate random temp folder path e.g. C:\Users\Zenju\AppData\Local\Temp\FFS-068b2e88 Zstring tempPathTmp = appendSeparator(getTempFolderPath()); //throw FileError @@ -1657,7 +1670,7 @@ void TempFileBuffer::createTempFiles(const std::set<FileDescriptor>& workLoad, P tempFolderPath_ = tempPathTmp; }, callback); //throw X - if (errMsg) return; + if (!errMsg.empty()) return; } for (const FileDescriptor& descr : workLoad) @@ -1684,20 +1697,24 @@ void TempFileBuffer::createTempFiles(const std::set<FileDescriptor>& workLoad, P 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))); - - auto notifyUnbufferedIO = [&](int64_t bytesDelta) { statReporter.reportDelta(0, bytesDelta); }; + callback.reportInfo(replaceCpy(_("Creating file %x"), L"%x", fmtPath(tempFilePath))); //throw X + auto notifyUnbufferedIO = [&](int64_t bytesDelta) + { + statReporter.reportDelta(0, bytesDelta); + callback.requestUiRefresh(); //throw X + }; /*const AFS::FileCopyResult result =*/ AFS::copyFileTransactional(descr.path, sourceAttr, //throw FileError, ErrorFileLocked createItemPathNative(tempFilePath), false /*copyFilePermissions*/, true /*transactionalCopy*/, nullptr /*onDeleteTargetFile*/, notifyUnbufferedIO); //result.errorModTime? => irrelevant for temp files! - statReporter.reportDelta(1, 0); tempFilePaths_[descr] = tempFilePath; }, callback); //throw X + + callback.requestUiRefresh(); //throw X } } diff --git a/FreeFileSync/Source/algorithm.h b/FreeFileSync/Source/base/algorithm.h index f3a81e12..178e056e 100755 --- a/FreeFileSync/Source/algorithm.h +++ b/FreeFileSync/Source/base/algorithm.h @@ -9,8 +9,8 @@ #include <functional> #include "file_hierarchy.h" -#include "lib/soft_filter.h" -#include "lib/process_xml.h" +#include "soft_filter.h" +#include "process_xml.h" #include "process_callback.h" diff --git a/FreeFileSync/Source/application.cpp b/FreeFileSync/Source/base/application.cpp index f4519639..6169031d 100755 --- a/FreeFileSync/Source/application.cpp +++ b/FreeFileSync/Source/base/application.cpp @@ -16,12 +16,12 @@ #include "comparison.h" #include "algorithm.h" #include "synchronization.h" -#include "ui/batch_status_handler.h" -#include "ui/main_dlg.h" -#include "lib/help_provider.h" -#include "lib/process_xml.h" -#include "lib/error_log.h" -#include "lib/resolve_path.h" +#include "help_provider.h" +#include "process_xml.h" +#include "error_log.h" +#include "resolve_path.h" +#include "../ui/batch_status_handler.h" +#include "../ui/main_dlg.h" #include <gtk/gtk.h> @@ -49,7 +49,6 @@ const wxEventType EVENT_ENTER_EVENT_LOOP = wxNewEventType(); //################################################################################################################## - bool Application::OnInit() { //do not call wxApp::OnInit() to avoid using wxWidgets command line parser @@ -130,7 +129,8 @@ void Application::onQueryEndSession(wxEvent& event) if (auto mainWin = dynamic_cast<MainDialog*>(GetTopWindow())) mainWin->onQueryEndSession(); //it's futile to try and clean up while the process is in full swing (CRASH!) => just terminate! - std::abort(); //on Windows calls ::ExitProcess() which can still internally process Window messages and crash! + std::exit(FFS_RC_ABORTED); + //don't use std::abort() => crashes process with "EXC_CRASH (SIGABRT)" on macOS } diff --git a/FreeFileSync/Source/application.h b/FreeFileSync/Source/base/application.h index 1b930074..c08491c8 100755 --- a/FreeFileSync/Source/application.h +++ b/FreeFileSync/Source/base/application.h @@ -10,7 +10,7 @@ #include <vector> #include <zen/zstring.h> #include <wx/app.h> -#include "lib/return_codes.h" +#include "return_codes.h" namespace fff //avoid name clash with "int ffs()" for fuck's sake! (maxOS, Linux issue only: <string> internally includes <strings.h>, WTF!) @@ -19,7 +19,7 @@ class Application : public wxApp { private: bool OnInit() override; - int OnRun() override; + int OnRun () override; int OnExit() override; bool OnExceptionInMainLoop() override { throw; } //just re-throw and avoid display of additional messagebox: it will be caught in OnRun() void OnUnhandledException () override { throw; } //just re-throw and avoid display of additional messagebox diff --git a/FreeFileSync/Source/lib/binary.cpp b/FreeFileSync/Source/base/binary.cpp index bfa5cb97..32fe37a6 100755 --- a/FreeFileSync/Source/lib/binary.cpp +++ b/FreeFileSync/Source/base/binary.cpp @@ -44,7 +44,7 @@ struct StreamReader defaultBlockSize_(stream_->getBlockSize()), dynamicBlockSize_(defaultBlockSize_) { assert(defaultBlockSize_ > 0); } - void appendChunk(std::vector<char>& buffer) //throw FileError, X + void appendChunk(std::vector<std::byte>& buffer) //throw FileError, X { assert(!eof_); if (eof_) return; @@ -104,8 +104,8 @@ bool fff::filesHaveSameContent(const AbstractPath& filePath1, const AbstractPath StreamReader* readerLow = &reader1; StreamReader* readerHigh = &reader2; - std::vector<char> bufferLow; - std::vector<char> bufferHigh; + std::vector<std::byte> bufferLow; + std::vector<std::byte> bufferHigh; for (;;) { diff --git a/FreeFileSync/Source/lib/binary.h b/FreeFileSync/Source/base/binary.h index bba321da..bba321da 100755 --- a/FreeFileSync/Source/lib/binary.h +++ b/FreeFileSync/Source/base/binary.h diff --git a/FreeFileSync/Source/lib/cmp_filetime.h b/FreeFileSync/Source/base/cmp_filetime.h index ce3a04e9..ce3a04e9 100755 --- a/FreeFileSync/Source/lib/cmp_filetime.h +++ b/FreeFileSync/Source/base/cmp_filetime.h diff --git a/FreeFileSync/Source/comparison.cpp b/FreeFileSync/Source/base/comparison.cpp index b0e7bcec..75aaee7f 100755 --- a/FreeFileSync/Source/comparison.cpp +++ b/FreeFileSync/Source/base/comparison.cpp @@ -8,12 +8,12 @@ #include <zen/process_priority.h> #include <zen/perf.h> #include "algorithm.h" -#include "lib/parallel_scan.h" -#include "lib/dir_exist_async.h" -#include "lib/binary.h" -#include "lib/cmp_filetime.h" -#include "lib/status_handler_impl.h" -#include "fs/concrete.h" +#include "parallel_scan.h" +#include "dir_exist_async.h" +#include "binary.h" +#include "cmp_filetime.h" +#include "status_handler_impl.h" +#include "../fs/concrete.h" using namespace zen; using namespace fff; @@ -73,7 +73,7 @@ ResolvedBaseFolders initializeBaseFolders(const std::vector<FolderPairCfg>& fpCf { std::set<AbstractPath> uniqueBaseFolders; - //support "retry" for environment variable and and variable driver letter resolution! + //support "retry" for environment variable and variable driver letter resolution! output.resolvedPairs.clear(); for (const FolderPairCfg& fpCfg : fpCfgList) { @@ -143,7 +143,8 @@ private: std::map<DirectoryKey, DirectoryValue> directoryBuffer_; //contains only *existing* directories const int fileTimeTolerance_; - ProcessCallback& callback_; + ProcessCallback& cb_; + const std::map<AbstractPath, size_t> deviceParallelOps_; }; @@ -151,24 +152,24 @@ ComparisonBuffer::ComparisonBuffer(const std::set<DirectoryKey>& foldersToRead, const std::map<AbstractPath, size_t>& deviceParallelOps, int fileTimeTolerance, ProcessCallback& callback) : - fileTimeTolerance_(fileTimeTolerance), callback_(callback) + fileTimeTolerance_(fileTimeTolerance), cb_(callback), deviceParallelOps_(deviceParallelOps) { class CbImpl : public FillBufferCallback { public: - CbImpl(ProcessCallback& pcb) : callback_(pcb) {} + CbImpl(ProcessCallback& pcb) : cb_(pcb) {} - void reportStatus(const std::wstring& statusMsg, int itemsTotal) override + void reportStatus(const std::wstring& statusMsg, int itemsTotal) override //throw X { - callback_.updateDataProcessed(itemsTotal - itemsReported_, 0); //processed bytes are reported in subfunctions! + cb_.updateDataProcessed(itemsTotal - itemsReported_, 0); //processed bytes are reported in subfunctions! itemsReported_ = itemsTotal; - callback_.reportStatus(statusMsg); //may throw + cb_.reportStatus(statusMsg); //throw X } HandleError reportError(const std::wstring& msg, size_t retryNumber) override { - switch (callback_.reportError(msg, retryNumber)) + switch (cb_.reportError(msg, retryNumber)) { case ProcessCallback::IGNORE_ERROR: return ON_ERROR_CONTINUE; @@ -184,17 +185,17 @@ ComparisonBuffer::ComparisonBuffer(const std::set<DirectoryKey>& foldersToRead, int getItemsTotal() const { return itemsReported_; } private: - ProcessCallback& callback_; + ProcessCallback& cb_; int itemsReported_ = 0; } cb(callback); - fillBuffer(foldersToRead, //in + fillBuffer(foldersToRead, //in directoryBuffer_, //out deviceParallelOps, - cb, + cb, //throw X UI_UPDATE_INTERVAL / 2); //every ~50 ms - callback.reportInfo(_("Comparison finished:") + L" " + _P("1 item found", "%x items found", cb.getItemsTotal())); + callback.reportInfo(_("Comparison finished:") + L" " + _P("1 item found", "%x items found", cb.getItemsTotal())); //throw X } @@ -209,41 +210,41 @@ const wchar_t arrowRight[] = L"->"; // => only add path info if information is relevant, e.g. conflict is specific to left/right side only template <SelectedSide side, class FileOrLinkPair> inline -std::wstring getConflictInvalidDate(const FileOrLinkPair& file) +Zstringw getConflictInvalidDate(const FileOrLinkPair& file) { - return replaceCpy(_("File %x has an invalid date."), L"%x", fmtPath(AFS::getDisplayPath(file.template getAbstractPath<side>()))) + L"\n" + - _("Date:") + L" " + formatUtcToLocalTime(file.template getLastWriteTime<side>()); + return copyStringTo<Zstringw>(replaceCpy(_("File %x has an invalid date."), L"%x", fmtPath(AFS::getDisplayPath(file.template getAbstractPath<side>()))) + L"\n" + + _("Date:") + L" " + formatUtcToLocalTime(file.template getLastWriteTime<side>())); } -std::wstring getConflictSameDateDiffSize(const FilePair& file) +Zstringw getConflictSameDateDiffSize(const FilePair& file) { - return _("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<LEFT_SIDE>()) + L"\n" + - arrowRight + L" " + _("Date:") + L" " + formatUtcToLocalTime(file.getLastWriteTime<RIGHT_SIDE>()) + L" " + _("Size:") + L" " + formatNumber(file.getFileSize<RIGHT_SIDE>()); + return copyStringTo<Zstringw>(_("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<LEFT_SIDE>()) + L"\n" + + arrowRight + L" " + _("Date:") + L" " + formatUtcToLocalTime(file.getLastWriteTime<RIGHT_SIDE>()) + L" " + _("Size:") + L" " + formatNumber(file.getFileSize<RIGHT_SIDE>())); } -std::wstring getConflictSkippedBinaryComparison(const FilePair& file) +Zstringw getConflictSkippedBinaryComparison(const FilePair& file) { - return _("Content comparison was skipped for excluded files."); + return copyStringTo<Zstringw>(_("Content comparison was skipped for excluded files.")); } -std::wstring getDescrDiffMetaShortnameCase(const FileSystemObject& fsObj) +Zstringw getDescrDiffMetaShortnameCase(const FileSystemObject& fsObj) { - return _("Items differ in attributes only") + L"\n" + - arrowLeft + L" " + fmtPath(fsObj.getItemName< LEFT_SIDE>()) + L"\n" + - arrowRight + L" " + fmtPath(fsObj.getItemName<RIGHT_SIDE>()); + return copyStringTo<Zstringw>(_("Items differ in attributes only") + L"\n" + + arrowLeft + L" " + fmtPath(fsObj.getItemName< LEFT_SIDE>()) + L"\n" + + arrowRight + L" " + fmtPath(fsObj.getItemName<RIGHT_SIDE>())); } template <class FileOrLinkPair> -std::wstring getDescrDiffMetaDate(const FileOrLinkPair& file) +Zstringw getDescrDiffMetaDate(const FileOrLinkPair& file) { - return _("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<RIGHT_SIDE>()); + return copyStringTo<Zstringw>(_("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<RIGHT_SIDE>())); } //----------------------------------------------------------------------------- @@ -338,22 +339,24 @@ std::shared_ptr<BaseFolderPair> ComparisonBuffer::compareByTimeSize(const Resolv } +namespace +{ void categorizeSymlinkByContent(SymlinkPair& symlink, ProcessCallback& callback) { //categorize symlinks that exist on both sides std::string binaryContentL; std::string binaryContentR; - Opt<std::wstring> errMsg = tryReportingError([&] + const std::wstring errMsg = tryReportingError([&] { - callback.reportStatus(replaceCpy(_("Resolving symbolic link %x"), L"%x", fmtPath(AFS::getDisplayPath(symlink.getAbstractPath<LEFT_SIDE>())))); + callback.reportStatus(replaceCpy(_("Resolving symbolic link %x"), L"%x", fmtPath(AFS::getDisplayPath(symlink.getAbstractPath<LEFT_SIDE>())))); //throw X binaryContentL = AFS::getSymlinkBinaryContent(symlink.getAbstractPath<LEFT_SIDE>()); //throw FileError - callback.reportStatus(replaceCpy(_("Resolving symbolic link %x"), L"%x", fmtPath(AFS::getDisplayPath(symlink.getAbstractPath<RIGHT_SIDE>())))); + callback.reportStatus(replaceCpy(_("Resolving symbolic link %x"), L"%x", fmtPath(AFS::getDisplayPath(symlink.getAbstractPath<RIGHT_SIDE>())))); //throw X binaryContentR = AFS::getSymlinkBinaryContent(symlink.getAbstractPath<RIGHT_SIDE>()); //throw FileError }, callback); //throw X - if (errMsg) - symlink.setCategoryConflict(*errMsg); + if (!errMsg.empty()) + symlink.setCategoryConflict(copyStringTo<Zstringw>(errMsg)); else { if (binaryContentL == binaryContentR) @@ -375,6 +378,7 @@ void categorizeSymlinkByContent(SymlinkPair& symlink, ProcessCallback& callback) symlink.setCategory<FILE_DIFFERENT_CONTENT>(); } } +} std::shared_ptr<BaseFolderPair> ComparisonBuffer::compareBySize(const ResolvedFolderPair& fp, const FolderPairCfg& fpConfig) const @@ -386,7 +390,7 @@ std::shared_ptr<BaseFolderPair> ComparisonBuffer::compareBySize(const ResolvedFo //finish symlink categorization for (SymlinkPair* symlink : uncategorizedLinks) - categorizeSymlinkByContent(*symlink, callback_); //"compare by size" has the semantics of a quick content-comparison! + categorizeSymlinkByContent(*symlink, cb_); //"compare by size" has the semantics of a quick content-comparison! //harmonize with algorithm.cpp, stillInSync()! //categorize files that exist on both sides @@ -410,17 +414,113 @@ std::shared_ptr<BaseFolderPair> ComparisonBuffer::compareBySize(const ResolvedFo } +namespace parallel +{ +//-------------------------------------------------------------- +//ATTENTION CALLBACKS: they also run asynchronously *outside* the singleThread lock! +//-------------------------------------------------------------- +inline +bool filesHaveSameContent(const AbstractPath& filePath1, const AbstractPath& filePath2, //throw FileError + const zen::IOCallback& notifyUnbufferedIO, //may be nullptr + std::mutex& singleThread) +{ return parallelScope([=] { return filesHaveSameContent(filePath1, filePath2, notifyUnbufferedIO); /*throw FileError*/ }, singleThread); } +} + +namespace +{ +void categorizeFileByContent(FilePair& file, const std::wstring& txtComparingContentOfFiles, AsyncCallback& acb, std::mutex& singleThread) //throw ThreadInterruption +{ + acb.reportStatus(replaceCpy(txtComparingContentOfFiles, L"%x", fmtPath(file.getPairRelativePath()))); //throw ThreadInterruption + + bool haveSameContent = false; + const std::wstring errMsg = tryReportingError([&] + { + AsyncItemStatReporter statReporter(1, file.getFileSize<LEFT_SIDE>(), acb); + + //callbacks run *outside* singleThread_ lock! => fine + auto notifyUnbufferedIO = [&statReporter](int64_t bytesDelta) + { + statReporter.reportDelta(0, bytesDelta); + interruptionPoint(); //throw ThreadInterruption + }; + + haveSameContent = parallel::filesHaveSameContent(file.getAbstractPath<LEFT_SIDE >(), + file.getAbstractPath<RIGHT_SIDE>(), notifyUnbufferedIO, singleThread); //throw FileError + statReporter.reportDelta(1, 0); + }, acb); //throw ThreadInterruption + + if (!errMsg.empty()) + file.setCategoryConflict(copyStringTo<Zstringw>(errMsg)); + else + { + if (haveSameContent) + { + //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.getItemName<LEFT_SIDE>() != file.getItemName<RIGHT_SIDE>()) + file.setCategoryDiffMetadata(getDescrDiffMetaShortnameCase(file)); +#if 0 //don't synchronize modtime only see FolderPairSyncer::synchronizeFileInt(), SO_COPY_METADATA_TO_* + else if (!sameFileTime(file.getLastWriteTime<LEFT_SIDE>(), + file.getLastWriteTime<RIGHT_SIDE>(), file.base().getFileTimeTolerance(), file.base().getIgnoredTimeShift())) + file.setCategoryDiffMetadata(getDescrDiffMetaDate(file)); +#endif + else + file.setCategory<FILE_EQUAL>(); + } + else + file.setCategory<FILE_DIFFERENT_CONTENT>(); + } +} +} + + std::list<std::shared_ptr<BaseFolderPair>> ComparisonBuffer::compareByContent(const std::vector<std::pair<ResolvedFolderPair, FolderPairCfg>>& workLoad) const { - warn_static("perf: make parallel") - std::list<std::shared_ptr<BaseFolderPair>> output; - if (workLoad.empty()) - return output; + struct ParallelOps + { + size_t current = 0; + size_t effectiveMax = 0; //a folder pair is allowed to use the maximum parallelOps that left/right devices support + // => consider max over all folder pairs, that a device is involved with! + }; + std::map<AbstractPath, ParallelOps> parallelOpsStatus; + + struct BinaryWorkload + { + ParallelOps& parallelOpsL; // + ParallelOps& parallelOpsR; //consider aliasing! + RingBuffer<FilePair*> filesToCompareBytewise; + }; + std::vector<BinaryWorkload> fpWorkload; + + auto getDefaultParallelOps = [&](const AbstractPath& rootPath) + { + auto itParOps = deviceParallelOps_.find(rootPath); + return std::max<size_t>(itParOps != deviceParallelOps_.end() ? itParOps->second : 1, 1); //sanitize early for correct status display + }; + + auto addToBinaryWorkload = [&](const AbstractPath& basePathL, const AbstractPath& basePathR, RingBuffer<FilePair*>&& filesToCompareBytewise) + { + const AbstractPath rootPathL = AFS::getPathComponents(basePathL).rootPath; + const AbstractPath rootPathR = AFS::getPathComponents(basePathR).rootPath; + + //calculate effective max parallelOps that devices must support + const size_t parallelOpsFp = std::max(getDefaultParallelOps(rootPathL), + getDefaultParallelOps(rootPathR)); + + ParallelOps& posL = parallelOpsStatus[rootPathL]; + ParallelOps& posR = parallelOpsStatus[rootPathR]; + + posL.effectiveMax = std::max(posL.effectiveMax, parallelOpsFp); + posR.effectiveMax = std::max(posR.effectiveMax, parallelOpsFp); + + fpWorkload.push_back({ posL, posR, std::move(filesToCompareBytewise) }); + }; //PERF_START; - std::vector<FilePair*> filesToCompareBytewise; + std::list<std::shared_ptr<BaseFolderPair>> output; - //process folder pairs one after another for (const auto& w : workLoad) { std::vector<FilePair*> undefinedFiles; @@ -431,6 +531,7 @@ std::list<std::shared_ptr<BaseFolderPair>> ComparisonBuffer::compareByContent(co //content comparison of file content happens AFTER finding corresponding files and AFTER filtering //in order to separate into two processes (scanning and comparing) + RingBuffer<FilePair*> filesToCompareBytewise; for (FilePair* file : undefinedFiles) //pre-check: files have different content if they have a different filesize (must not be FILE_EQUAL: see InSyncFile) if (file->getFileSize<LEFT_SIDE>() != file->getFileSize<RIGHT_SIDE>()) @@ -444,68 +545,95 @@ std::list<std::shared_ptr<BaseFolderPair>> ComparisonBuffer::compareByContent(co else filesToCompareBytewise.push_back(file); } + if (!filesToCompareBytewise.empty()) + addToBinaryWorkload(output.back()->getAbstractPath<LEFT_SIDE >(), + output.back()->getAbstractPath<RIGHT_SIDE>(), std::move(filesToCompareBytewise)); //finish symlink categorization for (SymlinkPair* symlink : uncategorizedLinks) - categorizeSymlinkByContent(*symlink, callback_); + categorizeSymlinkByContent(*symlink, cb_); } - //finish categorization... - const int itemsTotal = static_cast<int>(filesToCompareBytewise.size()); + //finish categorization: compare files (that have same size) bytewise... + if (!fpWorkload.empty()) //run PHASE_COMPARING_CONTENT only when needed + { + int itemsTotal = 0; + uint64_t bytesTotal = 0; + for (const BinaryWorkload& bwl : fpWorkload) + { + itemsTotal += bwl.filesToCompareBytewise.size(); - uint64_t bytesTotal = 0; //left and right filesizes are equal - for (FilePair* file : filesToCompareBytewise) - bytesTotal += file->getFileSize<LEFT_SIDE>(); + for (const FilePair* file : bwl.filesToCompareBytewise) + bytesTotal += file->getFileSize<LEFT_SIDE>(); //left and right file sizes are equal + } + cb_.initNewPhase(itemsTotal, bytesTotal, ProcessCallback::PHASE_COMPARING_CONTENT); //throw X - callback_.initNewPhase(itemsTotal, bytesTotal, ProcessCallback::PHASE_COMPARING_CONTENT); //may throw + //PERF_START; - const std::wstring txtComparingContentOfFiles = _("Comparing content of files %x"); + warn_static("review") - //PERF_START; + std::mutex singleThread; //only a single worker thread may run at a time, except for parallel file I/O - //compare files (that have same size) bytewise... - for (FilePair* file : filesToCompareBytewise) - { - callback_.reportStatus(replaceCpy(txtComparingContentOfFiles, L"%x", fmtPath(file->getPairRelativePath()))); + AsyncCallback acb; // + std::function<void()> scheduleMoreTasks; //manage life time: enclose ThreadGroup! + const std::wstring txtComparingContentOfFiles = _("Comparing content of files %x"); // - //check files that exist in left and right model but have different content + ThreadGroup<std::function<void()>> tg(std::numeric_limits<size_t>::max(), "Binary Comparison"); - bool haveSameContent = false; - Opt<std::wstring> errMsg = tryReportingError([&] + scheduleMoreTasks = [&] { - ItemStatReporter statReporter(1, file->getFileSize<LEFT_SIDE>(), callback_); + bool wereDone = true; - auto notifyUnbufferedIO = [&](int64_t bytesDelta) { statReporter.reportDelta(0, bytesDelta); }; + for (size_t j = 0; j < fpWorkload.size(); ++j) + { + BinaryWorkload& bwl = fpWorkload[j]; - haveSameContent = filesHaveSameContent(file->getAbstractPath<LEFT_SIDE>(), - file->getAbstractPath<RIGHT_SIDE>(), notifyUnbufferedIO); //throw FileError - statReporter.reportDelta(1, 0); - }, callback_); //throw X + ParallelOps& posL = bwl.parallelOpsL; + ParallelOps& posR = bwl.parallelOpsR; - if (errMsg) - file->setCategoryConflict(*errMsg); - else - { - if (haveSameContent) - { - //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->getItemName<LEFT_SIDE>() != file->getItemName<RIGHT_SIDE>()) - file->setCategoryDiffMetadata(getDescrDiffMetaShortnameCase(*file)); -#if 0 //don't synchronize modtime only see FolderPairSyncer::synchronizeFileInt(), SO_COPY_METADATA_TO_* - else if (!sameFileTime(file->getLastWriteTime<LEFT_SIDE>(), - file->getLastWriteTime<RIGHT_SIDE>(), file->base().getFileTimeTolerance(), file->base().getIgnoredTimeShift())) - file->setCategoryDiffMetadata(getDescrDiffMetaDate(*file)); -#endif - else - file->setCategory<FILE_EQUAL>(); + const size_t newTaskCount = numeric::min<size_t>(posL.effectiveMax - posL.current, + posR.effectiveMax - posR.current, + bwl.filesToCompareBytewise.size()); + if (&posL != &posR) posL.current += newTaskCount; // + /**/ posR.current += newTaskCount; //consider aliasing! + + for (size_t i = 0; i < newTaskCount; ++i) + { + tg.run([&, statusPrio = j, &file = *bwl.filesToCompareBytewise.front()] + { + acb.notifyTaskBegin(statusPrio); //prioritize status messages according to natural order of folder pairs + ZEN_ON_SCOPE_EXIT(acb.notifyTaskEnd()); + + std::lock_guard<std::mutex> dummy(singleThread); //protect ALL variable accesses unless explicitly not needed ("parallel" scope)! + //--------------------------------------------------------------------------------------------------- + ZEN_ON_SCOPE_SUCCESS(if (&posL != &posR) --posL.current; + /**/ --posR.current; + scheduleMoreTasks();); + + categorizeFileByContent(file, txtComparingContentOfFiles, acb, singleThread); //throw ThreadInterruption + }); + + bwl.filesToCompareBytewise.pop_front(); + } + + assert(0 <= posL.current && posL.current <= posL.effectiveMax); + assert(0 <= posR.current && posR.current <= posR.effectiveMax); + + if (posL.current != 0 || posR.current != 0 || !bwl.filesToCompareBytewise.empty()) + wereDone = false; } - else - file->setCategory<FILE_DIFFERENT_CONTENT>(); + if (wereDone) + acb.notifyAllDone(); + }; + + { + std::lock_guard<std::mutex> dummy(singleThread); //[!] potential race with worker threads! + scheduleMoreTasks(); //set initial load } + + acb.waitUntilDone(UI_UPDATE_INTERVAL / 2 /*every ~50 ms*/, cb_); //throw X } + return output; } @@ -514,7 +642,7 @@ std::list<std::shared_ptr<BaseFolderPair>> ComparisonBuffer::compareByContent(co class MergeSides { public: - MergeSides(const std::map<Zstring, std::wstring, LessFilePath>& failedItemReads, + MergeSides(const std::map<Zstring, Zstringw, LessFilePath>& failedItemReads, std::vector<FilePair*>& undefinedFilesOut, std::vector<SymlinkPair*>& undefinedSymlinksOut) : failedItemReads_(failedItemReads), @@ -531,21 +659,21 @@ public: } private: - void mergeTwoSides(const FolderContainer& lhs, const FolderContainer& rhs, const std::wstring* errorMsg, ContainerObject& output); + void mergeTwoSides(const FolderContainer& lhs, const FolderContainer& rhs, const Zstringw* errorMsg, ContainerObject& output); template <SelectedSide side> - void fillOneSide(const FolderContainer& folderCont, const std::wstring* errorMsg, ContainerObject& output); + void fillOneSide(const FolderContainer& folderCont, const Zstringw* errorMsg, ContainerObject& output); - const std::wstring* checkFailedRead(FileSystemObject& fsObj, const std::wstring* errorMsg); + const Zstringw* checkFailedRead(FileSystemObject& fsObj, const Zstringw* errorMsg); - const std::map<Zstring, std::wstring, LessFilePath>& failedItemReads_; //base-relative paths or empty if read-error for whole base directory + const std::map<Zstring, Zstringw, LessFilePath>& failedItemReads_; //base-relative paths or empty if read-error for whole base directory std::vector<FilePair*>& undefinedFiles_; std::vector<SymlinkPair*>& undefinedSymlinks_; }; inline -const std::wstring* MergeSides::checkFailedRead(FileSystemObject& fsObj, const std::wstring* errorMsg) +const Zstringw* MergeSides::checkFailedRead(FileSystemObject& fsObj, const Zstringw* errorMsg) { if (!errorMsg) { @@ -557,14 +685,15 @@ const std::wstring* MergeSides::checkFailedRead(FileSystemObject& fsObj, const s if (errorMsg) { fsObj.setActive(false); - fsObj.setCategoryConflict(*errorMsg); + fsObj.setCategoryConflict(*errorMsg); //peak memory: Zstringw is ref-counted, unlike std::wstring! + static_assert(std::is_same_v<const Zstringw&, decltype(*errorMsg)>); } return errorMsg; } template <SelectedSide side> -void MergeSides::fillOneSide(const FolderContainer& folderCont, const std::wstring* errorMsg, ContainerObject& output) +void MergeSides::fillOneSide(const FolderContainer& folderCont, const Zstringw* errorMsg, ContainerObject& output) { for (const auto& file : folderCont.files) { @@ -581,7 +710,7 @@ void MergeSides::fillOneSide(const FolderContainer& folderCont, const std::wstri for (const auto& dir : folderCont.folders) { FolderPair& newFolder = output.addSubFolder<side>(dir.first, dir.second.first); - const std::wstring* errorMsgNew = checkFailedRead(newFolder, errorMsg); + const Zstringw* errorMsgNew = checkFailedRead(newFolder, errorMsg); fillOneSide<side>(dir.second.second, errorMsgNew, newFolder); //recurse } } @@ -628,9 +757,9 @@ void linearMerge(const MapType& mapLeft, const MapType& mapRight, ProcessLeftOnl } -void MergeSides::mergeTwoSides(const FolderContainer& lhs, const FolderContainer& rhs, const std::wstring* errorMsg, ContainerObject& output) +void MergeSides::mergeTwoSides(const FolderContainer& lhs, const FolderContainer& rhs, const Zstringw* errorMsg, ContainerObject& output) { - using FileData = const FolderContainer::FileList::value_type; + using FileData = FolderContainer::FileList::value_type; linearMerge(lhs.files, rhs.files, [&](const FileData& fileLeft ) { FilePair& newItem = output.addSubFile< LEFT_SIDE>(fileLeft .first, fileLeft .second); checkFailedRead(newItem, errorMsg); }, //left only @@ -640,16 +769,16 @@ void MergeSides::mergeTwoSides(const FolderContainer& lhs, const FolderContainer { FilePair& newItem = output.addSubFile(fileLeft.first, fileLeft.second, - FILE_EQUAL, //dummy-value until categorization is finished later + FILE_CONFLICT, //dummy-value until categorization is finished later fileRight.first, fileRight.second); if (!checkFailedRead(newItem, errorMsg)) undefinedFiles_.push_back(&newItem); - static_assert(IsSameType<ContainerObject::FileList, FixedList<FilePair>>::value, ""); //ContainerObject::addSubFile() must NOT invalidate references used in "undefinedFiles"! + static_assert(std::is_same_v<ContainerObject::FileList, std::list<FilePair>>); //ContainerObject::addSubFile() must NOT invalidate references used in "undefinedFiles"! }); //----------------------------------------------------------------------------------------------- - using SymlinkData = const FolderContainer::SymlinkList::value_type; + using SymlinkData = FolderContainer::SymlinkList::value_type; linearMerge(lhs.symlinks, rhs.symlinks, [&](const SymlinkData& symlinkLeft ) { SymlinkPair& newItem = output.addSubLink< LEFT_SIDE>(symlinkLeft .first, symlinkLeft .second); checkFailedRead(newItem, errorMsg); }, //left only @@ -659,7 +788,7 @@ void MergeSides::mergeTwoSides(const FolderContainer& lhs, const FolderContainer { SymlinkPair& newItem = output.addSubLink(symlinkLeft.first, symlinkLeft.second, - SYMLINK_EQUAL, //dummy-value until categorization is finished later + SYMLINK_CONFLICT, //dummy-value until categorization is finished later symlinkRight.first, symlinkRight.second); if (!checkFailedRead(newItem, errorMsg)) @@ -667,26 +796,26 @@ void MergeSides::mergeTwoSides(const FolderContainer& lhs, const FolderContainer }); //----------------------------------------------------------------------------------------------- - using FolderData = const FolderContainer::FolderList::value_type; + using FolderData = FolderContainer::FolderList::value_type; linearMerge(lhs.folders, rhs.folders, [&](const FolderData& dirLeft) //left only { FolderPair& newFolder = output.addSubFolder<LEFT_SIDE>(dirLeft.first, dirLeft.second.first); - const std::wstring* errorMsgNew = checkFailedRead(newFolder, errorMsg); + const Zstringw* errorMsgNew = checkFailedRead(newFolder, errorMsg); this->fillOneSide<LEFT_SIDE>(dirLeft.second.second, errorMsgNew, newFolder); //recurse }, [&](const FolderData& dirRight) //right only { FolderPair& newFolder = output.addSubFolder<RIGHT_SIDE>(dirRight.first, dirRight.second.first); - const std::wstring* errorMsgNew = checkFailedRead(newFolder, errorMsg); + const Zstringw* errorMsgNew = checkFailedRead(newFolder, errorMsg); this->fillOneSide<RIGHT_SIDE>(dirRight.second.second, errorMsgNew, newFolder); //recurse }, [&](const FolderData& dirLeft, const FolderData& dirRight) //both sides { FolderPair& newFolder = output.addSubFolder(dirLeft.first, dirLeft.second.first, DIR_EQUAL, dirRight.first, dirRight.second.first); - const std::wstring* errorMsgNew = checkFailedRead(newFolder, errorMsg); + const Zstringw* errorMsgNew = checkFailedRead(newFolder, errorMsg); if (!errorMsgNew) if (dirLeft.first != dirRight.first) @@ -706,8 +835,8 @@ void stripExcludedDirectories(ContainerObject& hierObj, const HardFilter& filter //remove superfluous directories: // this does not invalidate "std::vector<FilePair*>& undefinedFiles", since we delete folders only - // and there is no side-effect for memory positions of FilePair and SymlinkPair thanks to zen::FixedList! - static_assert(IsSameType<FixedList<FolderPair>, ContainerObject::FolderList>::value, ""); + // and there is no side-effect for memory positions of FilePair and SymlinkPair thanks to std::list! + static_assert(std::is_same_v<std::list<FolderPair>, ContainerObject::FolderList>); hierObj.refSubFolders().remove_if([&](FolderPair& folder) { @@ -730,8 +859,8 @@ std::shared_ptr<BaseFolderPair> ComparisonBuffer::performComparison(const Resolv std::vector<FilePair*>& undefinedFiles, std::vector<SymlinkPair*>& undefinedSymlinks) const { - callback_.reportStatus(_("Generating file list...")); - callback_.forceUiRefresh(); //throw X + cb_.reportStatus(_("Generating file list...")); //throw X + cb_.forceUiRefresh(); //throw X auto getDirValue = [&](const AbstractPath& folderPath) -> const DirectoryValue* { @@ -742,16 +871,21 @@ std::shared_ptr<BaseFolderPair> ComparisonBuffer::performComparison(const Resolv const DirectoryValue* bufValueLeft = getDirValue(fp.folderPathLeft); const DirectoryValue* bufValueRight = getDirValue(fp.folderPathRight); - std::map<Zstring, std::wstring, LessFilePath> failedReads; //base-relative paths or empty if read-error for whole base directory + std::map<Zstring, Zstringw, LessFilePath> failedReads; //base-relative paths or empty if read-error for whole base directory { + auto append = [&](const std::map<Zstring, std::wstring, LessFilePath>& c) + { + for (const auto& item : c) + failedReads.emplace(item.first, copyStringTo<Zstringw>(item.second)); + }; //mix failedFolderReads with failedItemReads: //mark directory errors already at directory-level (instead for child items only) to show on GUI! See "MergeSides" //=> minor pessimization for "excludefilterFailedRead" which needlessly excludes parent folders, too - if (bufValueLeft ) append(failedReads, bufValueLeft ->failedFolderReads); - if (bufValueRight) append(failedReads, bufValueRight->failedFolderReads); + if (bufValueLeft ) append(bufValueLeft ->failedFolderReads); + if (bufValueRight) append(bufValueRight->failedFolderReads); - if (bufValueLeft ) append(failedReads, bufValueLeft ->failedItemReads); - if (bufValueRight) append(failedReads, bufValueRight->failedItemReads); + if (bufValueLeft ) append(bufValueLeft ->failedItemReads); + if (bufValueRight) append(bufValueRight->failedItemReads); } Zstring excludefilterFailedRead; @@ -822,7 +956,7 @@ void fff::logNonDefaultSettings(const XmlGlobalSettings& activeSettings, Process changedSettingsMsg += L"\n " + _("Verify copied files") + L" - " + (activeSettings.verifyFileCopy ? _("Enabled") : _("Disabled")); if (!changedSettingsMsg.empty()) - callback.reportInfo(_("Using non-default global settings:") + changedSettingsMsg); + callback.reportInfo(_("Using non-default global settings:") + changedSettingsMsg); //throw X } @@ -841,7 +975,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, 0, ProcessCallback::PHASE_SCANNING); //may throw; it's unknown how many files will be scanned => -1 objects + callback.initNewPhase(-1, 0, ProcessCallback::PHASE_SCANNING); //throw X; it's unknown how many files will be scanned => -1 objects //callback.reportInfo(Comparison started")); -> still useful? //------------------------------------------------------------------------------- @@ -855,7 +989,7 @@ FolderComparison fff::compare(WarningDialogs& warnings, } catch (const FileError& e) //not an error in this context { - callback.reportInfo(e.toString()); //may throw! + callback.reportInfo(e.toString()); //throw X } //prevent operating system going into sleep state @@ -866,7 +1000,7 @@ FolderComparison fff::compare(WarningDialogs& warnings, } catch (const FileError& e) //not an error in this context { - callback.reportInfo(e.toString()); //may throw! + callback.reportInfo(e.toString()); //throw X } const ResolvedBaseFolders& resInfo = initializeBaseFolders(fpCfgList, deviceParallelOps, folderAccessTimeout, allowUserInteraction, callback); @@ -978,7 +1112,7 @@ FolderComparison fff::compare(WarningDialogs& warnings, if (!outputByContent.empty()) { output.push_back(outputByContent.front()); - outputByContent.pop_front(); + /**/ outputByContent.pop_front(); } break; } @@ -991,7 +1125,7 @@ FolderComparison fff::compare(WarningDialogs& warnings, { const FolderPairCfg& fpCfg = fpCfgList[it - output.begin()]; - callback.reportStatus(_("Calculating sync directions...")); + callback.reportStatus(_("Calculating sync directions...")); //throw X callback.forceUiRefresh(); //throw X tryReportingError([&] diff --git a/FreeFileSync/Source/comparison.h b/FreeFileSync/Source/base/comparison.h index 4e9e7b5a..356dbe89 100755 --- a/FreeFileSync/Source/comparison.h +++ b/FreeFileSync/Source/base/comparison.h @@ -8,10 +8,10 @@ #define COMPARISON_H_8032178534545426 #include "file_hierarchy.h" -#include "lib/process_xml.h" +#include "process_xml.h" #include "process_callback.h" -#include "lib/norm_filter.h" -#include "lib/lock_holder.h" +#include "norm_filter.h" +#include "lock_holder.h" namespace fff diff --git a/FreeFileSync/Source/lib/db_file.cpp b/FreeFileSync/Source/base/db_file.cpp index 6b13014d..5a134e29 100755 --- a/FreeFileSync/Source/lib/db_file.cpp +++ b/FreeFileSync/Source/base/db_file.cpp @@ -272,7 +272,7 @@ private: { writeNumber<std::int64_t>(streamOut, descr.modTime); writeContainer(streamOut, descr.fileId); - static_assert(IsSameType<decltype(descr.fileId), Zbase<char>>::value, ""); + static_assert(std::is_same_v<decltype(descr.fileId), Zbase<char>>); } static void writeLinkDescr(MemoryStreamOut<ByteArray>& streamOut, const InSyncDescrLink& descr) @@ -560,7 +560,11 @@ private: template <class M, class V> static V& mapAddOrUpdate(M& map, const Zstring& key, V&& value) { +#if defined ZEN_LINUX && !defined __cpp_lib_map_try_emplace auto rv = map.emplace(key, value); //C++11 emplace will move r-value arguments => don't use std::forward! +#else //C++17's map::try_emplace() is faster than map::emplace() if key is already existing + auto rv = map.try_emplace(key, std::forward<V>(value)); //and does NOT MOVE r-value arguments in this case! +#endif if (rv.second) return rv.first->second; diff --git a/FreeFileSync/Source/lib/db_file.h b/FreeFileSync/Source/base/db_file.h index df61d892..17409c2b 100755 --- a/FreeFileSync/Source/lib/db_file.h +++ b/FreeFileSync/Source/base/db_file.h @@ -8,7 +8,7 @@ #define DB_FILE_H_834275398588021574 #include <zen/file_error.h> -#include "../file_hierarchy.h" +#include "file_hierarchy.h" namespace fff diff --git a/FreeFileSync/Source/lib/dir_exist_async.h b/FreeFileSync/Source/base/dir_exist_async.h index 4daee747..cc5aa479 100755 --- a/FreeFileSync/Source/lib/dir_exist_async.h +++ b/FreeFileSync/Source/base/dir_exist_async.h @@ -7,12 +7,11 @@ #ifndef DIR_EXIST_ASYNC_H_0817328167343215806734213 #define DIR_EXIST_ASYNC_H_0817328167343215806734213 -#include <list> #include <zen/thread.h> #include <zen/file_error.h> #include <zen/basic_math.h> +#include "process_callback.h" #include "../fs/abstract.h" -#include "../process_callback.h" namespace fff @@ -32,7 +31,7 @@ struct FolderStatus FolderStatus getFolderStatusNonBlocking(const std::set<AbstractPath>& folderPaths, const std::map<AbstractPath, size_t>& deviceParallelOps, int folderAccessTimeout, bool allowUserInteraction, - ProcessCallback& procCallback) + ProcessCallback& procCallback) //throw X { using namespace zen; @@ -42,21 +41,20 @@ FolderStatus getFolderStatusNonBlocking(const std::set<AbstractPath>& folderPath for (const AbstractPath& folderPath : folderPaths) if (!AFS::isNullPath(folderPath)) //skip empty dirs perDevicePaths[AFS::getPathComponents(folderPath).rootPath].insert(folderPath); - warn_static("relax for native paths? consider hanging network share!?") - std::list<std::pair<AbstractPath, std::future<bool>>> futureInfo; + std::vector<std::pair<AbstractPath, std::future<bool>>> futureInfo; - std::list<ThreadGroup<std::packaged_task<bool()>>> perDeviceThreads; + std::vector<ThreadGroup<std::packaged_task<bool()>>> perDeviceThreads; for (const auto& item : perDevicePaths) { const AbstractPath& rootPath = item.first; auto itParOps = deviceParallelOps.find(rootPath); const size_t parallelOps = std::max<size_t>(itParOps != deviceParallelOps.end() ? itParOps->second : 1, 1); - const size_t threadCount = std::min(parallelOps, item.second.size()); - perDeviceThreads.emplace_back(threadCount, "DirExist Device: " + utfTo<std::string>(AFS::getDisplayPath(rootPath))); + perDeviceThreads.emplace_back(parallelOps, "DirExist: " + utfTo<std::string>(AFS::getDisplayPath(rootPath))); auto& threadGroup = perDeviceThreads.back(); + threadGroup.detach(); //don't wait on threads hanging longer than "folderAccessTimeout" for (const AbstractPath& folderPath : item.second) { @@ -67,7 +65,7 @@ FolderStatus getFolderStatusNonBlocking(const std::set<AbstractPath>& folderPath //2. check dir existence return static_cast<bool>(AFS::getItemTypeIfExists(folderPath)); //throw FileError - //TODO: consider ItemType:FILE a failure instead? In any case: return "false" IFF nothing (of any type) exists + //TODO: consider ItemType::FILE a failure instead? In any case: return "false" IFF nothing (of any type) exists }); auto fut = pt.get_future(); threadGroup.run(std::move(pt)); @@ -85,11 +83,11 @@ FolderStatus getFolderStatusNonBlocking(const std::set<AbstractPath>& folderPath { const std::wstring& displayPathFmt = fmtPath(AFS::getDisplayPath(fi.first)); - procCallback.reportStatus(replaceCpy(_("Searching for folder %x..."), L"%x", displayPathFmt)); //may throw! + procCallback.reportStatus(replaceCpy(_("Searching for folder %x..."), L"%x", displayPathFmt)); //throw X while (numeric::dist(std::chrono::steady_clock::now(), startTime) < std::chrono::seconds(folderAccessTimeout) && //handle potential chrono wrap-around! fi.second.wait_for(UI_UPDATE_INTERVAL / 2) != std::future_status::ready) - procCallback.requestUiRefresh(); //may throw! + procCallback.requestUiRefresh(); //throw X if (isReady(fi.second)) { diff --git a/FreeFileSync/Source/lib/dir_lock.cpp b/FreeFileSync/Source/base/dir_lock.cpp index 68ce72f4..b2e30e40 100755 --- a/FreeFileSync/Source/lib/dir_lock.cpp +++ b/FreeFileSync/Source/base/dir_lock.cpp @@ -13,8 +13,8 @@ #include <zen/file_access.h> #include <zen/file_io.h> #include <zen/optional.h> -#include <wx/log.h> -#include <wx/app.h> +//#include <wx/log.h> +//#include <wx/app.h> #include <fcntl.h> //open() #include <sys/stat.h> // @@ -36,7 +36,6 @@ const char LOCK_FORMAT_DESCR[] = "FreeFileSync"; const int LOCK_FORMAT_VER = 2; //lock file format version - //worker thread class LifeSigns { @@ -49,26 +48,17 @@ public: { const Opt<Zstring> parentDirPath = getParentFolderPath(lockFilePath_); setCurrentThreadName(("DirLock: " + (parentDirPath ? utfTo<std::string>(*parentDirPath) : "")).c_str()); - warn_static("set nice name for setCurrentThreadName in other places, too") - try - { - for (;;) - { - interruptibleSleep(EMIT_LIFE_SIGN_INTERVAL); //throw ThreadInterruption - //actual work - emitLifeSign(); //throw () - } - } - catch (const std::exception& e) //exceptions must be catched per thread + for (;;) { - const auto title = copyStringTo<std::wstring>(wxTheApp->GetAppDisplayName()) + SPACED_DASH + _("An exception occurred"); - wxSafeShowMessage(title, utfTo<wxString>(e.what()) + L" (Dirlock)"); //simple wxMessageBox won't do for threads + interruptibleSleep(EMIT_LIFE_SIGN_INTERVAL); //throw ThreadInterruption + emitLifeSign(); //noexcept } } private: - void emitLifeSign() const //try to append one byte..., throw() + //try to append one byte... + void emitLifeSign() const //noexcept { const int fileHandle = ::open(lockFilePath_.c_str(), O_WRONLY | O_APPEND); if (fileHandle == -1) @@ -195,8 +185,8 @@ void serialize(const LockInformation& lockInfo, MemoryStreamOut<ByteArray>& stre writeArray(stream, LOCK_FORMAT_DESCR, sizeof(LOCK_FORMAT_DESCR)); writeNumber<int32_t>(stream, LOCK_FORMAT_VER); - static_assert(sizeof(lockInfo.processId) <= sizeof(uint64_t), ""); //ensure cross-platform compatibility! - static_assert(sizeof(lockInfo.sessionId) <= sizeof(uint64_t), ""); // + static_assert(sizeof(lockInfo.processId) <= sizeof(uint64_t)); //ensure cross-platform compatibility! + static_assert(sizeof(lockInfo.sessionId) <= sizeof(uint64_t)); // writeContainer(stream, lockInfo.lockId); writeContainer(stream, lockInfo.computerName); @@ -327,7 +317,7 @@ void waitOnDirLock(const Zstring& lockFilePath, const DirLockCallback& notifySta //one signal missed: it's likely this is an abandoned lock => show countdown if (lastCheckTime >= lastLifeSign + EMIT_LIFE_SIGN_INTERVAL + std::chrono::seconds(1)) { - const int remainingSeconds = std::max<int>(0, std::chrono::duration_cast<std::chrono::seconds>(DETECT_ABANDONED_INTERVAL - (now - lastLifeSign)).count()); + const int remainingSeconds = std::max(0, static_cast<int>(std::chrono::duration_cast<std::chrono::seconds>(DETECT_ABANDONED_INTERVAL - (now - lastLifeSign)).count())); notifyStatus(infoMsg + L" | " + _("Detecting abandoned lock...") + L' ' + _P("1 sec", "%x sec", remainingSeconds)); //throw X } else @@ -431,7 +421,7 @@ public: //create or retrieve a SharedDirLock std::shared_ptr<SharedDirLock> retrieve(const Zstring& lockFilePath, const DirLockCallback& notifyStatus, std::chrono::milliseconds cbInterval) //throw FileError { - assert(std::this_thread::get_id() == mainThreadId); //function is not thread-safe! + assert(runningMainThread()); //function is not thread-safe! tidyUp(); diff --git a/FreeFileSync/Source/lib/dir_lock.h b/FreeFileSync/Source/base/dir_lock.h index 910e551d..910e551d 100755 --- a/FreeFileSync/Source/lib/dir_lock.h +++ b/FreeFileSync/Source/base/dir_lock.h diff --git a/FreeFileSync/Source/lib/error_log.h b/FreeFileSync/Source/base/error_log.h index 022bf836..022bf836 100755 --- a/FreeFileSync/Source/lib/error_log.h +++ b/FreeFileSync/Source/base/error_log.h diff --git a/FreeFileSync/Source/lib/ffs_paths.cpp b/FreeFileSync/Source/base/ffs_paths.cpp index a9c43d36..a9c43d36 100755 --- a/FreeFileSync/Source/lib/ffs_paths.cpp +++ b/FreeFileSync/Source/base/ffs_paths.cpp diff --git a/FreeFileSync/Source/lib/ffs_paths.h b/FreeFileSync/Source/base/ffs_paths.h index 3cb4c07b..3cb4c07b 100755 --- a/FreeFileSync/Source/lib/ffs_paths.h +++ b/FreeFileSync/Source/base/ffs_paths.h diff --git a/FreeFileSync/Source/file_hierarchy.cpp b/FreeFileSync/Source/base/file_hierarchy.cpp index fd0e8beb..a10646be 100755 --- a/FreeFileSync/Source/file_hierarchy.cpp +++ b/FreeFileSync/Source/base/file_hierarchy.cpp @@ -13,8 +13,6 @@ using namespace zen; using namespace fff; - - std::wstring fff::getShortDisplayNameForFolderPair(const AbstractPath& itemPathL, const AbstractPath& itemPathR) { Zstring commonTrail; @@ -194,7 +192,7 @@ SyncOperation FileSystemObject::testSyncOperation(SyncDirection testSyncDir) con SyncOperation FileSystemObject::getSyncOperation() const { - return getIsolatedSyncOperation(!isEmpty<LEFT_SIDE>(), !isEmpty<RIGHT_SIDE>(), getCategory(), selectedForSync_, getSyncDir(), syncDirectionConflict_.get() != nullptr); + return getIsolatedSyncOperation(!isEmpty<LEFT_SIDE>(), !isEmpty<RIGHT_SIDE>(), 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!!! } diff --git a/FreeFileSync/Source/file_hierarchy.h b/FreeFileSync/Source/base/file_hierarchy.h index 482cf50a..f0a61f99 100755 --- a/FreeFileSync/Source/file_hierarchy.h +++ b/FreeFileSync/Source/base/file_hierarchy.h @@ -8,18 +8,18 @@ #define FILE_HIERARCHY_H_257235289645296 #include <map> -#include <cstddef> //required by GCC 4.8.1 to find ptrdiff_t +//#include <cstddef> //required by GCC 4.9 to find ptrdiff_t #include <string> #include <memory> +#include <list> #include <functional> #include <unordered_set> #include <zen/zstring.h> -#include <zen/fixed_list.h> #include <zen/stl_tools.h> #include <zen/file_id_def.h> #include "structures.h" -#include "lib/hard_filter.h" -#include "fs/abstract.h" +#include "hard_filter.h" +#include "../fs/abstract.h" namespace fff @@ -36,7 +36,7 @@ struct FileAttributes fileId(idIn), isFollowedSymlink(isSymlink) { - static_assert(std::is_signed<time_t>::value, "... and signed!"); + static_assert(std::is_signed_v<time_t>, "... and signed!"); } time_t modTime = 0; //number of seconds since Jan. 1st 1970 UTC @@ -75,10 +75,10 @@ template <SelectedSide side> struct OtherSide; template <> -struct OtherSide<LEFT_SIDE> { static const SelectedSide result = RIGHT_SIDE; }; +struct OtherSide<LEFT_SIDE> { static const SelectedSide value = RIGHT_SIDE; }; template <> -struct OtherSide<RIGHT_SIDE> { static const SelectedSide result = LEFT_SIDE; }; +struct OtherSide<RIGHT_SIDE> { static const SelectedSide value = LEFT_SIDE; }; template <SelectedSide side> @@ -201,9 +201,9 @@ class ContainerObject : public virtual PathInformation friend class FileSystemObject; public: - using FileList = zen::FixedList<FilePair>; //MergeSides::execute() requires a structure that doesn't invalidate pointers after push_back() - using SymlinkList = zen::FixedList<SymlinkPair>; // - using FolderList = zen::FixedList<FolderPair>; + using FileList = std::list<FilePair>; //MergeSides::execute() requires a structure that doesn't invalidate pointers after push_back() + using SymlinkList = std::list<SymlinkPair>; // + using FolderList = std::list<FolderPair>; FolderPair& addSubFolder(const Zstring& itemNameL, const FolderAttributes& left, //file exists on both sides @@ -332,10 +332,16 @@ private: //get rid of shared_ptr indirection template <class IterImpl, //underlying iterator type - class V> //target value type -class DerefIter : public std::iterator<std::bidirectional_iterator_tag, V> + class T> //target value type +class DerefIter { public: + using iterator_category = std::bidirectional_iterator_tag; + using value_type = T; + using difference_type = std::ptrdiff_t; + using pointer = T*; + using reference = T&; + DerefIter() {} DerefIter(IterImpl it) : it_(it) {} DerefIter(const DerefIter& other) : it_(other.it_) {} @@ -346,24 +352,12 @@ public: inline friend ptrdiff_t operator-(const DerefIter& lhs, const DerefIter& rhs) { return lhs.it_ - rhs.it_; } inline friend bool operator==(const DerefIter& lhs, const DerefIter& rhs) { return lhs.it_ == rhs.it_; } inline friend bool operator!=(const DerefIter& lhs, const DerefIter& rhs) { return !(lhs == rhs); } - V& operator* () const { return **it_; } - V* operator->() const { return &** it_; } + T& operator* () const { return **it_; } + T* operator->() const { return &** it_; } private: IterImpl it_{}; }; -/* -C++17: specialize std::iterator_traits instead of inherting from std::iterator -namespace std -{ -template <class IterImpl, class V> -struct iterator_traits<zen::DerefIter<IterImpl, V>> -{ - using iterator_category = std::bidirectional_iterator_tag; - using value_type = V; -}; -} -*/ using FolderComparison = std::vector<std::shared_ptr<BaseFolderPair>>; //make sure pointers to sub-elements remain valid //don't change this back to std::vector<BaseFolderPair> too easily: comparison uses push_back to add entries which may result in a full copy! @@ -383,8 +377,6 @@ struct FSObjectVisitor }; - - //inherit from this class to allow safe random access by id instead of unsafe raw pointer //allow for similar semantics like std::weak_ptr without having to use std::shared_ptr template <class T> @@ -415,7 +407,7 @@ private: static std::unordered_set<const ObjectMgr*>& activeObjects() { //our global ObjectMgr is not thread-safe (and currently does not need to be!) - //assert(std::this_thread::get_id() == mainThreadId); -> still, may be accessed by synchronization worker thread, one-at-a-time + //assert(runningMainThread()); -> still, may be accessed by synchronization worker threads, one thread at a time static std::unordered_set<const ObjectMgr*> inst; return inst; //external linkage (even in header file!) @@ -462,8 +454,8 @@ public: //for use during init in "CompareProcess" only: template <CompareFilesResult res> void setCategory(); - void setCategoryConflict (const std::wstring& description); - void setCategoryDiffMetadata(const std::wstring& description); + void setCategoryConflict (const Zstringw& description); + void setCategoryDiffMetadata(const Zstringw& description); protected: FileSystemObject(const Zstring& itemNameL, @@ -501,14 +493,14 @@ private: void propagateChangedItemName(const Zstring& itemNameOld); //required after any itemName changes //categorization - std::unique_ptr<std::wstring> cmpResultDescr_; //only filled if getCategory() == FILE_CONFLICT or FILE_DIFFERENT_METADATA + Zstringw cmpResultDescr_; //only filled if getCategory() == FILE_CONFLICT or FILE_DIFFERENT_METADATA CompareFilesResult cmpResult_; //although this uses 4 bytes there is currently *no* space wasted in class layout! bool selectedForSync_ = true; //Note: we model *four* states with following two variables => "syncDirectionConflict is empty or syncDir == NONE" is a class invariant!!! SyncDirection syncDir_ = SyncDirection::NONE; //1 byte: optimize memory layout! - std::unique_ptr<std::wstring> syncDirectionConflict_; //non-empty if we have a conflict setting sync-direction + Zstringw syncDirectionConflict_; //non-empty if we have a conflict setting sync-direction //get rid of std::wstring small string optimization (consumes 32/48 byte on VS2010 x86/x64!) Zstring itemNameL_; //slightly redundant under Linux, but on Windows the "same" file paths can differ in case @@ -739,9 +731,7 @@ inline std::wstring FileSystemObject::getCatExtraDescription() const { assert(getCategory() == FILE_CONFLICT || getCategory() == FILE_DIFFERENT_METADATA); - if (cmpResultDescr_) //avoid ternary-WTF! (implicit copy-constructor call!!!!!!) - return *cmpResultDescr_; - return std::wstring(); + return zen::copyStringTo<std::wstring>(cmpResultDescr_); } @@ -749,7 +739,7 @@ inline void FileSystemObject::setSyncDir(SyncDirection newDir) { syncDir_ = newDir; - syncDirectionConflict_.reset(); + syncDirectionConflict_.clear(); notifySyncCfgChanged(); } @@ -758,8 +748,9 @@ void FileSystemObject::setSyncDir(SyncDirection newDir) inline void FileSystemObject::setSyncDirConflict(const std::wstring& description) { + assert(!description.empty()); syncDir_ = SyncDirection::NONE; - syncDirectionConflict_ = std::make_unique<std::wstring>(description); + syncDirectionConflict_ = zen::copyStringTo<Zstringw>(description); notifySyncCfgChanged(); } @@ -769,9 +760,7 @@ inline std::wstring FileSystemObject::getSyncOpConflict() const { assert(getSyncOperation() == SO_UNRESOLVED_CONFLICT); - if (syncDirectionConflict_) //avoid ternary-WTF! (implicit copy-constructor call!!!!!!) - return *syncDirectionConflict_; - return std::wstring(); + return zen::copyStringTo<std::wstring>(syncDirectionConflict_); } @@ -805,7 +794,7 @@ Zstring FileSystemObject::getItemName() const const Zstring& itemName = SelectParam<side>::ref(itemNameL_, itemNameR_); //empty if not existing if (!itemName.empty()) //avoid ternary-WTF! (implicit copy-constructor call!!!!!!) return itemName; - return SelectParam<OtherSide<side>::result>::ref(itemNameL_, itemNameR_); //empty if not existing + return SelectParam<OtherSide<side>::value>::ref(itemNameL_, itemNameR_); //empty if not existing } @@ -871,17 +860,19 @@ template <> void FileSystemObject::setCategory<FILE_LEFT_SIDE_ONLY> () = dele template <> void FileSystemObject::setCategory<FILE_RIGHT_SIDE_ONLY> () = delete; // inline -void FileSystemObject::setCategoryConflict(const std::wstring& description) +void FileSystemObject::setCategoryConflict(const Zstringw& description) { + assert(!description.empty()); cmpResult_ = FILE_CONFLICT; - cmpResultDescr_ = std::make_unique<std::wstring>(description); + cmpResultDescr_ = zen::copyStringTo<Zstringw>(description); } inline -void FileSystemObject::setCategoryDiffMetadata(const std::wstring& description) +void FileSystemObject::setCategoryDiffMetadata(const Zstringw& description) { + assert(!description.empty()); cmpResult_ = FILE_DIFFERENT_METADATA; - cmpResultDescr_ = std::make_unique<std::wstring>(description); + cmpResultDescr_ = zen::copyStringTo<Zstringw>(description); } inline @@ -1173,7 +1164,7 @@ void FilePair::setSyncedTo(const Zstring& itemName, bool isSymlinkSrc) { //FILE_EQUAL is only allowed for same short name and file size: enforced by this method! - static const SelectedSide sideSrc = OtherSide<sideTrg>::result; + constexpr SelectedSide sideSrc = OtherSide<sideTrg>::value; SelectParam<sideTrg>::ref(attrL_, attrR_) = FileAttributes(lastWriteTimeTrg, fileSize, fileIdTrg, isSymlinkTrg); SelectParam<sideSrc>::ref(attrL_, attrR_) = FileAttributes(lastWriteTimeSrc, fileSize, fileIdSrc, isSymlinkSrc); @@ -1188,7 +1179,7 @@ void SymlinkPair::setSyncedTo(const Zstring& itemName, int64_t lastWriteTimeTrg, int64_t lastWriteTimeSrc) { - static const SelectedSide sideSrc = OtherSide<sideTrg>::result; + constexpr SelectedSide sideSrc = OtherSide<sideTrg>::value; SelectParam<sideTrg>::ref(attrL_, attrR_) = LinkAttributes(lastWriteTimeTrg); SelectParam<sideSrc>::ref(attrL_, attrR_) = LinkAttributes(lastWriteTimeSrc); @@ -1202,7 +1193,7 @@ void FolderPair::setSyncedTo(const Zstring& itemName, bool isSymlinkTrg, bool isSymlinkSrc) { - static const SelectedSide sideSrc = OtherSide<sideTrg>::result; + constexpr SelectedSide sideSrc = OtherSide<sideTrg>::value; SelectParam<sideTrg>::ref(attrL_, attrR_) = FolderAttributes(isSymlinkTrg); SelectParam<sideSrc>::ref(attrL_, attrR_) = FolderAttributes(isSymlinkSrc); diff --git a/FreeFileSync/Source/lib/generate_logfile.cpp b/FreeFileSync/Source/base/generate_logfile.cpp index 2ee7f309..293bedb7 100755 --- a/FreeFileSync/Source/lib/generate_logfile.cpp +++ b/FreeFileSync/Source/base/generate_logfile.cpp @@ -217,8 +217,7 @@ void fff::limitLogfileCount(const AbstractPath& logFolderPath, const std::wstrin //traverse source directory one level deep auto lt = std::make_shared<LogTraverserCallback>(prefix, [&] { if (notifyStatus) notifyStatus(cleaningMsg); }); - const AFS::PathComponents pc = AFS::getPathComponents(logFolderPath); - AFS::traverseFolderParallel(pc.rootPath, {{ pc.relPath, lt }}, 1 /*parallelOps*/); //throw FileError + AFS::traverseFolderParallel(logFolderPath, {{ {}, lt }}, 1 /*parallelOps*/); //throw FileError std::vector<Zstring> logFileNames = lt->refFileNames(); Opt<FileError> lastError = lt->getLastError(); diff --git a/FreeFileSync/Source/lib/generate_logfile.h b/FreeFileSync/Source/base/generate_logfile.h index ecd1db1f..40be8d4a 100755 --- a/FreeFileSync/Source/lib/generate_logfile.h +++ b/FreeFileSync/Source/base/generate_logfile.h @@ -9,7 +9,7 @@ #include <zen/error_log.h> #include "ffs_paths.h" -#include "../file_hierarchy.h" +#include "file_hierarchy.h" namespace fff diff --git a/FreeFileSync/Source/lib/hard_filter.cpp b/FreeFileSync/Source/base/hard_filter.cpp index 3008aa24..d4085f84 100755 --- a/FreeFileSync/Source/lib/hard_filter.cpp +++ b/FreeFileSync/Source/base/hard_filter.cpp @@ -30,7 +30,7 @@ namespace //constructing Zstrings of these in addFilterEntry becomes perf issue for large filter lists => use global POD! const Zchar sepAsterisk[] = Zstr("/*"); const Zchar asteriskSep[] = Zstr("*/"); -static_assert(FILE_NAME_SEPARATOR == '/', ""); +static_assert(FILE_NAME_SEPARATOR == '/'); void addFilterEntry(const Zstring& filterPhrase, std::vector<Zstring>& masksFileFolder, std::vector<Zstring>& masksFolder) diff --git a/FreeFileSync/Source/lib/hard_filter.h b/FreeFileSync/Source/base/hard_filter.h index f829761e..f829761e 100755 --- a/FreeFileSync/Source/lib/hard_filter.h +++ b/FreeFileSync/Source/base/hard_filter.h diff --git a/FreeFileSync/Source/lib/help_provider.h b/FreeFileSync/Source/base/help_provider.h index 1ac0a278..8cee813b 100755 --- a/FreeFileSync/Source/lib/help_provider.h +++ b/FreeFileSync/Source/base/help_provider.h @@ -10,7 +10,7 @@ #if 1 namespace fff { -inline void displayHelpEntry(const wxString& topic, wxWindow* parent) { wxLaunchDefaultBrowser(L"https://www.freefilesync.org/manual.php?topic=" + topic); } +inline void displayHelpEntry(const wxString& topic, wxWindow* parent) { wxLaunchDefaultBrowser(L"https://freefilesync.org/manual.php?topic=" + topic); } inline void uninitializeHelp() {} } @@ -42,7 +42,7 @@ inline void displayHelpEntry(const wxString& topic, wxWindow* parent) { if (internetIsAlive()) //noexcept - wxLaunchDefaultBrowser(L"https://www.freefilesync.org/manual.php?topic=" + topic); + wxLaunchDefaultBrowser(L"https://freefilesync.org/manual.php?topic=" + topic); else -> what if FFS is blocked, but the web browser would have internet access?? { diff --git a/FreeFileSync/Source/lib/icon_buffer.cpp b/FreeFileSync/Source/base/icon_buffer.cpp index 15bd6f93..6c030bb5 100755 --- a/FreeFileSync/Source/lib/icon_buffer.cpp +++ b/FreeFileSync/Source/base/icon_buffer.cpp @@ -24,11 +24,10 @@ namespace const size_t BUFFER_SIZE_MAX = 800; //maximum number of icons to hold in buffer: must be big enough to hold visible icons + preload buffer! Consider OS limit on GDI resources (wxBitmap)!!! - //destroys raw icon! Call from GUI thread only! wxBitmap extractWxBitmap(ImageHolder&& ih) { - assert(std::this_thread::get_id() == mainThreadId); + assert(runningMainThread()); if (!ih.getRgb()) return wxNullBitmap; @@ -81,7 +80,7 @@ public: //context of main thread void set(const std::vector<AbstractPath>& newLoad) { - assert(std::this_thread::get_id() == mainThreadId); + assert(runningMainThread()); { std::lock_guard<std::mutex> dummy(lockFiles_); @@ -90,12 +89,12 @@ public: workLoad_.emplace_back(filePath); } conditionNewWork_.notify_all(); //instead of notify_one(); workaround bug: https://svn.boost.org/trac/boost/ticket/7796 - //condition handling, see: http://www.boost.org/doc/libs/1_43_0/doc/html/thread/synchronization.html#thread.synchronization.condvar_ref + //condition handling, see: https://www.boost.org/doc/libs/1_43_0/doc/html/thread/synchronization.html#thread.synchronization.condvar_ref } void add(const AbstractPath& filePath) //context of main thread { - assert(std::this_thread::get_id() == mainThreadId); + assert(runningMainThread()); { std::lock_guard<std::mutex> dummy(lockFiles_); workLoad_.emplace_back(filePath); //set as next item to retrieve @@ -106,7 +105,7 @@ public: //context of worker thread, blocking: AbstractPath extractNext() //throw ThreadInterruption { - assert(std::this_thread::get_id() != mainThreadId); + assert(!runningMainThread()); std::unique_lock<std::mutex> dummy(lockFiles_); interruptibleWait(conditionNewWork_, dummy, [this] { return !workLoad_.empty(); }); //throw ThreadInterruption @@ -137,7 +136,7 @@ public: //must be called by main thread only! => wxBitmap is NOT thread-safe like an int (non-atomic ref-count!!!) Opt<wxBitmap> retrieve(const AbstractPath& filePath) { - assert(std::this_thread::get_id() == mainThreadId); + assert(runningMainThread()); std::lock_guard<std::mutex> dummy(lockIconList_); auto it = iconList.find(filePath); @@ -161,7 +160,7 @@ public: std::lock_guard<std::mutex> dummy(lockIconList_); //thread safety: moving ImageHolder is free from side effects, but ~wxBitmap() is NOT! => do NOT delete items from iconList here! - auto rc = iconList.emplace(filePath, makeValueObject()); + auto rc = iconList.emplace(filePath, IconData()); assert(rc.second); //insertion took place if (rc.second) { @@ -174,7 +173,7 @@ public: //call at an appropriate time, e.g. after Workload::set() void limitSize() { - assert(std::this_thread::get_id() == mainThreadId); + assert(runningMainThread()); std::lock_guard<std::mutex> dummy(lockIconList_); while (iconList.size() > BUFFER_SIZE_MAX) @@ -187,16 +186,8 @@ public: private: struct IconData; - -#ifdef __clang__ //workaround libc++ limitation for incomplete types: http://llvm.org/bugs/show_bug.cgi?id=17701 - using FileIconMap = std::map<AbstractPath, std::unique_ptr<IconData>>; - static IconData& refData(FileIconMap::iterator it) { return *(it->second); } - static std::unique_ptr<IconData> makeValueObject() { return std::make_unique<IconData>(); } -#else using FileIconMap = std::map<AbstractPath, IconData>; IconData& refData(FileIconMap::iterator it) { return it->second; } - static IconData makeValueObject() { return IconData(); } -#endif //call while holding lock: void priorityListPopFront() @@ -290,7 +281,7 @@ struct IconBuffer::Impl Buffer buffer; // InterruptibleThread worker; - + //------------------------- //------------------------- std::map<Zstring, wxBitmap, LessFilePath> extensionIcons; //no item count limit!? Test case C:\ ~ 3800 unique file extensions }; @@ -301,14 +292,15 @@ IconBuffer::IconBuffer(IconSize sz) : pimpl_(std::make_unique<Impl>()), iconSize pimpl_->worker = InterruptibleThread([&workload = pimpl_->workload, &buffer = pimpl_->buffer, sz] { setCurrentThreadName("Icon Buffer"); - for (;;) - { - //start work: blocks until next icon to load is retrieved: - const AbstractPath itemPath = workload.extractNext(); //throw ThreadInterruption - if (!buffer.hasIcon(itemPath)) //perf: workload may contain duplicate entries? - buffer.insert(itemPath, getDisplayIcon(itemPath, sz)); - } + for (;;) + { + //start work: blocks until next icon to load is retrieved: + const AbstractPath itemPath = workload.extractNext(); //throw ThreadInterruption + + if (!buffer.hasIcon(itemPath)) //perf: workload may contain duplicate entries? + buffer.insert(itemPath, getDisplayIcon(itemPath, sz)); + } }); } @@ -361,7 +353,7 @@ void IconBuffer::setWorkload(const std::vector<AbstractPath>& load) assert(load.size() < BUFFER_SIZE_MAX / 2); pimpl_->workload.set(load); //since buffer can only increase due to new workload, - pimpl_->buffer.limitSize(); //this is the place to impose the limit from main thread! + pimpl_->buffer.limitSize(); //this is the place to impose the limit from main thread! } @@ -369,7 +361,7 @@ wxBitmap IconBuffer::getIconByExtension(const Zstring& filePath) { const Zstring& ext = getFileExtension(filePath); - assert(std::this_thread::get_id() == mainThreadId); + assert(runningMainThread()); auto it = pimpl_->extensionIcons.find(ext); if (it == pimpl_->extensionIcons.end()) @@ -377,7 +369,8 @@ wxBitmap IconBuffer::getIconByExtension(const Zstring& filePath) const Zstring& templateName(ext.empty() ? Zstr("file") : Zstr("file.") + ext); //don't pass actual file name to getIconByTemplatePath(), e.g. "AUTHORS" has own mime type on Linux!!! //=> we want to buffer by extension only to minimize buffer-misses! - it = pimpl_->extensionIcons.emplace(ext, extractWxBitmap(getIconByTemplatePath(templateName, IconBuffer::getSize(iconSizeType_)))).first; + + it = pimpl_->extensionIcons.emplace(ext, extractWxBitmap(getIconByTemplatePath(templateName, getSize(iconSizeType_)))).first; } //need buffer size limit??? return it->second; diff --git a/FreeFileSync/Source/lib/icon_buffer.h b/FreeFileSync/Source/base/icon_buffer.h index e5ff932b..f0ad78e4 100755 --- a/FreeFileSync/Source/lib/icon_buffer.h +++ b/FreeFileSync/Source/base/icon_buffer.h @@ -33,11 +33,11 @@ public: static int getSize(IconSize sz); //expected and *maximum* icon size in pixel int getSize() const { return getSize(iconSizeType_); } // - bool readyForRetrieval(const AbstractPath& filePath); + void setWorkload (const std::vector<AbstractPath>& load); //(re-)set new workload of icons to be retrieved; + bool readyForRetrieval(const AbstractPath& filePath); zen::Opt<wxBitmap> retrieveFileIcon (const AbstractPath& filePath); //... and mark as hot - void setWorkload (const std::vector<AbstractPath>& load); //(re-)set new workload of icons to be retrieved; - wxBitmap getIconByExtension(const Zstring& filePath); //...and add to buffer + //retrieveFileIcon() + getIconByExtension() are safe to call from within WM_PAINT handler! no COM calls (...on calling thread) static wxBitmap genericFileIcon(IconSize sz); static wxBitmap genericDirIcon (IconSize sz); diff --git a/FreeFileSync/Source/lib/icon_loader.cpp b/FreeFileSync/Source/base/icon_loader.cpp index 71b783a2..71b783a2 100755 --- a/FreeFileSync/Source/lib/icon_loader.cpp +++ b/FreeFileSync/Source/base/icon_loader.cpp diff --git a/FreeFileSync/Source/lib/icon_loader.h b/FreeFileSync/Source/base/icon_loader.h index 7f14ff54..8f6b31b9 100755 --- a/FreeFileSync/Source/lib/icon_loader.h +++ b/FreeFileSync/Source/base/icon_loader.h @@ -15,12 +15,13 @@ namespace fff { //=> all functions are safe to call from multiple threads! //!!!Note: init COM + system image list before loading icons!!! +//=> 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/ //return null icon on failure: zen::ImageHolder getIconByTemplatePath(const Zstring& templatePath, int pixelSize); zen::ImageHolder genericFileIcon(int pixelSize); -zen::ImageHolder genericDirIcon(int pixelSize); -zen::ImageHolder getFileIcon(const Zstring& filePath, int pixelSize); +zen::ImageHolder genericDirIcon (int pixelSize); +zen::ImageHolder getFileIcon (const Zstring& filePath, int pixelSize); zen::ImageHolder getThumbnailImage(const Zstring& filePath, int pixelSize); } diff --git a/FreeFileSync/Source/lib/localization.cpp b/FreeFileSync/Source/base/localization.cpp index bc27e6ea..bc27e6ea 100755 --- a/FreeFileSync/Source/lib/localization.cpp +++ b/FreeFileSync/Source/base/localization.cpp diff --git a/FreeFileSync/Source/lib/localization.h b/FreeFileSync/Source/base/localization.h index bf2f6b60..bf2f6b60 100755 --- a/FreeFileSync/Source/lib/localization.h +++ b/FreeFileSync/Source/base/localization.h diff --git a/FreeFileSync/Source/lib/lock_holder.h b/FreeFileSync/Source/base/lock_holder.h index 128f4b8e..cfe6a4fb 100755 --- a/FreeFileSync/Source/lib/lock_holder.h +++ b/FreeFileSync/Source/base/lock_holder.h @@ -46,7 +46,7 @@ public: msg += L"\n" + replaceCpy(fl.second.toString(), L"\n\n", L"\n"); } - pcb.reportWarning(msg, warnDirectoryLockFailed); //may throw! + pcb.reportWarning(msg, warnDirectoryLockFailed); //throw X } } diff --git a/FreeFileSync/Source/lib/norm_filter.h b/FreeFileSync/Source/base/norm_filter.h index c2606ffc..c2606ffc 100755 --- a/FreeFileSync/Source/lib/norm_filter.h +++ b/FreeFileSync/Source/base/norm_filter.h diff --git a/FreeFileSync/Source/lib/parallel_scan.cpp b/FreeFileSync/Source/base/parallel_scan.cpp index 954b6b07..7f356ee7 100755 --- a/FreeFileSync/Source/lib/parallel_scan.cpp +++ b/FreeFileSync/Source/base/parallel_scan.cpp @@ -10,7 +10,6 @@ #include <zen/basic_math.h> #include <zen/thread.h> #include <zen/scope_guard.h> -#include <zen/fixed_list.h> #include "db_file.h" #include "lock_holder.h" @@ -20,7 +19,6 @@ using namespace fff; namespace { - /* #ifdef ZEN_WIN @@ -90,7 +88,7 @@ DiskInfo retrieveDiskInfo(const Zstring& itemPath) return output; ZEN_ON_SCOPE_EXIT(::CloseHandle(hVolume)); - std::vector<char> buffer(sizeof(VOLUME_DISK_EXTENTS) + sizeof(DISK_EXTENT)); //reserve buffer for at most one disk! call below will then fail if volume spans multiple disks! + std::vector<std::byte> buffer(sizeof(VOLUME_DISK_EXTENTS) + sizeof(DISK_EXTENT)); //reserve buffer for at most one disk! call below will then fail if volume spans multiple disks! DWORD bytesReturned = 0; if (!::DeviceIoControl(hVolume, //_In_ HANDLE hDevice, @@ -163,7 +161,7 @@ public: //blocking call: context of worker thread FillBufferCallback::HandleError reportError(const std::wstring& msg, size_t retryNumber) //throw ThreadInterruption { - assert(std::this_thread::get_id() != mainThreadId); + assert(!runningMainThread()); std::unique_lock<std::mutex> dummy(lockRequest_); interruptibleWait(conditionReadyForNewRequest_, dummy, [this] { return !errorRequest_ && !errorResponse_; }); //throw ThreadInterruption @@ -186,7 +184,7 @@ public: //context of main thread void waitUntilDone(std::chrono::milliseconds duration, FillBufferCallback& callback) //throw X { - assert(std::this_thread::get_id() == mainThreadId); + assert(runningMainThread()); for (;;) { const std::chrono::steady_clock::time_point callbackTime = std::chrono::steady_clock::now() + duration; @@ -235,7 +233,7 @@ public: void reportCurrentFile(const std::wstring& filePath) //context of worker thread { - assert(std::this_thread::get_id() != mainThreadId); + assert(!runningMainThread()); std::lock_guard<std::mutex> dummy(lockCurrentStatus_); currentFile_ = filePath; } @@ -275,7 +273,7 @@ public: private: std::wstring getCurrentStatus() //context of main thread, call repreatedly { - assert(std::this_thread::get_id() == mainThreadId); + assert(runningMainThread()); size_t parallelOpsTotal = 0; std::wstring filePath; @@ -287,12 +285,10 @@ private: filePath = currentFile_; } - - std::wstring output = textScanning_; if (parallelOpsTotal >= 2) - output += L"[" + _P("1 thread", "%x threads", parallelOpsTotal) + L"] "; - output += filePath; - return output; + return textScanning_ + L"[" + _P("1 thread", "%x threads", parallelOpsTotal) + L"] " + filePath; + else + return textScanning_ + filePath; } //---- main <-> worker communication channel ---- @@ -302,10 +298,11 @@ private: std::condition_variable conditionHaveResponse_; Opt<std::pair<std::wstring, size_t>> errorRequest_; //error message + retry number Opt<FillBufferCallback::HandleError> errorResponse_; - size_t threadsToFinish_; //!= activeThreadIdxs_.size(), which may be 0 during worker thread construction! + size_t threadsToFinish_; //can't use activeThreadIdxs_.size() which is locked by different mutex! + //also note: activeThreadIdxs_.size() may be 0 during worker thread construction! //---- status updates ---- - std::mutex lockCurrentStatus_; //use a different lock for current file: continue traversing while some thread may process an error + std::mutex lockCurrentStatus_; //different lock for status updates so that we're not blocked by other threads reporting errors std::wstring currentFile_; std::map<int /*threadIdx*/, size_t /*parallelOps*/> activeThreadIdxs_; @@ -350,10 +347,12 @@ public: virtual std::shared_ptr<TraverserCallback> onFolder (const FolderInfo& fi) override; //throw ThreadInterruption virtual HandleLink onSymlink(const SymlinkInfo& li) override; // - HandleError reportDirError (const std::wstring& msg, size_t retryNumber) override; //throw ThreadInterruption - HandleError reportItemError(const std::wstring& msg, size_t retryNumber, const Zstring& itemName) override; // + HandleError reportDirError (const std::wstring& msg, size_t retryNumber) override { return reportError(msg, retryNumber, Zstring()); } //throw ThreadInterruption + HandleError reportItemError(const std::wstring& msg, size_t retryNumber, const Zstring& itemName) override { return reportError(msg, retryNumber, itemName); } // private: + HandleError reportError(const std::wstring& msg, size_t retryNumber, const Zstring& itemName /*optional*/); //throw ThreadInterruption + TraverserConfig& cfg_; const Zstring parentRelPathPf_; FolderContainer& output_; @@ -450,11 +449,15 @@ std::shared_ptr<AFS::TraverserCallback> DirCallback::onFolder(const FolderInfo& //------------------------------------------------------------------------------------ if (level_ > 100) //Win32 traverser: stack overflow approximately at level 1000 //check after FolderContainer::addSubFolder() - if (!tryReportingItemError([&] //throw ThreadInterruption - { - throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", AFS::getDisplayPath(AFS::appendRelPath(cfg_.baseFolderPath, folderRelPath))), L"Endless recursion."); - }, *this, fi.itemName)) - return nullptr; + for (size_t retryNumber = 0;; ++retryNumber) + switch (reportItemError(replaceCpy(_("Cannot read directory %x."), L"%x", AFS::getDisplayPath(AFS::appendRelPath(cfg_.baseFolderPath, folderRelPath))) + + L"\n\n" L"Endless recursion.", retryNumber, fi.itemName)) //throw ThreadInterruption + { + case AbstractFileSystem::TraverserCallback::ON_ERROR_RETRY: + break; + case AbstractFileSystem::TraverserCallback::ON_ERROR_CONTINUE: + return nullptr; + } return std::make_shared<DirCallback>(cfg_, folderRelPath + FILE_NAME_SEPARATOR, subFolder, level_ + 1); } @@ -501,28 +504,15 @@ DirCallback::HandleLink DirCallback::onSymlink(const SymlinkInfo& si) //throw Th } -DirCallback::HandleError DirCallback::reportDirError(const std::wstring& msg, size_t retryNumber) //throw ThreadInterruption -{ - switch (cfg_.acb.reportError(msg, retryNumber)) //throw ThreadInterruption - { - case FillBufferCallback::ON_ERROR_CONTINUE: - cfg_.failedDirReads[beforeLast(parentRelPathPf_, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE)] = msg; - return ON_ERROR_CONTINUE; - - case FillBufferCallback::ON_ERROR_RETRY: - return ON_ERROR_RETRY; - } - assert(false); - return ON_ERROR_CONTINUE; -} - - -DirCallback::HandleError DirCallback::reportItemError(const std::wstring& msg, size_t retryNumber, const Zstring& itemName) //throw ThreadInterruption +DirCallback::HandleError DirCallback::reportError(const std::wstring& msg, size_t retryNumber, const Zstring& itemName) //throw ThreadInterruption { switch (cfg_.acb.reportError(msg, retryNumber)) //throw ThreadInterruption { case FillBufferCallback::ON_ERROR_CONTINUE: - cfg_.failedItemReads[parentRelPathPf_ + itemName] = msg; + if (itemName.empty()) + cfg_.failedDirReads.emplace(beforeLast(parentRelPathPf_, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE), msg); + else + cfg_.failedItemReads.emplace(parentRelPathPf_ + itemName, msg); return ON_ERROR_CONTINUE; case FillBufferCallback::ON_ERROR_RETRY: @@ -554,7 +544,7 @@ void fff::fillBuffer(const std::set<DirectoryKey>& foldersToRead, //in //communication channel used by threads AsyncCallback acb(perDeviceFolders.size() /*threadsToFinish*/, cbInterval); //manage life time: enclose InterruptibleThread's!!! - FixedList<InterruptibleThread> worker; + std::vector<InterruptibleThread> worker; ZEN_ON_SCOPE_EXIT( for (InterruptibleThread& wt : worker) wt.join(); ); ZEN_ON_SCOPE_FAIL( for (InterruptibleThread& wt : worker) wt.interrupt(); ); //interrupt all first, then join @@ -564,12 +554,9 @@ void fff::fillBuffer(const std::set<DirectoryKey>& foldersToRead, //in const AbstractPath& rootPath = item.first; const int threadIdx = static_cast<int>(worker.size()); -#if !defined ZEN_LINUX || defined ZEN_LINUX_TRAVERSER_MODERN auto itParOps = deviceParallelOps.find(rootPath); const size_t parallelOps = std::max<size_t>(itParOps != deviceParallelOps.end() ? itParOps->second : 1, 1); //sanitize early for correct status display -#elif defined ZEN_LINUX && defined ZEN_LINUX_TRAVERSER_LEGACY - const size_t parallelOps = 1; -#endif + std::map<DirectoryKey, DirectoryValue*> workload; for (const DirectoryKey& key : item.second) @@ -577,7 +564,7 @@ void fff::fillBuffer(const std::set<DirectoryKey>& foldersToRead, //in worker.emplace_back([rootPath, workload, threadIdx, &acb, parallelOps]() mutable { - setCurrentThreadName(("Traverser[" + numberTo<std::string>(threadIdx) + "]").c_str()); + setCurrentThreadName(("Comp Worker[" + numberTo<std::string>(threadIdx) + "]").c_str()); acb.notifyWorkBegin(threadIdx, parallelOps); ZEN_ON_SCOPE_EXIT(acb.notifyWorkEnd(threadIdx)); diff --git a/FreeFileSync/Source/lib/parallel_scan.h b/FreeFileSync/Source/base/parallel_scan.h index 7e54428a..f3f3e597 100755 --- a/FreeFileSync/Source/lib/parallel_scan.h +++ b/FreeFileSync/Source/base/parallel_scan.h @@ -11,8 +11,8 @@ #include <set> #include <chrono> #include "hard_filter.h" -#include "../structures.h" -#include "../file_hierarchy.h" +#include "structures.h" +#include "file_hierarchy.h" namespace fff @@ -60,7 +60,7 @@ struct FillBufferCallback ON_ERROR_RETRY, ON_ERROR_CONTINUE }; - virtual HandleError reportError (const std::wstring& msg, size_t retryNumber) = 0; //may throw! + virtual HandleError reportError (const std::wstring& msg, size_t retryNumber) = 0; //throw X virtual void reportStatus(const std::wstring& msg, int itemsTotal ) = 0; // }; diff --git a/FreeFileSync/Source/lib/parse_lng.h b/FreeFileSync/Source/base/parse_lng.h index b6d486e2..b6d486e2 100755 --- a/FreeFileSync/Source/lib/parse_lng.h +++ b/FreeFileSync/Source/base/parse_lng.h diff --git a/FreeFileSync/Source/lib/parse_plural.h b/FreeFileSync/Source/base/parse_plural.h index 8a9173e3..8a9173e3 100755 --- a/FreeFileSync/Source/lib/parse_plural.h +++ b/FreeFileSync/Source/base/parse_plural.h diff --git a/FreeFileSync/Source/lib/perf_check.cpp b/FreeFileSync/Source/base/perf_check.cpp index 8b6f52d6..ae4a8cc8 100755 --- a/FreeFileSync/Source/lib/perf_check.cpp +++ b/FreeFileSync/Source/base/perf_check.cpp @@ -45,8 +45,7 @@ std::tuple<double /*timeDelta*/, int /*itemsDelta*/, double /*bytesDelta*/> Perf const int itemsDelta = itBack->second.items - itFront->second.items; const double bytesDelta = itBack->second.bytes - itFront->second.bytes; - //return { timeDelta, itemsDelta, bytesDelta }; -> requires C++17 (Linux-only issue) - return std::make_tuple(timeDelta, itemsDelta, bytesDelta); + return { timeDelta, itemsDelta, bytesDelta }; } diff --git a/FreeFileSync/Source/lib/perf_check.h b/FreeFileSync/Source/base/perf_check.h index 401d08f5..401d08f5 100755 --- a/FreeFileSync/Source/lib/perf_check.h +++ b/FreeFileSync/Source/base/perf_check.h diff --git a/FreeFileSync/Source/process_callback.h b/FreeFileSync/Source/base/process_callback.h index 0a8f487e..0a8f487e 100755 --- a/FreeFileSync/Source/process_callback.h +++ b/FreeFileSync/Source/base/process_callback.h diff --git a/FreeFileSync/Source/lib/process_xml.cpp b/FreeFileSync/Source/base/process_xml.cpp index 95cac76e..95cac76e 100755 --- a/FreeFileSync/Source/lib/process_xml.cpp +++ b/FreeFileSync/Source/base/process_xml.cpp diff --git a/FreeFileSync/Source/lib/process_xml.h b/FreeFileSync/Source/base/process_xml.h index 13709ec6..1c9a3d72 100755 --- a/FreeFileSync/Source/lib/process_xml.h +++ b/FreeFileSync/Source/base/process_xml.h @@ -10,7 +10,7 @@ #include <zen/xml_io.h> #include <wx/gdicmn.h> #include "localization.h" -#include "../structures.h" +#include "structures.h" #include "../ui/file_grid_attr.h" #include "../ui/tree_grid_attr.h" //RTS: avoid tree grid's "file_hierarchy.h" dependency! #include "../ui/cfg_grid.h" @@ -192,8 +192,8 @@ struct XmlGlobalSettings size_t lastSyncsLogFileSizeMax = 100000; //maximum size for LastSyncs.log: use a human-readable number Zstring soundFileCompareFinished; Zstring soundFileSyncFinished = Zstr("gong.wav"); - bool autoCloseProgressDialog = false; + bool autoCloseProgressDialog = false; ConfirmationDialogs confirmDlgs; WarningDialogs warnDlgs; diff --git a/FreeFileSync/Source/lib/resolve_path.cpp b/FreeFileSync/Source/base/resolve_path.cpp index 7a17422b..b40b91ce 100755 --- a/FreeFileSync/Source/lib/resolve_path.cpp +++ b/FreeFileSync/Source/base/resolve_path.cpp @@ -17,11 +17,9 @@ using namespace zen; namespace { - - Opt<Zstring> getEnvironmentVar(const Zstring& name) { - assert(std::this_thread::get_id() == mainThreadId); //getenv() is not thread-safe! + assert(runningMainThread()); //getenv() is not thread-safe! const char* buffer = ::getenv(name.c_str()); //no extended error reporting if (!buffer) @@ -43,7 +41,7 @@ Opt<Zstring> getEnvironmentVar(const Zstring& name) Zstring resolveRelativePath(const Zstring& relativePath) { - assert(std::this_thread::get_id() == mainThreadId); //GetFullPathName() is documented to NOT be thread-safe! + assert(runningMainThread()); //GetFullPathName() is documented to NOT be thread-safe! //http://linux.die.net/man/2/path_resolution if (!startsWith(relativePath, FILE_NAME_SEPARATOR)) //absolute names are exactly those starting with a '/' diff --git a/FreeFileSync/Source/lib/resolve_path.h b/FreeFileSync/Source/base/resolve_path.h index dc23d1de..dc23d1de 100755 --- a/FreeFileSync/Source/lib/resolve_path.h +++ b/FreeFileSync/Source/base/resolve_path.h diff --git a/FreeFileSync/Source/lib/return_codes.h b/FreeFileSync/Source/base/return_codes.h index 9604142c..9604142c 100755 --- a/FreeFileSync/Source/lib/return_codes.h +++ b/FreeFileSync/Source/base/return_codes.h diff --git a/FreeFileSync/Source/lib/soft_filter.h b/FreeFileSync/Source/base/soft_filter.h index f0b7eec8..d0552c2d 100755 --- a/FreeFileSync/Source/lib/soft_filter.h +++ b/FreeFileSync/Source/base/soft_filter.h @@ -9,7 +9,7 @@ #include <algorithm> #include <limits> -#include "../structures.h" +#include "structures.h" namespace fff diff --git a/FreeFileSync/Source/lib/status_handler.cpp b/FreeFileSync/Source/base/status_handler.cpp index 9e2f78db..9e2f78db 100755 --- a/FreeFileSync/Source/lib/status_handler.cpp +++ b/FreeFileSync/Source/base/status_handler.cpp diff --git a/FreeFileSync/Source/lib/status_handler.h b/FreeFileSync/Source/base/status_handler.h index c84396ec..a5cbab86 100755 --- a/FreeFileSync/Source/lib/status_handler.h +++ b/FreeFileSync/Source/base/status_handler.h @@ -7,13 +7,13 @@ #ifndef STATUS_HANDLER_H_81704805908341534 #define STATUS_HANDLER_H_81704805908341534 -#include "../process_callback.h" #include <vector> #include <chrono> #include <thread> #include <string> #include <zen/i18n.h> #include <zen/basic_math.h> +#include "process_callback.h" namespace fff @@ -71,7 +71,7 @@ public: numbersTotal_ (4) {} // //implement parts of ProcessCallback - void initNewPhase(int itemsTotal, int64_t bytesTotal, Phase phaseId) override //may throw + void initNewPhase(int itemsTotal, int64_t bytesTotal, Phase phaseId) override //(throw X) { currentPhase_ = phaseId; refNumbers(numbersTotal_, currentPhase_) = { itemsTotal, bytesTotal }; @@ -107,8 +107,7 @@ public: void reportStatus(const std::wstring& text) override final //throw X { - warn_static("std::wstring statusText_: perf issue?") - //assert(!text.empty()); -> possible: start of parallel scan + //assert(!text.empty()); -> possible, e.g. start of parallel scan statusText_ = text; //update text *before* running operations that can throw requestUiRefresh(); //throw X } diff --git a/FreeFileSync/Source/base/status_handler_impl.h b/FreeFileSync/Source/base/status_handler_impl.h new file mode 100755 index 00000000..02b190c8 --- /dev/null +++ b/FreeFileSync/Source/base/status_handler_impl.h @@ -0,0 +1,386 @@ +// ***************************************************************************** +// * 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_07682758976 +#define STATUS_HANDLER_IMPL_H_07682758976 + +#include <zen/optional.h> +#include <zen/file_error.h> +#include <zen/thread.h> +#include "process_callback.h" + + +namespace fff +{ +class AsyncCallback //actor pattern +{ +public: + AsyncCallback() {} + + //non-blocking: context of worker thread (and main thread, see reportStats()) + void updateDataProcessed(int itemsDelta, int64_t bytesDelta) //noexcept!! + { + itemsDeltaProcessed_ += itemsDelta; + bytesDeltaProcessed_ += bytesDelta; + } + void updateDataTotal(int itemsDelta, int64_t bytesDelta) //noexcept!! + { + itemsDeltaTotal_ += itemsDelta; + bytesDeltaTotal_ += bytesDelta; + } + + //context of worker thread + void reportStatus(const std::wstring& msg) //throw ThreadInterruption + { + assert(!zen::runningMainThread()); + { + std::lock_guard<std::mutex> dummy(lockCurrentStatus_); + if (ThreadStatus* ts = getThreadStatus()) //call while holding "lockCurrentStatus_" lock!! + ts->statusMsg = msg; + } + zen::interruptionPoint(); //throw ThreadInterruption + } + + //blocking call: context of worker thread + //=> indirect support for "pause": reportInfo() 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 ThreadInterruption + { + reportStatus(msg); //throw ThreadInterruption + logInfo (msg); // + } + + //blocking call: context of worker thread + void logInfo(const std::wstring& msg) //throw ThreadInterruption + { + assert(!zen::runningMainThread()); + std::unique_lock<std::mutex> dummy(lockRequest_); + zen::interruptibleWait(conditionReadyForNewRequest_, dummy, [this] { return !logInfoRequest_; }); //throw ThreadInterruption + + logInfoRequest_ = /*std::move(taskPrefix) + */ msg; + + dummy.unlock(); //optimization for condition_variable::notify_all() + conditionNewRequest.notify_all(); + } + + //blocking call: context of worker thread + ProcessCallback::Response reportError(const std::wstring& msg, size_t retryNumber) //throw ThreadInterruption + { + assert(!zen::runningMainThread()); + std::unique_lock<std::mutex> dummy(lockRequest_); + zen::interruptibleWait(conditionReadyForNewRequest_, dummy, [this] { return !errorRequest_ && !errorResponse_; }); //throw ThreadInterruption + + errorRequest_ = ErrorInfo({ /*std::move(taskPrefix) + */ msg, retryNumber }); + conditionNewRequest.notify_all(); + + zen::interruptibleWait(conditionHaveResponse_, dummy, [this] { return static_cast<bool>(errorResponse_); }); //throw ThreadInterruption + + ProcessCallback::Response rv = *errorResponse_; + + errorRequest_ = zen::NoValue(); + errorResponse_ = zen::NoValue(); + + dummy.unlock(); //optimization for condition_variable::notify_all() + conditionReadyForNewRequest_.notify_all(); //=> spurious wake-up for AsyncCallback::logInfo() + return rv; + } + + //context of main thread + void waitUntilDone(std::chrono::milliseconds duration, ProcessCallback& cb) //throw X + { + assert(zen::runningMainThread()); + for (;;) + { + const std::chrono::steady_clock::time_point callbackTime = std::chrono::steady_clock::now() + duration; + + for (std::unique_lock<std::mutex> dummy(lockRequest_) ;;) //process all errors without delay + { + const bool rv = conditionNewRequest.wait_until(dummy, callbackTime, [this] { return (errorRequest_ && !errorResponse_) || logInfoRequest_ || finishNowRequest_; }); + if (!rv) //time-out + condition not met + break; + + if (errorRequest_ && !errorResponse_) + { + assert(!finishNowRequest_); + errorResponse_ = cb.reportError(errorRequest_->msg, errorRequest_->retryNumber); //throw X + conditionHaveResponse_.notify_all(); //instead of notify_one(); workaround bug: https://svn.boost.org/trac/boost/ticket/7796 + } + if (logInfoRequest_) + { + cb.logInfo(*logInfoRequest_); + logInfoRequest_ = zen::NoValue(); + conditionReadyForNewRequest_.notify_all(); //=> spurious wake-up for AsyncCallback::reportError() + } + if (finishNowRequest_) + { + dummy.unlock(); //call member functions outside of mutex scope: + reportStats(cb); //one last call for accurate stat-reporting! + return; + } + } + + //call member functions outside of mutex scope: + cb.reportStatus(getCurrentStatus()); //throw X + reportStats(cb); + } + } + + void notifyTaskBegin(size_t prio) //noexcept + { + assert(!zen::runningMainThread()); + const uint64_t threadId = zen::getThreadId(); + std::lock_guard<std::mutex> dummy(lockCurrentStatus_); + + //const size_t taskIdx = [&]() -> size_t + //{ + // auto it = std::find(usedIndexNums_.begin(), usedIndexNums_.end(), false); + // if (it != usedIndexNums_.end()) + // { + // *it = true; + // return it - usedIndexNums_.begin(); + // } + + // usedIndexNums_.push_back(true); + // return usedIndexNums_.size() - 1; + //}(); + + if (statusByPriority_.size() < prio + 1) + statusByPriority_.resize(prio + 1); + + statusByPriority_[prio].push_back({ threadId, /*taskIdx,*/ std::wstring() }); + } + + void notifyTaskEnd() //noexcept + { + assert(!zen::runningMainThread()); + const uint64_t threadId = zen::getThreadId(); + std::lock_guard<std::mutex> dummy(lockCurrentStatus_); + + for (auto& sbp : statusByPriority_) + for (ThreadStatus& ts : sbp) + if (ts.threadId == threadId) + { + //usedIndexNums_[ts.taskIdx] = false; + std::swap(ts, sbp.back()); + sbp.pop_back(); + return; + } + assert(false); + } + + void notifyAllDone() //noexcept + { + std::lock_guard<std::mutex> dummy(lockRequest_); + assert(!finishNowRequest_); + finishNowRequest_ = true; + conditionNewRequest.notify_all(); //perf: should unlock mutex before notify!? (insignificant) + } + +private: + AsyncCallback (const AsyncCallback&) = delete; + AsyncCallback& operator=(const AsyncCallback&) = delete; + + struct ThreadStatus + { + uint64_t threadId = 0; + //size_t taskIdx = 0; //nice human-readable task id for GUI + std::wstring statusMsg; + }; + + ThreadStatus* getThreadStatus() //call while holding "lockCurrentStatus_" lock!! + { + assert(!zen::runningMainThread()); + const uint64_t threadId = zen::getThreadId(); + + for (auto& sbp : statusByPriority_) + for (ThreadStatus& ts : sbp) + if (ts.threadId == threadId) + return &ts; + assert(false); + return nullptr; + } + +#if 0 //maybe not that relevant after all!? + std::wstring getTaskPrefix() //call *outside* of "lockCurrentStatus_" lock!! + { + const size_t taskIdx = [&] + { + std::lock_guard<std::mutex> dummy(lockCurrentStatus_); + const ThreadStatus* ts = getThreadStatus(); //call while holding "lockCurrentStatus_" lock!! + return ts ? ts->taskIdx : static_cast<size_t>(-2); + }(); + return totalThreadCount_ > 1 ? L"[" + zen::numberTo<std::wstring>(taskIdx + 1) + L"] " : L""; + } +#endif + + //context of main thread + void reportStats(ProcessCallback& cb) + { + assert(zen::runningMainThread()); + + const std::pair<int, int64_t> deltaProcessed(itemsDeltaProcessed_, bytesDeltaProcessed_); + if (deltaProcessed.first != 0 || deltaProcessed.second != 0) + { + updateDataProcessed (-deltaProcessed.first, -deltaProcessed.second); //careful with these atomics: don't just set to 0 + cb.updateDataProcessed( deltaProcessed.first, deltaProcessed.second); //noexcept!! + } + const std::pair<int, int64_t> deltaTotal(itemsDeltaTotal_, bytesDeltaTotal_); + if (deltaTotal.first != 0 || deltaTotal.second != 0) + { + updateDataTotal (-deltaTotal.first, -deltaTotal.second); + cb.updateDataTotal( deltaTotal.first, deltaTotal.second); //noexcept!! + } + } + + //context of main thread, call repreatedly + std::wstring getCurrentStatus() + { + assert(zen::runningMainThread()); + + int parallelOpsTotal = 0; + std::wstring statusMsg; + { + std::lock_guard<std::mutex> dummy(lockCurrentStatus_); + + for (const auto& sbp : statusByPriority_) + parallelOpsTotal += sbp.size(); + + statusMsg = [&] + { + for (const auto& sbp : statusByPriority_) + for (const ThreadStatus& ts : sbp) + if (!ts.statusMsg.empty()) + return ts.statusMsg; + return std::wstring(); + }(); + } + if (parallelOpsTotal >= 2) + return L"[" + _P("1 thread", "%x threads", parallelOpsTotal) + L"] " + statusMsg; + else + return statusMsg; + } + + struct ErrorInfo + { + std::wstring msg; + size_t retryNumber = 0; + }; + + //---- main <-> worker communication channel ---- + std::mutex lockRequest_; + std::condition_variable conditionReadyForNewRequest_; + std::condition_variable conditionNewRequest; + std::condition_variable conditionHaveResponse_; + zen::Opt<ErrorInfo> errorRequest_; + zen::Opt<ProcessCallback::Response> errorResponse_; + zen::Opt<std::wstring> logInfoRequest_; + bool finishNowRequest_ = false; + + //---- status updates ---- + std::mutex lockCurrentStatus_; //different lock for status updates so that we're not blocked by other threads reporting errors + std::vector<std::vector<ThreadStatus>> statusByPriority_; + //give status messages priority according to their folder pair (e.g. first folder pair has prio 0) => visualize (somewhat) natural processing order + + //std::vector<char/*bool*/> usedIndexNums_; //keep info for human-readable task index numbers + + //---- status updates II (lock-free) ---- + std::atomic<int> itemsDeltaProcessed_{ 0 }; // + std::atomic<int64_t> bytesDeltaProcessed_{ 0 }; //std:atomic is uninitialized by default! + std::atomic<int> itemsDeltaTotal_ { 0 }; // + std::atomic<int64_t> bytesDeltaTotal_ { 0 }; // +}; + + +//manage statistics reporting for a single item of work +template <class Callback = ProcessCallback> +class ItemStatReporter +{ +public: + ItemStatReporter(int itemsExpected, int64_t bytesExpected, Callback& cb) : + itemsExpected_(itemsExpected), + bytesExpected_(bytesExpected), + cb_(cb) {} + + ~ItemStatReporter() + { + const bool scopeFail = std::uncaught_exceptions() > exeptionCount_; + if (scopeFail) + cb_.updateDataTotal(itemsReported_, bytesReported_); //=> unexpected increase of total workload + else + //update statistics to consider the real amount of data, e.g. more than the "file size" for ADS streams, + //less for sparse and compressed files, or file changed in the meantime! + cb_.updateDataTotal(itemsReported_ - itemsExpected_, bytesReported_ - bytesExpected_); //noexcept! + } + + void reportStatus(const std::wstring& msg) { cb_.reportStatus(msg); } //throw ThreadInterruption + + void reportDelta(int itemsDelta, int64_t bytesDelta) //nothrow! + { + cb_.updateDataProcessed(itemsDelta, bytesDelta); //nothrow! + itemsReported_ += itemsDelta; + bytesReported_ += bytesDelta; + + //special rule: avoid temporary statistics mess up, even though they are corrected anyway below: + if (itemsReported_ > itemsExpected_) + { + cb_.updateDataTotal(itemsReported_ - itemsExpected_, 0); + itemsReported_ = itemsExpected_; + } + if (bytesReported_ > bytesExpected_) + { + cb_.updateDataTotal(0, bytesReported_ - bytesExpected_); //=> everything above "bytesExpected" adds to both "processed" and "total" data + bytesReported_ = bytesExpected_; + } + } + +private: + int itemsReported_ = 0; + int64_t bytesReported_ = 0; + const int itemsExpected_; + const int64_t bytesExpected_; + Callback& cb_; + const int exeptionCount_ = std::uncaught_exceptions(); +}; + +using AsyncItemStatReporter = ItemStatReporter<AsyncCallback>; + +//===================================================================================================================== + +template <class Function, class Callback> inline //return ignored error message if available +std::wstring tryReportingError(Function cmd /*throw FileError*/, Callback& cb /*throw X*/) +{ + for (size_t retryNumber = 0;; ++retryNumber) + try + { + cmd(); //throw FileError + return std::wstring(); + } + catch (zen::FileError& e) + { + assert(!e.toString().empty()); + switch (cb.reportError(e.toString(), retryNumber)) //throw X + { + case ProcessCallback::IGNORE_ERROR: + return e.toString(); + case ProcessCallback::RETRY: + break; //continue with loop + } + } +} + +//===================================================================================================================== + +template <class Function> inline +auto parallelScope(Function&& fun, std::mutex& singleThread) //throw X +{ + singleThread.unlock(); + ZEN_ON_SCOPE_EXIT(singleThread.lock()); + + return fun(); //throw X +} +} + +#endif //STATUS_HANDLER_IMPL_H_07682758976 diff --git a/FreeFileSync/Source/structures.cpp b/FreeFileSync/Source/base/structures.cpp index a882807f..eff069ce 100755 --- a/FreeFileSync/Source/structures.cpp +++ b/FreeFileSync/Source/base/structures.cpp @@ -10,7 +10,7 @@ #include <ctime> #include <zen/i18n.h> #include <zen/time.h> -#include "lib/hard_filter.h" +#include "hard_filter.h" using namespace zen; using namespace fff; @@ -110,7 +110,7 @@ std::wstring fff::getVariantName(DirectionConfig::Variant var) } -//use in sync log files where users expect ANSI: https://www.freefilesync.org/forum/viewtopic.php?t=4647 +//use in sync log files where users expect ANSI: https://freefilesync.org/forum/viewtopic.php?t=4647 std::wstring fff::getVariantNameForLog(DirectionConfig::Variant var) { return getVariantNameImpl(var, L"<-", L"->", L">"); diff --git a/FreeFileSync/Source/structures.h b/FreeFileSync/Source/base/structures.h index 2403cf8d..90e565b6 100755 --- a/FreeFileSync/Source/structures.h +++ b/FreeFileSync/Source/base/structures.h @@ -11,7 +11,7 @@ #include <memory> #include <zen/zstring.h> #include <zen/optional.h> -#include "fs/abstract.h" +#include "../fs/abstract.h" namespace fff diff --git a/FreeFileSync/Source/synchronization.cpp b/FreeFileSync/Source/base/synchronization.cpp index 89c9684c..273ffb9b 100755 --- a/FreeFileSync/Source/synchronization.cpp +++ b/FreeFileSync/Source/base/synchronization.cpp @@ -11,13 +11,13 @@ #include <zen/guid.h> #include <zen/crc.h> #include "algorithm.h" -#include "lib/db_file.h" -#include "lib/dir_exist_async.h" -#include "lib/status_handler_impl.h" -#include "lib/versioning.h" -#include "lib/binary.h" -#include "fs/concrete.h" -#include "fs/native.h" +#include "db_file.h" +#include "dir_exist_async.h" +#include "status_handler_impl.h" +#include "versioning.h" +#include "binary.h" +#include "../fs/concrete.h" +#include "../fs/native.h" #include <unistd.h> //fsync #include <fcntl.h> //open @@ -368,16 +368,6 @@ void verifyFiles(const AbstractPath& sourcePath, const AbstractPath& targetPath, namespace parallel { -template <class Function> inline -auto parallelScope(Function fun, std::mutex& singleThread) //throw X -{ - singleThread.unlock(); - ZEN_ON_SCOPE_EXIT(singleThread.lock()); - - return fun(); //throw X -} - - inline AFS::ItemType getItemType(const AbstractPath& ap, std::mutex& singleThread) //throw FileError { return parallelScope([ap] { return AFS::getItemType(ap); /*throw FileError*/ }, singleThread); } @@ -467,306 +457,6 @@ void verifyFiles(const AbstractPath& apSource, const AbstractPath& apTarget, con } - -namespace -{ -class AsyncCallback //actor pattern -{ -public: - AsyncCallback(size_t threadCount) : threadStatus_(threadCount), totalThreadCount_(threadCount) {} - - //non-blocking: context of worker thread - void updateDataProcessed(int itemsDelta, int64_t bytesDelta) //noexcept!! - { - itemsDeltaProcessed_ += itemsDelta; - bytesDeltaProcessed_ += bytesDelta; - } - void updateDataTotal(int itemsDelta, int64_t bytesDelta) //noexcept!! - { - itemsDeltaTotal_ += itemsDelta; - bytesDeltaTotal_ += bytesDelta; - } - - //context of main thread - void reportStats(ProcessCallback& cb) - { - assert(std::this_thread::get_id() == mainThreadId); - - const std::pair<int, int64_t> deltaProcessed(itemsDeltaProcessed_, bytesDeltaProcessed_); - if (deltaProcessed.first != 0 || deltaProcessed.second != 0) - { - updateDataProcessed (-deltaProcessed.first, -deltaProcessed.second); //careful with these atomics: don't just set to 0 - cb.updateDataProcessed( deltaProcessed.first, deltaProcessed.second); //noexcept!! - } - const std::pair<int, int64_t> deltaTotal(itemsDeltaTotal_, bytesDeltaTotal_); - if (deltaTotal.first != 0 || deltaTotal.second != 0) - { - updateDataTotal (-deltaTotal.first, -deltaTotal.second); - cb.updateDataTotal( deltaTotal.first, deltaTotal.second); //noexcept!! - } - } - - //context of worker thread - void reportStatus(const std::wstring& msg, size_t threadIdx) //throw ThreadInterruption - { - assert(std::this_thread::get_id() != mainThreadId); - std::lock_guard<std::mutex> dummy(lockCurrentStatus_); - - assert(threadStatus_[threadIdx].active); - threadStatus_[threadIdx].statusMsg = msg; - - interruptionPoint(); //throw ThreadInterruption - } - - //context of main thread, call repreatedly - std::wstring getCurrentStatus() - { - assert(std::this_thread::get_id() == mainThreadId); - - int activeThreadCount = 0; - std::wstring statusMsg; - { - std::lock_guard<std::mutex> dummy(lockCurrentStatus_); - - for (const ThreadStatus& ts : threadStatus_) - if (ts.active) - { - ++activeThreadCount; - if (statusMsg.empty()) - statusMsg = ts.statusMsg; - } - } - - std::wstring output; - if (activeThreadCount >= 2) - output = L"[" + _P("1 thread", "%x threads", activeThreadCount) + L"] "; - output += statusMsg; - return output; - } - - //blocking call: context of worker thread - //=> indirect support for "pause": reportInfo() 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, size_t threadIdx) //throw ThreadInterruption - { - reportStatus(msg, threadIdx); //throw ThreadInterruption - logInfo (msg, threadIdx); // - } - - //blocking call: context of worker thread - void logInfo(const std::wstring& msg, size_t threadIdx) //throw ThreadInterruption - { - assert(std::this_thread::get_id() != mainThreadId); - std::unique_lock<std::mutex> dummy(lockRequest_); - interruptibleWait(conditionReadyForNewRequest_, dummy, [this] { return !logInfoRequest_; }); //throw ThreadInterruption - - logInfoRequest_ = (totalThreadCount_ > 1 ? L"[" + numberTo<std::wstring>(threadIdx + 1) + L"] " : L"") + msg; - - dummy.unlock(); //optimization for condition_variable::notify_all() - conditionNewRequest.notify_all(); - } - - //blocking call: context of worker thread - ProcessCallback::Response reportError(const std::wstring& msg, size_t retryNumber, size_t threadIdx) //throw ThreadInterruption - { - assert(std::this_thread::get_id() != mainThreadId); - std::unique_lock<std::mutex> dummy(lockRequest_); - interruptibleWait(conditionReadyForNewRequest_, dummy, [this] { return !errorRequest_ && !errorResponse_; }); //throw ThreadInterruption - - errorRequest_ = ErrorInfo({ (totalThreadCount_ > 1 ? L"[" + numberTo<std::wstring>(threadIdx + 1) + L"] " : L"") + msg, retryNumber }); - conditionNewRequest.notify_all(); - - interruptibleWait(conditionHaveResponse_, dummy, [this] { return static_cast<bool>(errorResponse_); }); //throw ThreadInterruption - - ProcessCallback::Response rv = *errorResponse_; - - errorRequest_ = NoValue(); - errorResponse_ = NoValue(); - - dummy.unlock(); //optimization for condition_variable::notify_all() - conditionReadyForNewRequest_.notify_all(); //=> spurious wake-up for AsyncCallback::logInfo() - return rv; - } - - //context of main thread - void waitUntilDone(std::chrono::milliseconds duration, ProcessCallback& cb) //throw X - { - assert(std::this_thread::get_id() == mainThreadId); - for (;;) - { - const std::chrono::steady_clock::time_point callbackTime = std::chrono::steady_clock::now() + duration; - - for (std::unique_lock<std::mutex> dummy(lockRequest_) ;;) //process all errors without delay - { - const bool rv = conditionNewRequest.wait_until(dummy, callbackTime, [this] { return (errorRequest_ && !errorResponse_) || logInfoRequest_ || finishNowRequest_; }); - if (!rv) //time-out + condition not met - break; - - if (errorRequest_ && !errorResponse_) - { - assert(!finishNowRequest_); - errorResponse_ = cb.reportError(errorRequest_->msg, errorRequest_->retryNumber); //throw X - conditionHaveResponse_.notify_all(); //instead of notify_one(); workaround bug: https://svn.boost.org/trac/boost/ticket/7796 - } - if (logInfoRequest_) - { - cb.logInfo(*logInfoRequest_); - logInfoRequest_ = NoValue(); - conditionReadyForNewRequest_.notify_all(); //=> spurious wake-up for AsyncCallback::reportError() - } - if (finishNowRequest_) - { - dummy.unlock(); //call member functions outside of mutex scope: - reportStats(cb); //one last call for accurate stat-reporting! - return; - } - } - - //call member functions outside of mutex scope: - cb.reportStatus(getCurrentStatus()); //throw X - reportStats(cb); - } - } - - void notifyWorkBegin(size_t threadIdx) //noexcept - { - std::lock_guard<std::mutex> dummy(lockCurrentStatus_); - assert(!threadStatus_[threadIdx].active); - threadStatus_[threadIdx].active = true; - } - - void notifyWorkEnd(size_t threadIdx) //noexcept - { - std::lock_guard<std::mutex> dummy(lockCurrentStatus_); - assert(threadStatus_[threadIdx].active); - threadStatus_[threadIdx].active = false; - threadStatus_[threadIdx].statusMsg.clear(); - } - - void notifyAllDone() //noexcept - { - std::lock_guard<std::mutex> dummy(lockRequest_); - assert(!finishNowRequest_); - finishNowRequest_ = true; - conditionNewRequest.notify_all(); //perf: should unlock mutex before notify!? (insignificant) - } - -private: - AsyncCallback (const AsyncCallback&) = delete; - AsyncCallback& operator=(const AsyncCallback&) = delete; - - struct ThreadStatus - { - bool active = false; - std::wstring statusMsg; - }; - struct ErrorInfo - { - std::wstring msg; - size_t retryNumber = 0; - }; - - //---- main <-> worker communication channel ---- - std::mutex lockRequest_; - std::condition_variable conditionReadyForNewRequest_; - std::condition_variable conditionNewRequest; - std::condition_variable conditionHaveResponse_; - Opt<ErrorInfo> errorRequest_; - Opt<ProcessCallback::Response> errorResponse_; - Opt<std::wstring> logInfoRequest_; - bool finishNowRequest_ = false; - - //---- status updates ---- - std::mutex lockCurrentStatus_; //use a different lock for current file: continue traversing while some thread may process an error - std::vector<ThreadStatus> threadStatus_; - - const size_t totalThreadCount_; - - //---- status updates II (lock-free) ---- - std::atomic<int> itemsDeltaProcessed_{ 0 }; // - std::atomic<int64_t> bytesDeltaProcessed_{ 0 }; //std:atomic is uninitialized by default! - std::atomic<int> itemsDeltaTotal_ { 0 }; // - std::atomic<int64_t> bytesDeltaTotal_ { 0 }; // -}; - - -template <typename Function> inline //return ignored error message if available -Opt<std::wstring> tryReportingError(Function cmd, size_t threadIdx, AsyncCallback& acb) //throw ThreadInterruption -{ - for (size_t retryNumber = 0;; ++retryNumber) - try - { - cmd(); //throw FileError - return NoValue(); - } - catch (FileError& error) - { - switch (acb.reportError(error.toString(), retryNumber, threadIdx)) //throw ThreadInterruption - { - case ProcessCallback::IGNORE_ERROR: - return error.toString(); - case ProcessCallback::RETRY: - break; //continue with loop - } - } -} - - -//manage statistics reporting for a single item of work -class AsyncItemStatReporter -{ -public: - AsyncItemStatReporter(int itemsExpected, int64_t bytesExpected, size_t threadIdx, AsyncCallback& acb) : - itemsExpected_(itemsExpected), - bytesExpected_(bytesExpected), - threadIdx_(threadIdx), - acb_(acb) {} - - ~AsyncItemStatReporter() - { - const bool scopeFail = getUncaughtExceptionCount() > exeptionCount_; - if (scopeFail) - acb_.updateDataTotal(itemsReported_, bytesReported_); //=> unexpected increase of total workload - else - //update statistics to consider the real amount of data, e.g. more than the "file size" for ADS streams, - //less for sparse and compressed files, or file changed in the meantime! - acb_.updateDataTotal(itemsReported_ - itemsExpected_, bytesReported_ - bytesExpected_); //noexcept! - } - - void reportStatus(const std::wstring& text) { acb_.reportStatus(text, threadIdx_); } //throw ThreadInterruption - - void reportDelta(int itemsDelta, int64_t bytesDelta) //throw ThreadInterruption - { - acb_.updateDataProcessed(itemsDelta, bytesDelta); //nothrow! - itemsReported_ += itemsDelta; - bytesReported_ += bytesDelta; - - //special rule: avoid temporary statistics mess up, even though they are corrected anyway below: - if (itemsReported_ > itemsExpected_) - { - acb_.updateDataTotal(itemsReported_ - itemsExpected_, 0); - itemsReported_ = itemsExpected_; - } - if (bytesReported_ > bytesExpected_) - { - acb_.updateDataTotal(0, bytesReported_ - bytesExpected_); //=> everything above "bytesExpected" adds to both "processed" and "total" data - bytesReported_ = bytesExpected_; - } - - interruptionPoint(); //throw ThreadInterruption - } - -private: - int itemsReported_ = 0; - int64_t bytesReported_ = 0; - const int itemsExpected_; - const int64_t bytesExpected_; - const size_t threadIdx_; - AsyncCallback& acb_; - const int exeptionCount_ = getUncaughtExceptionCount(); -}; -} - //################################################################################################################# //################################################################################################################# @@ -883,7 +573,7 @@ txtRemovingFolder_([&] void DeletionHandling::tryCleanup(ProcessCallback& cb /*throw X*/, bool allowCallbackException) //throw FileError { - assert(std::this_thread::get_id() == mainThreadId); + assert(runningMainThread()); switch (deletionPolicy_) { case DeletionPolicy::PERMANENT: @@ -941,9 +631,10 @@ void DeletionHandling::removeDirWithCallback(const AbstractPath& folderPath,//th auto notifyDeletion = [&statReporter](const std::wstring& statusText, const std::wstring& displayPath) { statReporter.reportStatus(replaceCpy(statusText, L"%x", fmtPath(displayPath))); //throw ThreadInterruption - statReporter.reportDelta(1, 0); //throw ThreadInterruption; it would be more correct to report *after* work was done! + statReporter.reportDelta(1, 0); //it would be more correct to report *after* work was done! + //OTOH: ThreadInterruption must not happen after last deletion was successful: allow for transactional file model update! }; - static_assert(std::is_const<decltype(txtRemovingFile_)>::value, "callbacks better be thread-safe!"); + static_assert(std::is_const_v<decltype(txtRemovingFile_)>, "callbacks better be thread-safe!"); auto onBeforeFileDeletion = [&](const std::wstring& displayPath) { notifyDeletion(txtRemovingFile_, displayPath); }; auto onBeforeDirDeletion = [&](const std::wstring& displayPath) { notifyDeletion(txtRemovingFolder_, displayPath); }; @@ -953,7 +644,7 @@ void DeletionHandling::removeDirWithCallback(const AbstractPath& folderPath,//th case DeletionPolicy::RECYCLER: parallel::recycleItem(getOrCreateRecyclerSession(), folderPath, relativePath, singleThread); //throw FileError - statReporter.reportDelta(1, 0); //throw ThreadInterruption; moving to recycler is ONE logical operation, irrespective of the number of child elements! + statReporter.reportDelta(1, 0); //moving to recycler is ONE logical operation, irrespective of the number of child elements! break; case DeletionPolicy::VERSIONING: @@ -962,12 +653,12 @@ void DeletionHandling::removeDirWithCallback(const AbstractPath& folderPath,//th auto notifyMove = [&statReporter](const std::wstring& statusText, const std::wstring& displayPathFrom, const std::wstring& displayPathTo) { statReporter.reportStatus(replaceCpy(replaceCpy(statusText, L"%x", L"\n" + fmtPath(displayPathFrom)), L"%y", L"\n" + fmtPath(displayPathTo))); //throw ThreadInterruption - statReporter.reportDelta(1, 0); //throw ThreadInterruption; it would be more correct to report *after* work was done! + statReporter.reportDelta(1, 0); //it would be more correct to report *after* work was done! }; - static_assert(std::is_const<decltype(txtMovingFileXtoY_)>::value, "callbacks better be thread-safe!"); + static_assert(std::is_const_v<decltype(txtMovingFileXtoY_)>, "callbacks better be thread-safe!"); auto onBeforeFileMove = [&](const std::wstring& displayPathFrom, const std::wstring& displayPathTo) { notifyMove(txtMovingFileXtoY_, displayPathFrom, displayPathTo); }; auto onBeforeFolderMove = [&](const std::wstring& displayPathFrom, const std::wstring& displayPathTo) { notifyMove(txtMovingFolderXtoY_, displayPathFrom, displayPathTo); }; - auto notifyUnbufferedIO = [&](int64_t bytesDelta) { statReporter.reportDelta(0, bytesDelta); }; //throw ThreadInterruption + auto notifyUnbufferedIO = [&](int64_t bytesDelta) { statReporter.reportDelta(0, bytesDelta); interruptionPoint(); }; //throw ThreadInterruption parallel::revisionFolder(getOrCreateVersioner(), folderPath, relativePath, onBeforeFileMove, onBeforeFolderMove, notifyUnbufferedIO, singleThread); //throw FileError, ThreadInterruption } @@ -995,7 +686,7 @@ void DeletionHandling::removeFileWithCallback(const FileDescriptor& fileDescr, / case DeletionPolicy::VERSIONING: { //callback runs *outside* singleThread_ lock! => fine - auto notifyUnbufferedIO = [&](int64_t bytesDelta) { statReporter.reportDelta(0, bytesDelta); }; //throw ThreadInterruption + auto notifyUnbufferedIO = [&](int64_t bytesDelta) { statReporter.reportDelta(0, bytesDelta); interruptionPoint(); }; //throw ThreadInterruption parallel::revisionFile(getOrCreateVersioner(), fileDescr, relativePath, notifyUnbufferedIO, singleThread); //throw FileError } @@ -1004,7 +695,7 @@ void DeletionHandling::removeFileWithCallback(const FileDescriptor& fileDescr, / //even if the source item does not exist anymore, significant I/O work was done => report //-> also consider unconditional statReporter.reportDelta(-1, 0) when overwriting a file - statReporter.reportDelta(1, 0); //throw ThreadInterruption + statReporter.reportDelta(1, 0); } @@ -1024,9 +715,10 @@ void DeletionHandling::removeLinkWithCallback(const AbstractPath& linkPath, //th parallel::revisionSymlink(getOrCreateVersioner(), linkPath, relativePath, singleThread); //throw FileError break; } + //remain transactional as much as possible => no more callbacks that can throw after successful deletion! (next: update file model!) //report unconditionally, see removeFileWithCallback() - statReporter.reportDelta(1, 0); //throw ThreadInterruption + statReporter.reportDelta(1, 0); } //------------------------------------------------------------------------------------------------------------ @@ -1113,7 +805,9 @@ private: int64_t spaceNeededRight_ = 0; }; -//---------------------------------------------------------------------------------------- +//=================================================================================================== +//=================================================================================================== + class Workload; class FolderPairSyncer @@ -1147,16 +841,14 @@ private: PASS_NEVER //skip item }; - FolderPairSyncer(SyncCtx& syncCtx, Workload& workload, std::mutex& singleThread, size_t threadIdx, AsyncCallback& acb) : + FolderPairSyncer(SyncCtx& syncCtx, std::mutex& singleThread, AsyncCallback& acb) : errorsModTime_ (syncCtx.errorsModTime), delHandlingLeft_ (syncCtx.delHandlingLeft), delHandlingRight_ (syncCtx.delHandlingRight), verifyCopiedFiles_ (syncCtx.verifyCopiedFiles), copyFilePermissions_(syncCtx.copyFilePermissions), failSafeFileCopy_ (syncCtx.failSafeFileCopy), - workload_(workload), singleThread_(singleThread), - threadIdx_(threadIdx), acb_(acb) {} static PassNo getPass(const FilePair& file); @@ -1165,9 +857,10 @@ private: static void runPass(PassNo pass, SyncCtx& syncCtx, BaseFolderPair& baseFolder, ProcessCallback& cb); //throw X - static void appendFolderLevelWorkItems(PassNo pass, ContainerObject& hierObj, //in - std::vector<std::function<void(FolderPairSyncer& fps)>>& workItems, //out - std::vector<ContainerObject*>& foldersToProcess); // + void appendFolderLevelWorkItems(PassNo pass, ContainerObject& hierObj, //in + Workload& workload, + RingBuffer<std::function<void()>>& workItems, //out + RingBuffer<ContainerObject*>& foldersToProcess); // template <SelectedSide side> void setup2StepMove(FilePair& sourceObj, FilePair& targetObj); //throw FileError, ThreadInterruption @@ -1185,10 +878,10 @@ private: void synchronizeFolder(FolderPair& folder); // template <SelectedSide sideTrg> void synchronizeFolderInt(FolderPair& folder, SyncOperation syncOp); //throw FileError, ThreadInterruption - void reportInfo(const std::wstring& rawText, const std::wstring& displayPath) { acb_.reportInfo(replaceCpy(rawText, L"%x", fmtPath(displayPath)), threadIdx_); } - void reportInfo(const std::wstring& rawText, const std::wstring& displayPath1, const std::wstring& displayPath2) + void reportInfo(const std::wstring& rawText, const std::wstring& displayPath) { acb_.reportInfo(replaceCpy(rawText, L"%x", fmtPath(displayPath))); } + void reportInfo(const std::wstring& rawText, const std::wstring& displayPath1, const std::wstring& displayPath2) //throw ThreadInterruption { - acb_.reportInfo(replaceCpy(replaceCpy(rawText, L"%x", L"\n" + fmtPath(displayPath1)), L"%y", L"\n" + fmtPath(displayPath2)), threadIdx_); + acb_.reportInfo(replaceCpy(replaceCpy(rawText, L"%x", L"\n" + fmtPath(displayPath1)), L"%y", L"\n" + fmtPath(displayPath2))); //throw ThreadInterruption } //target existing after onDeleteTargetFile(): undefined behavior! (fail/overwrite/auto-rename) @@ -1205,9 +898,7 @@ private: const bool copyFilePermissions_; const bool failSafeFileCopy_; - Workload& workload_; std::mutex& singleThread_; - const size_t threadIdx_; AsyncCallback& acb_; //preload status texts (premature?) @@ -1222,7 +913,8 @@ private: const std::wstring txtSourceItemNotFound_{_("Source item %x not found" )}; }; -//--------------------------------------------------------------------------------------------------------------- +//=================================================================================================== +//=================================================================================================== /* ___________________________ | | | Multithreaded File Copy | @@ -1245,15 +937,18 @@ Notes: - All threads share a single mutex, unlocked only during file I/O => do N - Maximize opportunity for parallelization ASAP: Workload buckets serve folder-items *before* files/symlinks => reduce risk of work-stealing - Memory consumption: "Folders to process" may grow indefinitely; however: test case "C:\", 100.000 folders => worst case only ~ 800kB on x64 */ - class Workload { public: - Workload(FolderPairSyncer::PassNo pass, BaseFolderPair& baseFolder, size_t threadCount, AsyncCallback& acb) : - pass_(pass), acb_(acb), workload_(threadCount), foldersToProcess_{ &baseFolder } { assert(threadCount > 0); } + Workload(FolderPairSyncer& fps, FolderPairSyncer::PassNo pass, BaseFolderPair& baseFolder, size_t threadCount, AsyncCallback& acb) : + fps_(fps), pass_(pass), acb_(acb), workload_(threadCount) + { + foldersToProcess_.push_back(&baseFolder); + assert(threadCount > 0); + } //blocking call: context of worker thread - std::function<void(FolderPairSyncer& fps)> getNext(size_t threadIdx) //throw ThreadInterruption + std::function<void()> getNext(size_t threadIdx) //throw ThreadInterruption { std::unique_lock<std::mutex> dummy(lockWork_); for (;;) @@ -1262,19 +957,19 @@ public: { if (!workload_[threadIdx].empty()) { - auto workItem = workload_[threadIdx]. back(); //yes, no strong exception guarantee (std::bad_alloc) - /**/ workload_[threadIdx].pop_back(); // + auto workItem = workload_[threadIdx]. front(); //yes, no strong exception guarantee (std::bad_alloc) + /**/ workload_[threadIdx].pop_front(); // return workItem; } if (!foldersToProcess_.empty()) { - ContainerObject& hierObj = *foldersToProcess_. back(); - /**/ foldersToProcess_.pop_back(); + ContainerObject& hierObj = *foldersToProcess_. front(); + /**/ foldersToProcess_.pop_front(); //thread-safe thanks to std::mutex singleThread: - FolderPairSyncer::appendFolderLevelWorkItems(pass_, hierObj, //in - workload_[threadIdx], //out, appending - foldersToProcess_); // + fps_.appendFolderLevelWorkItems(pass_, hierObj, *this, //in + workload_[threadIdx], //out, appending + foldersToProcess_); // } else break; @@ -1284,18 +979,19 @@ public: WorkItems& items = *std::max_element(workload_.begin(), workload_.end(), [](const WorkItems& lhs, const WorkItems& rhs) { return lhs.size() < rhs.size(); }); if (!items.empty()) //=> != workload_[threadIdx] { - size_t pos = 0; - erase_if(items, [&](const WorkItem& wi) + const size_t sz = items.size(); //[!] variable during loop! + for (size_t i = 0; i < sz; ++i) { - if (pos++ % 2 == 0) - { - workload_[threadIdx].push_back(wi); - return true; - } - return false; - }); - auto workItem = workload_[threadIdx]. back(); //yes, no strong exception guarantee (std::bad_alloc) - /**/ workload_[threadIdx].pop_back(); // + auto wi = std::move(items. front()); + /**/ items.pop_front(); + if (i % 2 == 0) + workload_[threadIdx].push_back(std::move(wi)); + else + items.push_back(std::move(wi)); + } + + auto workItem = workload_[threadIdx]. front(); //yes, no strong exception guarantee (std::bad_alloc) + /**/ workload_[threadIdx].pop_front(); // return workItem; } @@ -1303,9 +999,6 @@ public: acb_.notifyAllDone(); //noexcept ZEN_ON_SCOPE_EXIT(--idleThreads_); - acb_.notifyWorkEnd(threadIdx); - ZEN_ON_SCOPE_EXIT(acb_.notifyWorkBegin(threadIdx)); - auto haveNewWork = [&] { return !foldersToProcess_.empty() || std::any_of(workload_.begin(), workload_.end(), [](const WorkItems& wi) { return !wi.empty(); }); }; interruptibleWait(conditionNewWork_, dummy, [&] { return haveNewWork(); }); //throw ThreadInterruption @@ -1326,9 +1019,10 @@ private: Workload (const Workload&) = delete; Workload& operator=(const Workload&) = delete; - using WorkItem = std::function<void(FolderPairSyncer& fps) /*throw ThreadInterruption*/>; - using WorkItems = std::vector<WorkItem>; + using WorkItem = std::function<void() /*throw ThreadInterruption*/>; + using WorkItems = RingBuffer<WorkItem>; //FIFO! + FolderPairSyncer& fps_; const FolderPairSyncer::PassNo pass_; AsyncCallback& acb_; @@ -1338,7 +1032,7 @@ private: size_t idleThreads_ = 0; std::vector<WorkItems> workload_; //thread-specific buckets - std::vector<ContainerObject*> foldersToProcess_; + RingBuffer<ContainerObject*> foldersToProcess_; //FIFO! }; @@ -1348,26 +1042,26 @@ void FolderPairSyncer::runPass(PassNo pass, SyncCtx& syncCtx, BaseFolderPair& ba std::mutex singleThread; //only a single worker thread may run at a time, except for parallel file I/O - AsyncCallback acb(threadCount); // - Workload workload(pass, baseFolder, threadCount, acb); //manage life time: enclose InterruptibleThread's!!! - - FixedList<InterruptibleThread> worker; + AsyncCallback acb; // + FolderPairSyncer fps(syncCtx, singleThread, acb); //manage life time: enclose InterruptibleThread's!!! + Workload workload(fps, pass, baseFolder, threadCount, acb); // + std::vector<InterruptibleThread> worker; ZEN_ON_SCOPE_EXIT( for (InterruptibleThread& wt : worker) wt.join(); ); ZEN_ON_SCOPE_EXIT( for (InterruptibleThread& wt : worker) wt.interrupt(); ); //interrupt all first, then join for (size_t threadIdx = 0; threadIdx < threadCount; ++threadIdx) - worker.emplace_back([fps = FolderPairSyncer(syncCtx, workload, singleThread, threadIdx, acb), threadIdx, &singleThread, &acb, &workload]() mutable + worker.emplace_back([threadIdx, &singleThread, &acb, &workload] { setCurrentThreadName(("Sync Worker[" + numberTo<std::string>(threadIdx) + "]").c_str()); - acb.notifyWorkBegin(threadIdx); - ZEN_ON_SCOPE_EXIT(acb.notifyWorkEnd(threadIdx)); - - while (/*blocking call:*/ std::function<void(FolderPairSyncer& fps)> workItem = workload.getNext(threadIdx)) //throw ThreadInterruption + while (/*blocking call:*/ std::function<void()> workItem = workload.getNext(threadIdx)) //throw ThreadInterruption { + acb.notifyTaskBegin(0 /*prio*/); //same prio, while processing only one folder pair at a time + ZEN_ON_SCOPE_EXIT(acb.notifyTaskEnd()); + std::lock_guard<std::mutex> dummy(singleThread); //protect ALL accesses to "fps" and workItem execution! - workItem(fps); //throw ThreadInterruption + workItem(); //throw ThreadInterruption } }); @@ -1375,21 +1069,18 @@ void FolderPairSyncer::runPass(PassNo pass, SyncCtx& syncCtx, BaseFolderPair& ba } -void FolderPairSyncer::appendFolderLevelWorkItems(PassNo pass, ContainerObject& hierObj, //in - std::vector<std::function<void(FolderPairSyncer& fps)>>& workItems, //out - std::vector<ContainerObject* >& foldersToProcess) // +void FolderPairSyncer::appendFolderLevelWorkItems(PassNo pass, ContainerObject& hierObj, Workload& workload, //in + RingBuffer<std::function<void()>>& workItems, //out + RingBuffer<ContainerObject* >& foldersToProcess) // { - const size_t itemCountOld = workItems.size(); - const size_t folderCountOld = foldersToProcess.size(); - //synchronize folders: for (FolderPair& folder : hierObj.refSubFolders()) if (pass == getPass(folder)) - workItems.push_back([&folder](FolderPairSyncer& fps) + workItems.push_back([this, &folder, &workload] { - tryReportingError([&] { fps.synchronizeFolder(folder); }, fps.threadIdx_, fps.acb_); //throw ThreadInterruption - fps.workload_.addFolderToProcess(folder); - warn_static("unnatural processing order!?") + tryReportingError([&] { synchronizeFolder(folder); }, acb_); //throw ThreadInterruption + + workload.addFolderToProcess(folder); }); else foldersToProcess.push_back(&folder); @@ -1397,24 +1088,20 @@ void FolderPairSyncer::appendFolderLevelWorkItems(PassNo pass, ContainerObject& //synchronize files: for (FilePair& file : hierObj.refSubFiles()) if (pass == PASS_ZERO) - workItems.push_back([&file](FolderPairSyncer& fps) { fps.prepareFileMove(file); /*throw ThreadInterruption*/ }); - else if (pass == getPass(file)) - workItems.push_back([&file](FolderPairSyncer& fps) - { - tryReportingError([&] { fps.synchronizeFile(file); }, fps.threadIdx_, fps.acb_); //throw ThreadInterruption - }); + workItems.push_back([this, &file] { prepareFileMove(file); /*throw ThreadInterruption*/ }); + else if (pass == getPass(file)) + workItems.push_back([this, &file] + { + tryReportingError([&] { synchronizeFile(file); }, acb_); //throw ThreadInterruption + }); //synchronize symbolic links: for (SymlinkPair& symlink : hierObj.refSubLinks()) if (pass == getPass(symlink)) - workItems.push_back([&symlink](FolderPairSyncer& fps) + workItems.push_back([this, &symlink] { - tryReportingError([&] { fps.synchronizeLink(symlink); }, fps.threadIdx_, fps.acb_); //throw ThreadInterruption + tryReportingError([&] { synchronizeLink(symlink); }, acb_); //throw ThreadInterruption }); - - //ensure natural processing order despite LIFO: - std::reverse(workItems .begin() + itemCountOld, workItems .end()); - std::reverse(foldersToProcess.begin() + folderCountOld, foldersToProcess.end()); } @@ -1482,8 +1169,8 @@ void FolderPairSyncer::setup2StepMove(FilePair& sourceObj, //throw FileError, Th //update file hierarchy FilePair& tempFile = sourceObj.base().addSubFile<side>(afterLast(sourceRelPathTmp, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL), sourceObj.getAttributes<side>()); - static_assert(IsSameType<FixedList<FilePair>, ContainerObject::FileList>::value, - "ATTENTION: we're adding to the file list WHILE looping over it! This is only working because FixedList iterators are not invalidated by insertion!"); + static_assert(std::is_same_v<ContainerObject::FileList, std::list<FilePair>>, + "ATTENTION: we're adding to the file list WHILE looping over it! This is only working because std::list iterators are not invalidated by insertion!"); sourceObj.removeObject<side>(); //remove only *after* evaluating "sourceObj, side"! //note: this new item is *not* considered at the end of 0th pass because "!sourceWillBeDeleted && !haveNameClash" @@ -1592,15 +1279,15 @@ void FolderPairSyncer::prepareFileMove(FilePair& file) //throw ThreadInterruptio FilePair* sourceObj = &file; assert(dynamic_cast<FilePair*>(FileSystemObject::retrieve(targetObj->getMoveRef())) == sourceObj); - Opt<std::wstring> errMsg = tryReportingError([&] //throw ThreadInterruption + const std::wstring errMsg = tryReportingError([&] //throw ThreadInterruption { if (syncOp == SO_MOVE_LEFT_FROM) resolveMoveConflicts<LEFT_SIDE>(*sourceObj, *targetObj); //throw FileError, ThreadInterruption else resolveMoveConflicts<RIGHT_SIDE>(*sourceObj, *targetObj); // - }, threadIdx_, acb_); //throw ThreadInterruption + }, acb_); //throw ThreadInterruption - if (errMsg) + if (!errMsg.empty()) { //move operation has failed! We cannot allow to continue and have move source's parent directory deleted, messing up statistics! // => revert to ordinary "copy + delete" @@ -1767,7 +1454,7 @@ void FolderPairSyncer::synchronizeFile(FilePair& file) //throw FileError, Thread template <SelectedSide sideTrg> void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) //throw FileError, ThreadInterruption { - static const SelectedSide sideSrc = OtherSide<sideTrg>::result; + constexpr SelectedSide sideSrc = OtherSide<sideTrg>::value; DeletionHandling& delHandlingTrg = SelectParam<sideTrg>::ref(delHandlingLeft_, delHandlingRight_); switch (syncOp) @@ -1781,9 +1468,9 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) //can't use "getAbstractPath<sideTrg>()" as file name is not available! const AbstractPath targetPath = file.getAbstractPath<sideTrg>(); - reportInfo(txtCreatingFile_, AFS::getDisplayPath(targetPath)); + reportInfo(txtCreatingFile_, AFS::getDisplayPath(targetPath)); //throw ThreadInterruption - AsyncItemStatReporter statReporter(1, file.getFileSize<sideSrc>(), threadIdx_, acb_); + AsyncItemStatReporter statReporter(1, file.getFileSize<sideSrc>(), acb_); try { const AFS::FileCopyResult result = copyFileWithCallback({ file.getAbstractPath<sideSrc>(), file.getAttributes<sideSrc>() }, @@ -1814,9 +1501,9 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) { //even if the source item does not exist anymore, significant I/O work was done => report statReporter.reportDelta(1, 0); - reportInfo(txtSourceItemNotFound_, AFS::getDisplayPath(file.getAbstractPath<sideSrc>())); - file.removeObject<sideSrc>(); //source deleted meanwhile...nothing was done (logical point of view!) + + reportInfo(txtSourceItemNotFound_, AFS::getDisplayPath(file.getAbstractPath<sideSrc>())); //throw ThreadInterruption } else throw; @@ -1826,9 +1513,9 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) case SO_DELETE_LEFT: case SO_DELETE_RIGHT: - reportInfo(delHandlingTrg.getTxtRemovingFile(), AFS::getDisplayPath(file.getAbstractPath<sideTrg>())); + reportInfo(delHandlingTrg.getTxtRemovingFile(), AFS::getDisplayPath(file.getAbstractPath<sideTrg>())); //throw ThreadInterruption { - AsyncItemStatReporter statReporter(1, 0, threadIdx_, acb_); + AsyncItemStatReporter statReporter(1, 0, acb_); delHandlingTrg.removeFileWithCallback({ file.getAbstractPath<sideTrg>(), file.getAttributes<sideTrg>() }, file.getPairRelativePath(), statReporter, singleThread_); //throw FileError, X @@ -1848,9 +1535,9 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) const AbstractPath pathFrom = moveFrom->getAbstractPath<sideTrg>(); const AbstractPath pathTo = moveTo ->getAbstractPath<sideTrg>(); - reportInfo(txtMovingFileXtoY_, AFS::getDisplayPath(pathFrom), AFS::getDisplayPath(pathTo)); + reportInfo(txtMovingFileXtoY_, AFS::getDisplayPath(pathFrom), AFS::getDisplayPath(pathTo)); //throw ThreadInterruption - AsyncItemStatReporter statReporter(1, 0, threadIdx_, acb_); + AsyncItemStatReporter statReporter(1, 0, acb_); //TODO: synchronizeFileInt: consider ErrorDifferentVolume! e.g. symlink aliasing! @@ -1883,9 +1570,9 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) if (file.isFollowedSymlink<sideTrg>()) //follow link when updating file rather than delete it and replace with regular file!!! targetPathResolvedOld = targetPathResolvedNew = parallel::getSymlinkResolvedPath(file.getAbstractPath<sideTrg>(), singleThread_); //throw FileError - reportInfo(txtUpdatingFile_, AFS::getDisplayPath(targetPathResolvedOld)); + reportInfo(txtUpdatingFile_, AFS::getDisplayPath(targetPathResolvedOld)); //throw ThreadInterruption - AsyncItemStatReporter statReporter(1, file.getFileSize<sideSrc>(), threadIdx_, acb_); + AsyncItemStatReporter statReporter(1, file.getFileSize<sideSrc>(), acb_); if (file.isFollowedSymlink<sideTrg>()) //since we follow the link, we need to sync case sensitivity of the link manually! if (file.getItemName<sideTrg>() != file.getItemName<sideSrc>()) //have difference in case? @@ -1932,9 +1619,9 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) case SO_COPY_METADATA_TO_LEFT: case SO_COPY_METADATA_TO_RIGHT: //harmonize with file_hierarchy.cpp::getSyncOpDescription!! - reportInfo(txtUpdatingAttributes_, AFS::getDisplayPath(file.getAbstractPath<sideTrg>())); + reportInfo(txtUpdatingAttributes_, AFS::getDisplayPath(file.getAbstractPath<sideTrg>())); //throw ThreadInterruption { - AsyncItemStatReporter statReporter(1, 0, threadIdx_, acb_); + AsyncItemStatReporter statReporter(1, 0, acb_); assert(file.getItemName<sideTrg>() != file.getItemName<sideSrc>()); if (file.getItemName<sideTrg>() != file.getItemName<sideSrc>()) //have difference in case? @@ -1993,8 +1680,7 @@ void FolderPairSyncer::synchronizeLink(SymlinkPair& link) //throw FileError, Thr template <SelectedSide sideTrg> void FolderPairSyncer::synchronizeLinkInt(SymlinkPair& symlink, SyncOperation syncOp) //throw FileError, ThreadInterruption { - warn_static("test constexpr compiler conformance") - static const SelectedSide sideSrc = OtherSide<sideTrg>::result; + constexpr SelectedSide sideSrc = OtherSide<sideTrg>::value; DeletionHandling& delHandlingTrg = SelectParam<sideTrg>::ref(delHandlingLeft_, delHandlingRight_); switch (syncOp) @@ -2007,9 +1693,9 @@ void FolderPairSyncer::synchronizeLinkInt(SymlinkPair& symlink, SyncOperation sy return; //if parent directory creation failed, there's no reason to show more errors! const AbstractPath targetPath = symlink.getAbstractPath<sideTrg>(); - reportInfo(txtCreatingLink_, AFS::getDisplayPath(targetPath)); + reportInfo(txtCreatingLink_, AFS::getDisplayPath(targetPath)); //throw ThreadInterruption - AsyncItemStatReporter statReporter(1, 0, threadIdx_, acb_); + AsyncItemStatReporter statReporter(1, 0, acb_); try { parallel::copySymlink(symlink.getAbstractPath<sideSrc>(), targetPath, copyFilePermissions_, singleThread_); //throw FileError @@ -2033,9 +1719,9 @@ void FolderPairSyncer::synchronizeLinkInt(SymlinkPair& symlink, SyncOperation sy { //even if the source item does not exist anymore, significant I/O work was done => report statReporter.reportDelta(1, 0); - reportInfo(txtSourceItemNotFound_, AFS::getDisplayPath(symlink.getAbstractPath<sideSrc>())); - symlink.removeObject<sideSrc>(); //source deleted meanwhile...nothing was done (logical point of view!) + + reportInfo(txtSourceItemNotFound_, AFS::getDisplayPath(symlink.getAbstractPath<sideSrc>())); //throw ThreadInterruption } else throw; @@ -2045,9 +1731,9 @@ void FolderPairSyncer::synchronizeLinkInt(SymlinkPair& symlink, SyncOperation sy case SO_DELETE_LEFT: case SO_DELETE_RIGHT: - reportInfo(delHandlingTrg.getTxtRemovingSymLink(), AFS::getDisplayPath(symlink.getAbstractPath<sideTrg>())); + reportInfo(delHandlingTrg.getTxtRemovingSymLink(), AFS::getDisplayPath(symlink.getAbstractPath<sideTrg>())); //throw ThreadInterruption { - AsyncItemStatReporter statReporter(1, 0, threadIdx_, acb_); + AsyncItemStatReporter statReporter(1, 0, acb_); delHandlingTrg.removeLinkWithCallback(symlink.getAbstractPath<sideTrg>(), symlink.getPairRelativePath(), statReporter, singleThread_); //throw FileError, X @@ -2057,9 +1743,9 @@ void FolderPairSyncer::synchronizeLinkInt(SymlinkPair& symlink, SyncOperation sy case SO_OVERWRITE_LEFT: case SO_OVERWRITE_RIGHT: - reportInfo(txtUpdatingLink_, AFS::getDisplayPath(symlink.getAbstractPath<sideTrg>())); + reportInfo(txtUpdatingLink_, AFS::getDisplayPath(symlink.getAbstractPath<sideTrg>())); //throw ThreadInterruption { - AsyncItemStatReporter statReporter(1, 0, threadIdx_, acb_); + AsyncItemStatReporter statReporter(1, 0, acb_); //reportStatus(delHandlingTrg.getTxtRemovingSymLink(), AFS::getDisplayPath(symlink.getAbstractPath<sideTrg>())); delHandlingTrg.removeLinkWithCallback(symlink.getAbstractPath<sideTrg>(), symlink.getPairRelativePath(), statReporter, singleThread_); //throw FileError, X @@ -2085,9 +1771,9 @@ void FolderPairSyncer::synchronizeLinkInt(SymlinkPair& symlink, SyncOperation sy case SO_COPY_METADATA_TO_LEFT: case SO_COPY_METADATA_TO_RIGHT: - reportInfo(txtUpdatingAttributes_, AFS::getDisplayPath(symlink.getAbstractPath<sideTrg>())); + reportInfo(txtUpdatingAttributes_, AFS::getDisplayPath(symlink.getAbstractPath<sideTrg>())); //throw ThreadInterruption { - AsyncItemStatReporter statReporter(1, 0, threadIdx_, acb_); + AsyncItemStatReporter statReporter(1, 0, acb_); if (symlink.getItemName<sideTrg>() != symlink.getItemName<sideSrc>()) //have difference in case? parallel::renameItem(symlink.getAbstractPath<sideTrg>(), //throw FileError, (ErrorDifferentVolume) @@ -2140,7 +1826,7 @@ void FolderPairSyncer::synchronizeFolder(FolderPair& folder) //throw FileError, template <SelectedSide sideTrg> void FolderPairSyncer::synchronizeFolderInt(FolderPair& folder, SyncOperation syncOp) //throw FileError, ThreadInterruption { - static const SelectedSide sideSrc = OtherSide<sideTrg>::result; + constexpr SelectedSide sideSrc = OtherSide<sideTrg>::value; DeletionHandling& delHandlingTrg = SelectParam<sideTrg>::ref(delHandlingLeft_, delHandlingRight_); switch (syncOp) @@ -2153,12 +1839,12 @@ void FolderPairSyncer::synchronizeFolderInt(FolderPair& folder, SyncOperation sy return; //if parent directory creation failed, there's no reason to show more errors! const AbstractPath targetPath = folder.getAbstractPath<sideTrg>(); - reportInfo(txtCreatingFolder_, AFS::getDisplayPath(targetPath)); + reportInfo(txtCreatingFolder_, AFS::getDisplayPath(targetPath)); //throw ThreadInterruption //shallow-"copying" a folder might not fail if source is missing, so we need to check this first: if (parallel::getItemTypeIfExists(folder.getAbstractPath<sideSrc>(), singleThread_)) //throw FileError { - AsyncItemStatReporter statReporter(1, 0, threadIdx_, acb_); + AsyncItemStatReporter statReporter(1, 0, acb_); try { //target existing: undefined behavior! (fail/overwrite) @@ -2184,27 +1870,28 @@ void FolderPairSyncer::synchronizeFolderInt(FolderPair& folder, SyncOperation sy else //source deleted meanwhile... { const SyncStatistics subStats(folder); - AsyncItemStatReporter statReporter(1 + getCUD(subStats), subStats.getBytesToProcess(), threadIdx_, acb_); + AsyncItemStatReporter statReporter(1 + getCUD(subStats), subStats.getBytesToProcess(), acb_); //even if the source item does not exist anymore, significant I/O work was done => report statReporter.reportDelta(1, 0); - reportInfo(txtSourceItemNotFound_, AFS::getDisplayPath(folder.getAbstractPath<sideSrc>())); //remove only *after* evaluating folder!! folder.refSubFiles ().clear(); // folder.refSubLinks ().clear(); //update FolderPair folder.refSubFolders().clear(); // folder.removeObject<sideSrc>(); // + + reportInfo(txtSourceItemNotFound_, AFS::getDisplayPath(folder.getAbstractPath<sideSrc>())); //throw ThreadInterruption } } break; case SO_DELETE_LEFT: case SO_DELETE_RIGHT: - reportInfo(delHandlingTrg.getTxtRemovingFolder(), AFS::getDisplayPath(folder.getAbstractPath<sideTrg>())); + reportInfo(delHandlingTrg.getTxtRemovingFolder(), AFS::getDisplayPath(folder.getAbstractPath<sideTrg>())); //throw ThreadInterruption { const SyncStatistics subStats(folder); //counts sub-objects only! - AsyncItemStatReporter statReporter(1 + getCUD(subStats), subStats.getBytesToProcess(), threadIdx_, acb_); + AsyncItemStatReporter statReporter(1 + getCUD(subStats), subStats.getBytesToProcess(), acb_); delHandlingTrg.removeDirWithCallback(folder.getAbstractPath<sideTrg>(), folder.getPairRelativePath(), statReporter, singleThread_); //throw FileError, X @@ -2221,9 +1908,9 @@ void FolderPairSyncer::synchronizeFolderInt(FolderPair& folder, SyncOperation sy case SO_OVERWRITE_RIGHT: // case SO_COPY_METADATA_TO_LEFT: case SO_COPY_METADATA_TO_RIGHT: - reportInfo(txtUpdatingAttributes_, AFS::getDisplayPath(folder.getAbstractPath<sideTrg>())); + reportInfo(txtUpdatingAttributes_, AFS::getDisplayPath(folder.getAbstractPath<sideTrg>())); //throw ThreadInterruption { - AsyncItemStatReporter statReporter(1, 0, threadIdx_, acb_); + AsyncItemStatReporter statReporter(1, 0, acb_); assert(folder.getItemName<sideTrg>() != folder.getItemName<sideSrc>()); if (folder.getItemName<sideTrg>() != folder.getItemName<sideSrc>()) //have difference in case? @@ -2280,7 +1967,11 @@ AFS::FileCopyResult FolderPairSyncer::copyFileWithCallback(const FileDescriptor& onDeleteTargetFile(); } }, - [&](int64_t bytesDelta) { statReporter.reportDelta(0, bytesDelta); }, //callback runs *outside* singleThread_ lock! => fine + [&](int64_t bytesDelta) //callback runs *outside* singleThread_ lock! => fine + { + statReporter.reportDelta(0, bytesDelta); + interruptionPoint(); //throw ThreadInterruption + }, singleThread_); //#################### Verification ############################# @@ -2289,7 +1980,7 @@ AFS::FileCopyResult FolderPairSyncer::copyFileWithCallback(const FileDescriptor& ZEN_ON_SCOPE_FAIL(try { parallel::removeFilePlain(targetPath, singleThread_); } catch (FileError&) {}); //delete target if verification fails - reportInfo(txtVerifyingFile_, AFS::getDisplayPath(targetPath)); + reportInfo(txtVerifyingFile_, AFS::getDisplayPath(targetPath)); //throw ThreadInterruption //callback runs *outside* singleThread_ lock! => fine auto verifyCallback = [&](int64_t bytesDelta) { interruptionPoint(); /*throw ThreadInterruption*/ }; @@ -2312,28 +2003,32 @@ bool baseFolderDrop(BaseFolderPair& baseFolder, int folderAccessTimeout, Process const AbstractPath folderPath = baseFolder.getAbstractPath<side>(); if (baseFolder.isAvailable<side>()) - if (Opt<std::wstring> errMsg = tryReportingError([&] { - const FolderStatus status = getFolderStatusNonBlocking({ folderPath }, {} /*deviceParallelOps*/, - folderAccessTimeout, false /*allowUserInteraction*/, callback); + const std::wstring errMsg = tryReportingError([&] + { + const FolderStatus status = getFolderStatusNonBlocking({ folderPath }, {} /*deviceParallelOps*/, + folderAccessTimeout, false /*allowUserInteraction*/, callback); - static_assert(IsSameType<decltype(status.failedChecks.begin()->second), FileError>::value, ""); + static_assert(std::is_same_v<decltype(status.failedChecks.begin()->second), FileError>); if (!status.failedChecks.empty()) throw status.failedChecks.begin()->second; if (status.existing.find(folderPath) == status.existing.end()) throw FileError(replaceCpy(_("Cannot find folder %x."), L"%x", fmtPath(AFS::getDisplayPath(folderPath)))); //should really be logged as a "fatal error" if ignored by the user... - }, callback)) //throw X - return true; + }, callback); //throw X + if (!errMsg.empty()) + return true; + } return false; } template <SelectedSide side> //create base directories first (if not yet existing) -> no symlink or attribute copying! -bool createBaseFolder(BaseFolderPair& baseFolder, int folderAccessTimeout, ProcessCallback& callback) //return false if fatal error occurred +bool createBaseFolder(BaseFolderPair& baseFolder, bool copyFilePermissions, int folderAccessTimeout, ProcessCallback& callback) //return false if fatal error occurred { + static const SelectedSide sideSrc = OtherSide<side>::value; const AbstractPath baseFolderPath = baseFolder.getAbstractPath<side>(); if (AFS::isNullPath(baseFolderPath)) @@ -2342,18 +2037,28 @@ bool createBaseFolder(BaseFolderPair& baseFolder, int folderAccessTimeout, Proce if (!baseFolder.isAvailable<side>()) //create target directory: user presumably ignored error "dir existing" in order to have it created automatically { bool temporaryNetworkDrop = false; - zen::Opt<std::wstring> errMsg = tryReportingError([&] + const std::wstring errMsg = tryReportingError([&] { const FolderStatus status = getFolderStatusNonBlocking({ baseFolderPath }, {} /*deviceParallelOps*/, folderAccessTimeout, false /*allowUserInteraction*/, callback); - static_assert(IsSameType<decltype(status.failedChecks.begin()->second), FileError>::value, ""); + static_assert(std::is_same_v<decltype(status.failedChecks.begin()->second), FileError>); if (!status.failedChecks.empty()) throw status.failedChecks.begin()->second; if (status.notExisting.find(baseFolderPath) != status.notExisting.end()) { - AFS::createFolderIfMissingRecursion(baseFolderPath); //throw FileError + if (baseFolder.isAvailable<sideSrc>()) //copy file permissions + { + if (Opt<AbstractPath> parentPath = AFS::getParentFolderPath(baseFolderPath)) + if (AFS::getParentFolderPath(*parentPath)) //not device root + AFS::createFolderIfMissingRecursion(*parentPath); //throw FileError + + AFS::copyNewFolder(baseFolder.getAbstractPath<sideSrc>(), baseFolderPath, copyFilePermissions); //throw FileError + } + else + AFS::createFolderIfMissingRecursion(baseFolderPath); //throw FileError + baseFolder.setAvailable<side>(true); //update our model! } else @@ -2369,7 +2074,7 @@ bool createBaseFolder(BaseFolderPair& baseFolder, int folderAccessTimeout, Proce // 3. log file creates containing folder -> no, log only created in batch mode, and only *before* comparison } }, callback); //throw X - return !errMsg && !temporaryNetworkDrop; + return errMsg.empty() && !temporaryNetworkDrop; } return true; } @@ -2434,7 +2139,7 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime } catch (const FileError& e) //not an error in this context { - callback.reportInfo(e.toString()); //may throw! + callback.reportInfo(e.toString()); //throw X } //prevent operating system going into sleep state @@ -2445,7 +2150,7 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime } catch (const FileError& e) //not an error in this context { - callback.reportInfo(e.toString()); //may throw! + callback.reportInfo(e.toString()); //throw X } //-------------------execute basic checks all at once before starting sync-------------------------------------- @@ -2600,7 +2305,7 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime if (!AFS::isNullPath(baseFolderPath)) if (recyclerSupported.find(baseFolderPath) == recyclerSupported.end()) //perf: avoid duplicate checks! { - callback.reportStatus(replaceCpy(_("Checking recycle bin availability for folder %x..."), L"%x", + callback.reportStatus(replaceCpy(_("Checking recycle bin availability for folder %x..."), L"%x", //throw X fmtPath(AFS::getDisplayPath(baseFolderPath)))); bool recSupported = false; tryReportingError([&] @@ -2745,7 +2450,7 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime continue; //------------------------------------------------------------------------------------------ - callback.reportInfo(_("Synchronizing folder pair:") + L" " + getVariantNameForLog(folderPairCfg.syncVariant) + L"\n" + + callback.reportInfo(_("Synchronizing folder pair:") + L" " + getVariantNameForLog(folderPairCfg.syncVariant) + L"\n" + //throw X L" " + AFS::getDisplayPath(baseFolder.getAbstractPath< LEFT_SIDE>()) + L"\n" + L" " + AFS::getDisplayPath(baseFolder.getAbstractPath<RIGHT_SIDE>())); //------------------------------------------------------------------------------------------ @@ -2757,8 +2462,8 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime //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, folderAccessTimeout, callback) || //+ detect temporary network drop!! - !createBaseFolder<RIGHT_SIDE>(baseFolder, folderAccessTimeout, callback)) // + if (!createBaseFolder< LEFT_SIDE>(baseFolder, copyFilePermissions, folderAccessTimeout, callback) || //+ detect temporary network drop!! + !createBaseFolder<RIGHT_SIDE>(baseFolder, copyFilePermissions, folderAccessTimeout, callback)) // continue; //------------------------------------------------------------------------------------------ @@ -2864,7 +2569,7 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime //(try to gracefully) write database file if (folderPairCfg.saveSyncDB) { - callback.reportStatus(_("Generating database...")); + callback.reportStatus(_("Generating database...")); //throw X callback.forceUiRefresh(); //throw X tryReportingError([&] diff --git a/FreeFileSync/Source/synchronization.h b/FreeFileSync/Source/base/synchronization.h index 6e09709c..4e5173c5 100755 --- a/FreeFileSync/Source/synchronization.h +++ b/FreeFileSync/Source/base/synchronization.h @@ -9,7 +9,7 @@ #include <chrono> #include "file_hierarchy.h" -#include "lib/process_xml.h" +#include "process_xml.h" #include "process_callback.h" diff --git a/FreeFileSync/Source/lib/versioning.cpp b/FreeFileSync/Source/base/versioning.cpp index 9fb2804f..e3972d9f 100755 --- a/FreeFileSync/Source/lib/versioning.cpp +++ b/FreeFileSync/Source/base/versioning.cpp @@ -1,5 +1,5 @@ #include "versioning.h" -#include <cstddef> //required by GCC 4.8.1 to find ptrdiff_t +//#include <cstddef> //required by GCC 4.8.1 to find ptrdiff_t using namespace zen; using namespace fff; @@ -114,7 +114,7 @@ void moveExistingItemToVersioning(const AbstractPath& sourcePath, const Abstract const AFS::PathStatus& ps = *psTmp; //previous exception contains context-level information, but current exception is the immediate problem => combine both //=> e.g. prevEx might be about missing parent folder; FFS considers session faulty and tries to create a new one, - //which might fail with: LIBSSH2_ERROR_AUTHENTICATION_FAILED (due to limit on #sessions?) https://www.freefilesync.org/forum/viewtopic.php?t=4765#p16016 + //which might fail with: LIBSSH2_ERROR_AUTHENTICATION_FAILED (due to limit on #sessions?) https://freefilesync.org/forum/viewtopic.php?t=4765#p16016 if (ps.relPath.empty()) //already existing { @@ -280,8 +280,7 @@ void FileVersioner::revisionFolderImpl(const AbstractPath& folderPath, const Zst //create target directories only when needed in moveFileToVersioning(): avoid empty directories! auto ft = std::make_shared<FlatTraverserCallback>(folderPath); //traverse source directory one level deep - const AFS::PathComponents pc = AFS::getPathComponents(folderPath); - AFS::traverseFolderParallel(pc.rootPath, {{ pc.relPath, ft }}, 1 /*parallelOps*/); //throw FileError + AFS::traverseFolderParallel(folderPath, {{ {}, ft }}, 1 /*parallelOps*/); //throw FileError const Zstring relPathPf = appendSeparator(relativePath); diff --git a/FreeFileSync/Source/lib/versioning.h b/FreeFileSync/Source/base/versioning.h index 78a031a0..ca0e4869 100755 --- a/FreeFileSync/Source/lib/versioning.h +++ b/FreeFileSync/Source/base/versioning.h @@ -10,9 +10,9 @@ #include <functional> #include <zen/time.h> #include <zen/file_error.h> -#include "../structures.h" +#include "structures.h" +#include "algorithm.h" #include "../fs/abstract.h" -#include "../algorithm.h" namespace fff diff --git a/FreeFileSync/Source/fs/abstract.cpp b/FreeFileSync/Source/fs/abstract.cpp index 1b857161..34404fca 100755 --- a/FreeFileSync/Source/fs/abstract.cpp +++ b/FreeFileSync/Source/fs/abstract.cpp @@ -69,15 +69,12 @@ Opt<AfsPath> AFS::getParentAfsPath(const AfsPath& afsPath) } -void AFS::traverseFolderParallel(const AbstractPath& rootPath, const AFS::TraverserWorkload& workload, size_t parallelOps) +void AFS::traverseFolderParallel(const AbstractPath& basePath, const AFS::TraverserWorkload& workload, size_t parallelOps) { - warn_static("just glue to rootPath!") - assert(rootPath.afsPath.value.empty()); - TraverserWorkloadImpl wlImpl; for (const auto& item : workload) { - AfsPath afsPath; + AfsPath afsPath = basePath.afsPath; for (const Zstring& itemName : item.first) { assert(!contains(itemName, FILE_NAME_SEPARATOR)); @@ -87,7 +84,7 @@ void AFS::traverseFolderParallel(const AbstractPath& rootPath, const AFS::Traver } wlImpl.emplace_back(afsPath, item.second); } - rootPath.afs->traverseFolderParallel(wlImpl, parallelOps); //throw + basePath.afs->traverseFolderParallel(wlImpl, parallelOps); //throw } @@ -128,8 +125,8 @@ AFS::FileCopyResult AFS::copyFileAsStream(const AfsPath& afsPathSource, const St /* is setting modtime after closing the file handle a pessimization? Native: no, needed for functional correctness, see file_access.cpp - MTP: maybe a minor one (need to retrieve objectId one more time) - SFTP: no, needed for functional correctness, just as for Native + MTP: maybe a minor one (need to determine objectId one more time) + SFTP: no, needed for functional correctness (synology server), just as for Native FTP: maybe a minor one: could set modtime via CURLOPT_POSTQUOTE (but this would internally trigger an extra round-trip anyway!) */ setModTime(apTarget, attrSourceNew.modTime); //throw FileError, follows symlinks @@ -138,12 +135,11 @@ AFS::FileCopyResult AFS::copyFileAsStream(const AfsPath& afsPathSource, const St { /* Failing to set modification time is not a serious problem from synchronization perspective (treated like external update) - - => Support additional scenarios: - - GVFS failing to set modTime for FTP: https://www.freefilesync.org/forum/viewtopic.php?t=2372 - - GVFS failing to set modTime for MTP: https://www.freefilesync.org/forum/viewtopic.php?t=2803 - - MTP failing to set modTime in general: fail non-silently rather than silently during file creation - - FTP failing to set modTime for servers lacking MFMT-support + => 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 + - MTP failing to set modTime in general: fail non-silently rather than silently during file creation + - FTP failing to set modTime for servers lacking MFMT-support */ errorModTime = FileError(e.toString()); //avoid slicing } @@ -191,7 +187,7 @@ AFS::FileCopyResult AFS::copyFileTransactional(const AbstractPath& apSource, con const Zstring fileName = AFS::getItemName(apTarget); //- generate (hopefully) unique file name to avoid clashing with some remnant ffs_tmp file - //- do not loop and avoid pathological cases, e.g. https://www.freefilesync.org/forum/viewtopic.php?t=1592 + //- do not loop and avoid pathological cases, e.g. https://freefilesync.org/forum/viewtopic.php?t=1592 const Zstring shortGuid = printNumber<Zstring>(Zstr("%04x"), static_cast<unsigned int>(getCrc16(generateGUID()))); auto it = find_last(fileName.begin(), fileName.end(), Zchar('.')); //gracefully handle case of missing "." const Zstring fileNameTmp = Zstring(fileName.begin(), it) + Zchar('.') + shortGuid + TEMP_FILE_ENDING; @@ -377,8 +373,7 @@ void removeFolderIfExistsRecursionImpl(const AbstractPath& folderPath, //throw F //deferred recursion => save stack space and allow deletion of extremely deep hierarchies! auto ft = std::make_shared<FlatTraverserCallback>(folderPath); - const AFS::PathComponents pc = AFS::getPathComponents(folderPath); - AFS::traverseFolderParallel(pc.rootPath, {{ pc.relPath, ft }}, 1 /*parallelOps*/); //throw FileError + AFS::traverseFolderParallel(folderPath, {{ {}, ft }}, 1 /*parallelOps*/); //throw FileError for (const Zstring& fileName : ft->refFileNames()) { diff --git a/FreeFileSync/Source/fs/abstract.h b/FreeFileSync/Source/fs/abstract.h index 9774aead..9ca897f7 100755 --- a/FreeFileSync/Source/fs/abstract.h +++ b/FreeFileSync/Source/fs/abstract.h @@ -11,7 +11,6 @@ #include <zen/file_error.h> #include <zen/zstring.h> #include <zen/optional.h> -#include <zen/thread.h> #include <zen/serialize.h> //InputStream/OutputStream support buffered stream concept #include <wx+/image_holder.h> //NOT a wxWidgets dependency! @@ -226,7 +225,7 @@ struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model t using TraverserWorkload = std::vector<std::pair<std::vector<Zstring> /*relPath*/, std::shared_ptr<TraverserCallback> /*throw X*/>>; //- client needs to handle duplicate file reports! (FilePlusTraverser fallback, retrying to read directory contents, ...) - static void traverseFolderParallel(const AbstractPath& rootPath, const TraverserWorkload& workload, size_t parallelOps); + static void traverseFolderParallel(const AbstractPath& basePath, const TraverserWorkload& workload, size_t parallelOps); //---------------------------------------------------------------------------------------------------------------- @@ -393,227 +392,6 @@ inline bool operator==(const AbstractPath& lhs, const AbstractPath& rhs) { retur inline bool operator!=(const AbstractPath& lhs, const AbstractPath& rhs) { return !(lhs == rhs); } -//implement "retry" in a generic way: -template <class Command> inline //function object expecting to throw FileError if operation fails -bool tryReportingDirError(Command cmd, AbstractFileSystem::TraverserCallback& callback) //throw X, return "true" on success, "false" if error was ignored -{ - for (size_t retryNumber = 0;; ++retryNumber) - try - { - cmd(); //throw FileError - return true; - } - catch (const zen::FileError& e) - { - switch (callback.reportDirError(e.toString(), retryNumber)) //throw X - { - case AbstractFileSystem::TraverserCallback::ON_ERROR_RETRY: - break; - case AbstractFileSystem::TraverserCallback::ON_ERROR_CONTINUE: - return false; - } - } -} - - -template <class Command> inline //function object expecting to throw FileError if operation fails -bool tryReportingItemError(Command cmd, AbstractFileSystem::TraverserCallback& callback, const Zstring& itemName) //throw X, return "true" on success, "false" if error was ignored -{ - for (size_t retryNumber = 0;; ++retryNumber) - try - { - cmd(); //throw FileError - return true; - } - catch (const zen::FileError& e) - { - switch (callback.reportItemError(e.toString(), retryNumber, itemName)) //throw X - { - case AbstractFileSystem::TraverserCallback::ON_ERROR_RETRY: - break; - case AbstractFileSystem::TraverserCallback::ON_ERROR_CONTINUE: - return false; - } - } -} - - -#if __GNUC__ < 6 //support for "fold expressions" requires GCC 6.0 or later: http://en.cppreference.com/w/cpp/compiler_support - #define ZEN_LINUX_TRAVERSER_LEGACY -#else - #define ZEN_LINUX_TRAVERSER_MODERN -#endif - -#if __GNUC__ == 6 //std::apply available with GCC 7 -} -namespace std -{ -template <class F, class T0, class T1, class T2> -constexpr decltype(auto) apply(F&& f, std::tuple<T0, T1, T2>& t) { return std::invoke(std::forward<F>(f), std::get<0>(t), std::get<1>(t), std::get<2>(t)); } -} -namespace fff -{ -#endif - - -#if !defined ZEN_LINUX || defined ZEN_LINUX_TRAVERSER_MODERN -template <class Function> -struct WorkItem -{ - Function getResult; //throw FileError - Zstring errorItemName; //empty if all items affected - size_t errorRetryCount = 0; - std::shared_ptr<AbstractFileSystem::TraverserCallback> cb; //call by controlling thread only! => don't require traverseFolderParallel() callbacks to be thread-safe! -}; - -template <class Function> -struct ResultItem -{ - WorkItem<Function> wi; - std::exception_ptr error; //mutually exclusive - decltype(wi.getResult()) value; // -}; - - -template <class... Functions> //avoid std::function memory alloc + virtual calls -class AsyncTraverserWorkload -{ -public: - AsyncTraverserWorkload() {} - - //context of controlling thread, non-blocking: - //NOTE: AsyncTraverserWorkload must out-live threadGroup!!! - template <class Function> - void run(WorkItem<Function>&& wi, zen::ThreadGroup<std::function<void()>>& threadGroup) - { - threadGroup.run([this, wi] - { - try { this->returnResult<Function>({ wi, nullptr, wi.getResult() }); } //throw FileError - catch (...) { this->returnResult<Function>({ wi, std::current_exception(), {} }); } - }); - - std::lock_guard<std::mutex> dummy(lockWork_); - ++workItemsInProcess_; - } - - //context of controlling thread, blocking: - bool getResults(std::tuple<std::vector<ResultItem<Functions>>...>& results) //returns false when traversal is finished - { - std::apply([](auto&... r) { (..., r.clear()); }, results); - - std::unique_lock<std::mutex> dummy(lockWork_); - - auto resultsReady = [&] - { - bool ready = false; - std::apply([&ready](const auto&... r) { ready = (... || !r.empty()); }, results_); - return ready; - }; - - if (!resultsReady() && workItemsInProcess_ == 0) - return false; - - conditionNewResult_.wait(dummy, [&resultsReady] { return resultsReady(); }); - - results.swap(results_); //reuse memory + avoid needless item-level mutex locking - return true; - } - -private: - AsyncTraverserWorkload (const AsyncTraverserWorkload&) = delete; - AsyncTraverserWorkload& operator=(const AsyncTraverserWorkload&) = delete; - - //context of worker threads, non-blocking: - template <class Function> - void returnResult(ResultItem<Function>&& r) - { - { - std::lock_guard<std::mutex> dummy(lockWork_); - - std::get<std::vector<ResultItem<Function>>>(results_).push_back(std::move(r)); - --workItemsInProcess_; - } - conditionNewResult_.notify_all(); - } - - std::mutex lockWork_; - size_t workItemsInProcess_ = 0; - std::tuple<std::vector<ResultItem<Functions>>...> results_; - std::condition_variable conditionNewResult_; -}; - - -template <class... Functions> -class GenericDirTraverser -{ -public: - using Function1 = typename zen::GetFirstOf<Functions...>::Type; - - GenericDirTraverser(std::vector<WorkItem<Function1>>&& initialWorkItems, size_t parallelOps, const std::string& threadGroupName) : - threadGroup_(std::max<size_t>(1, parallelOps), threadGroupName) - { - //set the initial work load - for (auto& item : initialWorkItems) - asyncWorkload_.template run<Function1>(std::move(item), threadGroup_); - - //run loop - std::tuple<std::vector<ResultItem<Functions>>...> results; //avoid per-getNextResults() memory allocations (=> swap instead!) - - while (asyncWorkload_.getResults(results)) - std::apply([&](auto&... r) { (..., this->evalResultList(r)); }, results); //throw X - } - -private: - GenericDirTraverser (const GenericDirTraverser&) = delete; - GenericDirTraverser& operator=(const GenericDirTraverser&) = delete; - - template <class Function> - void evalResultList(std::vector<ResultItem<Function>>& results) //throw X - { - for (ResultItem<Function>& result : results) - evalResult(result); //throw X - } - - template <class Function> - void evalResult(ResultItem<Function>& result); //throw X - - //specialize! - template <class Function> - void evalResultValue(const typename Function::Result& r, std::shared_ptr<AbstractFileSystem::TraverserCallback>& cb); //throw X - - AsyncTraverserWorkload<Functions...> asyncWorkload_; //ATTENTION: asyncWorkload_ must out-live threadGroup_!!! - zen::ThreadGroup<std::function<void()>> threadGroup_; // -}; - - -template <class... Functions> -template <class Function> -void GenericDirTraverser<Functions...>::evalResult(ResultItem<Function>& result) //throw X -{ - auto& cb = result.wi.cb; - try - { - if (result.error) - std::rethrow_exception(result.error); //throw FileError - } - catch (const zen::FileError& e) - { - switch (result.wi.errorItemName.empty() ? - cb->reportDirError (e.toString(), result.wi.errorRetryCount) : //throw X - cb->reportItemError(e.toString(), result.wi.errorRetryCount, result.wi.errorItemName)) //throw X - { - case AbstractFileSystem::TraverserCallback::ON_ERROR_RETRY: - asyncWorkload_.template run<Function>({ std::move(result.wi.getResult), result.wi.errorItemName, result.wi.errorRetryCount + 1, cb }, threadGroup_); - return; - - case AbstractFileSystem::TraverserCallback::ON_ERROR_CONTINUE: - return; - } - } - - evalResultValue<Function>(result.value, result.wi.cb); //throw X -} -#endif diff --git a/FreeFileSync/Source/fs/concrete_impl.h b/FreeFileSync/Source/fs/concrete_impl.h new file mode 100644 index 00000000..5ad9da54 --- /dev/null +++ b/FreeFileSync/Source/fs/concrete_impl.h @@ -0,0 +1,211 @@ +// ***************************************************************************** +// * 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 IMPL_HELPER_H_873450978453042524534234 +#define IMPL_HELPER_H_873450978453042524534234 + +#include "abstract.h" +#include <zen/thread.h> + + +namespace fff +{ +template <class Function> inline //return ignored error message if available +std::wstring tryReportingDirError(Function cmd /*throw FileError*/, AbstractFileSystem::TraverserCallback& cb /*throw X*/) +{ + for (size_t retryNumber = 0;; ++retryNumber) + try + { + cmd(); //throw FileError + return std::wstring(); + } + catch (const zen::FileError& e) + { + assert(!e.toString().empty()); + switch (cb.reportDirError(e.toString(), retryNumber)) //throw X + { + case AbstractFileSystem::TraverserCallback::ON_ERROR_CONTINUE: + return e.toString(); + case AbstractFileSystem::TraverserCallback::ON_ERROR_RETRY: + break; //continue with loop + } + } +} + + +template <class Context, class Function> +struct Task +{ + Function getResult; //throw FileError + /* [[no_unique_address]] */ Context ctx; +}; + + +template <class Context, class Function> +struct TaskResult +{ + Task<Context, Function> wi; + std::exception_ptr error; //mutually exclusive + decltype(wi.getResult()) value; // +}; + +enum class SchedulerStatus +{ + HAVE_RESULT, + FINISHED, +}; + +template <class Context, class... Functions> //avoid std::function memory alloc + virtual calls +class TaskScheduler +{ +public: + TaskScheduler(size_t threadCount, const std::string& groupName) : + threadGroup_(zen::ThreadGroup<std::function<void()>>(threadCount, groupName)) {} + + ~TaskScheduler() { threadGroup_ = zen::NoValue(); } //TaskScheduler must out-live threadGroup! (captured "this") + + //context of controlling thread, non-blocking: + template <class Function> + void run(Task<Context, Function>&& wi) + { + threadGroup_->run([this, wi = std::move(wi)] + { + try { this->returnResult<Function>({ wi, nullptr, wi.getResult() }); } //throw FileError + catch (...) { this->returnResult<Function>({ wi, std::current_exception(), {} }); } + }); + + std::lock_guard<std::mutex> dummy(lockResult_); + ++resultsPending_; + } + + //context of controlling thread, blocking: + SchedulerStatus getResults(std::tuple<std::vector<TaskResult<Context, Functions>>...>& results) + { + std::apply([](auto&... r) { (..., r.clear()); }, results); + + std::unique_lock<std::mutex> dummy(lockResult_); + + auto resultsReady = [&] + { + bool ready = false; + std::apply([&ready](const auto&... r) { ready = (... || !r.empty()); }, results_); + return ready; + }; + + if (!resultsReady() && resultsPending_ == 0) + return SchedulerStatus::FINISHED; + + conditionNewResult_.wait(dummy, [&resultsReady] { return resultsReady(); }); + + results.swap(results_); //reuse memory + avoid needless item-level mutex locking + return SchedulerStatus::HAVE_RESULT; + } + +private: + TaskScheduler (const TaskScheduler&) = delete; + TaskScheduler& operator=(const TaskScheduler&) = delete; + + //context of worker threads, non-blocking: + template <class Function> + void returnResult(TaskResult<Context, Function>&& r) + { + { + std::lock_guard<std::mutex> dummy(lockResult_); + + std::get<std::vector<TaskResult<Context, Function>>>(results_).push_back(std::move(r)); + --resultsPending_; + } + conditionNewResult_.notify_all(); + } + + zen::Opt<zen::ThreadGroup<std::function<void()>>> threadGroup_; + + std::mutex lockResult_; + size_t resultsPending_ = 0; + std::tuple<std::vector<TaskResult<Context, Functions>>...> results_; + std::condition_variable conditionNewResult_; +}; + + +struct TravContext +{ + Zstring errorItemName; //empty if all items affected + size_t errorRetryCount = 0; + std::shared_ptr<AbstractFileSystem::TraverserCallback> cb; //call by controlling thread only! => don't require traverseFolderParallel() callbacks to be thread-safe! +}; + + +template <class... Functions> +class GenericDirTraverser +{ +public: + using Function1 = zen::GetFirstOfT<Functions...>; + + GenericDirTraverser(std::vector<Task<TravContext, Function1>>&& initialTasks, size_t parallelOps, const std::string& threadGroupName) : + scheduler_(parallelOps, threadGroupName) + { + //set the initial work load + for (auto& item : initialTasks) + scheduler_.template run<Function1>(std::move(item)); + + //run loop + std::tuple<std::vector<TaskResult<TravContext, Functions>>...> results; //avoid per-getNextResults() memory allocations (=> swap instead!) + + while (scheduler_.getResults(results) == SchedulerStatus::HAVE_RESULT) + std::apply([&](auto&... r) { (..., this->evalResultList(r)); }, results); //throw X + } + +private: + GenericDirTraverser (const GenericDirTraverser&) = delete; + GenericDirTraverser& operator=(const GenericDirTraverser&) = delete; + + template <class Function> + void evalResultList(std::vector<TaskResult<TravContext, Function>>& results) //throw X + { + for (TaskResult<TravContext, Function>& result : results) + evalResult(result); //throw X + } + + template <class Function> + void evalResult(TaskResult<TravContext, Function>& result); //throw X + + //specialize! + template <class Function> + void evalResultValue(const typename Function::Result& r, std::shared_ptr<AbstractFileSystem::TraverserCallback>& cb); //throw X + + TaskScheduler<TravContext, Functions...> scheduler_; +}; + + +template <class... Functions> +template <class Function> +void GenericDirTraverser<Functions...>::evalResult(TaskResult<TravContext, Function>& result) //throw X +{ + auto& cb = result.wi.ctx.cb; + try + { + if (result.error) + std::rethrow_exception(result.error); //throw FileError + } + catch (const zen::FileError& e) + { + switch (result.wi.ctx.errorItemName.empty() ? + cb->reportDirError (e.toString(), result.wi.ctx.errorRetryCount) : //throw X + cb->reportItemError(e.toString(), result.wi.ctx.errorRetryCount, result.wi.ctx.errorItemName)) //throw X + { + case AbstractFileSystem::TraverserCallback::ON_ERROR_RETRY: + scheduler_.template run<Function>({ std::move(result.wi.getResult), TravContext{ result.wi.ctx.errorItemName, result.wi.ctx.errorRetryCount + 1, cb }}); + return; + + case AbstractFileSystem::TraverserCallback::ON_ERROR_CONTINUE: + return; + } + } + evalResultValue<Function>(result.value, cb); //throw X +} +} + +#endif //IMPL_HELPER_H_873450978453042524534234 diff --git a/FreeFileSync/Source/fs/native.cpp b/FreeFileSync/Source/fs/native.cpp index 864b1369..de8e0e7c 100755 --- a/FreeFileSync/Source/fs/native.cpp +++ b/FreeFileSync/Source/fs/native.cpp @@ -14,8 +14,9 @@ #include <zen/thread.h> #include <zen/guid.h> #include <zen/crc.h> -#include "../lib/resolve_path.h" -#include "../lib/icon_loader.h" +#include "concrete_impl.h" +#include "../base/resolve_path.h" +#include "../base/icon_loader.h" #include <cstddef> //offsetof @@ -49,7 +50,6 @@ AFS::FileId convertToAbstractFileId(const zen::FileId& fid) } -#if !defined ZEN_LINUX || defined ZEN_LINUX_TRAVERSER_MODERN struct FsItemRaw { Zstring itemName; @@ -198,13 +198,13 @@ private: }; -void traverseFolderParallelNative(const std::vector<std::pair<Zstring, std::shared_ptr<AFS::TraverserCallback>>>& initialWorkItems, size_t parallelOps) //throw X +void traverseFolderParallelNative(const std::vector<std::pair<Zstring, std::shared_ptr<AFS::TraverserCallback>>>& initialTasks, size_t parallelOps) //throw X { - std::vector<WorkItem<GetDirDetails>> genItems; + std::vector<Task<TravContext, GetDirDetails>> genItems; - for (const auto& item : initialWorkItems) + for (const auto& item : initialTasks) genItems.push_back({ GetDirDetails(item.first), - Zstring() /*errorItemName*/, 0 /*errorRetryCount*/, item.second /*TraverserCallback*/ }); + TravContext{ Zstring() /*errorItemName*/, 0 /*errorRetryCount*/, item.second /*TraverserCallback*/ }}); GenericDirTraverser<GetDirDetails, GetItemDetails, GetLinkTargetDetails>(std::move(genItems), parallelOps, "Native Traverser"); //throw X } @@ -217,8 +217,7 @@ template <> void GenericDirTraverser<GetDirDetails, GetItemDetails, GetLinkTargetDetails>::evalResultValue<GetDirDetails>(const GetDirDetails::Result& r, std::shared_ptr<AFS::TraverserCallback>& cb) //throw X { for (const FsItemRaw& rawItem : r) - asyncWorkload_.run<GetItemDetails>({ GetItemDetails(rawItem), - rawItem.itemName, 0 /*errorRetryCount*/, cb }, threadGroup_); + scheduler_.run<GetItemDetails>({ GetItemDetails(rawItem), TravContext{ rawItem.itemName, 0 /*errorRetryCount*/, cb }}); } @@ -234,16 +233,14 @@ void GenericDirTraverser<GetDirDetails, GetItemDetails, GetLinkTargetDetails>::e case ItemType::FOLDER: if (std::shared_ptr<AFS::TraverserCallback> cbSub = cb->onFolder({ r.raw.itemName, nullptr /*symlinkInfo*/ })) //throw X - asyncWorkload_.run<GetDirDetails>({ GetDirDetails(r.raw.itemPath), - Zstring() /*errorItemName*/, 0 /*errorRetryCount*/, std::move(cbSub) }, threadGroup_); + scheduler_.run<GetDirDetails>({ GetDirDetails(r.raw.itemPath), TravContext{ Zstring() /*errorItemName*/, 0 /*errorRetryCount*/, std::move(cbSub) }}); break; case ItemType::SYMLINK: switch (cb->onSymlink({ r.raw.itemName, r.details.modTime })) //throw X { case AFS::TraverserCallback::LINK_FOLLOW: - asyncWorkload_.run<GetLinkTargetDetails>({ GetLinkTargetDetails(r.raw, r.details), - r.raw.itemName, 0 /*errorRetryCount*/, cb }, threadGroup_); + scheduler_.run<GetLinkTargetDetails>({ GetLinkTargetDetails(r.raw, r.details), TravContext{ r.raw.itemName, 0 /*errorRetryCount*/, cb }}); break; case AFS::TraverserCallback::LINK_SKIP: @@ -265,8 +262,7 @@ void GenericDirTraverser<GetDirDetails, GetItemDetails, GetLinkTargetDetails>::e if (r.target.type == ItemType::FOLDER) { if (std::shared_ptr<AFS::TraverserCallback> cbSub = cb->onFolder({ r.raw.itemName, &linkInfo })) //throw X - asyncWorkload_.run<GetDirDetails>({ GetDirDetails(r.raw.itemPath), - Zstring() /*errorItemName*/, 0 /*errorRetryCount*/, std::move(cbSub) }, threadGroup_); + scheduler_.run<GetDirDetails>({ GetDirDetails(r.raw.itemPath), TravContext{ Zstring() /*errorItemName*/, 0 /*errorRetryCount*/, std::move(cbSub) }}); } else //a file or named pipe, ect. cb->onFile({ r.raw.itemName, r.target.fileSize, r.target.modTime, convertToAbstractFileId(r.target.fileId), &linkInfo }); //throw X @@ -275,165 +271,6 @@ void GenericDirTraverser<GetDirDetails, GetItemDetails, GetLinkTargetDetails>::e namespace { -#elif defined ZEN_LINUX && defined ZEN_LINUX_TRAVERSER_LEGACY //support legacy GCC versions < 6 -static_assert(__GNUC__ < 6, ""); - -class LegacyDirTraverser -{ -public: - static void execute(const std::vector<std::pair<Zstring, std::shared_ptr<AFS::TraverserCallback>>>& initialWorkItems, size_t parallelOps) - { - assert(parallelOps == 1); - //Parallel operations > 1 are not supported because FreeFileSync was built with GCC < version 6.", - // => at least parallel_scan.h will show the correct number of threads - - for (const auto& item : initialWorkItems) - LegacyDirTraverser(item.first, *item.second); //throw X - } - -private: - LegacyDirTraverser (const LegacyDirTraverser&) = delete; - LegacyDirTraverser& operator=(const LegacyDirTraverser&) = delete; - - LegacyDirTraverser(const Zstring& baseDirPath, AFS::TraverserCallback& cb) - { - //set the initial work load - workload_.push_back({ baseDirPath, std::shared_ptr<AFS::TraverserCallback>(&cb, [](AFS::TraverserCallback*) {}) }); - - while (!workload_.empty()) - { - WorkItem wi = std::move(workload_. back()); //yes, no strong exception guarantee (std::bad_alloc) - /**/ workload_.pop_back(); // - - tryReportingDirError([&] //throw X - { - traverseWithException(wi.dirPath, *wi.cb); //throw FileError, X - }, *wi.cb); - } - } - - void traverseWithException(const Zstring& dirPath, AFS::TraverserCallback& cb) //throw FileError, X - { - //no need to check for endless recursion: - //1. Linux has a fixed limit on the number of symbolic links in a path - //2. fails with "too many open files" or "path too long" before reaching stack overflow - - DIR* folder = ::opendir(dirPath.c_str()); //directory must NOT end with path separator, except "/" - if (!folder) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot open directory %x."), L"%x", fmtPath(dirPath)), L"opendir"); - ZEN_ON_SCOPE_EXIT(::closedir(folder)); //never close nullptr handles! -> crash - - for (;;) - { - /* - Linux: - http://man7.org/linux/man-pages/man3/readdir_r.3.html - "It is recommended that applications use readdir(3) instead of readdir_r" - "... in modern implementations (including the glibc implementation), concurrent calls to readdir(3) that specify different directory streams are thread-safe" - - 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); - if (!dirEntry) - { - if (errno == 0) //errno left unchanged => no more items - return; - - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), L"readdir"); - //don't retry but restart dir traversal on error! https://blogs.msdn.microsoft.com/oldnewthing/20140612-00/?p=753/ - } - - const char* itemNameRaw = dirEntry->d_name; //evaluate dirEntry *before* going into recursion - - //skip "." and ".." - if (itemNameRaw[0] == '.' && - (itemNameRaw[1] == 0 || (itemNameRaw[1] == '.' && itemNameRaw[2] == 0))) - continue; - - const Zstring& itemName = itemNameRaw; - if (itemName.empty()) //checks result of normalizeUtfForPosix, too! - throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), L"readdir: Data corruption; item with empty name."); - - const Zstring& itemPath = appendSeparator(dirPath) + itemName; - - struct ::stat statData = {}; - if (!tryReportingItemError([&] //throw X - { - if (::lstat(itemPath.c_str(), &statData) != 0) //lstat() does not resolve symlinks - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"lstat"); - }, cb, itemName)) - continue; //ignore error: skip file - - if (S_ISLNK(statData.st_mode)) //on Linux there is no distinction between file and directory symlinks! - { - const AFS::TraverserCallback::SymlinkInfo linkInfo = { itemName, statData.st_mtime }; - - switch (cb.onSymlink(linkInfo)) //throw X - { - case AFS::TraverserCallback::LINK_FOLLOW: - { - //try to resolve symlink (and report error on failure!!!) - struct ::stat statDataTrg = {}; - - const bool validLink = tryReportingItemError([&] //throw X - { - if (::stat(itemPath.c_str(), &statDataTrg) != 0) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(itemPath)), L"stat"); - }, cb, itemName); - - if (validLink) - { - if (S_ISDIR(statDataTrg.st_mode)) //a directory - { - if (std::shared_ptr<AFS::TraverserCallback> trav = cb.onFolder({ itemName, &linkInfo })) //throw X - workload_.push_back({ itemPath, std::move(trav) }); - } - else //a file or named pipe, ect. - { - AFS::TraverserCallback::FileInfo fi = { itemName, makeUnsigned(statDataTrg.st_size), statDataTrg.st_mtime, convertToAbstractFileId(extractFileId(statDataTrg)), &linkInfo }; - cb.onFile(fi); //throw X - } - } - // else //broken symlink -> ignore: it's client's responsibility to handle error! - } - break; - - case AFS::TraverserCallback::LINK_SKIP: - break; - } - } - else if (S_ISDIR(statData.st_mode)) //a directory - { - if (std::shared_ptr<AFS::TraverserCallback> trav = cb.onFolder({ itemName, nullptr })) //throw X - workload_.push_back({ itemPath, std::move(trav) }); - warn_static("workload_ item order is reversed => fix!?") - } - else //a file or named pipe, ect. => dont't check using S_ISREG(): see comment in file_traverser.cpp - { - AFS::TraverserCallback::FileInfo fi = { itemName, makeUnsigned(statData.st_size), statData.st_mtime, convertToAbstractFileId(extractFileId(statData)), nullptr /*symlinkInfo*/ }; - cb.onFile(fi); //throw X - } - } - } - - struct WorkItem - { - Zstring dirPath; - std::shared_ptr<AFS::TraverserCallback> cb; - }; - - std::vector<WorkItem> workload_; -}; - - -void traverseFolderParallelNative(const std::vector<std::pair<Zstring, std::shared_ptr<AFS::TraverserCallback>>>& initialWorkItems, size_t parallelOps) //throw X -{ - LegacyDirTraverser::execute(initialWorkItems, parallelOps); //throw X -} -#endif //==================================================================================================== //==================================================================================================== diff --git a/FreeFileSync/Source/lib/status_handler_impl.h b/FreeFileSync/Source/lib/status_handler_impl.h deleted file mode 100755 index 6404c915..00000000 --- a/FreeFileSync/Source/lib/status_handler_impl.h +++ /dev/null @@ -1,92 +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_07682758976 -#define STATUS_HANDLER_IMPL_H_07682758976 - -#include <zen/optional.h> -#include <zen/file_error.h> -#include "../process_callback.h" - - -namespace fff -{ -template <typename Function> inline -zen::Opt<std::wstring> tryReportingError(Function cmd, ProcessCallback& cb /*throw X*/) //return ignored error message if available -{ - for (size_t retryNumber = 0;; ++retryNumber) - try - { - cmd(); //throw FileError - return zen::NoValue(); - } - catch (zen::FileError& error) - { - switch (cb.reportError(error.toString(), retryNumber)) //throw X - { - case ProcessCallback::IGNORE_ERROR: - return error.toString(); - case ProcessCallback::RETRY: - break; //continue with loop - } - } -} - - -//manage statistics reporting for a single item of work -class ItemStatReporter -{ -public: - ItemStatReporter(int itemsExpected, int64_t bytesExpected, ProcessCallback& cb) : - itemsExpected_(itemsExpected), - bytesExpected_(bytesExpected), - cb_(cb) {} - - ~ItemStatReporter() - { - const bool scopeFail = getUncaughtExceptionCount() > exeptionCount_; - if (scopeFail) - cb_.updateDataTotal(itemsReported_, bytesReported_); //=> unexpected increase of total workload - else - //update statistics to consider the real amount of data, e.g. more than the "file size" for ADS streams, - //less for sparse and compressed files, or file changed in the meantime! - cb_.updateDataTotal(itemsReported_ - itemsExpected_, bytesReported_ - bytesExpected_); //noexcept! - } - - void reportStatus(const std::wstring& text) { cb_.reportStatus(text); } //throw X - - void reportDelta(int itemsDelta, int64_t bytesDelta) //throw X - { - cb_.updateDataProcessed(itemsDelta, bytesDelta); //nothrow! - itemsReported_ += itemsDelta; - bytesReported_ += bytesDelta; - - //special rule: avoid temporary statistics mess up, even though they are corrected anyway below: - if (itemsReported_ > itemsExpected_) - { - cb_.updateDataTotal(itemsReported_ - itemsExpected_, 0); - itemsReported_ = itemsExpected_; - } - if (bytesReported_ > bytesExpected_) - { - cb_.updateDataTotal(0, bytesReported_ - bytesExpected_); //=> everything above "bytesExpected" adds to both "processed" and "total" data - bytesReported_ = bytesExpected_; - } - - cb_.requestUiRefresh(); //throw X - } - -private: - int itemsReported_ = 0; - int64_t bytesReported_ = 0; - const int itemsExpected_; - const int64_t bytesExpected_; - ProcessCallback& cb_; - const int exeptionCount_ = getUncaughtExceptionCount(); -}; -} - -#endif //STATUS_HANDLER_IMPL_H_07682758976 diff --git a/FreeFileSync/Source/ui/batch_config.cpp b/FreeFileSync/Source/ui/batch_config.cpp index 54643a5c..d0135d53 100755 --- a/FreeFileSync/Source/ui/batch_config.cpp +++ b/FreeFileSync/Source/ui/batch_config.cpp @@ -13,8 +13,8 @@ #include <wx+/choice_enum.h> #include "gui_generated.h" #include "folder_selector.h" -#include "../lib/help_provider.h" -#include "../lib/generate_logfile.h" +#include "../base/help_provider.h" +#include "../base/generate_logfile.h" using namespace zen; diff --git a/FreeFileSync/Source/ui/batch_config.h b/FreeFileSync/Source/ui/batch_config.h index 7dc68db5..9a6804ca 100755 --- a/FreeFileSync/Source/ui/batch_config.h +++ b/FreeFileSync/Source/ui/batch_config.h @@ -8,7 +8,7 @@ #define BATCH_CONFIG_H_3921674832168945 #include <wx/window.h> -#include "../lib/process_xml.h" +#include "../base/process_xml.h" namespace fff diff --git a/FreeFileSync/Source/ui/batch_status_handler.cpp b/FreeFileSync/Source/ui/batch_status_handler.cpp index c5541cad..66459d8d 100755 --- a/FreeFileSync/Source/ui/batch_status_handler.cpp +++ b/FreeFileSync/Source/ui/batch_status_handler.cpp @@ -10,10 +10,10 @@ #include <zen/shutdown.h> #include <wx+/popup_dlg.h> #include <wx/app.h> -#include "../lib/ffs_paths.h" -#include "../lib/resolve_path.h" -#include "../lib/status_handler_impl.h" -#include "../lib/generate_logfile.h" +#include "../base/ffs_paths.h" +#include "../base/resolve_path.h" +#include "../base/status_handler_impl.h" +#include "../base/generate_logfile.h" #include "../fs/concrete.h" using namespace zen; @@ -39,7 +39,7 @@ std::unique_ptr<AFS::OutputStream> prepareNewLogfile(const AbstractPath& logFold AFS::createFolderIfMissingRecursion(logFolderPath); //throw FileError //const std::string colon = "\xcb\xb8"; //="modifier letter raised colon" => regular colon is forbidden in file names on Windows and OS X - //=> too many issues, most notably cmd.exe is not Unicode-aware: https://www.freefilesync.org/forum/viewtopic.php?t=1679 + //=> too many issues, most notably cmd.exe is not Unicode-aware: https://freefilesync.org/forum/viewtopic.php?t=1679 //assemble logfile name const TimeComp tc = getLocalTime(std::chrono::system_clock::to_time_t(syncStartTime)); @@ -247,7 +247,7 @@ BatchStatusHandler::~BatchStatusHandler() if (!commandLine.empty()) try { - //use ExecutionType::ASYNC until there is reason not to: https://www.freefilesync.org/forum/viewtopic.php?t=31 + //use ExecutionType::ASYNC until there is reason not to: https://freefilesync.org/forum/viewtopic.php?t=31 shellExecute(expandMacros(commandLine), ExecutionType::ASYNC); //throw FileError } catch (const FileError& e) { errorLog_.logMsg(e.toString(), MSG_TYPE_ERROR); } diff --git a/FreeFileSync/Source/ui/batch_status_handler.h b/FreeFileSync/Source/ui/batch_status_handler.h index f3f3cc61..baf7f102 100755 --- a/FreeFileSync/Source/ui/batch_status_handler.h +++ b/FreeFileSync/Source/ui/batch_status_handler.h @@ -10,9 +10,9 @@ #include <chrono> #include <zen/error_log.h> #include "progress_indicator.h" -#include "../lib/status_handler.h" -#include "../lib/process_xml.h" -#include "../lib/return_codes.h" +#include "../base/status_handler.h" +#include "../base/process_xml.h" +#include "../base/return_codes.h" namespace fff diff --git a/FreeFileSync/Source/ui/cfg_grid.cpp b/FreeFileSync/Source/ui/cfg_grid.cpp index f6af408f..42d8ff45 100755 --- a/FreeFileSync/Source/ui/cfg_grid.cpp +++ b/FreeFileSync/Source/ui/cfg_grid.cpp @@ -11,8 +11,8 @@ #include <wx+/rtl.h> #include <wx+/image_resources.h> #include <wx/settings.h> -#include "../lib/icon_buffer.h" -#include "../lib/ffs_paths.h" +#include "../base/icon_buffer.h" +#include "../base/ffs_paths.h" using namespace zen; using namespace fff; @@ -124,14 +124,14 @@ 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<>(), Int2Type<ascending>())(lhs->second.lastSyncTime, rhs->second.lastSyncTime); + return makeSortDirection(std::greater<>(), std::bool_constant<ascending>())(lhs->second.lastSyncTime, rhs->second.lastSyncTime); //[!] ascending LAST_SYNC shows lowest "days past" first <=> highest lastSyncTime first }; switch (sortColumn_) { case ColumnTypeCfg::NAME: - std::sort(cfgListView_.begin(), cfgListView_.end(), makeSortDirection(lessCfgName, Int2Type<ascending>())); + std::sort(cfgListView_.begin(), cfgListView_.end(), makeSortDirection(lessCfgName, std::bool_constant<ascending>())); break; case ColumnTypeCfg::LAST_SYNC: std::sort(cfgListView_.begin(), cfgListView_.end(), lessLastSync); @@ -353,7 +353,7 @@ ConfigView& cfggrid::getDataView(Grid& grid) { if (auto* prov = dynamic_cast<GridDataCfg*>(grid.getDataProvider())) return prov->getDataView(); - throw std::runtime_error("cfggrid was not initialized! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); + throw std::runtime_error(std::string(__FILE__) + "[" + numberTo<std::string>(__LINE__) + "] cfggrid was not initialized."); } @@ -361,7 +361,7 @@ void cfggrid::addAndSelect(Grid& grid, const std::vector<Zstring>& filePaths, bo { auto* prov = dynamic_cast<GridDataCfg*>(grid.getDataProvider()); if (!prov) - throw std::runtime_error("cfggrid was not initialized! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); + throw std::runtime_error(std::string(__FILE__) + "[" + numberTo<std::string>(__LINE__) + "] cfggrid was not initialized."); prov->getDataView().addCfgFiles(filePaths); grid.Refresh(); //[!] let Grid know about changed row count *before* fiddling with selection!!! @@ -394,7 +394,7 @@ int cfggrid::getSyncOverdueDays(Grid& grid) { if (auto* prov = dynamic_cast<GridDataCfg*>(grid.getDataProvider())) return prov->getSyncOverdueDays(); - throw std::runtime_error("cfggrid was not initialized! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); +throw std::runtime_error(std::string(__FILE__) + "[" + numberTo<std::string>(__LINE__) + "] cfggrid was not initialized."); } @@ -402,7 +402,7 @@ void cfggrid::setSyncOverdueDays(Grid& grid, int syncOverdueDays) { auto* prov = dynamic_cast<GridDataCfg*>(grid.getDataProvider()); if (!prov) - throw std::runtime_error("cfggrid was not initialized! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); +throw std::runtime_error(std::string(__FILE__) + "[" + numberTo<std::string>(__LINE__) + "] cfggrid was not initialized."); prov->setSyncOverdueDays(syncOverdueDays); grid.Refresh(); diff --git a/FreeFileSync/Source/ui/file_grid.cpp b/FreeFileSync/Source/ui/file_grid.cpp index 7ff66729..29fc2080 100755 --- a/FreeFileSync/Source/ui/file_grid.cpp +++ b/FreeFileSync/Source/ui/file_grid.cpp @@ -18,7 +18,7 @@ #include <wx+/dc.h> #include <wx+/image_tools.h> #include <wx+/image_resources.h> -#include "../file_hierarchy.h" +#include "../base/file_hierarchy.h" using namespace zen; using namespace fff; @@ -1507,6 +1507,8 @@ private: target.GetViewStart(nullptr, &yOld); if (yOld != y) target.Scroll(-1, y); //empirical test Windows/Ubuntu: this call does NOT trigger a wxEVT_SCROLLWIN event, which would incorrectly set "scrollMaster" to "&target"! + //CAVEAT: wxScrolledWindow::Scroll() internally calls wxWindow::Update(), leading to immediate WM_PAINT handling in the target grid! + // an this while we're still in our WM_PAINT handler! => no recusion, fine (hopefully) }; int y = 0; lead->GetViewStart(nullptr, &y); diff --git a/FreeFileSync/Source/ui/file_grid.h b/FreeFileSync/Source/ui/file_grid.h index 3904786a..3ae06651 100755 --- a/FreeFileSync/Source/ui/file_grid.h +++ b/FreeFileSync/Source/ui/file_grid.h @@ -10,7 +10,7 @@ #include <wx+/grid.h> #include "file_view.h" #include "file_grid_attr.h" -#include "../lib/icon_buffer.h" +#include "../base/icon_buffer.h" namespace fff diff --git a/FreeFileSync/Source/ui/file_view.cpp b/FreeFileSync/Source/ui/file_view.cpp index 19a77f8c..3f96e152 100755 --- a/FreeFileSync/Source/ui/file_view.cpp +++ b/FreeFileSync/Source/ui/file_view.cpp @@ -5,10 +5,10 @@ // ***************************************************************************** #include "file_view.h" -#include "sorting.h" -#include "../synchronization.h" #include <zen/stl_tools.h> #include <zen/perf.h> +#include "sorting.h" +#include "../base/synchronization.h" using namespace zen; using namespace fff; @@ -287,7 +287,7 @@ private: No sorting: 30 ms */ template <class ItemPair> - static std::vector<ItemPair*> getItemsSorted(FixedList<ItemPair>& itemList) + static std::vector<ItemPair*> getItemsSorted(std::list<ItemPair>& itemList) { std::vector<ItemPair*> output; for (ItemPair& item : itemList) diff --git a/FreeFileSync/Source/ui/file_view.h b/FreeFileSync/Source/ui/file_view.h index deaf763f..c87590e7 100755 --- a/FreeFileSync/Source/ui/file_view.h +++ b/FreeFileSync/Source/ui/file_view.h @@ -10,7 +10,7 @@ #include <vector> #include <unordered_map> #include "file_grid_attr.h" -#include "../file_hierarchy.h" +#include "../base/file_hierarchy.h" namespace fff diff --git a/FreeFileSync/Source/ui/folder_history_box.cpp b/FreeFileSync/Source/ui/folder_history_box.cpp index 57a2a5fd..888d2a0f 100755 --- a/FreeFileSync/Source/ui/folder_history_box.cpp +++ b/FreeFileSync/Source/ui/folder_history_box.cpp @@ -8,7 +8,7 @@ #include <list> #include <zen/scope_guard.h> #include <wx+/dc.h> -#include "../lib/resolve_path.h" +#include "../base/resolve_path.h" #include <gtk/gtk.h> using namespace zen; diff --git a/FreeFileSync/Source/ui/folder_pair.h b/FreeFileSync/Source/ui/folder_pair.h index 640907fa..4ac801dd 100755 --- a/FreeFileSync/Source/ui/folder_pair.h +++ b/FreeFileSync/Source/ui/folder_pair.h @@ -16,8 +16,8 @@ #include "folder_selector.h" #include "small_dlgs.h" #include "sync_cfg.h" -#include "../lib/norm_filter.h" -#include "../structures.h" +#include "../base/norm_filter.h" +#include "../base/structures.h" namespace fff diff --git a/FreeFileSync/Source/ui/folder_selector.cpp b/FreeFileSync/Source/ui/folder_selector.cpp index 66e45aae..4a311891 100755 --- a/FreeFileSync/Source/ui/folder_selector.cpp +++ b/FreeFileSync/Source/ui/folder_selector.cpp @@ -14,17 +14,14 @@ #include <wx+/image_resources.h> #include "../fs/concrete.h" #include "../fs/native.h" -#include "../lib/icon_buffer.h" - - // #include <gtk/gtk.h> +#include "../base/icon_buffer.h" + using AFS = fff::AbstractFileSystem; using namespace zen; using namespace fff; - using AFS = AbstractFileSystem; - namespace { diff --git a/FreeFileSync/Source/ui/gui_generated.cpp b/FreeFileSync/Source/ui/gui_generated.cpp index 0ee3fbd0..d7775da4 100755 --- a/FreeFileSync/Source/ui/gui_generated.cpp +++ b/FreeFileSync/Source/ui/gui_generated.cpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////// -// C++ code generated with wxFormBuilder (version Jan 23 2018) +// C++ code generated with wxFormBuilder (version May 29 2018) // http://www.wxformbuilder.org/ // // PLEASE DO *NOT* EDIT THIS FILE! @@ -3920,7 +3920,7 @@ OptionsDlgGenerated::OptionsDlgGenerated( wxWindow* parent, wxWindowID id, const wxBoxSizer* bSizer1881; bSizer1881 = new wxBoxSizer( wxVERTICAL ); - m_buttonResetDialogs = new zen::BitmapTextButton( m_panel39, wxID_ANY, _("Show hidden dialogs again"), wxDefaultPosition, wxSize( -1, -1 ), 0 ); + m_buttonResetDialogs = new zen::BitmapTextButton( m_panel39, wxID_ANY, _("dummy"), wxDefaultPosition, wxSize( -1, -1 ), 0 ); bSizer1881->Add( m_buttonResetDialogs, 0, wxALL, 5 ); m_staticTextResetDialogs = new wxStaticText( m_panel39, wxID_ANY, _("Show all permanently hidden dialogs and warning messages again"), wxDefaultPosition, wxDefaultSize, 0 ); @@ -4142,14 +4142,16 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS m_panel41 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); m_panel41->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); - wxBoxSizer* bSizer162; - bSizer162 = new wxBoxSizer( wxVERTICAL ); + wxBoxSizer* bSizer174; + bSizer174 = new wxBoxSizer( wxHORIZONTAL ); + + bSizerMainSection = new wxBoxSizer( wxVERTICAL ); m_bitmapLogo = new wxStaticBitmap( m_panel41, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), 0 ); - bSizer162->Add( m_bitmapLogo, 0, 0, 5 ); + bSizerMainSection->Add( m_bitmapLogo, 0, 0, 5 ); m_staticline341 = new wxStaticLine( m_panel41, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); - bSizer162->Add( m_staticline341, 0, wxEXPAND, 5 ); + bSizerMainSection->Add( m_staticline341, 0, wxEXPAND, 5 ); wxBoxSizer* bSizer186; bSizer186 = new wxBoxSizer( wxVERTICAL ); @@ -4161,68 +4163,68 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS wxBoxSizer* bSizer166; bSizer166 = new wxBoxSizer( wxHORIZONTAL ); - - bSizer166->Add( 0, 0, 1, wxEXPAND, 5 ); + wxBoxSizer* bSizer251; + bSizer251 = new wxBoxSizer( wxVERTICAL ); m_bitmapHomepage = new wxStaticBitmap( m_panel41, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), 0 ); m_bitmapHomepage->SetToolTip( _("Home page") ); - bSizer166->Add( m_bitmapHomepage, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 ); + bSizer251->Add( m_bitmapHomepage, 0, wxALIGN_CENTER_HORIZONTAL, 5 ); - m_hyperlink1 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("FreeFileSync.org"), wxT("https://www.freefilesync.org/"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink1 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("FreeFileSync.org"), wxT("https://freefilesync.org/"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); m_hyperlink1->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, true, wxEmptyString ) ); m_hyperlink1->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); - m_hyperlink1->SetToolTip( _("https://www.freefilesync.org") ); + m_hyperlink1->SetToolTip( _("https://freefilesync.org") ); + + bSizer251->Add( m_hyperlink1, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); - bSizer166->Add( m_hyperlink1, 0, wxALIGN_CENTER_VERTICAL, 5 ); + bSizer166->Add( bSizer251, 0, wxALIGN_BOTTOM|wxRIGHT|wxLEFT, 5 ); - bSizer166->Add( 0, 0, 1, wxEXPAND, 5 ); + wxBoxSizer* bSizer250; + bSizer250 = new wxBoxSizer( wxVERTICAL ); m_bitmapForum = new wxStaticBitmap( m_panel41, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), 0 ); m_bitmapForum->SetToolTip( _("FreeFileSync Forum") ); - bSizer166->Add( m_bitmapForum, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 ); + bSizer250->Add( m_bitmapForum, 0, wxALIGN_CENTER_HORIZONTAL, 5 ); - m_hyperlink21 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("FreeFileSync Forum"), wxT("https://www.freefilesync.org/forum/"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink21 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("FreeFileSync Forum"), wxT("https://freefilesync.org/forum/"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); m_hyperlink21->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, true, wxEmptyString ) ); m_hyperlink21->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); - m_hyperlink21->SetToolTip( _("https://www.freefilesync.org/forum/") ); + m_hyperlink21->SetToolTip( _("https://freefilesync.org/forum/") ); - bSizer166->Add( m_hyperlink21, 0, wxALIGN_CENTER_VERTICAL, 5 ); + bSizer250->Add( m_hyperlink21, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); - bSizer166->Add( 0, 0, 1, wxEXPAND, 5 ); + bSizer166->Add( bSizer250, 0, wxALIGN_BOTTOM|wxRIGHT|wxLEFT, 5 ); + + wxBoxSizer* bSizer249; + bSizer249 = new wxBoxSizer( wxVERTICAL ); m_bitmapEmail = new wxStaticBitmap( m_panel41, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), 0 ); m_bitmapEmail->SetToolTip( _("Email") ); - bSizer166->Add( m_bitmapEmail, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 ); + bSizer249->Add( m_bitmapEmail, 0, wxALIGN_CENTER_HORIZONTAL, 5 ); m_hyperlink2 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("zenju@freefilesync.org"), wxT("mailto:zenju@freefilesync.org"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); m_hyperlink2->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, true, wxEmptyString ) ); m_hyperlink2->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); m_hyperlink2->SetToolTip( _("mailto:zenju@freefilesync.org") ); - bSizer166->Add( m_hyperlink2, 0, wxALIGN_CENTER_VERTICAL, 5 ); + bSizer249->Add( m_hyperlink2, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); - bSizer166->Add( 0, 0, 1, wxEXPAND, 5 ); + bSizer166->Add( bSizer249, 0, wxALIGN_BOTTOM|wxRIGHT|wxLEFT, 5 ); - bSizer186->Add( bSizer166, 0, wxBOTTOM|wxRIGHT|wxLEFT|wxEXPAND, 5 ); + bSizer186->Add( bSizer166, 0, wxBOTTOM|wxRIGHT|wxLEFT|wxALIGN_CENTER_HORIZONTAL, 5 ); - bSizer162->Add( bSizer186, 0, wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 5 ); + bSizerMainSection->Add( bSizer186, 0, wxALL|wxEXPAND, 5 ); m_staticline3412 = new wxStaticLine( m_panel41, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); - bSizer162->Add( m_staticline3412, 0, wxEXPAND, 5 ); - - wxBoxSizer* bSizer174; - bSizer174 = new wxBoxSizer( wxHORIZONTAL ); - - wxBoxSizer* bSizer181; - bSizer181 = new wxBoxSizer( wxVERTICAL ); + bSizerMainSection->Add( m_staticline3412, 0, wxEXPAND, 5 ); m_panelDonate = new wxPanel( m_panel41, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); m_panelDonate->SetBackgroundColour( wxColour( 153, 170, 187 ) ); @@ -4254,7 +4256,7 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS m_buttonDonate = new wxButton( m_panel39, wxID_ANY, _("Support with a donation"), wxDefaultPosition, wxDefaultSize, 0 ); m_buttonDonate->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) ); - m_buttonDonate->SetToolTip( _("https://www.freefilesync.org/donate.php") ); + m_buttonDonate->SetToolTip( _("https://freefilesync.org/donate.php") ); bSizer178->Add( m_buttonDonate, 0, wxBOTTOM|wxRIGHT|wxLEFT|wxALIGN_CENTER_HORIZONTAL, 5 ); @@ -4274,7 +4276,7 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS m_panelDonate->SetSizer( bSizer183 ); m_panelDonate->Layout(); bSizer183->Fit( m_panelDonate ); - bSizer181->Add( m_panelDonate, 0, wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 10 ); + bSizerMainSection->Add( m_panelDonate, 0, wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 10 ); m_panelThankYou = new wxPanel( m_panel41, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); m_panelThankYou->SetBackgroundColour( wxColour( 153, 170, 187 ) ); @@ -4325,7 +4327,7 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS m_panelThankYou->SetSizer( bSizer1831 ); m_panelThankYou->Layout(); bSizer1831->Fit( m_panelThankYou ); - bSizer181->Add( m_panelThankYou, 0, wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 10 ); + bSizerMainSection->Add( m_panelThankYou, 0, wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 10 ); wxBoxSizer* bSizer187; bSizer187 = new wxBoxSizer( wxVERTICAL ); @@ -4379,9 +4381,9 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS bSizer172->Add( m_hyperlink12, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); - m_hyperlink13 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("Boost"), wxT("http://www.boost.org"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink13 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("Boost"), wxT("https://www.boost.org"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); m_hyperlink13->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); - m_hyperlink13->SetToolTip( _("http://www.boost.org") ); + m_hyperlink13->SetToolTip( _("https://www.boost.org") ); bSizer172->Add( m_hyperlink13, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); @@ -4413,10 +4415,10 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS bSizer187->Add( bSizer172, 0, wxBOTTOM|wxRIGHT|wxLEFT|wxALIGN_CENTER_HORIZONTAL, 5 ); - bSizer181->Add( bSizer187, 0, wxALL|wxEXPAND, 5 ); + bSizerMainSection->Add( bSizer187, 0, wxALL|wxEXPAND, 5 ); m_staticline34 = new wxStaticLine( m_panel41, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); - bSizer181->Add( m_staticline34, 0, wxEXPAND, 5 ); + bSizerMainSection->Add( m_staticline34, 0, wxEXPAND, 5 ); wxBoxSizer* bSizer185; bSizer185 = new wxBoxSizer( wxVERTICAL ); @@ -4440,10 +4442,10 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS bSizer185->Add( bSizer1671, 0, wxALIGN_CENTER_HORIZONTAL|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); - bSizer181->Add( bSizer185, 0, wxALL|wxEXPAND, 5 ); + bSizerMainSection->Add( bSizer185, 0, wxALL|wxEXPAND, 5 ); - bSizer174->Add( bSizer181, 0, 0, 5 ); + bSizer174->Add( bSizerMainSection, 0, 0, 5 ); m_staticline37 = new wxStaticLine( m_panel41, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL ); bSizer174->Add( m_staticline37, 0, wxEXPAND, 5 ); @@ -4451,11 +4453,14 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS wxBoxSizer* bSizer177; bSizer177 = new wxBoxSizer( wxVERTICAL ); + m_staticline74 = new wxStaticLine( m_panel41, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + bSizer177->Add( m_staticline74, 0, wxEXPAND, 5 ); + m_staticTextThanksForLoc = new wxStaticText( m_panel41, wxID_ANY, _("Many thanks for localization:"), wxDefaultPosition, wxSize( -1, -1 ), 0 ); m_staticTextThanksForLoc->Wrap( -1 ); m_staticTextThanksForLoc->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) ); - bSizer177->Add( m_staticTextThanksForLoc, 0, wxALIGN_CENTER_HORIZONTAL|wxALL, 5 ); + bSizer177->Add( m_staticTextThanksForLoc, 0, wxALIGN_CENTER_HORIZONTAL|wxTOP|wxRIGHT|wxLEFT, 10 ); bSizer177->Add( 0, 5, 0, 0, 5 ); @@ -4472,18 +4477,15 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS m_scrolledWindowTranslators->SetSizer( fgSizerTranslators ); m_scrolledWindowTranslators->Layout(); fgSizerTranslators->Fit( m_scrolledWindowTranslators ); - bSizer177->Add( m_scrolledWindowTranslators, 1, wxLEFT|wxEXPAND, 5 ); - - - bSizer174->Add( bSizer177, 0, wxEXPAND|wxLEFT, 5 ); + bSizer177->Add( m_scrolledWindowTranslators, 1, wxLEFT|wxEXPAND, 10 ); - bSizer162->Add( bSizer174, 0, 0, 5 ); + bSizer174->Add( bSizer177, 0, wxEXPAND, 5 ); - m_panel41->SetSizer( bSizer162 ); + m_panel41->SetSizer( bSizer174 ); m_panel41->Layout(); - bSizer162->Fit( m_panel41 ); + bSizer174->Fit( m_panel41 ); bSizer31->Add( m_panel41, 0, wxEXPAND, 5 ); m_staticline36 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); diff --git a/FreeFileSync/Source/ui/gui_generated.h b/FreeFileSync/Source/ui/gui_generated.h index 4c91d1fe..2358df75 100755 --- a/FreeFileSync/Source/ui/gui_generated.h +++ b/FreeFileSync/Source/ui/gui_generated.h @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////// -// C++ code generated with wxFormBuilder (version Jan 23 2018) +// C++ code generated with wxFormBuilder (version May 29 2018) // http://www.wxformbuilder.org/ // // PLEASE DO *NOT* EDIT THIS FILE! @@ -1036,6 +1036,7 @@ private: protected: wxPanel* m_panel41; + wxBoxSizer* bSizerMainSection; wxStaticBitmap* m_bitmapLogo; wxStaticLine* m_staticline341; wxStaticText* m_staticText94; @@ -1074,6 +1075,7 @@ protected: wxStaticBitmap* m_bitmapGpl; wxHyperlinkCtrl* m_hyperlink5; wxStaticLine* m_staticline37; + wxStaticLine* m_staticline74; wxStaticText* m_staticTextThanksForLoc; wxScrolledWindow* m_scrolledWindowTranslators; wxFlexGridSizer* fgSizerTranslators; @@ -1163,7 +1165,7 @@ protected: public: - ActivationDlgGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("FreeFileSync Donation Edition"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER ); + ActivationDlgGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("dummy"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER ); ~ActivationDlgGenerated(); }; diff --git a/FreeFileSync/Source/ui/gui_status_handler.cpp b/FreeFileSync/Source/ui/gui_status_handler.cpp index 198b5726..312ee7db 100755 --- a/FreeFileSync/Source/ui/gui_status_handler.cpp +++ b/FreeFileSync/Source/ui/gui_status_handler.cpp @@ -12,9 +12,9 @@ #include <wx+/bitmap_button.h> #include <wx+/popup_dlg.h> #include "main_dlg.h" -#include "../lib/generate_logfile.h" -#include "../lib/resolve_path.h" -#include "../lib/status_handler_impl.h" +#include "../base/generate_logfile.h" +#include "../base/resolve_path.h" +#include "../base/status_handler_impl.h" using namespace zen; using namespace fff; @@ -343,7 +343,7 @@ StatusHandlerFloatingDialog::~StatusHandlerFloatingDialog() if (!commandLine.empty()) try { - //use ExecutionType::ASYNC until there is reason not to: https://www.freefilesync.org/forum/viewtopic.php?t=31 + //use ExecutionType::ASYNC until there is reason not to: https://freefilesync.org/forum/viewtopic.php?t=31 shellExecute(expandMacros(commandLine), ExecutionType::ASYNC); //throw FileError } catch (const FileError& e) { errorLog_.logMsg(e.toString(), MSG_TYPE_ERROR); } diff --git a/FreeFileSync/Source/ui/gui_status_handler.h b/FreeFileSync/Source/ui/gui_status_handler.h index ae8125f6..48dbbd29 100755 --- a/FreeFileSync/Source/ui/gui_status_handler.h +++ b/FreeFileSync/Source/ui/gui_status_handler.h @@ -11,7 +11,7 @@ #include <wx/event.h> #include "progress_indicator.h" #include "main_dlg.h" -#include "../lib/status_handler.h" +#include "../base/status_handler.h" namespace fff diff --git a/FreeFileSync/Source/ui/main_dlg.cpp b/FreeFileSync/Source/ui/main_dlg.cpp index 5e40c4c4..7925034c 100755 --- a/FreeFileSync/Source/ui/main_dlg.cpp +++ b/FreeFileSync/Source/ui/main_dlg.cpp @@ -36,15 +36,15 @@ #include "batch_config.h" #include "triple_splitter.h" #include "app_icon.h" -#include "../comparison.h" -#include "../synchronization.h" -#include "../algorithm.h" +#include "../base/comparison.h" +#include "../base/synchronization.h" +#include "../base/algorithm.h" #include "../fs/concrete.h" -#include "../lib/resolve_path.h" -#include "../lib/ffs_paths.h" -#include "../lib/help_provider.h" -#include "../lib/lock_holder.h" -#include "../lib/localization.h" +#include "../base/resolve_path.h" +#include "../base/ffs_paths.h" +#include "../base/help_provider.h" +#include "../base/lock_holder.h" +#include "../base/localization.h" #include "../version/version.h" using namespace zen; @@ -298,14 +298,14 @@ void MainDialog::create(const Zstring& globalConfigFilePath) //------------------------------------------------------------------------------------------ //check existence of all files in parallel: - GetFirstResult<FalseType> firstUnavailableFile; + GetFirstResult<std::false_type> firstUnavailableFile; for (const Zstring& filePath : cfgFilePaths) - firstUnavailableFile.addJob([filePath]() -> Opt<FalseType> + firstUnavailableFile.addJob([filePath]() -> Opt<std::false_type> { assert(!filePath.empty()); if (!fileAvailable(filePath)) - return FalseType(); + return std::false_type(); return NoValue(); }); @@ -534,10 +534,10 @@ MainDialog::MainDialog(const Zstring& globalConfigFilePath, m_gridOverview->Connect(EVENT_GRID_SELECT_RANGE, GridSelectEventHandler(MainDialog::onTreeGridSelection), nullptr, this); //cfg grid: - m_gridCfgHistory->Connect(EVENT_GRID_SELECT_RANGE, GridSelectEventHandler(MainDialog::onCfgGridSelection), nullptr, this); - m_gridCfgHistory->Connect(EVENT_GRID_MOUSE_LEFT_DOUBLE, GridClickEventHandler(MainDialog::onCfgGridDoubleClick), nullptr, this); - m_gridCfgHistory->getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainDialog::onCfgGridKeyEvent), nullptr, this); - m_gridCfgHistory->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(MainDialog::onCfgGridContext), nullptr, this); + m_gridCfgHistory->Connect(EVENT_GRID_SELECT_RANGE, GridSelectEventHandler(MainDialog::onCfgGridSelection), nullptr, this); + m_gridCfgHistory->Connect(EVENT_GRID_MOUSE_LEFT_DOUBLE, GridClickEventHandler(MainDialog::onCfgGridDoubleClick), nullptr, this); + m_gridCfgHistory->getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainDialog::onCfgGridKeyEvent), nullptr, this); + m_gridCfgHistory->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(MainDialog::onCfgGridContext), nullptr, this); m_gridCfgHistory->Connect(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, GridLabelClickEventHandler(MainDialog::onCfgGridLabelContext ), nullptr, this); m_gridCfgHistory->Connect(EVENT_GRID_COL_LABEL_MOUSE_LEFT, GridLabelClickEventHandler(MainDialog::onCfgGridLabelLeftClick), nullptr, this); //---------------------------------------------------------------------------------- @@ -752,9 +752,9 @@ MainDialog::MainDialog(const Zstring& globalConfigFilePath, if (havePartialPair != haveFullPair) //either all pairs full or all half-filled -> validity check! { //check existence of all directories in parallel! - GetFirstResult<FalseType> firstMissingDir; + GetFirstResult<std::false_type> firstMissingDir; for (const AbstractPath& folderPath : folderPathsToCheck) - firstMissingDir.addJob([folderPath]() -> Opt<FalseType> + firstMissingDir.addJob([folderPath]() -> Opt<std::false_type> { try { @@ -762,7 +762,7 @@ MainDialog::MainDialog(const Zstring& globalConfigFilePath, return NoValue(); } catch (FileError&) {} - return FalseType(); + return std::false_type(); }); const bool startComparisonNow = !firstMissingDir.timedWait(std::chrono::milliseconds(500)) || //= no result yet => start comparison anyway! @@ -1088,17 +1088,12 @@ void MainDialog::setFilterManually(const std::vector<FileSystemObject*>& selecti } -namespace -{ -//perf: wxString doesn't model exponential growth and is unsuitable for large data sets -using zxString = Zbase<wchar_t>; //guaranteed exponential growth -} - void MainDialog::copySelectionToClipboard(const std::vector<const Grid*>& gridRefs) { try { - zxString clipboardString; +//perf: wxString doesn't model exponential growth and is unsuitable for large data sets + Zstringw clipboardString; auto addSelection = [&](const Grid& grid) { @@ -1112,10 +1107,10 @@ void MainDialog::copySelectionToClipboard(const std::vector<const Grid*>& gridRe std::for_each(colAttr.begin(), colAttr.end() - 1, [&](const Grid::ColAttributes& ca) { - clipboardString += copyStringTo<zxString>(prov->getValue(row, ca.type)); + clipboardString += copyStringTo<Zstringw>(prov->getValue(row, ca.type)); clipboardString += L'\t'; }); - clipboardString += copyStringTo<zxString>(prov->getValue(row, colAttr.back().type)); + clipboardString += copyStringTo<Zstringw>(prov->getValue(row, colAttr.back().type)); clipboardString += L'\n'; } } @@ -1332,7 +1327,7 @@ void invokeCommandLine(const Zstring& commandLinePhrase, //throw FileError const std::vector<FileSystemObject*>& selection, const TempFileBuffer& tempFileBuf) { - static const SelectedSide side2 = OtherSide<side>::result; + constexpr SelectedSide side2 = OtherSide<side>::value; for (const FileSystemObject* fsObj : selection) //context menu calls this function only if selection is not empty! { @@ -2695,7 +2690,7 @@ void MainDialog::cfgHistoryRemoveObsolete(const std::vector<Zstring>& filePaths) availableFiles.push_back(runAsync([=] { return fileAvailable(filePath); })); //potentially slow network access => limit maximum wait time! - wait_for_all_timed(availableFiles.begin(), availableFiles.end(), std::chrono::milliseconds(1000)); + wait_for_all_timed(availableFiles.begin(), availableFiles.end(), std::chrono::seconds(1)); std::vector<Zstring> pathsToRemove; @@ -2746,10 +2741,17 @@ void MainDialog::updateUnsavedCfgStatus() else if (activeConfigFiles_.size() > 1) { title += extractJobName(activeConfigFiles_[0]); - std::for_each(activeConfigFiles_.begin() + 1, activeConfigFiles_.end(), [&](const Zstring& filepath) { title += SPACED_DASH + extractJobName(filepath); }); + std::for_each(activeConfigFiles_.begin() + 1, activeConfigFiles_.end(), [&](const Zstring& filePath) { title += SPACED_DASH + extractJobName(filePath); }); } else - title += wxString(L"FreeFileSync ") + ffsVersion + SPACED_DASH + _("Folder Comparison and Synchronization"); + { + const std::wstring versionName = [&] + { + return L"FreeFileSync " + utfTo<std::wstring>(ffsVersion); + }(); + + title += versionName + SPACED_DASH + _("Folder Comparison and Synchronization"); + } SetTitle(title); } @@ -3725,7 +3727,7 @@ void MainDialog::OnCompare(wxCommandEvent& event) { flashStatusInformation(_("All files are in sync")); - //update last sync date for selected cfg files https://www.freefilesync.org/forum/viewtopic.php?t=4991 + //update last sync date for selected cfg files https://freefilesync.org/forum/viewtopic.php?t=4991 updateLastSyncTimesToNow(); } } @@ -3743,7 +3745,7 @@ void MainDialog::updateGui() updateTopButton(*m_buttonSync, getResourceImage(L"file_sync"), getSyncVariantName(getConfig().mainCfg), folderCmp_.empty()); m_panelTopButtons->Layout(); - m_menuItemExportList->Enable(!folderCmp_.empty()); //a CSV without even folder names confuses users: https://www.freefilesync.org/forum/viewtopic.php?t=4787 + m_menuItemExportList->Enable(!folderCmp_.empty()); //a CSV without even folder names confuses users: https://freefilesync.org/forum/viewtopic.php?t=4787 auiMgr_.Update(); //fix small display distortion, if view filter panel is empty } diff --git a/FreeFileSync/Source/ui/main_dlg.h b/FreeFileSync/Source/ui/main_dlg.h index e4f4be05..3ca17d97 100755 --- a/FreeFileSync/Source/ui/main_dlg.h +++ b/FreeFileSync/Source/ui/main_dlg.h @@ -8,7 +8,6 @@ #define MAIN_DLG_H_8910481324545644545 #include <map> -#include <list> #include <memory> #include <wx+/async_task.h> #include <wx+/file_drop.h> @@ -18,7 +17,7 @@ #include "tree_grid.h" #include "sync_cfg.h" #include "folder_history_box.h" -#include "../algorithm.h" +#include "../base/algorithm.h" namespace fff @@ -312,7 +311,7 @@ private: //*********************************************** //status information - std::list<wxString> oldStatusMsgs_; //the first one is the original/non-flash status message + std::vector<wxString> oldStatusMsgs_; //the first one is the original/non-flash status message //compare status panel (hidden on start, shown when comparing) std::unique_ptr<CompareProgressDialog> compareStatus_; //always bound diff --git a/FreeFileSync/Source/ui/progress_indicator.cpp b/FreeFileSync/Source/ui/progress_indicator.cpp index dcb9539c..d3d5cf31 100755 --- a/FreeFileSync/Source/ui/progress_indicator.cpp +++ b/FreeFileSync/Source/ui/progress_indicator.cpp @@ -31,8 +31,8 @@ #include <wx+/choice_enum.h> #include <wx+/focus.h> #include "gui_generated.h" -#include "../lib/ffs_paths.h" -#include "../lib/perf_check.h" +#include "../base/ffs_paths.h" +#include "../base/perf_check.h" #include "tray_icon.h" #include "taskbar.h" #include "app_icon.h" @@ -68,7 +68,7 @@ inline wxColor getColorItemsBackgroundRim() { return { 53, 25, 255 }; } //dark //don't use wxStopWatch for long-running measurements: internally it uses ::QueryPerformanceCounter() which can overflow after only a few days: -//https://www.freefilesync.org/forum/viewtopic.php?t=1426 +//https://freefilesync.org/forum/viewtopic.php?t=1426 // std::chrono::system_clock is not a steady clock, but at least doesn't overflow! (wraps ::GetSystemTimePreciseAsFileTime()) // std::chrono::steady_clock also wraps ::QueryPerformanceCounter() => same flaw like wxStopWatch??? @@ -519,7 +519,7 @@ public: { time_t time = 0; MessageType type = MSG_TYPE_INFO; - MsgString messageLine; + Zstringw messageLine; bool firstLine = false; //if LogEntry::message spans multiple rows }; @@ -546,7 +546,7 @@ public: for (auto it = log_.begin(); it != log_.end(); ++it) if (it->type & includedTypes) { - static_assert(IsSameType<GetCharType<MsgString>::Type, wchar_t>::value, ""); + static_assert(std::is_same_v<GetCharTypeT<Zstringw>, wchar_t>); assert(!startsWith(it->message, L'\n')); size_t rowNumber = 0; @@ -568,19 +568,19 @@ public: } private: - static MsgString extractLine(const MsgString& message, size_t textRow) + static Zstringw extractLine(const Zstringw& message, size_t textRow) { auto it1 = message.begin(); for (;;) { auto it2 = std::find_if(it1, message.end(), [](wchar_t c) { return c == L'\n'; }); if (textRow == 0) - return it1 == message.end() ? MsgString() : MsgString(&*it1, it2 - it1); //must not dereference iterator pointing to "end"! + return it1 == message.end() ? Zstringw() : Zstringw(&*it1, it2 - it1); //must not dereference iterator pointing to "end"! if (it2 == message.end()) { assert(false); - return MsgString(); + return Zstringw(); } it1 = it2 + 1; //skip newline @@ -824,8 +824,7 @@ private: { if (auto* prov = dynamic_cast<GridDataMessages*>(m_gridMessages->getDataProvider())) return prov->getDataView(); - - throw std::runtime_error("m_gridMessages was not initialized! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); +throw std::runtime_error(std::string(__FILE__) + "[" + numberTo<std::string>(__LINE__) + "] m_gridMessages was not initialized."); } void OnErrors(wxCommandEvent& event) override @@ -972,8 +971,7 @@ private: { try { - using zxString = Zbase<wchar_t>; //guaranteed exponential growth, unlike wxString - zxString clipboardString; + Zstringw clipboardString; //guaranteed exponential growth, unlike wxString if (auto prov = m_gridMessages->getDataProvider()) { @@ -985,10 +983,10 @@ private: std::for_each(colAttr.begin(), --colAttr.end(), [&](const Grid::ColAttributes& ca) { - clipboardString += copyStringTo<zxString>(prov->getValue(row, ca.type)); + clipboardString += copyStringTo<Zstringw>(prov->getValue(row, ca.type)); clipboardString += L'\t'; }); - clipboardString += copyStringTo<zxString>(prov->getValue(row, colAttr.back().type)); + clipboardString += copyStringTo<Zstringw>(prov->getValue(row, colAttr.back().type)); clipboardString += L'\n'; } } @@ -1372,9 +1370,9 @@ SyncProgressDialogImpl<TopLevelDialog>::SyncProgressDialogImpl(long style, //wxF syncStat_ (&syncStat), abortCb_ (&abortCb) { - static_assert(IsSameType<TopLevelDialog, wxFrame >::value || - IsSameType<TopLevelDialog, wxDialog>::value, ""); - assert((IsSameType<TopLevelDialog, wxFrame>::value == !parentFrame)); + static_assert(std::is_same_v<TopLevelDialog, wxFrame > || + std::is_same_v<TopLevelDialog, wxDialog>); + assert((std::is_same_v<TopLevelDialog, wxFrame> == !parentFrame)); //finish construction of this dialog: this->pnl_.m_panelProgress->SetMinSize(wxSize(fastFromDIP(550), fastFromDIP(340))); diff --git a/FreeFileSync/Source/ui/progress_indicator.h b/FreeFileSync/Source/ui/progress_indicator.h index ca45bd72..0a4d0ed8 100755 --- a/FreeFileSync/Source/ui/progress_indicator.h +++ b/FreeFileSync/Source/ui/progress_indicator.h @@ -11,8 +11,8 @@ #include <zen/error_log.h> #include <zen/zstring.h> #include <wx/frame.h> -#include "../lib/status_handler.h" -#include "../lib/process_xml.h" +#include "../base/status_handler.h" +#include "../base/process_xml.h" namespace fff diff --git a/FreeFileSync/Source/ui/small_dlgs.cpp b/FreeFileSync/Source/ui/small_dlgs.cpp index 29365b27..f332c6e5 100755 --- a/FreeFileSync/Source/ui/small_dlgs.cpp +++ b/FreeFileSync/Source/ui/small_dlgs.cpp @@ -24,12 +24,12 @@ #include "gui_generated.h" #include "folder_selector.h" #include "version_check.h" -#include "../algorithm.h" -#include "../synchronization.h" -#include "../lib/help_provider.h" -#include "../lib/hard_filter.h" +#include "../base/algorithm.h" +#include "../base/synchronization.h" +#include "../base/help_provider.h" +#include "../base/hard_filter.h" +#include "../base/status_handler.h" //updateUiIsAllowed() #include "../version/version.h" -#include "../lib/status_handler.h" //updateUiIsAllowed() @@ -46,7 +46,7 @@ public: private: void OnOK (wxCommandEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_OKAY); } void OnClose (wxCloseEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } - void OnDonate(wxCommandEvent& event) override { wxLaunchDefaultBrowser(L"https://www.freefilesync.org/donate.php"); } + void OnDonate(wxCommandEvent& event) override { wxLaunchDefaultBrowser(L"https://freefilesync.org/donate.php"); } void onLocalKeyEvent(wxKeyEvent& event); }; @@ -120,8 +120,7 @@ AboutDlg::AboutDlg(wxWindow* parent) : AboutDlgGenerated(parent) wxImage versionImage = stackImages(appnameImg, buildImg, ImageStackLayout::VERTICAL, ImageStackAlignment::CENTER, 0); const int borderSize = fastFromDIP(5); - - wxBitmap headerBmp(GetClientSize().GetWidth(), versionImage.GetHeight() + 2 * borderSize, 24); + wxBitmap headerBmp(bSizerMainSection->GetSize().x, versionImage.GetHeight() + 2 * borderSize, 24); //attention: *must* pass 24 bits, auto-determination fails on Windows high-contrast colors schemes!!! //problem only shows when calling wxDC::DrawBitmap { @@ -585,7 +584,6 @@ OptionsDlg::OptionsDlg(wxWindow* parent, XmlGlobalSettings& globalSettings) : m_bitmapSettings ->SetBitmap (getResourceImage(L"settings")); m_bpButtonAddRow ->SetBitmapLabel(getResourceImage(L"item_add")); m_bpButtonRemoveRow->SetBitmapLabel(getResourceImage(L"item_remove")); - setBitmapTextLabel(*m_buttonResetDialogs, getResourceImage(L"reset_dialogs").ConvertToImage(), m_buttonResetDialogs->GetLabel()); m_staticTextResetDialogs->Wrap(std::max(fastFromDIP(200), m_buttonResetDialogs->GetMinSize().x)); @@ -647,9 +645,13 @@ void OptionsDlg::onResize(wxSizeEvent& event) void OptionsDlg::updateGui() { - m_buttonResetDialogs->Enable(confirmDlgs_ != defaultCfg_.confirmDlgs || - warnDlgs_ != defaultCfg_.warnDlgs || - autoCloseProgressDialog_ != defaultCfg_.autoCloseProgressDialog); + const bool haveHiddenDialogs = confirmDlgs_ != defaultCfg_.confirmDlgs || + warnDlgs_ != defaultCfg_.warnDlgs || + autoCloseProgressDialog_ != defaultCfg_.autoCloseProgressDialog; + + setBitmapTextLabel(*m_buttonResetDialogs, getResourceImage(L"reset_dialogs").ConvertToImage(), haveHiddenDialogs ? _("Show hidden dialogs again") : _("No hidden dialogs")); + Layout(); + m_buttonResetDialogs->Enable(haveHiddenDialogs); } @@ -963,6 +965,8 @@ ActivationDlg::ActivationDlg(wxWindow* parent, { setStandardButtonLayout(*bSizerStdButtons, StdButtons().setCancel(m_buttonCancel)); + SetTitle(std::wstring(L"FreeFileSync ") + ffsVersion + L" [" + _("Donation Edition") + L"]"); + //setMainInstructionFont(*m_staticTextMain); m_bitmapActivation->SetBitmap(getResourceImage(L"website")); diff --git a/FreeFileSync/Source/ui/small_dlgs.h b/FreeFileSync/Source/ui/small_dlgs.h index 6d79b416..c5b806ce 100755 --- a/FreeFileSync/Source/ui/small_dlgs.h +++ b/FreeFileSync/Source/ui/small_dlgs.h @@ -8,8 +8,8 @@ #define SMALL_DLGS_H_8321790875018750245 #include <wx/window.h> -#include "../lib/process_xml.h" -#include "../synchronization.h" +#include "../base/process_xml.h" +#include "../base/synchronization.h" namespace fff diff --git a/FreeFileSync/Source/ui/sorting.h b/FreeFileSync/Source/ui/sorting.h index 8fdeca5b..8c63be1c 100755 --- a/FreeFileSync/Source/ui/sorting.h +++ b/FreeFileSync/Source/ui/sorting.h @@ -7,8 +7,8 @@ #ifndef SORTING_H_82574232452345 #define SORTING_H_82574232452345 -#include <zen/type_tools.h> -#include "../file_hierarchy.h" +#include <zen/type_traits.h> +#include "../base/file_hierarchy.h" namespace fff @@ -52,7 +52,7 @@ bool lessShortFileName(const FileSystemObject& a, const FileSystemObject& b) return true; //sort directories and files/symlinks by short name - return makeSortDirection(LessNaturalSort() /*even on Linux*/, zen::Int2Type<ascending>())(a.getItemName<side>(), b.getItemName<side>()); + return zen::makeSortDirection(LessNaturalSort() /*even on Linux*/, std::bool_constant<ascending>())(a.getItemName<side>(), b.getItemName<side>()); } @@ -65,7 +65,7 @@ bool lessFullPath(const FileSystemObject& a, const FileSystemObject& b) else if (b.isEmpty<side>()) return true; - return makeSortDirection(LessNaturalSort() /*even on Linux*/, zen::Int2Type<ascending>())( + return zen::makeSortDirection(LessNaturalSort() /*even on Linux*/, std::bool_constant<ascending>())( zen::utfTo<Zstring>(AFS::getDisplayPath(a.getAbstractPath<side>())), zen::utfTo<Zstring>(AFS::getDisplayPath(b.getAbstractPath<side>()))); } @@ -88,7 +88,7 @@ bool lessRelativeFolder(const FileSystemObject& a, const FileSystemObject& b) const int rv = CmpNaturalSort()(relFolderA.c_str(), relFolderA.size(), relFolderB.c_str(), relFolderB.size()); if (rv != 0) - return makeSortDirection(std::less<int>(), zen::Int2Type<ascending>())(rv, 0); + return zen::makeSortDirection(std::less<int>(), std::bool_constant<ascending>())(rv, 0); //make directories always appear before contained files if (isDirectoryB) @@ -96,7 +96,7 @@ bool lessRelativeFolder(const FileSystemObject& a, const FileSystemObject& b) else if (isDirectoryA) return true; - return makeSortDirection(LessNaturalSort(), zen::Int2Type<ascending>())(a.getPairItemName(), b.getPairItemName()); + return zen::makeSortDirection(LessNaturalSort(), std::bool_constant<ascending>())(a.getPairItemName(), b.getPairItemName()); } @@ -125,7 +125,7 @@ bool lessFilesize(const FileSystemObject& a, const FileSystemObject& b) return true; //return list beginning with largest files first - return makeSortDirection(std::less<>(), zen::Int2Type<ascending>())(fileA->getFileSize<side>(), fileB->getFileSize<side>()); + return zen::makeSortDirection(std::less<>(), std::bool_constant<ascending>())(fileA->getFileSize<side>(), fileB->getFileSize<side>()); } @@ -152,7 +152,7 @@ bool lessFiletime(const FileSystemObject& a, const FileSystemObject& b) const int64_t dateB = fileB ? fileB->getLastWriteTime<side>() : symlinkB->getLastWriteTime<side>(); //return list beginning with newest files first - return makeSortDirection(std::less<>(), zen::Int2Type<ascending>())(dateA, dateB); + return zen::makeSortDirection(std::less<>(), std::bool_constant<ascending>())(dateA, dateB); } @@ -174,7 +174,7 @@ bool lessExtension(const FileSystemObject& a, const FileSystemObject& b) return afterLast(fsObj.getItemName<side>(), Zchar('.'), zen::IF_MISSING_RETURN_NONE); }; - return makeSortDirection(LessNaturalSort() /*even on Linux*/, zen::Int2Type<ascending>())(getExtension(a), getExtension(b)); + return zen::makeSortDirection(LessNaturalSort() /*even on Linux*/, std::bool_constant<ascending>())(getExtension(a), getExtension(b)); } @@ -187,14 +187,14 @@ bool lessCmpResult(const FileSystemObject& a, const FileSystemObject& b) if (b.getCategory() == FILE_EQUAL) return true; - return makeSortDirection(std::less<CompareFilesResult>(), zen::Int2Type<ascending>())(a.getCategory(), b.getCategory()); + return zen::makeSortDirection(std::less<CompareFilesResult>(), std::bool_constant<ascending>())(a.getCategory(), b.getCategory()); } template <bool ascending> inline bool lessSyncDirection(const FileSystemObject& a, const FileSystemObject& b) { - return makeSortDirection(std::less<>(), zen::Int2Type<ascending>())(a.getSyncOperation(), b.getSyncOperation()); + return zen::makeSortDirection(std::less<>(), std::bool_constant<ascending>())(a.getSyncOperation(), b.getSyncOperation()); } } diff --git a/FreeFileSync/Source/ui/sync_cfg.cpp b/FreeFileSync/Source/ui/sync_cfg.cpp index 101e2b8b..a1802988 100755 --- a/FreeFileSync/Source/ui/sync_cfg.cpp +++ b/FreeFileSync/Source/ui/sync_cfg.cpp @@ -19,9 +19,9 @@ #include "gui_generated.h" #include "command_box.h" #include "folder_selector.h" -#include "../file_hierarchy.h" -#include "../lib/help_provider.h" -#include "../lib/norm_filter.h" +#include "../base/file_hierarchy.h" +#include "../base/help_provider.h" +#include "../base/norm_filter.h" #include "../fs/concrete.h" @@ -68,7 +68,6 @@ private: void OnHelpComparisonSettings(wxHyperlinkEvent& event) override { displayHelpEntry(L"comparison-settings", this); } void OnHelpTimeShift (wxHyperlinkEvent& event) override { displayHelpEntry(L"daylight-saving-time", this); } void OnHelpPerformance (wxHyperlinkEvent& event) override { displayHelpEntry(L"performance", this); } - warn_static("write performance help entry") void OnToggleLocalCompSettings(wxCommandEvent& event) override { updateCompGui(); updateSyncGui(); /*affects sync settings, too!*/ } void OnCompByTimeSize (wxCommandEvent& event) override { localCmpVar_ = CompareVariant::TIME_SIZE; updateCompGui(); updateSyncGui(); } // diff --git a/FreeFileSync/Source/ui/sync_cfg.h b/FreeFileSync/Source/ui/sync_cfg.h index fd41a529..bf64fcb4 100755 --- a/FreeFileSync/Source/ui/sync_cfg.h +++ b/FreeFileSync/Source/ui/sync_cfg.h @@ -8,7 +8,7 @@ #define SYNC_CFG_H_31289470134253425 #include <wx/window.h> -#include "../structures.h" +#include "../base/structures.h" namespace fff diff --git a/FreeFileSync/Source/ui/taskbar.cpp b/FreeFileSync/Source/ui/taskbar.cpp index 66125d61..c4bc7bec 100755 --- a/FreeFileSync/Source/ui/taskbar.cpp +++ b/FreeFileSync/Source/ui/taskbar.cpp @@ -25,10 +25,10 @@ class Taskbar::Impl //throw (TaskbarNotAvailable) { public: Impl(const wxFrame& window) : - tbEntry(unity_launcher_entry_get_for_desktop_id(FFS_DESKTOP_FILE)) - //tbEntry(unity_launcher_entry_get_for_app_uri("application://freefilesync.desktop")) + tbEntry_(unity_launcher_entry_get_for_desktop_id(FFS_DESKTOP_FILE)) + //tbEntry_(unity_launcher_entry_get_for_app_uri("application://freefilesync.desktop")) { - if (!tbEntry) + if (!tbEntry_) throw TaskbarNotAvailable(); } @@ -39,32 +39,32 @@ public: switch (status) { case Taskbar::STATUS_ERROR: - unity_launcher_entry_set_urgent(tbEntry, true); + unity_launcher_entry_set_urgent(tbEntry_, true); break; case Taskbar::STATUS_INDETERMINATE: - unity_launcher_entry_set_urgent(tbEntry, false); - unity_launcher_entry_set_progress_visible(tbEntry, false); + unity_launcher_entry_set_urgent(tbEntry_, false); + unity_launcher_entry_set_progress_visible(tbEntry_, false); break; case Taskbar::STATUS_NORMAL: - unity_launcher_entry_set_urgent(tbEntry, false); - unity_launcher_entry_set_progress_visible(tbEntry, true); + unity_launcher_entry_set_urgent(tbEntry_, false); + unity_launcher_entry_set_progress_visible(tbEntry_, true); break; case Taskbar::STATUS_PAUSED: - unity_launcher_entry_set_urgent(tbEntry, false); + unity_launcher_entry_set_urgent(tbEntry_, false); break; } } void setProgress(double fraction) { - unity_launcher_entry_set_progress(tbEntry, fraction); + unity_launcher_entry_set_progress(tbEntry_, fraction); } private: - UnityLauncherEntry* tbEntry; + UnityLauncherEntry* const tbEntry_; }; #else //no taskbar support diff --git a/FreeFileSync/Source/ui/tree_grid.cpp b/FreeFileSync/Source/ui/tree_grid.cpp index de7561d0..83a733df 100755 --- a/FreeFileSync/Source/ui/tree_grid.cpp +++ b/FreeFileSync/Source/ui/tree_grid.cpp @@ -16,7 +16,7 @@ #include <wx+/dc.h> #include <wx+/context_menu.h> #include <wx+/image_resources.h> -#include "../lib/icon_buffer.h" +#include "../base/icon_buffer.h" using namespace zen; using namespace fff; @@ -69,7 +69,7 @@ void TreeView::extractVisibleSubtree(ContainerObject& hierObj, //in // } //prefer file-browser semantics over sync preview (=> always show useful numbers, even for SyncDirection::NONE) - //discussion: https://www.freefilesync.org/forum/viewtopic.php?t=1595 + //discussion: https://freefilesync.org/forum/viewtopic.php?t=1595 return std::max(file.getFileSize<LEFT_SIDE>(), file.getFileSize<RIGHT_SIDE>()); }; @@ -180,8 +180,8 @@ struct TreeView::LessShortName { case TreeView::TYPE_ROOT: return makeSortDirection(LessNaturalSort() /*even on Linux*/, - Int2Type<ascending>())(utfTo<Zstring>(static_cast<const RootNodeImpl*>(lhs.node)->displayName), - utfTo<Zstring>(static_cast<const RootNodeImpl*>(rhs.node)->displayName)); + std::bool_constant<ascending>())(utfTo<Zstring>(static_cast<const RootNodeImpl*>(lhs.node)->displayName), + utfTo<Zstring>(static_cast<const RootNodeImpl*>(rhs.node)->displayName)); case TreeView::TYPE_DIRECTORY: { @@ -193,7 +193,7 @@ struct TreeView::LessShortName else if (!folderR) return true; - return makeSortDirection(LessNaturalSort() /*even on Linux*/, Int2Type<ascending>())(folderL->getPairItemName(), folderR->getPairItemName()); + return makeSortDirection(LessNaturalSort() /*even on Linux*/, std::bool_constant<ascending>())(folderL->getPairItemName(), folderR->getPairItemName()); } case TreeView::TYPE_FILES: @@ -246,10 +246,10 @@ void TreeView::sortSingleLevel(std::vector<TreeLine>& items, ColumnTypeTree colu std::sort(items.begin(), items.end(), LessShortName<ascending>()); break; case ColumnTypeTree::ITEM_COUNT: - std::sort(items.begin(), items.end(), makeSortDirection(lessCount, Int2Type<ascending>())); + std::sort(items.begin(), items.end(), makeSortDirection(lessCount, std::bool_constant<ascending>())); break; case ColumnTypeTree::BYTES: - std::sort(items.begin(), items.end(), makeSortDirection(lessBytes, Int2Type<ascending>())); + std::sort(items.begin(), items.end(), makeSortDirection(lessBytes, std::bool_constant<ascending>())); break; } } @@ -840,7 +840,6 @@ private: //percentage bar if (showPercentBar_) { - const wxRect areaPerc(rectTmp.x, rectTmp.y + 2, percentageBarWidth_, rectTmp.height - 4); { //clear background @@ -1207,7 +1206,8 @@ void treegrid::init(Grid& grid) grid.setDataProvider(std::make_shared<GridDataTree>(grid)); grid.showRowLabel(false); - const int rowHeight = std::max(IconBuffer::getSize(IconBuffer::SIZE_SMALL), grid.getMainWin().GetCharHeight()) + 2; //allow 1 pixel space on top and bottom; dearly needed on OS X! + const int rowHeight = std::max(IconBuffer::getSize(IconBuffer::SIZE_SMALL) + 2, //1 extra pixel on top/bottom; dearly needed on OS X! + grid.getMainWin().GetCharHeight()); //seems to already include 3 margin pixels on top/bottom (consider percentage area) grid.setRowHeight(rowHeight); } @@ -1216,8 +1216,7 @@ TreeView& treegrid::getDataView(Grid& grid) { if (auto* prov = dynamic_cast<GridDataTree*>(grid.getDataProvider())) return prov->getDataView(); - - throw std::runtime_error("treegrid was not initialized! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); +throw std::runtime_error(std::string(__FILE__) + "[" + numberTo<std::string>(__LINE__) + "] treegrid was not initialized."); } diff --git a/FreeFileSync/Source/ui/tree_grid.h b/FreeFileSync/Source/ui/tree_grid.h index 22ae293d..6bbfb446 100755 --- a/FreeFileSync/Source/ui/tree_grid.h +++ b/FreeFileSync/Source/ui/tree_grid.h @@ -11,7 +11,7 @@ #include <zen/optional.h> #include <wx+/grid.h> #include "tree_grid_attr.h" -#include "../file_hierarchy.h" +#include "../base/file_hierarchy.h" namespace fff diff --git a/FreeFileSync/Source/ui/version_check.cpp b/FreeFileSync/Source/ui/version_check.cpp index 822ce230..47863f06 100755 --- a/FreeFileSync/Source/ui/version_check.cpp +++ b/FreeFileSync/Source/ui/version_check.cpp @@ -12,11 +12,11 @@ #include <zen/build_info.h> #include <zen/basic_math.h> #include <zen/file_error.h> -#include <zen/thread.h> //std::thread::id +#include <zen/http.h> +#include <zen/thread.h> #include <wx+/popup_dlg.h> -#include <wx+/http.h> #include <wx+/image_resources.h> -#include "../lib/ffs_paths.h" +#include "../base/ffs_paths.h" #include "small_dlgs.h" #include "version_check_impl.h" @@ -28,12 +28,11 @@ using namespace fff; namespace { - -const wchar_t ffsUpdateCheckUserAgent[] = L"FFS-Update-Check"; +const Zchar ffsUpdateCheckUserAgent[] = Zstr("FFS-Update-Check"); std::wstring getIso639Language() { - assert(std::this_thread::get_id() == mainThreadId); //this function is not thread-safe, consider wxWidgets usage + assert(runningMainThread()); //this function is not thread-safe, consider wxWidgets usage const std::wstring localeName(wxLocale::GetLanguageCanonicalName(wxLocale::GetSystemLanguage())); if (!localeName.empty()) @@ -48,7 +47,7 @@ std::wstring getIso639Language() std::wstring getIso3166Country() { - assert(std::this_thread::get_id() == mainThreadId); //this function is not thread-safe, consider wxWidgets usage + assert(runningMainThread()); //this function is not thread-safe, consider wxWidgets usage const std::wstring localeName(wxLocale::GetLanguageCanonicalName(wxLocale::GetSystemLanguage())); if (!localeName.empty()) @@ -64,7 +63,7 @@ std::wstring getIso3166Country() //coordinate with get_latest_version_number.php std::vector<std::pair<std::string, std::string>> geHttpPostParameters() { - assert(std::this_thread::get_id() == mainThreadId); //this function is not thread-safe, e.g. consider wxWidgets usage in isPortableVersion() + assert(runningMainThread()); //this function is not thread-safe, e.g. consider wxWidgets usage in isPortableVersion() std::vector<std::pair<std::string, std::string>> params; params.emplace_back("ffs_version", ffsVersion); @@ -107,7 +106,7 @@ void showUpdateAvailableDialog(wxWindow* parent, const std::string& onlineVersio try { //consider wxHTTP limitation: URL must be accessible without https!!! - const std::string buf = sendHttpPost(L"http://www.freefilesync.org/get_latest_changes.php", ffsUpdateCheckUserAgent, + const std::string buf = sendHttpPost(Zstr("http://freefilesync.org/get_latest_changes.php"), ffsUpdateCheckUserAgent, nullptr /*notifyUnbufferedIO*/, { { "since", ffsVersion } }).readAll(); //throw SysError updateDetailsMsg = utfTo<std::wstring>(buf); } @@ -127,7 +126,7 @@ void showUpdateAvailableDialog(wxWindow* parent, const std::string& onlineVersio _("&Download"))) { case ConfirmationButton::ACCEPT: - wxLaunchDefaultBrowser(L"https://www.freefilesync.org/get_latest.php"); + wxLaunchDefaultBrowser(L"https://freefilesync.org/get_latest.php"); break; case ConfirmationButton::CANCEL: break; @@ -139,7 +138,7 @@ void showUpdateAvailableDialog(wxWindow* parent, const std::string& onlineVersio std::string getOnlineVersion(const std::vector<std::pair<std::string, std::string>>& postParams) //throw SysError { //consider wxHTTP limitation: URL must be accessible without https!!! - const std::string buffer = sendHttpPost(L"http://www.freefilesync.org/get_latest_version_number.php", ffsUpdateCheckUserAgent, + const std::string buffer = sendHttpPost(Zstr("http://freefilesync.org/get_latest_version_number.php"), ffsUpdateCheckUserAgent, nullptr /*notifyUnbufferedIO*/, postParams).readAll(); //throw SysError return trimCpy(buffer); } @@ -207,7 +206,7 @@ void fff::checkForUpdateNow(wxWindow* parent, std::string& lastOnlineVersion) setDetailInstructions(e.toString()), _("&Check"), _("&Retry"))) { case QuestionButton2::YES: - wxLaunchDefaultBrowser(L"https://www.freefilesync.org/get_latest.php"); + wxLaunchDefaultBrowser(L"https://freefilesync.org/get_latest.php"); break; case QuestionButton2::NO: //retry checkForUpdateNow(parent, lastOnlineVersion); //note: retry via recursion!!! @@ -219,7 +218,7 @@ void fff::checkForUpdateNow(wxWindow* parent, std::string& lastOnlineVersion) else switch (showConfirmationDialog(parent, DialogInfoType::ERROR2, PopupDialogCfg(). setTitle(_("Check for Program Updates")). - setMainInstructions(replaceCpy(_("Unable to connect to %x."), L"%x", L"www.freefilesync.org")). + setMainInstructions(replaceCpy(_("Unable to connect to %x."), L"%x", L"freefilesync.org")). setDetailInstructions(e.toString()), _("&Retry"))) { case ConfirmationButton::ACCEPT: //retry @@ -234,19 +233,18 @@ void fff::checkForUpdateNow(wxWindow* parent, std::string& lastOnlineVersion) struct fff::UpdateCheckResultPrep { - const std::vector<std::pair<std::string, std::string>> postParameters { geHttpPostParameters() }; + const std::vector<std::pair<std::string, std::string>> postParameters = geHttpPostParameters(); }; -//run on main thread: std::shared_ptr<UpdateCheckResultPrep> fff::automaticUpdateCheckPrepare() { - return nullptr; + assert(runningMainThread()); + return std::make_shared<UpdateCheckResultPrep>(); } struct fff::UpdateCheckResult { - UpdateCheckResult() {} UpdateCheckResult(const std::string& ver, const Opt<zen::SysError>& err, bool alive) : onlineVersion(ver), error(err), internetIsAlive(alive) {} std::string onlineVersion; @@ -254,28 +252,27 @@ struct fff::UpdateCheckResult bool internetIsAlive = false; }; -//run on worker thread: std::shared_ptr<UpdateCheckResult> fff::automaticUpdateCheckRunAsync(const UpdateCheckResultPrep* resultPrep) { - return nullptr; -} - - -//run on main thread: -void fff::automaticUpdateCheckEval(wxWindow* parent, time_t& lastUpdateCheck, std::string& lastOnlineVersion, const UpdateCheckResult* resultAsync) -{ - - UpdateCheckResult result; + //assert(!runningMainThread()); -> allow synchronous call, too try { - result.onlineVersion = getOnlineVersion(geHttpPostParameters()); //throw SysError - result.internetIsAlive = true; + const std::string onlineVersion = getOnlineVersion(resultPrep->postParameters); //throw SysError + return std::make_shared<UpdateCheckResult>(onlineVersion, NoValue(), true); } catch (const zen::SysError& e) { - result.error = e; - result.internetIsAlive = internetIsAlive(); + return std::make_shared<UpdateCheckResult>("", e, internetIsAlive()); } +} + + +void fff::automaticUpdateCheckEval(wxWindow* parent, time_t& lastUpdateCheck, std::string& lastOnlineVersion, const UpdateCheckResult* asyncResult) +{ + assert(runningMainThread()); + + + const UpdateCheckResult& result = *asyncResult; if (!result.error) { @@ -298,10 +295,10 @@ void fff::automaticUpdateCheckEval(wxWindow* parent, time_t& lastUpdateCheck, st _("&Check"), _("&Retry"))) { case QuestionButton2::YES: - wxLaunchDefaultBrowser(L"https://www.freefilesync.org/get_latest.php"); + wxLaunchDefaultBrowser(L"https://freefilesync.org/get_latest.php"); break; case QuestionButton2::NO: //retry - automaticUpdateCheckEval(parent, lastUpdateCheck, lastOnlineVersion, resultAsync); //note: retry via recursion!!! + automaticUpdateCheckEval(parent, lastUpdateCheck, lastOnlineVersion, asyncResult); //note: retry via recursion!!! break; case QuestionButton2::CANCEL: break; diff --git a/FreeFileSync/Source/ui/version_check.h b/FreeFileSync/Source/ui/version_check.h index 75cef84e..013849e4 100755 --- a/FreeFileSync/Source/ui/version_check.h +++ b/FreeFileSync/Source/ui/version_check.h @@ -30,7 +30,7 @@ std::shared_ptr<UpdateCheckResultPrep> automaticUpdateCheckPrepare(); std::shared_ptr<UpdateCheckResult> automaticUpdateCheckRunAsync(const UpdateCheckResultPrep* resultPrep); //run on main thread: void automaticUpdateCheckEval(wxWindow* parent, time_t& lastUpdateCheck, std::string& lastOnlineVersion, - const UpdateCheckResult* resultAsync); + const UpdateCheckResult* asyncResult); //---------------------------------------------------------------------------- //call from main thread: void checkForUpdateNow(wxWindow* parent, std::string& lastOnlineVersion); diff --git a/FreeFileSync/Source/version/version.h b/FreeFileSync/Source/version/version.h index 797c1901..158c2fc1 100755 --- a/FreeFileSync/Source/version/version.h +++ b/FreeFileSync/Source/version/version.h @@ -3,7 +3,7 @@ namespace fff { -const char ffsVersion[] = "10.0"; //internal linkage! +const char ffsVersion[] = "10.1"; //internal linkage! const char FFS_VERSION_SEPARATOR = '.'; } diff --git a/wx+/async_task.h b/wx+/async_task.h index 1d64e262..8c2602ca 100755 --- a/wx+/async_task.h +++ b/wx+/async_task.h @@ -49,12 +49,12 @@ public: bool resultReady () const override { return isReady(asyncResult_); } void evaluateResult() override { - evalResult(IsSameType<ResultType, void>()); + evalResult(std::is_same<ResultType, void>()); } private: - void evalResult(FalseType /*void result type*/) { evalOnGui_(asyncResult_.get()); } - void evalResult(TrueType /*void result type*/) { asyncResult_.get(); evalOnGui_(); } + void evalResult(std::false_type /*void result type*/) { evalOnGui_(asyncResult_.get()); } + void evalResult(std::true_type /*void result type*/) { asyncResult_.get(); evalOnGui_(); } std::future<ResultType> asyncResult_; Fun evalOnGui_; //keep "evalOnGui" strictly separated from async thread: in particular do not copy in thread! diff --git a/wx+/grid.cpp b/wx+/grid.cpp index a1beee01..adc0615c 100755 --- a/wx+/grid.cpp +++ b/wx+/grid.cpp @@ -169,12 +169,12 @@ wxSize GridData::drawCellText(wxDC& dc, const wxRect& rect, const std::wstring& if (alignment & wxALIGN_RIGHT) //note: wxALIGN_LEFT == 0! pt.x += rect.width - extentTrunc.GetWidth(); else if (alignment & wxALIGN_CENTER_HORIZONTAL) - pt.x += (rect.width - extentTrunc.GetWidth()) / 2; + pt.x += static_cast<int>(std::floor((rect.width - extentTrunc.GetWidth()) / 2.0)); //round down negative values, too! if (alignment & wxALIGN_BOTTOM) //note: wxALIGN_TOP == 0! pt.y += rect.height - extentTrunc.GetHeight(); else if (alignment & wxALIGN_CENTER_VERTICAL) - pt.y += (rect.height - extentTrunc.GetHeight()) / 2; + pt.y += static_cast<int>(std::floor((rect.height - extentTrunc.GetHeight()) / 2.0)); //round down negative values, too! RecursiveDcClipper clip(dc, rect); dc.DrawText(textTrunc, pt); diff --git a/wx+/http.cpp b/wx+/http.cpp deleted file mode 100755 index 1526d30f..00000000 --- a/wx+/http.cpp +++ /dev/null @@ -1,279 +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 * -// ***************************************************************************** - -#include "http.h" - - #include <wx/app.h> - #include <zen/thread.h> //std::thread::id - #include <wx/protocol/http.h> - -using namespace zen; - - -namespace -{ - -struct UrlRedirectError -{ - UrlRedirectError(const std::wstring& url) : newUrl(url) {} - std::wstring newUrl; -}; -} - - -class HttpInputStream::Impl -{ -public: - Impl(const std::wstring& url, const std::wstring& userAgent, const IOCallback& notifyUnbufferedIO, //throw SysError, UrlRedirectError - const std::string* postParams) : //issue POST if bound, GET otherwise - notifyUnbufferedIO_(notifyUnbufferedIO) - { - ZEN_ON_SCOPE_FAIL( cleanup(); /*destructor call would lead to member double clean-up!!!*/ ); - - //assert(!startsWith(url, L"https:", CmpAsciiNoCase())); //not supported by wxHTTP! - - const std::wstring urlFmt = afterFirst(url, L"://", IF_MISSING_RETURN_NONE); - const std::wstring server = beforeFirst(urlFmt, L'/', IF_MISSING_RETURN_ALL); - const std::wstring page = L'/' + afterFirst(urlFmt, L'/', IF_MISSING_RETURN_NONE); - - assert(std::this_thread::get_id() == mainThreadId); - assert(wxApp::IsMainLoopRunning()); - - webAccess_.SetHeader(L"User-Agent", userAgent); - webAccess_.SetTimeout(10 /*[s]*/); //default: 10 minutes: WTF are these wxWidgets people thinking??? - - if (!webAccess_.Connect(server)) //will *not* fail for non-reachable url here! - throw SysError(L"wxHTTP::Connect"); - - if (postParams) - if (!webAccess_.SetPostText(L"application/x-www-form-urlencoded", utfTo<wxString>(*postParams))) - throw SysError(L"wxHTTP::SetPostText"); - - httpStream_.reset(webAccess_.GetInputStream(page)); //pass ownership - const int sc = webAccess_.GetResponse(); - - //http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_Redirection - if (sc / 100 == 3) //e.g. 301, 302, 303, 307... we're not too greedy since we check location, too! - { - const std::wstring newUrl(webAccess_.GetHeader(L"Location")); - if (newUrl.empty()) - throw SysError(L"Unresolvable redirect. Empty target Location."); - - throw UrlRedirectError(newUrl); - } - - if (sc != 200) //HTTP_STATUS_OK - throw SysError(replaceCpy<std::wstring>(L"HTTP status code %x.", L"%x", numberTo<std::wstring>(sc))); - - if (!httpStream_ || webAccess_.GetError() != wxPROTO_NOERR) - throw SysError(L"wxHTTP::GetError (" + numberTo<std::wstring>(webAccess_.GetError()) + L")"); - } - - ~Impl() { cleanup(); } - - size_t read(void* buffer, size_t bytesToRead) //throw SysError, X; return "bytesToRead" bytes unless end of stream! - { - const size_t blockSize = getBlockSize(); - assert(memBuf_.size() >= blockSize); - assert(bufPos_ <= bufPosEnd_ && bufPosEnd_ <= memBuf_.size()); - - char* it = static_cast<char*>(buffer); - char* const itEnd = it + bytesToRead; - for (;;) - { - const size_t junkSize = std::min(static_cast<size_t>(itEnd - it), bufPosEnd_ - bufPos_); - std::memcpy(it, &memBuf_[0] + bufPos_, junkSize); - bufPos_ += junkSize; - it += junkSize; - - if (it == itEnd) - break; - //-------------------------------------------------------------------- - const size_t bytesRead = tryRead(&memBuf_[0], blockSize); //throw SysError; may return short, only 0 means EOF! => CONTRACT: bytesToRead > 0 - bufPos_ = 0; - bufPosEnd_ = bytesRead; - - if (notifyUnbufferedIO_) notifyUnbufferedIO_(bytesRead); //throw X - - if (bytesRead == 0) //end of file - break; - } - return it - static_cast<char*>(buffer); - } - - size_t getBlockSize() const { return 64 * 1024; } - -private: - size_t tryRead(void* buffer, size_t bytesToRead) //throw SysError; may return short, only 0 means EOF! - { - if (bytesToRead == 0) //"read() with a count of 0 returns zero" => indistinguishable from end of file! => check! - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); - assert(bytesToRead == getBlockSize()); - - httpStream_->Read(buffer, bytesToRead); - - const wxStreamError ec = httpStream_->GetLastError(); - if (ec != wxSTREAM_NO_ERROR && ec != wxSTREAM_EOF) - throw SysError(L"wxInputStream::GetLastError (" + numberTo<std::wstring>(httpStream_->GetLastError()) + L")"); - - const size_t bytesRead = httpStream_->LastRead(); - //"if there are not enough bytes in the stream right now, LastRead() value will be - // less than size but greater than 0. If it is 0, it means that EOF has been reached." - assert(bytesRead > 0 || ec == wxSTREAM_EOF); - if (bytesRead > bytesToRead) //better safe than sorry - throw SysError(L"InternetReadFile: buffer overflow."); - - return bytesRead; //"zero indicates end of file" - } - - Impl (const Impl&) = delete; - Impl& operator=(const Impl&) = delete; - - void cleanup() - { - } - - wxHTTP webAccess_; - std::unique_ptr<wxInputStream> httpStream_; //must be deleted BEFORE webAccess is closed - - const IOCallback notifyUnbufferedIO_; //throw X - - std::vector<char> memBuf_ = std::vector<char>(getBlockSize()); - size_t bufPos_ = 0; //buffered I/O; see file_io.cpp - size_t bufPosEnd_ = 0; // -}; - - -HttpInputStream::HttpInputStream(std::unique_ptr<Impl>&& pimpl) : pimpl_(std::move(pimpl)) {} - -HttpInputStream::~HttpInputStream() {} - -size_t HttpInputStream::read(void* buffer, size_t bytesToRead) { return pimpl_->read(buffer, bytesToRead); } //throw SysError, X; return "bytesToRead" bytes unless end of stream! - -size_t HttpInputStream::getBlockSize() const { return pimpl_->getBlockSize(); } - -std::string HttpInputStream::readAll() { return bufferedLoad<std::string>(*pimpl_); } //throw SysError, X; - -namespace -{ -std::unique_ptr<HttpInputStream::Impl> sendHttpRequestImpl(const std::wstring& url, const std::wstring& userAgent, const IOCallback& notifyUnbufferedIO, //throw SysError - const std::string* postParams) //issue POST if bound, GET otherwise -{ - std::wstring urlRed = url; - //"A user agent should not automatically redirect a request more than five times, since such redirections usually indicate an infinite loop." - for (int redirects = 0; redirects < 6; ++redirects) - try - { - return std::make_unique<HttpInputStream::Impl>(urlRed, userAgent, notifyUnbufferedIO, postParams); //throw SysError, UrlRedirectError - } - catch (const UrlRedirectError& e) { urlRed = e.newUrl; } - throw SysError(L"Too many redirects."); -} - - -//encode into "application/x-www-form-urlencoded" -std::string urlencode(const std::string& str) -{ - std::string out; - for (const char c : str) //follow PHP spec: https://github.com/php/php-src/blob/master/ext/standard/url.c#L500 - if (c == ' ') - out += '+'; - else if (('0' <= c && c <= '9') || - ('A' <= c && c <= 'Z') || - ('a' <= c && c <= 'z') || - c == '-' || c == '.' || c == '_') //note: "~" is encoded by PHP! - out += c; - else - { - const std::pair<char, char> hex = hexify(c); - - out += '%'; - out += hex.first; - out += hex.second; - } - return out; -} - - -std::string urldecode(const std::string& str) -{ - std::string out; - for (size_t i = 0; i < str.size(); ++i) - { - const char c = str[i]; - if (c == '+') - out += ' '; - else if (c == '%' && str.size() - i >= 3 && - isHexDigit(str[i + 1]) && - isHexDigit(str[i + 2])) - { - out += unhexify(str[i + 1], str[i + 2]); - i += 2; - } - else - out += c; - } - return out; -} -} - - -std::string zen::xWwwFormUrlEncode(const std::vector<std::pair<std::string, std::string>>& paramPairs) -{ - std::string output; - for (const auto& pair : paramPairs) - output += urlencode(pair.first) + '=' + urlencode(pair.second) + '&'; - //encode both key and value: https://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1 - if (!output.empty()) - output.pop_back(); - return output; -} - - -std::vector<std::pair<std::string, std::string>> zen::xWwwFormUrlDecode(const std::string& str) -{ - std::vector<std::pair<std::string, std::string>> output; - - for (const std::string& nvPair : split(str, '&', SplitType::SKIP_EMPTY)) - output.emplace_back(urldecode(beforeFirst(nvPair, '=', IF_MISSING_RETURN_ALL)), - urldecode(afterFirst (nvPair, '=', IF_MISSING_RETURN_NONE))); - return output; -} - - -HttpInputStream zen::sendHttpPost(const std::wstring& url, const std::wstring& userAgent, const IOCallback& notifyUnbufferedIO, - const std::vector<std::pair<std::string, std::string>>& postParams) //throw SysError -{ - const std::string encodedParams = xWwwFormUrlEncode(postParams); - return sendHttpRequestImpl(url, userAgent, notifyUnbufferedIO, &encodedParams); //throw SysError -} - - -HttpInputStream zen::sendHttpGet(const std::wstring& url, const std::wstring& userAgent, const IOCallback& notifyUnbufferedIO) //throw SysError -{ - return sendHttpRequestImpl(url, userAgent, notifyUnbufferedIO, nullptr); //throw SysError -} - - -bool zen::internetIsAlive() //noexcept -{ - assert(std::this_thread::get_id() == mainThreadId); - - const wxString server = L"www.google.com"; - const wxString page = L"/"; - - wxHTTP webAccess; - webAccess.SetTimeout(10 /*[s]*/); //default: 10 minutes: WTF are these wxWidgets people thinking??? - - if (!webAccess.Connect(server)) //will *not* fail for non-reachable url here! - return false; - - std::unique_ptr<wxInputStream> httpStream(webAccess.GetInputStream(page)); //call before checking wxHTTP::GetResponse() - const int sc = webAccess.GetResponse(); - //attention: http://www.google.com/ might redirect to "https" => don't follow, just return "true"!!! - return sc / 100 == 2 || //e.g. 200 - sc / 100 == 3; //e.g. 301, 302, 303, 307... when in doubt, consider internet alive! -} diff --git a/wx+/image_holder.h b/wx+/image_holder.h index 6804d5fc..f4bf3c8a 100755 --- a/wx+/image_holder.h +++ b/wx+/image_holder.h @@ -18,15 +18,15 @@ struct ImageHolder //prepare conversion to wxImage as much as possible while sta { ImageHolder() {} - ImageHolder(int w, int h, bool withAlpha) : //init with allocated memory + ImageHolder(int w, int h, bool withAlpha) : //init with memory allocated width_(w), height_(h), rgb_(static_cast<unsigned char*>(::malloc(w * h * 3))), alpha_(withAlpha ? static_cast<unsigned char*>(::malloc(w * h)) : nullptr) {} - ImageHolder (ImageHolder&& tmp) = default; // - ImageHolder& operator=(ImageHolder&& tmp) = default; //move semantics only! - ImageHolder (const ImageHolder&) = delete; // - ImageHolder& operator=(const ImageHolder&) = delete; // + ImageHolder (ImageHolder&&) noexcept = default; // + ImageHolder& operator=(ImageHolder&&) noexcept = default; //move semantics only! + ImageHolder (const ImageHolder&) = delete; // + ImageHolder& operator=(const ImageHolder&) = delete; // explicit operator bool() const { return rgb_.get() != nullptr; } diff --git a/wx+/image_resources.cpp b/wx+/image_resources.cpp index e361515a..bbeeff8b 100755 --- a/wx+/image_resources.cpp +++ b/wx+/image_resources.cpp @@ -26,8 +26,6 @@ using namespace zen; namespace { - - ImageHolder dpiScale(int width, int height, int dpiWidth, int dpiHeight, const unsigned char* imageRgb, const unsigned char* imageAlpha, int hqScale) { assert(imageRgb && imageAlpha); //see convertToVanillaImage() @@ -84,113 +82,41 @@ ImageHolder dpiScale(int width, int height, int dpiWidth, int dpiHeight, const u } -struct WorkItem +auto getScalerTask(const wxString& name, const wxImage& img, int hqScale, Protected<std::vector<std::pair<std::wstring, ImageHolder>>>& result) { - std::wstring name; //don't trust wxString to be thread-safe like an int - int width = 0; - int height = 0; - int dpiWidth = 0; - int dpiHeight = 0; - const unsigned char* rgb = nullptr; - const unsigned char* alpha = nullptr; -}; - - -class WorkLoad -{ -public: - void add(const WorkItem& wi) //context of main thread - { - assert(std::this_thread::get_id() == mainThreadId); - { - std::lock_guard<std::mutex> dummy(lockWork_); - workLoad_.push_back(wi); - } - conditionNewWork_.notify_all(); - } - - void noMoreWork() + return [name = copyStringTo<std::wstring>(name), //don't trust wxString to be thread-safe like an int + width = img.GetWidth(), + height = img.GetHeight(), + dpiWidth = fastFromDIP(img.GetWidth()), + dpiHeight = fastFromDIP(img.GetHeight()), //don't call fastFromDIP() from worker thread (wxWidgets function!) + rgb = img.GetData(), + alpha = img.GetAlpha(), + hqScale, &result] { - assert(std::this_thread::get_id() == mainThreadId); - { - std::lock_guard<std::mutex> dummy(lockWork_); - expectMoreWork_ = false; - } - conditionNewWork_.notify_all(); - } - - //context of worker thread, blocking: - Opt<WorkItem> extractNext() //throw ThreadInterruption - { - assert(std::this_thread::get_id() != mainThreadId); - std::unique_lock<std::mutex> dummy(lockWork_); - - interruptibleWait(conditionNewWork_, dummy, [this] { return !workLoad_.empty() || !expectMoreWork_; }); //throw ThreadInterruption - - if (!workLoad_.empty()) - { - WorkItem wi = std::move(workLoad_. back()); // - /**/ workLoad_.pop_back(); //yes, no strong exception guarantee (std::bad_alloc) - return std::move(wi); // - } - return NoValue(); - } - -private: - std::mutex lockWork_; - std::vector<WorkItem> workLoad_; - std::condition_variable conditionNewWork_; //signal event: data for processing available - bool expectMoreWork_ = true; -}; + ImageHolder ih = dpiScale(width, height, + dpiWidth, dpiHeight, + rgb, alpha, hqScale); + result.access([&](std::vector<std::pair<std::wstring, ImageHolder>>& r) { r.emplace_back(name, std::move(ih)); }); + }; +} class DpiParallelScaler { public: - DpiParallelScaler(int hqScale) - { - assert(hqScale > 1); - const int threadCount = std::max<int>(std::thread::hardware_concurrency(), 1); //hardware_concurrency() == 0 if "not computable or well defined" - - for (int i = 0; i < threadCount; ++i) - worker_.push_back([hqScale, &workload = workload_, &result = result_] - { - setCurrentThreadName("xBRZ Scaler"); - while (Opt<WorkItem> wi = workload.extractNext()) //throw ThreadInterruption - { - ImageHolder ih = dpiScale(wi->width, wi->height, - wi->dpiWidth, wi->dpiHeight, - wi->rgb, wi->alpha, hqScale); - result.access([&](std::vector<std::pair<std::wstring, ImageHolder>>& r) { r.emplace_back(wi->name, std::move(ih)); }); - } - }); - } + DpiParallelScaler(int hqScale) : hqScale_(hqScale) { assert(hqScale > 1); } - ~DpiParallelScaler() - { - for (InterruptibleThread& w : worker_) - w.interrupt(); - - for (InterruptibleThread& w : worker_) - if (w.joinable()) - w.join(); - } + ~DpiParallelScaler() { threadGroup_ = zen::NoValue(); } //DpiParallelScaler must out-live threadGroup!!! void add(const wxString& name, const wxImage& img) { imgKeeper_.push_back(img); //retain (ref-counted) wxImage so that the rgb/alpha pointers remain valid after passed to threads - workload_.add({ copyStringTo<std::wstring>(name), - img.GetWidth(), img.GetHeight(), - fastFromDIP(img.GetWidth()), fastFromDIP(img.GetHeight()), //don't call fastFromDIP() from worker thread (wxWidgets function!) - img.GetData(), img.GetAlpha() }); + threadGroup_->run(getScalerTask(name, img, hqScale_, result_)); } std::map<wxString, wxBitmap> waitAndGetResult() { - workload_.noMoreWork(); - - for (InterruptibleThread& w : worker_) - w.join(); + threadGroup_->wait(); std::map<wxString, wxBitmap> output; @@ -203,17 +129,20 @@ public: wxImage img(ih.getWidth(), ih.getHeight(), ih.releaseRgb(), false /*static_data*/); //pass ownership img.SetAlpha(ih.releaseAlpha(), false /*static_data*/); - output[item.first] = wxBitmap(img); + output.emplace(item.first, std::move(img)); } }); return output; } private: - std::vector<InterruptibleThread> worker_; - WorkLoad workload_; - Protected<std::vector<std::pair<std::wstring, ImageHolder>>> result_; + const int hqScale_; std::vector<wxImage> imgKeeper_; + Protected<std::vector<std::pair<std::wstring, ImageHolder>>> result_; + + using TaskType = FunctionReturnTypeT<decltype(&getScalerTask)>; + Opt<ThreadGroup<TaskType>> threadGroup_{ ThreadGroup<TaskType>(std::max<int>(std::thread::hardware_concurrency(), 1), "xBRZ Scaler") }; + //hardware_concurrency() == 0 if "not computable or well defined" }; @@ -222,12 +151,12 @@ void loadAnimFromZip(wxZipInputStream& zipInput, wxAnimation& anim) //work around wxWidgets bug: //construct seekable input stream (zip-input stream is not seekable) for wxAnimation::Load() //luckily this method call is very fast: below measurement precision! - std::vector<char> data; + std::vector<std::byte> data; data.reserve(10000); int newValue = 0; while ((newValue = zipInput.GetC()) != wxEOF) - data.push_back(newValue); + data.push_back(static_cast<std::byte>(newValue)); wxMemoryInputStream seekAbleStream(&data.front(), data.size()); //stream does not take ownership of data @@ -243,7 +172,7 @@ public: static std::shared_ptr<GlobalBitmaps> instance() { static Global<GlobalBitmaps> inst(std::make_unique<GlobalBitmaps>()); - assert(std::this_thread::get_id() == mainThreadId); //wxWidgets is not thread-safe! + assert(runningMainThread()); //wxWidgets is not thread-safe! return inst.get(); } diff --git a/wx+/image_tools.cpp b/wx+/image_tools.cpp index 26a5b37c..88a78b21 100755 --- a/wx+/image_tools.cpp +++ b/wx+/image_tools.cpp @@ -83,7 +83,7 @@ wxImage zen::stackImages(const wxImage& img1, const wxImage& img2, ImageStackLay switch (align) { case ImageStackAlignment::CENTER: - return (totalExtent - imageExtent) / 2; + return static_cast<int>(std::floor((totalExtent - imageExtent) / 2.0)); //consistency: round down negative values, too! case ImageStackAlignment::LEFT: return 0; case ImageStackAlignment::RIGHT: diff --git a/wx+/zlib_wrap.h b/wx+/zlib_wrap.h index 7265331e..d3bb017b 100755 --- a/wx+/zlib_wrap.h +++ b/wx+/zlib_wrap.h @@ -52,8 +52,8 @@ BinContainer compress(const BinContainer& stream, int level) //throw ZlibInterna //save uncompressed stream size for decompression const uint64_t uncompressedSize = stream.size(); //use portable number type! contOut.resize(sizeof(uncompressedSize)); - std::copy(reinterpret_cast<const char*>(&uncompressedSize), - reinterpret_cast<const char*>(&uncompressedSize) + sizeof(uncompressedSize), + std::copy(reinterpret_cast<const std::byte*>(&uncompressedSize), + reinterpret_cast<const std::byte*>(&uncompressedSize) + sizeof(uncompressedSize), &*contOut.begin()); const size_t bufferEstimate = impl::zlib_compressBound(stream.size()); //upper limit for buffer size, larger than input size!!! @@ -85,7 +85,7 @@ BinContainer decompress(const BinContainer& stream) //throw ZlibInternalError throw ZlibInternalError(); std::copy(&*stream.begin(), &*stream.begin() + sizeof(uncompressedSize), - reinterpret_cast<char*>(&uncompressedSize)); + reinterpret_cast<std::byte*>(&uncompressedSize)); //attention: contOut MUST NOT be empty! Else it will pass a nullptr to zlib_decompress() => Z_STREAM_ERROR although "uncompressedSize == 0"!!! //secondary bug: don't dereference iterator into empty container! if (uncompressedSize == 0) //cannot be 0: compress() directly maps empty -> empty container skipping zlib! diff --git a/xBRZ/src/xbrz.cpp b/xBRZ/src/xbrz.cpp index 4d3ccd25..3cbd0d64 100755 --- a/xBRZ/src/xbrz.cpp +++ b/xBRZ/src/xbrz.cpp @@ -29,7 +29,7 @@ namespace template <unsigned int M, unsigned int N> inline uint32_t gradientRGB(uint32_t pixFront, uint32_t pixBack) //blend front color with opacity M / N over opaque background: http://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending { - static_assert(0 < M && M < N && N <= 1000, ""); + 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; }; @@ -42,7 +42,7 @@ uint32_t gradientRGB(uint32_t pixFront, uint32_t pixBack) //blend front color wi template <unsigned int M, unsigned int N> inline uint32_t gradientARGB(uint32_t pixFront, uint32_t pixBack) //find intermediate color between two colors with alpha channels (=> NO alpha blending!!!) { - static_assert(0 < M && M < N && N <= 1000, ""); + static_assert(0 < M && M < N && N <= 1000); const unsigned int weightFront = getAlpha(pixFront) * M; const unsigned int weightBack = getAlpha(pixBack) * (N - M); @@ -473,7 +473,7 @@ void scaleImage(const uint32_t* src, uint32_t* trg, int srcWidth, int srcHeight, const int bufferSize = srcWidth; unsigned char* preProcBuffer = reinterpret_cast<unsigned char*>(trg + yLast * Scaler::scale * trgWidth) - bufferSize; std::fill(preProcBuffer, preProcBuffer + bufferSize, '\0'); - static_assert(BLEND_NONE == 0, ""); + static_assert(BLEND_NONE == 0); //initialize preprocessing buffer for first row of current stripe: detect upper left and right corner blending //this cannot be optimized for adjacent processing stripes; we must not allow for a memory race condition! @@ -1071,7 +1071,7 @@ if (factor == 1) return; } - static_assert(SCALE_FACTOR_MAX == 6, ""); + static_assert(SCALE_FACTOR_MAX == 6); switch (colFmt) { case ColorFormat::RGB: diff --git a/zen/basic_math.h b/zen/basic_math.h index 16f69bde..0d08f6a6 100755 --- a/zen/basic_math.h +++ b/zen/basic_math.h @@ -13,6 +13,7 @@ #include <cmath> #include <functional> #include <cassert> +#include "type_traits.h" namespace numeric @@ -65,6 +66,7 @@ const double sqrt2 = 1.41421356237309504880; const double ln2 = 0.693147180559945309417; //static_assert(pi + e + sqrt2 + ln2 == 7.9672352249818781, "whoopsie"); + //---------------------------------------------------------------------------------- @@ -83,7 +85,7 @@ const double ln2 = 0.693147180559945309417; template <class T> inline T abs(T value) { - //static_assert(std::is_signed<T>::value, ""); + //static_assert(std::is_signed_v<T>); if (value < 0) return -value; //operator "?:" caveat: may be different type than "value" else @@ -100,7 +102,7 @@ auto dist(T a, T b) //return type might be different than T, e.g. std::chrono::d template <class T> inline int sign(T value) //returns one of {-1, 0, 1} { - static_assert(std::is_signed<T>::value, ""); + static_assert(std::is_signed_v<T>); return value < 0 ? -1 : (value > 0 ? 1 : 0); } @@ -231,8 +233,8 @@ int64_t round(double d) template <class N, class D> inline auto integerDivideRoundUp(N numerator, D denominator) { - static_assert(std::is_integral<N>::value, ""); - static_assert(std::is_integral<D>::value, ""); + static_assert(zen::IsInteger<N>::value); + static_assert(zen::IsInteger<D>::value); assert(numerator > 0 && denominator > 0); return (numerator + denominator - 1) / denominator; } diff --git a/zen/build_info.h b/zen/build_info.h index 9b8b7fc0..e80f3721 100755 --- a/zen/build_info.h +++ b/zen/build_info.h @@ -16,11 +16,11 @@ #endif #ifdef ZEN_BUILD_32BIT - static_assert(sizeof(void*) == 4, ""); + static_assert(sizeof(void*) == 4); #endif #ifdef ZEN_BUILD_64BIT - static_assert(sizeof(void*) == 8, ""); + static_assert(sizeof(void*) == 8); #endif #endif //BUILD_INFO_H_5928539285603428657 @@ -7,6 +7,7 @@ #ifndef CRC_H_23489275827847235 #define CRC_H_23489275827847235 +//boost, clean this mess up! #include <boost/crc.hpp> @@ -28,12 +29,12 @@ inline uint32_t getCrc32(const std::string& str) { return getCrc32(str.begin(), template <class ByteIterator> inline uint16_t getCrc16(ByteIterator first, ByteIterator last) { - static_assert(sizeof(typename std::iterator_traits<ByteIterator>::value_type) == 1, ""); + static_assert(sizeof(typename std::iterator_traits<ByteIterator>::value_type) == 1); boost::crc_16_type result; if (first != last) result.process_bytes(&*first, last - first); auto rv = result.checksum(); - static_assert(sizeof(rv) == sizeof(uint16_t), ""); + static_assert(sizeof(rv) == sizeof(uint16_t)); return rv; } @@ -41,12 +42,12 @@ uint16_t getCrc16(ByteIterator first, ByteIterator last) template <class ByteIterator> inline uint32_t getCrc32(ByteIterator first, ByteIterator last) { - static_assert(sizeof(typename std::iterator_traits<ByteIterator>::value_type) == 1, ""); + static_assert(sizeof(typename std::iterator_traits<ByteIterator>::value_type) == 1); boost::crc_32_type result; if (first != last) result.process_bytes(&*first, last - first); auto rv = result.checksum(); - static_assert(sizeof(rv) == sizeof(uint32_t), ""); + static_assert(sizeof(rv) == sizeof(uint32_t)); return rv; } } diff --git a/zen/deprecate.h b/zen/deprecate.h deleted file mode 100755 index 1f4e6ab4..00000000 --- a/zen/deprecate.h +++ /dev/null @@ -1,18 +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 DEPRECATE_H_234897087787348 -#define DEPRECATE_H_234897087787348 - -//compiler macros: http://predef.sourceforge.net/precomp.html -#ifdef __GNUC__ - #define ZEN_DEPRECATE __attribute__ ((deprecated)) - -#else - #error add your platform here! -#endif - -#endif //DEPRECATE_H_234897087787348 diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp index 0cbde150..2bb3fd26 100755 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -103,7 +103,7 @@ DirWatcher::~DirWatcher() std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void()>& requestUiRefresh, std::chrono::milliseconds cbInterval) //throw FileError { - std::vector<char> buffer(512 * (sizeof(struct ::inotify_event) + NAME_MAX + 1)); + std::vector<std::byte> buffer(512 * (sizeof(struct ::inotify_event) + NAME_MAX + 1)); ssize_t bytesRead = 0; do diff --git a/zen/error_log.h b/zen/error_log.h index b6660850..0de88856 100755 --- a/zen/error_log.h +++ b/zen/error_log.h @@ -13,7 +13,7 @@ #include <string> #include "time.h" #include "i18n.h" -#include "string_base.h" +#include "zstring.h" namespace zen @@ -26,13 +26,11 @@ enum MessageType MSG_TYPE_FATAL_ERROR = 0x8, }; -using MsgString = Zbase<wchar_t>; //std::wstring may employ small string optimization: we cannot accept bloating the "ErrorLog::entries" memory block below (think 1 million items) - struct LogEntry { time_t time = 0; MessageType type = MSG_TYPE_FATAL_ERROR; - MsgString message; + Zstringw message; //std::wstring may employ small string optimization: we cannot accept bloating the "ErrorLog::entries" memory block below (think 1 million items) }; template <class String> @@ -69,7 +67,7 @@ private: template <class String> inline void ErrorLog::logMsg(const String& text, MessageType type) { - entries_.push_back({ std::time(nullptr), type, copyStringTo<MsgString>(text) }); + entries_.push_back({ std::time(nullptr), type, copyStringTo<Zstringw>(text) }); } diff --git a/zen/file_access.cpp b/zen/file_access.cpp index 18c0ed26..fca1e3d8 100755 --- a/zen/file_access.cpp +++ b/zen/file_access.cpp @@ -52,18 +52,18 @@ Opt<PathComponents> zen::parsePathComponents(const Zstring& itemPath) if (startsWith(itemPath, "/")) { - if (startsWith(itemPath, "/media/")) + if (startsWith(itemPath, "/media/")) { //Ubuntu: e.g. /media/zenju/DEVICE_NAME if (const char* username = ::getenv("USER")) if (startsWith(itemPath, std::string("/media/") + username + "/")) return doParse(4 /*sepCountVolumeRoot*/, false /*rootWithSep*/); - //Ubuntu: e.g. /media/cdrom0 + //Ubuntu: e.g. /media/cdrom0 return doParse(3 /*sepCountVolumeRoot*/, false /*rootWithSep*/); } - if (startsWith(itemPath, "/run/media/")) //Suse: e.g. /run/media/zenju/DEVICE_NAME + if (startsWith(itemPath, "/run/media/")) //Suse: e.g. /run/media/zenju/DEVICE_NAME if (const char* username = ::getenv("USER")) if (startsWith(itemPath, std::string("/run/media/") + username + "/")) return doParse(5 /*sepCountVolumeRoot*/, false /*rootWithSep*/); @@ -372,7 +372,7 @@ void zen::renameFile(const Zstring& pathSource, const Zstring& pathTarget) //thr } catch (ErrorTargetExisting&) { -#if 0 //"Work around pen drive failing to change file name case" => enable if needed: https://www.freefilesync.org/forum/viewtopic.php?t=4279 +#if 0 //"Work around pen drive failing to change file name case" => enable if needed: https://freefilesync.org/forum/viewtopic.php?t=4279 const Zstring fileNameSrc = afterLast (pathSource, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); const Zstring fileNameTrg = afterLast (pathTarget, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); const Zstring parentPathSrc = beforeLast(pathSource, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE); @@ -411,17 +411,17 @@ void setWriteTimeNative(const Zstring& itemPath, const struct ::timespec& modTim using open()/futimens() for regular files and utimensat(AT_SYMLINK_NOFOLLOW) for symlinks is consistent with "cp" and "touch"! */ struct ::timespec newTimes[2] = {}; - newTimes[0].tv_sec = ::time(nullptr); //access time; using UTIME_OMIT for tv_nsec would trigger even more bugs: https://www.freefilesync.org/forum/viewtopic.php?t=1701 + 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 if (procSl == ProcSymlink::FOLLOW) { //hell knows why files on gvfs-mounted Samba shares fail to open(O_WRONLY) returning EOPNOTSUPP: - //https://www.freefilesync.org/forum/viewtopic.php?t=2803 => utimensat() works (but not for gvfs SFTP) + //https://freefilesync.org/forum/viewtopic.php?t=2803 => utimensat() works (but not for gvfs SFTP) if (::utimensat(AT_FDCWD, itemPath.c_str(), newTimes, 0) == 0) return; - //in other cases utimensat() returns EINVAL for CIFS/NTFS drives, but open+futimens works: https://www.freefilesync.org/forum/viewtopic.php?t=387 + //in other cases utimensat() returns EINVAL for CIFS/NTFS drives, but open+futimens works: https://freefilesync.org/forum/viewtopic.php?t=387 const int fdFile = ::open(itemPath.c_str(), O_WRONLY | O_APPEND); //2017-07-04: O_WRONLY | O_APPEND seems to avoid EOPNOTSUPP on gvfs SFTP! if (fdFile == -1) THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(itemPath)), L"open"); @@ -676,7 +676,7 @@ FileCopyResult copyFileOsSpecific(const Zstring& sourceFile, //throw FileError, //this triggers bugs on samba shares where the modification time is set to current time instead. //Linux: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=340236 // http://comments.gmane.org/gmane.linux.file-systems.cifs/2854 - //OS X: https://www.freefilesync.org/forum/viewtopic.php?t=356 + //OS X: https://freefilesync.org/forum/viewtopic.php?t=356 setWriteTimeNative(targetFile, sourceInfo.st_mtim, ProcSymlink::FOLLOW); //throw FileError } catch (const FileError& e) diff --git a/zen/file_io.cpp b/zen/file_io.cpp index 1cbd970b..25dc93ce 100755 --- a/zen/file_io.cpp +++ b/zen/file_io.cpp @@ -132,7 +132,7 @@ size_t FileInput::tryRead(void* buffer, size_t bytesToRead) //throw FileError, E size_t FileInput::read(void* buffer, size_t bytesToRead) //throw FileError, ErrorFileLocked, X; return "bytesToRead" bytes unless end of stream! { /* - FFS 8.9-9.5 perf issues on macOS: https://www.freefilesync.org/forum/viewtopic.php?t=4808 + FFS 8.9-9.5 perf issues on macOS: https://freefilesync.org/forum/viewtopic.php?t=4808 app-level buffering is essential to optimize random data sizes; e.g. "export file list": => big perf improvement on Windows, Linux. No significant improvement on macOS in tests impact on stream-based file copy: @@ -148,8 +148,8 @@ size_t FileInput::read(void* buffer, size_t bytesToRead) //throw FileError, Erro assert(memBuf_.size() >= blockSize); assert(bufPos_ <= bufPosEnd_ && bufPosEnd_ <= memBuf_.size()); - char* it = static_cast<char*>(buffer); - char* const itEnd = it + bytesToRead; + auto it = static_cast<std::byte*>(buffer); + const auto itEnd = it + bytesToRead; for (;;) { const size_t junkSize = std::min(static_cast<size_t>(itEnd - it), bufPosEnd_ - bufPos_); @@ -169,7 +169,7 @@ size_t FileInput::read(void* buffer, size_t bytesToRead) //throw FileError, Erro if (bytesRead == 0) //end of file break; } - return it - static_cast<char*>(buffer); + return it - static_cast<std::byte*>(buffer); } //---------------------------------------------------------------------------------------------------- @@ -253,8 +253,8 @@ void FileOutput::write(const void* buffer, size_t bytesToWrite) //throw FileErro assert(memBuf_.size() >= blockSize); assert(bufPos_ <= bufPosEnd_ && bufPosEnd_ <= memBuf_.size()); - const char* it = static_cast<const char*>(buffer); - const char* const itEnd = it + bytesToWrite; + auto it = static_cast<const std::byte*>(buffer); + const auto itEnd = it + bytesToWrite; for (;;) { if (memBuf_.size() - bufPos_ < blockSize) //support memBuf_.size() > blockSize to reduce memmove()s, but perf test shows: not really needed! diff --git a/zen/file_io.h b/zen/file_io.h index 5b0b8cb0..d05d97db 100755 --- a/zen/file_io.h +++ b/zen/file_io.h @@ -66,7 +66,7 @@ private: const IOCallback notifyUnbufferedIO_; //throw X - std::vector<char> memBuf_ = std::vector<char>(getBlockSize()); + std::vector<std::byte> memBuf_ = std::vector<std::byte>(getBlockSize()); size_t bufPos_ = 0; size_t bufPosEnd_= 0; }; @@ -95,7 +95,7 @@ private: IOCallback notifyUnbufferedIO_; //throw X - std::vector<char> memBuf_ = std::vector<char>(getBlockSize()); + std::vector<std::byte> memBuf_ = std::vector<std::byte>(getBlockSize()); size_t bufPos_ = 0; size_t bufPosEnd_ = 0; }; diff --git a/zen/fixed_list.h b/zen/fixed_list.h deleted file mode 100755 index 10b66233..00000000 --- a/zen/fixed_list.h +++ /dev/null @@ -1,240 +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 FIXED_LIST_H_01238467085684139453534 -#define FIXED_LIST_H_01238467085684139453534 - -#include <cassert> -#include <iterator> -#include "stl_tools.h" - - -namespace zen -{ -//std::list(C++11)-like class for inplace element construction supporting non-copyable/non-movable types -//-> no iterator invalidation after emplace_back() - -template <class T> -class FixedList -{ - struct Node - { - template <class... Args> - Node(Args&& ... args) : val(std::forward<Args>(args)...) {} - - Node* next = nullptr; //singly-linked list is sufficient - T val; - }; - -public: - FixedList() {} - - ~FixedList() { clear(); } - - template <class NodeT, class U> - class FixedIterator : public std::iterator<std::forward_iterator_tag, U> - { - public: - FixedIterator(NodeT* it = nullptr) : it_(it) {} - FixedIterator& operator++() { it_ = it_->next; return *this; } - inline friend bool operator==(const FixedIterator& lhs, const FixedIterator& rhs) { return lhs.it_ == rhs.it_; } - inline friend bool operator!=(const FixedIterator& lhs, const FixedIterator& rhs) { return !(lhs == rhs); } - U& operator* () const { return it_->val; } - U* operator->() const { return &it_->val; } - private: - NodeT* it_; - }; - - using value_type = T; - using iterator = FixedIterator< Node, T>; - using const_iterator = FixedIterator<const Node, const T>; - using reference = T&; - using const_reference = const T&; - - iterator begin() { return firstInsert_; } - iterator end () { return iterator(); } - - const_iterator begin() const { return firstInsert_; } - const_iterator end () const { return const_iterator(); } - - //const_iterator cbegin() const { return firstInsert_; } - //const_iterator cend () const { return const_iterator(); } - - reference front() { return firstInsert_->val; } - const_reference front() const { return firstInsert_->val; } - - reference& back() { return lastInsert_->val; } - const_reference& back() const { return lastInsert_->val; } - - template <class... Args> - void emplace_back(Args&& ... args) - { - Node* newNode = new Node(std::forward<Args>(args)...); - - if (!lastInsert_) - { - assert(!firstInsert_ && sz_ == 0); - firstInsert_ = lastInsert_ = newNode; - } - else - { - assert(lastInsert_->next == nullptr); - lastInsert_->next = newNode; - lastInsert_ = newNode; - } - ++sz_; - } - - template <class Predicate> - void remove_if(Predicate pred) - { - Node* prev = nullptr; - Node* ptr = firstInsert_; - - while (ptr) - if (pred(ptr->val)) - { - Node* next = ptr->next; - - delete ptr; - assert(sz_ > 0); - --sz_; - - ptr = next; - - if (prev) - prev->next = next; - else - firstInsert_ = next; - if (!next) - lastInsert_ = prev; - } - else - { - prev = ptr; - ptr = ptr->next; - } - } - - void clear() - { - Node* ptr = firstInsert_; - while (ptr) - { - Node* next = ptr->next; - delete ptr; - ptr = next; - } - - sz_ = 0; - firstInsert_ = lastInsert_ = nullptr; - } - - bool empty() const { return sz_ == 0; } - - size_t size() const { return sz_; } - - void swap(FixedList& other) - { - std::swap(firstInsert_, other.firstInsert_); - std::swap(lastInsert_, other.lastInsert_); - std::swap(sz_, other.sz_); - } - -private: - FixedList (const FixedList&) = delete; - FixedList& operator=(const FixedList&) = delete; - - Node* firstInsert_ = nullptr; - Node* lastInsert_ = nullptr; //point to last insertion; required by efficient emplace_back() - size_t sz_ = 0; -}; - - -//just as fast as FixedList, but simpler, more CPU-cache-friendly => superseeds FixedList! -template <class T> -class FixedVector -{ -public: - FixedVector() {} - - /* - class EndIterator {}; //just like FixedList: no iterator invalidation after emplace_back() - - template <class V> - class FixedIterator : public std::iterator<std::forward_iterator_tag, V> //could make this random-access if needed - { - public: - FixedIterator(std::vector<std::unique_ptr<T>>& cont, size_t pos) : cont_(cont), pos_(pos) {} - FixedIterator& operator++() { ++pos_; return *this; } - inline friend bool operator==(const FixedIterator& lhs, EndIterator) { return lhs.pos_ == lhs.cont_.size(); } - inline friend bool operator!=(const FixedIterator& lhs, EndIterator) { return !(lhs == EndIterator()); } - V& operator* () const { return *cont_[pos_]; } - V* operator->() const { return &*cont_[pos_]; } - private: - std::vector<std::unique_ptr<T>>& cont_; - size_t pos_ = 0; - }; - */ - - template <class IterImpl, class V> - class FixedIterator : public std::iterator<std::forward_iterator_tag, V> //could make this bidirectional if needed - { - public: - FixedIterator(IterImpl it) : it_(it) {} - FixedIterator& operator++() { ++it_; return *this; } - inline friend bool operator==(const FixedIterator& lhs, const FixedIterator& rhs) { return lhs.it_ == rhs.it_; } - inline friend bool operator!=(const FixedIterator& lhs, const FixedIterator& rhs) { return !(lhs == rhs); } - V& operator* () const { return **it_; } - V* operator->() const { return &** it_; } - private: - IterImpl it_; //TODO: avoid iterator invalidation after emplace_back(); caveat: end() must not store old length! - }; - - using value_type = T; - using iterator = FixedIterator<typename std::vector<std::unique_ptr<T>>::iterator, T>; - using const_iterator = FixedIterator<typename std::vector<std::unique_ptr<T>>::const_iterator, const T>; - using reference = T&; - using const_reference = const T&; - - iterator begin() { return items_.begin(); } - iterator end () { return items_.end (); } - - const_iterator begin() const { return items_.begin(); } - const_iterator end () const { return items_.end (); } - - reference front() { return *items_.front(); } - const_reference front() const { return *items_.front(); } - - reference& back() { return *items_.back(); } - const_reference& back() const { return *items_.back(); } - - template <class... Args> - void emplace_back(Args&& ... args) - { - items_.push_back(std::make_unique<T>(std::forward<Args>(args)...)); - } - - template <class Predicate> - void remove_if(Predicate pred) - { - erase_if(items_, [&](const std::unique_ptr<T>& p) { return pred(*p); }); - } - - void clear() { items_.clear(); } - bool empty() const { return items_.empty(); } - size_t size () const { return items_.size(); } - void swap(FixedVector& other) { items_.swap(other.items_); } - -private: - FixedVector (const FixedVector&) = delete; - FixedVector& operator=(const FixedVector&) = delete; - - std::vector<std::unique_ptr<T>> items_; -}; -} - -#endif //FIXED_LIST_H_01238467085684139453534 diff --git a/zen/format_unit.cpp b/zen/format_unit.cpp index 931a29be..3e75278b 100755 --- a/zen/format_unit.cpp +++ b/zen/format_unit.cpp @@ -193,7 +193,7 @@ std::wstring zen::formatUtcToLocalTime(time_t utcTime) auto errorMsg = [&] { return _("Error") + L" (time_t: " + numberTo<std::wstring>(utcTime) + L")"; }; TimeComp loc = getLocalTime(utcTime); - + std::wstring dateString = formatTime<std::wstring>(L"%x %X", loc); return !dateString.empty() ? dateString : errorMsg(); } diff --git a/zen/globals.h b/zen/globals.h index 2066c380..10975414 100755 --- a/zen/globals.h +++ b/zen/globals.h @@ -21,10 +21,12 @@ class Global public: Global() { - static_assert(std::is_trivially_destructible<Pod>::value, "this memory needs to live forever"); + static_assert(std::is_trivially_destructible_v<Pod>, "this memory needs to live forever"); assert(!pod_.inst && !pod_.spinLock); //we depend on static zero-initialization! } + explicit Global(std::unique_ptr<T>&& newInst) { set(std::move(newInst)); } + ~Global() { set(nullptr); } std::shared_ptr<T> get() //=> return std::shared_ptr to let instance life time be handled by caller (MT usage!) @@ -60,7 +62,6 @@ private: //serialize access; can't use std::mutex: has non-trival destructor } pod_; }; - } #endif //GLOBALS_H_8013740213748021573485 @@ -8,16 +8,7 @@ #define GUID_H_80425780237502345 #include <string> - -#ifdef __GNUC__ //boost should clean this mess up - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#endif -#include <boost/uuid/uuid_generators.hpp> -#ifdef __GNUC__ - #pragma GCC diagnostic pop -#endif - + #include <boost/uuid/uuid_generators.hpp> namespace zen { @@ -28,6 +19,7 @@ std::string generateGUID() //creates a 16-byte GUID // retrieve GUID: 0.13µs per call //generator is only thread-safe like an int => keep thread-local thread_local boost::uuids::random_generator gen; + static_assert(boost::uuids::uuid::static_size() == 16); const boost::uuids::uuid nativeRep = gen(); return std::string(nativeRep.begin(), nativeRep.end()); } diff --git a/zen/http.cpp b/zen/http.cpp new file mode 100755 index 00000000..d06d3309 --- /dev/null +++ b/zen/http.cpp @@ -0,0 +1,376 @@ +// ***************************************************************************** +// * 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 * +// ***************************************************************************** + +#include "http.h" + + #include "socket.h" + +using namespace zen; + + +class HttpInputStream::Impl +{ +public: + Impl(const Zstring& url, const Zstring& userAgent, const IOCallback& notifyUnbufferedIO, //throw SysError + const std::vector<std::pair<std::string, std::string>>* postParams) : //issue POST if bound, GET otherwise + notifyUnbufferedIO_(notifyUnbufferedIO) + { + ZEN_ON_SCOPE_FAIL( cleanup(); /*destructor call would lead to member double clean-up!!!*/ ); + + const Zstring urlFmt = afterFirst(url, Zstr("://"), IF_MISSING_RETURN_NONE); + const Zstring server = beforeFirst(urlFmt, Zstr('/'), IF_MISSING_RETURN_ALL); + const Zstring page = Zstr('/') + afterFirst(urlFmt, Zstr('/'), IF_MISSING_RETURN_NONE); + + const bool useTls = [&] + { + if (startsWith(url, Zstr("http://"), CmpAsciiNoCase())) + return false; + if (startsWith(url, Zstr("https://"), CmpAsciiNoCase())) + return true; + throw SysError(L"URL uses unexpected protocol."); + }(); + + assert(!useTls); //not supported by our plain socket! + (void)useTls; + + socket_ = std::make_unique<Socket>(server, Zstr("http")); //throw SysError + //HTTP default port: 80, see %WINDIR%\system32\drivers\etc\services + + std::map<std::string, std::string, LessAsciiNoCase> headers; + headers["Host" ] = utfTo<std::string>(server); //only required for HTTP/1.1 + headers["User-Agent"] = utfTo<std::string>(userAgent); + headers["Accept" ] = "*/*"; //won't hurt? + + const std::string postBuf = postParams ? xWwwFormUrlEncode(*postParams) : ""; + + if (!postParams) //HTTP GET + headers["Pragma"] = "no-cache"; //HTTP 1.0 only! superseeded by "Cache-Control" + //consider internetIsAlive() test; not relevant for POST (= never cached) + else //HTTP POST + { + headers["Content-type"] = "application/x-www-form-urlencoded"; + headers["Content-Length"] = numberTo<std::string>(postBuf.size()); + } + + //https://www.w3.org/Protocols/HTTP/1.0/spec.html#Request-Line + std::string msg = (postParams ? "POST " : "GET ") + utfTo<std::string>(page) + " HTTP/1.0\r\n"; + for (const auto& item : headers) + msg += item.first + ": " + item.second + "\r\n"; + msg += "\r\n"; + msg += postBuf; + + //send request + for (size_t bytesToSend = msg.size(); bytesToSend > 0;) + { + int bytesSent = 0; + for (;;) + { + bytesSent = ::send(socket_->get(), //_In_ SOCKET s, + &*(msg.end() - bytesToSend), //_In_ const char *buf, + static_cast<int>(bytesToSend), //_In_ int len, + 0); //_In_ int flags + if (bytesSent >= 0 || errno != EINTR) + break; + } + if (bytesSent < 0) + THROW_LAST_SYS_ERROR_WSA(L"send"); + if (bytesSent > static_cast<int>(bytesToSend)) + throw SysError(L"send: buffer overflow."); + if (bytesSent == 0) + throw SysError(L"send: zero bytes processed"); + + bytesToSend -= bytesSent; + } + if (::shutdown(socket_->get(), SHUT_WR) != 0) + THROW_LAST_SYS_ERROR_WSA(L"shutdown"); + + //receive response: + std::string headBuf; + const std::string headerDelim = "\r\n\r\n"; + for (std::string buf;;) + { + const size_t blockSize = std::min(static_cast<size_t>(1024), memBuf_.size()); //smaller block size: try to only read header part + buf.resize(buf.size() + blockSize); + const size_t bytesReceived = tryRead(&*(buf.end() - blockSize), blockSize); //throw SysError + buf.resize(buf.size() - blockSize + bytesReceived); //caveat: unsigned arithmetics + + if (contains(buf, headerDelim)) + { + headBuf = beforeFirst(buf, headerDelim, IF_MISSING_RETURN_NONE); + const std::string bodyBuf = afterFirst (buf, headerDelim, IF_MISSING_RETURN_NONE); + //put excess bytes into instance buffer for body retrieval + assert(bufPos_ == 0 && bufPosEnd_ == 0); + bufPosEnd_ = bodyBuf.size(); + std::copy(bodyBuf.begin(), bodyBuf.end(), reinterpret_cast<char*>(&memBuf_[0])); + break; + } + if (bytesReceived == 0) + break; + } + //parse header + const std::string statusBuf = beforeFirst(headBuf, "\r\n", IF_MISSING_RETURN_ALL); + const std::string headersBuf = afterFirst (headBuf, "\r\n", IF_MISSING_RETURN_NONE); + + const std::vector<std::string> statusItems = split(statusBuf, ' ', SplitType::ALLOW_EMPTY); //HTTP-Version SP Status-Code SP Reason-Phrase CRLF + if (statusItems.size() < 2 || !startsWith(statusItems[0], "HTTP/")) + throw SysError(L"Invalid HTTP response: \"" + utfTo<std::wstring>(statusBuf) + L"\""); + + statusCode_ = stringTo<int>(statusItems[1]); + + for (const std::string& line : split(headersBuf, "\r\n", SplitType::SKIP_EMPTY)) + responseHeaders_[trimCpy(beforeFirst(line, ":", IF_MISSING_RETURN_ALL))] = + /**/ trimCpy(afterFirst (line, ":", IF_MISSING_RETURN_NONE)); + + //try to get "Content-Length" header if available + if (const std::string* value = getHeader("Content-Length")) + contentRemaining_ = stringTo<int64_t>(*value) - (bufPosEnd_ - bufPos_); + } + + ~Impl() { cleanup(); } + + + const int getStatusCode() const { return statusCode_; } + + const std::string* getHeader(const std::string& name) const + { + auto it = responseHeaders_.find(name); + return it != responseHeaders_.end() ? &it->second : nullptr; + } + + size_t read(void* buffer, size_t bytesToRead) //throw SysError, X; return "bytesToRead" bytes unless end of stream! + { + const size_t blockSize = getBlockSize(); + assert(memBuf_.size() >= blockSize); + assert(bufPos_ <= bufPosEnd_ && bufPosEnd_ <= memBuf_.size()); + + auto it = static_cast<std::byte*>(buffer); + const auto itEnd = it + bytesToRead; + for (;;) + { + const size_t junkSize = std::min(static_cast<size_t>(itEnd - it), bufPosEnd_ - bufPos_); + std::memcpy(it, &memBuf_[0] + bufPos_, junkSize); + bufPos_ += junkSize; + it += junkSize; + + if (it == itEnd) + break; + //-------------------------------------------------------------------- + const size_t bytesRead = tryRead(&memBuf_[0], blockSize); //throw SysError; may return short, only 0 means EOF! => CONTRACT: bytesToRead > 0 + bufPos_ = 0; + bufPosEnd_ = bytesRead; + + if (notifyUnbufferedIO_) notifyUnbufferedIO_(bytesRead); //throw X + + if (bytesRead == 0) //end of file + break; + } + return it - static_cast<std::byte*>(buffer); + } + + size_t getBlockSize() const { return 64 * 1024; } + +private: + size_t tryRead(void* buffer, size_t bytesToRead) //throw SysError; may return short, only 0 means EOF! + { + if (bytesToRead == 0) //"read() with a count of 0 returns zero" => indistinguishable from end of file! => check! + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); + assert(bytesToRead <= getBlockSize()); //block size might be 1000 while reading HTTP header + + if (contentRemaining_ >= 0) + { + if (contentRemaining_ == 0) + return 0; + bytesToRead = static_cast<size_t>(std::min(static_cast<int64_t>(bytesToRead), contentRemaining_)); //[!] contentRemaining_ > 4 GB possible! + } + int bytesReceived = 0; + for (;;) + { + bytesReceived = ::recv(socket_->get(), //_In_ SOCKET s, + static_cast<char*>(buffer), //_Out_ char *buf, + static_cast<int>(bytesToRead), //_In_ int len, + 0); //_In_ int flags + if (bytesReceived >= 0 || errno != EINTR) + break; + } + if (bytesReceived < 0) + THROW_LAST_SYS_ERROR_WSA(L"recv"); + if (static_cast<size_t>(bytesReceived) > bytesToRead) //better safe than sorry + throw SysError(L"HttpInputStream::tryRead: buffer overflow."); + + if (contentRemaining_ >= 0) + contentRemaining_ -= bytesReceived; + + if (bytesReceived == 0 && contentRemaining_ > 0) + throw SysError(replaceCpy<std::wstring>(L"HttpInputStream::tryRead: incomplete server response; %x more bytes expected.", L"%x", numberTo<std::wstring>(contentRemaining_))); + + return bytesReceived; //"zero indicates end of file" + } + + Impl (const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + + void cleanup() + { + } + + std::unique_ptr<Socket> socket_; //*bound* after constructor has run + int statusCode_ = 0; + std::map<std::string, std::string, LessAsciiNoCase> responseHeaders_; + + int64_t contentRemaining_ = -1; //consider "Content-Length" if available + + const IOCallback notifyUnbufferedIO_; //throw X + + std::vector<std::byte> memBuf_ = std::vector<std::byte>(getBlockSize()); + size_t bufPos_ = 0; //buffered I/O; see file_io.cpp + size_t bufPosEnd_ = 0; // +}; + + +HttpInputStream::HttpInputStream(std::unique_ptr<Impl>&& pimpl) : pimpl_(std::move(pimpl)) {} + +HttpInputStream::~HttpInputStream() {} + +size_t HttpInputStream::read(void* buffer, size_t bytesToRead) { return pimpl_->read(buffer, bytesToRead); } //throw SysError, X; return "bytesToRead" bytes unless end of stream! + +size_t HttpInputStream::getBlockSize() const { return pimpl_->getBlockSize(); } + +std::string HttpInputStream::readAll() { return bufferedLoad<std::string>(*pimpl_); } //throw SysError, X; + + +namespace +{ +std::unique_ptr<HttpInputStream::Impl> sendHttpRequestImpl(const Zstring& url, const Zstring& userAgent, const IOCallback& notifyUnbufferedIO, //throw SysError + const std::vector<std::pair<std::string, std::string>>* postParams) //issue POST if bound, GET otherwise +{ + Zstring urlRed = url; + //"A user agent should not automatically redirect a request more than five times, since such redirections usually indicate an infinite loop." + for (int redirects = 0; redirects < 6; ++redirects) + { + auto response = std::make_unique<HttpInputStream::Impl>(urlRed, userAgent, notifyUnbufferedIO, postParams); //throw SysError + + //http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_Redirection + const int statusCode = response->getStatusCode(); + if (statusCode / 100 == 3) //e.g. 301, 302, 303, 307... we're not too greedy since we check location, too! + { + const std::string* value = response->getHeader("Location"); + if (!value || value->empty()) + throw SysError(L"Unresolvable redirect. No target Location."); + + urlRed = utfTo<Zstring>(*value); + } + else + { + if (statusCode != 200) //HTTP_STATUS_OK + throw SysError(replaceCpy<std::wstring>(L"HTTP status code %x.", L"%x", numberTo<std::wstring>(statusCode))); + //e.g. 404 - HTTP_STATUS_NOT_FOUND + + return response; + } + } + throw SysError(L"Too many redirects."); +} + + +//encode into "application/x-www-form-urlencoded" +std::string urlencode(const std::string& str) +{ + std::string out; + for (const char c : str) //follow PHP spec: https://github.com/php/php-src/blob/master/ext/standard/url.c#L500 + if (c == ' ') + out += '+'; + else if (('0' <= c && c <= '9') || + ('A' <= c && c <= 'Z') || + ('a' <= c && c <= 'z') || + c == '-' || c == '.' || c == '_') //note: "~" is encoded by PHP! + out += c; + else + { + const std::pair<char, char> hex = hexify(c); + + out += '%'; + out += hex.first; + out += hex.second; + } + return out; +} + + +std::string urldecode(const std::string& str) +{ + std::string out; + for (size_t i = 0; i < str.size(); ++i) + { + const char c = str[i]; + if (c == '+') + out += ' '; + else if (c == '%' && str.size() - i >= 3 && + isHexDigit(str[i + 1]) && + isHexDigit(str[i + 2])) + { + out += unhexify(str[i + 1], str[i + 2]); + i += 2; + } + else + out += c; + } + return out; +} +} + + +std::string zen::xWwwFormUrlEncode(const std::vector<std::pair<std::string, std::string>>& paramPairs) +{ + std::string output; + for (const auto& pair : paramPairs) + output += urlencode(pair.first) + '=' + urlencode(pair.second) + '&'; + //encode both key and value: https://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1 + if (!output.empty()) + output.pop_back(); + return output; +} + + +std::vector<std::pair<std::string, std::string>> zen::xWwwFormUrlDecode(const std::string& str) +{ + std::vector<std::pair<std::string, std::string>> output; + + for (const std::string& nvPair : split(str, '&', SplitType::SKIP_EMPTY)) + output.emplace_back(urldecode(beforeFirst(nvPair, '=', IF_MISSING_RETURN_ALL)), + urldecode(afterFirst (nvPair, '=', IF_MISSING_RETURN_NONE))); + return output; +} + + +HttpInputStream zen::sendHttpPost(const Zstring& url, const Zstring& userAgent, const IOCallback& notifyUnbufferedIO, + const std::vector<std::pair<std::string, std::string>>& postParams) //throw SysError +{ + return sendHttpRequestImpl(url, userAgent, notifyUnbufferedIO, &postParams); //throw SysError +} + + +HttpInputStream zen::sendHttpGet(const Zstring& url, const Zstring& userAgent, const IOCallback& notifyUnbufferedIO) //throw SysError +{ + return sendHttpRequestImpl(url, userAgent, notifyUnbufferedIO, nullptr); //throw SysError +} + + +bool zen::internetIsAlive() //noexcept +{ + try + { + auto response = std::make_unique<HttpInputStream::Impl>(Zstr("http://www.google.com/"), + Zstr("FreeFileSync"), + nullptr /*notifyUnbufferedIO*/, + nullptr /*postParams*/); //throw SysError + const int statusCode = response->getStatusCode(); + + //attention: http://www.google.com/ might redirect to "https" => don't follow, just return "true"!!! + return statusCode / 100 == 2 || //e.g. 200 + statusCode / 100 == 3; //e.g. 301, 302, 303, 307... when in doubt, consider internet alive! + } + catch (SysError&) { return false; } +} @@ -7,16 +7,15 @@ #ifndef HTTP_H_879083425703425702 #define HTTP_H_879083425703425702 -#include <zen/file_error.h> +#include <zen/zstring.h> +#include <zen/sys_error.h> #include <zen/serialize.h> namespace zen { /* - TREAD-SAFETY - ------------ - Windows: WinInet-based => may be called from worker thread, supports HTTPS - Linux: wxWidgets-based => don't call from worker thread + - thread-safe! (Window/Linux/macOS) + - HTTPS supported only for Windows */ class HttpInputStream { @@ -29,7 +28,7 @@ public: class Impl; HttpInputStream(std::unique_ptr<Impl>&& pimpl); - HttpInputStream(HttpInputStream&&) = default; + HttpInputStream(HttpInputStream&&) noexcept = default; ~HttpInputStream(); private: @@ -37,9 +36,9 @@ private: }; -HttpInputStream sendHttpGet (const std::wstring& url, const std::wstring& userAgent, const IOCallback& notifyUnbufferedIO); //throw SysError -HttpInputStream sendHttpPost(const std::wstring& url, const std::wstring& userAgent, const IOCallback& notifyUnbufferedIO, - const std::vector<std::pair<std::string, std::string>>& postParams); //throw SysError +HttpInputStream sendHttpGet (const Zstring& url, const Zstring& userAgent, const IOCallback& notifyUnbufferedIO /*throw X*/); //throw SysError +HttpInputStream sendHttpPost(const Zstring& url, const Zstring& userAgent, const IOCallback& notifyUnbufferedIO /*throw X*/, // + const std::vector<std::pair<std::string, std::string>>& postParams); bool internetIsAlive(); //noexcept std::string xWwwFormUrlEncode(const std::vector<std::pair<std::string, std::string>>& paramPairs); @@ -95,7 +95,7 @@ std::wstring translate(const std::wstring& text) template <class T> inline std::wstring translate(const std::wstring& singular, const std::wstring& plural, T n) { - static_assert(sizeof(n) <= sizeof(int64_t), ""); + static_assert(sizeof(n) <= sizeof(int64_t)); const auto n64 = static_cast<int64_t>(n); assert(contains(plural, L"%x")); diff --git a/zen/legacy_compiler.h b/zen/legacy_compiler.h new file mode 100755 index 00000000..5a0013b3 --- /dev/null +++ b/zen/legacy_compiler.h @@ -0,0 +1,89 @@ +// ***************************************************************************** +// * 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 LEGACY_COMPILER_H_839567308565656789 +#define LEGACY_COMPILER_H_839567308565656789 + + #include <memory> + #include <cstddef> //std::byte + + +namespace std +{ +//https://gcc.gnu.org/onlinedocs/libstdc++/manual/status.html +//https://isocpp.org/std/standing-documents/sd-6-sg10-feature-test-recommendations + +#ifndef __cpp_lib_type_trait_variable_templates //GCC 7.1 + template<class T, class U> constexpr bool is_same_v = is_same<T, U>::value; + template<class T> constexpr bool is_const_v = is_const<T>::value; + template<class T> constexpr bool is_signed_v = is_signed<T>::value; + template<class T> constexpr bool is_trivially_destructible_v = is_trivially_destructible<T>::value; +#endif + +#if __GNUC__ < 7 || (__GNUC__ == 7 && __GNUC_MINOR__ < 1) //GCC doesn't define __cpp_lib_raw_memory_algorithms +template<class T> void destroy_at(T* p) { p->~T(); } + +template< class ForwardIt > +void destroy(ForwardIt first, ForwardIt last) +{ + for (; first != last; ++first) + std::destroy_at(std::addressof(*first)); +} + +template<class InputIt, class ForwardIt> +ForwardIt uninitialized_move(InputIt first, InputIt last, ForwardIt trg_first) +{ + typedef typename std::iterator_traits<ForwardIt>::value_type Value; + ForwardIt current = trg_first; + try + { + for (; first != last; ++first, ++current) + ::new (static_cast<void*>(std::addressof(*current))) Value(std::move(*first)); + return current; + } + catch (...) + { + for (; trg_first != current; ++trg_first) + trg_first->~Value(); + throw; + } +} +#endif + +#ifndef __cpp_lib_apply //GCC 7.1 +template <class F, class T0, class T1, class T2> +constexpr decltype(auto) apply(F&& f, std::tuple<T0, T1, T2>& t) { return f(std::get<0>(t), std::get<1>(t), std::get<2>(t)); } +#endif + +#if __GNUC__ < 7 || (__GNUC__ == 7 && __GNUC_MINOR__ < 1) //__cpp_lib_byte not defined before GCC 7.3 but supported earlier + typedef unsigned char byte; +#endif + +#ifndef __cpp_lib_bool_constant //GCC 6.1 + template<bool B> using bool_constant = integral_constant<bool, B>; +#endif + +//================================================================================ + +} +namespace __cxxabiv1 +{ +struct __cxa_eh_globals; +extern "C" __cxa_eh_globals* __cxa_get_globals() noexcept; +} +namespace std +{ +inline int uncaught_exceptions_legacy_hack() noexcept +{ + return *(reinterpret_cast<unsigned int*>(static_cast<char*>(static_cast<void*>(__cxxabiv1::__cxa_get_globals())) + sizeof(void*))); +} +#ifndef __cpp_lib_uncaught_exceptions //GCC 6.1 +inline int uncaught_exceptions() noexcept { return uncaught_exceptions_legacy_hack(); } +#endif + +} + +#endif //LEGACY_COMPILER_H_839567308565656789 diff --git a/zen/optional.h b/zen/optional.h index 88928ac0..e4605c5f 100755 --- a/zen/optional.h +++ b/zen/optional.h @@ -7,7 +7,6 @@ #ifndef OPTIONAL_H_2857428578342203589 #define OPTIONAL_H_2857428578342203589 -//#include <cassert> #include <type_traits> @@ -51,16 +50,6 @@ public: ~Opt() { if (T* val = get()) val->~T(); } - Opt& operator=(NoValue) //support assignment to Opt<const T> - { - if (T* val = get()) - { - valid_ = false; - val->~T(); - } - return *this; - } - Opt& operator=(const Opt& other) //strong exception-safety iff T::operator=() is strongly exception-safe { if (T* val = get()) @@ -81,6 +70,16 @@ public: return *this; } + Opt& operator=(NoValue) //support assignment to Opt<const T> + { + if (T* val = get()) + { + valid_ = false; + val->~T(); + } + return *this; + } + explicit operator bool() const { return valid_; } //thank you, C++11!!! const T* get() const { return valid_ ? reinterpret_cast<const T*>(&rawMem_) : nullptr; } @@ -8,8 +8,8 @@ #define PERF_H_83947184145342652456 #include <chrono> -#include "deprecate.h" #include "scope_guard.h" +#include "string_tools.h" #include <iostream> @@ -31,7 +31,7 @@ namespace zen class PerfTimer { public: - ZEN_DEPRECATE PerfTimer() {} + [[deprecated]] PerfTimer() {} ~PerfTimer() { if (!resultShown_) showResult(); } @@ -75,7 +75,8 @@ public: if (wasRunning) pause(); //don't include call to MessageBox()! ZEN_ON_SCOPE_EXIT(if (wasRunning) resume()); - std::clog << "Perf: duration: " << timeMs() << " ms\n"; + const std::string msg = numberTo<std::string>(timeMs()) + " ms"; + std::clog << "Perf: duration: " << msg << "\n"; resultShown_ = true; } diff --git a/zen/ring_buffer.h b/zen/ring_buffer.h new file mode 100755 index 00000000..1a67c452 --- /dev/null +++ b/zen/ring_buffer.h @@ -0,0 +1,230 @@ +// ***************************************************************************** +// * 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 RING_BUFFER_H_01238467085684139453534 +#define RING_BUFFER_H_01238467085684139453534 + +#include <cassert> +#include <vector> +#include <stdexcept> +#include "type_traits.h" +#include "scope_guard.h" + + +namespace zen +{ +//basically a std::deque<> but with a non-garbage implementation => circular buffer with std::vector<>-like exponential growth! +//https://stackoverflow.com/questions/39324192/why-is-an-stl-deque-not-implemented-as-just-a-circular-vector + +template <class T> +class RingBuffer +{ +public: + RingBuffer() {} + + RingBuffer(RingBuffer&& tmp) noexcept : rawMem_(std::move(tmp.rawMem_)), capacity_(tmp.capacity_), bufStart_(tmp.bufStart_), size_(tmp.size_) + { + tmp.capacity_ = tmp.bufStart_ = tmp.size_ = 0; + } + RingBuffer& operator=(RingBuffer&& tmp) noexcept { swap(tmp); return *this; } //noexcept *required* to support move for reallocations in std::vector and std::swap!!! + + using value_type = T; + using reference = T&; + using const_reference = const T&; + + size_t size() const { return size_; } + bool empty() const { return size_ == 0; } + + ~RingBuffer() { clear(); } + + reference front() { return getBufPtr()[bufStart_]; } + const_reference front() const { return getBufPtr()[bufStart_]; } + + template <class U> + void push_back(U&& value) + { + checkInvariants(); + reserve(size_ + 1); //throw ? + ::new (getBufPtr() + getBufPos(size_)) T(std::forward<U>(value)); //throw ? + ++size_; + } + + void pop_front() + { + checkInvariants(); + if (empty()) + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); + + front().~T(); + ++bufStart_; + --size_; + + if (size_ == 0 || bufStart_ == capacity_) + bufStart_ = 0; + } + + void clear() + { + checkInvariants(); + + const size_t frontSize = std::min(size_, capacity_ - bufStart_); + + std::destroy(getBufPtr() + bufStart_, getBufPtr() + bufStart_ + frontSize); + std::destroy(getBufPtr(), getBufPtr() + size_ - frontSize); + bufStart_ = size_ = 0; + } + + template <class Iterator> + void insert_back(Iterator first, Iterator last) //throw ? (strong exception-safety!) + { + checkInvariants(); + + const size_t len = last - first; + reserve(size_ + len); //throw ? + + const size_t endPos = getBufPos(size_); + const size_t tailSize = std::min(len, capacity_ - endPos); + + std::uninitialized_copy(first, first + tailSize, getBufPtr() + endPos); //throw ? + ZEN_ON_SCOPE_FAIL(std::destroy(first, first + tailSize)); + std::uninitialized_copy(first + tailSize, last, getBufPtr()); //throw ? + + size_ += len; + } + + //contract: last - first <= size() + template <class Iterator> + void extract_front(Iterator first, Iterator last) //throw ? strongly exception-safe! (but only basic exception safety for [first, last) range) + { + checkInvariants(); + + const size_t len = last - first; + if (size_ < len) + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); + + const size_t frontSize = std::min(len, capacity_ - bufStart_); + + auto itTrg = std::copy(getBufPtr() + bufStart_, getBufPtr() + bufStart_ + frontSize, first); //throw ? + /**/ std::copy(getBufPtr(), getBufPtr() + len - frontSize, itTrg); // + + std::destroy(getBufPtr() + bufStart_, getBufPtr() + bufStart_ + frontSize); + std::destroy(getBufPtr(), getBufPtr() + len - frontSize); + + bufStart_ += len; + size_ -= len; + + if (size_ == 0) + bufStart_ = 0; + else if (bufStart_ >= capacity_) + bufStart_ -= capacity_; + } + + void swap(RingBuffer& other) + { + std::swap(rawMem_, other.rawMem_); + std::swap(capacity_, other.capacity_); + std::swap(bufStart_, other.bufStart_); + std::swap(size_, other.size_); + } + + void reserve(size_t minSize) //throw ? (strong exception-safety!) + { + if (minSize > capacity_) + { + const size_t newCapacity = std::max(minSize + minSize / 2, minSize); //no minimum capacity: just like std::vector<> implementation + + RingBuffer newBuf(newCapacity); //throw ? + + T* itTrg = reinterpret_cast<T*>(newBuf.rawMem_.get()); + + const size_t frontSize = std::min(size_, capacity_ - bufStart_); + + itTrg = uninitializedMoveIfNoexcept(getBufPtr() + bufStart_, getBufPtr() + bufStart_ + frontSize, itTrg); //throw ? + newBuf.size_ = frontSize; //pass ownership + /**/ uninitializedMoveIfNoexcept(getBufPtr(), getBufPtr() + size_ - frontSize, itTrg); //throw ? + newBuf.size_ = size_; // + + newBuf.swap(*this); + } + } + + const T& operator[](size_t offset) const + { + assert(offset < size()); //design by contract! no runtime check! + return getBufPtr()[getBufPos(offset)]; + } + + T& operator[](size_t offset) { return const_cast<T&>(static_cast<const RingBuffer*>(this)->operator[](offset)); } + + template <class Container, class Value> + class Iterator + { + public: + Iterator(Container& container, size_t offset) : container_(&container), offset_(offset) {} + Iterator& operator++() { ++offset_; return *this; } + inline friend bool operator==(const Iterator& lhs, const Iterator& rhs) { assert(lhs.container_ == rhs.container_); return lhs.offset_ == rhs.offset_; } + inline friend bool operator!=(const Iterator& lhs, const Iterator& rhs) { return !(lhs == rhs); } + Value& operator* () const { return (*container_)[offset_]; } + Value* operator->() const { return &(*container_)[offset_]; } + private: + Container* container_ = nullptr; + size_t offset_ = 0; + }; + using iterator = Iterator< RingBuffer, T>; + using const_iterator = Iterator<const RingBuffer, const T>; + + 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 cbegin() const { return begin(); } + const_iterator cend () const { return end (); } + +private: + RingBuffer (const RingBuffer&) = delete; //wait until there is a reason to copy a RingBuffer + RingBuffer& operator=(const RingBuffer&) = delete; // + + RingBuffer(size_t capacity) : + rawMem_(static_cast<std::byte*>(::operator new (capacity * sizeof(T)))), //throw std::bad_alloc + capacity_(capacity) {} + + struct FreeStoreDelete { void operator()(std::byte* p) const { ::operator delete (p); } }; + + T* getBufPtr() { return reinterpret_cast<T*>(rawMem_.get()); } + const T* getBufPtr() const { return reinterpret_cast<T*>(rawMem_.get()); } + + //unlike pure std::uninitialized_move, this one allows for strong exception-safety! + static T* uninitializedMoveIfNoexcept(T* first, T* last, T* firstTrg) + { + return uninitializedMoveIfNoexcept(first, last, firstTrg, std::is_nothrow_move_constructible<T>()); + } + static T* uninitializedMoveIfNoexcept(T* first, T* last, T* firstTrg, std::true_type ) { return std::uninitialized_move(first, last, firstTrg); } + static T* uninitializedMoveIfNoexcept(T* first, T* last, T* firstTrg, std::false_type) { return std::uninitialized_copy(first, last, firstTrg); } //throw ? + + void checkInvariants() + { + assert(bufStart_ == 0 || bufStart_ < capacity_); + assert(size_ <= capacity_); + } + + size_t getBufPos(size_t offset) const //< capacity_ + { + size_t bufPos = bufStart_ + offset; + if (bufPos >= capacity_) + bufPos -= capacity_; + return bufPos; + } + + std::unique_ptr<std::byte, FreeStoreDelete> rawMem_; + size_t capacity_ = 0; //as number of T + size_t bufStart_ = 0; //< capacity_ + size_t size_ = 0; //<= capacity_ +}; +} + +#endif //RING_BUFFER_H_01238467085684139453534 diff --git a/zen/scope_guard.h b/zen/scope_guard.h index 6a732c9f..d056bb2a 100755 --- a/zen/scope_guard.h +++ b/zen/scope_guard.h @@ -9,23 +9,7 @@ #include <cassert> #include <exception> -#include "type_tools.h" - - -//std::uncaught_exceptions() currently unsupported on GCC and Clang => clean up ASAP - static_assert(__GNUC__ < 7 || (__GNUC__ == 7 && (__GNUC_MINOR__ < 3 || (__GNUC_MINOR__ == 3 && __GNUC_PATCHLEVEL__ <= 1))), "check std::uncaught_exceptions support"); - -namespace __cxxabiv1 -{ -struct __cxa_eh_globals; -extern "C" __cxa_eh_globals* __cxa_get_globals() noexcept; -} - -inline -int getUncaughtExceptionCount() -{ - return *(reinterpret_cast<unsigned int*>(static_cast<char*>(static_cast<void*>(__cxxabiv1::__cxa_get_globals())) + sizeof(void*))); -} +#include "type_traits.h" //best of Zen, Loki and C++17 @@ -53,7 +37,7 @@ enum class ScopeGuardRunMode //partially specialize scope guard destructor code and get rid of those pesky MSVC "4127 conditional expression is constant" template <typename F> inline -void runScopeGuardDestructor(F& fun, int /*exeptionCountOld*/, StaticEnum<ScopeGuardRunMode, ScopeGuardRunMode::ON_EXIT>) +void runScopeGuardDestructor(F& fun, int /*exeptionCountOld*/, std::integral_constant<ScopeGuardRunMode, ScopeGuardRunMode::ON_EXIT>) { try { fun(); } catch (...) { assert(false); } //consistency: don't expect exceptions for ON_EXIT even if "!failed"! @@ -61,18 +45,18 @@ void runScopeGuardDestructor(F& fun, int /*exeptionCountOld*/, StaticEnum<ScopeG template <typename F> inline -void runScopeGuardDestructor(F& fun, int exeptionCountOld, StaticEnum<ScopeGuardRunMode, ScopeGuardRunMode::ON_SUCCESS>) +void runScopeGuardDestructor(F& fun, int exeptionCountOld, std::integral_constant<ScopeGuardRunMode, ScopeGuardRunMode::ON_SUCCESS>) { - const bool failed = getUncaughtExceptionCount() > exeptionCountOld; + const bool failed = std::uncaught_exceptions() > exeptionCountOld; if (!failed) fun(); //throw X } template <typename F> inline -void runScopeGuardDestructor(F& fun, int exeptionCountOld, StaticEnum<ScopeGuardRunMode, ScopeGuardRunMode::ON_FAIL>) +void runScopeGuardDestructor(F& fun, int exeptionCountOld, std::integral_constant<ScopeGuardRunMode, ScopeGuardRunMode::ON_FAIL>) { - const bool failed = getUncaughtExceptionCount() > exeptionCountOld; + const bool failed = std::uncaught_exceptions() > exeptionCountOld; if (failed) try { fun(); } catch (...) { assert(false); } @@ -93,7 +77,7 @@ public: ~ScopeGuard() noexcept(runMode != ScopeGuardRunMode::ON_SUCCESS) { if (!dismissed_) - runScopeGuardDestructor(fun_, exeptionCount_, StaticEnum<ScopeGuardRunMode, runMode>()); + runScopeGuardDestructor(fun_, exeptionCount_, std::integral_constant<ScopeGuardRunMode, runMode>()); } void dismiss() { dismissed_ = true; } @@ -103,7 +87,7 @@ private: ScopeGuard& operator=(const ScopeGuard&) = delete; F fun_; - const int exeptionCount_ = getUncaughtExceptionCount(); + const int exeptionCount_ = std::uncaught_exceptions(); bool dismissed_ = false; }; diff --git a/zen/serialize.h b/zen/serialize.h index 381d102a..16375cff 100755 --- a/zen/serialize.h +++ b/zen/serialize.h @@ -21,7 +21,7 @@ namespace zen -------------------------- |Binary Container Concept| -------------------------- -binary container for data storage: must support "basic" std::vector interface (e.g. std::vector<char>, std::string, Zbase<char>) +binary container for data storage: must support "basic" std::vector interface (e.g. std::vector<std::byte>, std::string, Zbase<char>) */ //binary container reference implementations @@ -29,12 +29,12 @@ using Utf8String = Zbase<char>; //ref-counted + COW text stream + guaranteed per class ByteArray; //ref-counted byte stream + guaranteed performance: exponential growth -> no COW, but 12% faster than Utf8String (due to no null-termination?) -class ByteArray //essentially a std::vector<char> with ref-counted semantics, but no COW! => *almost* value type semantics, but not quite +class ByteArray //essentially a std::vector<std::byte> with ref-counted semantics, but no COW! => *almost* value type semantics, but not quite { public: - using value_type = std::vector<char>::value_type; - using iterator = std::vector<char>::iterator; - using const_iterator = std::vector<char>::const_iterator; + using value_type = std::vector<std::byte>::value_type; + using iterator = std::vector<std::byte>::iterator; + using const_iterator = std::vector<std::byte>::const_iterator; iterator begin() { return buffer_->begin(); } iterator end () { return buffer_->end (); } @@ -49,7 +49,7 @@ public: inline friend bool operator==(const ByteArray& lhs, const ByteArray& rhs) { return *lhs.buffer_ == *rhs.buffer_; } private: - std::shared_ptr<std::vector<char>> buffer_ { std::make_shared<std::vector<char>>() }; //always bound! + std::shared_ptr<std::vector<std::byte>> buffer_ = std::make_shared<std::vector<std::byte>>(); //always bound! //perf: shared_ptr indirection irrelevant: less than 1% slower! }; @@ -122,10 +122,11 @@ struct MemoryStreamIn size_t read(void* buffer, size_t bytesToRead) //return "bytesToRead" bytes unless end of stream! { - static_assert(sizeof(typename BinContainer::value_type) == 1, ""); //expect: bytes + using Byte = typename BinContainer::value_type; + static_assert(sizeof(Byte) == 1); const size_t bytesRead = std::min(bytesToRead, buffer_.size() - pos_); auto itFirst = buffer_.begin() + pos_; - std::copy(itFirst, itFirst + bytesRead, static_cast<char*>(buffer)); + std::copy(itFirst, itFirst + bytesRead, static_cast<Byte*>(buffer)); pos_ += bytesRead; return bytesRead; } @@ -147,9 +148,11 @@ struct MemoryStreamOut void write(const void* buffer, size_t bytesToWrite) { - static_assert(sizeof(typename BinContainer::value_type) == 1, ""); //expect: bytes + using Byte = typename BinContainer::value_type; + static_assert(sizeof(Byte) == 1); buffer_.resize(buffer_.size() + bytesToWrite); - std::copy(static_cast<const char*>(buffer), static_cast<const char*>(buffer) + bytesToWrite, buffer_.end() - bytesToWrite); + const auto it = static_cast<const Byte*>(buffer); + std::copy(it, it + bytesToWrite, buffer_.end() - bytesToWrite); } const BinContainer& ref() const { return buffer_; } @@ -177,7 +180,7 @@ void bufferedStreamCopy(BufferedInputStream& streamIn, //throw X if (blockSize == 0) throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); - std::vector<char> buffer(blockSize); + std::vector<std::byte> buffer(blockSize); for (;;) { const size_t bytesRead = streamIn.read(&buffer[0], blockSize); //throw X; return "bytesToRead" bytes unless end of stream! @@ -192,7 +195,7 @@ void bufferedStreamCopy(BufferedInputStream& streamIn, //throw X template <class BinContainer, class BufferedInputStream> inline BinContainer bufferedLoad(BufferedInputStream& streamIn) //throw X { - static_assert(sizeof(typename BinContainer::value_type) == 1, ""); //expect: bytes + static_assert(sizeof(typename BinContainer::value_type) == 1); //expect: bytes const size_t blockSize = streamIn.getBlockSize(); if (blockSize == 0) @@ -221,7 +224,7 @@ void writeArray(BufferedOutputStream& stream, const void* buffer, size_t len) template <class N, class BufferedOutputStream> inline void writeNumber(BufferedOutputStream& stream, const N& num) { - static_assert(IsArithmetic<N>::value || IsSameType<N, bool>::value, "not a number!"); + static_assert(IsArithmetic<N>::value || std::is_same_v<N, bool>); writeArray(stream, &num, sizeof(N)); } @@ -249,7 +252,7 @@ void readArray(BufferedInputStream& stream, void* buffer, size_t len) //throw Un template <class N, class BufferedInputStream> inline N readNumber(BufferedInputStream& stream) //throw UnexpectedEndOfStreamError { - static_assert(IsArithmetic<N>::value || IsSameType<N, bool>::value, ""); + static_assert(IsArithmetic<N>::value || std::is_same_v<N, bool>); N num = 0; readArray(stream, &num, sizeof(N)); //throw UnexpectedEndOfStreamError return num; diff --git a/zen/socket.h b/zen/socket.h new file mode 100755 index 00000000..c92071e2 --- /dev/null +++ b/zen/socket.h @@ -0,0 +1,84 @@ +// ***************************************************************************** +// * 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 SOCKET_H_23498325972583947678456437 +#define SOCKET_H_23498325972583947678456437 + +#include <zen/zstring.h> +#include "sys_error.h" + #include <unistd.h> //close + #include <sys/socket.h> + #include <netdb.h> //getaddrinfo + + +namespace zen +{ +#define THROW_LAST_SYS_ERROR_WSA(functionName) \ + do { const ErrorCode ecInternal = getLastError(); throw SysError(formatSystemError(functionName, ecInternal)); } while (false) + + +//Winsock needs to be initialized before calling any of these functions! (WSAStartup/WSACleanup) + +class Socket //throw SysError +{ +public: + Socket(const Zstring& server, const Zstring& serviceName) //throw SysError + { + ::addrinfo hints = {}; + hints.ai_socktype = SOCK_STREAM; //we *do* care about this one! + hints.ai_flags = AI_ADDRCONFIG; //save a AAAA lookup on machines that can't use the returned data anyhow + + ::addrinfo* servinfo = nullptr; + ZEN_ON_SCOPE_EXIT(if (servinfo) ::freeaddrinfo(servinfo)); + + const int rcGai = ::getaddrinfo(server.c_str(), serviceName.c_str(), &hints, &servinfo); + if (rcGai != 0) + throw SysError(formatSystemError(L"getaddrinfo", replaceCpy(_("Error Code %x"), L"%x", numberTo<std::wstring>(rcGai)), utfTo<std::wstring>(::gai_strerror(rcGai)))); + if (!servinfo) + throw SysError(L"getaddrinfo: empty server info"); + + auto getConnectedSocket = [&](const auto& /*::addrinfo*/ ai) + { + SocketType testSocket = ::socket(ai.ai_family, ai.ai_socktype, ai.ai_protocol); + if (testSocket == invalidSocket) + THROW_LAST_SYS_ERROR_WSA(L"socket"); + ZEN_ON_SCOPE_FAIL(closeSocket(testSocket)); + + if (::connect(testSocket, ai.ai_addr, static_cast<int>(ai.ai_addrlen)) != 0) + THROW_LAST_SYS_ERROR_WSA(L"connect"); + + return testSocket; + }; + + Opt<SysError> firstError; + for (const auto* /*::addrinfo*/ si = servinfo; si; si = si->ai_next) + try + { + socket_ = getConnectedSocket(*si); //throw SysError; pass ownership + return; + } + catch (const SysError& e) { if (!firstError) firstError = e; } + + throw* firstError; //list was not empty, so there must have been an error! + } + + ~Socket() { closeSocket(socket_); } + + using SocketType = int; + SocketType get() const { return socket_; } + +private: + Socket (const Socket&) = delete; + Socket& operator=(const Socket&) = delete; + + static const SocketType invalidSocket = -1; + static void closeSocket(SocketType s) { ::close(s); } + + SocketType socket_ = invalidSocket; +}; +} + +#endif //SOCKET_H_23498325972583947678456437 diff --git a/zen/stl_tools.h b/zen/stl_tools.h index f09639e1..2ce2cf33 100755 --- a/zen/stl_tools.h +++ b/zen/stl_tools.h @@ -12,7 +12,7 @@ #include <vector> #include <memory> #include <algorithm> -#include "type_tools.h" +#include "type_traits.h" #include "build_info.h" @@ -54,10 +54,6 @@ template <class BidirectionalIterator1, class BidirectionalIterator2> BidirectionalIterator1 search_last(BidirectionalIterator1 first1, BidirectionalIterator1 last1, BidirectionalIterator2 first2, BidirectionalIterator2 last2); -template <class InputIterator1, class InputIterator2> -bool equal(InputIterator1 first1, InputIterator1 last1, - InputIterator2 first2, InputIterator2 last2); - template <class Num, class ByteIterator> Num hashBytes (ByteIterator first, ByteIterator last); template <class Num, class ByteIterator> Num hashBytesAppend(Num hashVal, ByteIterator first, ByteIterator last); @@ -69,7 +65,7 @@ struct StringHash { const auto* strFirst = strBegin(str); return hashBytes<size_t>(reinterpret_cast<const char*>(strFirst), - reinterpret_cast<const char*>(strFirst + strLength(str))); + reinterpret_cast<const char*>(strFirst + strLength(str))); } }; @@ -181,35 +177,25 @@ BidirectionalIterator1 search_last(const BidirectionalIterator1 first1, Bi } -template <class InputIterator1, class InputIterator2> inline -bool equal(InputIterator1 first1, InputIterator1 last1, - InputIterator2 first2, InputIterator2 last2) -{ - return last1 - first1 == last2 - first2 && std::equal(first1, last1, first2); -} - - - - //FNV-1a: http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function template <class Num, class ByteIterator> inline -Num hashBytes(ByteIterator first, ByteIterator last) +Num hashBytes(ByteIterator first, ByteIterator last) { - static_assert(std::is_integral<Num>::value, ""); - static_assert(sizeof(Num) == 4 || sizeof(Num) == 8, ""); //macOS: size_t is "unsigned long" - const Num base = sizeof(Num) == 4 ? 2166136261U : 14695981039346656037ULL; + static_assert(IsInteger<Num>::value); + static_assert(sizeof(Num) == 4 || sizeof(Num) == 8); //macOS: size_t is "unsigned long" + constexpr Num base = sizeof(Num) == 4 ? 2166136261U : 14695981039346656037ULL; - return hashBytesAppend(base, first, last); + return hashBytesAppend(base, first, last); } template <class Num, class ByteIterator> inline Num hashBytesAppend(Num hashVal, ByteIterator first, ByteIterator last) { - static_assert(sizeof(typename std::iterator_traits<ByteIterator>::value_type) == 1, ""); - const Num prime = sizeof(Num) == 4 ? 16777619U : 1099511628211ULL; + static_assert(sizeof(typename std::iterator_traits<ByteIterator>::value_type) == 1); + constexpr Num prime = sizeof(Num) == 4 ? 16777619U : 1099511628211ULL; - for (; first != last; ++first) + for (; first != last; ++first) { hashVal ^= static_cast<Num>(*first); hashVal *= prime; diff --git a/zen/string_base.h b/zen/string_base.h index 2d043d4f..9632eba4 100755 --- a/zen/string_base.h +++ b/zen/string_base.h @@ -27,9 +27,9 @@ Allocator Policy: class AllocatorOptimalSpeed //exponential growth + min size { protected: - //::operator new/ ::operator delete show same performance characterisics like malloc()/free()! - static void* allocate(size_t size) { return ::malloc(size); } //throw std::bad_alloc - static void deallocate(void* ptr) { ::free(ptr); } + //::operator new/delete show same performance characterisics like malloc()/free()! + static void* allocate(size_t size) { return ::operator new (size); } //throw std::bad_alloc + static void deallocate(void* ptr) { ::operator delete (ptr); } static size_t calcCapacity(size_t length) { return std::max<size_t>(16, std::max(length + length / 2, length)); } //- size_t might overflow! => better catch here than return a too small size covering up the real error: a way too large length! //- any growth rate should not exceed golden ratio: 1.618033989 @@ -39,8 +39,8 @@ protected: class AllocatorOptimalMemory //no wasted memory, but more reallocations required when manipulating string { protected: - static void* allocate(size_t size) { return ::malloc(size); } //throw std::bad_alloc - static void deallocate(void* ptr) { ::free(ptr); } + static void* allocate(size_t size) { return ::operator new (size); } //throw std::bad_alloc + static void deallocate(void* ptr) { ::operator delete (ptr); } static size_t calcCapacity(size_t length) { return length; } }; @@ -114,7 +114,7 @@ private: capacity(static_cast<uint32_t>(cap)) {} uint32_t length; - uint32_t capacity; //allocated size without null-termination + const uint32_t capacity; //allocated size without null-termination }; static Descriptor* descr( Char* ptr) { return reinterpret_cast< Descriptor*>(ptr) - 1; } @@ -187,11 +187,15 @@ private: { Descriptor(size_t len, size_t cap) : length (static_cast<uint32_t>(len)), - capacity(static_cast<uint32_t>(cap)) { static_assert(ATOMIC_INT_LOCK_FREE == 2, ""); } //2: "The atomic type is always lock-free" + capacity(static_cast<uint32_t>(cap)) + { + static_assert(ATOMIC_INT_LOCK_FREE == 2); //2: "The atomic type is always lock-free" + //static_assert(decltype(refCount)::is_always_lock_free); //C++17 variant (not yet supported on GCC 6.3) + } std::atomic<unsigned int> refCount { 1 }; //std:atomic is uninitialized by default! uint32_t length; - uint32_t capacity; //allocated size without null-termination + const uint32_t capacity; //allocated size without null-termination }; static Descriptor* descr( Char* ptr) { return reinterpret_cast< Descriptor*>(ptr) - 1; } @@ -234,8 +238,10 @@ public: iterator begin(); iterator end (); + const_iterator begin () const { return rawStr_; } const_iterator end () const { return rawStr_ + length(); } + const_iterator cbegin() const { return begin(); } const_iterator cend () const { return end (); } @@ -357,7 +363,7 @@ Zbase<Char, SP>::Zbase(Zbase<Char, SP>&& tmp) noexcept template <class Char, template <class> class SP> inline Zbase<Char, SP>::~Zbase() { - static_assert(noexcept(this->~Zbase()), ""); //has exception spec of compiler-generated destructor by default + static_assert(noexcept(this->~Zbase())); //has exception spec of compiler-generated destructor by default this->destroy(rawStr_); //rawStr_ may be nullptr; see move constructor! } diff --git a/zen/string_tools.h b/zen/string_tools.h index 7734b6f0..58cb6ea6 100755 --- a/zen/string_tools.h +++ b/zen/string_tools.h @@ -127,7 +127,7 @@ bool isWhiteSpace(wchar_t c) template <class Char> inline bool isDigit(Char c) //similar to implmenetation of std::isdigit()! { - static_assert(IsSameType<Char, char>::value || IsSameType<Char, wchar_t>::value, ""); + static_assert(std::is_same_v<Char, char> || std::is_same_v<Char, wchar_t>); return static_cast<Char>('0') <= c && c <= static_cast<Char>('9'); } @@ -135,7 +135,7 @@ bool isDigit(Char c) //similar to implmenetation of std::isdigit()! template <class Char> inline bool isHexDigit(Char c) { - static_assert(IsSameType<Char, char>::value || IsSameType<Char, wchar_t>::value, ""); + static_assert(std::is_same_v<Char, char> || std::is_same_v<Char, wchar_t>); return (static_cast<Char>('0') <= c && c <= static_cast<Char>('9')) || (static_cast<Char>('A') <= c && c <= static_cast<Char>('F')) || (static_cast<Char>('a') <= c && c <= static_cast<Char>('f')); @@ -145,7 +145,7 @@ bool isHexDigit(Char c) template <class Char> inline bool isAsciiAlpha(Char c) { - static_assert(IsSameType<Char, char>::value || IsSameType<Char, wchar_t>::value, ""); + static_assert(std::is_same_v<Char, char> || std::is_same_v<Char, wchar_t>); return (static_cast<Char>('A') <= c && c <= static_cast<Char>('Z')) || (static_cast<Char>('a') <= c && c <= static_cast<Char>('z')); } @@ -209,7 +209,7 @@ template <class S, class T> inline bool strEqual (const S& lhs, const T& rhs template <class S, class T> inline bool contains(const S& str, const T& term) { - static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, ""); + static_assert(std::is_same_v<GetCharTypeT<S>, GetCharTypeT<T>>); const size_t strLen = strLength(str); const size_t termLen = strLength(term); if (strLen < termLen) @@ -227,7 +227,7 @@ bool contains(const S& str, const T& term) template <class S, class T> inline S afterLast(const S& str, const T& term, FailureReturnVal rv) { - static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, ""); + static_assert(std::is_same_v<GetCharTypeT<S>, GetCharTypeT<T>>); const size_t termLen = strLength(term); assert(termLen > 0); @@ -248,7 +248,7 @@ S afterLast(const S& str, const T& term, FailureReturnVal rv) template <class S, class T> inline S beforeLast(const S& str, const T& term, FailureReturnVal rv) { - static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, ""); + static_assert(std::is_same_v<GetCharTypeT<S>, GetCharTypeT<T>>); const size_t termLen = strLength(term); assert(termLen > 0); @@ -268,7 +268,7 @@ S beforeLast(const S& str, const T& term, FailureReturnVal rv) template <class S, class T> inline S afterFirst(const S& str, const T& term, FailureReturnVal rv) { - static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, ""); + static_assert(std::is_same_v<GetCharTypeT<S>, GetCharTypeT<T>>); const size_t termLen = strLength(term); assert(termLen > 0); @@ -289,7 +289,7 @@ S afterFirst(const S& str, const T& term, FailureReturnVal rv) template <class S, class T> inline S beforeFirst(const S& str, const T& term, FailureReturnVal rv) { - static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, ""); + static_assert(std::is_same_v<GetCharTypeT<S>, GetCharTypeT<T>>); const size_t termLen = strLength(term); assert(termLen > 0); @@ -309,7 +309,7 @@ S beforeFirst(const S& str, const T& term, FailureReturnVal rv) template <class S, class T> inline std::vector<S> split(const S& str, const T& delimiter, SplitType st) { - static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, ""); + static_assert(std::is_same_v<GetCharTypeT<S>, GetCharTypeT<T>>); const size_t delimLen = strLength(delimiter); assert(delimLen > 0); if (delimLen == 0) @@ -346,18 +346,18 @@ ZEN_INIT_DETECT_MEMBER(append); //either call operator+=(S(str, len)) or append(str, len) template <class S, class InputIterator> inline -typename EnableIf<HasMember_append<S>::value>::Type stringAppend(S& str, InputIterator first, InputIterator last) { str.append(first, last); } +std::enable_if_t<HasMember_append<S>::value> stringAppend(S& str, InputIterator first, InputIterator last) { str.append(first, last); } template <class S, class InputIterator> inline -typename EnableIf<!HasMember_append<S>::value>::Type stringAppend(S& str, InputIterator first, InputIterator last) { str += S(first, last); } +std::enable_if_t<!HasMember_append<S>::value> stringAppend(S& str, InputIterator first, InputIterator last) { str += S(first, last); } } template <class S, class T, class U> inline S replaceCpy(const S& str, const T& oldTerm, const U& newTerm, bool replaceAll) { - static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, ""); - static_assert(IsSameType<typename GetCharType<T>::Type, typename GetCharType<U>::Type>::value, ""); + static_assert(std::is_same_v<GetCharTypeT<S>, GetCharTypeT<T>>); + static_assert(std::is_same_v<GetCharTypeT<T>, GetCharTypeT<U>>); const size_t oldLen = strLength(oldTerm); if (oldLen == 0) return str; @@ -427,7 +427,7 @@ void trim(S& str, bool fromLeft, bool fromRight, Function trimThisChar) template <class S> inline void trim(S& str, bool fromLeft, bool fromRight) { - using CharType = typename GetCharType<S>::Type; + using CharType = GetCharTypeT<S>; trim(str, fromLeft, fromRight, [](CharType c) { return isWhiteSpace(c); }); } @@ -509,11 +509,10 @@ int saferPrintf(wchar_t* buffer, size_t bufferSize, const wchar_t* format, const template <class S, class T, class Num> inline S printNumber(const T& format, const Num& number) //format a single number using ::sprintf { - static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, ""); - using CharType = typename GetCharType<S>::Type; + static_assert(std::is_same_v<GetCharTypeT<S>, GetCharTypeT<T>>); const int BUFFER_SIZE = 128; - CharType buffer[BUFFER_SIZE]; //zero-initialize? + GetCharTypeT<S> buffer[BUFFER_SIZE]; //zero-initialize? const int charsWritten = impl::saferPrintf(buffer, BUFFER_SIZE, strBegin(format), number); return 0 < charsWritten && charsWritten < BUFFER_SIZE ? S(buffer, charsWritten) : S(); @@ -522,21 +521,19 @@ S printNumber(const T& format, const Num& number) //format a single number using namespace impl { -enum NumberType +enum class NumberType { - NUM_TYPE_SIGNED_INT, - NUM_TYPE_UNSIGNED_INT, - NUM_TYPE_FLOATING_POINT, - NUM_TYPE_OTHER, + SIGNED_INT, + UNSIGNED_INT, + FLOATING_POINT, + OTHER, }; template <class S, class Num> inline -S numberTo(const Num& number, Int2Type<NUM_TYPE_OTHER>) //default number to string conversion using streams: convenient, but SLOW, SLOW, SLOW!!!! (~ factor of 20) +S numberTo(const Num& number, std::integral_constant<NumberType, NumberType::OTHER>) //default number to string conversion using streams: convenient, but SLOW, SLOW, SLOW!!!! (~ factor of 20) { - using CharType = typename GetCharType<S>::Type; - - std::basic_ostringstream<CharType> ss; + std::basic_ostringstream<GetCharTypeT<S>> ss; ss << number; return copyStringTo<S>(ss.str()); } @@ -546,9 +543,9 @@ template <class S, class Num> inline S floatToString(const Num& number, char ) template <class S, class Num> inline S floatToString(const Num& number, wchar_t) { return printNumber<S>(L"%g", static_cast<double>(number)); } template <class S, class Num> inline -S numberTo(const Num& number, Int2Type<NUM_TYPE_FLOATING_POINT>) +S numberTo(const Num& number, std::integral_constant<NumberType, NumberType::FLOATING_POINT>) { - return floatToString<S>(number, typename GetCharType<S>::Type()); + return floatToString<S>(number, GetCharTypeT<S>()); } @@ -591,10 +588,9 @@ void formatPositiveInteger(Num n, OutputIterator& it) template <class S, class Num> inline -S numberTo(const Num& number, Int2Type<NUM_TYPE_SIGNED_INT>) +S numberTo(const Num& number, std::integral_constant<NumberType, NumberType::SIGNED_INT>) { - using CharType = typename GetCharType<S>::Type; - CharType buffer[2 + sizeof(Num) * 241 / 100]; //zero-initialize? + GetCharTypeT<S> buffer[2 + sizeof(Num) * 241 / 100]; //zero-initialize? //it's generally faster to use a buffer than to rely on String::operator+=() (in)efficiency //required chars (+ sign char): 1 + ceil(ln_10(256^sizeof(n) / 2 + 1)) -> divide by 2 for signed half-range; second +1 since one half starts with 1! // <= 1 + ceil(ln_10(256^sizeof(n))) =~ 1 + ceil(sizeof(n) * 2.4082) <= 2 + floor(sizeof(n) * 2.41) @@ -612,10 +608,9 @@ S numberTo(const Num& number, Int2Type<NUM_TYPE_SIGNED_INT>) template <class S, class Num> inline -S numberTo(const Num& number, Int2Type<NUM_TYPE_UNSIGNED_INT>) +S numberTo(const Num& number, std::integral_constant<NumberType, NumberType::UNSIGNED_INT>) { - using CharType = typename GetCharType<S>::Type; - CharType buffer[1 + sizeof(Num) * 241 / 100]; //zero-initialize? + GetCharTypeT<S> buffer[1 + sizeof(Num) * 241 / 100]; //zero-initialize? //required chars: ceil(ln_10(256^sizeof(n))) =~ ceil(sizeof(n) * 2.4082) <= 1 + floor(sizeof(n) * 2.41) auto it = std::end(buffer); @@ -628,9 +623,9 @@ S numberTo(const Num& number, Int2Type<NUM_TYPE_UNSIGNED_INT>) //-------------------------------------------------------------------------------- template <class Num, class S> inline -Num stringTo(const S& str, Int2Type<NUM_TYPE_OTHER>) //default string to number conversion using streams: convenient, but SLOW +Num stringTo(const S& str, std::integral_constant<NumberType, NumberType::OTHER>) //default string to number conversion using streams: convenient, but SLOW { - using CharType = typename GetCharType<S>::Type; + using CharType = GetCharTypeT<S>; Num number = 0; std::basic_istringstream<CharType>(copyStringTo<std::basic_string<CharType>>(str)) >> number; return number; @@ -641,7 +636,7 @@ template <class Num> inline Num stringToFloat(const char* str) { return std:: template <class Num> inline Num stringToFloat(const wchar_t* str) { return std::wcstod(str, nullptr); } template <class Num, class S> inline -Num stringTo(const S& str, Int2Type<NUM_TYPE_FLOATING_POINT>) +Num stringTo(const S& str, std::integral_constant<NumberType, NumberType::FLOATING_POINT>) { return stringToFloat<Num>(strBegin(str)); } @@ -649,7 +644,7 @@ Num stringTo(const S& str, Int2Type<NUM_TYPE_FLOATING_POINT>) template <class Num, class S> Num extractInteger(const S& str, bool& hasMinusSign) //very fast conversion to integers: slightly faster than std::atoi, but more importantly: generic { - using CharType = typename GetCharType<S>::Type; + using CharType = GetCharTypeT<S>; const CharType* first = strBegin(str); const CharType* last = first + strLength(str); @@ -687,7 +682,7 @@ Num extractInteger(const S& str, bool& hasMinusSign) //very fast conversion to i template <class Num, class S> inline -Num stringTo(const S& str, Int2Type<NUM_TYPE_SIGNED_INT>) +Num stringTo(const S& str, std::integral_constant<NumberType, NumberType::SIGNED_INT>) { bool hasMinusSign = false; //handle minus sign const Num number = extractInteger<Num>(str, hasMinusSign); @@ -696,7 +691,7 @@ Num stringTo(const S& str, Int2Type<NUM_TYPE_SIGNED_INT>) template <class Num, class S> inline -Num stringTo(const S& str, Int2Type<NUM_TYPE_UNSIGNED_INT>) //very fast conversion to integers: slightly faster than std::atoi, but more importantly: generic +Num stringTo(const S& str, std::integral_constant<NumberType, NumberType::UNSIGNED_INT>) //very fast conversion to integers: slightly faster than std::atoi, but more importantly: generic { bool hasMinusSign = false; //handle minus sign const Num number = extractInteger<Num>(str, hasMinusSign); @@ -713,11 +708,11 @@ Num stringTo(const S& str, Int2Type<NUM_TYPE_UNSIGNED_INT>) //very fast conversi template <class S, class Num> inline S numberTo(const Num& number) { - using TypeTag = Int2Type< - IsSignedInt <Num>::value ? impl::NUM_TYPE_SIGNED_INT : - IsUnsignedInt<Num>::value ? impl::NUM_TYPE_UNSIGNED_INT : - IsFloat <Num>::value ? impl::NUM_TYPE_FLOATING_POINT : - impl::NUM_TYPE_OTHER>; + using TypeTag = std::integral_constant<impl::NumberType, + IsSignedInt <Num>::value ? impl::NumberType::SIGNED_INT : + IsUnsignedInt<Num>::value ? impl::NumberType::UNSIGNED_INT : + IsFloat <Num>::value ? impl::NumberType::FLOATING_POINT : + impl::NumberType::OTHER>; return impl::numberTo<S>(number, TypeTag()); } @@ -726,11 +721,11 @@ S numberTo(const Num& number) template <class Num, class S> inline Num stringTo(const S& str) { - using TypeTag = Int2Type< - IsSignedInt <Num>::value ? impl::NUM_TYPE_SIGNED_INT : - IsUnsignedInt<Num>::value ? impl::NUM_TYPE_UNSIGNED_INT : - IsFloat <Num>::value ? impl::NUM_TYPE_FLOATING_POINT : - impl::NUM_TYPE_OTHER>; + using TypeTag = std::integral_constant<impl::NumberType, + IsSignedInt <Num>::value ? impl::NumberType::SIGNED_INT : + IsUnsignedInt<Num>::value ? impl::NumberType::UNSIGNED_INT : + IsFloat <Num>::value ? impl::NumberType::FLOATING_POINT : + impl::NumberType::OTHER>; return impl::stringTo<Num>(str, TypeTag()); } diff --git a/zen/string_traits.h b/zen/string_traits.h index 805db46d..8187126d 100755 --- a/zen/string_traits.h +++ b/zen/string_traits.h @@ -8,20 +8,20 @@ #define STRING_TRAITS_H_813274321443234 #include <cstring> //strlen -#include "type_tools.h" +#include "type_traits.h" //uniform access to string-like types, both classes and character arrays namespace zen { /* -IsStringLike<>::value: - IsStringLike<const wchar_t*>::value; //equals "true" - IsStringLike<const int*> ::value; //equals "false" +IsStringLikeV<>: + IsStringLikeV<const wchar_t*> //equals "true" + IsStringLikeV<const int*> //equals "false" -GetCharType<>::Type: - GetCharType<std::wstring>::Type //equals wchar_t - GetCharType<wchar_t[5]> ::Type //equals wchar_t +GetCharTypeT<>: + GetCharTypeT<std::wstring> //equals wchar_t + GetCharTypeT<wchar_t[5]> //equals wchar_t strLength(): strLength(str); //equals str.length() @@ -34,6 +34,7 @@ strBegin(): -> not null-terminated! -> may be nullptr if length is 0! strBegin(array); //returns array */ + //reference a sub-string for consumption by zen string_tools template <class Char> class StringRef @@ -79,15 +80,14 @@ public: }; -template <class S, bool isStringClass> struct GetCharTypeImpl : ResultType<NullType> {}; +template <class S, bool isStringClass> struct GetCharTypeImpl { using Type = void; }; template <class S> -struct GetCharTypeImpl<S, true> : - ResultType< - typename SelectIf<HasConversion<S, wchar_t>::value, wchar_t, - typename SelectIf<HasConversion<S, char >::value, char, NullType>::Type - >::Type> +struct GetCharTypeImpl<S, true> { + using Type = std::conditional_t<HasConversion<S, wchar_t>::value, wchar_t, + /**/ std::conditional_t<HasConversion<S, char >::value, char, void>>; + //using Type = typename S::value_type; /*DON'T use S::value_type: 1. support Glib::ustring: value_type is "unsigned int" but c_str() returns "const char*" @@ -96,13 +96,13 @@ struct GetCharTypeImpl<S, true> : */ }; -template <> struct GetCharTypeImpl<char, false> : ResultType<char > {}; -template <> struct GetCharTypeImpl<wchar_t, false> : ResultType<wchar_t> {}; +template <> struct GetCharTypeImpl<char, false> { using Type = char; }; +template <> struct GetCharTypeImpl<wchar_t, false> { using Type = wchar_t; }; -template <> struct GetCharTypeImpl<StringRef<char >, false> : ResultType<char > {}; -template <> struct GetCharTypeImpl<StringRef<wchar_t >, false> : ResultType<wchar_t> {}; -template <> struct GetCharTypeImpl<StringRef<const char >, false> : ResultType<char > {}; -template <> struct GetCharTypeImpl<StringRef<const wchar_t>, false> : ResultType<wchar_t> {}; +template <> struct GetCharTypeImpl<StringRef<char >, false> { using Type = char; }; +template <> struct GetCharTypeImpl<StringRef<wchar_t >, false> { using Type = wchar_t; }; +template <> struct GetCharTypeImpl<StringRef<const char >, false> { using Type = char; }; +template <> struct GetCharTypeImpl<StringRef<const wchar_t>, false> { using Type = wchar_t; }; ZEN_INIT_DETECT_MEMBER_TYPE(value_type); @@ -112,35 +112,42 @@ ZEN_INIT_DETECT_MEMBER(length); // template <class S> class StringTraits { - using NonRefType = typename RemoveRef <S >::Type; - using NonConstType = typename RemoveConst <NonRefType >::Type; - using NonArrayType = typename RemoveArray <NonConstType>::Type; - using NonPtrType = typename RemovePointer<NonArrayType>::Type; - using UndecoratedType = typename RemoveConst <NonPtrType >::Type ; //handle "const char* const" + using CleanType = std::remove_cv_t<std::remove_reference_t<S>>; //std::remove_cvref requires C++20 + using NonArrayType = std::remove_extent_t <CleanType>; + using NonPtrType = std::remove_pointer_t<NonArrayType>; + using UndecoratedType = std::remove_cv_t <NonPtrType>; //handle "const char* const" public: enum { - isStringClass = HasMemberType_value_type<NonConstType>::value && - HasMember_c_str <NonConstType>::value && - HasMember_length <NonConstType>::value + isStringClass = HasMemberType_value_type<CleanType>::value && + HasMember_c_str <CleanType>::value && + HasMember_length <CleanType>::value }; using CharType = typename GetCharTypeImpl<UndecoratedType, isStringClass>::Type; enum { - isStringLike = IsSameType<CharType, char>::value || - IsSameType<CharType, wchar_t>::value + isStringLike = std::is_same_v<CharType, char> || + std::is_same_v<CharType, wchar_t> }; }; } template <class T> -struct IsStringLike : StaticBool<impl::StringTraits<T>::isStringLike> {}; +struct IsStringLike : std::bool_constant<impl::StringTraits<T>::isStringLike> {}; template <class T> -struct GetCharType : ResultType<typename impl::StringTraits<T>::CharType> {}; +struct GetCharType { using Type = typename impl::StringTraits<T>::CharType; }; + + +//template alias helpers: +template<class T> +constexpr bool IsStringLikeV = IsStringLike<T>::value; + +template<class T> +using GetCharTypeT = typename GetCharType<T>::Type; namespace impl @@ -154,7 +161,7 @@ inline size_t cStringLength(const wchar_t* str) { return std::wcslen(str); } template <class C> inline size_t cStringLength(const C* str) { - static_assert(IsSameType<C, char>::value || IsSameType<C, wchar_t>::value, ""); + static_assert(std::is_same_v<C, char> || std::is_same_v<C, wchar_t>); size_t len = 0; while (*str++ != 0) ++len; @@ -162,8 +169,8 @@ size_t cStringLength(const C* str) } #endif -template <class S, typename = typename EnableIf<StringTraits<S>::isStringClass>::Type> inline -const typename GetCharType<S>::Type* strBegin(const S& str) //SFINAE: T must be a "string" +template <class S, typename = std::enable_if_t<StringTraits<S>::isStringClass>> inline +const GetCharTypeT<S>* strBegin(const S& str) //SFINAE: T must be a "string" { return str.c_str(); } @@ -179,7 +186,7 @@ inline const char* strBegin(const StringRef<const char >& ref) { return ref inline const wchar_t* strBegin(const StringRef<const wchar_t>& ref) { return ref.data(); } -template <class S, typename = typename EnableIf<StringTraits<S>::isStringClass>::Type> inline +template <class S, typename = std::enable_if_t<StringTraits<S>::isStringClass>> inline size_t strLength(const S& str) //SFINAE: T must be a "string" { return str.length(); @@ -198,9 +205,9 @@ inline size_t strLength(const StringRef<const wchar_t>& ref) { return ref.length template <class S> inline -auto strBegin(S&& str) -> const typename GetCharType<S>::Type* +auto strBegin(S&& str) -> const GetCharTypeT<S>* { - static_assert(IsStringLike<S>::value, ""); + static_assert(IsStringLikeV<S>); return impl::strBegin(std::forward<S>(str)); } @@ -208,7 +215,7 @@ auto strBegin(S&& str) -> const typename GetCharType<S>::Type* template <class S> inline size_t strLength(S&& str) { - static_assert(IsStringLike<S>::value, ""); + static_assert(IsStringLikeV<S>); return impl::strLength(std::forward<S>(str)); } } diff --git a/zen/sys_error.h b/zen/sys_error.h index c179ec8a..a087172f 100755 --- a/zen/sys_error.h +++ b/zen/sys_error.h @@ -81,7 +81,6 @@ std::wstring formatSystemError(const std::wstring& functionName, long long lastE inline std::wstring formatSystemError(const std::wstring& functionName, ErrorCode ec) { - //static_assert(sizeof(ec) == sizeof(int), ""); //const std::wstring errorCode = printNumber<std::wstring>(L"0x%08x", static_cast<int>(ec)); const std::wstring errorCode = numberTo<std::wstring>(ec); diff --git a/zen/thread.cpp b/zen/thread.cpp new file mode 100755 index 00000000..8016d4a9 --- /dev/null +++ b/zen/thread.cpp @@ -0,0 +1,55 @@ +// ***************************************************************************** +// * 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 * +// ***************************************************************************** + +#include "thread.h" + #include <sys/prctl.h> + #include <unistd.h> + #include <sys/syscall.h> + +using namespace zen; + + + + +void zen::setCurrentThreadName(const char* threadName) +{ + ::prctl(PR_SET_NAME, threadName, 0, 0, 0); + +} + + +namespace +{ +uint64_t getThreadIdNative() +{ + const pid_t tid = ::syscall(SYS_gettid); //no-fail + //"Invalid thread and process IDs": https://blogs.msdn.microsoft.com/oldnewthing/20040223-00/?p=40503 + //if (tid == 0) -> not sure this holds on Linux, too! + // throw std::runtime_error(std::string(__FILE__) + "[" + numberTo<std::string>(__LINE__) + "] Failed to get thread ID."); + static_assert(sizeof(uint64_t) >= sizeof(tid)); + return tid; +} + + +struct InitMainThreadIdOnStartup +{ + InitMainThreadIdOnStartup() { getMainThreadId(); } +} startupInitMainThreadId; +} + + +uint64_t zen::getThreadId() +{ + thread_local const uint64_t tid = getThreadIdNative(); //buffer to get predictable perf characteristics + return tid; +} + + +uint64_t zen::getMainThreadId() +{ + static const uint64_t mainThreadId = getThreadId(); + return mainThreadId; +} diff --git a/zen/thread.h b/zen/thread.h index ed61e06b..ee36f305 100755 --- a/zen/thread.h +++ b/zen/thread.h @@ -10,9 +10,9 @@ #include <thread> #include <future> #include "scope_guard.h" -#include "type_traits.h" +#include "ring_buffer.h" #include "optional.h" - #include <sys/prctl.h> +#include "string_tools.h" namespace zen @@ -23,8 +23,8 @@ class InterruptibleThread { public: InterruptibleThread() {} - InterruptibleThread (InterruptibleThread&& tmp) = default; - InterruptibleThread& operator=(InterruptibleThread&& tmp) = default; + InterruptibleThread (InterruptibleThread&&) noexcept = default; + InterruptibleThread& operator=(InterruptibleThread&&) noexcept = default; template <class Function> InterruptibleThread(Function&& f); @@ -46,7 +46,7 @@ public: private: std::thread stdThread_; - std::shared_ptr<InterruptionStatus> intStatus_{ std::make_shared<InterruptionStatus>() }; + std::shared_ptr<InterruptionStatus> intStatus_ = std::make_shared<InterruptionStatus>(); std::future<void> threadCompleted_; }; @@ -62,7 +62,9 @@ void interruptibleSleep(const std::chrono::duration<Rep, Period>& relTime); //th void setCurrentThreadName(const char* threadName); uint64_t getThreadId(); //simple integer thread id, unlike boost::thread::id: https://svn.boost.org/trac/boost/ticket/5754 +uint64_t getMainThreadId(); +inline bool runningMainThread() { return getThreadId() == getMainThreadId(); } //------------------------------------------------------------------------------------------ /* @@ -118,6 +120,7 @@ class Protected public: Protected() {} Protected(const T& value) : value_(value) {} + //Protected( T&& tmp ) : value_(std::move(tmp)) {} <- wait until needed template <class Function> auto access(Function fun) //-> decltype(fun(std::declval<T&>())) @@ -140,53 +143,101 @@ template <class Function> class ThreadGroup { public: - ThreadGroup(size_t threadCount, const std::string& groupName) - { - for (size_t i = 0; i < threadCount; ++i) - worker_.emplace_back([this, groupName, i, threadCount] - { - setCurrentThreadName((groupName + "[" + numberTo<std::string>(i + 1) + "/" + numberTo<std::string>(threadCount) + "]").c_str()); - for (;;) - getNextWorkItem()(); //throw ThreadInterruption - }); - } + ThreadGroup(size_t threadCountMax, const std::string& groupName) : threadCountMax_(threadCountMax), groupName_(groupName) + { if (threadCountMax == 0) throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); } + ~ThreadGroup() { for (InterruptibleThread& w : worker_) w.interrupt(); //interrupt all first, then join - for (InterruptibleThread& w : worker_) w.join(); + for (InterruptibleThread& w : worker_) detach_ ? w.detach() : w.join(); } + ThreadGroup(ThreadGroup&& tmp) noexcept : + worker_ (std::move(tmp.worker_)), + workLoad_ (std::move(tmp.workLoad_)), + detach_ (tmp.detach_), + threadCountMax_(tmp.threadCountMax_), + groupName_ (std::move(tmp.groupName_)) { tmp.worker_.clear(); /*just in case: make sure destructor is no-op!*/ } + + ThreadGroup& operator=(ThreadGroup&& tmp) noexcept { swap(tmp); return *this; } //noexcept *required* to support move for reallocations in std::vector and std::swap!!! + //context of controlling thread, non-blocking: void run(Function&& wi) { - assert(!worker_.empty()); + size_t tasksPending = 0; { - std::lock_guard<std::mutex> dummy(lockWork_); - workItems_.push_back(std::move(wi)); + std::lock_guard<std::mutex> dummy(workLoad_->lock); + workLoad_->tasks.push_back(std::move(wi)); + tasksPending = ++(workLoad_->tasksPending); } - conditionNewWork_.notify_all(); + workLoad_->conditionNewTask.notify_all(); + + if (worker_.size() < std::min(tasksPending, threadCountMax_)) + addWorkerThread(); } + //context of controlling thread, blocking: + void wait() + { + std::unique_lock<std::mutex> dummy(workLoad_->lock); + workLoad_->conditionTasksDone.wait(dummy, [&tasksPending = workLoad_->tasksPending] { return tasksPending == 0; }); + } + + void detach() { detach_ = true; } //not expected to also interrupt! + private: ThreadGroup (const ThreadGroup&) = delete; ThreadGroup& operator=(const ThreadGroup&) = delete; - //context of worker threads, blocking: - Function getNextWorkItem() //throw ThreadInterruption + void addWorkerThread() { - std::unique_lock<std::mutex> dummy(lockWork_); + std::string threadName = groupName_ + '[' + numberTo<std::string>(worker_.size() + 1) + '/' + numberTo<std::string>(threadCountMax_) + ']'; + + worker_.emplace_back([wl = workLoad_, threadName = std::move(threadName)] //don't capture "this"! consider detach() and swap() + { + setCurrentThreadName(threadName.c_str()); + + std::unique_lock<std::mutex> dummy(wl->lock); + for (;;) + { + interruptibleWait(wl->conditionNewTask, dummy, [&tasks = wl->tasks] { return !tasks.empty(); }); //throw ThreadInterruption + + Function task = std::move(wl->tasks. front()); //noexcept thanks to move + /**/ wl->tasks.pop_front(); // + dummy.unlock(); - interruptibleWait(conditionNewWork_, dummy, [this] { return !workItems_.empty(); }); //throw ThreadInterruption - warn_static("implement FIFO!?") + task(); - Function wi = std::move(workItems_. back()); // - /**/ workItems_.pop_back(); //noexcept thanks to move - return wi; // + dummy.lock(); + if (--(wl->tasksPending) == 0) + wl->conditionTasksDone.notify_all(); //too difficult to notify outside the lock + } + }); } + + void swap(ThreadGroup& other) + { + std::swap(worker_, other.worker_); + std::swap(workLoad_, other.workLoad_); + std::swap(detach_, other.detach_); + std::swap(threadCountMax_, other.threadCountMax_); + std::swap(groupName_, other.groupName_); + } + + struct WorkLoad + { + std::mutex lock; + RingBuffer<Function> tasks; //FIFO! :) + size_t tasksPending = 0; + std::condition_variable conditionNewTask; + std::condition_variable conditionTasksDone; + }; + std::vector<InterruptibleThread> worker_; - std::mutex lockWork_; - std::vector<Function> workItems_; - std::condition_variable conditionNewWork_; + std::shared_ptr<WorkLoad> workLoad_ = std::make_shared<WorkLoad>(); + bool detach_ = false; + size_t threadCountMax_; + std::string groupName_; }; @@ -201,7 +252,7 @@ private: namespace impl { template <class Function> inline -auto runAsync(Function&& fun, TrueType /*copy-constructible*/) +auto runAsync(Function&& fun, std::true_type /*copy-constructible*/) { using ResultType = decltype(fun()); @@ -214,11 +265,11 @@ auto runAsync(Function&& fun, TrueType /*copy-constructible*/) template <class Function> inline -auto runAsync(Function&& fun, FalseType /*copy-constructible*/) +auto runAsync(Function&& fun, std::false_type /*copy-constructible*/) { //support move-only function objects! auto sharedFun = std::make_shared<Function>(std::forward<Function>(fun)); - return runAsync([sharedFun] { return (*sharedFun)(); }, TrueType()); + return runAsync([sharedFun] { return (*sharedFun)(); }, std::true_type()); } } @@ -226,7 +277,7 @@ auto runAsync(Function&& fun, FalseType /*copy-constructible*/) template <class Function> inline auto runAsync(Function&& fun) { - return impl::runAsync(std::forward<Function>(fun), StaticBool<std::is_copy_constructible<Function>::value>()); + return impl::runAsync(std::forward<Function>(fun), std::is_copy_constructible<Function>()); } @@ -308,14 +359,6 @@ Opt<T> GetFirstResult<T>::get() const { return asyncResult_->getResult(jobsTotal //------------------------------------------------------------------------------------------ -//thread_local with non-POD seems to cause memory leaks on VS 14 => pointer only is fine: -#if defined __GNUC__ || defined __clang__ - #define ZEN_THREAD_LOCAL_SPECIFIER __thread -#else - #error "Game over!" -#endif - - class ThreadInterruption {}; @@ -354,7 +397,7 @@ public: ZEN_ON_SCOPE_EXIT(setConditionVar(nullptr)); //"interrupted_" is not protected by cv's mutex => signal may get lost!!! e.g. after condition was checked but before the wait begins - //=> add artifical time out to mitigate! CPU: 0.25% vs 0% for longer time out! + //=> add artifical time out to mitigate! CPU: 0.25% vs 0% for longer time out! while (!cv.wait_for(lock, std::chrono::milliseconds(1), [&] { return this->interrupted_ || pred(); })) ; @@ -393,7 +436,8 @@ namespace impl inline InterruptionStatus*& refThreadLocalInterruptionStatus() { - static ZEN_THREAD_LOCAL_SPECIFIER InterruptionStatus* threadLocalInterruptionStatus = nullptr; + //thread_local with non-POD seems to cause memory leaks on VS 14 => pointer only is fine: + thread_local InterruptionStatus* threadLocalInterruptionStatus = nullptr; return threadLocalInterruptionStatus; } } @@ -457,26 +501,6 @@ InterruptibleThread::InterruptibleThread(Function&& f) inline void InterruptibleThread::interrupt() { intStatus_->interrupt(); } - - - - -inline -void setCurrentThreadName(const char* threadName) -{ - ::prctl(PR_SET_NAME, threadName, 0, 0, 0); - -} - - -inline -uint64_t getThreadId() -{ - //obviously "gettid()" is not available on Ubuntu/Debian/Suse => use the OpenSSL approach: - static_assert(sizeof(uint64_t) >= sizeof(void*), ""); - return reinterpret_cast<uint64_t>(static_cast<void*>(&errno)); - -} } #endif //THREAD_H_7896323423432235246427 @@ -217,12 +217,11 @@ struct PredefinedFormatTag {}; template <class String, class String2> inline String formatTime(const String2& format, const TimeComp& tc, UserDefinedFormatTag) //format as specified by "std::strftime", returns empty string on failure { - using CharType = typename GetCharType<String>::Type; std::tm ctc = toClibTimeComponents(tc); std::mktime(&ctc); // unfortunately std::strftime() needs all elements of "struct tm" filled, e.g. tm_wday, tm_yday //note: although std::mktime() explicitly expects "local time", calculating weekday and day of year *should* be time-zone and DST independent - CharType buffer[256] = {}; + GetCharTypeT<String> buffer[256] = {}; const size_t charsWritten = strftimeWrap(buffer, 256, strBegin(format), &ctc); return String(buffer, charsWritten); } @@ -231,8 +230,7 @@ String formatTime(const String2& format, const TimeComp& tc, UserDefinedFormatTa template <class String, class FormatType> inline String formatTime(FormatType, const TimeComp& tc, PredefinedFormatTag) { - using CharType = typename GetCharType<String>::Type; - return formatTime<String>(GetFormat<FormatType>().format(CharType()), tc, UserDefinedFormatTag()); + return formatTime<String>(GetFormat<FormatType>().format(GetCharTypeT<String>()), tc, UserDefinedFormatTag()); } } @@ -306,13 +304,13 @@ String formatTime(const String2& format, const TimeComp& tc) if (tc == TimeComp()) //failure code from getLocalTime() return String(); - using FormatTag = typename SelectIf< - IsSameType<String2, FormatDateTag >::value || - IsSameType<String2, FormatTimeTag >::value || - IsSameType<String2, FormatDateTimeTag >::value || - IsSameType<String2, FormatIsoDateTag >::value || - IsSameType<String2, FormatIsoTimeTag >::value || - IsSameType<String2, FormatIsoDateTimeTag>::value, impl::PredefinedFormatTag, impl::UserDefinedFormatTag>::Type; + using FormatTag = std::conditional_t< + std::is_same_v<String2, FormatDateTag > || + std::is_same_v<String2, FormatTimeTag > || + std::is_same_v<String2, FormatDateTimeTag > || + std::is_same_v<String2, FormatIsoDateTag > || + std::is_same_v<String2, FormatIsoTimeTag > || + std::is_same_v<String2, FormatIsoDateTimeTag>, impl::PredefinedFormatTag, impl::UserDefinedFormatTag>; return impl::formatTime<String>(format, tc, FormatTag()); } @@ -323,8 +321,8 @@ namespace impl template <class String, class String2> TimeComp parseTime(const String& format, const String2& str, UserDefinedFormatTag) { - using CharType = typename GetCharType<String>::Type; - static_assert(IsSameType<CharType, typename GetCharType<String2>::Type>::value, ""); + using CharType = GetCharTypeT<String>; + static_assert(std::is_same_v<CharType, GetCharTypeT<String2>>); const CharType* itStr = strBegin(str); const CharType* const strLast = itStr + strLength(str); @@ -429,8 +427,7 @@ TimeComp parseTime(const String& format, const String2& str, UserDefinedFormatTa template <class FormatType, class String> inline TimeComp parseTime(FormatType, const String& str, PredefinedFormatTag) { - using CharType = typename GetCharType<String>::Type; - return parseTime(GetFormat<FormatType>().format(CharType()), str, UserDefinedFormatTag()); + return parseTime(GetFormat<FormatType>().format(GetCharTypeT<String>()), str, UserDefinedFormatTag()); } } @@ -438,10 +435,10 @@ TimeComp parseTime(FormatType, const String& str, PredefinedFormatTag) template <class String, class String2> inline TimeComp parseTime(const String& format, const String2& str) { - using FormatTag = typename SelectIf< - IsSameType<String, FormatIsoDateTag >::value || - IsSameType<String, FormatIsoTimeTag >::value || - IsSameType<String, FormatIsoDateTimeTag>::value, impl::PredefinedFormatTag, impl::UserDefinedFormatTag>::Type; + using FormatTag = std::conditional_t< + std::is_same_v<String, FormatIsoDateTag > || + std::is_same_v<String, FormatIsoTimeTag > || + std::is_same_v<String, FormatIsoDateTimeTag>, impl::PredefinedFormatTag, impl::UserDefinedFormatTag>; return impl::parseTime(format, str, FormatTag()); } diff --git a/zen/type_tools.h b/zen/type_tools.h deleted file mode 100755 index a705d9bd..00000000 --- a/zen/type_tools.h +++ /dev/null @@ -1,103 +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 TYPE_TOOLS_H_45237590734254545 -#define TYPE_TOOLS_H_45237590734254545 - -#include "type_traits.h" - - -namespace zen -{ -//########## Strawman Classes ########################## -struct NullType {}; //:= no type here - -//########## Type Mapping ############################## -template <int n> -struct Int2Type {}; -//------------------------------------------------------ -template <class T> -struct Type2Type {}; - -//########## Control Structures ######################## -template <bool flag, class T, class U> -struct SelectIf : ResultType<T> {}; - -template <class T, class U> -struct SelectIf<false, T, U> : ResultType<U> {}; -//------------------------------------------------------ -template <class T, class U> -struct IsSameType : FalseType {}; - -template <class T> -struct IsSameType<T, T> : TrueType {}; - -//------------------------------------------------------ -template <bool, class T = void> -struct EnableIf {}; - -template <class T> -struct EnableIf<true, T> : ResultType<T> {}; -//########## Type Cleanup ############################## -template <class T> -struct RemoveRef : ResultType<T> {}; - -template <class T> -struct RemoveRef<T&> : ResultType<T> {}; - -template <class T> -struct RemoveRef<T&&> : ResultType<T> {}; -//------------------------------------------------------ -template <class T> -struct RemoveConst : ResultType<T> {}; - -template <class T> -struct RemoveConst<const T> : ResultType<T> {}; -//------------------------------------------------------ -template <class T> -struct RemovePointer : ResultType<T> {}; - -template <class T> -struct RemovePointer<T*> : ResultType<T> {}; -//------------------------------------------------------ -template <class T> -struct RemoveArray : ResultType<T> {}; - -template <class T, int N> -struct RemoveArray<T[N]> : ResultType<T> {}; - -//########## Sorting ############################## -/* -Generate a descending binary predicate at compile time! - -Usage: - static const bool ascending = ... - makeSortDirection(old binary predicate, Int2Type<ascending>()) -> new binary predicate - -or directly; - makeDescending(old binary predicate) -> new binary predicate -*/ - -template <class Predicate> -struct LessDescending -{ - LessDescending(Predicate lessThan) : lessThan_(lessThan) {} - template <class T> bool operator()(const T& lhs, const T& rhs) const { return lessThan_(rhs, lhs); } -private: - Predicate lessThan_; -}; - -template <class Predicate> inline -/**/ Predicate makeSortDirection(Predicate pred, Int2Type<true>) { return pred; } - -template <class Predicate> inline -LessDescending<Predicate> makeSortDirection(Predicate pred, Int2Type<false>) { return pred; } - -template <class Predicate> inline -LessDescending<Predicate> makeDescending(Predicate pred) { return pred; } -} - -#endif //TYPE_TOOLS_H_45237590734254545 diff --git a/zen/type_traits.h b/zen/type_traits.h index 83a74d1e..2d4e7a97 100755 --- a/zen/type_traits.h +++ b/zen/type_traits.h @@ -7,41 +7,42 @@ #ifndef TYPE_TRAITS_H_3425628658765467 #define TYPE_TRAITS_H_3425628658765467 -#include <type_traits> //all we need is std::is_class!! +#include <type_traits> +#include "legacy_compiler.h" +//http://en.cppreference.com/w/cpp/header/type_traits namespace zen { -//################# TMP compile time return values: "inherit to return compile-time result" ############## -template <int i> -struct StaticInt +template<class T, class...> +struct GetFirstOf { - enum { value = i }; + using Type = T; }; +template<class... T> using GetFirstOfT = typename GetFirstOf<T...>::Type; -template <bool b> -struct StaticBool : StaticInt<b> {}; - -using TrueType = StaticBool<true>; -using FalseType = StaticBool<false>; - -template <class EnumType, EnumType val> -struct StaticEnum +template <class F> +class FunctionReturnType { - static const EnumType value = val; -}; -//--------------------------------------------------------- -template <class T> -struct ResultType -{ - using Type = T; + template <class R, class... Args> static R dummyFun(R(*)(Args...)); +public: + using Type = decltype(dummyFun(F())); }; +template<class F> using FunctionReturnTypeT = typename FunctionReturnType<F>::Type; -template<class T, class...> -struct GetFirstOf +//============================================================================= + +template<class T, size_t N> +constexpr size_t arraySize(T (&)[N]) { return N; } + +template<class S, class T, size_t N> +constexpr S arrayAccumulate(T (&arr)[N]) { - using Type = T; -}; + S sum = 0; + for (size_t i = 0; i < N; ++i) + sum += arr[i]; + return sum; +} //Herb Sutter's signedness conversion helpers: http://herbsutter.com/2013/06/13/gotw-93-solution-auto-variables-part-2/ template<class T> inline auto makeSigned (T t) { return static_cast<std::make_signed_t <T>>(t); } @@ -50,16 +51,19 @@ template<class T> inline auto makeUnsigned(T t) { return static_cast<std::make_u //################# Built-in Types ######################## //Example: "IsSignedInt<int>::value" evaluates to "true" +//unfortunate standardized nonsense: std::is_integral<> includes bool, char, wchar_t! => roll our own: template <class T> struct IsUnsignedInt; template <class T> struct IsSignedInt; -template <class T> struct IsFloat; -template <class T> struct IsInteger; //IsSignedInt or IsUnsignedInt -template <class T> struct IsArithmetic; //IsInteger or IsFloat +template <class T> using IsFloat = std::is_floating_point<T>; +template <class T> using IsInteger = std::bool_constant<IsUnsignedInt<T>::value || IsSignedInt<T>::value>; +template <class T> using IsArithmetic = std::bool_constant<IsInteger <T>::value || IsFloat <T>::value>; + //remaining non-arithmetic types: bool, char, wchar_t + //optional: specialize new types like: -//template <> struct IsUnsignedInt<UInt64> : TrueType {}; +//template <> struct IsUnsignedInt<UInt64> : std::true_type {}; //################# Class Members ######################## @@ -80,13 +84,29 @@ template <class T> struct IsArithmetic; //IsInteger or IsFloat 2. HasMemberType_value_type<T>::value -> use as boolean */ +//########## Sorting ############################## +/* +Generate a descending binary predicate at compile time! +Usage: + static const bool ascending = ... + makeSortDirection(old binary predicate, std::bool_constant<ascending>()) -> new binary predicate +*/ +template <class Predicate> +struct LessDescending +{ + LessDescending(Predicate lessThan) : lessThan_(lessThan) {} + template <class T> bool operator()(const T& lhs, const T& rhs) const { return lessThan_(rhs, lhs); } +private: + Predicate lessThan_; +}; +template <class Predicate> inline +/**/ Predicate makeSortDirection(Predicate pred, std::true_type) { return pred; } - - - +template <class Predicate> inline +LessDescending<Predicate> makeSortDirection(Predicate pred, std::false_type) { return pred; } @@ -95,43 +115,23 @@ template <class T> struct IsArithmetic; //IsInteger or IsFloat //################ implementation ###################### -#define ZEN_SPECIALIZE_TRAIT(X, Y) template <> struct X<Y> : TrueType {}; - template <class T> -struct IsUnsignedInt : FalseType {}; +struct IsUnsignedInt : std::false_type {}; -ZEN_SPECIALIZE_TRAIT(IsUnsignedInt, unsigned char); -ZEN_SPECIALIZE_TRAIT(IsUnsignedInt, unsigned short int); -ZEN_SPECIALIZE_TRAIT(IsUnsignedInt, unsigned int); -ZEN_SPECIALIZE_TRAIT(IsUnsignedInt, unsigned long int); -ZEN_SPECIALIZE_TRAIT(IsUnsignedInt, unsigned long long int); //new with C++11 - same type as unsigned __int64 in VS2010 -//------------------------------------------------------ +template <> struct IsUnsignedInt<unsigned char > : std::true_type {}; +template <> struct IsUnsignedInt<unsigned short int > : std::true_type {}; +template <> struct IsUnsignedInt<unsigned int > : std::true_type {}; +template <> struct IsUnsignedInt<unsigned long int > : std::true_type {}; +template <> struct IsUnsignedInt<unsigned long long int> : std::true_type {}; template <class T> -struct IsSignedInt : FalseType {}; - -ZEN_SPECIALIZE_TRAIT(IsSignedInt, signed char); -ZEN_SPECIALIZE_TRAIT(IsSignedInt, short int); -ZEN_SPECIALIZE_TRAIT(IsSignedInt, int); -ZEN_SPECIALIZE_TRAIT(IsSignedInt, long int); -ZEN_SPECIALIZE_TRAIT(IsSignedInt, long long int); //new with C++11 - same type as __int64 in VS2010 -//------------------------------------------------------ +struct IsSignedInt : std::false_type {}; -template <class T> -struct IsFloat : FalseType {}; - -ZEN_SPECIALIZE_TRAIT(IsFloat, float); -ZEN_SPECIALIZE_TRAIT(IsFloat, double); -ZEN_SPECIALIZE_TRAIT(IsFloat, long double); -//------------------------------------------------------ - -#undef ZEN_SPECIALIZE_TRAIT - -template <class T> -struct IsInteger : StaticBool<IsUnsignedInt<T>::value || IsSignedInt<T>::value> {}; - -template <class T> -struct IsArithmetic : StaticBool<IsInteger<T>::value || IsFloat<T>::value> {}; +template <> struct IsSignedInt<signed char > : std::true_type {}; +template <> struct IsSignedInt<short int > : std::true_type {}; +template <> struct IsSignedInt<int > : std::true_type {}; +template <> struct IsSignedInt<long int > : std::true_type {}; +template <> struct IsSignedInt<long long int> : std::true_type {}; //#################################################################### #define ZEN_INIT_DETECT_MEMBER(NAME) \ @@ -145,6 +145,7 @@ struct IsArithmetic : StaticBool<IsInteger<T>::value || IsFloat<T>::value> {}; \ template <typename U, U t> \ class Helper {}; \ + \ struct Fallback { int NAME; }; \ \ template <class U> \ @@ -156,11 +157,11 @@ struct IsArithmetic : StaticBool<IsInteger<T>::value || IsFloat<T>::value> {}; enum { value = sizeof(hasMember<T>(nullptr)) == sizeof(Yes) }; \ }; \ \ - template<class T> \ - struct HasMemberImpl_##NAME<false, T> : FalseType {}; \ + template<class T> \ + struct HasMemberImpl_##NAME<false, T> : std::false_type {}; \ \ - template<typename T> \ - struct HasMember_##NAME : StaticBool<HasMemberImpl_##NAME<std::is_class<T>::value, T>::value> {}; + template<typename T> \ + struct HasMember_##NAME : std::bool_constant<HasMemberImpl_##NAME<std::is_class<T>::value, T>::value> {}; //#################################################################### @@ -53,6 +53,8 @@ const CodePoint TRAIL_SURROGATE_MAX = 0xdfff; const CodePoint REPLACEMENT_CHAR = 0xfffd; const CodePoint CODE_POINT_MAX = 0x10ffff; +static_assert(LEAD_SURROGATE + TRAIL_SURROGATE + TRAIL_SURROGATE_MAX + REPLACEMENT_CHAR + CODE_POINT_MAX == 1348603); + template <class Function> inline void codePointToUtf16(CodePoint cp, Function writeOutput) //"writeOutput" is a unary function taking a Char16 @@ -245,14 +247,14 @@ private: //---------------------------------------------------------------------------------------------------------------- -template <class Function> inline void codePointToUtf(CodePoint cp, Function writeOutput, Int2Type<1>) { codePointToUtf8 (cp, writeOutput); } //UTF8-char -template <class Function> inline void codePointToUtf(CodePoint cp, Function writeOutput, Int2Type<2>) { codePointToUtf16(cp, writeOutput); } //Windows: UTF16-wchar_t -template <class Function> inline void codePointToUtf(CodePoint cp, Function writeOutput, Int2Type<4>) { writeOutput(cp); } //other OS: UTF32-wchar_t +template <class Function> inline void codePointToUtf(CodePoint cp, Function writeOutput, std::integral_constant<int, 1>) { codePointToUtf8 (cp, writeOutput); } //UTF8-char +template <class Function> inline void codePointToUtf(CodePoint cp, Function writeOutput, std::integral_constant<int, 2>) { codePointToUtf16(cp, writeOutput); } //Windows: UTF16-wchar_t +template <class Function> inline void codePointToUtf(CodePoint cp, Function writeOutput, std::integral_constant<int, 4>) { writeOutput(cp); } //other OS: UTF32-wchar_t template <class CharType, class Function> inline void codePointToUtf(CodePoint cp, Function writeOutput) //"writeOutput" is a unary function taking a CharType { - return codePointToUtf(cp, writeOutput, Int2Type<sizeof(CharType)>()); + return codePointToUtf(cp, writeOutput, std::integral_constant<int, sizeof(CharType)>()); } //---------------------------------------------------------------------------------------------------------------- @@ -311,7 +313,7 @@ bool isValidUtf(const UtfString& str) { using namespace impl; - UtfDecoder<typename GetCharType<UtfString>::Type> decoder(strBegin(str), strLength(str)); + UtfDecoder<GetCharTypeT<UtfString>> decoder(strBegin(str), strLength(str)); while (Opt<CodePoint> cp = decoder.getNext()) if (*cp == REPLACEMENT_CHAR) return false; @@ -324,7 +326,7 @@ template <class UtfString> inline size_t unicodeLength(const UtfString& str) //return number of code points (+ correctly handle broken UTF encoding) { size_t uniLen = 0; - impl::UtfDecoder<typename GetCharType<UtfString>::Type> decoder(strBegin(str), strLength(str)); + impl::UtfDecoder<GetCharTypeT<UtfString>> decoder(strBegin(str), strLength(str)); while (decoder.getNext()) ++uniLen; return uniLen; @@ -336,7 +338,7 @@ UtfString getUnicodeSubstring(const UtfString& str, size_t uniPosFirst, size_t u { assert(uniPosFirst <= uniPosLast && uniPosLast <= unicodeLength(str)); using namespace impl; - using CharType = typename GetCharType<UtfString>::Type; + using CharType = GetCharTypeT<UtfString>; UtfString output; if (uniPosFirst >= uniPosLast) //optimize for empty range return output; @@ -357,11 +359,11 @@ UtfString getUnicodeSubstring(const UtfString& str, size_t uniPosFirst, size_t u namespace impl { template <class TargetString, class SourceString> inline -TargetString utfTo(const SourceString& str, FalseType) +TargetString utfTo(const SourceString& str, std::false_type) { - using CharSrc = typename GetCharType<SourceString>::Type; - using CharTrg = typename GetCharType<TargetString>::Type; - static_assert(sizeof(CharSrc) != sizeof(CharTrg), "no UTF-conversion needed"); + using CharSrc = GetCharTypeT<SourceString>; + using CharTrg = GetCharTypeT<TargetString>; + static_assert(sizeof(CharSrc) != sizeof(CharTrg)); TargetString output; @@ -374,14 +376,14 @@ TargetString utfTo(const SourceString& str, FalseType) template <class TargetString, class SourceString> inline -TargetString utfTo(const SourceString& str, TrueType) { return copyStringTo<TargetString>(str); } +TargetString utfTo(const SourceString& str, std::true_type) { return copyStringTo<TargetString>(str); } } template <class TargetString, class SourceString> inline TargetString utfTo(const SourceString& str) { - return impl::utfTo<TargetString>(str, StaticBool<sizeof(typename GetCharType<SourceString>::Type) == sizeof(typename GetCharType<TargetString>::Type)>()); + return impl::utfTo<TargetString>(str, std::bool_constant<sizeof(GetCharTypeT<SourceString>) == sizeof(GetCharTypeT<TargetString>)>()); } } diff --git a/zen/warn_static.h b/zen/warn_static.h index e4931c08..6f0a2691 100755 --- a/zen/warn_static.h +++ b/zen/warn_static.h @@ -14,13 +14,11 @@ Usage: warn_static("my message") */ -#if defined __GNUC__ -#define STATIC_WARNING_CONCAT_SUB(X, Y) X ## Y -#define STATIC_WARNING_CONCAT(X, Y) STATIC_WARNING_CONCAT_SUB(X, Y) +#define ZEN_STATIC_WARNING_STRINGIZE(NUM) #NUM -#define warn_static(TXT) \ - typedef int STATIC_WARNING_87903124 __attribute__ ((deprecated)); \ - enum { STATIC_WARNING_CONCAT(warn_static_dummy_value, __LINE__) = sizeof(STATIC_WARNING_87903124) }; +#if defined __GNUC__ //Clang also defines __GNUC__! +#define warn_static(MSG) \ + _Pragma(ZEN_STATIC_WARNING_STRINGIZE(GCC warning MSG)) #endif #endif //WARN_STATIC_H_08724567834560832745 diff --git a/zen/zstring.cpp b/zen/zstring.cpp index afa62c93..1fe31eaf 100755 --- a/zen/zstring.cpp +++ b/zen/zstring.cpp @@ -54,8 +54,8 @@ int compareNoCaseUtf8(const char* lhs, size_t lhsLen, const char* rhs, size_t rh if (!cpL || !cpR) return static_cast<int>(!cpR) - static_cast<int>(!cpL); -//support unit-testing on Windows: CodePoint is truncated to wchar_t -static_assert(sizeof(wchar_t) == sizeof(impl::CodePoint), ""); + //support unit-testing on Windows: CodePoint is truncated to wchar_t + static_assert(sizeof(wchar_t) == sizeof(impl::CodePoint)); const wchar_t charL = ::towlower(static_cast<wchar_t>(*cpL)); //ordering: towlower() converts to higher code points than towupper() const wchar_t charR = ::towlower(static_cast<wchar_t>(*cpR)); //uses LC_CTYPE category of current locale diff --git a/zen/zstring.h b/zen/zstring.h index 5a6ecbdd..cb19318c 100755 --- a/zen/zstring.h +++ b/zen/zstring.h @@ -18,6 +18,9 @@ //a high-performance string for interfacing with native OS APIs in multithreaded contexts using Zstring = zen::Zbase<Zchar>; + //for special UI-contexts: guaranteed exponential growth + ref-counting +using Zstringw = zen::Zbase<wchar_t>; + //Compare filepaths: Windows/OS X does NOT distinguish between upper/lower-case, while Linux DOES struct CmpFilePath @@ -131,8 +134,8 @@ template <class S, class T, class U> inline S ciReplaceCpy(const S& str, const T& oldTerm, const U& newTerm) { using namespace zen; - static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, ""); - static_assert(IsSameType<typename GetCharType<T>::Type, typename GetCharType<U>::Type>::value, ""); + static_assert(std::is_same_v<GetCharTypeT<S>, GetCharTypeT<T>>); + static_assert(std::is_same_v<GetCharTypeT<T>, GetCharTypeT<U>>); const size_t oldLen = strLength(oldTerm); if (oldLen == 0) return str; diff --git a/zenXml/zenxml/cvrt_struc.h b/zenXml/zenxml/cvrt_struc.h index 3a18ea73..11795107 100755 --- a/zenXml/zenxml/cvrt_struc.h +++ b/zenXml/zenxml/cvrt_struc.h @@ -56,14 +56,13 @@ ZEN_INIT_DETECT_MEMBER(insert) // } template <typename T> -struct IsStlContainer : - StaticBool< - impl_2384343::HasMemberType_value_type <T>::value&& - impl_2384343::HasMemberType_iterator <T>::value&& - impl_2384343::HasMemberType_const_iterator<T>::value&& - impl_2384343::HasMember_begin <T>::value&& - impl_2384343::HasMember_end <T>::value&& - impl_2384343::HasMember_insert <T>::value> {}; +using IsStlContainer = std::bool_constant< + impl_2384343::HasMemberType_value_type <T>::value && + impl_2384343::HasMemberType_iterator <T>::value && + impl_2384343::HasMemberType_const_iterator<T>::value && + impl_2384343::HasMember_begin <T>::value && + impl_2384343::HasMember_end <T>::value && + impl_2384343::HasMember_insert <T>::value>; namespace impl_2384343 @@ -76,29 +75,28 @@ ZEN_INIT_DETECT_MEMBER(second) // } template <typename T> -struct IsStlPair : - StaticBool< - impl_2384343::HasMemberType_first_type <T>::value&& - impl_2384343::HasMemberType_second_type<T>::value&& - impl_2384343::HasMember_first <T>::value&& - impl_2384343::HasMember_second <T>::value> {}; +using IsStlPair = std::bool_constant< + impl_2384343::HasMemberType_first_type <T>::value && + impl_2384343::HasMemberType_second_type<T>::value && + impl_2384343::HasMember_first <T>::value && + impl_2384343::HasMember_second <T>::value>; //###################################################################################### //Conversion from arbitrary types to an XML element -enum ValueType +enum class ValueType { - VALUE_TYPE_STL_CONTAINER, - VALUE_TYPE_STL_PAIR, - VALUE_TYPE_OTHER, + STL_CONTAINER, + STL_PAIR, + OTHER, }; template <class T> -struct GetValueType : StaticEnum<ValueType, - GetTextType<T>::value != TEXT_TYPE_OTHER ? VALUE_TYPE_OTHER : //some string classes are also STL containers, so check this first - IsStlContainer<T>::value ? VALUE_TYPE_STL_CONTAINER : - IsStlPair<T>::value ? VALUE_TYPE_STL_PAIR : - VALUE_TYPE_OTHER> {}; +using GetValueType = std::integral_constant<ValueType, + GetTextType <T>::value != TEXT_TYPE_OTHER ? ValueType::OTHER : //some string classes are also STL containers, so check this first + IsStlContainer<T>::value ? ValueType::STL_CONTAINER : + IsStlPair <T>::value ? ValueType::STL_PAIR : + ValueType::OTHER>; template <class T, ValueType type> @@ -113,7 +111,7 @@ struct ConvertElement; //partial specialization: handle conversion for all STL-container types! template <class T> -struct ConvertElement<T, VALUE_TYPE_STL_CONTAINER> +struct ConvertElement<T, ValueType::STL_CONTAINER> { void writeStruc(const T& value, XmlElement& output) const { @@ -145,7 +143,7 @@ struct ConvertElement<T, VALUE_TYPE_STL_CONTAINER> //partial specialization: handle conversion for std::pair template <class T> -struct ConvertElement<T, VALUE_TYPE_STL_PAIR> +struct ConvertElement<T, ValueType::STL_PAIR> { void writeStruc(const T& value, XmlElement& output) const { @@ -173,7 +171,7 @@ struct ConvertElement<T, VALUE_TYPE_STL_PAIR> //partial specialization: not a pure structured type, try text conversion (thereby respect user specializations of writeText()/readText()) template <class T> -struct ConvertElement<T, VALUE_TYPE_OTHER> +struct ConvertElement<T, ValueType::OTHER> { void writeStruc(const T& value, XmlElement& output) const { diff --git a/zenXml/zenxml/cvrt_text.h b/zenXml/zenxml/cvrt_text.h index 17444861..32a961b2 100755 --- a/zenXml/zenxml/cvrt_text.h +++ b/zenXml/zenxml/cvrt_text.h @@ -112,10 +112,10 @@ enum TextType }; template <class T> -struct GetTextType : StaticEnum<TextType, - IsSameType<T, bool>::value ? TEXT_TYPE_BOOL : - IsStringLike<T>::value ? TEXT_TYPE_STRING : //string before number to correctly handle char/wchar_t -> this was an issue with Loki only! - IsArithmetic<T>::value ? TEXT_TYPE_NUMBER : // +struct GetTextType : std::integral_constant<TextType, + std::is_same_v<T, bool> ? TEXT_TYPE_BOOL : + IsStringLikeV<T> ? TEXT_TYPE_STRING : //string before number to correctly handle char/wchar_t -> this was an issue with Loki only! + IsArithmetic<T>::value ? TEXT_TYPE_NUMBER : // TEXT_TYPE_OTHER> {}; //###################################################################################### @@ -186,7 +186,7 @@ template <class T> struct ConvertText<T, TEXT_TYPE_OTHER> { //########################################################################################################################################### - static_assert(sizeof(T) == -1, ""); + static_assert(sizeof(T) == -1); /* ATTENTION: The data type T is yet unknown to the zen::Xml framework! diff --git a/zenXml/zenxml/dom.h b/zenXml/zenxml/dom.h index 15700ee2..566af330 100755 --- a/zenXml/zenxml/dom.h +++ b/zenXml/zenxml/dom.h @@ -10,7 +10,6 @@ #include <string> #include <list> #include <map> -#include <zen/fixed_list.h> #include "cvrt_text.h" //"readText/writeText" @@ -144,9 +143,15 @@ public: template < class IterTy, //underlying iterator type class T, //target object type class AccessPolicy > //access policy: see AccessPtrMap - class PtrIter : public std::iterator<std::input_iterator_tag, T>, private AccessPolicy //get rid of shared_ptr indirection + class PtrIter : private AccessPolicy //get rid of shared_ptr indirection { public: + using iterator_category = std::input_iterator_tag; + using value_type = T; + using difference_type = std::ptrdiff_t; + using pointer = T*; + using reference = T&; + PtrIter(IterTy it) : it_(it) {} PtrIter(const PtrIter& other) : it_(other.it_) {} PtrIter& operator++() { ++it_; return *this; } @@ -191,8 +196,8 @@ public: T& objectRef(const IterTy& it) const { return *it; } }; - using ChildIter = PtrIter<FixedList<XmlElement>::iterator, XmlElement, AccessListElement>; - using ChildIterConst = PtrIter<FixedList<XmlElement>::const_iterator, const XmlElement, AccessListElement>; + using ChildIter = PtrIter<std::list<XmlElement>::iterator, XmlElement, AccessListElement>; + using ChildIterConst = PtrIter<std::list<XmlElement>::const_iterator, const XmlElement, AccessListElement>; ///Access all child elements sequentially via STL iterators. /** @@ -257,7 +262,7 @@ private: std::list<Attribute> attributes_; //attributes in order of creation std::map<std::string, std::list<Attribute>::iterator> attributesSorted_; //alternate view: sorted by attribute name - FixedList<XmlElement> childElements_; //child elements in order of creation + std::list<XmlElement> childElements_; //child elements in order of creation std::multimap<std::string, XmlElement*> childElementsSorted_; //alternate view: sorted by element name XmlElement* parent_ = nullptr; }; |