From 2c4db439d235b68478d90c450289d2d0ba418547 Mon Sep 17 00:00:00 2001 From: B Stack Date: Wed, 18 Mar 2020 08:59:09 -0400 Subject: add upstream 10.21 --- Bugs.txt | 24 +- Changelog.txt | 22 +- FreeFileSync/Build/Resources/Icons.zip | Bin 389295 -> 388959 bytes FreeFileSync/Build/Resources/Languages.zip | Bin 522133 -> 522150 bytes FreeFileSync/Source/Makefile | 18 +- FreeFileSync/Source/RealTimeSync/Makefile | 7 +- FreeFileSync/Source/RealTimeSync/application.cpp | 18 +- FreeFileSync/Source/RealTimeSync/config.cpp | 4 +- FreeFileSync/Source/RealTimeSync/gui_generated.cpp | 8 +- FreeFileSync/Source/RealTimeSync/gui_generated.h | 3 + FreeFileSync/Source/RealTimeSync/main_dlg.cpp | 9 +- FreeFileSync/Source/RealTimeSync/main_dlg.h | 1 + FreeFileSync/Source/RealTimeSync/monitor.cpp | 4 +- FreeFileSync/Source/RealTimeSync/tray_menu.cpp | 2 +- FreeFileSync/Source/afs/abstract.cpp | 6 +- FreeFileSync/Source/afs/abstract.h | 10 +- FreeFileSync/Source/afs/abstract_impl.h | 2 +- FreeFileSync/Source/afs/ftp.cpp | 42 +- FreeFileSync/Source/afs/ftp_common.h | 14 +- FreeFileSync/Source/afs/gdrive.cpp | 54 +- FreeFileSync/Source/afs/native.cpp | 13 +- FreeFileSync/Source/afs/sftp.cpp | 45 +- FreeFileSync/Source/application.cpp | 649 ++++++ FreeFileSync/Source/application.h | 35 + FreeFileSync/Source/base/algorithm.cpp | 16 +- FreeFileSync/Source/base/algorithm.h | 3 +- FreeFileSync/Source/base/application.cpp | 647 ------ FreeFileSync/Source/base/application.h | 35 - FreeFileSync/Source/base/binary.cpp | 2 +- FreeFileSync/Source/base/comparison.cpp | 155 +- FreeFileSync/Source/base/comparison.h | 5 +- FreeFileSync/Source/base/config.cpp | 2364 ------------------- FreeFileSync/Source/base/config.h | 287 --- FreeFileSync/Source/base/db_file.cpp | 137 +- FreeFileSync/Source/base/dir_exist_async.h | 2 +- FreeFileSync/Source/base/dir_lock.cpp | 14 +- FreeFileSync/Source/base/fatal_error.h | 45 - FreeFileSync/Source/base/ffs_paths.cpp | 103 - FreeFileSync/Source/base/ffs_paths.h | 31 - FreeFileSync/Source/base/file_hierarchy.cpp | 24 +- FreeFileSync/Source/base/file_hierarchy.h | 29 +- FreeFileSync/Source/base/help_provider.h | 22 - FreeFileSync/Source/base/icon_buffer.cpp | 413 ---- FreeFileSync/Source/base/icon_buffer.h | 55 - FreeFileSync/Source/base/localization.cpp | 549 ----- FreeFileSync/Source/base/localization.h | 39 - FreeFileSync/Source/base/lock_holder.h | 6 +- FreeFileSync/Source/base/log_file.cpp | 648 ------ FreeFileSync/Source/base/log_file.h | 38 - FreeFileSync/Source/base/parallel_scan.cpp | 10 +- FreeFileSync/Source/base/parallel_scan.h | 4 +- FreeFileSync/Source/base/parse_lng.h | 824 ------- FreeFileSync/Source/base/parse_plural.h | 482 ---- FreeFileSync/Source/base/path_filter.h | 6 +- FreeFileSync/Source/base/perf_check.cpp | 218 -- FreeFileSync/Source/base/perf_check.h | 47 - FreeFileSync/Source/base/resolve_path.cpp | 18 +- FreeFileSync/Source/base/return_codes.h | 80 - FreeFileSync/Source/base/status_handler.cpp | 28 - FreeFileSync/Source/base/status_handler.h | 192 -- FreeFileSync/Source/base/status_handler_impl.h | 6 +- FreeFileSync/Source/base/structures.cpp | 327 +-- FreeFileSync/Source/base/structures.h | 45 +- FreeFileSync/Source/base/synchronization.cpp | 85 +- FreeFileSync/Source/base/synchronization.h | 15 +- FreeFileSync/Source/base/versioning.cpp | 10 +- FreeFileSync/Source/base/versioning.h | 4 +- FreeFileSync/Source/base_tools.cpp | 357 +++ FreeFileSync/Source/base_tools.h | 33 + FreeFileSync/Source/config.cpp | 2395 ++++++++++++++++++++ FreeFileSync/Source/config.h | 255 +++ FreeFileSync/Source/fatal_error.h | 45 + FreeFileSync/Source/ffs_paths.cpp | 103 + FreeFileSync/Source/ffs_paths.h | 31 + FreeFileSync/Source/help_provider.h | 22 + FreeFileSync/Source/icon_buffer.cpp | 413 ++++ FreeFileSync/Source/icon_buffer.h | 55 + FreeFileSync/Source/localization.cpp | 549 +++++ FreeFileSync/Source/localization.h | 39 + FreeFileSync/Source/log_file.cpp | 638 ++++++ FreeFileSync/Source/log_file.h | 45 + FreeFileSync/Source/parse_lng.h | 824 +++++++ FreeFileSync/Source/parse_plural.h | 482 ++++ FreeFileSync/Source/perf_check.cpp | 218 ++ FreeFileSync/Source/perf_check.h | 47 + FreeFileSync/Source/return_codes.h | 80 + FreeFileSync/Source/status_handler.cpp | 28 + FreeFileSync/Source/status_handler.h | 192 ++ FreeFileSync/Source/ui/abstract_folder_picker.cpp | 4 +- FreeFileSync/Source/ui/batch_config.cpp | 2 +- FreeFileSync/Source/ui/batch_config.h | 2 +- FreeFileSync/Source/ui/batch_status_handler.cpp | 27 +- FreeFileSync/Source/ui/batch_status_handler.h | 8 +- FreeFileSync/Source/ui/cfg_grid.cpp | 22 +- FreeFileSync/Source/ui/cfg_grid.h | 2 +- FreeFileSync/Source/ui/file_grid.cpp | 12 +- FreeFileSync/Source/ui/file_grid.h | 2 +- FreeFileSync/Source/ui/folder_pair.h | 2 +- FreeFileSync/Source/ui/folder_selector.cpp | 3 +- FreeFileSync/Source/ui/gui_generated.cpp | 180 +- FreeFileSync/Source/ui/gui_generated.h | 27 +- FreeFileSync/Source/ui/gui_status_handler.cpp | 36 +- FreeFileSync/Source/ui/gui_status_handler.h | 6 +- FreeFileSync/Source/ui/log_panel.cpp | 75 +- FreeFileSync/Source/ui/main_dlg.cpp | 88 +- FreeFileSync/Source/ui/main_dlg.h | 6 +- FreeFileSync/Source/ui/progress_indicator.cpp | 22 +- FreeFileSync/Source/ui/progress_indicator.h | 6 +- FreeFileSync/Source/ui/small_dlgs.cpp | 98 +- FreeFileSync/Source/ui/small_dlgs.h | 2 +- FreeFileSync/Source/ui/sync_cfg.cpp | 47 +- FreeFileSync/Source/ui/sync_cfg.h | 2 +- FreeFileSync/Source/ui/tree_grid.cpp | 6 +- FreeFileSync/Source/ui/version_check.cpp | 4 +- FreeFileSync/Source/version/version.h | 2 +- libcurl/curl_wrap.h | 5 +- libcurl/rest.cpp | 14 +- libssh2/libssh2_wrap.h | 6 +- wx+/file_drop.cpp | 25 +- wx+/file_drop.h | 3 +- wx+/grid.cpp | 2 +- wx+/image_resources.cpp | 2 +- wx+/image_tools.cpp | 2 +- wx+/popup_dlg.cpp | 8 +- xBRZ/src/xbrz.cpp | 271 ++- zen/basic_math.h | 16 +- zen/crc.h | 58 +- zen/dir_watcher.cpp | 3 +- zen/error_log.h | 85 +- zen/file_access.cpp | 4 +- zen/file_io.cpp | 6 +- zen/file_io.h | 2 +- zen/format_unit.cpp | 5 +- zen/guid.h | 12 +- zen/http.cpp | 42 +- zen/http.h | 8 +- zen/legacy_compiler.h | 17 +- zen/open_ssl.cpp | 31 +- zen/perf.h | 2 +- zen/recycler.cpp | 4 +- zen/serialize.h | 38 +- zen/shell_execute.h | 4 +- zen/socket.h | 4 +- zen/string_base.h | 32 +- zen/string_tools.h | 12 +- zen/string_traits.h | 3 +- zen/sys_error.cpp | 184 ++ zen/sys_error.h | 62 +- zen/system.cpp | 31 +- zen/thread.cpp | 2 +- zen/thread.h | 2 +- zen/time.h | 184 +- zen/zlib_wrap.cpp | 14 +- zen/zlib_wrap.h | 6 +- zen/zstring.cpp | 3 +- zen/zstring.h | 7 +- zenXml/zenxml/cvrt_struc.h | 10 +- zenXml/zenxml/dom.h | 2 +- zenXml/zenxml/xml.h | 12 +- 159 files changed, 9078 insertions(+), 8771 deletions(-) mode change 100644 => 100755 FreeFileSync/Source/Makefile create mode 100644 FreeFileSync/Source/application.cpp create mode 100644 FreeFileSync/Source/application.h delete mode 100644 FreeFileSync/Source/base/application.cpp delete mode 100644 FreeFileSync/Source/base/application.h delete mode 100644 FreeFileSync/Source/base/config.cpp delete mode 100644 FreeFileSync/Source/base/config.h delete mode 100644 FreeFileSync/Source/base/fatal_error.h delete mode 100644 FreeFileSync/Source/base/ffs_paths.cpp delete mode 100644 FreeFileSync/Source/base/ffs_paths.h delete mode 100644 FreeFileSync/Source/base/help_provider.h delete mode 100644 FreeFileSync/Source/base/icon_buffer.cpp delete mode 100644 FreeFileSync/Source/base/icon_buffer.h delete mode 100644 FreeFileSync/Source/base/localization.cpp delete mode 100644 FreeFileSync/Source/base/localization.h delete mode 100644 FreeFileSync/Source/base/log_file.cpp delete mode 100644 FreeFileSync/Source/base/log_file.h delete mode 100644 FreeFileSync/Source/base/parse_lng.h delete mode 100644 FreeFileSync/Source/base/parse_plural.h delete mode 100644 FreeFileSync/Source/base/perf_check.cpp delete mode 100644 FreeFileSync/Source/base/perf_check.h delete mode 100644 FreeFileSync/Source/base/return_codes.h delete mode 100644 FreeFileSync/Source/base/status_handler.cpp delete mode 100644 FreeFileSync/Source/base/status_handler.h create mode 100644 FreeFileSync/Source/base_tools.cpp create mode 100644 FreeFileSync/Source/base_tools.h create mode 100644 FreeFileSync/Source/config.cpp create mode 100644 FreeFileSync/Source/config.h create mode 100644 FreeFileSync/Source/fatal_error.h create mode 100644 FreeFileSync/Source/ffs_paths.cpp create mode 100644 FreeFileSync/Source/ffs_paths.h create mode 100644 FreeFileSync/Source/help_provider.h create mode 100644 FreeFileSync/Source/icon_buffer.cpp create mode 100644 FreeFileSync/Source/icon_buffer.h create mode 100644 FreeFileSync/Source/localization.cpp create mode 100644 FreeFileSync/Source/localization.h create mode 100644 FreeFileSync/Source/log_file.cpp create mode 100644 FreeFileSync/Source/log_file.h create mode 100644 FreeFileSync/Source/parse_lng.h create mode 100644 FreeFileSync/Source/parse_plural.h create mode 100644 FreeFileSync/Source/perf_check.cpp create mode 100644 FreeFileSync/Source/perf_check.h create mode 100644 FreeFileSync/Source/return_codes.h create mode 100644 FreeFileSync/Source/status_handler.cpp create mode 100644 FreeFileSync/Source/status_handler.h diff --git a/Bugs.txt b/Bugs.txt index fe61dc97..9d0592c8 100755 --- a/Bugs.txt +++ b/Bugs.txt @@ -5,27 +5,25 @@ the ones mentioned below. The remaining issues that are yet to be fixed are list ----------------- -| libcurl 7.6.7 | +| libcurl 7.6.9 | ----------------- __________________________________________________________________________________________________________ /lib/ftp.c https://github.com/curl/curl/issues/1455 -Add: - static bool is_routable_ip_v4(unsigned int ip[4]) - { - if (ip[0] == 127 || //127.0.0.0/8 (localhost) - ip[0] == 10 || //10.0.0.0/8 (private) - (ip[0] == 192 && ip[1] == 168) || //192.168.0.0/16 (private) - (ip[0] == 169 && ip[1] == 254) || //169.254.0.0/16 (link-local) - (ip[0] == 172 && ip[1] / 16 == 1)) //172.16.0.0/12 (private) - return false; - return true; - } ++ static bool is_routable_ip_v4(unsigned int ip[4]) ++ { ++ if (ip[0] == 127 || //127.0.0.0/8 (localhost) ++ ip[0] == 10 || //10.0.0.0/8 (private) ++ (ip[0] == 192 && ip[1] == 168) || //192.168.0.0/16 (private) ++ (ip[0] == 169 && ip[1] == 254) || //169.254.0.0/16 (link-local) ++ (ip[0] == 172 && ip[1] / 16 == 1)) //172.16.0.0/12 (private) ++ return false; ++ return true; ++ } - if (data->set.ftp_skip_ip) - + bool skipIp = data->set.ftp_skip_ip; + if (!skipIp && !is_routable_ip_v4(ip)) + { diff --git a/Changelog.txt b/Changelog.txt index a4103889..cc6a1c8a 100755 --- a/Changelog.txt +++ b/Changelog.txt @@ -1,9 +1,27 @@ +FreeFileSync 10.21 [2020-03-17] +------------------------------- +Preselect last-used email address +Select log file format (HTML or plain text) +Aggregate email notifications when hitting sending limits +Show code literals in system error messages +Limit conflict item count for log file warning message +Show log icon error indicator even if error occured after sync +Disable background drag & drop when showing modal dialog +Hide dummy model, vendor names in log files +Fixed ANSI encoding used for log file time formatting +Reduced memory consumption for large number of log messages +Correctly parse lock files despite corrupted trail data +Show emoji instead of Unicode icon in email subject +Fixed IWbemServices::ConnectServer error after sync +Fixed aggregate email logs incomplete truncation + + FreeFileSync 10.20 [2020-02-14] ------------------------------- -Send email notifications after sync +Send email notifications after sync (Donation Edition) Generate log files in HTML format Detect sync database consistency errors -Start log file with preview of first 50 errors/warnings +Start log file with preview of first 25 errors/warnings Mitigate lock file data corruption Print Windows error codes in hexadecimal Fixed missing MTP and network links in folder picker (Linux) diff --git a/FreeFileSync/Build/Resources/Icons.zip b/FreeFileSync/Build/Resources/Icons.zip index 79a4dd7f..0a393bbc 100644 Binary files a/FreeFileSync/Build/Resources/Icons.zip and b/FreeFileSync/Build/Resources/Icons.zip differ diff --git a/FreeFileSync/Build/Resources/Languages.zip b/FreeFileSync/Build/Resources/Languages.zip index 81e8e960..3f00d604 100644 Binary files a/FreeFileSync/Build/Resources/Languages.zip and b/FreeFileSync/Build/Resources/Languages.zip differ diff --git a/FreeFileSync/Source/Makefile b/FreeFileSync/Source/Makefile old mode 100644 new mode 100755 index 87352ea4..5d4f6b20 --- a/FreeFileSync/Source/Makefile +++ b/FreeFileSync/Source/Makefile @@ -28,24 +28,25 @@ LINKFLAGS += `pkg-config --libs libselinux` endif CPP_FILES= +CPP_FILES+=application.cpp +CPP_FILES+=base_tools.cpp +CPP_FILES+=config.cpp +CPP_FILES+=ffs_paths.cpp +CPP_FILES+=icon_buffer.cpp +CPP_FILES+=localization.cpp +CPP_FILES+=log_file.cpp +CPP_FILES+=perf_check.cpp +CPP_FILES+=status_handler.cpp CPP_FILES+=base/algorithm.cpp -CPP_FILES+=base/application.cpp CPP_FILES+=base/binary.cpp CPP_FILES+=base/comparison.cpp -CPP_FILES+=base/config.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/icon_buffer.cpp CPP_FILES+=base/icon_loader.cpp -CPP_FILES+=base/localization.cpp -CPP_FILES+=base/log_file.cpp CPP_FILES+=base/parallel_scan.cpp CPP_FILES+=base/path_filter.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 @@ -90,6 +91,7 @@ CPP_FILES+=../../zen/legacy_compiler.cpp CPP_FILES+=../../zen/open_ssl.cpp CPP_FILES+=../../zen/process_priority.cpp CPP_FILES+=../../zen/shutdown.cpp +CPP_FILES+=../../zen/sys_error.cpp CPP_FILES+=../../zen/system.cpp CPP_FILES+=../../zen/thread.cpp CPP_FILES+=../../zen/zlib_wrap.cpp diff --git a/FreeFileSync/Source/RealTimeSync/Makefile b/FreeFileSync/Source/RealTimeSync/Makefile index 3d0e98ec..9c7f88e0 100755 --- a/FreeFileSync/Source/RealTimeSync/Makefile +++ b/FreeFileSync/Source/RealTimeSync/Makefile @@ -20,11 +20,11 @@ CPP_FILES+=tray_menu.cpp CPP_FILES+=monitor.cpp CPP_FILES+=folder_selector2.cpp CPP_FILES+=../afs/abstract.cpp -CPP_FILES+=../base/icon_buffer.cpp CPP_FILES+=../base/icon_loader.cpp -CPP_FILES+=../base/localization.cpp CPP_FILES+=../base/resolve_path.cpp -CPP_FILES+=../base/ffs_paths.cpp +CPP_FILES+=../ffs_paths.cpp +CPP_FILES+=../icon_buffer.cpp +CPP_FILES+=../localization.cpp CPP_FILES+=../../../zen/dir_watcher.cpp CPP_FILES+=../../../zen/file_access.cpp CPP_FILES+=../../../zen/file_io.cpp @@ -32,6 +32,7 @@ CPP_FILES+=../../../zen/file_traverser.cpp CPP_FILES+=../../../zen/format_unit.cpp CPP_FILES+=../../../zen/legacy_compiler.cpp CPP_FILES+=../../../zen/shutdown.cpp +CPP_FILES+=../../../zen/sys_error.cpp CPP_FILES+=../../../zen/thread.cpp CPP_FILES+=../../../zen/zstring.cpp CPP_FILES+=../../../wx+/file_drop.cpp diff --git a/FreeFileSync/Source/RealTimeSync/application.cpp b/FreeFileSync/Source/RealTimeSync/application.cpp index 7f81883b..037d7059 100644 --- a/FreeFileSync/Source/RealTimeSync/application.cpp +++ b/FreeFileSync/Source/RealTimeSync/application.cpp @@ -15,12 +15,12 @@ #include #include #include "config.h" -#include "../base/localization.h" -#include "../base/ffs_paths.h" -#include "../base/return_codes.h" -#include "../base/fatal_error.h" -#include "../base/help_provider.h" #include "../base/resolve_path.h" +#include "../localization.h" +#include "../ffs_paths.h" +#include "../return_codes.h" +#include "../fatal_error.h" +#include "../help_provider.h" #include @@ -61,13 +61,15 @@ bool Application::OnInit() (fff::getResourceDirPf() + "Gtk3Styles.css").c_str(), //const gchar* path, &error); //GError** error if (error) - throw SysError(formatSystemError(L"gtk_css_provider_load_from_data", replaceCpy(_("Error Code %x"), L"%x", numberTo(error->code)), utfTo(error->message))); + throw SysError(formatSystemError(L"gtk_css_provider_load_from_data", replaceCpy(_("Error Code %x"), L"%x", + numberTo(error->code)), + utfTo(error->message))); ::gtk_style_context_add_provider_for_screen(::gdk_screen_get_default(), //GdkScreen* screen, GTK_STYLE_PROVIDER(provider), //GtkStyleProvider* provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); //guint priority } - catch (const SysError& e) { std::cerr << utfTo(e.toString()) << "\n"; } + catch (const SysError& e) { std::cerr << utfTo(e.toString()) << '\n'; } #else #error unknown GTK version! #endif @@ -158,7 +160,7 @@ int Application::OnRun() fff::logFatalError(e.what()); //it's not always possible to display a message box, e.g. corrupted stack, however low-level file output works! const auto titleFmt = copyStringTo(wxTheApp->GetAppDisplayName()) + SPACED_DASH + _("An exception occurred"); - std::cerr << utfTo(titleFmt + SPACED_DASH) << e.what() << "\n"; + std::cerr << utfTo(titleFmt + SPACED_DASH) << e.what() << '\n'; return fff::FFS_RC_EXCEPTION; } //catch (...) -> let it crash and create mini dump!!! diff --git a/FreeFileSync/Source/RealTimeSync/config.cpp b/FreeFileSync/Source/RealTimeSync/config.cpp index 3f084f3a..7051c6d1 100644 --- a/FreeFileSync/Source/RealTimeSync/config.cpp +++ b/FreeFileSync/Source/RealTimeSync/config.cpp @@ -8,8 +8,8 @@ #include #include #include -#include "../base/ffs_paths.h" -#include "../base/localization.h" +#include "../ffs_paths.h" +#include "../localization.h" using namespace zen; using namespace rts; diff --git a/FreeFileSync/Source/RealTimeSync/gui_generated.cpp b/FreeFileSync/Source/RealTimeSync/gui_generated.cpp index 7709981d..dccc3495 100644 --- a/FreeFileSync/Source/RealTimeSync/gui_generated.cpp +++ b/FreeFileSync/Source/RealTimeSync/gui_generated.cpp @@ -121,10 +121,13 @@ MainDlgGenerated::MainDlgGenerated( wxWindow* parent, wxWindowID id, const wxStr m_staticText11->Wrap( -1 ); m_staticText11->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) ); - bSizer152->Add( m_staticText11, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 2 ); + bSizer152->Add( m_staticText11, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 2 ); + m_hyperlink243 = new wxHyperlinkCtrl( this, wxID_ANY, _("Show examples"), wxEmptyString, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + bSizer152->Add( m_hyperlink243, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 ); - bSizer161->Add( bSizer152, 0, wxALIGN_CENTER_HORIZONTAL|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); + + bSizer161->Add( bSizer152, 0, wxALL|wxALIGN_CENTER_HORIZONTAL, 5 ); bSizerMain->Add( bSizer161, 0, wxALL|wxEXPAND, 5 ); @@ -303,6 +306,7 @@ MainDlgGenerated::MainDlgGenerated( wxWindow* parent, wxWindowID id, const wxStr m_menuFile->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDlgGenerated::OnMenuQuit ), this, m_menuItemQuit->GetId()); m_menuHelp->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDlgGenerated::OnShowHelp ), this, m_menuItemContent->GetId()); m_menuHelp->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDlgGenerated::OnMenuAbout ), this, m_menuItemAbout->GetId()); + m_hyperlink243->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( MainDlgGenerated::OnHelpRealTimeSync ), NULL, this ); m_bpButtonAddFolder->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDlgGenerated::OnAddFolder ), NULL, this ); m_bpButtonRemoveTopFolder->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDlgGenerated::OnRemoveTopFolder ), NULL, this ); m_buttonStart->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDlgGenerated::OnStart ), NULL, this ); diff --git a/FreeFileSync/Source/RealTimeSync/gui_generated.h b/FreeFileSync/Source/RealTimeSync/gui_generated.h index 1fd190f0..5cc0f0ca 100644 --- a/FreeFileSync/Source/RealTimeSync/gui_generated.h +++ b/FreeFileSync/Source/RealTimeSync/gui_generated.h @@ -24,6 +24,7 @@ namespace zen { class BitmapTextButton; } #include #include #include +#include #include #include #include @@ -64,6 +65,7 @@ protected: wxStaticText* m_staticText10; wxStaticBitmap* m_bitmapBatch; wxStaticText* m_staticText11; + wxHyperlinkCtrl* m_hyperlink243; wxStaticLine* m_staticline2; wxPanel* m_panelMain; wxStaticBitmap* m_bitmapFolders; @@ -95,6 +97,7 @@ protected: virtual void OnMenuQuit( wxCommandEvent& event ) { event.Skip(); } virtual void OnShowHelp( wxCommandEvent& event ) { event.Skip(); } virtual void OnMenuAbout( wxCommandEvent& event ) { event.Skip(); } + virtual void OnHelpRealTimeSync( wxHyperlinkEvent& event ) { event.Skip(); } virtual void OnAddFolder( wxCommandEvent& event ) { event.Skip(); } virtual void OnRemoveTopFolder( wxCommandEvent& event ) { event.Skip(); } virtual void OnStart( wxCommandEvent& event ) { event.Skip(); } diff --git a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp index 57e188ea..694d6e79 100644 --- a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp +++ b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp @@ -17,9 +17,9 @@ #include "config.h" #include "tray_menu.h" #include "app_icon.h" -#include "../base/help_provider.h" -#include "../base/icon_buffer.h" -#include "../base/ffs_paths.h" +#include "../help_provider.h" +#include "../icon_buffer.h" +#include "../ffs_paths.h" #include "../version/version.h" #include @@ -177,6 +177,7 @@ void MainDialog::onQueryEndSession() } + void MainDialog::OnShowHelp(wxCommandEvent& event) { fff::displayHelpEntry(L"realtimesync", this); @@ -201,7 +202,7 @@ void MainDialog::OnMenuAbout(wxCommandEvent& event) #endif build += SPACED_BULLET; - build += formatTime(FORMAT_DATE, getCompileTime()); + build += utfTo(formatTime(formatDateTag, getCompileTime())); showNotificationDialog(this, DialogInfoType::info, PopupDialogCfg(). setTitle(_("About")). diff --git a/FreeFileSync/Source/RealTimeSync/main_dlg.h b/FreeFileSync/Source/RealTimeSync/main_dlg.h index d20b889c..39052f93 100644 --- a/FreeFileSync/Source/RealTimeSync/main_dlg.h +++ b/FreeFileSync/Source/RealTimeSync/main_dlg.h @@ -38,6 +38,7 @@ private: void OnClose (wxCloseEvent& event ) override { Destroy(); } void OnShowHelp (wxCommandEvent& event) override; + void OnHelpRealTimeSync(wxHyperlinkEvent& event) override { OnShowHelp(event); } void OnMenuAbout (wxCommandEvent& event) override; void OnAddFolder (wxCommandEvent& event) override; void OnRemoveFolder (wxCommandEvent& event); diff --git a/FreeFileSync/Source/RealTimeSync/monitor.cpp b/FreeFileSync/Source/RealTimeSync/monitor.cpp index d5266c6c..66a83f3c 100644 --- a/FreeFileSync/Source/RealTimeSync/monitor.cpp +++ b/FreeFileSync/Source/RealTimeSync/monitor.cpp @@ -26,10 +26,10 @@ std::set waitForMissingDirs(const std::vector& const std::function& requestUiUpdate, std::chrono::milliseconds cbInterval) { //early failure! check for unsupported folder paths: - for (const Zstring& protoName : { Zstr("ftp"), Zstr("sftp"), Zstr("mtp"), Zstr("gdrive") }) + for (const std::string& protoName : { "ftp", "sftp", "mtp", "gdrive" }) for (const Zstring& phrase : folderPathPhrases) //hopefully clear enough now: https://freefilesync.org/forum/viewtopic.php?t=4302 - if (startsWithAsciiNoCase(trimCpy(phrase), protoName + Zstr(":"))) + if (startsWithAsciiNoCase(trimCpy(phrase), protoName + ':')) throw FileError(replaceCpy(_("The %x protocol does not support directory monitoring:"), L"%x", utfTo(protoName)) + L"\n\n" + fmtPath(phrase)); for (;;) diff --git a/FreeFileSync/Source/RealTimeSync/tray_menu.cpp b/FreeFileSync/Source/RealTimeSync/tray_menu.cpp index a4c1a91a..09928566 100644 --- a/FreeFileSync/Source/RealTimeSync/tray_menu.cpp +++ b/FreeFileSync/Source/RealTimeSync/tray_menu.cpp @@ -91,7 +91,7 @@ public: case TRAY_MODE_WAITING: assert(!missingFolderPath.empty()); - setTrayIcon(greyScale(trayBmp_), _("Waiting until directory is available:") + L" " + fmtPath(missingFolderPath)); + setTrayIcon(greyScale(trayBmp_), _("Waiting until directory is available:") + L' ' + fmtPath(missingFolderPath)); break; case TRAY_MODE_ERROR: diff --git a/FreeFileSync/Source/afs/abstract.cpp b/FreeFileSync/Source/afs/abstract.cpp index 9454f68b..10cb7995 100644 --- a/FreeFileSync/Source/afs/abstract.cpp +++ b/FreeFileSync/Source/afs/abstract.cpp @@ -399,7 +399,7 @@ void AFS::removeFileIfExists(const AbstractPath& ap) //throw FileError if (!AFS::itemStillExists(ap)) //throw FileError return; } - catch (const FileError& e2) { throw FileError(replaceCpy(_("Cannot delete file %x."), L"%x", fmtPath(getDisplayPath(ap))), replaceCpy(e2.toString(), L"\n\n", L"\n")); } + catch (const FileError& e2) { throw FileError(replaceCpy(_("Cannot delete file %x."), L"%x", fmtPath(getDisplayPath(ap))), replaceCpy(e2.toString(), L"\n\n", L'\n')); } //more relevant than previous exception (which could be "item not found") throw; } @@ -419,7 +419,7 @@ void AFS::removeSymlinkIfExists(const AbstractPath& ap) //throw FileError if (!AFS::itemStillExists(ap)) //throw FileError return; } - catch (const FileError& e2) { throw FileError(replaceCpy(_("Cannot delete symbolic link %x."), L"%x", fmtPath(getDisplayPath(ap))), replaceCpy(e2.toString(), L"\n\n", L"\n")); } + catch (const FileError& e2) { throw FileError(replaceCpy(_("Cannot delete symbolic link %x."), L"%x", fmtPath(getDisplayPath(ap))), replaceCpy(e2.toString(), L"\n\n", L'\n')); } //more relevant than previous exception (which could be "item not found") throw; } @@ -439,7 +439,7 @@ void AFS::removeEmptyFolderIfExists(const AbstractPath& ap) //throw FileError if (!AFS::itemStillExists(ap)) //throw FileError return; } - catch (const FileError& e2) { throw FileError(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtPath(getDisplayPath(ap))), replaceCpy(e2.toString(), L"\n\n", L"\n")); } + catch (const FileError& e2) { throw FileError(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtPath(getDisplayPath(ap))), replaceCpy(e2.toString(), L"\n\n", L'\n')); } //more relevant than previous exception (which could be "item not found") throw; diff --git a/FreeFileSync/Source/afs/abstract.h b/FreeFileSync/Source/afs/abstract.h index bc4932b1..d48c5f87 100644 --- a/FreeFileSync/Source/afs/abstract.h +++ b/FreeFileSync/Source/afs/abstract.h @@ -79,7 +79,7 @@ struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model t static bool hasNativeTransactionalCopy(const AbstractPath& ap) { return ap.afsDevice.ref().hasNativeTransactionalCopy(); } //---------------------------------------------------------------------------------------------------------------- - using FileId = zen::Zbase; //AfsDevice-dependent unique ID + using FileId = std::string; //AfsDevice-dependent unique ID enum class ItemType : unsigned char { @@ -497,8 +497,8 @@ void AbstractFileSystem::moveAndRenameItem(const AbstractPath& pathFrom, const A return pathFrom.afsDevice.ref().moveAndRenameItemForSameAfsType(pathFrom.afsPath, pathTo); //throw FileError, ErrorMoveUnsupported throw ErrorMoveUnsupported(replaceCpy(replaceCpy(_("Cannot move file %x to %y."), - L"%x", L"\n" + fmtPath(getDisplayPath(pathFrom))), - L"%y", L"\n" + fmtPath(getDisplayPath(pathTo))), _("Operation not supported between different devices.")); + L"%x", L'\n' + fmtPath(getDisplayPath(pathFrom))), + L"%y", L'\n' + fmtPath(getDisplayPath(pathTo))), _("Operation not supported between different devices.")); } @@ -530,8 +530,8 @@ void AbstractFileSystem::copySymlink(const AbstractPath& apSource, const Abstrac return apSource.afsDevice.ref().copySymlinkForSameAfsType(apSource.afsPath, apTarget, copyFilePermissions); //throw FileError throw FileError(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."), - L"%x", L"\n" + fmtPath(getDisplayPath(apSource))), - L"%y", L"\n" + fmtPath(getDisplayPath(apTarget))), _("Operation not supported between different devices.")); + L"%x", L'\n' + fmtPath(getDisplayPath(apSource))), + L"%y", L'\n' + fmtPath(getDisplayPath(apTarget))), _("Operation not supported between different devices.")); } } diff --git a/FreeFileSync/Source/afs/abstract_impl.h b/FreeFileSync/Source/afs/abstract_impl.h index c2d07828..ceabb6b6 100644 --- a/FreeFileSync/Source/afs/abstract_impl.h +++ b/FreeFileSync/Source/afs/abstract_impl.h @@ -123,7 +123,7 @@ public: size_t read(void* buffer, size_t bytesToRead) //throw { 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__) + ":" + zen::numberTo(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + zen::numberTo(__LINE__)); auto it = static_cast(buffer); const auto itEnd = it + bytesToRead; diff --git a/FreeFileSync/Source/afs/ftp.cpp b/FreeFileSync/Source/afs/ftp.cpp index e502936a..394c5966 100644 --- a/FreeFileSync/Source/afs/ftp.cpp +++ b/FreeFileSync/Source/afs/ftp.cpp @@ -104,8 +104,8 @@ Zstring ansiToUtfEncoding(const std::string& str) //throw SysError if (!error) throw SysError(L"g_convert: unknown error. (" + utfTo(str) + L")"); //user should never see this - throw SysError(formatSystemError(L"g_convert", replaceCpy(_("Error Code %x"), L"%x", numberTo(error->code)), - utfTo(error->message)) + L" (" + utfTo(str) + L")"); + throw SysError(formatSystemError(L"g_convert(" + utfTo(str) + L")", + replaceCpy(_("Error Code %x"), L"%x", numberTo(error->code)), utfTo(error->message)) ); } ZEN_ON_SCOPE_EXIT(::g_free(utfStr)); @@ -134,8 +134,8 @@ std::string utfToAnsiEncoding(const Zstring& str) //throw SysError if (!error) throw SysError(L"g_convert: unknown error. (" + utfTo(str) + L")"); //user should never see this - throw SysError(formatSystemError(L"g_convert", replaceCpy(_("Error Code %x"), L"%x", numberTo(error->code)), - utfTo(error->message)) + L" (" + utfTo(str) + L")"); + throw SysError(formatSystemError(L"g_convert(" + utfTo(str) + L")", + replaceCpy(_("Error Code %x"), L"%x", numberTo(error->code)), utfTo(error->message))); } ZEN_ON_SCOPE_EXIT(::g_free(ansiStr)); @@ -239,7 +239,7 @@ private: //---------------------------------------------------------------------------------------------------------------- -std::wstring formatFtpStatusCode(int sc) +std::wstring formatFtpStatus(int sc) { const wchar_t* statusText = [&] //https://en.wikipedia.org/wiki/List_of_FTP_server_return_codes { @@ -495,7 +495,7 @@ public: errorMsg += (errorMsg.empty() ? L"" : L"\n") + trimCpy(utfTo(headerLines.back())); //that *should* be the servers error response } else //failed to get server response - errorMsg += (errorMsg.empty() ? L"" : L"\n") + formatFtpStatusCode(ftpStatusCode); + errorMsg += (errorMsg.empty() ? L"" : L"\n") + formatFtpStatus(ftpStatusCode); #if 0 //utfTo(::curl_easy_strerror(ec)) is uninteresting //use CURLINFO_OS_ERRNO ?? https://curl.haxx.se/libcurl/c/CURLINFO_OS_ERRNO.html @@ -686,7 +686,7 @@ private: curl_socket_t currentSocket = 0; const CURLcode rc = ::curl_easy_getinfo(easyHandle_, CURLINFO_ACTIVESOCKET, ¤tSocket); if (rc != CURLE_OK) - throw SysError(formatSystemError(L"curl_easy_getinfo: CURLINFO_ACTIVESOCKET", formatCurlStatusCode(rc), utfTo(::curl_easy_strerror(rc)))); + throw SysError(formatSystemError(L"curl_easy_getinfo(CURLINFO_ACTIVESOCKET)", formatCurlStatusCode(rc), utfTo(::curl_easy_strerror(rc)))); if (currentSocket != CURL_SOCKET_BAD) return currentSocket; } @@ -1288,7 +1288,7 @@ private: } catch (const SysError& e) { - throw SysError(L"Failed to parse FTP response. (" + utfTo(rawLine) + L")" + (haveGroup ? L"" : L" [no-group]") + L" " + e.toString()); + throw SysError(L"Failed to parse FTP response. (" + utfTo(rawLine) + L")" + (haveGroup ? L"" : L" [no-group]") + L' ' + e.toString()); } } @@ -1769,7 +1769,7 @@ private: if (modTime_) try { - const std::string isoTime = formatTime("%Y%m%d%H%M%S", getUtcTime(*modTime_)); //returns empty string on failure + const std::string isoTime = utfTo(formatTime(Zstr("%Y%m%d%H%M%S"), getUtcTime(*modTime_))); //returns empty string on failure if (isoTime.empty()) throw SysError(L"Invalid modification time (time_t: " + numberTo(*modTime_) + L")"); @@ -1778,7 +1778,7 @@ private: if (!session.supportsMfmt(login_.timeoutSec)) //throw SysError throw SysError(L"Server does not support the MFMT command."); - session.runSingleFtpCommand("MFMT " + isoTime + " " + session.getServerPathInternal(afsPath_, login_.timeoutSec), + session.runSingleFtpCommand("MFMT " + isoTime + ' ' + session.getServerPathInternal(afsPath_, login_.timeoutSec), true /*requiresUtf8*/, login_.timeoutSec); //throw SysError //Does MFMT follow symlinks?? Anyway, our FTP implementation supports folder symlinks only }); @@ -2040,8 +2040,8 @@ private: void copySymlinkForSameAfsType(const AfsPath& afsPathSource, const AbstractPath& apTarget, bool copyFilePermissions) const override { throw FileError(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."), - L"%x", L"\n" + fmtPath(getDisplayPath(afsPathSource))), - L"%y", L"\n" + fmtPath(AFS::getDisplayPath(apTarget))), _("Operation not supported by device.")); + L"%x", L'\n' + fmtPath(getDisplayPath(afsPathSource))), + L"%y", L'\n' + fmtPath(AFS::getDisplayPath(apTarget))), _("Operation not supported by device.")); } //target existing: undefined behavior! (fail/overwrite/auto-rename) @@ -2052,8 +2052,8 @@ private: void moveAndRenameItemForSameAfsType(const AfsPath& pathFrom, const AbstractPath& pathTo) const override //throw FileError, ErrorMoveUnsupported { auto generateErrorMsg = [&] { return replaceCpy(replaceCpy(_("Cannot move file %x to %y."), - L"%x", L"\n" + fmtPath(getDisplayPath(pathFrom))), - L"%y", L"\n" + fmtPath(AFS::getDisplayPath(pathTo))); + L"%x", L'\n' + fmtPath(getDisplayPath(pathFrom))), + L"%y", L'\n' + fmtPath(AFS::getDisplayPath(pathTo))); }; if (compareDeviceSameAfsType(pathTo.afsDevice.ref()) != 0) @@ -2121,7 +2121,7 @@ Zstring concatenateFtpFolderPathPhrase(const FtpLoginInfo& login, const AfsPath& { Zstring port; if (login.port > 0) - port = Zstr(":") + numberTo(login.port); + port = Zstr(':') + numberTo(login.port); Zstring options; if (login.timeoutSec != FtpLoginInfo().timeoutSec) @@ -2182,11 +2182,11 @@ Zstring fff::condenseToFtpFolderPathPhrase(const FtpLoginInfo& login, const Zstr loginTmp.timeoutSec = std::max(1, loginTmp.timeoutSec); - if (startsWithAsciiNoCase(loginTmp.server, Zstr("http:" )) || - startsWithAsciiNoCase(loginTmp.server, Zstr("https:")) || - startsWithAsciiNoCase(loginTmp.server, Zstr("ftp:" )) || - startsWithAsciiNoCase(loginTmp.server, Zstr("ftps:" )) || - startsWithAsciiNoCase(loginTmp.server, Zstr("sftp:" ))) + if (startsWithAsciiNoCase(loginTmp.server, "http:" ) || + startsWithAsciiNoCase(loginTmp.server, "https:") || + startsWithAsciiNoCase(loginTmp.server, "ftp:" ) || + startsWithAsciiNoCase(loginTmp.server, "ftps:" ) || + startsWithAsciiNoCase(loginTmp.server, "sftp:" )) loginTmp.server = afterFirst(loginTmp.server, Zstr(':'), IF_MISSING_RETURN_NONE); trim(loginTmp.server, true, false, [](Zchar c) { return c == Zstr('/') || c == Zstr('\\'); }); @@ -2211,7 +2211,7 @@ FtpPathInfo fff::getResolvedFtpPath(const Zstring& folderPathPhrase) //noexcept const Zstring fullPathOpt = afterFirst(pathPhrase, Zstr('@'), IF_MISSING_RETURN_ALL); FtpLoginInfo login; - login.username = decodeFtpUsername(beforeFirst(credentials, Zstr(':'), IF_MISSING_RETURN_ALL)); //support standard FTP syntax, even though ":" + login.username = decodeFtpUsername(beforeFirst(credentials, Zstr(':'), IF_MISSING_RETURN_ALL)); //support standard FTP syntax, even though ':' login.password = afterFirst(credentials, Zstr(':'), IF_MISSING_RETURN_NONE); //is not used by our concatenateSftpFolderPathPhrase()! const Zstring fullPath = beforeFirst(fullPathOpt, Zstr('|'), IF_MISSING_RETURN_ALL); diff --git a/FreeFileSync/Source/afs/ftp_common.h b/FreeFileSync/Source/afs/ftp_common.h index edd3da54..c8fcff0a 100644 --- a/FreeFileSync/Source/afs/ftp_common.h +++ b/FreeFileSync/Source/afs/ftp_common.h @@ -35,9 +35,9 @@ inline Zstring encodeFtpUsername(Zstring name) { using namespace zen; - replace(name, Zstr("%"), Zstr("%25")); //first! - replace(name, Zstr("@"), Zstr("%40")); - replace(name, Zstr(":"), Zstr("%3A")); + replace(name, Zstr('%'), Zstr("%25")); //first! + replace(name, Zstr('@'), Zstr("%40")); + replace(name, Zstr(':'), Zstr("%3A")); return name; } @@ -46,10 +46,10 @@ inline Zstring decodeFtpUsername(Zstring name) { using namespace zen; - replace(name, Zstr("%40"), Zstr("@")); - replace(name, Zstr("%3A"), Zstr(":")); - replace(name, Zstr("%3a"), Zstr(":")); - replace(name, Zstr("%25"), Zstr("%")); //last! + replace(name, Zstr("%40"), Zstr('@')); + replace(name, Zstr("%3A"), Zstr(':')); + replace(name, Zstr("%3a"), Zstr(':')); + replace(name, Zstr("%25"), Zstr('%')); //last! return name; } diff --git a/FreeFileSync/Source/afs/gdrive.cpp b/FreeFileSync/Source/afs/gdrive.cpp index 8c92eb47..467c40fe 100644 --- a/FreeFileSync/Source/afs/gdrive.cpp +++ b/FreeFileSync/Source/afs/gdrive.cpp @@ -294,7 +294,7 @@ HttpSession::Result googleHttpsRequest(const std::string& serverRelPath, //throw { //https://developers.google.com/drive/api/v3/performance //"In order to receive a gzip-encoded response you must do two things: Set an Accept-Encoding header, ["gzip" automatically set by HttpSession] - { CURLOPT_USERAGENT, "FreeFileSync (gzip)" }, // and modify your user agent to contain the string gzip." + { CURLOPT_USERAGENT, "FreeFileSync (gzip)" }, //and modify your user agent to contain the string gzip." }; append(options, extraOptions); @@ -547,7 +547,7 @@ for (;;) //::accept() blocks forever if no client connects (e.g. user just close const size_t blockSize = 64 * 1024; reqLine.resize(reqLine.size() + blockSize); const size_t bytesReceived = tryReadSocket(clientSocket, &*(reqLine.end() - blockSize), blockSize); //throw SysError - reqLine.resize(reqLine.size() - blockSize + bytesReceived); //caveat: unsigned arithmetics + reqLine.resize(reqLine.size() - (blockSize - bytesReceived)); //caveat: unsigned arithmetics if (contains(reqLine, "\r\n")) { @@ -1058,7 +1058,7 @@ void gdriveMoveAndRenameItem(const std::string& itemId, const std::string& paren //more Google Drive peculiarities: changing the file name changes modifiedTime!!! => workaround: //RFC 3339 date-time: e.g. "2018-09-29T08:39:12.053Z" - const std::string modTimeRfc = formatTime("%Y-%m-%dT%H:%M:%S.000Z", getUtcTime(newModTime)); //returns empty string on failure + const std::string modTimeRfc = utfTo(formatTime(Zstr("%Y-%m-%dT%H:%M:%S.000Z"), getUtcTime(newModTime))); //returns empty string on failure if (modTimeRfc.empty()) throw SysError(L"Invalid modification time (time_t: " + numberTo(newModTime) + L")"); @@ -1250,7 +1250,7 @@ std::string /*itemId*/ gdriveUploadFile(const Zstring& fileName, const std::stri std::string postBuf = "{\n"; if (modTime) //convert to RFC 3339 date-time: e.g. "2018-09-29T08:39:12.053Z" { - const std::string& modTimeRfc = formatTime("%Y-%m-%dT%H:%M:%S.000Z", getUtcTime(*modTime)); //returns empty string on failure + const std::string& modTimeRfc = utfTo(formatTime(Zstr("%Y-%m-%dT%H:%M:%S.000Z"), getUtcTime(*modTime))); //returns empty string on failure if (modTimeRfc.empty()) throw SysError(L"Invalid modification time (time_t: " + numberTo(*modTime) + L")"); @@ -1345,7 +1345,7 @@ class GoogleAccessBuffer //per-user-session! => serialize access (perf: amortize public: GoogleAccessBuffer(const GoogleAccessInfo& accessInfo) : accessInfo_(accessInfo) {} - GoogleAccessBuffer(MemoryStreamIn& stream) //throw UnexpectedEndOfStreamError + GoogleAccessBuffer(MemoryStreamIn& stream) //throw UnexpectedEndOfStreamError { accessInfo_.accessToken.validUntil = readNumber(stream); // accessInfo_.accessToken.value = readContainer(stream); // @@ -1354,7 +1354,7 @@ public: accessInfo_.userInfo.email = utfTo< Zstring>(readContainer(stream)); // } - void serialize(MemoryStreamOut& stream) const + void serialize(MemoryStreamOut& stream) const { writeNumber(stream, accessInfo_.accessToken.validUntil); static_assert(sizeof(accessInfo_.accessToken.validUntil) <= sizeof(int64_t)); //ensure cross-platform compatibility! @@ -1379,7 +1379,7 @@ public: void update(const GoogleAccessInfo& accessInfo) { if (!equalAsciiNoCase(accessInfo.userInfo.email, accessInfo_.userInfo.email)) - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); accessInfo_ = accessInfo; } @@ -1402,7 +1402,7 @@ public: rootId_ (getRootItemId (accessBuf.getAccessToken())), //throw SysError accessBuf_(accessBuf) {} // - GoogleFileState(MemoryStreamIn& stream, GoogleAccessBuffer& accessBuf, int dbVersion) : accessBuf_(accessBuf) //throw UnexpectedEndOfStreamError + GoogleFileState(MemoryStreamIn& stream, GoogleAccessBuffer& accessBuf, int dbVersion) : accessBuf_(accessBuf) //throw UnexpectedEndOfStreamError { lastSyncToken_ = readContainer(stream); //UnexpectedEndOfStreamError rootId_ = readContainer(stream); // @@ -1441,14 +1441,14 @@ public: } } - void serialize(MemoryStreamOut& stream) const + void serialize(MemoryStreamOut& stream) const { writeContainer(stream, lastSyncToken_); writeContainer(stream, rootId_); for (const auto& [folderId, content] : folderContents_) if (folderId.empty()) - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); else if (content.isKnownFolder) writeContainer(stream, folderId); writeContainer(stream, std::string()); //sentinel @@ -1501,7 +1501,7 @@ public: const std::string fileId = getItemId(afsPath); //throw SysError auto it = itemDetails_.find(fileId); if (it == itemDetails_.end()) - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); return *it; } @@ -1657,7 +1657,7 @@ private: notifyFolderContent(registerFileStateDelta(), folderId, readFolderContent(folderId, accessBuf_.getAccessToken())); //throw SysError if (!folderContents_[folderId].isKnownFolder) - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); childItems = &folderContents_[folderId].childItems; } @@ -1668,7 +1668,7 @@ private: { if (itFound != itemDetails_.end()) throw SysError(replaceCpy(_("Cannot find %x."), L"%x", - fmtPath(getGoogleDisplayPath({ accessBuf_.getUserEmail(), AfsPath(nativeAppendPaths(folderPath.value, relPath.front())) }))) + L" " + + fmtPath(getGoogleDisplayPath({ accessBuf_.getUserEmail(), AfsPath(nativeAppendPaths(folderPath.value, relPath.front())) }))) + L' ' + replaceCpy(_("The name %x is used by more than one item in the folder."), L"%x", fmtPath(relPath.front()))); itFound = itDetails; @@ -1714,7 +1714,7 @@ private: if (it != itemDetails_.end()) //update { if (it->second.isFolder != details->isFolder) - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo(__LINE__)); //WTF!? + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); //WTF!? std::vector parentIdsNew = details->parentIds; std::vector parentIdsRemoved = it->second.parentIds; @@ -1992,7 +1992,7 @@ private: static void saveSession(const Zstring& dbFilePath, const UserSession& userSession) //throw FileError { - MemoryStreamOut streamOut; + MemoryStreamOut streamOut; writeArray(streamOut, DB_FILE_DESCR, sizeof(DB_FILE_DESCR)); writeNumber(streamOut, DB_FILE_VERSION); @@ -2000,7 +2000,7 @@ private: userSession.accessBuf.ref().serialize(streamOut); userSession.fileState.ref().serialize(streamOut); - ByteArray zstreamOut; + std::string zstreamOut; try { zstreamOut = compress(streamOut.ref(), 3 /*compression level: see db_file.cpp*/); //throw SysError @@ -2012,13 +2012,13 @@ private: static UserSession loadSession(const Zstring& dbFilePath) //throw FileError { - ByteArray zstream = loadBinContainer(dbFilePath, nullptr /*notifyUnbufferedIO*/); //throw FileError - ByteArray rawStream; + const std::string zstream = loadBinContainer(dbFilePath, nullptr /*notifyUnbufferedIO*/); //throw FileError + std::string rawStream; try { rawStream = decompress(zstream); //throw SysError } - catch (const SysError& e) { throw FileError(_("Database file is corrupted:") + L" " + fmtPath(dbFilePath), e.toString()); } + catch (const SysError& e) { throw FileError(_("Database file is corrupted:") + L' ' + fmtPath(dbFilePath), e.toString()); } MemoryStreamIn streamIn(rawStream); try @@ -2041,7 +2041,7 @@ private: auto fileState = makeSharedRef(streamIn, accessBuf.ref(), version); //throw UnexpectedEndOfStreamError return { accessBuf, fileState }; } - catch (UnexpectedEndOfStreamError&) { throw FileError(_("Database file is corrupted:") + L" " + fmtPath(dbFilePath), L"Unexpected end of stream."); } + catch (UnexpectedEndOfStreamError&) { throw FileError(_("Database file is corrupted:") + L' ' + fmtPath(dbFilePath), L"Unexpected end of stream."); } } struct UserSession @@ -2162,7 +2162,7 @@ private: } else { - AFS::FileId fileId = copyStringTo(item.itemId); + AFS::FileId fileId = item.itemId; cb.onFile({ itemName, item.details.fileSize, item.details.modTime, fileId, nullptr /*symlinkInfo*/ }); //throw X } } @@ -2245,7 +2245,7 @@ struct InputStreamGdrive : public AbstractFileSystem::InputStream const auto& [itemId, gdriveAttr] = fileState.getFileAttributes(gdrivePath_.itemPath); //throw SysError attr.modTime = gdriveAttr.modTime; attr.fileSize = gdriveAttr.fileSize; - attr.fileId = copyStringTo(itemId); + attr.fileId = itemId; }); } catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(getGoogleDisplayPath(gdrivePath_))), e.toString()); } @@ -2336,7 +2336,7 @@ struct OutputStreamGdrive : public AbstractFileSystem::OutputStreamImpl fileState.notifyItemCreated(aai.stateDelta, newFileItem); }); - pFileId.set_value(copyStringTo(fileIdNew)); + pFileId.set_value(fileIdNew); } catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getGoogleDisplayPath(gdrivePath))), e.toString()); } } @@ -2621,8 +2621,8 @@ private: void copySymlinkForSameAfsType(const AfsPath& afsPathSource, const AbstractPath& apTarget, bool copyFilePermissions) const override //throw FileError { throw FileError(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."), - L"%x", L"\n" + fmtPath(getDisplayPath(afsPathSource))), - L"%y", L"\n" + fmtPath(AFS::getDisplayPath(apTarget))), _("Operation not supported by device.")); + L"%x", L'\n' + fmtPath(getDisplayPath(afsPathSource))), + L"%y", L'\n' + fmtPath(AFS::getDisplayPath(apTarget))), _("Operation not supported by device.")); } //target existing: undefined behavior! (fail/overwrite/auto-rename) @@ -2630,8 +2630,8 @@ private: void moveAndRenameItemForSameAfsType(const AfsPath& pathFrom, const AbstractPath& pathTo) const override //throw FileError, ErrorMoveUnsupported { auto generateErrorMsg = [&] { return replaceCpy(replaceCpy(_("Cannot move file %x to %y."), - L"%x", L"\n" + fmtPath(getDisplayPath(pathFrom))), - L"%y", L"\n" + fmtPath(AFS::getDisplayPath(pathTo))); + L"%x", L'\n' + fmtPath(getDisplayPath(pathFrom))), + L"%y", L'\n' + fmtPath(AFS::getDisplayPath(pathTo))); }; if (compareDeviceSameAfsType(pathTo.afsDevice.ref()) != 0) diff --git a/FreeFileSync/Source/afs/native.cpp b/FreeFileSync/Source/afs/native.cpp index da016788..6e1c96fc 100644 --- a/FreeFileSync/Source/afs/native.cpp +++ b/FreeFileSync/Source/afs/native.cpp @@ -118,8 +118,7 @@ std::vector getDirContentFlat(const Zstring& dirPath) //throw FileErr (itemNameRaw[1] == 0 || (itemNameRaw[1] == '.' && itemNameRaw[2] == 0))) continue; - /* - Unicode normalization is file-system-dependent: + /* Unicode normalization is file-system-dependent: OS Accepts Gives back ---------- ------- ---------- @@ -128,7 +127,7 @@ std::vector getDirContentFlat(const Zstring& dirPath) //throw FileErr Windows (NTFS, FAT) all some file systems return precomposed others decomposed UTF8: https://developer.apple.com/library/mac/#qa/qa1173/_index.html - - OS X edit controls and text fields may return precomposed UTF as directly received by keyboard or decomposed UTF that was copy & pasted in! + - OS X edit controls and text fields may return precomposed UTF as directly received by keyboard or decomposed UTF that was copy & pasted! - Posix APIs require decomposed form: https://freefilesync.org/forum/viewtopic.php?t=2480 => General recommendation: always preserve input UNCHANGED (both unicode normalization and case sensitivity) @@ -556,8 +555,8 @@ private: //=> maybe we can even save some actual I/O in some cases? if (compareDeviceSameAfsType(pathTo.afsDevice.ref()) != 0) throw ErrorMoveUnsupported(replaceCpy(replaceCpy(_("Cannot move file %x to %y."), - L"%x", L"\n" + fmtPath(getDisplayPath(pathFrom))), - L"%y", L"\n" + fmtPath(AFS::getDisplayPath(pathTo))), + L"%x", L'\n' + fmtPath(getDisplayPath(pathFrom))), + L"%y", L'\n' + fmtPath(AFS::getDisplayPath(pathTo))), _("Operation not supported between different devices.")); initComForThread(); //throw FileError const Zstring nativePathTarget = static_cast(pathTo.afsDevice.ref()).getNativePath(pathTo.afsPath); @@ -639,7 +638,7 @@ void RecycleSessionNative::recycleItemIfExists(const AbstractPath& itemPath, con std::optional itemPathNative = AFS::getNativeItemPath(itemPath); if (!itemPathNative) - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); recycleOrDeleteIfExists(*itemPathNative); //throw FileError } @@ -658,7 +657,7 @@ bool fff::acceptsItemPathPhraseNative(const Zstring& itemPathPhrase) //noexcept trim(path); - if (startsWith(path, Zstr("["))) //drive letter by volume name syntax + if (startsWith(path, Zstr('['))) //drive letter by volume name syntax return true; //don't accept relative paths!!! indistinguishable from MTP paths as shown in Explorer's address bar! diff --git a/FreeFileSync/Source/afs/sftp.cpp b/FreeFileSync/Source/afs/sftp.cpp index d6279ebd..ed0ff13e 100644 --- a/FreeFileSync/Source/afs/sftp.cpp +++ b/FreeFileSync/Source/afs/sftp.cpp @@ -302,7 +302,7 @@ public: } else throw SysError(replaceCpy(_("The server does not support authentication via %x."), L"%x", L"\"username/password\"") + - L"\n" +_("Required:") + L" " + utfTo(authList)); + L'\n' +_("Required:") + L' ' + utfTo(authList)); } break; @@ -310,7 +310,7 @@ public: { if (!supportAuthKeyfile) throw SysError(replaceCpy(_("The server does not support authentication via %x."), L"%x", L"\"key file\"") + - L"\n" +_("Required:") + L" " + utfTo(authList)); + L'\n' +_("Required:") + L' ' + utfTo(authList)); std::string passphrase = passwordUtf8; std::string pkStream; @@ -365,9 +365,9 @@ public: return nullptr; //other: maybe invalid, maybe not }(); if (invalidKeyFormat) - throw SysError(_("Authentication failed.") + L" " + + throw SysError(_("Authentication failed.") + L' ' + replaceCpy(L"%x is not an OpenSSH or PuTTY private key file.", L"%x", - fmtPath(sessionId_.privateKeyFilePath) + L" [" + invalidKeyFormat + L"]")); + fmtPath(sessionId_.privateKeyFilePath) + L" [" + invalidKeyFormat + L']')); throw SysError(formatLastSshError(L"libssh2_userauth_publickey_frommemory", nullptr)); } @@ -511,7 +511,7 @@ public: if (sshSessions.empty()) return; if (sshSessions.size() > FD_SETSIZE) //precise: this limit is for both fd_set containers *each*! - throw FatalSshError(_P("Cannot wait on more than 1 connection at a time.", "Cannot wait on more than %x connections at a time.", FD_SETSIZE) + L" " + + throw FatalSshError(_P("Cannot wait on more than 1 connection at a time.", "Cannot wait on more than %x connections at a time.", FD_SETSIZE) + L' ' + replaceCpy(_("Active connections: %x"), L"%x", numberTo(sshSessions.size()))); SocketType nfds = 0; fd_set rfd = {}; @@ -585,7 +585,7 @@ public: { if (sshSession.sftpChannels_.empty()) return msg; - return msg + L" " + replaceCpy(_("Failed to open SFTP channel number %x."), L"%x", numberTo(sshSession.sftpChannels_.size() + 1)); + return msg + L' ' + replaceCpy(_("Failed to open SFTP channel number %x."), L"%x", numberTo(sshSession.sftpChannels_.size() + 1)); }; std::optional firstSysError; @@ -1294,7 +1294,7 @@ private: { //libssh2_sftp_read has same semantics as Posix read: 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(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); assert(bytesToRead == getBlockSize()); ssize_t bytesRead = 0; @@ -1455,7 +1455,7 @@ private: size_t tryWrite(const void* buffer, size_t bytesToWrite) //throw FileError; may return short! CONTRACT: bytesToWrite > 0 { if (bytesToWrite == 0) - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); assert(bytesToWrite <= getBlockSize()); ssize_t bytesWritten = 0; @@ -1682,7 +1682,7 @@ private: std::string getSymlinkBinaryContent(const AfsPath& afsPath) const override //throw FileError { const unsigned int bufSize = 10000; - std::vector buf(bufSize + 1); //ensure buffer is always null-terminated since we don't evaluate the byte count returned by libssh2_sftp_readlink()! + std::string buf(bufSize + 1, '\0'); //ensure buffer is always null-terminated since we don't evaluate the byte count returned by libssh2_sftp_readlink()! try { runSftpCommand(login_, L"libssh2_sftp_readlink", //throw SysError @@ -1690,7 +1690,8 @@ private: } catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(getDisplayPath(afsPath))), e.toString()); } - return &buf[0]; + buf.resize(strLength(&buf[0])); + return buf; } //---------------------------------------------------------------------------------------------------------------- @@ -1743,16 +1744,16 @@ private: void copySymlinkForSameAfsType(const AfsPath& afsPathSource, const AbstractPath& apTarget, bool copyFilePermissions) const override { throw FileError(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."), - L"%x", L"\n" + fmtPath(getDisplayPath(afsPathSource))), - L"%y", L"\n" + fmtPath(AFS::getDisplayPath(apTarget))), _("Operation not supported by device.")); + L"%x", L'\n' + fmtPath(getDisplayPath(afsPathSource))), + L"%y", L'\n' + fmtPath(AFS::getDisplayPath(apTarget))), _("Operation not supported by device.")); } //target existing: undefined behavior! (fail/overwrite/auto-rename) => SFTP will fail with obscure LIBSSH2_FX_FAILURE error message void moveAndRenameItemForSameAfsType(const AfsPath& pathFrom, const AbstractPath& pathTo) const override //throw FileError, ErrorMoveUnsupported { auto generateErrorMsg = [&] { return replaceCpy(replaceCpy(_("Cannot move file %x to %y."), - L"%x", L"\n" + fmtPath(getDisplayPath(pathFrom))), - L"%y", L"\n" + fmtPath(AFS::getDisplayPath(pathTo))); + L"%x", L'\n' + fmtPath(getDisplayPath(pathFrom))), + L"%y", L'\n' + fmtPath(AFS::getDisplayPath(pathTo))); }; if (compareDeviceSameAfsType(pathTo.afsDevice.ref()) != 0) @@ -1849,7 +1850,7 @@ Zstring concatenateSftpFolderPathPhrase(const SftpLoginInfo& login, const AfsPat { Zstring port; if (login.port > 0) - port = Zstr(":") + numberTo(login.port); + port = Zstr(':') + numberTo(login.port); const SftpLoginInfo loginDefault; @@ -1915,11 +1916,11 @@ Zstring fff::condenseToSftpFolderPathPhrase(const SftpLoginInfo& login, const Zs loginTmp.timeoutSec = std::max(1, loginTmp.timeoutSec); loginTmp.traverserChannelsPerConnection = std::max(1, loginTmp.traverserChannelsPerConnection); - if (startsWithAsciiNoCase(loginTmp.server, Zstr("http:" )) || - startsWithAsciiNoCase(loginTmp.server, Zstr("https:")) || - startsWithAsciiNoCase(loginTmp.server, Zstr("ftp:" )) || - startsWithAsciiNoCase(loginTmp.server, Zstr("ftps:" )) || - startsWithAsciiNoCase(loginTmp.server, Zstr("sftp:" ))) + if (startsWithAsciiNoCase(loginTmp.server, "http:" ) || + startsWithAsciiNoCase(loginTmp.server, "https:") || + startsWithAsciiNoCase(loginTmp.server, "ftp:" ) || + startsWithAsciiNoCase(loginTmp.server, "ftps:" ) || + startsWithAsciiNoCase(loginTmp.server, "sftp:" )) loginTmp.server = afterFirst(loginTmp.server, Zstr(':'), IF_MISSING_RETURN_NONE); trim(loginTmp.server, true, false, [](Zchar c) { return c == Zstr('/') || c == Zstr('\\'); }); @@ -1948,7 +1949,7 @@ int fff::getServerMaxChannelsPerConnection(const SftpLoginInfo& login) //throw F if (numeric::dist(std::chrono::steady_clock::now(), startTime) > SFTP_CHANNEL_LIMIT_DETECTION_TIME_OUT) throw SysError(_P("Operation timed out after 1 second.", "Operation timed out after %x seconds.", - std::chrono::seconds(SFTP_CHANNEL_LIMIT_DETECTION_TIME_OUT).count()) + L" " + + std::chrono::seconds(SFTP_CHANNEL_LIMIT_DETECTION_TIME_OUT).count()) + L' ' + replaceCpy(_("Failed to open SFTP channel number %x."), L"%x", numberTo(exSession->getSftpChannelCount() + 1))); } } @@ -1976,7 +1977,7 @@ SftpPathInfo fff::getResolvedSftpPath(const Zstring& folderPathPhrase) //noexcep const Zstring fullPathOpt = afterFirst(pathPhrase, Zstr('@'), IF_MISSING_RETURN_ALL); SftpLoginInfo login; - login.username = decodeFtpUsername(beforeFirst(credentials, Zstr(':'), IF_MISSING_RETURN_ALL)); //support standard FTP syntax, even though ":" + login.username = decodeFtpUsername(beforeFirst(credentials, Zstr(':'), IF_MISSING_RETURN_ALL)); //support standard FTP syntax, even though ':' login.password = afterFirst(credentials, Zstr(':'), IF_MISSING_RETURN_NONE); //is not used by our concatenateSftpFolderPathPhrase()! const Zstring fullPath = beforeFirst(fullPathOpt, Zstr('|'), IF_MISSING_RETURN_ALL); diff --git a/FreeFileSync/Source/application.cpp b/FreeFileSync/Source/application.cpp new file mode 100644 index 00000000..65755b0e --- /dev/null +++ b/FreeFileSync/Source/application.cpp @@ -0,0 +1,649 @@ +// ***************************************************************************** +// * 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 "application.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "afs/concrete.h" +#include "base/algorithm.h" +#include "base/comparison.h" +#include "base/resolve_path.h" +#include "base/synchronization.h" +#include "ui/batch_status_handler.h" +#include "ui/main_dlg.h" +#include "base_tools.h" +#include "config.h" +#include "help_provider.h" +#include "fatal_error.h" +#include "log_file.h" + + #include + +using namespace zen; +using namespace fff; + + +IMPLEMENT_APP(Application) + + +namespace +{ + + +std::vector getCommandlineArgs(const wxApp& app) +{ + std::vector args; + for (int i = 1; i < app.argc; ++i) //wxWidgets screws up once again making "argv implicitly convertible to a wxChar**" in 2.9.3, + args.push_back(utfTo(wxString(app.argv[i]))); //so we are forced to use this pitiful excuse for a range construction!! + return args; +} + +const wxEventType EVENT_ENTER_EVENT_LOOP = wxNewEventType(); +} + +//################################################################################################################## + +bool Application::OnInit() +{ + //do not call wxApp::OnInit() to avoid using wxWidgets command line parser + + //GTK should already have been initialized by wxWidgets (see \src\gtk\app.cpp:wxApp::Initialize) +#if GTK_MAJOR_VERSION == 2 + ::gtk_rc_parse((getResourceDirPf() + "Gtk2Styles.rc").c_str()); + + //hang on Ubuntu 19.10 (GLib 2.62) caused by ibus initialization: https://freefilesync.org/forum/viewtopic.php?t=6704 + //=> work around 1: bonus: avoid needless DBus calls: https://developer.gnome.org/gio/stable/running-gio-apps.html + // drawback: missing MTP and network links in folder picker: https://freefilesync.org/forum/viewtopic.php?t=6871 + //if (::setenv("GIO_USE_VFS", "local", true /*overwrite*/) != 0) + // std::cerr << utfTo(formatSystemError(L"setenv(GIO_USE_VFS)", errno)) << "\n"; + // + //=> work around 2: + g_vfs_get_default(); //returns unowned GVfs* + //no such issue on GTK3! + +#elif GTK_MAJOR_VERSION == 3 + try + { + GtkCssProvider* provider = ::gtk_css_provider_new (); + ZEN_ON_SCOPE_EXIT(::g_object_unref(provider)); + + GError* error = nullptr; + ZEN_ON_SCOPE_EXIT(if (error) ::g_error_free(error);); + + ::gtk_css_provider_load_from_path(provider, //GtkCssProvider* css_provider, + (getResourceDirPf() + "Gtk3Styles.css").c_str(), //const gchar* path, + &error); //GError** error + if (error) + throw SysError(formatSystemError(L"gtk_css_provider_load_from_data", replaceCpy(_("Error Code %x"), L"%x", + numberTo(error->code)), + utfTo(error->message))); + + ::gtk_style_context_add_provider_for_screen(::gdk_screen_get_default(), //GdkScreen* screen, + GTK_STYLE_PROVIDER(provider), //GtkStyleProvider* provider, + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); //guint priority + } + catch (const SysError& e) { std::cerr << utfTo(e.toString()) << '\n'; } +#else +#error unknown GTK version! +#endif + + + //Windows User Experience Interaction Guidelines: tool tips should have 5s timeout, info tips no timeout => compromise: + wxToolTip::Enable(true); //yawn, a wxWidgets screw-up: wxToolTip::SetAutoPop is no-op if global tooltip window is not yet constructed: wxToolTip::Enable creates it + wxToolTip::SetAutoPop(10000); //https://msdn.microsoft.com/en-us/library/windows/desktop/aa511495 + + SetAppName(L"FreeFileSync"); //if not set, the default is the executable's name! + + initResourceImages(getResourceDirPf() + Zstr("Icons.zip")); //parallel xBRZ-scaling! => run as early as possible + + try + { + //tentatively set program language to OS default until GlobalSettings.xml is read later + setLanguage(XmlGlobalSettings().programLanguage); //throw FileError + } + catch (FileError&) { assert(false); } + + initAfs({ getResourceDirPf(), getConfigDirPathPf() }); //bonus: using FTP Gdrive implicitly inits OpenSSL (used in runSanityChecks() on Linux) alredy during globals init + + + Connect(wxEVT_QUERY_END_SESSION, wxEventHandler(Application::onQueryEndSession), nullptr, this); //can veto + Connect(wxEVT_END_SESSION, wxEventHandler(Application::onQueryEndSession), nullptr, this); //can *not* veto + + //Note: app start is deferred: batch mode requires the wxApp eventhandler to be established for UI update events. This is not the case at the time of OnInit()! + Connect(EVENT_ENTER_EVENT_LOOP, wxEventHandler(Application::onEnterEventLoop), nullptr, this); + AddPendingEvent(wxCommandEvent(EVENT_ENTER_EVENT_LOOP)); + return true; //true: continue processing; false: exit immediately. +} + + +int Application::OnExit() +{ + releaseWxLocale(); + cleanupResourceImages(); + teardownAfs(); + return wxApp::OnExit(); +} + + +wxLayoutDirection Application::GetLayoutDirection() const { return getLayoutDirection(); } + + +void Application::onEnterEventLoop(wxEvent& event) +{ + Disconnect(EVENT_ENTER_EVENT_LOOP, wxEventHandler(Application::onEnterEventLoop), nullptr, this); + + //determine FFS mode of operation + std::vector commandArgs = getCommandlineArgs(*this); + launch(commandArgs); +} + + + + +int Application::OnRun() +{ + try + { + wxApp::OnRun(); + } + catch (const std::bad_alloc& e) //the only kind of exception we don't want crash dumps for + { + logFatalError(e.what()); //it's not always possible to display a message box, e.g. corrupted stack, however low-level file output works! + + const auto titleFmt = copyStringTo(wxTheApp->GetAppDisplayName()) + SPACED_DASH + _("An exception occurred"); + std::cerr << utfTo(titleFmt + SPACED_DASH) << e.what() << '\n'; + return FFS_RC_EXCEPTION; + } + //catch (...) -> let it crash and create mini dump!!! + + return returnCode_; +} + + +void Application::onQueryEndSession(wxEvent& event) +{ + if (auto mainWin = dynamic_cast(GetTopWindow())) + mainWin->onQueryEndSession(); + //it's futile to try and clean up while the process is in full swing (CRASH!) => just terminate! + //also: avoid wxCloseEvent::Veto() cancelling shutdown when some dialogs receive a close event from the system + terminateProcess(FFS_RC_ABORTED); +} + + +void runGuiMode (const Zstring& globalConfigFile); +void runGuiMode (const Zstring& globalConfigFile, const XmlGuiConfig& guiCfg, const std::vector& cfgFilePaths, bool startComparison); +void runBatchMode(const Zstring& globalConfigFile, const XmlBatchConfig& batchCfg, const Zstring& cfgFilePath, FfsReturnCode& returnCode); +void showSyntaxHelp(); + + +void Application::launch(const std::vector& commandArgs) +{ + //wxWidgets app exit handling is weird... we want to exit only if the logical main window is closed, not just *any* window! + wxTheApp->SetExitOnFrameDelete(false); //prevent popup-windows from becoming temporary top windows leading to program exit after closure + ZEN_ON_SCOPE_EXIT(if (!mainWindowWasSet()) wxTheApp->ExitMainLoop();); //quit application, if no main window was set (batch silent mode) + + auto notifyFatalError = [&](const std::wstring& msg, const std::wstring& title) + { + logFatalError(utfTo(msg)); + + //error handling strategy unknown and no sync log output available at this point! + auto titleFmt = copyStringTo(wxTheApp->GetAppDisplayName()) + SPACED_DASH + title; + std::cerr << utfTo(titleFmt + SPACED_DASH + msg) << '\n'; + //alternative0: std::wcerr: cannot display non-ASCII at all, so why does it exist??? + //alternative1: wxSafeShowMessage => NO console output on Debian x86, WTF! + //alternative2: wxMessageBox() => works, but we probably shouldn't block during command line usage + raiseReturnCode(returnCode_, FFS_RC_ABORTED); + }; + + //parse command line arguments + std::vector> dirPathPhrasePairs; + std::vector> configFiles; //XmlType: batch or GUI files only + Zstring globalConfigFile; + bool openForEdit = false; + { + const char* optionEdit = "-edit"; + const char* optionDirPair = "-dirpair"; + const char* optionSendTo = "-sendto"; //remaining arguments are unspecified number of folder paths; wonky syntax; let's keep it undocumented + + auto isHelpRequest = [](const Zstring& arg) + { + auto it = std::find_if(arg.begin(), arg.end(), [](Zchar c) { return c != Zstr('/') && c != Zstr('-'); }); + if (it == arg.begin()) return false; //require at least one prefix character + + const Zstring argTmp(it, arg.end()); + return equalAsciiNoCase(argTmp, "help") || + equalAsciiNoCase(argTmp, "h") || + argTmp == Zstr("?"); + }; + + auto isCommandLineOption = [&](const Zstring& arg) + { + return equalAsciiNoCase(arg, optionEdit ) || + equalAsciiNoCase(arg, optionDirPair) || + equalAsciiNoCase(arg, optionSendTo ) || + isHelpRequest(arg); + }; + + for (auto it = commandArgs.begin(); it != commandArgs.end(); ++it) + if (isHelpRequest(*it)) + return showSyntaxHelp(); + else if (equalAsciiNoCase(*it, optionEdit)) + openForEdit = true; + else if (equalAsciiNoCase(*it, optionDirPair)) + { + if (++it == commandArgs.end() || isCommandLineOption(*it)) + return notifyFatalError(replaceCpy(_("A left and a right directory path are expected after %x."), L"%x", utfTo(optionDirPair)), _("Syntax error")); + dirPathPhrasePairs.emplace_back(*it, Zstring()); + + if (++it == commandArgs.end() || isCommandLineOption(*it)) + return notifyFatalError(replaceCpy(_("A left and a right directory path are expected after %x."), L"%x", utfTo(optionDirPair)), _("Syntax error")); + dirPathPhrasePairs.back().second = *it; + } + else if (equalAsciiNoCase(*it, optionSendTo)) + { + for (size_t i = 0; ; ++i) + { + if (++it == commandArgs.end() || isCommandLineOption(*it)) + { + --it; + break; + } + + if (i < 2) //else: -SendTo with more than 2 paths? Doesn't make any sense, does it!? + { + //for -SendTo we expect a list of full native paths, not "phrases" that need to be resolved! + auto getFolderPath = [](Zstring itemPath) + { + try + { + if (getItemType(itemPath) == ItemType::FILE) //throw FileError + if (std::optional parentPath = getParentFolderPath(itemPath)) + return *parentPath; + } + catch (FileError&) {} + + return itemPath; + }; + + if (i % 2 == 0) + dirPathPhrasePairs.emplace_back(getFolderPath(*it), Zstring()); + else + { + const Zstring folderPath = getFolderPath(*it); + if (dirPathPhrasePairs.back().first != folderPath) //else: user accidentally sending to two files, which each time yield the same parent folder + dirPathPhrasePairs.back().second = folderPath; + } + } + } + } + else + { + Zstring filePath = getResolvedFilePath(*it); + + if (!fileAvailable(filePath)) //...be a little tolerant + { + if (fileAvailable(filePath + Zstr(".ffs_batch"))) + filePath += Zstr(".ffs_batch"); + else if (fileAvailable(filePath + Zstr(".ffs_gui"))) + filePath += Zstr(".ffs_gui"); + else if (fileAvailable(filePath + Zstr(".xml"))) + filePath += Zstr(".xml"); + else + return notifyFatalError(replaceCpy(_("Cannot find file %x."), L"%x", fmtPath(filePath)), _("Error")); + } + + try + { + switch (getXmlType(filePath)) //throw FileError + { + case XmlType::gui: + configFiles.emplace_back(filePath, XmlType::gui); + break; + case XmlType::batch: + configFiles.emplace_back(filePath, XmlType::batch); + break; + case XmlType::global: + globalConfigFile = filePath; + break; + case XmlType::other: + return notifyFatalError(replaceCpy(_("File %x does not contain a valid configuration."), L"%x", fmtPath(filePath)), _("Error")); + } + } + catch (const FileError& e) + { + return notifyFatalError(e.toString(), _("Error")); + } + } + } + //---------------------------------------------------------------------------------------------------- + + auto hasNonDefaultConfig = [](const LocalPairConfig& lpc) + { + return lpc != LocalPairConfig{ lpc.folderPathPhraseLeft, + lpc.folderPathPhraseRight, + std::nullopt, std::nullopt, FilterConfig() }; + }; + + auto replaceDirectories = [&](MainConfiguration& mainCfg) + { + if (!dirPathPhrasePairs.empty()) + { + //check if config at folder-pair level is present: this probably doesn't make sense when replacing/adding the user-specified directories + if (hasNonDefaultConfig(mainCfg.firstPair) || std::any_of(mainCfg.additionalPairs.begin(), mainCfg.additionalPairs.end(), hasNonDefaultConfig)) + { + notifyFatalError(_("The config file must not contain settings at directory pair level when directories are set via command line."), _("Syntax error")); + return false; + } + + mainCfg.additionalPairs.clear(); + for (size_t i = 0; i < dirPathPhrasePairs.size(); ++i) + if (i == 0) + { + mainCfg.firstPair.folderPathPhraseLeft = dirPathPhrasePairs[0].first; + mainCfg.firstPair.folderPathPhraseRight = dirPathPhrasePairs[0].second; + } + else + mainCfg.additionalPairs.push_back({ dirPathPhrasePairs[i].first, dirPathPhrasePairs[i].second, + std::nullopt, std::nullopt, FilterConfig() }); + } + return true; + }; + + //distinguish sync scenarios: + //--------------------------- + const Zstring globalConfigFilePath = !globalConfigFile.empty() ? globalConfigFile : getGlobalConfigFile(); + + if (configFiles.empty()) + { + //gui mode: default startup + if (dirPathPhrasePairs.empty()) + runGuiMode(globalConfigFilePath); + //gui mode: default config with given directories + else + { + XmlGuiConfig guiCfg; + guiCfg.mainCfg.syncCfg.directionCfg.var = DirectionConfig::MIRROR; + + if (!replaceDirectories(guiCfg.mainCfg)) + return; + runGuiMode(globalConfigFilePath, guiCfg, std::vector(), !openForEdit /*startComparison*/); + } + } + else if (configFiles.size() == 1) + { + const Zstring filepath = configFiles[0].first; + + //batch mode + if (configFiles[0].second == XmlType::batch && !openForEdit) + { + XmlBatchConfig batchCfg; + try + { + std::wstring warningMsg; + readConfig(filepath, batchCfg, warningMsg); //throw FileError + + if (!warningMsg.empty()) + throw FileError(warningMsg); //batch mode: break on errors AND even warnings! + } + catch (const FileError& e) + { + return notifyFatalError(e.toString(), _("Error")); + } + if (!replaceDirectories(batchCfg.mainCfg)) + return; + runBatchMode(globalConfigFilePath, batchCfg, filepath, returnCode_); + } + //GUI mode: single config (ffs_gui *or* ffs_batch) + else + { + XmlGuiConfig guiCfg; + try + { + std::wstring warningMsg; + readAnyConfig({ filepath }, guiCfg, warningMsg); //throw FileError + + if (!warningMsg.empty()) + showNotificationDialog(nullptr, DialogInfoType::warning, PopupDialogCfg().setDetailInstructions(warningMsg)); + //what about simulating changed config on parsing errors? + } + catch (const FileError& e) + { + return notifyFatalError(e.toString(), _("Error")); + } + if (!replaceDirectories(guiCfg.mainCfg)) + return; + //what about simulating changed config due to directory replacement? + //-> propably fine to not show as changed on GUI and not ask user to save on exit! + + runGuiMode(globalConfigFilePath, guiCfg, { filepath }, !openForEdit); //caveat: guiCfg and filepath do not match if directories were set/replaced via command line! + } + } + //gui mode: merged configs + else + { + if (!dirPathPhrasePairs.empty()) + return notifyFatalError(_("Directories cannot be set for more than one configuration file."), _("Syntax error")); + + std::vector filePaths; + for (const auto& [filePath, xmlType] : configFiles) + filePaths.push_back(filePath); + + XmlGuiConfig guiCfg; //structure to receive gui settings with default values + try + { + std::wstring warningMsg; + readAnyConfig(filePaths, guiCfg, warningMsg); //throw FileError + + if (!warningMsg.empty()) + showNotificationDialog(nullptr, DialogInfoType::warning, PopupDialogCfg().setDetailInstructions(warningMsg)); + //what about simulating changed config on parsing errors? + } + catch (const FileError& e) + { + return notifyFatalError(e.toString(), _("Error")); + } + runGuiMode(globalConfigFilePath, guiCfg, filePaths, !openForEdit /*startComparison*/); + } +} + + +void runGuiMode(const Zstring& globalConfigFilePath) { MainDialog::create(globalConfigFilePath); } + + +void runGuiMode(const Zstring& globalConfigFilePath, + const XmlGuiConfig& guiCfg, + const std::vector& cfgFilePaths, + bool startComparison) +{ + MainDialog::create(globalConfigFilePath, nullptr, guiCfg, cfgFilePaths, startComparison); +} + + +void showSyntaxHelp() +{ + showNotificationDialog(nullptr, DialogInfoType::info, PopupDialogCfg(). + setTitle(_("Command line")). + setDetailInstructions(_("Syntax:") + L"\n\n" + + L"./FreeFileSync" + L'\n' + + L" [" + _("config files:") + L" *.ffs_gui/*.ffs_batch]" + L'\n' + + L" [-DirPair " + _("directory") + L' ' + _("directory") + L"]" L"\n" + + L" [-Edit]" + L'\n' + + L" [" + _("global config file:") + L" GlobalSettings.xml]" + L"\n" + L"\n" + + + _("config files:") + L'\n' + + _("Any number of FreeFileSync \"ffs_gui\" and/or \"ffs_batch\" configuration files.") + L"\n\n" + + + L"-DirPair " + _("directory") + L' ' + _("directory") + L'\n' + + _("Any number of alternative directory pairs for at most one config file.") + L"\n\n" + + + L"-Edit" + "\n" + + _("Open the selected configuration for editing only, without executing it.") + L"\n\n" + + + _("global config file:") + L'\n' + + _("Path to an alternate GlobalSettings.xml file."))); +} + + +void runBatchMode(const Zstring& globalConfigFilePath, const XmlBatchConfig& batchCfg, const Zstring& cfgFilePath, FfsReturnCode& returnCode) +{ + const bool showPopupAllowed = !batchCfg.mainCfg.ignoreErrors && batchCfg.batchExCfg.batchErrorHandling == BatchErrorHandling::showPopup; + + auto notifyError = [&](const std::wstring& msg, FfsReturnCode rc) + { + if (showPopupAllowed) + showNotificationDialog(nullptr, DialogInfoType::error, PopupDialogCfg().setDetailInstructions(msg)); + else //"exit" or "ignore" + logFatalError(utfTo(msg)); + + raiseReturnCode(returnCode, rc); + }; + + XmlGlobalSettings globalCfg; + try + { + std::wstring warningMsg; + readConfig(globalConfigFilePath, globalCfg, warningMsg); //throw FileError + assert(warningMsg.empty()); //ignore parsing errors: should be migration problems only *cross-fingers* + } + catch (FileError&) + { + try + { + if (itemStillExists(globalConfigFilePath)) //throw FileError + throw; + } + catch (const FileError& e) + { + return notifyError(e.toString(), FFS_RC_ABORTED); //abort sync! + } + } + + try + { + setLanguage(globalCfg.programLanguage); //throw FileError + } + catch (const FileError& e) + { + notifyError(e.toString(), FFS_RC_WARNING); + //continue! + } + + //all settings have been read successfully... + + //regular check for program updates -> disabled for batch + //if (batchCfg.showProgress && manualProgramUpdateRequired()) + // checkForUpdatePeriodically(globalCfg.lastUpdateCheck); + //WinInet not working when FFS is running as a service!!! https://support.microsoft.com/en-us/kb/238425 + + + std::set logFilePathsToKeep; + for (const ConfigFileItem& item : globalCfg.gui.mainDlg.cfgFileHistory) + logFilePathsToKeep.insert(item.logFilePath); + + const std::chrono::system_clock::time_point syncStartTime = std::chrono::system_clock::now(); + + //class handling status updates and error messages + BatchStatusHandler statusHandler(!batchCfg.batchExCfg.runMinimized, + batchCfg.batchExCfg.autoCloseSummary, + extractJobName(cfgFilePath), + globalCfg.soundFileSyncFinished, + syncStartTime, + batchCfg.mainCfg.ignoreErrors, + batchCfg.batchExCfg.batchErrorHandling, + batchCfg.mainCfg.automaticRetryCount, + batchCfg.mainCfg.automaticRetryDelay, + batchCfg.batchExCfg.postSyncAction); + try + { + //inform about (important) non-default global settings + logNonDefaultSettings(globalCfg, statusHandler); //throw AbortProcess + + //batch mode: place directory locks on directories during both comparison AND synchronization + std::unique_ptr dirLocks; + + //COMPARE DIRECTORIES + FolderComparison cmpResult = compare(globalCfg.warnDlgs, + globalCfg.fileTimeTolerance, + showPopupAllowed, //allowUserInteraction + globalCfg.runWithBackgroundPriority, + globalCfg.createLockFile, + dirLocks, + extractCompareCfg(batchCfg.mainCfg), + statusHandler); //throw AbortProcess + //START SYNCHRONIZATION + if (!cmpResult.empty()) + synchronize(syncStartTime, + globalCfg.verifyFileCopy, + globalCfg.copyLockedFiles, + globalCfg.copyFilePermissions, + globalCfg.failSafeFileCopy, + globalCfg.runWithBackgroundPriority, + extractSyncCfg(batchCfg.mainCfg), + cmpResult, + globalCfg.warnDlgs, + statusHandler); //throw AbortProcess + } + catch (AbortProcess&) {} //exit used by statusHandler + + BatchStatusHandler::Result r = statusHandler.reportResults(batchCfg.mainCfg.postSyncCommand, batchCfg.mainCfg.postSyncCondition, + batchCfg.mainCfg.altLogFolderPathPhrase, globalCfg.logfilesMaxAgeDays, globalCfg.logFormat, logFilePathsToKeep, + batchCfg.mainCfg.emailNotifyAddress, batchCfg.mainCfg.emailNotifyCondition); //noexcept + //---------------------------------------------------------------------- + + raiseReturnCode(returnCode, mapToReturnCode(r.resultStatus)); + + //update last sync stats for the selected cfg file + for (ConfigFileItem& cfi : globalCfg.gui.mainDlg.cfgFileHistory) + if (equalNativePath(cfi.cfgFilePath, cfgFilePath)) + { + if (r.resultStatus != SyncResult::aborted) + cfi.lastSyncTime = std::chrono::system_clock::to_time_t(syncStartTime); + assert(!AFS::isNullPath(r.logFilePath)); + if (!AFS::isNullPath(r.logFilePath)) + { + cfi.logFilePath = r.logFilePath; + cfi.logResult = r.resultStatus; + } + break; + } + + //--------------------------------------------------------------------------- + try //save global settings to XML: e.g. ignored warnings, last sync stats + { + writeConfig(globalCfg, globalConfigFilePath); //FileError + } + catch (const FileError& e) + { + notifyError(e.toString(), FFS_RC_WARNING); + } + + using FinalRequest = BatchStatusHandler::FinalRequest; + switch (r.finalRequest) + { + case FinalRequest::none: + break; + case FinalRequest::switchGui: //open new top-level window *after* progress dialog is gone => run on main event loop + MainDialog::create(globalConfigFilePath, &globalCfg, convertBatchToGui(batchCfg), { cfgFilePath }, true /*startComparison*/); + break; + case FinalRequest::shutdown: //run *after* last sync stats were updated and saved! https://freefilesync.org/forum/viewtopic.php?t=5761 + try + { + shutdownSystem(); //throw FileError + terminateProcess(0 /*exitCode*/); //no point in continuing and saving cfg again in onQueryEndSession() while the OS will kill us anytime! + } + catch (const FileError& e) { notifyError(e.toString(), FFS_RC_WARNING); } + break; + } +} diff --git a/FreeFileSync/Source/application.h b/FreeFileSync/Source/application.h new file mode 100644 index 00000000..a52e6617 --- /dev/null +++ b/FreeFileSync/Source/application.h @@ -0,0 +1,35 @@ +// ***************************************************************************** +// * 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 APPLICATION_H_081568741942010985702395 +#define APPLICATION_H_081568741942010985702395 + +#include +#include +#include +#include "return_codes.h" + + +namespace fff //avoid name clash with "int ffs()" for fuck's sake! (maxOS, Linux issue only: internally includes , WTF!) +{ +class Application : public wxApp +{ +private: + bool OnInit() 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 + wxLayoutDirection GetLayoutDirection() const override; + void onEnterEventLoop(wxEvent& event); + void onQueryEndSession(wxEvent& event); + void launch(const std::vector& commandArgs); + + FfsReturnCode returnCode_ = FFS_RC_SUCCESS; +}; +} + +#endif //APPLICATION_H_081568741942010985702395 diff --git a/FreeFileSync/Source/base/algorithm.cpp b/FreeFileSync/Source/base/algorithm.cpp index ddb449d6..2b66e9a5 100644 --- a/FreeFileSync/Source/base/algorithm.cpp +++ b/FreeFileSync/Source/base/algorithm.cpp @@ -712,9 +712,9 @@ private: recurse(folder, dbEntryL, dbEntryR); } - const std::wstring txtBothSidesChanged_ = _("Both sides have changed since last synchronization."); - const std::wstring txtNoSideChanged_ = _("Cannot determine sync-direction:") + L" \n" + _("No change since last synchronization."); - const std::wstring txtDbNotInSync_ = _("Cannot determine sync-direction:") + L" \n" + _("The database entry is not in sync considering current settings."); + const Zstringc txtBothSidesChanged_ = utfTo(_("Both sides have changed since last synchronization.")); + const Zstringc txtNoSideChanged_ = utfTo(_("Cannot determine sync-direction:") + L" \n" + _("No change since last synchronization.")); + const Zstringc txtDbNotInSync_ = utfTo(_("Cannot determine sync-direction:") + L" \n" + _("The database entry is not in sync considering current settings.")); const CompareVariant cmpVar_; const int fileTimeTolerance_; @@ -736,7 +736,7 @@ std::vector> fff::extractDirectionCf mainCfg.additionalPairs.end()); if (folderCmp.size() != allPairs.size()) - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); std::vector> output; @@ -779,7 +779,7 @@ void fff::redetermineSyncDirection(const std::vector 1) - msg += "\n" + AFS::getDisplayPath(baseFolder->getAbstractPath< LEFT_SIDE>()) + L" " + getVariantNameForLog(dirCfg.var) + L" " + + msg += L'\n' + AFS::getDisplayPath(baseFolder->getAbstractPath< LEFT_SIDE>()) + L' ' + getVariantNameForLog(dirCfg.var) + L' ' + AFS::getDisplayPath(baseFolder->getAbstractPath()); try { callback.reportInfo(msg); /*throw X*/} catch (...) {}; @@ -1089,7 +1089,7 @@ void fff::applyFiltering(FolderComparison& folderCmp, const MainConfiguration& m if (folderCmp.empty()) return; else if (folderCmp.size() != mainCfg.additionalPairs.size() + 1) - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); //merge first and additional pairs std::vector allPairs; @@ -1610,11 +1610,11 @@ void fff::deleteFromGridAndHD(const std::vector& rowsToDelete if (useRecycleBin && std::any_of(recyclerSupported.begin(), recyclerSupported.end(), [](const auto& item) { return !item.second; })) { - std::wstring msg = _("The recycle bin is not supported by the following folders. Deleted or overwritten files will not be able to be restored:") + L"\n"; + std::wstring msg = _("The recycle bin is not supported by the following folders. Deleted or overwritten files will not be able to be restored:") + L'\n'; for (const auto& [folderPath, supported] : recyclerSupported) if (!supported) - msg += L"\n" + AFS::getDisplayPath(folderPath); + msg += L'\n' + AFS::getDisplayPath(folderPath); callback.reportWarning(msg, warnRecyclerMissing); //throw? } diff --git a/FreeFileSync/Source/base/algorithm.h b/FreeFileSync/Source/base/algorithm.h index 299a97a0..48d8b7c1 100644 --- a/FreeFileSync/Source/base/algorithm.h +++ b/FreeFileSync/Source/base/algorithm.h @@ -8,7 +8,8 @@ #define ALGORITHM_H_34218518475321452548 #include -#include "config.h" +//#include "config.h" +#include "structures.h" #include "file_hierarchy.h" #include "soft_filter.h" #include "process_callback.h" diff --git a/FreeFileSync/Source/base/application.cpp b/FreeFileSync/Source/base/application.cpp deleted file mode 100644 index 657eac13..00000000 --- a/FreeFileSync/Source/base/application.cpp +++ /dev/null @@ -1,647 +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 "application.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "comparison.h" -#include "config.h" -#include "algorithm.h" -#include "synchronization.h" -#include "help_provider.h" -#include "fatal_error.h" -#include "log_file.h" -#include "resolve_path.h" -#include "../ui/batch_status_handler.h" -#include "../ui/main_dlg.h" -#include "../afs/concrete.h" - - #include - -using namespace zen; -using namespace fff; - - -IMPLEMENT_APP(Application) - - -namespace -{ - - -std::vector getCommandlineArgs(const wxApp& app) -{ - std::vector args; - for (int i = 1; i < app.argc; ++i) //wxWidgets screws up once again making "argv implicitly convertible to a wxChar**" in 2.9.3, - args.push_back(utfTo(wxString(app.argv[i]))); //so we are forced to use this pitiful excuse for a range construction!! - return args; -} - -const wxEventType EVENT_ENTER_EVENT_LOOP = wxNewEventType(); -} - -//################################################################################################################## - -bool Application::OnInit() -{ - //do not call wxApp::OnInit() to avoid using wxWidgets command line parser - - //GTK should already have been initialized by wxWidgets (see \src\gtk\app.cpp:wxApp::Initialize) -#if GTK_MAJOR_VERSION == 2 - ::gtk_rc_parse((getResourceDirPf() + "Gtk2Styles.rc").c_str()); - - //hang on Ubuntu 19.10 (GLib 2.62) caused by ibus initialization: https://freefilesync.org/forum/viewtopic.php?t=6704 - //=> work around 1: bonus: avoid needless DBus calls: https://developer.gnome.org/gio/stable/running-gio-apps.html - // drawback: missing MTP and network links in folder picker: https://freefilesync.org/forum/viewtopic.php?t=6871 - //if (::setenv("GIO_USE_VFS", "local", true /*overwrite*/) != 0) - // std::cerr << utfTo(formatSystemError(L"setenv(GIO_USE_VFS)", errno)) << "\n"; - // - //=> work around 2: - g_vfs_get_default(); //returns unowned GVfs* - //no such issue on GTK3! - -#elif GTK_MAJOR_VERSION == 3 - try - { - GtkCssProvider* provider = ::gtk_css_provider_new (); - ZEN_ON_SCOPE_EXIT(::g_object_unref(provider)); - - GError* error = nullptr; - ZEN_ON_SCOPE_EXIT(if (error) ::g_error_free(error);); - - ::gtk_css_provider_load_from_path(provider, //GtkCssProvider* css_provider, - (getResourceDirPf() + "Gtk3Styles.css").c_str(), //const gchar* path, - &error); //GError** error - if (error) - throw SysError(formatSystemError(L"gtk_css_provider_load_from_data", - replaceCpy(_("Error Code %x"), L"%x", numberTo(error->code)), utfTo(error->message))); - - ::gtk_style_context_add_provider_for_screen(::gdk_screen_get_default(), //GdkScreen* screen, - GTK_STYLE_PROVIDER(provider), //GtkStyleProvider* provider, - GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); //guint priority - } - catch (const SysError& e) { std::cerr << utfTo(e.toString()) << "\n"; } -#else -#error unknown GTK version! -#endif - - - //Windows User Experience Interaction Guidelines: tool tips should have 5s timeout, info tips no timeout => compromise: - wxToolTip::Enable(true); //yawn, a wxWidgets screw-up: wxToolTip::SetAutoPop is no-op if global tooltip window is not yet constructed: wxToolTip::Enable creates it - wxToolTip::SetAutoPop(10000); //https://msdn.microsoft.com/en-us/library/windows/desktop/aa511495 - - SetAppName(L"FreeFileSync"); //if not set, the default is the executable's name! - - initResourceImages(getResourceDirPf() + Zstr("Icons.zip")); //parallel xBRZ-scaling! => run as early as possible - - try - { - //tentatively set program language to OS default until GlobalSettings.xml is read later - setLanguage(XmlGlobalSettings().programLanguage); //throw FileError - } - catch (FileError&) { assert(false); } - - initAfs({ getResourceDirPf(), getConfigDirPathPf() }); //bonus: using FTP Gdrive implicitly inits OpenSSL (used in runSanityChecks() on Linux) alredy during globals init - - - Connect(wxEVT_QUERY_END_SESSION, wxEventHandler(Application::onQueryEndSession), nullptr, this); //can veto - Connect(wxEVT_END_SESSION, wxEventHandler(Application::onQueryEndSession), nullptr, this); //can *not* veto - - //Note: app start is deferred: batch mode requires the wxApp eventhandler to be established for UI update events. This is not the case at the time of OnInit()! - Connect(EVENT_ENTER_EVENT_LOOP, wxEventHandler(Application::onEnterEventLoop), nullptr, this); - AddPendingEvent(wxCommandEvent(EVENT_ENTER_EVENT_LOOP)); - return true; //true: continue processing; false: exit immediately. -} - - -int Application::OnExit() -{ - releaseWxLocale(); - cleanupResourceImages(); - teardownAfs(); - return wxApp::OnExit(); -} - - -wxLayoutDirection Application::GetLayoutDirection() const { return getLayoutDirection(); } - - -void Application::onEnterEventLoop(wxEvent& event) -{ - Disconnect(EVENT_ENTER_EVENT_LOOP, wxEventHandler(Application::onEnterEventLoop), nullptr, this); - - //determine FFS mode of operation - std::vector commandArgs = getCommandlineArgs(*this); - launch(commandArgs); -} - - - - -int Application::OnRun() -{ - try - { - wxApp::OnRun(); - } - catch (const std::bad_alloc& e) //the only kind of exception we don't want crash dumps for - { - logFatalError(e.what()); //it's not always possible to display a message box, e.g. corrupted stack, however low-level file output works! - - const auto titleFmt = copyStringTo(wxTheApp->GetAppDisplayName()) + SPACED_DASH + _("An exception occurred"); - std::cerr << utfTo(titleFmt + SPACED_DASH) << e.what() << "\n"; - return FFS_RC_EXCEPTION; - } - //catch (...) -> let it crash and create mini dump!!! - - return returnCode_; -} - - -void Application::onQueryEndSession(wxEvent& event) -{ - if (auto mainWin = dynamic_cast(GetTopWindow())) - mainWin->onQueryEndSession(); - //it's futile to try and clean up while the process is in full swing (CRASH!) => just terminate! - //also: avoid wxCloseEvent::Veto() cancelling shutdown when some dialogs receive a close event from the system - terminateProcess(FFS_RC_ABORTED); -} - - -void runGuiMode (const Zstring& globalConfigFile); -void runGuiMode (const Zstring& globalConfigFile, const XmlGuiConfig& guiCfg, const std::vector& cfgFilePaths, bool startComparison); -void runBatchMode(const Zstring& globalConfigFile, const XmlBatchConfig& batchCfg, const Zstring& cfgFilePath, FfsReturnCode& returnCode); -void showSyntaxHelp(); - - -void Application::launch(const std::vector& commandArgs) -{ - //wxWidgets app exit handling is weird... we want to exit only if the logical main window is closed, not just *any* window! - wxTheApp->SetExitOnFrameDelete(false); //prevent popup-windows from becoming temporary top windows leading to program exit after closure - ZEN_ON_SCOPE_EXIT(if (!mainWindowWasSet()) wxTheApp->ExitMainLoop();); //quit application, if no main window was set (batch silent mode) - - auto notifyFatalError = [&](const std::wstring& msg, const std::wstring& title) - { - logFatalError(utfTo(msg)); - - //error handling strategy unknown and no sync log output available at this point! - auto titleFmt = copyStringTo(wxTheApp->GetAppDisplayName()) + SPACED_DASH + title; - std::cerr << utfTo(titleFmt + SPACED_DASH + msg) << "\n"; - //alternative0: std::wcerr: cannot display non-ASCII at all, so why does it exist??? - //alternative1: wxSafeShowMessage => NO console output on Debian x86, WTF! - //alternative2: wxMessageBox() => works, but we probably shouldn't block during command line usage - raiseReturnCode(returnCode_, FFS_RC_ABORTED); - }; - - //parse command line arguments - std::vector> dirPathPhrasePairs; - std::vector> configFiles; //XmlType: batch or GUI files only - Zstring globalConfigFile; - bool openForEdit = false; - { - const Zchar* optionEdit = Zstr("-edit"); - const Zchar* optionDirPair = Zstr("-dirpair"); - const Zchar* optionSendTo = Zstr("-sendto"); //remaining arguments are unspecified number of folder paths; wonky syntax; let's keep it undocumented - - auto isHelpRequest = [](const Zstring& arg) - { - auto it = std::find_if(arg.begin(), arg.end(), [](Zchar c) { return c != Zstr('/') && c != Zstr('-'); }); - if (it == arg.begin()) return false; //require at least one prefix character - - const Zstring argTmp(it, arg.end()); - return equalAsciiNoCase(argTmp, Zstr("help")) || - equalAsciiNoCase(argTmp, Zstr("h")) || - argTmp == Zstr("?"); - }; - - auto isCommandLineOption = [&](const Zstring& arg) - { - return equalAsciiNoCase(arg, optionEdit ) || - equalAsciiNoCase(arg, optionDirPair) || - equalAsciiNoCase(arg, optionSendTo ) || - isHelpRequest(arg); - }; - - for (auto it = commandArgs.begin(); it != commandArgs.end(); ++it) - if (isHelpRequest(*it)) - return showSyntaxHelp(); - else if (equalAsciiNoCase(*it, optionEdit)) - openForEdit = true; - else if (equalAsciiNoCase(*it, optionDirPair)) - { - if (++it == commandArgs.end() || isCommandLineOption(*it)) - return notifyFatalError(replaceCpy(_("A left and a right directory path are expected after %x."), L"%x", utfTo(optionDirPair)), _("Syntax error")); - dirPathPhrasePairs.emplace_back(*it, Zstring()); - - if (++it == commandArgs.end() || isCommandLineOption(*it)) - return notifyFatalError(replaceCpy(_("A left and a right directory path are expected after %x."), L"%x", utfTo(optionDirPair)), _("Syntax error")); - dirPathPhrasePairs.back().second = *it; - } - else if (equalAsciiNoCase(*it, optionSendTo)) - { - for (size_t i = 0; ; ++i) - { - if (++it == commandArgs.end() || isCommandLineOption(*it)) - { - --it; - break; - } - - if (i < 2) //else: -SendTo with more than 2 paths? Doesn't make any sense, does it!? - { - //for -SendTo we expect a list of full native paths, not "phrases" that need to be resolved! - auto getFolderPath = [](Zstring itemPath) - { - try - { - if (getItemType(itemPath) == ItemType::FILE) //throw FileError - if (std::optional parentPath = getParentFolderPath(itemPath)) - return *parentPath; - } - catch (FileError&) {} - - return itemPath; - }; - - if (i % 2 == 0) - dirPathPhrasePairs.emplace_back(getFolderPath(*it), Zstring()); - else - { - const Zstring folderPath = getFolderPath(*it); - if (dirPathPhrasePairs.back().first != folderPath) //else: user accidentally sending to two files, which each time yield the same parent folder - dirPathPhrasePairs.back().second = folderPath; - } - } - } - } - else - { - Zstring filePath = getResolvedFilePath(*it); - - if (!fileAvailable(filePath)) //...be a little tolerant - { - if (fileAvailable(filePath + Zstr(".ffs_batch"))) - filePath += Zstr(".ffs_batch"); - else if (fileAvailable(filePath + Zstr(".ffs_gui"))) - filePath += Zstr(".ffs_gui"); - else if (fileAvailable(filePath + Zstr(".xml"))) - filePath += Zstr(".xml"); - else - return notifyFatalError(replaceCpy(_("Cannot find file %x."), L"%x", fmtPath(filePath)), _("Error")); - } - - try - { - switch (getXmlType(filePath)) //throw FileError - { - case XmlType::gui: - configFiles.emplace_back(filePath, XmlType::gui); - break; - case XmlType::batch: - configFiles.emplace_back(filePath, XmlType::batch); - break; - case XmlType::global: - globalConfigFile = filePath; - break; - case XmlType::other: - return notifyFatalError(replaceCpy(_("File %x does not contain a valid configuration."), L"%x", fmtPath(filePath)), _("Error")); - } - } - catch (const FileError& e) - { - return notifyFatalError(e.toString(), _("Error")); - } - } - } - //---------------------------------------------------------------------------------------------------- - - auto hasNonDefaultConfig = [](const LocalPairConfig& lpc) - { - return lpc != LocalPairConfig{ lpc.folderPathPhraseLeft, - lpc.folderPathPhraseRight, - std::nullopt, std::nullopt, FilterConfig() }; - }; - - auto replaceDirectories = [&](MainConfiguration& mainCfg) - { - if (!dirPathPhrasePairs.empty()) - { - //check if config at folder-pair level is present: this probably doesn't make sense when replacing/adding the user-specified directories - if (hasNonDefaultConfig(mainCfg.firstPair) || std::any_of(mainCfg.additionalPairs.begin(), mainCfg.additionalPairs.end(), hasNonDefaultConfig)) - { - notifyFatalError(_("The config file must not contain settings at directory pair level when directories are set via command line."), _("Syntax error")); - return false; - } - - mainCfg.additionalPairs.clear(); - for (size_t i = 0; i < dirPathPhrasePairs.size(); ++i) - if (i == 0) - { - mainCfg.firstPair.folderPathPhraseLeft = dirPathPhrasePairs[0].first; - mainCfg.firstPair.folderPathPhraseRight = dirPathPhrasePairs[0].second; - } - else - mainCfg.additionalPairs.push_back({ dirPathPhrasePairs[i].first, dirPathPhrasePairs[i].second, - std::nullopt, std::nullopt, FilterConfig() }); - } - return true; - }; - - //distinguish sync scenarios: - //--------------------------- - const Zstring globalConfigFilePath = !globalConfigFile.empty() ? globalConfigFile : getGlobalConfigFile(); - - if (configFiles.empty()) - { - //gui mode: default startup - if (dirPathPhrasePairs.empty()) - runGuiMode(globalConfigFilePath); - //gui mode: default config with given directories - else - { - XmlGuiConfig guiCfg; - guiCfg.mainCfg.syncCfg.directionCfg.var = DirectionConfig::MIRROR; - - if (!replaceDirectories(guiCfg.mainCfg)) - return; - runGuiMode(globalConfigFilePath, guiCfg, std::vector(), !openForEdit /*startComparison*/); - } - } - else if (configFiles.size() == 1) - { - const Zstring filepath = configFiles[0].first; - - //batch mode - if (configFiles[0].second == XmlType::batch && !openForEdit) - { - XmlBatchConfig batchCfg; - try - { - std::wstring warningMsg; - readConfig(filepath, batchCfg, warningMsg); //throw FileError - - if (!warningMsg.empty()) - throw FileError(warningMsg); //batch mode: break on errors AND even warnings! - } - catch (const FileError& e) - { - return notifyFatalError(e.toString(), _("Error")); - } - if (!replaceDirectories(batchCfg.mainCfg)) - return; - runBatchMode(globalConfigFilePath, batchCfg, filepath, returnCode_); - } - //GUI mode: single config (ffs_gui *or* ffs_batch) - else - { - XmlGuiConfig guiCfg; - try - { - std::wstring warningMsg; - readAnyConfig({ filepath }, guiCfg, warningMsg); //throw FileError - - if (!warningMsg.empty()) - showNotificationDialog(nullptr, DialogInfoType::warning, PopupDialogCfg().setDetailInstructions(warningMsg)); - //what about simulating changed config on parsing errors? - } - catch (const FileError& e) - { - return notifyFatalError(e.toString(), _("Error")); - } - if (!replaceDirectories(guiCfg.mainCfg)) - return; - //what about simulating changed config due to directory replacement? - //-> propably fine to not show as changed on GUI and not ask user to save on exit! - - runGuiMode(globalConfigFilePath, guiCfg, { filepath }, !openForEdit); //caveat: guiCfg and filepath do not match if directories were set/replaced via command line! - } - } - //gui mode: merged configs - else - { - if (!dirPathPhrasePairs.empty()) - return notifyFatalError(_("Directories cannot be set for more than one configuration file."), _("Syntax error")); - - std::vector filePaths; - for (const auto& [filePath, xmlType] : configFiles) - filePaths.push_back(filePath); - - XmlGuiConfig guiCfg; //structure to receive gui settings with default values - try - { - std::wstring warningMsg; - readAnyConfig(filePaths, guiCfg, warningMsg); //throw FileError - - if (!warningMsg.empty()) - showNotificationDialog(nullptr, DialogInfoType::warning, PopupDialogCfg().setDetailInstructions(warningMsg)); - //what about simulating changed config on parsing errors? - } - catch (const FileError& e) - { - return notifyFatalError(e.toString(), _("Error")); - } - runGuiMode(globalConfigFilePath, guiCfg, filePaths, !openForEdit /*startComparison*/); - } -} - - -void runGuiMode(const Zstring& globalConfigFilePath) { MainDialog::create(globalConfigFilePath); } - - -void runGuiMode(const Zstring& globalConfigFilePath, - const XmlGuiConfig& guiCfg, - const std::vector& cfgFilePaths, - bool startComparison) -{ - MainDialog::create(globalConfigFilePath, nullptr, guiCfg, cfgFilePaths, startComparison); -} - - -void showSyntaxHelp() -{ - showNotificationDialog(nullptr, DialogInfoType::info, PopupDialogCfg(). - setTitle(_("Command line")). - setDetailInstructions(_("Syntax:") + L"\n\n" + - L"./FreeFileSync" + L"\n" + - L" [" + _("config files:") + L" *.ffs_gui/*.ffs_batch]" + L"\n" + - L" [-DirPair " + _("directory") + L" " + _("directory") + L"]" + L"\n" + - L" [-Edit]" + L"\n" + - L" [" + _("global config file:") + L" GlobalSettings.xml]" + L"\n" + - L"\n" + - - _("config files:") + L"\n" + - _("Any number of FreeFileSync \"ffs_gui\" and/or \"ffs_batch\" configuration files.") + L"\n\n" + - - L"-DirPair " + _("directory") + L" " + _("directory") + L"\n" + - _("Any number of alternative directory pairs for at most one config file.") + L"\n\n" + - - L"-Edit" + L"\n" + - _("Open the selected configuration for editing only, without executing it.") + L"\n\n" + - - _("global config file:") + L"\n" + - _("Path to an alternate GlobalSettings.xml file."))); -} - - -void runBatchMode(const Zstring& globalConfigFilePath, const XmlBatchConfig& batchCfg, const Zstring& cfgFilePath, FfsReturnCode& returnCode) -{ - const bool showPopupAllowed = !batchCfg.mainCfg.ignoreErrors && batchCfg.batchExCfg.batchErrorHandling == BatchErrorHandling::showPopup; - - auto notifyError = [&](const std::wstring& msg, FfsReturnCode rc) - { - if (showPopupAllowed) - showNotificationDialog(nullptr, DialogInfoType::error, PopupDialogCfg().setDetailInstructions(msg)); - else //"exit" or "ignore" - logFatalError(utfTo(msg)); - - raiseReturnCode(returnCode, rc); - }; - - XmlGlobalSettings globalCfg; - try - { - std::wstring warningMsg; - readConfig(globalConfigFilePath, globalCfg, warningMsg); //throw FileError - assert(warningMsg.empty()); //ignore parsing errors: should be migration problems only *cross-fingers* - } - catch (FileError&) - { - try - { - if (itemStillExists(globalConfigFilePath)) //throw FileError - throw; - } - catch (const FileError& e) - { - return notifyError(e.toString(), FFS_RC_ABORTED); //abort sync! - } - } - - try - { - setLanguage(globalCfg.programLanguage); //throw FileError - } - catch (const FileError& e) - { - notifyError(e.toString(), FFS_RC_WARNING); - //continue! - } - - //all settings have been read successfully... - - //regular check for program updates -> disabled for batch - //if (batchCfg.showProgress && manualProgramUpdateRequired()) - // checkForUpdatePeriodically(globalCfg.lastUpdateCheck); - //WinInet not working when FFS is running as a service!!! https://support.microsoft.com/en-us/kb/238425 - - - std::set logFilePathsToKeep; - for (const ConfigFileItem& item : globalCfg.gui.mainDlg.cfgFileHistory) - logFilePathsToKeep.insert(item.logFilePath); - - const std::chrono::system_clock::time_point syncStartTime = std::chrono::system_clock::now(); - - //class handling status updates and error messages - BatchStatusHandler statusHandler(!batchCfg.batchExCfg.runMinimized, - batchCfg.batchExCfg.autoCloseSummary, - extractJobName(cfgFilePath), - globalCfg.soundFileSyncFinished, - syncStartTime, - batchCfg.mainCfg.ignoreErrors, - batchCfg.batchExCfg.batchErrorHandling, - batchCfg.mainCfg.automaticRetryCount, - batchCfg.mainCfg.automaticRetryDelay, - batchCfg.batchExCfg.postSyncAction); - try - { - //inform about (important) non-default global settings - logNonDefaultSettings(globalCfg, statusHandler); //throw AbortProcess - - //batch mode: place directory locks on directories during both comparison AND synchronization - std::unique_ptr dirLocks; - - //COMPARE DIRECTORIES - FolderComparison cmpResult = compare(globalCfg.warnDlgs, - globalCfg.fileTimeTolerance, - showPopupAllowed, //allowUserInteraction - globalCfg.runWithBackgroundPriority, - globalCfg.createLockFile, - dirLocks, - extractCompareCfg(batchCfg.mainCfg), - statusHandler); //throw AbortProcess - //START SYNCHRONIZATION - if (!cmpResult.empty()) - synchronize(syncStartTime, - globalCfg.verifyFileCopy, - globalCfg.copyLockedFiles, - globalCfg.copyFilePermissions, - globalCfg.failSafeFileCopy, - globalCfg.runWithBackgroundPriority, - extractSyncCfg(batchCfg.mainCfg), - cmpResult, - globalCfg.warnDlgs, - statusHandler); //throw AbortProcess - } - catch (AbortProcess&) {} //exit used by statusHandler - - BatchStatusHandler::Result r = statusHandler.reportResults(batchCfg.mainCfg.postSyncCommand, batchCfg.mainCfg.postSyncCondition, - batchCfg.mainCfg.altLogFolderPathPhrase, globalCfg.logfilesMaxAgeDays, logFilePathsToKeep, - batchCfg.mainCfg.emailNotifyAddress, batchCfg.mainCfg.emailNotifyCondition); //noexcept - //---------------------------------------------------------------------- - - raiseReturnCode(returnCode, mapToReturnCode(r.resultStatus)); - - //update last sync stats for the selected cfg file - for (ConfigFileItem& cfi : globalCfg.gui.mainDlg.cfgFileHistory) - if (equalNativePath(cfi.cfgFilePath, cfgFilePath)) - { - if (r.resultStatus != SyncResult::aborted) - cfi.lastSyncTime = std::chrono::system_clock::to_time_t(syncStartTime); - assert(!AFS::isNullPath(r.logFilePath)); - if (!AFS::isNullPath(r.logFilePath)) - { - cfi.logFilePath = r.logFilePath; - cfi.logResult = r.resultStatus; - } - break; - } - - //--------------------------------------------------------------------------- - try //save global settings to XML: e.g. ignored warnings, last sync stats - { - writeConfig(globalCfg, globalConfigFilePath); //FileError - } - catch (const FileError& e) - { - notifyError(e.toString(), FFS_RC_WARNING); - } - - using FinalRequest = BatchStatusHandler::FinalRequest; - switch (r.finalRequest) - { - case FinalRequest::none: - break; - case FinalRequest::switchGui: //open new top-level window *after* progress dialog is gone => run on main event loop - MainDialog::create(globalConfigFilePath, &globalCfg, convertBatchToGui(batchCfg), { cfgFilePath }, true /*startComparison*/); - break; - case FinalRequest::shutdown: //run *after* last sync stats were updated and saved! https://freefilesync.org/forum/viewtopic.php?t=5761 - try - { - shutdownSystem(); //throw FileError - terminateProcess(0 /*exitCode*/); //no point in continuing and saving cfg again in onQueryEndSession() while the OS will kill us anytime! - } - catch (const FileError& e) { notifyError(e.toString(), FFS_RC_WARNING); } - break; - } -} diff --git a/FreeFileSync/Source/base/application.h b/FreeFileSync/Source/base/application.h deleted file mode 100644 index a52e6617..00000000 --- a/FreeFileSync/Source/base/application.h +++ /dev/null @@ -1,35 +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 APPLICATION_H_081568741942010985702395 -#define APPLICATION_H_081568741942010985702395 - -#include -#include -#include -#include "return_codes.h" - - -namespace fff //avoid name clash with "int ffs()" for fuck's sake! (maxOS, Linux issue only: internally includes , WTF!) -{ -class Application : public wxApp -{ -private: - bool OnInit() 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 - wxLayoutDirection GetLayoutDirection() const override; - void onEnterEventLoop(wxEvent& event); - void onQueryEndSession(wxEvent& event); - void launch(const std::vector& commandArgs); - - FfsReturnCode returnCode_ = FFS_RC_SUCCESS; -}; -} - -#endif //APPLICATION_H_081568741942010985702395 diff --git a/FreeFileSync/Source/base/binary.cpp b/FreeFileSync/Source/base/binary.cpp index db5dee54..f3199f67 100644 --- a/FreeFileSync/Source/base/binary.cpp +++ b/FreeFileSync/Source/base/binary.cpp @@ -137,7 +137,7 @@ bool fff::filesHaveSameContent(const AbstractPath& filePath1, const AbstractPath } if (totalUnbufferedIO % 2 != 0) - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); return true; } diff --git a/FreeFileSync/Source/base/comparison.cpp b/FreeFileSync/Source/base/comparison.cpp index e705551b..27e23867 100644 --- a/FreeFileSync/Source/base/comparison.cpp +++ b/FreeFileSync/Source/base/comparison.cpp @@ -103,14 +103,14 @@ ResolvedBaseFolders initializeBaseFolders(const std::vector& fpCf if (!status.failedChecks.empty()) { - std::wstring msg = _("Cannot find the following folders:") + L"\n"; + std::wstring msg = _("Cannot find the following folders:") + L'\n'; for (const auto& [folderPath, error] : status.failedChecks) - msg += L"\n" + AFS::getDisplayPath(folderPath); + msg += L'\n' + AFS::getDisplayPath(folderPath); msg += L"\n___________________________________________"; for (const auto& [folderPath, error] : status.failedChecks) - msg += L"\n\n" + replaceCpy(error.toString(), L"\n\n", L"\n"); + msg += L"\n\n" + replaceCpy(error.toString(), L"\n\n", L'\n'); throw FileError(msg); } @@ -119,10 +119,10 @@ ResolvedBaseFolders initializeBaseFolders(const std::vector& fpCf if (!notExisting.empty()) { - std::wstring msg = _("The following folders do not yet exist:") + L"\n"; + std::wstring msg = _("The following folders do not yet exist:") + L'\n'; for (const AbstractPath& folderPath : notExisting) - msg += L"\n" + AFS::getDisplayPath(folderPath); + msg += L'\n' + AFS::getDisplayPath(folderPath); msg += L"\n\n"; msg += _("The folders are created automatically when needed."); @@ -142,9 +142,9 @@ ResolvedBaseFolders initializeBaseFolders(const std::vector& fpCf for (const auto& [key, aliases] : ciPathAliases) if (aliases.size() > 1) { - msg += L"\n"; + msg += L'\n'; for (const AbstractPath& aliasPath : aliases) - msg += L"\n" + AFS::getDisplayPath(aliasPath); + msg += L'\n' + AFS::getDisplayPath(aliasPath); } callback.reportWarning(msg, warnings.warnFoldersDifferInCase); //throw X @@ -204,7 +204,7 @@ ComparisonBuffer::ComparisonBuffer(const std::set& foldersToRead, const std::chrono::steady_clock::time_point compareStartTime = std::chrono::steady_clock::now(); int itemsReported = 0; - auto onStatusUpdate = [&, textScanning = _("Scanning:") + L" "](const std::wstring& statusLine, int itemsTotal) + auto onStatusUpdate = [&, textScanning = _("Scanning:") + L' '](const std::wstring& statusLine, int itemsTotal) { callback.updateDataProcessed(itemsTotal - itemsReported, 0); //noexcept itemsReported = itemsTotal; @@ -219,9 +219,9 @@ ComparisonBuffer::ComparisonBuffer(const std::set& foldersToRead, const int64_t totalTimeSec = std::chrono::duration_cast(std::chrono::steady_clock::now() - compareStartTime).count(); - callback.reportInfo(_("Comparison finished:") + L" " + + callback.reportInfo(_("Comparison finished:") + L' ' + _P("1 item found", "%x items found", itemsReported) + L" | " + - _("Time elapsed:") + L" " + copyStringTo(wxTimeSpan::Seconds(totalTimeSec).Format())); //throw X + _("Time elapsed:") + L' ' + copyStringTo(wxTimeSpan::Seconds(totalTimeSec).Format())); //throw X } @@ -236,49 +236,49 @@ const wchar_t arrowRight[] = L"->"; // => only add path info if information is relevant, e.g. conflict is specific to left/right side only template inline -Zstringw getConflictInvalidDate(const FileOrLinkPair& file) +Zstringc getConflictInvalidDate(const FileOrLinkPair& file) { - return copyStringTo(replaceCpy(_("File %x has an invalid date."), L"%x", fmtPath(AFS::getDisplayPath(file.template getAbstractPath()))) + L"\n" + - _("Date:") + L" " + formatUtcToLocalTime(file.template getLastWriteTime())); + return utfTo(replaceCpy(_("File %x has an invalid date."), L"%x", fmtPath(AFS::getDisplayPath(file.template getAbstractPath()))) + L'\n' + + _("Date:") + L' ' + formatUtcToLocalTime(file.template getLastWriteTime())); } -Zstringw getConflictSameDateDiffSize(const FilePair& file) +Zstringc getConflictSameDateDiffSize(const FilePair& file) { - return copyStringTo(_("Files have the same date but a different size.") + L"\n" + - arrowLeft + L" " + _("Date:") + L" " + formatUtcToLocalTime(file.getLastWriteTime< LEFT_SIDE>()) + L" " + _("Size:") + L" " + formatNumber(file.getFileSize()) + L"\n" + - arrowRight + L" " + _("Date:") + L" " + formatUtcToLocalTime(file.getLastWriteTime()) + L" " + _("Size:") + L" " + formatNumber(file.getFileSize())); + return utfTo(_("Files have the same date but a different size.") + L'\n' + + arrowLeft + L' ' + _("Date:") + L' ' + formatUtcToLocalTime(file.getLastWriteTime< LEFT_SIDE>()) + L" " + _("Size:") + L' ' + formatNumber(file.getFileSize()) + L'\n' + + arrowRight + L' ' + _("Date:") + L' ' + formatUtcToLocalTime(file.getLastWriteTime()) + L" " + _("Size:") + L' ' + formatNumber(file.getFileSize())); } -Zstringw getConflictSkippedBinaryComparison() +Zstringc getConflictSkippedBinaryComparison() { - return copyStringTo(_("Content comparison was skipped for excluded files.")); + return utfTo(_("Content comparison was skipped for excluded files.")); } -Zstringw getDescrDiffMetaShortnameCase(const FileSystemObject& fsObj) +Zstringc getDescrDiffMetaShortnameCase(const FileSystemObject& fsObj) { - return copyStringTo(_("Items differ in attributes only") + L"\n" + - arrowLeft + L" " + fmtPath(fsObj.getItemName< LEFT_SIDE>()) + L"\n" + - arrowRight + L" " + fmtPath(fsObj.getItemName())); + return utfTo(_("Items differ in attributes only") + L'\n' + + arrowLeft + L' ' + fmtPath(fsObj.getItemName< LEFT_SIDE>()) + L'\n' + + arrowRight + L' ' + fmtPath(fsObj.getItemName())); } #if 0 template -Zstringw getDescrDiffMetaData(const FileOrLinkPair& file) +Zstringc getDescrDiffMetaData(const FileOrLinkPair& file) { - return copyStringTo(_("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())); + return utfTo(_("Items differ in attributes only") + L'\n' + + arrowLeft + L' ' + _("Date:") + L' ' + formatUtcToLocalTime(file.template getLastWriteTime< LEFT_SIDE>()) + L'\n' + + arrowRight + L' ' + _("Date:") + L' ' + formatUtcToLocalTime(file.template getLastWriteTime())); } #endif -Zstringw getConflictAmbiguousItemName(const Zstring& itemName) +Zstringc getConflictAmbiguousItemName(const Zstring& itemName) { - return copyStringTo(replaceCpy(_("The name %x is used by more than one item in the folder."), L"%x", fmtPath(itemName))); + return utfTo(replaceCpy(_("The name %x is used by more than one item in the folder."), L"%x", fmtPath(itemName))); } //----------------------------------------------------------------------------- @@ -392,7 +392,7 @@ void categorizeSymlinkByContent(SymlinkPair& symlink, PhaseCallback& callback) }, callback); //throw X if (!errMsg.empty()) - symlink.setCategoryConflict(copyStringTo(errMsg)); + symlink.setCategoryConflict(utfTo(errMsg)); else { if (binaryContentL == binaryContentR) @@ -489,7 +489,7 @@ void categorizeFileByContent(FilePair& file, const std::wstring& txtComparingCon }, acb); //throw ThreadInterruption if (!errMsg.empty()) - file.setCategoryConflict(copyStringTo(errMsg)); + file.setCategoryConflict(utfTo(errMsg)); else { if (haveSameContent) @@ -542,7 +542,7 @@ std::list> ComparisonBuffer::compareByContent(co //PERF_START; std::list> output; - const Zstringw txtConflictSkippedBinaryComparison = getConflictSkippedBinaryComparison(); //avoid premature pess.: save memory via ref-counted string + const Zstringc txtConflictSkippedBinaryComparison = getConflictSkippedBinaryComparison(); //avoid premature pess.: save memory via ref-counted string for (const auto& [folderPair, fpCfg] : workLoad) { @@ -654,7 +654,7 @@ std::list> ComparisonBuffer::compareByContent(co class MergeSides { public: - MergeSides(const std::map& errorsByRelPath, + MergeSides(const std::map& errorsByRelPath, std::vector& undefinedFilesOut, std::vector& undefinedSymlinksOut) : errorsByRelPath_(errorsByRelPath), @@ -671,21 +671,21 @@ public: } private: - void mergeTwoSides(const FolderContainer& lhs, const FolderContainer& rhs, const Zstringw* errorMsg, ContainerObject& output); + void mergeTwoSides(const FolderContainer& lhs, const FolderContainer& rhs, const Zstringc* errorMsg, ContainerObject& output); template - void fillOneSide(const FolderContainer& folderCont, const Zstringw* errorMsg, ContainerObject& output); + void fillOneSide(const FolderContainer& folderCont, const Zstringc* errorMsg, ContainerObject& output); - const Zstringw* checkFailedRead(FileSystemObject& fsObj, const Zstringw* errorMsg); + const Zstringc* checkFailedRead(FileSystemObject& fsObj, const Zstringc* errorMsg); - const std::map& errorsByRelPath_; //base-relative paths or empty if read-error for whole base directory + const std::map& errorsByRelPath_; //base-relative paths or empty if read-error for whole base directory std::vector& undefinedFiles_; std::vector& undefinedSymlinks_; }; inline -const Zstringw* MergeSides::checkFailedRead(FileSystemObject& fsObj, const Zstringw* errorMsg) +const Zstringc* MergeSides::checkFailedRead(FileSystemObject& fsObj, const Zstringc* errorMsg) { if (!errorMsg) { @@ -697,15 +697,15 @@ const Zstringw* MergeSides::checkFailedRead(FileSystemObject& fsObj, const Zstri if (errorMsg) { fsObj.setActive(false); - fsObj.setCategoryConflict(*errorMsg); //peak memory: Zstringw is ref-counted, unlike std::wstring! - static_assert(std::is_same_v); + fsObj.setCategoryConflict(*errorMsg); //peak memory: Zstringc is ref-counted, unlike std::string! + static_assert(std::is_same_v); } return errorMsg; } template -void MergeSides::fillOneSide(const FolderContainer& folderCont, const Zstringw* errorMsg, ContainerObject& output) +void MergeSides::fillOneSide(const FolderContainer& folderCont, const Zstringc* errorMsg, ContainerObject& output) { for (const auto& [fileName, attrib] : folderCont.files) { @@ -722,7 +722,7 @@ void MergeSides::fillOneSide(const FolderContainer& folderCont, const Zstringw* for (const auto& [folderName, attrAndSub] : folderCont.folders) { FolderPair& newFolder = output.addSubFolder(folderName, attrAndSub.first); - const Zstringw* errorMsgNew = checkFailedRead(newFolder, errorMsg); + const Zstringc* errorMsgNew = checkFailedRead(newFolder, errorMsg); fillOneSide(attrAndSub.second, errorMsgNew, newFolder); //recurse } } @@ -783,7 +783,7 @@ void matchFolders(const MapType& mapLeft, const MapType& mapRight, ProcessLeftOn auto itEndCase = std::find_if(itCase + 1, itEndEq, [&](const FileRef& fr) { return getUnicodeNormalForm(fr.ref->first) != getUnicodeNormalForm(itCase->ref->first); }); if (!tryMatchRange(itCase, itEndCase)) { - const Zstringw& conflictMsg = getConflictAmbiguousItemName(itCase->ref->first); + const Zstringc& conflictMsg = getConflictAmbiguousItemName(itCase->ref->first); std::for_each(itCase, itEndCase, [&](const FileRef& fr) { if (fr.leftSide) @@ -800,16 +800,16 @@ void matchFolders(const MapType& mapLeft, const MapType& mapRight, ProcessLeftOn } -void MergeSides::mergeTwoSides(const FolderContainer& lhs, const FolderContainer& rhs, const Zstringw* errorMsg, ContainerObject& output) +void MergeSides::mergeTwoSides(const FolderContainer& lhs, const FolderContainer& rhs, const Zstringc* errorMsg, ContainerObject& output) { using FileData = FolderContainer::FileList::value_type; - matchFolders(lhs.files, rhs.files, [&](const FileData& fileLeft, const Zstringw* conflictMsg) + matchFolders(lhs.files, rhs.files, [&](const FileData& fileLeft, const Zstringc* conflictMsg) { FilePair& newItem = output.addSubFile< LEFT_SIDE>(fileLeft .first, fileLeft .second); checkFailedRead(newItem, conflictMsg ? conflictMsg : errorMsg); }, - [&](const FileData& fileRight, const Zstringw* conflictMsg) + [&](const FileData& fileRight, const Zstringc* conflictMsg) { FilePair& newItem = output.addSubFile(fileRight.first, fileRight.second); checkFailedRead(newItem, conflictMsg ? conflictMsg : errorMsg); @@ -829,12 +829,12 @@ void MergeSides::mergeTwoSides(const FolderContainer& lhs, const FolderContainer //----------------------------------------------------------------------------------------------- using SymlinkData = FolderContainer::SymlinkList::value_type; - matchFolders(lhs.symlinks, rhs.symlinks, [&](const SymlinkData& symlinkLeft, const Zstringw* conflictMsg) + matchFolders(lhs.symlinks, rhs.symlinks, [&](const SymlinkData& symlinkLeft, const Zstringc* conflictMsg) { SymlinkPair& newItem = output.addSubLink< LEFT_SIDE>(symlinkLeft .first, symlinkLeft .second); checkFailedRead(newItem, conflictMsg ? conflictMsg : errorMsg); }, - [&](const SymlinkData& symlinkRight, const Zstringw* conflictMsg) + [&](const SymlinkData& symlinkRight, const Zstringc* conflictMsg) { SymlinkPair& newItem = output.addSubLink(symlinkRight.first, symlinkRight.second); checkFailedRead(newItem, conflictMsg ? conflictMsg : errorMsg); @@ -853,22 +853,22 @@ void MergeSides::mergeTwoSides(const FolderContainer& lhs, const FolderContainer //----------------------------------------------------------------------------------------------- using FolderData = FolderContainer::FolderList::value_type; - matchFolders(lhs.folders, rhs.folders, [&](const FolderData& dirLeft, const Zstringw* conflictMsg) + matchFolders(lhs.folders, rhs.folders, [&](const FolderData& dirLeft, const Zstringc* conflictMsg) { FolderPair& newFolder = output.addSubFolder(dirLeft.first, dirLeft.second.first); - const Zstringw* errorMsgNew = checkFailedRead(newFolder, conflictMsg ? conflictMsg : errorMsg); + const Zstringc* errorMsgNew = checkFailedRead(newFolder, conflictMsg ? conflictMsg : errorMsg); this->fillOneSide(dirLeft.second.second, errorMsgNew, newFolder); //recurse }, - [&](const FolderData& dirRight, const Zstringw* conflictMsg) + [&](const FolderData& dirRight, const Zstringc* conflictMsg) { FolderPair& newFolder = output.addSubFolder(dirRight.first, dirRight.second.first); - const Zstringw* errorMsgNew = checkFailedRead(newFolder, conflictMsg ? conflictMsg : errorMsg); + const Zstringc* errorMsgNew = checkFailedRead(newFolder, conflictMsg ? conflictMsg : errorMsg); this->fillOneSide(dirRight.second.second, errorMsgNew, newFolder); //recurse }, [&](const FolderData& dirLeft, const FolderData& dirRight) { FolderPair& newFolder = output.addSubFolder(dirLeft.first, dirLeft.second.first, DIR_EQUAL, dirRight.first, dirRight.second.first); - const Zstringw* errorMsgNew = checkFailedRead(newFolder, errorMsg); + const Zstringc* errorMsgNew = checkFailedRead(newFolder, errorMsg); if (!errorMsgNew) if (getUnicodeNormalForm(dirLeft.first) != @@ -925,12 +925,12 @@ std::shared_ptr ComparisonBuffer::performComparison(const Resolv const DirectoryValue* bufValueLeft = getDirValue(fp.folderPathLeft); const DirectoryValue* bufValueRight = getDirValue(fp.folderPathRight); - std::map failedReads; //base-relative paths or empty if read-error for whole base directory + std::map failedReads; //base-relative paths or empty if read-error for whole base directory { - auto append = [&](const std::map& c) + auto append = [&](const std::map& c) { for (const auto& [relPath, errorMsg] : c) - failedReads.emplace(relPath, copyStringTo(errorMsg)); + failedReads.emplace(relPath, errorMsg); }; //mix failedFolderReads with failedItemReads: @@ -948,7 +948,7 @@ std::shared_ptr ComparisonBuffer::performComparison(const Resolv excludefilterFailedRead += Zstr("*\n"); else for (const auto& [relPath, errorMsg] : failedReads) - excludefilterFailedRead += relPath.upperCase + Zstr("\n"); //exclude item AND (potential) child items! + excludefilterFailedRead += relPath.upperCase + Zstr('\n'); //exclude item AND (potential) child items! //somewhat obscure, but it's possible on Linux file systems to have a backslash as part of a file name //=> avoid misinterpretation when parsing the filter phrase in PathFilter (see path_filter.cpp::addFilterEntry()) @@ -986,37 +986,6 @@ std::shared_ptr ComparisonBuffer::performComparison(const Resolv } -void fff::logNonDefaultSettings(const XmlGlobalSettings& activeSettings, PhaseCallback& callback) -{ - const XmlGlobalSettings defaultSettings; - std::wstring changedSettingsMsg; - - if (activeSettings.failSafeFileCopy != defaultSettings.failSafeFileCopy) - changedSettingsMsg += L"\n " + _("Fail-safe file copy") + L" - " + (activeSettings.failSafeFileCopy ? _("Enabled") : _("Disabled")); - - if (activeSettings.copyLockedFiles != defaultSettings.copyLockedFiles) - changedSettingsMsg += L"\n " + _("Copy locked files") + L" - " + (activeSettings.copyLockedFiles ? _("Enabled") : _("Disabled")); - - if (activeSettings.copyFilePermissions != defaultSettings.copyFilePermissions) - changedSettingsMsg += L"\n " + _("Copy file access permissions") + L" - " + (activeSettings.copyFilePermissions ? _("Enabled") : _("Disabled")); - - if (activeSettings.fileTimeTolerance != defaultSettings.fileTimeTolerance) - changedSettingsMsg += L"\n " + _("File time tolerance") + L" - " + numberTo(activeSettings.fileTimeTolerance); - - if (activeSettings.runWithBackgroundPriority != defaultSettings.runWithBackgroundPriority) - changedSettingsMsg += L"\n " + _("Run with background priority") + L" - " + (activeSettings.runWithBackgroundPriority ? _("Enabled") : _("Disabled")); - - if (activeSettings.createLockFile != defaultSettings.createLockFile) - changedSettingsMsg += L"\n " + _("Lock directories during sync") + L" - " + (activeSettings.createLockFile ? _("Enabled") : _("Disabled")); - - if (activeSettings.verifyFileCopy != defaultSettings.verifyFileCopy) - changedSettingsMsg += L"\n " + _("Verify copied files") + L" - " + (activeSettings.verifyFileCopy ? _("Enabled") : _("Disabled")); - - if (!changedSettingsMsg.empty()) - callback.reportInfo(_("Using non-default global settings:") + changedSettingsMsg); //throw X -} - - FolderComparison fff::compare(WarningDialogs& warnings, int fileTimeTolerance, bool allowUserInteraction, @@ -1062,7 +1031,7 @@ FolderComparison fff::compare(WarningDialogs& warnings, allowUserInteraction, warnings, callback); //throw X //directory existence only checked *once* to avoid race conditions! if (resInfo.resolvedPairs.size() != fpCfgList.size()) - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); auto basefolderExisting = [&](const AbstractPath& folderPath) { return contains(resInfo.existingBaseFolders, folderPath); }; @@ -1099,14 +1068,14 @@ FolderComparison fff::compare(WarningDialogs& warnings, folderPair.folderPathRight, fpCfg.filter.nameFilter.ref())) { msg += L"\n\n" + - AFS::getDisplayPath(folderPair.folderPathLeft) + L"\n" + + AFS::getDisplayPath(folderPair.folderPathLeft) + L'\n' + AFS::getDisplayPath(folderPair.folderPathRight); if (!pd->relPath.empty()) - msg += L"\n" + _("Exclude:") + L" " + utfTo(FILE_NAME_SEPARATOR + pd->relPath + FILE_NAME_SEPARATOR); + msg += L'\n' + _("Exclude:") + L' ' + utfTo(FILE_NAME_SEPARATOR + pd->relPath + FILE_NAME_SEPARATOR); } if (!msg.empty()) - callback.reportWarning(_("One base folder of a folder pair is contained in the other one.") + L"\n" + //throw X + callback.reportWarning(_("One base folder of a folder pair is contained in the other one.") + L'\n' + //throw X _("The folder should be excluded from synchronization via filter.") + msg, warnings.warnDependentFolderPair); } //-------------------end of basic checks------------------------------------------ @@ -1187,7 +1156,7 @@ FolderComparison fff::compare(WarningDialogs& warnings, } catch (const std::bad_alloc& e) { - callback.reportFatalError(_("Out of memory.") + L" " + utfTo(e.what())); + callback.reportFatalError(_("Out of memory.") + L' ' + utfTo(e.what())); return {}; } } diff --git a/FreeFileSync/Source/base/comparison.h b/FreeFileSync/Source/base/comparison.h index b3315926..d547ee05 100644 --- a/FreeFileSync/Source/base/comparison.h +++ b/FreeFileSync/Source/base/comparison.h @@ -7,7 +7,7 @@ #ifndef COMPARISON_H_8032178534545426 #define COMPARISON_H_8032178534545426 -#include "config.h" +//#include "config.h" #include "file_hierarchy.h" #include "process_callback.h" #include "norm_filter.h" @@ -47,9 +47,6 @@ struct FolderPairCfg std::vector extractCompareCfg(const MainConfiguration& mainCfg); //fill FolderPairCfg and resolve folder pairs -//inform about (important) non-default global settings related to comparison and synchronization -void logNonDefaultSettings(const XmlGlobalSettings& currentSettings, PhaseCallback& callback); - //FFS core routine: output.size() == fpCfgList.size() or 0 on fatal error FolderComparison compare(WarningDialogs& warnings, int fileTimeTolerance, diff --git a/FreeFileSync/Source/base/config.cpp b/FreeFileSync/Source/base/config.cpp deleted file mode 100644 index 0a170bbb..00000000 --- a/FreeFileSync/Source/base/config.cpp +++ /dev/null @@ -1,2364 +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 "config.h" -#include -#include -#include -#include -#include -#include "ffs_paths.h" -//#include "../afs/concrete.h" - - -using namespace zen; -using namespace fff; //functionally needed for correct overload resolution!!! - - -namespace -{ -//------------------------------------------------------------------------------------------------------------------------------- -const int XML_FORMAT_GLOBAL_CFG = 16; //2020-01-30 -const int XML_FORMAT_SYNC_CFG = 15; //2020-01-30 -//------------------------------------------------------------------------------------------------------------------------------- -} - -XmlType getXmlTypeNoThrow(const XmlDoc& doc) //throw() -{ - if (doc.root().getNameAs() == "FreeFileSync") - { - std::string type; - if (doc.root().getAttribute("XmlType", type)) - { - if (type == "GUI") - return XmlType::gui; - else if (type == "BATCH") - return XmlType::batch; - else if (type == "GLOBAL") - return XmlType::global; - } - } - return XmlType::other; -} - - -XmlType fff::getXmlType(const Zstring& filePath) //throw FileError -{ - //quick exit if file is not an XML - XmlDoc doc = loadXml(filePath); //throw FileError - return ::getXmlTypeNoThrow(doc); -} - - -void setXmlType(XmlDoc& doc, XmlType type) //throw() -{ - switch (type) - { - case XmlType::gui: - doc.root().setAttribute("XmlType", "GUI"); - break; - case XmlType::batch: - doc.root().setAttribute("XmlType", "BATCH"); - break; - case XmlType::global: - doc.root().setAttribute("XmlType", "GLOBAL"); - break; - case XmlType::other: - assert(false); - break; - } -} - - -XmlGlobalSettings::XmlGlobalSettings() : - soundFileSyncFinished(getResourceDirPf() + Zstr("bell.wav")) -{ -} - -//################################################################################################################ - -Zstring fff::getGlobalConfigFile() -{ - return getConfigDirPathPf() + Zstr("GlobalSettings.xml"); -} - - -XmlGuiConfig fff::convertBatchToGui(const XmlBatchConfig& batchCfg) //noexcept -{ - XmlGuiConfig output; - output.mainCfg = batchCfg.mainCfg; - return output; -} - - -XmlBatchConfig fff::convertGuiToBatch(const XmlGuiConfig& guiCfg, const BatchExclusiveConfig& batchExCfg) //noexcept -{ - XmlBatchConfig output; - output.mainCfg = guiCfg.mainCfg; - output.batchExCfg = batchExCfg; - return output; -} - - -namespace -{ -std::vector splitFilterByLines(Zstring filterPhrase) -{ - trim(filterPhrase); - if (filterPhrase.empty()) - return {}; - - return split(filterPhrase, Zstr('\n'), SplitType::ALLOW_EMPTY); -} - -Zstring mergeFilterLines(const std::vector& filterLines) -{ - Zstring out; - for (const Zstring& line : filterLines) - { - out += line; - out += Zstr('\n'); - } - return trimCpy(out); -} -} - -namespace zen -{ -template <> inline -void writeText(const wxLanguage& value, std::string& output) -{ - //use description as unique wxLanguage identifier, see localization.cpp - //=> handle changes to wxLanguage enum between wxWidgets versions - if (const wxLanguageInfo* lngInfo = wxLocale::GetLanguageInfo(value)) - output = utfTo(lngInfo->Description); - else - { - assert(false); - output = "English (U.S.)"; - } -} - -template <> inline -bool readText(const std::string& input, wxLanguage& value) -{ - if (const wxLanguageInfo* lngInfo = wxLocale::FindLanguageInfo(utfTo(input))) - { - value = static_cast(lngInfo->Language); - return true; - } - return false; -} - - -template <> inline -void writeText(const CompareVariant& value, std::string& output) -{ - switch (value) - { - case CompareVariant::timeSize: - output = "TimeAndSize"; - break; - case CompareVariant::content: - output = "Content"; - break; - case CompareVariant::size: - output = "Size"; - break; - } -} - -template <> inline -bool readText(const std::string& input, CompareVariant& value) -{ - const std::string tmp = trimCpy(input); - if (tmp == "TimeAndSize") - value = CompareVariant::timeSize; - else if (tmp == "Content") - value = CompareVariant::content; - else if (tmp == "Size") - value = CompareVariant::size; - else - return false; - return true; -} - - -template <> inline -void writeText(const SyncDirection& value, std::string& output) -{ - switch (value) - { - case SyncDirection::LEFT: - output = "left"; - break; - case SyncDirection::RIGHT: - output = "right"; - break; - case SyncDirection::NONE: - output = "none"; - break; - } -} - -template <> inline -bool readText(const std::string& input, SyncDirection& value) -{ - const std::string tmp = trimCpy(input); - if (tmp == "left") - value = SyncDirection::LEFT; - else if (tmp == "right") - value = SyncDirection::RIGHT; - else if (tmp == "none") - value = SyncDirection::NONE; - else - return false; - return true; -} - - -template <> inline -void writeText(const BatchErrorHandling& value, std::string& output) -{ - switch (value) - { - case BatchErrorHandling::showPopup: - output = "Show"; - break; - case BatchErrorHandling::cancel: - output = "Cancel"; - break; - } -} - -template <> inline -bool readText(const std::string& input, ResultsNotification& value) -{ - const std::string tmp = trimCpy(input); - if (tmp == "Always") - value = ResultsNotification::always; - else if (tmp == "ErrorWarning") - value = ResultsNotification::errorWarning; - else if (tmp == "ErrorOnly") - value = ResultsNotification::errorOnly; - else - return false; - return true; -} - - -template <> inline -void writeText(const ResultsNotification& value, std::string& output) -{ - switch (value) - { - case ResultsNotification::always: - output = "Always"; - break; - case ResultsNotification::errorWarning: - output = "ErrorWarning"; - break; - case ResultsNotification::errorOnly: - output = "ErrorOnly"; - break; - } -} - - -template <> inline -bool readText(const std::string& input, BatchErrorHandling& value) -{ - const std::string tmp = trimCpy(input); - if (tmp == "Show") - value = BatchErrorHandling::showPopup; - else if (tmp == "Cancel") - value = BatchErrorHandling::cancel; - else - return false; - return true; -} - - -template <> inline -void writeText(const PostSyncCondition& value, std::string& output) -{ - switch (value) - { - case PostSyncCondition::COMPLETION: - output = "Completion"; - break; - case PostSyncCondition::ERRORS: - output = "Errors"; - break; - case PostSyncCondition::SUCCESS: - output = "Success"; - break; - } -} - -template <> inline -bool readText(const std::string& input, PostSyncCondition& value) -{ - const std::string tmp = trimCpy(input); - if (tmp == "Completion") - value = PostSyncCondition::COMPLETION; - else if (tmp == "Errors") - value = PostSyncCondition::ERRORS; - else if (tmp == "Success") - value = PostSyncCondition::SUCCESS; - else - return false; - return true; -} - - -template <> inline -void writeText(const PostSyncAction& value, std::string& output) -{ - switch (value) - { - case PostSyncAction::none: - output = "None"; - break; - case PostSyncAction::sleep: - output = "Sleep"; - break; - case PostSyncAction::shutdown: - output = "Shutdown"; - break; - } -} - -template <> inline -bool readText(const std::string& input, PostSyncAction& value) -{ - const std::string tmp = trimCpy(input); - if (tmp == "None") - value = PostSyncAction::none; - else if (tmp == "Sleep") - value = PostSyncAction::sleep; - else if (tmp == "Shutdown") - value = PostSyncAction::shutdown; - else - return false; - return true; -} - - -template <> inline -void writeText(const FileIconSize& value, std::string& output) -{ - switch (value) - { - case FileIconSize::SMALL: - output = "Small"; - break; - case FileIconSize::MEDIUM: - output = "Medium"; - break; - case FileIconSize::LARGE: - output = "Large"; - break; - } -} - -template <> inline -bool readText(const std::string& input, FileIconSize& value) -{ - const std::string tmp = trimCpy(input); - if (tmp == "Small") - value = FileIconSize::SMALL; - else if (tmp == "Medium") - value = FileIconSize::MEDIUM; - else if (tmp == "Large") - value = FileIconSize::LARGE; - else - return false; - return true; -} - - -template <> inline -void writeText(const DeletionPolicy& value, std::string& output) -{ - switch (value) - { - case DeletionPolicy::permanent: - output = "Permanent"; - break; - case DeletionPolicy::recycler: - output = "RecycleBin"; - break; - case DeletionPolicy::versioning: - output = "Versioning"; - break; - } -} - -template <> inline -bool readText(const std::string& input, DeletionPolicy& value) -{ - const std::string tmp = trimCpy(input); - if (tmp == "Permanent") - value = DeletionPolicy::permanent; - else if (tmp == "RecycleBin") - value = DeletionPolicy::recycler; - else if (tmp == "Versioning") - value = DeletionPolicy::versioning; - else - return false; - return true; -} - - -template <> inline -void writeText(const SymLinkHandling& value, std::string& output) -{ - switch (value) - { - case SymLinkHandling::EXCLUDE: - output = "Exclude"; - break; - case SymLinkHandling::DIRECT: - output = "Direct"; - break; - case SymLinkHandling::FOLLOW: - output = "Follow"; - break; - } -} - -template <> inline -bool readText(const std::string& input, SymLinkHandling& value) -{ - const std::string tmp = trimCpy(input); - if (tmp == "Exclude") - value = SymLinkHandling::EXCLUDE; - else if (tmp == "Direct") - value = SymLinkHandling::DIRECT; - else if (tmp == "Follow") - value = SymLinkHandling::FOLLOW; - else - return false; - return true; -} - - -template <> inline -void writeText(const ColumnTypeRim& value, std::string& output) -{ - switch (value) - { - case ColumnTypeRim::ITEM_PATH: - output = "Path"; - break; - case ColumnTypeRim::SIZE: - output = "Size"; - break; - case ColumnTypeRim::DATE: - output = "Date"; - break; - case ColumnTypeRim::EXTENSION: - output = "Ext"; - break; - } -} - -template <> inline -bool readText(const std::string& input, ColumnTypeRim& value) -{ - const std::string tmp = trimCpy(input); - if (tmp == "Path") - value = ColumnTypeRim::ITEM_PATH; - else if (tmp == "Size") - value = ColumnTypeRim::SIZE; - else if (tmp == "Date") - value = ColumnTypeRim::DATE; - else if (tmp == "Ext") - value = ColumnTypeRim::EXTENSION; - else - return false; - return true; -} - - -template <> inline -void writeText(const ItemPathFormat& value, std::string& output) -{ - switch (value) - { - case ItemPathFormat::FULL_PATH: - output = "Full"; - break; - case ItemPathFormat::RELATIVE_PATH: - output = "Relative"; - break; - case ItemPathFormat::ITEM_NAME: - output = "Item"; - break; - } -} - -template <> inline -bool readText(const std::string& input, ItemPathFormat& value) -{ - const std::string tmp = trimCpy(input); - if (tmp == "Full") - value = ItemPathFormat::FULL_PATH; - else if (tmp == "Relative") - value = ItemPathFormat::RELATIVE_PATH; - else if (tmp == "Item") - value = ItemPathFormat::ITEM_NAME; - else - return false; - return true; -} - -template <> inline -void writeText(const ColumnTypeCfg& value, std::string& output) -{ - switch (value) - { - case ColumnTypeCfg::name: - output = "Name"; - break; - case ColumnTypeCfg::lastSync: - output = "Last"; - break; - case ColumnTypeCfg::lastLog: - output = "Log"; - break; - } -} - -template <> inline -bool readText(const std::string& input, ColumnTypeCfg& value) -{ - const std::string tmp = trimCpy(input); - if (tmp == "Name") - value = ColumnTypeCfg::name; - else if (tmp == "Last") - value = ColumnTypeCfg::lastSync; - else if (tmp == "Log") - value = ColumnTypeCfg::lastLog; - else - return false; - return true; -} - - -template <> inline -void writeText(const ColumnTypeTree& value, std::string& output) -{ - switch (value) - { - case ColumnTypeTree::FOLDER_NAME: - output = "Tree"; - break; - case ColumnTypeTree::ITEM_COUNT: - output = "Count"; - break; - case ColumnTypeTree::BYTES: - output = "Bytes"; - break; - } -} - -template <> inline -bool readText(const std::string& input, ColumnTypeTree& value) -{ - const std::string tmp = trimCpy(input); - if (tmp == "Tree") - value = ColumnTypeTree::FOLDER_NAME; - else if (tmp == "Count") - value = ColumnTypeTree::ITEM_COUNT; - else if (tmp == "Bytes") - value = ColumnTypeTree::BYTES; - else - return false; - return true; -} - - -template <> inline -void writeText(const UnitSize& value, std::string& output) -{ - switch (value) - { - case UnitSize::NONE: - output = "None"; - break; - case UnitSize::BYTE: - output = "Byte"; - break; - case UnitSize::KB: - output = "KB"; - break; - case UnitSize::MB: - output = "MB"; - break; - } -} - -template <> inline -bool readText(const std::string& input, UnitSize& value) -{ - const std::string tmp = trimCpy(input); - if (tmp == "None") - value = UnitSize::NONE; - else if (tmp == "Byte") - value = UnitSize::BYTE; - else if (tmp == "KB") - value = UnitSize::KB; - else if (tmp == "MB") - value = UnitSize::MB; - else - return false; - return true; -} - -template <> inline -void writeText(const UnitTime& value, std::string& output) -{ - switch (value) - { - case UnitTime::NONE: - output = "None"; - break; - case UnitTime::TODAY: - output = "Today"; - break; - case UnitTime::THIS_MONTH: - output = "Month"; - break; - case UnitTime::THIS_YEAR: - output = "Year"; - break; - case UnitTime::LAST_X_DAYS: - output = "x-days"; - break; - } -} - -template <> inline -bool readText(const std::string& input, UnitTime& value) -{ - const std::string tmp = trimCpy(input); - if (tmp == "None") - value = UnitTime::NONE; - else if (tmp == "Today") - value = UnitTime::TODAY; - else if (tmp == "Month") - value = UnitTime::THIS_MONTH; - else if (tmp == "Year") - value = UnitTime::THIS_YEAR; - else if (tmp == "x-days") - value = UnitTime::LAST_X_DAYS; - else - return false; - return true; -} - -template <> inline -void writeText(const VersioningStyle& value, std::string& output) -{ - switch (value) - { - case VersioningStyle::replace: - output = "Replace"; - break; - case VersioningStyle::timestampFolder: - output = "TimeStamp-Folder"; - break; - case VersioningStyle::timestampFile: - output = "TimeStamp-File"; - break; - } -} - -template <> inline -bool readText(const std::string& input, VersioningStyle& value) -{ - const std::string tmp = trimCpy(input); - if (tmp == "Replace") - value = VersioningStyle::replace; - else if (tmp == "TimeStamp-Folder") - value = VersioningStyle::timestampFolder; - else if (tmp == "TimeStamp-File") - value = VersioningStyle::timestampFile; - else - return false; - return true; -} - - -template <> inline -void writeText(const DirectionConfig::Variant& value, std::string& output) -{ - switch (value) - { - case DirectionConfig::TWO_WAY: - output = "TwoWay"; - break; - case DirectionConfig::MIRROR: - output = "Mirror"; - break; - case DirectionConfig::UPDATE: - output = "Update"; - break; - case DirectionConfig::CUSTOM: - output = "Custom"; - break; - } -} - -template <> inline -bool readText(const std::string& input, DirectionConfig::Variant& value) -{ - const std::string tmp = trimCpy(input); - if (tmp == "TwoWay") - value = DirectionConfig::TWO_WAY; - else if (tmp == "Mirror") - value = DirectionConfig::MIRROR; - else if (tmp == "Update") - value = DirectionConfig::UPDATE; - else if (tmp == "Custom") - value = DirectionConfig::CUSTOM; - else - return false; - return true; -} - - -template <> inline -bool readStruc(const XmlElement& input, ColAttributesRim& value) -{ - XmlIn in(input); - bool rv1 = in.attribute("Type", value.type); - bool rv2 = in.attribute("Visible", value.visible); - bool rv3 = in.attribute("Width", value.offset); //offset == width if stretch is 0 - bool rv4 = in.attribute("Stretch", value.stretch); - return rv1 && rv2 && rv3 && rv4; -} - -template <> inline -void writeStruc(const ColAttributesRim& value, XmlElement& output) -{ - XmlOut out(output); - out.attribute("Type", value.type); - out.attribute("Visible", value.visible); - out.attribute("Width", value.offset); - out.attribute("Stretch", value.stretch); -} - - -template <> inline -bool readStruc(const XmlElement& input, ColAttributesCfg& value) -{ - XmlIn in(input); - bool rv1 = in.attribute("Type", value.type); - bool rv2 = in.attribute("Visible", value.visible); - bool rv3 = in.attribute("Width", value.offset); //offset == width if stretch is 0 - bool rv4 = in.attribute("Stretch", value.stretch); - return rv1 && rv2 && rv3 && rv4; -} - -template <> inline -void writeStruc(const ColAttributesCfg& value, XmlElement& output) -{ - XmlOut out(output); - out.attribute("Type", value.type); - out.attribute("Visible", value.visible); - out.attribute("Width", value.offset); - out.attribute("Stretch", value.stretch); -} - - -template <> inline -bool readStruc(const XmlElement& input, ColAttributesTree& value) -{ - XmlIn in(input); - bool rv1 = in.attribute("Type", value.type); - bool rv2 = in.attribute("Visible", value.visible); - bool rv3 = in.attribute("Width", value.offset); //offset == width if stretch is 0 - bool rv4 = in.attribute("Stretch", value.stretch); - return rv1 && rv2 && rv3 && rv4; -} - -template <> inline -void writeStruc(const ColAttributesTree& value, XmlElement& output) -{ - XmlOut out(output); - out.attribute("Type", value.type); - out.attribute("Visible", value.visible); - out.attribute("Width", value.offset); - out.attribute("Stretch", value.stretch); -} - - -template <> inline -bool readStruc(const XmlElement& input, ViewFilterDefault& value) -{ - XmlIn in(input); - - bool success = true; - auto readAttr = [&](XmlIn& elemIn, const char name[], bool& v) - { - if (!elemIn.attribute(name, v)) - success = false; - }; - - readAttr(in, "Equal", value.equal); - readAttr(in, "Conflict", value.conflict); - readAttr(in, "Excluded", value.excluded); - - XmlIn catView = in["CategoryView"]; - readAttr(catView, "LeftOnly", value.leftOnly); - readAttr(catView, "RightOnly", value.rightOnly); - readAttr(catView, "LeftNewer", value.leftNewer); - readAttr(catView, "RightNewer", value.rightNewer); - readAttr(catView, "Different", value.different); - - XmlIn actView = in["ActionView"]; - readAttr(actView, "CreateLeft", value.createLeft); - readAttr(actView, "CreateRight", value.createRight); - readAttr(actView, "UpdateLeft", value.updateLeft); - readAttr(actView, "UpdateRight", value.updateRight); - readAttr(actView, "DeleteLeft", value.deleteLeft); - readAttr(actView, "DeleteRight", value.deleteRight); - readAttr(actView, "DoNothing", value.doNothing); - - return success; //[!] avoid short-circuit evaluation above -} - -template <> inline -void writeStruc(const ViewFilterDefault& value, XmlElement& output) -{ - XmlOut out(output); - - out.attribute("Equal", value.equal); - out.attribute("Conflict", value.conflict); - out.attribute("Excluded", value.excluded); - - XmlOut catView = out["CategoryView"]; - catView.attribute("LeftOnly", value.leftOnly); - catView.attribute("RightOnly", value.rightOnly); - catView.attribute("LeftNewer", value.leftNewer); - catView.attribute("RightNewer", value.rightNewer); - catView.attribute("Different", value.different); - - XmlOut actView = out["ActionView"]; - actView.attribute("CreateLeft", value.createLeft); - actView.attribute("CreateRight", value.createRight); - actView.attribute("UpdateLeft", value.updateLeft); - actView.attribute("UpdateRight", value.updateRight); - actView.attribute("DeleteLeft", value.deleteLeft); - actView.attribute("DeleteRight", value.deleteRight); - actView.attribute("DoNothing", value.doNothing); -} - - -template <> inline -bool readStruc(const XmlElement& input, ExternalApp& value) -{ - XmlIn in(input); - const bool rv1 = in(value.cmdLine); - const bool rv2 = in.attribute("Label", value.description); - return rv1 && rv2; -} - -template <> inline -void writeStruc(const ExternalApp& value, XmlElement& output) -{ - XmlOut out(output); - out(value.cmdLine); - out.attribute("Label", value.description); -} - - -template <> inline -void writeText(const SyncResult& value, std::string& output) -{ - switch (value) - { - case SyncResult::finishedSuccess: - output = "Success"; - break; - case SyncResult::finishedWarning: - output = "Warning"; - break; - case SyncResult::finishedError: - output = "Error"; - break; - case SyncResult::aborted: - output = "Stopped"; - break; - } -} - -template <> inline -bool readText(const std::string& input, SyncResult& value) -{ - const std::string tmp = trimCpy(input); - if (tmp == "Success") - value = SyncResult::finishedSuccess; - else if (tmp == "Warning") - value = SyncResult::finishedWarning; - else if (tmp == "Error") - value = SyncResult::finishedError; - else if (tmp == "Stopped") - value = SyncResult::aborted; - else - return false; - return true; -} -} - - -namespace -{ - - -Zstring substituteFreeFileSyncDriveLetter(const Zstring& cfgFilePath) -{ - return cfgFilePath; -} - -Zstring resolveFreeFileSyncDriveMacro(const Zstring& cfgFilePhrase) -{ - return cfgFilePhrase; -} - - -Zstring substituteFfsResourcePath(const Zstring& filePath) -{ - const Zstring resPathPf = getResourceDirPf(); - if (startsWith(trimCpy(filePath, true, false), resPathPf)) - return Zstring(Zstr("%ffs_resource%")) + FILE_NAME_SEPARATOR + afterFirst(filePath, resPathPf, IF_MISSING_RETURN_NONE); - return filePath; -} - -Zstring resolveFfsResourceMacro(const Zstring& filePhrase) -{ - if (startsWith(trimCpy(filePhrase, true, false), Zstring(Zstr("%ffs_resource%")) + FILE_NAME_SEPARATOR)) - return getResourceDirPf() + afterFirst(filePhrase, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE); - return filePhrase; -} -} - - -namespace zen -{ -template <> inline -bool readStruc(const XmlElement& input, ConfigFileItem& value) -{ - XmlIn in(input); - - const bool rv1 = in.attribute("Result", value.logResult); - - Zstring cfgPathRaw; - bool rv2 = in.attribute("Config", cfgPathRaw); - if (!rv2) //TODO: remove after migration! 2020-02-09 - rv2 = in.attribute("CfgPath", cfgPathRaw); // - - //FFS portable: use special syntax for config file paths: e.g. "FFS:\SyncJob.ffs_gui" - if (rv2) value.cfgFilePath = resolveFreeFileSyncDriveMacro(cfgPathRaw); - - const bool rv3 = in.attribute("LastSync", value.lastSyncTime); - - Zstring logPathPhrase; - bool rv4 = in.attribute("Log", logPathPhrase); - if (!rv4) //TODO: remove after migration! 2020-02-09 - rv4 = in.attribute("LogPath", logPathPhrase); // - - if (rv4) value.logFilePath = createAbstractPath(resolveFreeFileSyncDriveMacro(logPathPhrase)); - - std::string hexColor; //optional XML attribute! - if (in.attribute("Color", hexColor) && hexColor.size() == 6) - value.backColor.Set(unhexify(hexColor[0], hexColor[1]), - unhexify(hexColor[2], hexColor[3]), - unhexify(hexColor[4], hexColor[5])); - - return rv1 && rv2 && rv3 && rv4; -} - -template <> inline -void writeStruc(const ConfigFileItem& value, XmlElement& output) -{ - XmlOut out(output); - out.attribute("Result", value.logResult); - out.attribute("Config", substituteFreeFileSyncDriveLetter(value.cfgFilePath)); - out.attribute("LastSync", value.lastSyncTime); - - if (std::optional nativePath = AFS::getNativeItemPath(value.logFilePath)) - out.attribute("Log", substituteFreeFileSyncDriveLetter(*nativePath)); - else - out.attribute("Log", AFS::getInitPathPhrase(value.logFilePath)); - - if (value.backColor.IsOk()) - { - const auto& [highR, lowR] = hexify(value.backColor.Red ()); - const auto& [highG, lowG] = hexify(value.backColor.Green()); - const auto& [highB, lowB] = hexify(value.backColor.Blue ()); - out.attribute("Color", std::string({ highR, lowR, highG, lowG, highB, lowB })); - } -} - -//TODO: remove after migration! 2018-07-27 -struct ConfigFileItemV9 -{ - Zstring filePath; - time_t lastSyncTime = 0; -}; -template <> inline -bool readStruc(const XmlElement& input, ConfigFileItemV9& value) -{ - XmlIn in(input); - - Zstring rawPath; - const bool rv1 = in(rawPath); - if (rv1) value.filePath = resolveFreeFileSyncDriveMacro(rawPath); - - const bool rv2 = in.attribute("LastSync", value.lastSyncTime); - return rv1 && rv2; -} -} - - -namespace -{ -void readConfig(const XmlIn& in, CompConfig& cmpCfg) -{ - in["Variant" ](cmpCfg.compareVar); - in["Symlinks"](cmpCfg.handleSymlinks); - - //TODO: remove old parameter after migration! 2015-11-05 - if (in["TimeShift"]) - { - std::wstring timeShiftPhrase; - if (in["TimeShift"](timeShiftPhrase)) - cmpCfg.ignoreTimeShiftMinutes = fromTimeShiftPhrase(timeShiftPhrase); - } - else - { - std::wstring timeShiftPhrase; - if (in["IgnoreTimeShift"](timeShiftPhrase)) - cmpCfg.ignoreTimeShiftMinutes = fromTimeShiftPhrase(timeShiftPhrase); - } -} - - -void readConfig(const XmlIn& in, DirectionConfig& dirCfg) -{ - in["Variant"](dirCfg.var); - - if (dirCfg.var == DirectionConfig::CUSTOM) - { - XmlIn inCustDir = in["CustomDirections"]; - inCustDir["LeftOnly" ](dirCfg.custom.exLeftSideOnly); - inCustDir["RightOnly" ](dirCfg.custom.exRightSideOnly); - inCustDir["LeftNewer" ](dirCfg.custom.leftNewer); - inCustDir["RightNewer"](dirCfg.custom.rightNewer); - inCustDir["Different" ](dirCfg.custom.different); - inCustDir["Conflict" ](dirCfg.custom.conflict); - } - //else - // dirCfg.custom = DirectionSet(); - - in["DetectMovedFiles"](dirCfg.detectMovedFiles); -} - - -void readConfig(const XmlIn& in, SyncConfig& syncCfg, std::map& deviceParallelOps, int formatVer) -{ - readConfig(in, syncCfg.directionCfg); - - in["DeletionPolicy" ](syncCfg.handleDeletion); - in["VersioningFolder"](syncCfg.versioningFolderPhrase); - - if (formatVer < 12) //TODO: remove if parameter migration after some time! 2018-06-21 - { - std::string tmp; - in["VersioningFolder"].attribute("Style", tmp); - - trim(tmp); - if (tmp == "Replace") - syncCfg.versioningStyle = VersioningStyle::replace; - else if (tmp == "TimeStamp") - syncCfg.versioningStyle = VersioningStyle::timestampFile; - - if (syncCfg.versioningStyle == VersioningStyle::replace) - { - if (endsWithAsciiNoCase(syncCfg.versioningFolderPhrase, Zstr("/%timestamp%")) || - endsWithAsciiNoCase(syncCfg.versioningFolderPhrase, Zstr("\\%timestamp%"))) - { - syncCfg.versioningFolderPhrase.resize(syncCfg.versioningFolderPhrase.size() - strLength(Zstr("/%timestamp%"))); - syncCfg.versioningStyle = VersioningStyle::timestampFolder; - - if (syncCfg.versioningFolderPhrase.size() == 2 && isAsciiAlpha(syncCfg.versioningFolderPhrase[0]) && syncCfg.versioningFolderPhrase[1] == Zstr(':')) - syncCfg.versioningFolderPhrase += Zstr('\\'); - } - } - } - else - { - size_t parallelOps = 1; - if (const XmlElement* e = in["VersioningFolder"].get()) e->getAttribute("Threads", parallelOps); //try to get attribute - - const size_t parallelOpsPrev = getDeviceParallelOps(deviceParallelOps, syncCfg.versioningFolderPhrase); - /**/ setDeviceParallelOps(deviceParallelOps, syncCfg.versioningFolderPhrase, std::max(parallelOps, parallelOpsPrev)); - - in["VersioningFolder"].attribute("Style", syncCfg.versioningStyle); - - if (syncCfg.versioningStyle != VersioningStyle::replace) - if (const XmlElement* e = in["VersioningFolder"].get()) - { - e->getAttribute("MaxAge", syncCfg.versionMaxAgeDays); //try to get attributes if available - - //TODO: remove if clause after migration! 2018-07-12 - if (formatVer < 13) - { - e->getAttribute("CountMin", syncCfg.versionCountMin); // => *no error* if not available - e->getAttribute("CountMax", syncCfg.versionCountMax); // - } - else - { - e->getAttribute("MinCount", syncCfg.versionCountMin); // => *no error* if not available - e->getAttribute("MaxCount", syncCfg.versionCountMax); // - } - } - } -} - - -void readConfig(const XmlIn& in, FilterConfig& filter, int formatVer) -{ - std::vector tmpIn = splitFilterByLines(filter.includeFilter); //consider default value - in["Include"](tmpIn); - filter.includeFilter = mergeFilterLines(tmpIn); - - std::vector tmpEx = splitFilterByLines(filter.excludeFilter); //consider default value - in["Exclude"](tmpEx); - filter.excludeFilter = mergeFilterLines(tmpEx); - - //TODO: remove macro migration after some time! 2017-02-16 - if (formatVer <= 6) replace(filter.includeFilter, Zstr(';'), Zstr('|')); - if (formatVer <= 6) replace(filter.excludeFilter, Zstr(';'), Zstr('|')); - - in["TimeSpan"](filter.timeSpan); - in["TimeSpan"].attribute("Type", filter.unitTimeSpan); - - in["SizeMin"](filter.sizeMin); - in["SizeMin"].attribute("Unit", filter.unitSizeMin); - - in["SizeMax"](filter.sizeMax); - in["SizeMax"].attribute("Unit", filter.unitSizeMax); -} - - -void readConfig(const XmlIn& in, LocalPairConfig& lpc, std::map& deviceParallelOps, int formatVer) -{ - //read folder pairs - in["Left" ](lpc.folderPathPhraseLeft); - in["Right"](lpc.folderPathPhraseRight); - - size_t parallelOpsL = 1; - size_t parallelOpsR = 1; - - //TODO: remove old parameter after migration! 2018-04-14 - if (formatVer < 11) - { - auto getParallelOps = [&](const Zstring& folderPathPhrase, size_t& parallelOps) - { - if (startsWithAsciiNoCase(folderPathPhrase, Zstr("sftp:")) || - startsWithAsciiNoCase(folderPathPhrase, Zstr( "ftp:"))) - { - for (const Zstring& optPhrase : split(folderPathPhrase, Zstr("|"), SplitType::SKIP_EMPTY)) - if (startsWith(optPhrase, Zstr("con="))) - parallelOps = stringTo(afterFirst(optPhrase, Zstr("con="), IF_MISSING_RETURN_NONE)); - } - }; - getParallelOps(lpc.folderPathPhraseLeft, parallelOpsL); - getParallelOps(lpc.folderPathPhraseRight, parallelOpsR); - } - else - { - if (const XmlElement* e = in["Left" ].get()) e->getAttribute("Threads", parallelOpsL); //try to get attributes: - if (const XmlElement* e = in["Right"].get()) e->getAttribute("Threads", parallelOpsR); // => *no error* if not available - //in["Left" ].attribute("Threads", parallelOpsL); - //in["Right"].attribute("Threads", parallelOpsR); - } - - auto setParallelOps = [&](const Zstring& folderPathPhrase, size_t parallelOps) - { - const size_t parallelOpsPrev = getDeviceParallelOps(deviceParallelOps, folderPathPhrase); - /**/ setDeviceParallelOps(deviceParallelOps, folderPathPhrase, std::max(parallelOps, parallelOpsPrev)); - }; - setParallelOps(lpc.folderPathPhraseLeft, parallelOpsL); - setParallelOps(lpc.folderPathPhraseRight, parallelOpsR); - - //TODO: remove after migration - 2016-07-24 - auto ciReplace = [](Zstring& pathPhrase, const Zstring& oldTerm, const Zstring& newTerm) { pathPhrase = replaceCpyAsciiNoCase(pathPhrase, oldTerm, newTerm); }; - ciReplace(lpc.folderPathPhraseLeft, Zstr("%csidl_MyDocuments%"), Zstr("%csidl_Documents%")); - ciReplace(lpc.folderPathPhraseLeft, Zstr("%csidl_MyMusic%" ), Zstr("%csidl_Music%")); - ciReplace(lpc.folderPathPhraseLeft, Zstr("%csidl_MyPictures%" ), Zstr("%csidl_Pictures%")); - ciReplace(lpc.folderPathPhraseLeft, Zstr("%csidl_MyVideos%" ), Zstr("%csidl_Videos%")); - ciReplace(lpc.folderPathPhraseRight, Zstr("%csidl_MyDocuments%"), Zstr("%csidl_Documents%")); - ciReplace(lpc.folderPathPhraseRight, Zstr("%csidl_MyMusic%" ), Zstr("%csidl_Music%")); - ciReplace(lpc.folderPathPhraseRight, Zstr("%csidl_MyPictures%" ), Zstr("%csidl_Pictures%")); - ciReplace(lpc.folderPathPhraseRight, Zstr("%csidl_MyVideos%" ), Zstr("%csidl_Videos%")); - - //TODO: remove after migration 2016-09-27 - if (formatVer < 6) //the-base64-encoded password is now stored as an option at the string end - { - //sftp://username:[base64]c2VjcmV0c@private.example.com -> - //sftp://username@private.example.com|pass64=c2VjcmV0c - auto updateSftpSyntax = [](Zstring& pathPhrase) - { - const size_t pos = pathPhrase.find(Zstr(":[base64]")); - if (pos != Zstring::npos) - { - const size_t posEnd = pathPhrase.find(Zstr("@"), pos); - if (posEnd != Zstring::npos) - pathPhrase = Zstring(pathPhrase.begin(), pathPhrase.begin() + pos) + (pathPhrase.c_str() + posEnd) + - Zstr("|pass64=") + Zstring(pathPhrase.begin() + pos + strLength(Zstr(":[base64]")), pathPhrase.begin() + posEnd); - } - }; - updateSftpSyntax(lpc.folderPathPhraseLeft); - updateSftpSyntax(lpc.folderPathPhraseRight); - } - - //########################################################### - //alternate comp configuration (optional) - if (XmlIn inLocalCmp = in[formatVer < 10 ? "CompareConfig" : "Compare"]) //TODO: remove if parameter migration after some time! 2018-02-25 - { - CompConfig cmpCfg; - readConfig(inLocalCmp, cmpCfg); - - lpc.localCmpCfg = cmpCfg; - } - //########################################################### - //alternate sync configuration (optional) - if (XmlIn inLocalSync = in[formatVer < 10 ? "SyncConfig" : "Synchronize"]) //TODO: remove if parameter migration after some time! 2018-02-25 - { - SyncConfig syncCfg; - readConfig(inLocalSync, syncCfg, deviceParallelOps, formatVer); - - lpc.localSyncCfg = syncCfg; - } - - //########################################################### - //alternate filter configuration - if (XmlIn inLocFilter = in[formatVer < 10 ? "LocalFilter" : "Filter"]) //TODO: remove if parameter migration after some time! 2018-02-25 - readConfig(inLocFilter, lpc.localFilter, formatVer); -} - - -void readConfig(const XmlIn& in, MainConfiguration& mainCfg, int formatVer) -{ - XmlIn inMain = formatVer < 10 ? in["MainConfig"] : in; //TODO: remove if parameter migration after some time! 2018-02-25 - - if (formatVer < 10) //TODO: remove if parameter migration after some time! 2018-02-25 - readConfig(inMain["Comparison"], mainCfg.cmpCfg); - else - readConfig(inMain["Compare"], mainCfg.cmpCfg); - //########################################################### - - //read sync configuration - if (formatVer < 10) //TODO: remove if parameter migration after some time! 2018-02-25 - readConfig(inMain["SyncConfig"], mainCfg.syncCfg, mainCfg.deviceParallelOps, formatVer); - else - readConfig(inMain["Synchronize"], mainCfg.syncCfg, mainCfg.deviceParallelOps, formatVer); - - //########################################################### - - //read filter settings - if (formatVer < 10) //TODO: remove if parameter migration after some time! 2018-02-25 - readConfig(inMain["GlobalFilter"], mainCfg.globalFilter, formatVer); - else - readConfig(inMain["Filter"], mainCfg.globalFilter, formatVer); - - //########################################################### - //read folder pairs - bool firstItem = true; - for (XmlIn inPair = inMain["FolderPairs"]["Pair"]; inPair; inPair.next()) - { - LocalPairConfig lpc; - readConfig(inPair, lpc, mainCfg.deviceParallelOps, formatVer); - - if (firstItem) - { - firstItem = false; - mainCfg.firstPair = lpc; - mainCfg.additionalPairs.clear(); - } - else - mainCfg.additionalPairs.push_back(lpc); - } - - //TODO: remove if parameter migration after some time! 2017-10-24 - if (formatVer < 8) - ; - else - //TODO: remove if parameter migration after some time! 2018-02-24 - if (formatVer < 10) - inMain["IgnoreErrors"](mainCfg.ignoreErrors); - else - { - inMain["Errors"].attribute("Ignore", mainCfg.ignoreErrors); - inMain["Errors"].attribute("Retry", mainCfg.automaticRetryCount); - inMain["Errors"].attribute("Delay", mainCfg.automaticRetryDelay); - } - - //TODO: remove if parameter migration after some time! 2017-10-24 - if (formatVer < 8) - inMain["OnCompletion"](mainCfg.postSyncCommand); - else - { - inMain["PostSyncCommand"](mainCfg.postSyncCommand); - inMain["PostSyncCommand"].attribute("Condition", mainCfg.postSyncCondition); - } - - //TODO: remove if parameter migration after some time! 2018-08-13 - if (formatVer < 14) - ; //path will be extracted from BatchExclusiveConfig - else - inMain["LogFolder"](mainCfg.altLogFolderPathPhrase); - - //TODO: remove if parameter migration after some time! 2020-01-30 - if (formatVer < 15) - ; - else - { - inMain["EmailNotification"](mainCfg.emailNotifyAddress); - inMain["EmailNotification"].attribute("Condition", mainCfg.emailNotifyCondition); - } -} - - -void readConfig(const XmlIn& in, XmlGuiConfig& cfg, int formatVer) -{ - //read main config - readConfig(in, cfg.mainCfg, formatVer); - - //read GUI specific config data - XmlIn inGuiCfg = in[formatVer < 10 ? "GuiConfig" : "Gui"]; //TODO: remove if parameter migration after some time! 2018-02-25 - - std::string val; - if (inGuiCfg["MiddleGridView"](val)) //refactor into enum!? - cfg.highlightSyncAction = val == "Action"; - - //TODO: remove if clause after migration! 2017-10-24 - if (formatVer < 8) - { - std::string str; - if (inGuiCfg["HandleError"](str)) - cfg.mainCfg.ignoreErrors = str == "Ignore"; - - str = trimCpy(utfTo(cfg.mainCfg.postSyncCommand)); - if (equalAsciiNoCase(str, "Close progress dialog")) - cfg.mainCfg.postSyncCommand.clear(); - } -} - - -void readConfig(const XmlIn& in, BatchExclusiveConfig& cfg, int formatVer) -{ - XmlIn inBatchCfg = in[formatVer < 10 ? "BatchConfig" : "Batch"]; //TODO: remove if parameter migration after some time! 2018-02-25 - - //TODO: remove if clause after migration! 2018-02-01 - if (formatVer < 9) - inBatchCfg["RunMinimized"](cfg.runMinimized); - else - inBatchCfg["ProgressDialog"].attribute("Minimized", cfg.runMinimized); - - //TODO: remove if clause after migration! 2018-02-01 - if (formatVer < 9) - ; //n/a - else - inBatchCfg["ProgressDialog"].attribute("AutoClose", cfg.autoCloseSummary); - - //TODO: remove if clause after migration! 2017-10-24 - if (formatVer < 8) - { - std::string str; - if (inBatchCfg["HandleError"](str)) - cfg.batchErrorHandling = str == "Stop" ? BatchErrorHandling::cancel : BatchErrorHandling::showPopup; - } - else - inBatchCfg["ErrorDialog"](cfg.batchErrorHandling); - - //TODO: remove if clause after migration! 2017-10-24 - if (formatVer < 8) - ; //n/a - //TODO: remove if clause after migration! 2018-02-01 - else if (formatVer == 8) - { - std::string tmp; - if (inBatchCfg["PostSyncAction"](tmp)) - { - tmp = trimCpy(tmp); - if (tmp == "Summary") - cfg.postSyncAction = PostSyncAction::none; - else if (tmp == "Exit") - cfg.autoCloseSummary = true; - else if (tmp == "Sleep") - cfg.postSyncAction = PostSyncAction::sleep; - else if (tmp == "Shutdown") - cfg.postSyncAction = PostSyncAction::shutdown; - } - } - else - inBatchCfg["PostSyncAction"](cfg.postSyncAction); -} - - -void readConfig(const XmlIn& in, XmlBatchConfig& cfg, int formatVer) -{ - readConfig(in, cfg.mainCfg, formatVer); - readConfig(in, cfg.batchExCfg, formatVer); - - //TODO: remove if clause after migration! 2018-08-13 - if (formatVer < 14) - { - XmlIn inBatchCfg = in[formatVer < 10 ? "BatchConfig" : "Batch"]; - inBatchCfg["LogfileFolder"](cfg.mainCfg.altLogFolderPathPhrase); - } - - //TODO: remove if clause after migration! 2017-10-24 - if (formatVer < 8) - { - std::string str; - if (in["BatchConfig"]["HandleError"](str)) - cfg.mainCfg.ignoreErrors = str == "Ignore"; - - str = trimCpy(utfTo(cfg.mainCfg.postSyncCommand)); - if (equalAsciiNoCase(str, "Close progress dialog")) - { - cfg.batchExCfg.autoCloseSummary = true; - cfg.mainCfg.postSyncCommand.clear(); - } - else if (str == "rundll32.exe powrprof.dll,SetSuspendState Sleep" || - str == "rundll32.exe powrprof.dll,SetSuspendState" || - str == "systemctl suspend" || - str == "osascript -e 'tell application \"System Events\" to sleep'") - { - cfg.batchExCfg.postSyncAction = PostSyncAction::sleep; - cfg.mainCfg.postSyncCommand.clear(); - } - else if (str == "shutdown /s /t 60" || - str == "shutdown -s -t 60" || - str == "systemctl poweroff" || - str == "osascript -e 'tell application \"System Events\" to shut down'") - { - cfg.batchExCfg.postSyncAction = PostSyncAction::shutdown; - cfg.mainCfg.postSyncCommand.clear(); - } - else if (cfg.batchExCfg.runMinimized) - cfg.batchExCfg.autoCloseSummary = true; - } -} - - -void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer) -{ - XmlIn inGeneral = in["General"]; - - //TODO: remove old parameter after migration! 2016-01-18 - if (in["Shared"]) - inGeneral = in["Shared"]; - - inGeneral["Language"].attribute("Name", cfg.programLanguage); - - inGeneral["FailSafeFileCopy" ].attribute("Enabled", cfg.failSafeFileCopy); - inGeneral["CopyLockedFiles" ].attribute("Enabled", cfg.copyLockedFiles); - inGeneral["CopyFilePermissions" ].attribute("Enabled", cfg.copyFilePermissions); - inGeneral["FileTimeTolerance" ].attribute("Seconds", cfg.fileTimeTolerance); - inGeneral["RunWithBackgroundPriority"].attribute("Enabled", cfg.runWithBackgroundPriority); - inGeneral["LockDirectoriesDuringSync"].attribute("Enabled", cfg.createLockFile); - inGeneral["VerifyCopiedFiles" ].attribute("Enabled", cfg.verifyFileCopy); - inGeneral["LogFiles" ].attribute("MaxAge", cfg.logfilesMaxAgeDays); - inGeneral["NotificationSound" ].attribute("CompareFinished", cfg.soundFileCompareFinished); - inGeneral["NotificationSound" ].attribute("SyncFinished", cfg.soundFileSyncFinished); - inGeneral["ProgressDialog" ].attribute("AutoClose", cfg.autoCloseProgressDialog); - - //TODO: remove if parameter migration after some time! 2019-05-29 - if (formatVer < 13) - { - if (!cfg.soundFileCompareFinished.empty()) cfg.soundFileCompareFinished = getResourceDirPf() + cfg.soundFileCompareFinished; - if (!cfg.soundFileSyncFinished .empty()) cfg.soundFileSyncFinished = getResourceDirPf() + cfg.soundFileSyncFinished; - } - else - { - cfg.soundFileCompareFinished = resolveFfsResourceMacro(cfg.soundFileCompareFinished); - cfg.soundFileSyncFinished = resolveFfsResourceMacro(cfg.soundFileSyncFinished); - } - - //TODO: remove if parameter migration after some time! 2018-08-13 - if (formatVer < 14) - if (cfg.logfilesMaxAgeDays == 14) //default value was too small - cfg.logfilesMaxAgeDays = XmlGlobalSettings().logfilesMaxAgeDays; - - //TODO: remove old parameter after migration! 2018-02-04 - if (formatVer < 8) - { - XmlIn inOpt = inGeneral["OptionalDialogs"]; - inOpt["ConfirmStartSync" ].attribute("Enabled", cfg.confirmDlgs.confirmSyncStart); - inOpt["ConfirmSaveConfig" ].attribute("Enabled", cfg.confirmDlgs.popupOnConfigChange); - inOpt["ConfirmExternalCommandMassInvoke"].attribute("Enabled", cfg.confirmDlgs.confirmCommandMassInvoke); - inOpt["WarnUnresolvedConflicts" ].attribute("Enabled", cfg.warnDlgs.warnUnresolvedConflicts); - inOpt["WarnNotEnoughDiskSpace" ].attribute("Enabled", cfg.warnDlgs.warnNotEnoughDiskSpace); - inOpt["WarnSignificantDifference" ].attribute("Enabled", cfg.warnDlgs.warnSignificantDifference); - inOpt["WarnRecycleBinNotAvailable" ].attribute("Enabled", cfg.warnDlgs.warnRecyclerMissing); - inOpt["WarnInputFieldEmpty" ].attribute("Enabled", cfg.warnDlgs.warnInputFieldEmpty); - inOpt["WarnModificationTimeError" ].attribute("Enabled", cfg.warnDlgs.warnModificationTimeError); - inOpt["WarnDependentFolderPair" ].attribute("Enabled", cfg.warnDlgs.warnDependentFolderPair); - inOpt["WarnDependentBaseFolders" ].attribute("Enabled", cfg.warnDlgs.warnDependentBaseFolders); - inOpt["WarnDirectoryLockFailed" ].attribute("Enabled", cfg.warnDlgs.warnDirectoryLockFailed); - inOpt["WarnVersioningFolderPartOfSync" ].attribute("Enabled", cfg.warnDlgs.warnVersioningFolderPartOfSync); - } - else - { - XmlIn inOpt = inGeneral["OptionalDialogs"]; - inOpt["ConfirmStartSync" ].attribute("Show", cfg.confirmDlgs.confirmSyncStart); - inOpt["ConfirmSaveConfig" ].attribute("Show", cfg.confirmDlgs.popupOnConfigChange); - if (formatVer < 12) //TODO: remove old parameter after migration! 2019-02-09 - inOpt["ConfirmExternalCommandMassInvoke"].attribute("Show", cfg.confirmDlgs.confirmCommandMassInvoke); - else - inOpt["ConfirmCommandMassInvoke"].attribute("Show", cfg.confirmDlgs.confirmCommandMassInvoke); - inOpt["WarnFolderNotExisting" ].attribute("Show", cfg.warnDlgs.warnFolderNotExisting); - inOpt["WarnFoldersDifferInCase" ].attribute("Show", cfg.warnDlgs.warnFoldersDifferInCase); - inOpt["WarnUnresolvedConflicts" ].attribute("Show", cfg.warnDlgs.warnUnresolvedConflicts); - inOpt["WarnNotEnoughDiskSpace" ].attribute("Show", cfg.warnDlgs.warnNotEnoughDiskSpace); - inOpt["WarnSignificantDifference" ].attribute("Show", cfg.warnDlgs.warnSignificantDifference); - inOpt["WarnRecycleBinNotAvailable" ].attribute("Show", cfg.warnDlgs.warnRecyclerMissing); - inOpt["WarnInputFieldEmpty" ].attribute("Show", cfg.warnDlgs.warnInputFieldEmpty); - inOpt["WarnModificationTimeError" ].attribute("Show", cfg.warnDlgs.warnModificationTimeError); - inOpt["WarnDependentFolderPair" ].attribute("Show", cfg.warnDlgs.warnDependentFolderPair); - inOpt["WarnDependentBaseFolders" ].attribute("Show", cfg.warnDlgs.warnDependentBaseFolders); - inOpt["WarnDirectoryLockFailed" ].attribute("Show", cfg.warnDlgs.warnDirectoryLockFailed); - inOpt["WarnVersioningFolderPartOfSync"].attribute("Show", cfg.warnDlgs.warnVersioningFolderPartOfSync); - } - - //GUI-specific global settings (optional) - XmlIn inGui = in["Gui"]; - XmlIn inWnd = inGui["MainDialog"]; - - //read application window size and position - inWnd.attribute("Width", cfg.gui.mainDlg.dlgSize.x); - inWnd.attribute("Height", cfg.gui.mainDlg.dlgSize.y); - inWnd.attribute("PosX", cfg.gui.mainDlg.dlgPos.x); - inWnd.attribute("PosY", cfg.gui.mainDlg.dlgPos.y); - inWnd.attribute("Maximized", cfg.gui.mainDlg.isMaximized); - - //########################################################### - - //TODO: remove old parameter after migration! 2018-02-04 - if (formatVer < 8) - inWnd["CaseSensitiveSearch"].attribute("Enabled", cfg.gui.mainDlg.textSearchRespectCase); - else - //TODO: remove if parameter migration after some time! 2018-09-09 - if (formatVer < 11) - inWnd["Search"].attribute("CaseSensitive", cfg.gui.mainDlg.textSearchRespectCase); - else - inWnd["SearchPanel"].attribute("CaseSensitive", cfg.gui.mainDlg.textSearchRespectCase); - - //TODO: remove if parameter migration after some time! 2018-09-09 - if (formatVer < 11) - inWnd["FolderPairsVisible" ].attribute("Max", cfg.gui.mainDlg.folderPairsVisibleMax); - - //########################################################### - - XmlIn inConfig = inWnd["ConfigPanel"]; - inConfig.attribute("ScrollPos", cfg.gui.mainDlg.cfgGridTopRowPos); - inConfig.attribute("SyncOverdue", cfg.gui.mainDlg.cfgGridSyncOverdueDays); - inConfig.attribute("SortByColumn", cfg.gui.mainDlg.cfgGridLastSortColumn); - inConfig.attribute("SortAscending", cfg.gui.mainDlg.cfgGridLastSortAscending); - - inConfig["Columns"](cfg.gui.mainDlg.cfgGridColumnAttribs); - - //TODO: remove after migration! 2018-07-27 - if (formatVer < 10) //reset once to show the new log column - cfg.gui.mainDlg.cfgGridColumnAttribs = XmlGlobalSettings().gui.mainDlg.cfgGridColumnAttribs; - - //TODO: remove parameter migration after some time! 2018-01-08 - if (formatVer < 6) - { - inGui["ConfigHistory"].attribute("MaxSize", cfg.gui.mainDlg.cfgHistItemsMax); - - std::vector cfgHist; - inGui["ConfigHistory"](cfgHist); - - for (const Zstring& cfgPath : cfgHist) - cfg.gui.mainDlg.cfgFileHistory.emplace_back( - cfgPath, - 0, getNullPath(), SyncResult::finishedSuccess, wxNullColour); - } - //TODO: remove after migration! 2018-07-27 - else if (formatVer < 10) - { - inConfig["Configurations"].attribute("MaxSize", cfg.gui.mainDlg.cfgHistItemsMax); - - std::vector cfgFileHistory; - inConfig["Configurations"](cfgFileHistory); - - for (const ConfigFileItemV9& item : cfgFileHistory) - cfg.gui.mainDlg.cfgFileHistory.emplace_back(item.filePath, item.lastSyncTime, getNullPath(), SyncResult::finishedSuccess, wxNullColour); - } - else - { - inConfig["Configurations"].attribute("MaxSize", cfg.gui.mainDlg.cfgHistItemsMax); - inConfig["Configurations"](cfg.gui.mainDlg.cfgFileHistory); - } - //TODO: remove after migration! 2019-11-30 - if (formatVer < 15) - { - const Zstring lastRunConfigPath = getConfigDirPathPf() + Zstr("LastRun.ffs_gui"); - for (ConfigFileItem& item : cfg.gui.mainDlg.cfgFileHistory) - if (equalNativePath(item.cfgFilePath, lastRunConfigPath)) - item.backColor = wxColor(0xdd, 0xdd, 0xdd); //light grey from onCfgGridContext() - } - - //TODO: remove parameter migration after some time! 2018-01-08 - if (formatVer < 6) - { - inGui["LastUsedConfig"](cfg.gui.mainDlg.lastUsedConfigFiles); - } - else - { - std::vector cfgPaths; - if (inConfig["LastUsed"](cfgPaths)) - { - for (Zstring& filePath : cfgPaths) - filePath = resolveFreeFileSyncDriveMacro(filePath); - - cfg.gui.mainDlg.lastUsedConfigFiles = cfgPaths; - } - } - - //########################################################### - - XmlIn inOverview = inWnd["OverviewPanel"]; - inOverview.attribute("ShowPercentage", cfg.gui.mainDlg.treeGridShowPercentBar); - inOverview.attribute("SortByColumn", cfg.gui.mainDlg.treeGridLastSortColumn); - inOverview.attribute("SortAscending", cfg.gui.mainDlg.treeGridLastSortAscending); - - //read column attributes - XmlIn inColTree = inOverview["Columns"]; - inColTree(cfg.gui.mainDlg.treeGridColumnAttribs); - - XmlIn inFileGrid = inWnd["FilePanel"]; - //TODO: remove parameter migration after some time! 2018-01-08 - if (formatVer < 6) - inFileGrid = inWnd["CenterPanel"]; - - inFileGrid.attribute("ShowIcons", cfg.gui.mainDlg.showIcons); - inFileGrid.attribute("IconSize", cfg.gui.mainDlg.iconSize); - inFileGrid.attribute("SashOffset", cfg.gui.mainDlg.sashOffset); - - //TODO: remove if parameter migration after some time! 2018-09-09 - if (formatVer < 11) - ; - //TODO: remove if parameter migration after some time! 2020-01-30 - else if (formatVer < 16) - inFileGrid.attribute("MaxFolderPairsShown", cfg.gui.mainDlg.folderPairsVisibleMax); - else - inFileGrid.attribute("FolderPairsMax", cfg.gui.mainDlg.folderPairsVisibleMax); - - inFileGrid["ColumnsLeft"].attribute("PathFormat", cfg.gui.mainDlg.itemPathFormatLeftGrid); - inFileGrid["ColumnsLeft"](cfg.gui.mainDlg.columnAttribLeft); - - inFileGrid["FolderHistoryLeft" ](cfg.gui.mainDlg.folderHistoryLeft); - - inFileGrid["ColumnsRight"].attribute("PathFormat", cfg.gui.mainDlg.itemPathFormatRightGrid); - inFileGrid["ColumnsRight"](cfg.gui.mainDlg.columnAttribRight); - - inFileGrid["FolderHistoryRight"](cfg.gui.mainDlg.folderHistoryRight); - - //TODO: remove parameter migration after some time! 2018-01-08 - if (formatVer < 6) - { - inGui["FolderHistoryLeft" ](cfg.gui.mainDlg.folderHistoryLeft); - inGui["FolderHistoryRight"](cfg.gui.mainDlg.folderHistoryRight); - } - - //########################################################### - XmlIn inCopyTo = inWnd["ManualCopyTo"]; - inCopyTo.attribute("KeepRelativePaths", cfg.gui.mainDlg.copyToCfg.keepRelPaths); - inCopyTo.attribute("OverwriteIfExists", cfg.gui.mainDlg.copyToCfg.overwriteIfExists); - - XmlIn inCopyToHistory = inCopyTo["FolderHistory"]; - inCopyToHistory(cfg.gui.mainDlg.copyToCfg.folderHistory); - inCopyToHistory.attribute("LastUsedPath", cfg.gui.mainDlg.copyToCfg.lastUsedPath); - //########################################################### - - inWnd["DefaultViewFilter"](cfg.gui.mainDlg.viewFilterDefault); - - //TODO: remove old parameter after migration! 2018-02-04 - if (formatVer < 8) - { - XmlIn sharedView = inWnd["DefaultViewFilter"]["Shared"]; - sharedView.attribute("Equal", cfg.gui.mainDlg.viewFilterDefault.equal); - sharedView.attribute("Conflict", cfg.gui.mainDlg.viewFilterDefault.conflict); - sharedView.attribute("Excluded", cfg.gui.mainDlg.viewFilterDefault.excluded); - } - - //TODO: remove old parameter after migration! 2018-01-16 - if (formatVer < 7) - inWnd["Perspective5"](cfg.gui.mainDlg.guiPerspectiveLast); - else - inWnd["Perspective"](cfg.gui.mainDlg.guiPerspectiveLast); - - //TODO: remove after migration! 2019-11-30 - auto splitEditMerge = [](wxString& perspective, wchar_t delim, const std::function& editItem) - { - std::vector v = split(perspective, delim, SplitType::ALLOW_EMPTY); - assert(!v.empty()); - perspective.clear(); - - std::for_each(v.begin(), v.end() - 1, [&](wxString& item) - { - editItem(item); - perspective += item; - perspective += delim; - }); - editItem(v.back()); - perspective += v.back(); - }; - - //TODO: remove after migration! 2018-07-27 - if (formatVer < 10) - splitEditMerge(cfg.gui.mainDlg.guiPerspectiveLast, L'|', [&](wxString& paneCfg) - { - if (contains(paneCfg, L"name=TopPanel")) - replace(paneCfg, L";row=2;", L";row=3;"); - }); - - //TODO: remove after migration! 2019-11-30 - if (formatVer < 15) - { - //set minimal TopPanel height => search and set actual height to 0 and let MainDialog's min-size handling kick in: - std::optional tpDir; - std::optional tpLayer; - std::optional tpRow; - splitEditMerge(cfg.gui.mainDlg.guiPerspectiveLast, L'|', [&](wxString& paneCfg) - { - if (contains(paneCfg, L"name=TopPanel")) - splitEditMerge(paneCfg, L';', [&](wxString& paneAttr) - { - if (startsWith(paneAttr, L"dir=")) - tpDir = stringTo(afterFirst(paneAttr, L'=', IF_MISSING_RETURN_NONE)); - else if (startsWith(paneAttr, L"layer=")) - tpLayer = stringTo(afterFirst(paneAttr, L'=', IF_MISSING_RETURN_NONE)); - else if (startsWith(paneAttr, L"row=")) - tpRow = stringTo(afterFirst(paneAttr, L'=', IF_MISSING_RETURN_NONE)); - }); - }); - - if (tpDir && tpLayer && tpRow) - { - const wxString tpSize = L"dock_size(" + - numberTo(*tpDir ) + L"," + - numberTo(*tpLayer) + L"," + - numberTo(*tpRow ) + L")="; - - splitEditMerge(cfg.gui.mainDlg.guiPerspectiveLast, L'|', [&](wxString& paneCfg) - { - if (startsWith(paneCfg, tpSize)) - paneCfg = tpSize + L"0"; - }); - } - } - - std::vector tmp = splitFilterByLines(cfg.gui.defaultExclusionFilter); //default value - inGui["DefaultExclusionFilter"](tmp); - cfg.gui.defaultExclusionFilter = mergeFilterLines(tmp); - - //TODO: remove parameter migration after some time! 2016-09-23 - if (formatVer < 4) - cfg.gui.mainDlg.cfgHistItemsMax = std::max(cfg.gui.mainDlg.cfgHistItemsMax, 100); - - //TODO: remove if parameter migration after some time! 2020-01-30 - if (formatVer < 16) - ; - else - inGui["FolderHistory" ].attribute("MaxSize", cfg.gui.folderHistoryMax); - - inGui["VersioningFolderHistory"](cfg.gui.versioningFolderHistory); - inGui["LogFolderHistory" ](cfg.gui.logFolderHistory); - - inGui["EmailHistory"](cfg.gui.emailHistory); - inGui["EmailHistory"].attribute("MaxSize", cfg.gui.emailHistoryMax); - - //TODO: remove if clause after migration! 2017-10-24 - if (formatVer < 5) - { - inGui["OnCompletionHistory"](cfg.gui.commandHistory); - inGui["OnCompletionHistory"].attribute("MaxSize", cfg.gui.commandHistoryMax); - } - else - { - inGui["CommandHistory"](cfg.gui.commandHistory); - inGui["CommandHistory"].attribute("MaxSize", cfg.gui.commandHistoryMax); - } - - //TODO: remove if parameter migration after some time! 2020-01-30 - if (formatVer < 15) - if (cfg.gui.commandHistoryMax <= 8) - cfg.gui.commandHistoryMax = XmlGlobalSettings().gui.commandHistoryMax; - - //external applications - //TODO: remove old parameter after migration! 2016-05-28 - if (inGui["ExternalApplications"]) - { - inGui["ExternalApplications"](cfg.gui.externalApps); - if (cfg.gui.externalApps.empty()) //who knows, let's repair some old failed data migrations - cfg.gui.externalApps = XmlGlobalSettings().gui.externalApps; - else - { - } - } - else - { - //TODO: remove old parameter after migration! 2018-01-16 - if (formatVer < 7) - { - std::vector> extApps; - if (inGui["ExternalApps"](extApps)) - { - cfg.gui.externalApps.clear(); - for (const auto& [description, cmdLine] : extApps) - cfg.gui.externalApps.push_back({ description, cmdLine }); - } - } - else - inGui["ExternalApps"](cfg.gui.externalApps); - } - - //TODO: remove macro migration after some time! 2016-06-30 - if (formatVer < 3) - for (ExternalApp& item : cfg.gui.externalApps) - { - replace(item.cmdLine, Zstr("%item2_path%"), Zstr("%item_path2%")); - replace(item.cmdLine, Zstr("%item_folder%"), Zstr("%folder_path%")); - replace(item.cmdLine, Zstr("%item2_folder%"), Zstr("%folder_path2%")); - - replace(item.cmdLine, Zstr("explorer /select, \"%item_path%\""), Zstr("explorer /select, \"%local_path%\"")); - replace(item.cmdLine, Zstr("\"%item_path%\""), Zstr("\"%local_path%\"")); - replace(item.cmdLine, Zstr("xdg-open \"%item_path%\""), Zstr("xdg-open \"%local_path%\"")); - replace(item.cmdLine, Zstr("open -R \"%item_path%\""), Zstr("open -R \"%local_path%\"")); - replace(item.cmdLine, Zstr("open \"%item_path%\""), Zstr("open \"%local_path%\"")); - - if (contains(makeUpperCopy(item.cmdLine), Zstr("WINMERGEU.EXE")) || - contains(makeUpperCopy(item.cmdLine), Zstr("PSPAD.EXE"))) - { - replace(item.cmdLine, Zstr("%item_path%"), Zstr("%local_path%")); - replace(item.cmdLine, Zstr("%item_path2%"), Zstr("%local_path2%")); - } - } - //TODO: remove macro migration after some time! 2016-07-18 - for (ExternalApp& item : cfg.gui.externalApps) - replace(item.cmdLine, Zstr("%item_folder%"), Zstr("%folder_path%")); - //TODO: remove after migration! 2019-11-30 - if (formatVer < 15) - for (ExternalApp& item : cfg.gui.externalApps) - { - replace(item.cmdLine, Zstr("%folder_path%"), Zstr("%parent_path%")); - replace(item.cmdLine, Zstr("%folder_path2%"), Zstr("%parent_path2%")); - } - - //last update check - inGui["LastOnlineCheck" ](cfg.gui.lastUpdateCheck); - inGui["LastOnlineVersion"](cfg.gui.lastOnlineVersion); - - //batch specific global settings - //XmlIn inBatch = in["Batch"]; - - - //TODO: remove parameter migration after some time! 2018-03-14 - if (formatVer < 9) - if (fastFromDIP(96) > 96) //high-DPI monitor => one-time migration - { - const XmlGlobalSettings defaultCfg; - cfg.gui.mainDlg.dlgSize = defaultCfg.gui.mainDlg.dlgSize; - cfg.gui.mainDlg.guiPerspectiveLast = defaultCfg.gui.mainDlg.guiPerspectiveLast; - cfg.gui.mainDlg.cfgGridColumnAttribs = defaultCfg.gui.mainDlg.cfgGridColumnAttribs; - cfg.gui.mainDlg.treeGridColumnAttribs = defaultCfg.gui.mainDlg.treeGridColumnAttribs; - cfg.gui.mainDlg.columnAttribLeft = defaultCfg.gui.mainDlg.columnAttribLeft; - cfg.gui.mainDlg.columnAttribRight = defaultCfg.gui.mainDlg.columnAttribRight; - } -} - - -template -void readConfig(const Zstring& filePath, XmlType type, ConfigType& cfg, int currentXmlFormatVer, std::wstring& warningMsg) //throw FileError -{ - XmlDoc doc = loadXml(filePath); //throw FileError - - if (getXmlTypeNoThrow(doc) != type) //noexcept - throw FileError(replaceCpy(_("File %x does not contain a valid configuration."), L"%x", fmtPath(filePath))); - - int formatVer = 0; - /*bool success =*/ doc.root().getAttribute("XmlFormat", formatVer); - - XmlIn in(doc); - ::readConfig(in, cfg, formatVer); - - try - { - checkXmlMappingErrors(in, filePath); //throw FileError - - //(try to) migrate old configuration automatically - if (formatVer < currentXmlFormatVer) - try { fff::writeConfig(cfg, filePath); /*throw FileError*/ } - catch (FileError&) { assert(false); } //don't bother user! - } - catch (const FileError& e) { warningMsg = e.toString(); } -} -} - - -void fff::readConfig(const Zstring& filePath, XmlGuiConfig& cfg, std::wstring& warningMsg) -{ - ::readConfig(filePath, XmlType::gui, cfg, XML_FORMAT_SYNC_CFG, warningMsg); //throw FileError -} - - -void fff::readConfig(const Zstring& filePath, XmlBatchConfig& cfg, std::wstring& warningMsg) -{ - ::readConfig(filePath, XmlType::batch, cfg, XML_FORMAT_SYNC_CFG, warningMsg); //throw FileError -} - - -void fff::readConfig(const Zstring& filePath, XmlGlobalSettings& cfg, std::wstring& warningMsg) -{ - ::readConfig(filePath, XmlType::global, cfg, XML_FORMAT_GLOBAL_CFG, warningMsg); //throw FileError -} - - -namespace -{ -template -XmlCfg parseConfig(const XmlDoc& doc, const Zstring& filePath, int currentXmlFormatVer, std::wstring& warningMsg) //nothrow -{ - int formatVer = 0; - /*bool success =*/ doc.root().getAttribute("XmlFormat", formatVer); - - XmlIn in(doc); - XmlCfg cfg; - ::readConfig(in, cfg, formatVer); - - try - { - checkXmlMappingErrors(in, filePath); //throw FileError - - //(try to) migrate old configuration if needed - if (formatVer < currentXmlFormatVer) - try { fff::writeConfig(cfg, filePath); /*throw FileError*/ } - catch (FileError&) { assert(false); } //don't bother user! - } - catch (const FileError& e) - { - if (warningMsg.empty()) - warningMsg = e.toString(); - } - return cfg; -} -} - - -void fff::readAnyConfig(const std::vector& filePaths, XmlGuiConfig& cfg, std::wstring& warningMsg) //throw FileError -{ - assert(!filePaths.empty()); - - std::vector mainCfgs; - - for (auto it = filePaths.begin(); it != filePaths.end(); ++it) - { - const Zstring& filePath = *it; - const bool firstItem = it == filePaths.begin(); //init all non-"mainCfg" settings with first config file - - XmlDoc doc = loadXml(filePath); //throw FileError - - switch (getXmlTypeNoThrow(doc)) - { - case XmlType::gui: - { - XmlGuiConfig guiCfg = parseConfig(doc, filePath, XML_FORMAT_SYNC_CFG, warningMsg); //nothrow - if (firstItem) - cfg = guiCfg; - mainCfgs.push_back(guiCfg.mainCfg); - } - break; - - case XmlType::batch: - { - XmlBatchConfig batchCfg = parseConfig(doc, filePath, XML_FORMAT_SYNC_CFG, warningMsg); //nothrow - if (firstItem) - cfg = convertBatchToGui(batchCfg); - mainCfgs.push_back(batchCfg.mainCfg); - } - break; - - case XmlType::global: - case XmlType::other: - throw FileError(replaceCpy(_("File %x does not contain a valid configuration."), L"%x", fmtPath(filePath))); - } - } - - cfg.mainCfg = merge(mainCfgs); -} - -//################################################################################################ - -namespace -{ -void writeConfig(const CompConfig& cmpCfg, XmlOut& out) -{ - out["Variant" ](cmpCfg.compareVar); - out["Symlinks"](cmpCfg.handleSymlinks); - out["IgnoreTimeShift"](toTimeShiftPhrase(cmpCfg.ignoreTimeShiftMinutes)); -} - - -void writeConfig(const DirectionConfig& dirCfg, XmlOut& out) -{ - out["Variant"](dirCfg.var); - - if (dirCfg.var == DirectionConfig::CUSTOM) - { - XmlOut outCustDir = out["CustomDirections"]; - outCustDir["LeftOnly" ](dirCfg.custom.exLeftSideOnly); - outCustDir["RightOnly" ](dirCfg.custom.exRightSideOnly); - outCustDir["LeftNewer" ](dirCfg.custom.leftNewer); - outCustDir["RightNewer"](dirCfg.custom.rightNewer); - outCustDir["Different" ](dirCfg.custom.different); - outCustDir["Conflict" ](dirCfg.custom.conflict); - } - - out["DetectMovedFiles"](dirCfg.detectMovedFiles); -} - - -void writeConfig(const SyncConfig& syncCfg, const std::map& deviceParallelOps, XmlOut& out) -{ - writeConfig(syncCfg.directionCfg, out); - - out["DeletionPolicy" ](syncCfg.handleDeletion); - out["VersioningFolder"](syncCfg.versioningFolderPhrase); - - const size_t parallelOps = getDeviceParallelOps(deviceParallelOps, syncCfg.versioningFolderPhrase); - if (parallelOps > 1) out["VersioningFolder"].attribute("Threads", parallelOps); - - out["VersioningFolder"].attribute("Style", syncCfg.versioningStyle); - - if (syncCfg.versioningStyle != VersioningStyle::replace) - { - if (syncCfg.versionMaxAgeDays > 0) out["VersioningFolder"].attribute("MaxAge", syncCfg.versionMaxAgeDays); - if (syncCfg.versionCountMin > 0) out["VersioningFolder"].attribute("MinCount", syncCfg.versionCountMin); - if (syncCfg.versionCountMax > 0) out["VersioningFolder"].attribute("MaxCount", syncCfg.versionCountMax); - } -} - - -void writeConfig(const FilterConfig& filter, XmlOut& out) -{ - out["Include"](splitFilterByLines(filter.includeFilter)); - out["Exclude"](splitFilterByLines(filter.excludeFilter)); - - out["TimeSpan"](filter.timeSpan); - out["TimeSpan"].attribute("Type", filter.unitTimeSpan); - - out["SizeMin"](filter.sizeMin); - out["SizeMin"].attribute("Unit", filter.unitSizeMin); - - out["SizeMax"](filter.sizeMax); - out["SizeMax"].attribute("Unit", filter.unitSizeMax); -} - - -void writeConfig(const LocalPairConfig& lpc, const std::map& deviceParallelOps, XmlOut& out) -{ - XmlOut outPair = out.ref().addChild("Pair"); - - //read folder pairs - outPair["Left" ](lpc.folderPathPhraseLeft); - outPair["Right"](lpc.folderPathPhraseRight); - - const size_t parallelOpsL = getDeviceParallelOps(deviceParallelOps, lpc.folderPathPhraseLeft); - const size_t parallelOpsR = getDeviceParallelOps(deviceParallelOps, lpc.folderPathPhraseRight); - - if (parallelOpsL > 1) outPair["Left" ].attribute("Threads", parallelOpsL); - if (parallelOpsR > 1) outPair["Right"].attribute("Threads", parallelOpsR); - - //avoid "fake" changed configs by only storing "real" parallel-enabled devices in deviceParallelOps - assert(std::all_of(deviceParallelOps.begin(), deviceParallelOps.end(), [](const auto& item) { return item.second > 1; })); - - //########################################################### - //alternate comp configuration (optional) - if (lpc.localCmpCfg) - { - XmlOut outLocalCmp = outPair["Compare"]; - writeConfig(*lpc.localCmpCfg, outLocalCmp); - } - //########################################################### - //alternate sync configuration (optional) - if (lpc.localSyncCfg) - { - XmlOut outLocalSync = outPair["Synchronize"]; - writeConfig(*lpc.localSyncCfg, deviceParallelOps, outLocalSync); - } - - //########################################################### - //alternate filter configuration - if (lpc.localFilter != FilterConfig()) //don't spam .ffs_gui file with default filter entries - { - XmlOut outFilter = outPair["Filter"]; - writeConfig(lpc.localFilter, outFilter); - } -} - - -void writeConfig(const MainConfiguration& mainCfg, XmlOut& out) -{ - XmlOut outMain = out; - - XmlOut outCmp = outMain["Compare"]; - - writeConfig(mainCfg.cmpCfg, outCmp); - //########################################################### - - XmlOut outSync = outMain["Synchronize"]; - - writeConfig(mainCfg.syncCfg, mainCfg.deviceParallelOps, outSync); - //########################################################### - - XmlOut outFilter = outMain["Filter"]; - //write filter settings - writeConfig(mainCfg.globalFilter, outFilter); - - //########################################################### - XmlOut outFp = outMain["FolderPairs"]; - //write folder pairs - writeConfig(mainCfg.firstPair, mainCfg.deviceParallelOps, outFp); - - for (const LocalPairConfig& lpc : mainCfg.additionalPairs) - writeConfig(lpc, mainCfg.deviceParallelOps, outFp); - - outMain["Errors"].attribute("Ignore", mainCfg.ignoreErrors); - outMain["Errors"].attribute("Retry", mainCfg.automaticRetryCount); - outMain["Errors"].attribute("Delay", mainCfg.automaticRetryDelay); - - outMain["PostSyncCommand"](mainCfg.postSyncCommand); - outMain["PostSyncCommand"].attribute("Condition", mainCfg.postSyncCondition); - - outMain["LogFolder"](mainCfg.altLogFolderPathPhrase); - - outMain["EmailNotification"](mainCfg.emailNotifyAddress); - outMain["EmailNotification"].attribute("Condition", mainCfg.emailNotifyCondition); -} - - -void writeConfig(const XmlGuiConfig& cfg, XmlOut& out) -{ - writeConfig(cfg.mainCfg, out); //write main config - - //write GUI specific config data - XmlOut outGuiCfg = out["Gui"]; - - outGuiCfg["MiddleGridView"](cfg.highlightSyncAction ? "Action" : "Category"); //refactor into enum!? -} - - -void writeConfig(const BatchExclusiveConfig& cfg, XmlOut& out) -{ - XmlOut outBatchCfg = out["Batch"]; - - outBatchCfg["ProgressDialog"].attribute("Minimized", cfg.runMinimized); - outBatchCfg["ProgressDialog"].attribute("AutoClose", cfg.autoCloseSummary); - outBatchCfg["ErrorDialog" ](cfg.batchErrorHandling); - outBatchCfg["PostSyncAction"](cfg.postSyncAction); -} - - -void writeConfig(const XmlBatchConfig& cfg, XmlOut& out) -{ - writeConfig(cfg.mainCfg, out); - writeConfig(cfg.batchExCfg, out); -} - - -void writeConfig(const XmlGlobalSettings& cfg, XmlOut& out) -{ - XmlOut outGeneral = out["General"]; - - outGeneral["Language"].attribute("Name", cfg.programLanguage); - - outGeneral["FailSafeFileCopy" ].attribute("Enabled", cfg.failSafeFileCopy); - outGeneral["CopyLockedFiles" ].attribute("Enabled", cfg.copyLockedFiles); - outGeneral["CopyFilePermissions" ].attribute("Enabled", cfg.copyFilePermissions); - outGeneral["FileTimeTolerance" ].attribute("Seconds", cfg.fileTimeTolerance); - outGeneral["RunWithBackgroundPriority"].attribute("Enabled", cfg.runWithBackgroundPriority); - outGeneral["LockDirectoriesDuringSync"].attribute("Enabled", cfg.createLockFile); - outGeneral["VerifyCopiedFiles" ].attribute("Enabled", cfg.verifyFileCopy); - outGeneral["LogFiles" ].attribute("MaxAge", cfg.logfilesMaxAgeDays); - outGeneral["NotificationSound" ].attribute("CompareFinished", substituteFfsResourcePath(cfg.soundFileCompareFinished)); - outGeneral["NotificationSound" ].attribute("SyncFinished", substituteFfsResourcePath(cfg.soundFileSyncFinished)); - outGeneral["ProgressDialog" ].attribute("AutoClose", cfg.autoCloseProgressDialog); - - XmlOut outOpt = outGeneral["OptionalDialogs"]; - outOpt["ConfirmStartSync" ].attribute("Show", cfg.confirmDlgs.confirmSyncStart); - outOpt["ConfirmSaveConfig" ].attribute("Show", cfg.confirmDlgs.popupOnConfigChange); - outOpt["ConfirmCommandMassInvoke" ].attribute("Show", cfg.confirmDlgs.confirmCommandMassInvoke); - outOpt["WarnFolderNotExisting" ].attribute("Show", cfg.warnDlgs.warnFolderNotExisting); - outOpt["WarnFoldersDifferInCase" ].attribute("Show", cfg.warnDlgs.warnFoldersDifferInCase); - outOpt["WarnUnresolvedConflicts" ].attribute("Show", cfg.warnDlgs.warnUnresolvedConflicts); - outOpt["WarnNotEnoughDiskSpace" ].attribute("Show", cfg.warnDlgs.warnNotEnoughDiskSpace); - outOpt["WarnSignificantDifference" ].attribute("Show", cfg.warnDlgs.warnSignificantDifference); - outOpt["WarnRecycleBinNotAvailable" ].attribute("Show", cfg.warnDlgs.warnRecyclerMissing); - outOpt["WarnInputFieldEmpty" ].attribute("Show", cfg.warnDlgs.warnInputFieldEmpty); - outOpt["WarnModificationTimeError" ].attribute("Show", cfg.warnDlgs.warnModificationTimeError); - outOpt["WarnDependentFolderPair" ].attribute("Show", cfg.warnDlgs.warnDependentFolderPair); - outOpt["WarnDependentBaseFolders" ].attribute("Show", cfg.warnDlgs.warnDependentBaseFolders); - outOpt["WarnDirectoryLockFailed" ].attribute("Show", cfg.warnDlgs.warnDirectoryLockFailed); - outOpt["WarnVersioningFolderPartOfSync"].attribute("Show", cfg.warnDlgs.warnVersioningFolderPartOfSync); - - //gui specific global settings (optional) - XmlOut outGui = out["Gui"]; - XmlOut outWnd = outGui["MainDialog"]; - - //write application window size and position - outWnd.attribute("Width", cfg.gui.mainDlg.dlgSize.x); - outWnd.attribute("Height", cfg.gui.mainDlg.dlgSize.y); - outWnd.attribute("PosX", cfg.gui.mainDlg.dlgPos.x); - outWnd.attribute("PosY", cfg.gui.mainDlg.dlgPos.y); - outWnd.attribute("Maximized", cfg.gui.mainDlg.isMaximized); - - //########################################################### - outWnd["SearchPanel" ].attribute("CaseSensitive", cfg.gui.mainDlg.textSearchRespectCase); - //########################################################### - - XmlOut outConfig = outWnd["ConfigPanel"]; - outConfig.attribute("ScrollPos", cfg.gui.mainDlg.cfgGridTopRowPos); - outConfig.attribute("SyncOverdue", cfg.gui.mainDlg.cfgGridSyncOverdueDays); - outConfig.attribute("SortByColumn", cfg.gui.mainDlg.cfgGridLastSortColumn); - outConfig.attribute("SortAscending", cfg.gui.mainDlg.cfgGridLastSortAscending); - - outConfig["Columns"](cfg.gui.mainDlg.cfgGridColumnAttribs); - outConfig["Configurations"].attribute("MaxSize", cfg.gui.mainDlg.cfgHistItemsMax); - outConfig["Configurations"](cfg.gui.mainDlg.cfgFileHistory); - { - std::vector cfgPaths = cfg.gui.mainDlg.lastUsedConfigFiles; - for (Zstring& filePath : cfgPaths) - filePath = substituteFreeFileSyncDriveLetter(filePath); - - outConfig["LastUsed"](cfgPaths); - } - - //########################################################### - - XmlOut outOverview = outWnd["OverviewPanel"]; - outOverview.attribute("ShowPercentage", cfg.gui.mainDlg.treeGridShowPercentBar); - outOverview.attribute("SortByColumn", cfg.gui.mainDlg.treeGridLastSortColumn); - outOverview.attribute("SortAscending", cfg.gui.mainDlg.treeGridLastSortAscending); - - //write column attributes - XmlOut outColTree = outOverview["Columns"]; - outColTree(cfg.gui.mainDlg.treeGridColumnAttribs); - - XmlOut outFileGrid = outWnd["FilePanel"]; - outFileGrid.attribute("ShowIcons", cfg.gui.mainDlg.showIcons); - outFileGrid.attribute("IconSize", cfg.gui.mainDlg.iconSize); - outFileGrid.attribute("SashOffset", cfg.gui.mainDlg.sashOffset); - outFileGrid.attribute("FolderPairsMax", cfg.gui.mainDlg.folderPairsVisibleMax); - - outFileGrid["ColumnsLeft"].attribute("PathFormat", cfg.gui.mainDlg.itemPathFormatLeftGrid); - outFileGrid["ColumnsLeft"](cfg.gui.mainDlg.columnAttribLeft); - - outFileGrid["FolderHistoryLeft" ](cfg.gui.mainDlg.folderHistoryLeft); - - outFileGrid["ColumnsRight"].attribute("PathFormat", cfg.gui.mainDlg.itemPathFormatRightGrid); - outFileGrid["ColumnsRight"](cfg.gui.mainDlg.columnAttribRight); - - outFileGrid["FolderHistoryRight"](cfg.gui.mainDlg.folderHistoryRight); - - //########################################################### - XmlOut outCopyTo = outWnd["ManualCopyTo"]; - outCopyTo.attribute("KeepRelativePaths", cfg.gui.mainDlg.copyToCfg.keepRelPaths); - outCopyTo.attribute("OverwriteIfExists", cfg.gui.mainDlg.copyToCfg.overwriteIfExists); - - XmlOut outCopyToHistory = outCopyTo["FolderHistory"]; - outCopyToHistory(cfg.gui.mainDlg.copyToCfg.folderHistory); - outCopyToHistory.attribute("LastUsedPath", cfg.gui.mainDlg.copyToCfg.lastUsedPath); - //########################################################### - - outWnd["DefaultViewFilter"](cfg.gui.mainDlg.viewFilterDefault); - outWnd["Perspective" ](cfg.gui.mainDlg.guiPerspectiveLast); - - outGui["DefaultExclusionFilter"](splitFilterByLines(cfg.gui.defaultExclusionFilter)); - - outGui["FolderHistory" ].attribute("MaxSize", cfg.gui.folderHistoryMax); - - outGui["VersioningFolderHistory"](cfg.gui.versioningFolderHistory); - outGui["LogFolderHistory" ](cfg.gui.logFolderHistory); - - outGui["EmailHistory"](cfg.gui.emailHistory); - outGui["EmailHistory"].attribute("MaxSize", cfg.gui.emailHistoryMax); - - outGui["CommandHistory"](cfg.gui.commandHistory); - outGui["CommandHistory"].attribute("MaxSize", cfg.gui.commandHistoryMax); - - //external applications - outGui["ExternalApps"](cfg.gui.externalApps); - - //last update check - outGui["LastOnlineCheck" ](cfg.gui.lastUpdateCheck); - outGui["LastOnlineVersion"](cfg.gui.lastOnlineVersion); - - //batch specific global settings - //XmlOut outBatch = out["Batch"]; -} - - -template -void writeConfig(const ConfigType& cfg, XmlType type, int xmlFormatVer, const Zstring& filePath) -{ - XmlDoc doc("FreeFileSync"); - setXmlType(doc, type); //throw() - - doc.root().setAttribute("XmlFormat", xmlFormatVer); - - XmlOut out(doc); - writeConfig(cfg, out); - - saveXml(doc, filePath); //throw FileError -} -} - -void fff::writeConfig(const XmlGuiConfig& cfg, const Zstring& filePath) -{ - ::writeConfig(cfg, XmlType::gui, XML_FORMAT_SYNC_CFG, filePath); //throw FileError -} - - -void fff::writeConfig(const XmlBatchConfig& cfg, const Zstring& filePath) -{ - ::writeConfig(cfg, XmlType::batch, XML_FORMAT_SYNC_CFG, filePath); //throw FileError -} - - -void fff::writeConfig(const XmlGlobalSettings& cfg, const Zstring& filePath) -{ - ::writeConfig(cfg, XmlType::global, XML_FORMAT_GLOBAL_CFG, filePath); //throw FileError -} - - -std::wstring fff::extractJobName(const Zstring& cfgFilePath) -{ - const Zstring fileName = afterLast(cfgFilePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); - const Zstring jobName = beforeLast(fileName, Zstr('.'), IF_MISSING_RETURN_ALL); - return utfTo(jobName); -} diff --git a/FreeFileSync/Source/base/config.h b/FreeFileSync/Source/base/config.h deleted file mode 100644 index dcafb207..00000000 --- a/FreeFileSync/Source/base/config.h +++ /dev/null @@ -1,287 +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 PROCESS_XML_H_28345825704254262435 -#define PROCESS_XML_H_28345825704254262435 - -#include -#include "localization.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" - - -namespace fff -{ -enum class XmlType -{ - gui, - batch, - global, - other -}; -XmlType getXmlType(const Zstring& filePath); //throw FileError - - -enum class BatchErrorHandling -{ - showPopup, - cancel -}; - - -enum class PostSyncAction -{ - none, - sleep, - shutdown -}; - -struct ExternalApp -{ - std::wstring description; - Zstring cmdLine; -}; - -//--------------------------------------------------------------------- -struct XmlGuiConfig -{ - MainConfiguration mainCfg; - bool highlightSyncAction = true; -}; - - -inline -bool operator==(const XmlGuiConfig& lhs, const XmlGuiConfig& rhs) -{ - return lhs.mainCfg == rhs.mainCfg && - lhs.highlightSyncAction == rhs.highlightSyncAction; -} -inline bool operator!=(const XmlGuiConfig& lhs, const XmlGuiConfig& rhs) { return !(lhs == rhs); } - - -struct BatchExclusiveConfig -{ - BatchErrorHandling batchErrorHandling = BatchErrorHandling::showPopup; - bool runMinimized = false; - bool autoCloseSummary = false; - PostSyncAction postSyncAction = PostSyncAction::none; -}; - - -struct XmlBatchConfig -{ - MainConfiguration mainCfg; - BatchExclusiveConfig batchExCfg; -}; - - -struct ConfirmationDialogs -{ - bool popupOnConfigChange = true; - bool confirmSyncStart = true; - bool confirmCommandMassInvoke = true; -}; -inline bool operator==(const ConfirmationDialogs& lhs, const ConfirmationDialogs& rhs) -{ - return lhs.popupOnConfigChange == rhs.popupOnConfigChange && - lhs.confirmSyncStart == rhs.confirmSyncStart && - lhs.confirmCommandMassInvoke == rhs.confirmCommandMassInvoke; -} -inline bool operator!=(const ConfirmationDialogs& lhs, const ConfirmationDialogs& rhs) { return !(lhs == rhs); } - - -struct WarningDialogs -{ - bool warnFolderNotExisting = true; - bool warnFoldersDifferInCase = true; - bool warnDependentFolderPair = true; - bool warnDependentBaseFolders = true; - bool warnSignificantDifference = true; - bool warnNotEnoughDiskSpace = true; - bool warnUnresolvedConflicts = true; - bool warnModificationTimeError = true; - bool warnRecyclerMissing = true; - bool warnInputFieldEmpty = true; - bool warnDirectoryLockFailed = true; - bool warnVersioningFolderPartOfSync = true; -}; -inline bool operator==(const WarningDialogs& lhs, const WarningDialogs& rhs) -{ - return lhs.warnFolderNotExisting == rhs.warnFolderNotExisting && - lhs.warnFoldersDifferInCase == rhs.warnFoldersDifferInCase && - lhs.warnDependentFolderPair == rhs.warnDependentFolderPair && - lhs.warnDependentBaseFolders == rhs.warnDependentBaseFolders && - lhs.warnSignificantDifference == rhs.warnSignificantDifference && - lhs.warnNotEnoughDiskSpace == rhs.warnNotEnoughDiskSpace && - lhs.warnUnresolvedConflicts == rhs.warnUnresolvedConflicts && - lhs.warnModificationTimeError == rhs.warnModificationTimeError && - lhs.warnRecyclerMissing == rhs.warnRecyclerMissing && - lhs.warnInputFieldEmpty == rhs.warnInputFieldEmpty && - lhs.warnDirectoryLockFailed == rhs.warnDirectoryLockFailed && - lhs.warnVersioningFolderPartOfSync == rhs.warnVersioningFolderPartOfSync; -} -inline bool operator!=(const WarningDialogs& lhs, const WarningDialogs& rhs) { return !(lhs == rhs); } - - - -enum class FileIconSize -{ - SMALL, - MEDIUM, - LARGE -}; - - -struct ViewFilterDefault -{ - //shared - bool equal = false; - bool conflict = true; - bool excluded = false; - //category view - bool leftOnly = true; - bool rightOnly = true; - bool leftNewer = true; - bool rightNewer = true; - bool different = true; - //action view - bool createLeft = true; - bool createRight = true; - bool updateLeft = true; - bool updateRight = true; - bool deleteLeft = true; - bool deleteRight = true; - bool doNothing = true; -}; - - -Zstring getGlobalConfigFile(); - - -struct XmlGlobalSettings -{ - XmlGlobalSettings(); //clang needs this anyway - - //--------------------------------------------------------------------- - //Shared (GUI/BATCH) settings - wxLanguage programLanguage = getSystemLanguage(); - bool failSafeFileCopy = true; - bool copyLockedFiles = false; //safer default: avoid copies of partially written files - bool copyFilePermissions = false; - - int fileTimeTolerance = 2; //max. allowed file time deviation; < 0 means unlimited tolerance; default 2s: FAT vs NTFS - bool runWithBackgroundPriority = false; - bool createLockFile = true; - bool verifyFileCopy = false; - int logfilesMaxAgeDays = 30; //<= 0 := no limit; for log files under %AppData%\FreeFileSync\Logs - - Zstring soundFileCompareFinished; - Zstring soundFileSyncFinished; - - bool autoCloseProgressDialog = false; - ConfirmationDialogs confirmDlgs; - WarningDialogs warnDlgs; - - //--------------------------------------------------------------------- - struct Gui - { - Gui() {} //clang needs this anyway - struct - { - wxPoint dlgPos; - wxSize dlgSize; - bool isMaximized = false; - - bool textSearchRespectCase = false; //good default for Linux, too! - int folderPairsVisibleMax = 6; - - size_t cfgGridTopRowPos = 0; - int cfgGridSyncOverdueDays = 7; - ColumnTypeCfg cfgGridLastSortColumn = cfgGridLastSortColumnDefault; - bool cfgGridLastSortAscending = getDefaultSortDirection(cfgGridLastSortColumnDefault); - std::vector cfgGridColumnAttribs = getCfgGridDefaultColAttribs(); - size_t cfgHistItemsMax = 100; - std::vector cfgFileHistory; - std::vector lastUsedConfigFiles; - - bool treeGridShowPercentBar = treeGridShowPercentageDefault; - ColumnTypeTree treeGridLastSortColumn = treeGridLastSortColumnDefault; //remember sort on overview panel - bool treeGridLastSortAscending = getDefaultSortDirection(treeGridLastSortColumnDefault); // - std::vector treeGridColumnAttribs = getTreeGridDefaultColAttribs(); - - struct - { - bool keepRelPaths = false; - bool overwriteIfExists = false; - Zstring lastUsedPath; - std::vector folderHistory; - } copyToCfg; - - std::vector folderHistoryLeft; - std::vector folderHistoryRight; - bool showIcons = true; - FileIconSize iconSize = FileIconSize::SMALL; - int sashOffset = 0; - - ItemPathFormat itemPathFormatLeftGrid = defaultItemPathFormatLeftGrid; - ItemPathFormat itemPathFormatRightGrid = defaultItemPathFormatRightGrid; - - std::vector columnAttribLeft = getFileGridDefaultColAttribsLeft(); - std::vector columnAttribRight = getFileGridDefaultColAttribsRight(); - - ViewFilterDefault viewFilterDefault; - wxString guiPerspectiveLast; //used by wxAuiManager - } mainDlg; - - Zstring defaultExclusionFilter = Zstr("/.Trash-*/") Zstr("\n") - Zstr("/.recycle/"); - size_t folderHistoryMax = 20; - - std::vector versioningFolderHistory; - std::vector logFolderHistory; - - std::vector emailHistory; - size_t emailHistoryMax = 10; - - std::vector commandHistory; - size_t commandHistoryMax = 10; - - std::vector externalApps - { - //default external app descriptions will be translated "on the fly"!!! - //CONTRACT: first entry will be used for [Enter] or mouse double-click! - { L"Browse directory", Zstr("xdg-open \"%parent_path%\"") }, - { L"Open with default application", Zstr("xdg-open \"%local_path%\"") }, - //mark for extraction: _("Browse directory") Linux doesn't use the term "folder" - }; - - time_t lastUpdateCheck = 0; //number of seconds since 00:00 hours, Jan 1, 1970 UTC - std::string lastOnlineVersion; - } gui; -}; - -//read/write specific config types -void readConfig(const Zstring& filePath, XmlGuiConfig& cfg, std::wstring& warningMsg); // -void readConfig(const Zstring& filePath, XmlBatchConfig& cfg, std::wstring& warningMsg); //throw FileError -void readConfig(const Zstring& filePath, XmlGlobalSettings& cfg, std::wstring& warningMsg); // - -void writeConfig(const XmlGuiConfig& cfg, const Zstring& filePath); // -void writeConfig(const XmlBatchConfig& cfg, const Zstring& filePath); //throw FileError -void writeConfig(const XmlGlobalSettings& cfg, const Zstring& filePath); // - -//convert (multiple) *.ffs_gui, *.ffs_batch files or combinations of both into target config structure: -void readAnyConfig(const std::vector& filePaths, XmlGuiConfig& cfg, std::wstring& warningMsg); //throw FileError - -//config conversion utilities -XmlGuiConfig convertBatchToGui(const XmlBatchConfig& batchCfg); //noexcept -XmlBatchConfig convertGuiToBatch(const XmlGuiConfig& guiCfg, const BatchExclusiveConfig& batchExCfg); // - -std::wstring extractJobName(const Zstring& cfgFilePath); -} - -#endif //PROCESS_XML_H_28345825704254262435 diff --git a/FreeFileSync/Source/base/db_file.cpp b/FreeFileSync/Source/base/db_file.cpp index 94a6afc4..23e42776 100644 --- a/FreeFileSync/Source/base/db_file.cpp +++ b/FreeFileSync/Source/base/db_file.cpp @@ -30,7 +30,7 @@ DEFINE_NEW_FILE_ERROR(FileErrorDatabaseNotExisting) struct SessionData { bool isLeadStream = false; - ByteArray rawStream; + std::string rawStream; }; bool operator==(const SessionData& lhs, const SessionData& rhs) { return lhs.isLeadStream == rhs.isLeadStream && lhs.rawStream == rhs.rawStream; } @@ -77,8 +77,8 @@ void saveStreams(const DbStreams& streamList, const AbstractPath& dbPath, const { writeContainer(memStreamOut, sessionID); - writeNumber (memStreamOut, sessionData.isLeadStream); - writeContainer(memStreamOut, sessionData.rawStream); + writeNumber(memStreamOut, sessionData.isLeadStream); + writeContainer (memStreamOut, sessionData.rawStream); } writeNumber(memStreamOut, getCrc32(memStreamOut.ref())); @@ -139,10 +139,10 @@ DbStreams loadStreams(const AbstractPath& dbPath, const IOCallback& notifyUnbuff { assert(byteStream.size() >= sizeof(uint32_t)); //obviously in this context! MemoryStreamOut crcStreamOut; - writeNumber(crcStreamOut, getCrc32({ byteStream.begin(), byteStream.end() - sizeof(uint32_t) })); + writeNumber(crcStreamOut, getCrc32(byteStream.begin(), byteStream.end() - sizeof(uint32_t))); if (!endsWith(byteStream, crcStreamOut.ref())) - throw FileError(_("Database file is corrupted:") + L" " + fmtPath(AFS::getDisplayPath(dbPath)), L"Invalid checksum."); + throw FileError(_("Database file is corrupted:") + L' ' + fmtPath(AFS::getDisplayPath(dbPath)), L"Invalid checksum."); } DbStreams output; @@ -157,7 +157,7 @@ DbStreams loadStreams(const AbstractPath& dbPath, const IOCallback& notifyUnbuff if (version == 9) //TODO: remove migration code at some time! v9 used until 2017-02-01 { - sessionData.rawStream = readContainer(memStreamIn); //throw UnexpectedEndOfStreamError + sessionData.rawStream = readContainer(memStreamIn); //throw UnexpectedEndOfStreamError MemoryStreamIn streamIn(sessionData.rawStream); const int streamVersion = readNumber(streamIn); //throw UnexpectedEndOfStreamError @@ -167,8 +167,8 @@ DbStreams loadStreams(const AbstractPath& dbPath, const IOCallback& notifyUnbuff } else { - sessionData.isLeadStream = readNumber (memStreamIn) != 0; //throw UnexpectedEndOfStreamError - sessionData.rawStream = readContainer(memStreamIn); // + sessionData.isLeadStream = readNumber (memStreamIn) != 0; //throw UnexpectedEndOfStreamError + sessionData.rawStream = readContainer(memStreamIn); // } output[sessionID] = std::move(sessionData); @@ -177,7 +177,7 @@ DbStreams loadStreams(const AbstractPath& dbPath, const IOCallback& notifyUnbuff } catch (UnexpectedEndOfStreamError&) { - throw FileError(_("Database file is corrupted:") + L" " + fmtPath(AFS::getDisplayPath(dbPath)), L"Unexpected end of stream."); + throw FileError(_("Database file is corrupted:") + L' ' + fmtPath(AFS::getDisplayPath(dbPath)), L"Unexpected end of stream."); } } @@ -189,16 +189,16 @@ public: static void execute(const InSyncFolder& dbFolder, //throw FileError const std::wstring& displayFilePathL, //used for diagnostics only const std::wstring& displayFilePathR, - ByteArray& streamL, - ByteArray& streamR) + std::string& streamL, + std::string& streamR) { - MemoryStreamOut outL; - MemoryStreamOut outR; + MemoryStreamOut outL; + MemoryStreamOut outR; //save format version writeNumber(outL, DB_STREAM_VERSION); writeNumber(outR, DB_STREAM_VERSION); - auto compStream = [&](const ByteArray& stream) -> ByteArray //throw FileError + auto compStream = [&](const std::string& stream) //throw FileError { try { @@ -227,16 +227,16 @@ public: generator.recurse(dbFolder); //PERF_STOP - const ByteArray bufText = compStream(generator.streamOutText_ .ref()); - const ByteArray bufSmallNum = compStream(generator.streamOutSmallNum_.ref()); - const ByteArray bufBigNum = compStream(generator.streamOutBigNum_ .ref()); + const std::string bufText = compStream(generator.streamOutText_ .ref()); + const std::string bufSmallNum = compStream(generator.streamOutSmallNum_.ref()); + const std::string bufBigNum = compStream(generator.streamOutBigNum_ .ref()); - MemoryStreamOut streamOut; + MemoryStreamOut streamOut; writeContainer(streamOut, bufText); writeContainer(streamOut, bufSmallNum); writeContainer(streamOut, bufBigNum); - const ByteArray& buf = streamOut.ref(); + const std::string& buf = streamOut.ref(); //distribute "outputBoth" over left and right streams: const size_t size1stPart = buf.size() / 2; @@ -245,11 +245,11 @@ public: writeNumber(outL, size1stPart); writeNumber(outR, size2ndPart); - if (size1stPart > 0) writeArray(outL, &*buf.begin(), size1stPart); - if (size2ndPart > 0) writeArray(outR, &*buf.begin() + size1stPart, size2ndPart); + if (size1stPart > 0) writeArray(outL, &buf[0], size1stPart); + if (size2ndPart > 0) writeArray(outR, &buf[0] + size1stPart, size2ndPart); - streamL = outL.ref(); - streamR = outR.ref(); + streamL = std::move(outL.ref()); + streamR = std::move(outR.ref()); } private: @@ -286,25 +286,25 @@ private: } } - static void writeUtf8(MemoryStreamOut& streamOut, const Zstring& str) { writeContainer(streamOut, utfTo>(str)); } + static void writeUtf8(MemoryStreamOut& streamOut, const Zstring& str) { writeContainer(streamOut, utfTo(str)); } - static void writeFileDescr(MemoryStreamOut& streamOut, const InSyncDescrFile& descr) + static void writeFileDescr(MemoryStreamOut& streamOut, const InSyncDescrFile& descr) { writeNumber(streamOut, descr.modTime); writeContainer(streamOut, descr.fileId); - static_assert(std::is_same_v>); + static_assert(std::is_same_v); } - static void writeLinkDescr(MemoryStreamOut& streamOut, const InSyncDescrLink& descr) + static void writeLinkDescr(MemoryStreamOut& streamOut, const InSyncDescrLink& descr) { writeNumber(streamOut, descr.modTime); } //maximize zlib compression by grouping similar data (=> 20% size reduction!) // -> further ~5% reduction possible by having one container per data type - MemoryStreamOut streamOutText_; // - MemoryStreamOut streamOutSmallNum_; //data with bias to lead side (= always left in this context) - MemoryStreamOut streamOutBigNum_; // + MemoryStreamOut streamOutText_; // + MemoryStreamOut streamOutSmallNum_; //data with bias to lead side (= always left in this context) + MemoryStreamOut streamOutBigNum_; // }; @@ -312,12 +312,12 @@ class StreamParser { public: static SharedRef execute(bool leadStreamLeft, //throw FileError - const ByteArray& streamL, - const ByteArray& streamR, + const std::string& streamL, + const std::string& streamR, const std::wstring& displayFilePathL, //for diagnostics only const std::wstring& displayFilePathR) { - auto decompStream = [&](const ByteArray& stream) -> ByteArray //throw FileError + auto decompStream = [&](const std::string& stream) //throw FileError { try { @@ -338,7 +338,7 @@ public: const int streamVersionR = readNumber(streamInR); // if (streamVersion != streamVersionR) - throw FileError(_("Database file is corrupted:") + L"\n" + fmtPath(displayFilePathL) + L"\n" + fmtPath(displayFilePathR), L"Different stream formats"); + throw FileError(_("Database file is corrupted:") + L'\n' + fmtPath(displayFilePathL) + L'\n' + fmtPath(displayFilePathR), L"Different stream formats"); //TODO: remove migration code at some time! 2017-02-01 if (streamVersion != 2 && @@ -352,23 +352,22 @@ public: const bool has1stPartR = readNumber(streamInR) != 0; // if (has1stPartL == has1stPartR) - throw FileError(_("Database file is corrupted:") + L"\n" + fmtPath(displayFilePathL) + L"\n" + fmtPath(displayFilePathR), L"Second stream part missing"); + throw FileError(_("Database file is corrupted:") + L'\n' + fmtPath(displayFilePathL) + L'\n' + fmtPath(displayFilePathR), L"Second stream part missing"); if (has1stPartL != leadStreamLeft) - throw FileError(_("Database file is corrupted:") + L"\n" + fmtPath(displayFilePathL) + L"\n" + fmtPath(displayFilePathR), L"has1stPartL != leadStreamLeft"); + throw FileError(_("Database file is corrupted:") + L'\n' + fmtPath(displayFilePathL) + L'\n' + fmtPath(displayFilePathR), L"has1stPartL != leadStreamLeft"); - MemoryStreamIn& in1stPart = leadStreamLeft ? streamInL : streamInR; - MemoryStreamIn& in2ndPart = leadStreamLeft ? streamInR : streamInL; + MemoryStreamIn& in1stPart = leadStreamLeft ? streamInL : streamInR; + MemoryStreamIn& in2ndPart = leadStreamLeft ? streamInR : streamInL; const size_t size1stPart = static_cast(readNumber(in1stPart)); const size_t size2ndPart = static_cast(readNumber(in2ndPart)); - ByteArray tmpB; - tmpB.resize(size1stPart + size2ndPart); //throw std::bad_alloc - readArray(in1stPart, &*tmpB.begin(), size1stPart); //stream always non-empty - readArray(in2ndPart, &*tmpB.begin() + size1stPart, size2ndPart); //throw UnexpectedEndOfStreamError + std::string tmpB(size1stPart + size2ndPart, '\0'); //throw std::bad_alloc + readArray(in1stPart, &tmpB[0], size1stPart); //stream always non-empty + readArray(in2ndPart, &tmpB[0] + size1stPart, size2ndPart); //throw UnexpectedEndOfStreamError - const ByteArray tmpL = readContainer(streamInL); - const ByteArray tmpR = readContainer(streamInR); + const std::string tmpL = readContainer(streamInL); + const std::string tmpR = readContainer(streamInR); auto output = makeSharedRef(InSyncFolder::DIR_STATUS_IN_SYNC); StreamParserV2 parser(decompStream(tmpL), @@ -379,22 +378,20 @@ public: } else { - MemoryStreamIn& streamInPart1 = leadStreamLeft ? streamInL : streamInR; - MemoryStreamIn& streamInPart2 = leadStreamLeft ? streamInR : streamInL; + MemoryStreamIn& streamInPart1 = leadStreamLeft ? streamInL : streamInR; + MemoryStreamIn& streamInPart2 = leadStreamLeft ? streamInR : streamInL; const size_t sizePart1 = static_cast(readNumber(streamInPart1)); const size_t sizePart2 = static_cast(readNumber(streamInPart2)); - ByteArray buf; - buf.resize(sizePart1 + sizePart2); //throw std::bad_alloc - - if (sizePart1 > 0) readArray(streamInPart1, &*buf.begin(), sizePart1); //throw UnexpectedEndOfStreamError - if (sizePart2 > 0) readArray(streamInPart2, &*buf.begin() + sizePart1, sizePart2); // + std::string buf(sizePart1 + sizePart2, '\0'); + if (sizePart1 > 0) readArray(streamInPart1, &buf[0], sizePart1); //throw UnexpectedEndOfStreamError + if (sizePart2 > 0) readArray(streamInPart2, &buf[0] + sizePart1, sizePart2); // MemoryStreamIn streamIn(buf); - const ByteArray bufText = readContainer(streamIn); // - const ByteArray bufSmallNum = readContainer(streamIn); //throw UnexpectedEndOfStreamError - const ByteArray bufBigNum = readContainer(streamIn); // + const std::string bufText = readContainer(streamIn); // + const std::string bufSmallNum = readContainer(streamIn); //throw UnexpectedEndOfStreamError + const std::string bufBigNum = readContainer(streamIn); // auto output = makeSharedRef(InSyncFolder::DIR_STATUS_IN_SYNC); StreamParser parser(streamVersion, @@ -410,12 +407,12 @@ public: } catch (UnexpectedEndOfStreamError&) { - throw FileError(_("Database file is corrupted:") + L"\n" + fmtPath(displayFilePathL) + L"\n" + fmtPath(displayFilePathR), L"Unexpected end of stream."); + throw FileError(_("Database file is corrupted:") + L'\n' + fmtPath(displayFilePathL) + L'\n' + fmtPath(displayFilePathR), L"Unexpected end of stream."); } } private: - StreamParser(int streamVersion, const ByteArray& bufText, const ByteArray& bufSmallNumbers, const ByteArray& bufBigNumbers) : + StreamParser(int streamVersion, const std::string& bufText, const std::string& bufSmallNumbers, const std::string& bufBigNumbers) : streamVersion_(streamVersion), streamInText_(bufText), streamInSmallNum_(bufSmallNumbers), @@ -467,20 +464,20 @@ private: } } - static Zstring readUtf8(MemoryStreamIn& streamIn) { return utfTo(readContainer>(streamIn)); } //throw UnexpectedEndOfStreamError + static Zstring readUtf8(MemoryStreamIn& streamIn) { return utfTo(readContainer(streamIn)); } //throw UnexpectedEndOfStreamError //optional: use null-termination: 5% overall size reduction //optional: split into streamInText_/streamInSmallNum_: overall size increase! (why?) - static InSyncDescrFile readFileDescr(MemoryStreamIn& streamIn) //throw UnexpectedEndOfStreamError + static InSyncDescrFile readFileDescr(MemoryStreamIn& streamIn) //throw UnexpectedEndOfStreamError { //attention: order of function argument evaluation is undefined! So do it one after the other... const auto modTime = readNumber(streamIn); //throw UnexpectedEndOfStreamError - const AFS::FileId fileId = readContainer>(streamIn); + const auto fileId = readContainer(streamIn); return InSyncDescrFile(modTime, fileId); } - static InSyncDescrLink readLinkDescr(MemoryStreamIn& streamIn) //throw UnexpectedEndOfStreamError + static InSyncDescrLink readLinkDescr(MemoryStreamIn& streamIn) //throw UnexpectedEndOfStreamError { const auto modTime = readNumber(streamIn); return InSyncDescrLink(modTime); @@ -490,9 +487,9 @@ private: class StreamParserV2 { public: - StreamParserV2(const ByteArray& bufferL, - const ByteArray& bufferR, - const ByteArray& bufferB) : + StreamParserV2(const std::string& bufferL, + const std::string& bufferR, + const std::string& bufferB) : inputLeft_ (bufferL), inputRight_(bufferR), inputBoth_ (bufferB) {} @@ -532,15 +529,15 @@ private: } private: - MemoryStreamIn inputLeft_; //data related to one side only - MemoryStreamIn inputRight_; // - MemoryStreamIn inputBoth_; //data concerning both sides + MemoryStreamIn inputLeft_; //data related to one side only + MemoryStreamIn inputRight_; // + MemoryStreamIn inputBoth_; //data concerning both sides }; const int streamVersion_; - MemoryStreamIn streamInText_; // - MemoryStreamIn streamInSmallNum_; //data with bias to lead side - MemoryStreamIn streamInBigNum_; // + MemoryStreamIn streamInText_; // + MemoryStreamIn streamInSmallNum_; //data with bias to lead side + MemoryStreamIn streamInBigNum_; // }; //####################################################################################################################################### @@ -757,7 +754,7 @@ std::pairsecond.isLeadStream != itR->second.isLeadStream) { if (itCommonL != streamsLeft.end()) //should not be possible! - throw FileError(_("Database file is corrupted:") + L"\n" + fmtPath(displayFilePathL) + L"\n" + fmtPath(displayFilePathR), + throw FileError(_("Database file is corrupted:") + L'\n' + fmtPath(displayFilePathL) + L'\n' + fmtPath(displayFilePathR), L"Multiple common sessions found."); itCommonL = itL; itCommonR = itR; diff --git a/FreeFileSync/Source/base/dir_exist_async.h b/FreeFileSync/Source/base/dir_exist_async.h index 74572919..ff431902 100644 --- a/FreeFileSync/Source/base/dir_exist_async.h +++ b/FreeFileSync/Source/base/dir_exist_async.h @@ -103,7 +103,7 @@ FolderStatus getFolderStatusNonBlocking(const std::set& folderPath if (!isReady(future)) output.failedChecks.emplace(folderPath, FileError(replaceCpy(_("Timeout while searching for folder %x."), L"%x", displayPathFmt) + - L" [" + _P("1 sec", "%x sec", deviceTimeOutSec) + L"]")); + L" [" + _P("1 sec", "%x sec", deviceTimeOutSec) + L']')); else try { diff --git a/FreeFileSync/Source/base/dir_lock.cpp b/FreeFileSync/Source/base/dir_lock.cpp index ab76158f..5ba444c7 100644 --- a/FreeFileSync/Source/base/dir_lock.cpp +++ b/FreeFileSync/Source/base/dir_lock.cpp @@ -149,7 +149,7 @@ LockInformation getLockInfoFromCurrentProcess() //throw FileError std::vector buffer(10000); if (::gethostname(&buffer[0], buffer.size()) != 0) THROW_LAST_FILE_ERROR(_("Cannot get process information."), L"gethostname"); - lockInfo.computerName = osName + " " + &buffer[0] + "."; + lockInfo.computerName = osName + ' ' + &buffer[0] + '.'; if (::getdomainname(&buffer[0], buffer.size()) != 0) THROW_LAST_FILE_ERROR(_("Cannot get process information."), L"getdomainname"); @@ -182,7 +182,7 @@ std::string serialize(const LockInformation& lockInfo) writeNumber(streamOut, lockInfo.processId); writeNumber(streamOut, getCrc32(streamOut.ref())); - writeArray(streamOut, "x", 1); //sentinel: mark logical end with a non-whitespace character + writeArray(streamOut, "x", 1); //sentinel: mark logical end with a non-space character return streamOut.ref(); } @@ -206,12 +206,14 @@ LockInformation unserialize(const std::string& byteStream) //throw UnexpectedEnd ; else //catch data corruption ASAP + don't rely on std::bad_alloc for consistency checking { - std::string byteStreamTrm = trimCpy(byteStream, false, true); //get rid of space chars - assert(byteStreamTrm.size() >= sizeof(uint32_t) + sizeof('x')); //obviously in this context! - byteStreamTrm.pop_back(); + const size_t posEnd = byteStream.rfind('x'); //skip blanks (+ unrelated corrupted data e.g. nulls!) + if (posEnd == std::string::npos) + throw UnexpectedEndOfStreamError(); //well, not really...!? + + const std::string_view byteStreamTrm = makeStringView(byteStream.begin(), posEnd); MemoryStreamOut crcStreamOut; - writeNumber(crcStreamOut, getCrc32({ byteStreamTrm.begin(), byteStreamTrm.end() - sizeof(uint32_t) })); + writeNumber(crcStreamOut, getCrc32(byteStreamTrm.begin(), byteStreamTrm.end() - sizeof(uint32_t))); if (!endsWith(byteStreamTrm, crcStreamOut.ref())) throw UnexpectedEndOfStreamError(); //well, not really...!? diff --git a/FreeFileSync/Source/base/fatal_error.h b/FreeFileSync/Source/base/fatal_error.h deleted file mode 100644 index a27e423b..00000000 --- a/FreeFileSync/Source/base/fatal_error.h +++ /dev/null @@ -1,45 +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 ERROR_LOG_H_89734181783491324134 -#define ERROR_LOG_H_89734181783491324134 - -#include -#include -#include -#include "ffs_paths.h" - - -namespace fff -{ -//write error message to a file (even with corrupted stack)- call in desperate situations when no other means of error handling is available -void logFatalError(const std::string& msg); //noexcept - - - - - - - - - -//##################### implementation ############################ -inline -void logFatalError(const std::string& msg) //noexcept -{ - using namespace zen; - - assert(false); //this is stuff we like to debug - const std::string logEntry = "[" + formatTime(FORMAT_DATE) + " " + formatTime(FORMAT_TIME) + "] " + msg; - try - { - saveBinContainer(getConfigDirPathPf() + Zstr("LastError.log"), logEntry, nullptr /*notifyUnbufferedIO*/); //throw FileError - } - catch (FileError&) {} -} -} - -#endif //ERROR_LOG_H_89734181783491324134 diff --git a/FreeFileSync/Source/base/ffs_paths.cpp b/FreeFileSync/Source/base/ffs_paths.cpp deleted file mode 100644 index 843e702e..00000000 --- a/FreeFileSync/Source/base/ffs_paths.cpp +++ /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 * -// ***************************************************************************** - -#include "ffs_paths.h" -#include -#include -#include -#include -#include - - -using namespace zen; - - -namespace -{ -Zstring getProcessParentFolderPath() -{ - //buffer getSymlinkResolvedPath()! - //note: compiler generates magic-statics code => fine, we don't expect accesses during shutdown => don't need FunStatGlobal<> - static const Zstring exeFolderParentPath = [] - { - Zstring exeFolderPath = beforeLast(utfTo(wxStandardPaths::Get().GetExecutablePath()), FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE); - try - { - //get rid of relative path fragments, e.g.: C:\Data\Projects\FreeFileSync\Source\..\Build\Bin - exeFolderPath = getSymlinkResolvedPath(exeFolderPath); //throw FileError - } - catch (FileError&) { assert(false); } - - return beforeLast(exeFolderPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE); - }(); - return exeFolderParentPath; -} -} - - - - -namespace -{ -//don't make this a function-scope static (avoid code-gen for "magic static") -//getFfsVolumeId() might be called during static destruction, e.g. async update check -std::once_flag onceFlagGetFfsVolumeId; -} - -VolumeId fff::getFfsVolumeId() //throw FileError -{ - static VolumeId volumeId; //POD => no "magic static" code gen - std::call_once(onceFlagGetFfsVolumeId, [] { volumeId = getVolumeId(getProcessParentFolderPath()); }); //throw FileError - return volumeId; -} - - -bool fff::isPortableVersion() -{ - return false; //users want local installation type: https://freefilesync.org/forum/viewtopic.php?t=5750 - -} - - -Zstring fff::getResourceDirPf() -{ - return getProcessParentFolderPath() + FILE_NAME_SEPARATOR + Zstr("Resources") + FILE_NAME_SEPARATOR; -} - - -Zstring fff::getConfigDirPathPf() -{ - //note: compiler generates magic-statics code => fine, we don't expect accesses during shutdown - static const Zstring cfgFolderPathPf = [] - { - //make independent from wxWidgets global variable "appname"; support being called by RealTimeSync - auto appName = wxTheApp->GetAppName(); - wxTheApp->SetAppName(L"FreeFileSync"); - ZEN_ON_SCOPE_EXIT(wxTheApp->SetAppName(appName)); - - //OS standard path (XDG layout): ~/.config/FreeFileSync - //wxBug: wxStandardPaths::GetUserDataDir() does not honor FileLayout_XDG flag - wxStandardPaths::Get().SetFileLayout(wxStandardPaths::FileLayout_XDG); - const Zstring cfgFolderPath = appendSeparator(utfTo(wxStandardPaths::Get().GetUserConfigDir())) + "FreeFileSync"; - - try //create the config folder if not existing + create "Logs" subfolder while we're at it - { - createDirectoryIfMissingRecursion(appendSeparator(cfgFolderPath) + Zstr("Logs")); //throw FileError - } - catch (FileError&) { assert(false); } - - return appendSeparator(cfgFolderPath); - }(); - return cfgFolderPathPf; -} - - -//this function is called by RealTimeSync!!! -Zstring fff::getFreeFileSyncLauncherPath() -{ - return getProcessParentFolderPath() + Zstr("/FreeFileSync"); - -} diff --git a/FreeFileSync/Source/base/ffs_paths.h b/FreeFileSync/Source/base/ffs_paths.h deleted file mode 100644 index bf103e28..00000000 --- a/FreeFileSync/Source/base/ffs_paths.h +++ /dev/null @@ -1,31 +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 FFS_PATHS_H_842759083425342534253 -#define FFS_PATHS_H_842759083425342534253 - -#include -#include - - -namespace fff -{ -//------------------------------------------------------------------------------ -//global program directories -//------------------------------------------------------------------------------ -Zstring getResourceDirPf (); //resource directory WITH trailing path separator -Zstring getConfigDirPathPf(); // config directory WITH trailing path separator -//------------------------------------------------------------------------------ - -bool isPortableVersion(); - - -zen::VolumeId getFfsVolumeId(); //throw FileError - -Zstring getFreeFileSyncLauncherPath(); //full path to application launcher C:\...\FreeFileSync.exe -} - -#endif //FFS_PATHS_H_842759083425342534253 diff --git a/FreeFileSync/Source/base/file_hierarchy.cpp b/FreeFileSync/Source/base/file_hierarchy.cpp index 1f10a793..debaa031 100644 --- a/FreeFileSync/Source/base/file_hierarchy.cpp +++ b/FreeFileSync/Source/base/file_hierarchy.cpp @@ -359,7 +359,7 @@ const wchar_t arrowRight[] = L"->"; std::wstring fff::getCategoryDescription(const FileSystemObject& fsObj) { - const std::wstring footer = L"\n[" + utfTo(fsObj. getItemNameAny()) + L"]"; + const std::wstring footer = L"\n[" + utfTo(fsObj. getItemNameAny()) + L']'; const CompareFileResult cmpRes = fsObj.getCategory(); switch (cmpRes) @@ -379,21 +379,21 @@ std::wstring fff::getCategoryDescription(const FileSystemObject& fsObj) [&](const FilePair& file) { descr += std::wstring(L"\n") + - arrowLeft + L" " + formatUtcToLocalTime(file.getLastWriteTime< LEFT_SIDE>()) + L"\n" + - arrowRight + L" " + formatUtcToLocalTime(file.getLastWriteTime()); + arrowLeft + L' ' + formatUtcToLocalTime(file.getLastWriteTime< LEFT_SIDE>()) + L'\n' + + arrowRight + L' ' + formatUtcToLocalTime(file.getLastWriteTime()); }, [&](const SymlinkPair& symlink) { descr += std::wstring(L"\n") + - arrowLeft + L" " + formatUtcToLocalTime(symlink.getLastWriteTime< LEFT_SIDE>()) + L"\n" + - arrowRight + L" " + formatUtcToLocalTime(symlink.getLastWriteTime()); + arrowLeft + L' ' + formatUtcToLocalTime(symlink.getLastWriteTime< LEFT_SIDE>()) + L'\n' + + arrowRight + L' ' + formatUtcToLocalTime(symlink.getLastWriteTime()); }); return descr + footer; } case FILE_DIFFERENT_METADATA: case FILE_CONFLICT: - return fsObj.getCatExtraDescription() + footer; + return utfTo(fsObj.getCatExtraDescription()) + footer; } assert(false); return std::wstring(); @@ -440,7 +440,7 @@ std::wstring fff::getSyncOpDescription(SyncOperation op) std::wstring fff::getSyncOpDescription(const FileSystemObject& fsObj) { - const std::wstring footer = L"\n[" + utfTo(fsObj. getItemNameAny()) + L"]"; + const std::wstring footer = L"\n[" + utfTo(fsObj. getItemNameAny()) + L']'; const SyncOperation op = fsObj.getSyncOperation(); switch (op) @@ -466,8 +466,8 @@ std::wstring fff::getSyncOpDescription(const FileSystemObject& fsObj) if (getUnicodeNormalForm(itemNameOld) != getUnicodeNormalForm(itemNameNew)) //detected change in case - return getSyncOpDescription(op) + L"\n" + - fmtPath(itemNameOld) + L" " + arrowRight + L"\n" + //show short name only + return getSyncOpDescription(op) + L'\n' + + fmtPath(itemNameOld) + L' ' + arrowRight + L'\n' + //show short name only fmtPath(itemNameNew) /*+ footer -> redundant */; } return getSyncOpDescription(op) + footer; //fallback @@ -492,14 +492,14 @@ std::wstring fff::getSyncOpDescription(const FileSystemObject& fsObj) const Zstring relPathTo = getRelName(*fileTo, onLeft); //attention: ::SetWindowText() doesn't handle tab characters correctly in combination with certain file names, so don't use them - return getSyncOpDescription(op) + L"\n" + + return getSyncOpDescription(op) + L'\n' + (beforeLast(relPathFrom, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE) == beforeLast(relPathTo, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE) ? //detected pure "rename" - fmtPath(afterLast(relPathFrom, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL)) + L" " + arrowRight + L"\n" + //show short name only + fmtPath(afterLast(relPathFrom, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL)) + L' ' + arrowRight + L'\n' + //show short name only fmtPath(afterLast(relPathTo, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL)) : //"move" or "move + rename" - fmtPath(relPathFrom) + L" " + arrowRight + L"\n" + + fmtPath(relPathFrom) + L' ' + arrowRight + L'\n' + fmtPath(relPathTo)) /*+ footer -> redundant */; } break; diff --git a/FreeFileSync/Source/base/file_hierarchy.h b/FreeFileSync/Source/base/file_hierarchy.h index c717e41f..862f5169 100644 --- a/FreeFileSync/Source/base/file_hierarchy.h +++ b/FreeFileSync/Source/base/file_hierarchy.h @@ -425,12 +425,12 @@ public: //comparison result CompareFileResult getCategory() const { return cmpResult_; } - std::wstring getCatExtraDescription() const; //only filled if getCategory() == FILE_CONFLICT or FILE_DIFFERENT_METADATA + Zstringc getCatExtraDescription() const; //only filled if getCategory() == FILE_CONFLICT or FILE_DIFFERENT_METADATA //sync settings SyncDirection getSyncDir() const { return syncDir_; } void setSyncDir(SyncDirection newDir); - void setSyncDirConflict(const std::wstring& description); //set syncDir = SyncDirection::NONE + fill conflict description + void setSyncDirConflict(const Zstringc& description); //set syncDir = SyncDirection::NONE + fill conflict description bool isActive() const { return selectedForSync_; } void setActive(bool active); @@ -449,8 +449,8 @@ public: //for use during init in "CompareProcess" only: template void setCategory(); - void setCategoryConflict (const Zstringw& description); - void setCategoryDiffMetadata(const Zstringw& description); + void setCategoryConflict (const Zstringc& description); + void setCategoryDiffMetadata(const Zstringc& description); protected: FileSystemObject(const Zstring& itemNameL, @@ -488,15 +488,16 @@ private: void propagateChangedItemName(const Zstring& itemNameOld); //required after any itemName changes //categorization - Zstringw cmpResultDescr_; //only filled if getCategory() == FILE_CONFLICT or FILE_DIFFERENT_METADATA + Zstringc cmpResultDescr_; //only filled if getCategory() == FILE_CONFLICT or FILE_DIFFERENT_METADATA + //conserve memory (avoid std::string SSO overhead + allow ref-counting!) CompareFileResult 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! - 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!) + Zstringc syncDirectionConflict_; //non-empty if we have a conflict setting sync-direction + //conserve memory (avoid std::string SSO overhead + allow ref-counting!) Zstring itemNameL_; //slightly redundant under Linux, but on Windows the "same" file paths can differ in case Zstring itemNameR_; //use as indicator: an empty name means: not existing on this side! @@ -723,10 +724,10 @@ CompareDirResult FolderPair::getDirCategory() const inline -std::wstring FileSystemObject::getCatExtraDescription() const +Zstringc FileSystemObject::getCatExtraDescription() const { assert(getCategory() == FILE_CONFLICT || getCategory() == FILE_DIFFERENT_METADATA); - return zen::copyStringTo(cmpResultDescr_); + return cmpResultDescr_; } @@ -741,11 +742,11 @@ void FileSystemObject::setSyncDir(SyncDirection newDir) inline -void FileSystemObject::setSyncDirConflict(const std::wstring& description) +void FileSystemObject::setSyncDirConflict(const Zstringc& description) { assert(!description.empty()); syncDir_ = SyncDirection::NONE; - syncDirectionConflict_ = zen::copyStringTo(description); + syncDirectionConflict_ = description; notifySyncCfgChanged(); } @@ -755,7 +756,7 @@ inline std::wstring FileSystemObject::getSyncOpConflict() const { assert(getSyncOperation() == SO_UNRESOLVED_CONFLICT); - return zen::copyStringTo(syncDirectionConflict_); + return zen::utfTo(syncDirectionConflict_); } @@ -855,7 +856,7 @@ template <> void FileSystemObject::setCategory () = dele template <> void FileSystemObject::setCategory () = delete; // inline -void FileSystemObject::setCategoryConflict(const Zstringw& description) +void FileSystemObject::setCategoryConflict(const Zstringc& description) { assert(!description.empty()); cmpResult_ = FILE_CONFLICT; @@ -863,7 +864,7 @@ void FileSystemObject::setCategoryConflict(const Zstringw& description) } inline -void FileSystemObject::setCategoryDiffMetadata(const Zstringw& description) +void FileSystemObject::setCategoryDiffMetadata(const Zstringc& description) { assert(!description.empty()); cmpResult_ = FILE_DIFFERENT_METADATA; diff --git a/FreeFileSync/Source/base/help_provider.h b/FreeFileSync/Source/base/help_provider.h deleted file mode 100644 index d9a1b3fc..00000000 --- a/FreeFileSync/Source/base/help_provider.h +++ /dev/null @@ -1,22 +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 HELP_PROVIDER_H_85930427583421563126 -#define HELP_PROVIDER_H_85930427583421563126 - -#include - - -namespace fff -{ -inline -void displayHelpEntry(const wxString& topic, wxWindow* parent) -{ - wxLaunchDefaultBrowser(L"https://freefilesync.org/manual.php?topic=" + topic); -} -} - -#endif //HELP_PROVIDER_H_85930427583421563126 diff --git a/FreeFileSync/Source/base/icon_buffer.cpp b/FreeFileSync/Source/base/icon_buffer.cpp deleted file mode 100644 index b10027a6..00000000 --- a/FreeFileSync/Source/base/icon_buffer.cpp +++ /dev/null @@ -1,413 +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 "icon_buffer.h" -#include -#include -#include //includes -#include -#include -#include -#include "icon_loader.h" - - -using namespace zen; -using namespace fff; -using AFS = AbstractFileSystem; - - -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(runningMainThread()); - - if (!ih.getRgb()) - return wxNullBitmap; - - wxImage img(ih.getWidth(), ih.getHeight(), ih.releaseRgb(), false /*static_data*/); //pass ownership - if (ih.getAlpha()) - img.SetAlpha(ih.releaseAlpha(), false /*static_data*/); - return wxBitmap(img); -} - - -} - -//################################################################################################################################################ - -ImageHolder getDisplayIcon(const AbstractPath& itemPath, IconBuffer::IconSize sz) -{ - //1. try to load thumbnails - switch (sz) - { - case IconBuffer::SIZE_SMALL: - break; - case IconBuffer::SIZE_MEDIUM: - case IconBuffer::SIZE_LARGE: - if (ImageHolder img = AFS::getThumbnailImage(itemPath, IconBuffer::getSize(sz))) - return img; - //else: fallback to non-thumbnail icon - break; - } - - const Zstring& templateName = AFS::getItemName(itemPath); - - //2. retrieve file icons - if (ImageHolder ih = AFS::getFileIcon(itemPath, IconBuffer::getSize(sz))) - return ih; - - //3. fallbacks - if (ImageHolder ih = getIconByTemplatePath(templateName, IconBuffer::getSize(sz))) - return ih; - - return genericFileIcon(IconBuffer::getSize(sz)); -} - -//################################################################################################################################################ - -//---------------------- Shared Data ------------------------- -class WorkLoad -{ -public: - //context of main thread - void set(const std::vector& newLoad) - { - assert(runningMainThread()); - { - std::lock_guard dummy(lockFiles_); - - workLoad_.clear(); - for (const AbstractPath& filePath : newLoad) - workLoad_.emplace_back(filePath); - } - conditionNewWork_.notify_all(); //instead of notify_one(); workaround bug: https://svn.boost.org/trac/boost/ticket/7796 - //condition handling, see: https://www.boost.org/doc/libs/1_43_0/doc/html/thread/synchronization.html#thread.synchronization.condvar_ref - } - - void add(const AbstractPath& filePath) //context of main thread - { - assert(runningMainThread()); - { - std::lock_guard dummy(lockFiles_); - workLoad_.emplace_back(filePath); //set as next item to retrieve - } - conditionNewWork_.notify_all(); - } - - //context of worker thread, blocking: - AbstractPath extractNext() //throw ThreadInterruption - { - assert(!runningMainThread()); - std::unique_lock dummy(lockFiles_); - - interruptibleWait(conditionNewWork_, dummy, [this] { return !workLoad_.empty(); }); //throw ThreadInterruption - - AbstractPath filePath = workLoad_. back(); //yes, no strong exception guarantee (std::bad_alloc) - /**/ workLoad_.pop_back(); // - return filePath; - } - -private: - //AbstractPath is thread-safe like an int! - std::mutex lockFiles_; - std::condition_variable conditionNewWork_; //signal event: data for processing available - std::vector workLoad_; //processes last elements of vector first! -}; - - -class Buffer -{ -public: - //called by main and worker thread: - bool hasIcon(const AbstractPath& filePath) const - { - std::lock_guard dummy(lockIconList_); - return contains(iconList, filePath); - } - - //must be called by main thread only! => wxBitmap is NOT thread-safe like an int (non-atomic ref-count!!!) - std::optional retrieve(const AbstractPath& filePath) - { - assert(runningMainThread()); - std::lock_guard dummy(lockIconList_); - - auto it = iconList.find(filePath); - if (it == iconList.end()) - return {}; - - markAsHot(it); - - IconData& idata = refData(it); - if (idata.iconRaw) //if not yet converted... - { - idata.iconFmt = std::make_unique(extractWxBitmap(std::move(idata.iconRaw))); //convert in main thread! - assert(!idata.iconRaw); - } - return idata.iconFmt ? *idata.iconFmt : wxNullBitmap; //idata.iconRaw may be inserted as empty from worker thread! - } - - //called by main and worker thread: - void insert(const AbstractPath& filePath, ImageHolder&& icon) - { - std::lock_guard 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, IconData()); - assert(rc.second); //insertion took place - if (rc.second) - { - refData(rc.first).iconRaw = std::move(icon); - priorityListPushBack(rc.first); - } - } - - //must be called by main thread only! => ~wxBitmap() is NOT thread-safe! - //call at an appropriate time, e.g. after Workload::set() - void limitSize() - { - assert(runningMainThread()); - std::lock_guard dummy(lockIconList_); - - while (iconList.size() > BUFFER_SIZE_MAX) - { - auto itDelPos = firstInsertPos_; - priorityListPopFront(); - iconList.erase(itDelPos); //remove oldest element - } - } - -private: - struct IconData; - using FileIconMap = std::map; - IconData& refData(FileIconMap::iterator it) { return it->second; } - - //call while holding lock: - void priorityListPopFront() - { - assert(firstInsertPos_!= iconList.end()); - firstInsertPos_ = refData(firstInsertPos_).next; - - if (firstInsertPos_ != iconList.end()) - refData(firstInsertPos_).prev = iconList.end(); - else //priority list size > BUFFER_SIZE_MAX in this context, but still for completeness: - lastInsertPos_ = iconList.end(); - } - - //call while holding lock: - void priorityListPushBack(FileIconMap::iterator it) - { - if (lastInsertPos_ == iconList.end()) - { - assert(firstInsertPos_ == iconList.end()); - firstInsertPos_ = lastInsertPos_ = it; - refData(it).prev = refData(it).next = iconList.end(); - } - else - { - refData(it).next = iconList.end(); - refData(it).prev = lastInsertPos_; - refData(lastInsertPos_).next = it; - lastInsertPos_ = it; - } - } - - //call while holding lock: - void markAsHot(FileIconMap::iterator it) //mark existing buffer entry as if newly inserted - { - assert(it != iconList.end()); - if (refData(it).next != iconList.end()) - { - if (refData(it).prev != iconList.end()) - { - refData(refData(it).prev).next = refData(it).next; //remove somewhere from the middle - refData(refData(it).next).prev = refData(it).prev; // - } - else - { - assert(it == firstInsertPos_); - priorityListPopFront(); - } - priorityListPushBack(it); - } - else - { - if (refData(it).prev != iconList.end()) - assert(it == lastInsertPos_); //nothing to do - else - assert(iconList.size() == 1 && it == firstInsertPos_ && it == lastInsertPos_); //nothing to do - } - } - - struct IconData - { - IconData() {} - IconData(IconData&& tmp) noexcept : iconRaw(std::move(tmp.iconRaw)), iconFmt(std::move(tmp.iconFmt)), prev(tmp.prev), next(tmp.next) {} - - ImageHolder iconRaw; //native icon representation: may be used by any thread - - std::unique_ptr iconFmt; //use ONLY from main thread! - //wxBitmap is NOT thread-safe: non-atomic ref-count just to begin with... - //- prohibit implicit calls to wxBitmap(const wxBitmap&) - //- prohibit calls to ~wxBitmap() and transitively ~IconData() - //- prohibit even wxBitmap() default constructor - better be safe than sorry! - - FileIconMap::iterator prev; //store list sorted by time of insertion into buffer - FileIconMap::iterator next; // - }; - - mutable std::mutex lockIconList_; - FileIconMap iconList; //shared resource; Zstring is thread-safe like an int - FileIconMap::iterator firstInsertPos_ = iconList.end(); - FileIconMap::iterator lastInsertPos_ = iconList.end(); -}; - -//################################################################################################################################################ - - -//######################### redirect to impl ##################################################### - -struct IconBuffer::Impl -{ - //communication channel used by threads: - WorkLoad workload; //manage life time: enclose InterruptibleThread's (until joined)!!! - Buffer buffer; // - - InterruptibleThread worker; - //------------------------- - //------------------------- - std::map extensionIcons; //no item count limit!? Test case C:\ ~ 3800 unique file extensions -}; - - -IconBuffer::IconBuffer(IconSize sz) : pimpl_(std::make_unique()), iconSizeType_(sz) -{ - 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)); - } - }); -} - - -IconBuffer::~IconBuffer() -{ - setWorkload({}); //make sure interruption point is always reached! needed??? - pimpl_->worker.interrupt(); - pimpl_->worker.join(); -} - - -int IconBuffer::getSize(IconSize sz) -{ - //coordinate with getIconByIndexImpl() and linkOverlayIcon()! - switch (sz) - { - case IconBuffer::SIZE_SMALL: - return fastFromDIP(24); - case IconBuffer::SIZE_MEDIUM: - return fastFromDIP(48); - - case IconBuffer::SIZE_LARGE: - return fastFromDIP(128); - } - assert(false); - return 0; -} - - -bool IconBuffer::readyForRetrieval(const AbstractPath& filePath) -{ - return pimpl_->buffer.hasIcon(filePath); -} - - -std::optional IconBuffer::retrieveFileIcon(const AbstractPath& filePath) -{ - if (std::optional ico = pimpl_->buffer.retrieve(filePath)) - return ico; - - //since this icon seems important right now, we don't want to wait until next setWorkload() to start retrieving - pimpl_->workload.add(filePath); - pimpl_->buffer.limitSize(); - return {}; -} - - -void IconBuffer::setWorkload(const std::vector& 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! -} - - -wxBitmap IconBuffer::getIconByExtension(const Zstring& filePath) -{ - const Zstring& ext = getFileExtension(filePath); - - assert(runningMainThread()); - - auto it = pimpl_->extensionIcons.find(ext); - if (it == pimpl_->extensionIcons.end()) - { - 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, getSize(iconSizeType_)))).first; - } - //need buffer size limit??? - return it->second; -} - - -wxBitmap IconBuffer::genericFileIcon(IconSize sz) -{ - return extractWxBitmap(fff::genericFileIcon(IconBuffer::getSize(sz))); -} - - -wxBitmap IconBuffer::genericDirIcon(IconSize sz) -{ - return extractWxBitmap(fff::genericDirIcon(IconBuffer::getSize(sz))); -} - - -wxBitmap IconBuffer::linkOverlayIcon(IconSize sz) -{ - //coordinate with IconBuffer::getSize()! - return getResourceImage([sz] - { - const int pixelSize = IconBuffer::getSize(sz); - - if (pixelSize >= fastFromDIP(128)) return L"link_128"; - if (pixelSize >= fastFromDIP(48)) return L"link_48"; - if (pixelSize >= fastFromDIP(24)) return L"link_24"; - return L"link_16"; - }()); -} - - -bool fff::hasLinkExtension(const Zstring& filepath) -{ - const Zstring& ext = getFileExtension(filepath); - return ext == "desktop"; - -} diff --git a/FreeFileSync/Source/base/icon_buffer.h b/FreeFileSync/Source/base/icon_buffer.h deleted file mode 100644 index d6dd504f..00000000 --- a/FreeFileSync/Source/base/icon_buffer.h +++ /dev/null @@ -1,55 +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 ICON_BUFFER_H_8425703245726394256 -#define ICON_BUFFER_H_8425703245726394256 - -#include -#include -#include -#include -#include "../afs/abstract.h" - - -namespace fff -{ -class IconBuffer -{ -public: - enum IconSize - { - SIZE_SMALL, - SIZE_MEDIUM, - SIZE_LARGE - }; - - IconBuffer(IconSize sz); - ~IconBuffer(); - - static int getSize(IconSize sz); //expected and *maximum* icon size in pixel - int getSize() const { return getSize(iconSizeType_); } // - - void setWorkload (const std::vector& load); //(re-)set new workload of icons to be retrieved; - bool readyForRetrieval(const AbstractPath& filePath); - std::optional retrieveFileIcon (const AbstractPath& filePath); //... and mark as hot - 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); - static wxBitmap linkOverlayIcon(IconSize sz); - -private: - struct Impl; - const std::unique_ptr pimpl_; - - const IconSize iconSizeType_; -}; - -bool hasLinkExtension(const Zstring& filepath); -} - -#endif //ICON_BUFFER_H_8425703245726394256 diff --git a/FreeFileSync/Source/base/localization.cpp b/FreeFileSync/Source/base/localization.cpp deleted file mode 100644 index ef1ee778..00000000 --- a/FreeFileSync/Source/base/localization.cpp +++ /dev/null @@ -1,549 +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 "localization.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "parse_plural.h" -#include "parse_lng.h" -#include "ffs_paths.h" - - #include //wcscasecmp - - -using namespace zen; -using namespace fff; - - -namespace -{ -class FFSTranslation : public TranslationHandler -{ -public: - FFSTranslation(const std::string& lngStream); //throw lng::ParsingError, plural::ParsingError - - std::wstring translate(const std::wstring& text) const override - { - //look for translation in buffer table - auto it = transMapping_.find(text); - if (it != transMapping_.end() && !it->second.empty()) - return it->second; - return text; //fallback - } - - std::wstring translate(const std::wstring& singular, const std::wstring& plural, int64_t n) const override - { - auto it = transMappingPl_.find({ singular, plural }); - if (it != transMappingPl_.end()) - { - const size_t formNo = pluralParser_->getForm(n); - assert(formNo < it->second.size()); - if (formNo < it->second.size()) - return replaceCpy(it->second[formNo], L"%x", formatNumber(n)); - } - return replaceCpy(std::abs(n) == 1 ? singular : plural, L"%x", formatNumber(n)); //fallback - } - -private: - using Translation = std::unordered_map; //hash_map is 15% faster than std::map on GCC - using TranslationPlural = std::map, std::vector>; - - Translation transMapping_; //map original text |-> translation - TranslationPlural transMappingPl_; - std::unique_ptr pluralParser_; //bound! -}; - - -FFSTranslation::FFSTranslation(const std::string& lngStream) //throw lng::ParsingError, plural::ParsingError -{ - lng::TransHeader header; - lng::TranslationMap transUtf; - lng::TranslationPluralMap transPluralUtf; - lng::parseLng(lngStream, header, transUtf, transPluralUtf); //throw ParsingError - - pluralParser_ = std::make_unique(header.pluralDefinition); //throw plural::ParsingError - - for (const auto& [original, translation] : transUtf) - transMapping_.emplace(utfTo(original), - utfTo(translation)); - - for (const auto& [singAndPlural, pluralForms] : transPluralUtf) - { - std::vector transPluralForms; - for (const std::string& pf : pluralForms) - transPluralForms.push_back(utfTo(pf)); - - transMappingPl_.insert( - { - { - utfTo(singAndPlural.first), - utfTo(singAndPlural.second) - }, - std::move(transPluralForms) }); - } -} - - -std::vector loadTranslations() -{ - const Zstring& zipPath = getResourceDirPf() + Zstr("Languages.zip"); - std::vector> streams; - - try //to load from ZIP first: - { - const std::string rawStream = loadBinContainer(zipPath, nullptr /*notifyUnbufferedIO*/); //throw FileError - wxMemoryInputStream memStream(rawStream.c_str(), rawStream.size()); //does not take ownership - wxZipInputStream zipStream(memStream, wxConvUTF8); - - while (const auto& entry = std::unique_ptr(zipStream.GetNextEntry())) //take ownership! - if (std::string stream(entry->GetSize(), '\0'); !stream.empty() && zipStream.ReadAll(&stream[0], stream.size())) - streams.emplace_back(utfTo(entry->GetName()), std::move(stream)); - else - assert(false); - } - catch (FileError&) //fall back to folder - { - traverseFolder(beforeLast(zipPath, Zstr(".zip"), IF_MISSING_RETURN_NONE), [&](const FileInfo& fi) - { - if (endsWith(fi.fullPath, Zstr(".lng"))) - try - { - std::string stream = loadBinContainer(fi.fullPath, nullptr /*notifyUnbufferedIO*/); //throw FileError - streams.emplace_back(fi.itemName, std::move(stream)); - } - catch (FileError&) { assert(false); } - }, nullptr, nullptr, [](const std::wstring& errorMsg) { assert(false); }); //errors are not really critical in this context - } - //-------------------------------------------------------------------- - - std::vector locMapping; - { - //default entry: - TranslationInfo newEntry; - newEntry.languageID = wxLANGUAGE_ENGLISH_US; - newEntry.languageName = std::wstring(L"English (US)") + LTR_MARK; //handle weak ")" for bidi-algorithm - newEntry.translatorName = L"Zenju"; - newEntry.languageFlag = L"flag_usa.png"; - newEntry.lngFileName = Zstr(""); - newEntry.lngStream = ""; - locMapping.push_back(newEntry); - } - - for (/*const*/ auto& [fileName, stream] : streams) - try - { - const lng::TransHeader lngHeader = lng::parseHeader(stream); //throw ParsingError - assert(!lngHeader.languageName .empty()); - assert(!lngHeader.translatorName.empty()); - assert(!lngHeader.localeName .empty()); - assert(!lngHeader.flagFile .empty()); - /* - Some ISO codes are used by multiple wxLanguage IDs which can lead to incorrect mapping by wxLocale::FindLanguageInfo()!!! - => Identify by description, e.g. "Chinese (Traditional)". The following IDs are affected: - wxLANGUAGE_CHINESE_TRADITIONAL - wxLANGUAGE_ENGLISH_UK - wxLANGUAGE_SPANISH //non-unique, but still mapped correctly (or is it incidentally???) - wxLANGUAGE_SERBIAN // - */ - if (const wxLanguageInfo* locInfo = wxLocale::FindLanguageInfo(utfTo(lngHeader.localeName))) - { - TranslationInfo newEntry; - newEntry.languageID = static_cast(locInfo->Language); - newEntry.languageName = utfTo(lngHeader.languageName); - newEntry.translatorName = utfTo(lngHeader.translatorName); - newEntry.languageFlag = utfTo(lngHeader.flagFile); - newEntry.lngFileName = fileName; - newEntry.lngStream = std::move(stream); - locMapping.push_back(newEntry); - } - else assert(false); - } - catch (lng::ParsingError&) { assert(false); } //better not show an error message here; scenario: batch jobs - - std::sort(locMapping.begin(), locMapping.end(), [](const TranslationInfo& lhs, const TranslationInfo& rhs) - { - return LessNaturalSort()(utfTo(lhs.languageName), - utfTo(rhs.languageName)); //use a more "natural" sort: ignore case and diacritics - }); - return locMapping; -} - - -wxLanguage mapLanguageDialect(wxLanguage language) -{ - switch (static_cast(language)) //avoid enumeration value wxLANGUAGE_*' not handled in switch [-Wswitch-enum] - { - //variants of wxLANGUAGE_ARABIC - case wxLANGUAGE_ARABIC_ALGERIA: - case wxLANGUAGE_ARABIC_BAHRAIN: - case wxLANGUAGE_ARABIC_EGYPT: - case wxLANGUAGE_ARABIC_IRAQ: - case wxLANGUAGE_ARABIC_JORDAN: - case wxLANGUAGE_ARABIC_KUWAIT: - case wxLANGUAGE_ARABIC_LEBANON: - case wxLANGUAGE_ARABIC_LIBYA: - case wxLANGUAGE_ARABIC_MOROCCO: - case wxLANGUAGE_ARABIC_OMAN: - case wxLANGUAGE_ARABIC_QATAR: - case wxLANGUAGE_ARABIC_SAUDI_ARABIA: - case wxLANGUAGE_ARABIC_SUDAN: - case wxLANGUAGE_ARABIC_SYRIA: - case wxLANGUAGE_ARABIC_TUNISIA: - case wxLANGUAGE_ARABIC_UAE: - case wxLANGUAGE_ARABIC_YEMEN: - return wxLANGUAGE_ARABIC; - - //variants of wxLANGUAGE_CHINESE_SIMPLIFIED - case wxLANGUAGE_CHINESE: - case wxLANGUAGE_CHINESE_SINGAPORE: - return wxLANGUAGE_CHINESE_SIMPLIFIED; - - //variants of wxLANGUAGE_CHINESE_TRADITIONAL - case wxLANGUAGE_CHINESE_TAIWAN: - case wxLANGUAGE_CHINESE_HONGKONG: - case wxLANGUAGE_CHINESE_MACAU: - return wxLANGUAGE_CHINESE_TRADITIONAL; - - //variants of wxLANGUAGE_DUTCH - case wxLANGUAGE_DUTCH_BELGIAN: - return wxLANGUAGE_DUTCH; - - //variants of wxLANGUAGE_ENGLISH_UK - case wxLANGUAGE_ENGLISH_AUSTRALIA: - case wxLANGUAGE_ENGLISH_NEW_ZEALAND: - case wxLANGUAGE_ENGLISH_TRINIDAD: - case wxLANGUAGE_ENGLISH_CARIBBEAN: - case wxLANGUAGE_ENGLISH_JAMAICA: - case wxLANGUAGE_ENGLISH_BELIZE: - case wxLANGUAGE_ENGLISH_EIRE: - case wxLANGUAGE_ENGLISH_SOUTH_AFRICA: - case wxLANGUAGE_ENGLISH_ZIMBABWE: - case wxLANGUAGE_ENGLISH_BOTSWANA: - case wxLANGUAGE_ENGLISH_DENMARK: - return wxLANGUAGE_ENGLISH_UK; - - //variants of wxLANGUAGE_ENGLISH_US - case wxLANGUAGE_ENGLISH: - case wxLANGUAGE_ENGLISH_CANADA: - case wxLANGUAGE_ENGLISH_PHILIPPINES: - return wxLANGUAGE_ENGLISH_US; - - //variants of wxLANGUAGE_FRENCH - case wxLANGUAGE_FRENCH_BELGIAN: - case wxLANGUAGE_FRENCH_CANADIAN: - case wxLANGUAGE_FRENCH_LUXEMBOURG: - case wxLANGUAGE_FRENCH_MONACO: - case wxLANGUAGE_FRENCH_SWISS: - return wxLANGUAGE_FRENCH; - - //variants of wxLANGUAGE_GERMAN - case wxLANGUAGE_GERMAN_AUSTRIAN: - case wxLANGUAGE_GERMAN_BELGIUM: - case wxLANGUAGE_GERMAN_LIECHTENSTEIN: - case wxLANGUAGE_GERMAN_LUXEMBOURG: - case wxLANGUAGE_GERMAN_SWISS: - return wxLANGUAGE_GERMAN; - - //variants of wxLANGUAGE_ITALIAN - case wxLANGUAGE_ITALIAN_SWISS: - return wxLANGUAGE_ITALIAN; - - //variants of wxLANGUAGE_NORWEGIAN_BOKMAL - case wxLANGUAGE_NORWEGIAN_NYNORSK: - return wxLANGUAGE_NORWEGIAN_BOKMAL; - - //variants of wxLANGUAGE_ROMANIAN - case wxLANGUAGE_MOLDAVIAN: - return wxLANGUAGE_ROMANIAN; - - //variants of wxLANGUAGE_RUSSIAN - case wxLANGUAGE_RUSSIAN_UKRAINE: - return wxLANGUAGE_RUSSIAN; - - //variants of wxLANGUAGE_SERBIAN - case wxLANGUAGE_SERBIAN_CYRILLIC: - case wxLANGUAGE_SERBIAN_LATIN: - case wxLANGUAGE_SERBO_CROATIAN: - return wxLANGUAGE_SERBIAN; - - //variants of wxLANGUAGE_SPANISH - case wxLANGUAGE_SPANISH_ARGENTINA: - case wxLANGUAGE_SPANISH_BOLIVIA: - case wxLANGUAGE_SPANISH_CHILE: - case wxLANGUAGE_SPANISH_COLOMBIA: - case wxLANGUAGE_SPANISH_COSTA_RICA: - case wxLANGUAGE_SPANISH_DOMINICAN_REPUBLIC: - case wxLANGUAGE_SPANISH_ECUADOR: - case wxLANGUAGE_SPANISH_EL_SALVADOR: - case wxLANGUAGE_SPANISH_GUATEMALA: - case wxLANGUAGE_SPANISH_HONDURAS: - case wxLANGUAGE_SPANISH_MEXICAN: - case wxLANGUAGE_SPANISH_MODERN: - case wxLANGUAGE_SPANISH_NICARAGUA: - case wxLANGUAGE_SPANISH_PANAMA: - case wxLANGUAGE_SPANISH_PARAGUAY: - case wxLANGUAGE_SPANISH_PERU: - case wxLANGUAGE_SPANISH_PUERTO_RICO: - case wxLANGUAGE_SPANISH_URUGUAY: - case wxLANGUAGE_SPANISH_US: - case wxLANGUAGE_SPANISH_VENEZUELA: - return wxLANGUAGE_SPANISH; - - //variants of wxLANGUAGE_SWEDISH - case wxLANGUAGE_SWEDISH_FINLAND: - return wxLANGUAGE_SWEDISH; - - //languages without variants: - //case wxLANGUAGE_BULGARIAN: - //case wxLANGUAGE_CROATIAN: - //case wxLANGUAGE_CZECH: - //case wxLANGUAGE_DANISH: - //case wxLANGUAGE_FINNISH: - //case wxLANGUAGE_GREEK: - //case wxLANGUAGE_HINDI: - //case wxLANGUAGE_HEBREW: - //case wxLANGUAGE_HUNGARIAN: - //case wxLANGUAGE_JAPANESE: - //case wxLANGUAGE_KOREAN: - //case wxLANGUAGE_LITHUANIAN: - //case wxLANGUAGE_POLISH: - //case wxLANGUAGE_PORTUGUESE: - //case wxLANGUAGE_PORTUGUESE_BRAZILIAN: - //case wxLANGUAGE_SCOTS_GAELIC: - //case wxLANGUAGE_SLOVAK: - //case wxLANGUAGE_SLOVENIAN: - //case wxLANGUAGE_TURKISH: - //case wxLANGUAGE_UKRAINIAN: - //case wxLANGUAGE_VIETNAMESE: - default: - return language; - } -} - - -//we need to interface with wxWidgets' translation handling for a few translations used in their internal source files -// => since there is no better API: dynamically generate a MO file and feed it to wxTranslation -class MemoryTranslationLoader : public wxTranslationsLoader -{ -public: - MemoryTranslationLoader(wxLanguage langId, std::map&& transMapping) : - canonicalName_(wxLocale::GetLanguageCanonicalName(langId)) - { - assert(!canonicalName_.empty()); - - //https://www.gnu.org/software/gettext/manual/html_node/MO-Files.html - transMapping[""] = L"Content-Type: text/plain; charset=UTF-8\n"; - - const int headerSize = 28; - writeNumber(moBuf_, 0x950412de); //magic number - writeNumber(moBuf_, 0); //format version - writeNumber(moBuf_, transMapping.size()); //string count - writeNumber(moBuf_, headerSize); //string references offset: original - writeNumber(moBuf_, headerSize + (2 * sizeof(uint32_t)) * transMapping.size()); //string references offset: translation - writeNumber(moBuf_, 0); //size of hashing table - writeNumber(moBuf_, 0); //offset of hashing table - - const int stringsOffset = headerSize + 2 * (2 * sizeof(uint32_t)) * transMapping.size(); - std::string stringsList; - - for (const auto& [original, translation] : transMapping) - { - writeNumber(moBuf_, original.size()); //string length - writeNumber(moBuf_, stringsOffset + stringsList.size()); //string offset - stringsList.append(original.c_str(), original.size() + 1); //include 0-termination - } - - for (const auto& [original, trans] : transMapping) - { - const auto& translation = utfTo(trans); - writeNumber(moBuf_, translation.size()); //string length - writeNumber(moBuf_, stringsOffset + stringsList.size()); //string offset - stringsList.append(translation.c_str(), translation.size() + 1); //include 0-termination - } - - writeArray(moBuf_, stringsList.c_str(), stringsList.size()); - } - - wxMsgCatalog* LoadCatalog(const wxString& domain, const wxString& lang) override - { - //"lang" is NOT (exactly) what we return from GetAvailableTranslations(), but has a little "extra", e.g.: de_DE.WINDOWS-1252 or ar.WINDOWS-1252 - if (equalAsciiNoCase(extractIsoLangCode(lang), extractIsoLangCode(canonicalName_))) - return wxMsgCatalog::CreateFromData(wxScopedCharBuffer::CreateNonOwned(moBuf_.ref().c_str(), moBuf_.ref().size()), domain); - assert(false); - return nullptr; - } - - wxArrayString GetAvailableTranslations(const wxString& domain) const override - { - wxArrayString available; - available.push_back(canonicalName_); - return available; - } - -private: - static wxString extractIsoLangCode(wxString langCode) - { - langCode = beforeLast(langCode, L".", IF_MISSING_RETURN_ALL); - return beforeLast(langCode, L"_", IF_MISSING_RETURN_ALL); - } - - const wxString canonicalName_; - MemoryStreamOut moBuf_; -}; - - -//global wxWidgets localization: sets up C localization runtime as well! -class wxWidgetsLocale -{ -public: - static wxWidgetsLocale& getInstance() - { - static wxWidgetsLocale inst; - return inst; - } - - void init(wxLanguage lng) - { - lng_ = lng; - - if (const wxLanguageInfo* selLngInfo = wxLocale::GetLanguageInfo(lng)) - layoutDir_ = selLngInfo->LayoutDirection; - else - layoutDir_ = wxLayout_LeftToRight; - - //use sys-lang to preserve sub-language specific rules (e.g. German Swiss number punctuation) - //beneficial even for Arabic locale: support user-specific date settings (instead of Hijri calendar year 1441 = Gregorian 2019) - if (!locale_) - { - //wxWidgets shows a modal dialog on error during wxLocale::Init() -> at least we can shut it up! - wxLog* oldLogTarget = wxLog::SetActiveTarget(new wxLogStderr); //transfer and receive ownership! - ZEN_ON_SCOPE_EXIT(delete wxLog::SetActiveTarget(oldLogTarget)); - - //locale_.reset(); //avoid global locale lifetime overlap! wxWidgets cannot handle this and will crash! - locale_ = std::make_unique(sysLng_, wxLOCALE_DONT_LOAD_DEFAULT /*we're not using wxwin.mo*/); - assert(locale_->IsOk()); - } - } - - void tearDown() { locale_.reset(); lng_ = wxLANGUAGE_UNKNOWN; layoutDir_ = wxLayout_Default; } - - wxLanguage getSysLanguage() const { return sysLng_; } - wxLanguage getLanguage () const { return lng_; } - wxLayoutDirection getLayoutDirection() const { return layoutDir_; } - -private: - wxWidgetsLocale() {} - ~wxWidgetsLocale() { assert(!locale_); } - - const wxLanguage sysLng_ = static_cast(wxLocale::GetSystemLanguage()); - wxLanguage lng_ = wxLANGUAGE_UNKNOWN; - wxLayoutDirection layoutDir_ = wxLayout_Default; - std::unique_ptr locale_; -}; -} - - -const std::vector& fff::getExistingTranslations() -{ - static const std::vector translations = loadTranslations(); - return translations; -} - - -void fff::releaseWxLocale() -{ - wxWidgetsLocale::getInstance().tearDown(); - setTranslator(nullptr); //good place for clean up rather than some time during static destruction: is this an actual benefit??? -} - - -void fff::setLanguage(wxLanguage lng) //throw FileError -{ - if (getLanguage() == lng) - return; //support polling - - //(try to) retrieve language file - std::string lngStream; - Zstring lngFileName; - - for (const TranslationInfo& e : getExistingTranslations()) - if (e.languageID == lng) - { - lngStream = e.lngStream; - lngFileName = e.lngFileName; - break; - } - - //load language file into buffer - if (lngStream.empty()) //if file stream is empty, texts will be English by default - { - setTranslator(nullptr); - lng = wxLANGUAGE_ENGLISH_US; - } - else - try - { - setTranslator(std::make_unique(lngStream)); //throw lng::ParsingError, plural::ParsingError - } - catch (lng::ParsingError& e) - { - throw FileError(replaceCpy(replaceCpy(replaceCpy(_("Error parsing file %x, row %y, column %z."), - L"%x", fmtPath(lngFileName)), - L"%y", numberTo(e.row + 1)), - L"%z", numberTo(e.col + 1)) - + L"\n\n" + e.msg); - } - catch (plural::ParsingError&) - { - throw FileError(L"Invalid plural form definition: " + fmtPath(lngFileName)); //user should never see this! - } - - //handle RTL swapping: we need wxWidgets to do this - wxWidgetsLocale::getInstance().init(lng); - - //add translation for wxWidgets-internal strings: - assert(wxTranslations::Get()); //already initialized by wxLocale - if (wxTranslations* wxtrans = wxTranslations::Get()) - { - std::map transMapping = - { - }; - wxtrans->SetLanguage(lng); //!= wxLocale's language, which could be wxLANGUAGE_DEFAULT (see wxWidgetsLocale) - wxtrans->SetLoader(new MemoryTranslationLoader(lng, std::move(transMapping))); - [[maybe_unused]] const bool catalogAdded = wxtrans->AddCatalog(wxString()); - assert(catalogAdded || lng == wxLANGUAGE_ENGLISH_US); - } -} - - -wxLanguage fff::getSystemLanguage() -{ - static const wxLanguage sysLng = mapLanguageDialect(wxWidgetsLocale::getInstance().getSysLanguage()); - return sysLng; -} - - -wxLanguage fff::getLanguage() -{ - return wxWidgetsLocale::getInstance().getLanguage(); -} - - -wxLayoutDirection fff::getLayoutDirection() -{ - return wxWidgetsLocale::getInstance().getLayoutDirection(); -} diff --git a/FreeFileSync/Source/base/localization.h b/FreeFileSync/Source/base/localization.h deleted file mode 100644 index e635ac1e..00000000 --- a/FreeFileSync/Source/base/localization.h +++ /dev/null @@ -1,39 +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 LOCALIZATION_H_8917342083178321534 -#define LOCALIZATION_H_8917342083178321534 - -#include -#include -#include -#include - - -namespace fff -{ -struct TranslationInfo -{ - wxLanguage languageID = wxLANGUAGE_UNKNOWN; - std::wstring languageName; - std::wstring translatorName; - std::wstring languageFlag; - Zstring lngFileName; - std::string lngStream; -}; -const std::vector& getExistingTranslations(); - -wxLanguage getSystemLanguage(); -wxLanguage getLanguage(); -wxLayoutDirection getLayoutDirection(); - -void setLanguage(wxLanguage lng); //throw FileError - -void releaseWxLocale(); //wxLocale crashes miserably on wxGTK when destructor runs during global cleanup => call in wxApp::OnExit -//"You should delete all wxWidgets object that you created by the time OnExit finishes. In particular, do not destroy them from application class' destructor!" -} - -#endif //LOCALIZATION_H_8917342083178321534 diff --git a/FreeFileSync/Source/base/lock_holder.h b/FreeFileSync/Source/base/lock_holder.h index ab24e9f4..87c075f9 100644 --- a/FreeFileSync/Source/base/lock_holder.h +++ b/FreeFileSync/Source/base/lock_holder.h @@ -5,7 +5,7 @@ #include #include #include "dir_lock.h" -#include "status_handler.h" +#include "process_callback.h" namespace fff @@ -43,8 +43,8 @@ public: for (const auto& [folderPath, error] : failedLocks) { msg += L"\n\n"; - //msg += fmtPath(folderPath) + L"\n" -> seems redundant - msg += replaceCpy(error.toString(), L"\n\n", L"\n"); + //msg += fmtPath(folderPath) + L'\n' -> seems redundant + msg += replaceCpy(error.toString(), L"\n\n", L'\n'); } pcb.reportWarning(msg, warnDirectoryLockFailed); //throw X diff --git a/FreeFileSync/Source/base/log_file.cpp b/FreeFileSync/Source/base/log_file.cpp deleted file mode 100644 index ac866e9a..00000000 --- a/FreeFileSync/Source/base/log_file.cpp +++ /dev/null @@ -1,648 +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 "log_file.h" -#include -#include -#include -#include -#include "ffs_paths.h" -#include "../afs/concrete.h" - -using namespace zen; -using namespace fff; -using AFS = AbstractFileSystem; - - -namespace -{ -const int LOG_FAIL_PREVIEW_MAX = 25; -const int SEPARATION_LINE_LEN = 40; - - -std::string generateLogHeaderTxt(const ProcessSummary& s, const ErrorLog& log, int logFailsPreviewMax) -{ - const std::string tabSpace(4, ' '); //4, the one true space count for tabs - - std::string headerLine; - for (const std::wstring& jobName : s.jobNames) - headerLine += (headerLine.empty() ? "" : " + ") + utfTo(jobName); - - if (!headerLine.empty()) - headerLine += " "; - - const TimeComp tc = getLocalTime(std::chrono::system_clock::to_time_t(s.startTime)); //returns empty string on failure - headerLine += formatTime(FORMAT_DATE, tc) + " [" + formatTime(FORMAT_TIME, tc) + ']'; - - //assemble summary box - std::vector summary; - summary.emplace_back(); - summary.push_back(tabSpace + utfTo(getResultsStatusLabel(s.resultStatus))); - summary.emplace_back(); - - const int errorCount = log.getItemCount(MSG_TYPE_ERROR | MSG_TYPE_FATAL_ERROR); - const int warningCount = log.getItemCount(MSG_TYPE_WARNING); - - if (errorCount > 0) summary.push_back(tabSpace + utfTo(_("Errors:") + L" " + formatNumber(errorCount))); - if (warningCount > 0) summary.push_back(tabSpace + utfTo(_("Warnings:") + L" " + formatNumber(warningCount))); - - summary.push_back(tabSpace + utfTo(_("Items processed:") + L" " + formatNumber(s.statsProcessed.items) + //show always, even if 0! - L" (" + formatFilesizeShort(s.statsProcessed.bytes) + L')')); - - if ((s.statsTotal.items < 0 && s.statsTotal.bytes < 0) || //no total items/bytes: e.g. for pure folder comparison - s.statsProcessed == s.statsTotal) //...if everything was processed successfully - ; - else - summary.push_back(tabSpace + utfTo(_("Items remaining:") + - L" " + formatNumber (s.statsTotal.items - s.statsProcessed.items) + - L" (" + formatFilesizeShort(s.statsTotal.bytes - s.statsProcessed.bytes) + L')')); - - const int64_t totalTimeSec = std::chrono::duration_cast(s.totalTime).count(); - summary.push_back(tabSpace + utfTo(_("Total time:")) + " " + utfTo(wxTimeSpan::Seconds(totalTimeSec).Format())); - - size_t sepLineLen = 0; //calculate max width (considering Unicode!) - for (const std::string& str : summary) sepLineLen = std::max(sepLineLen, unicodeLength(str)); - - std::string output = headerLine + '\n'; - output += std::string(sepLineLen + 1, '_') + '\n'; - - for (const std::string& str : summary) - output += '|' + str + '\n'; - - output += '|' + std::string(sepLineLen, '_') + "\n\n"; - - //------------ warnings/errors preview ---------------- - const int logFailTotal = errorCount + warningCount; - if (logFailTotal > 0) - { - output += '\n' + utfTo(_("Errors and warnings:")) + '\n'; - output += std::string(SEPARATION_LINE_LEN, '_') + '\n'; - - int previewCount = 0; - for (const LogEntry& entry : log) - if (entry.type & (MSG_TYPE_WARNING | MSG_TYPE_ERROR | MSG_TYPE_FATAL_ERROR)) - { - output += utfTo(formatMessage(entry)); - if (++previewCount >= logFailsPreviewMax) - break; - } - if (logFailTotal > previewCount) - output += " [...] " + utfTo(replaceCpy(_P("Showing %y of 1 row", "Showing %y of %x rows", logFailTotal), //%x used as plural form placeholder! - L"%y", formatNumber(previewCount))) + '\n'; - output += std::string(SEPARATION_LINE_LEN, '_') + "\n\n\n"; - } - return output; -} - - -std::string generateLogFooterTxt(const std::wstring& logFilePath, int logItemsTotal, int logItemsPreviewMax) //throw FileError -{ - const ComputerModel cm = getComputerModel(); //throw FileError - - std::string output; - if (logItemsTotal > logItemsPreviewMax) - output += " [...] " + utfTo(replaceCpy(_P("Showing %y of 1 row", "Showing %y of %x rows", logItemsTotal), //%x used as plural form placeholder! - L"%y", formatNumber(logItemsPreviewMax))) + '\n'; - - return output += '\n' + utfTo(getOsDescription() + /*throw FileError*/ + - L" [" + getUserName() /*throw FileError*/ + L"] - " + cm.model + L" - " + cm.vendor + L'\n' + - std::wstring(SEPARATION_LINE_LEN, L'_') + L'\n' + - _("Log file") + L": " + logFilePath) + '\n'; -} - - -std::string htmlTxtImpl(std::string&& str) -{ - trim(str); - std::string msg = htmlSpecialChars(str); - if (!contains(msg, '\n')) - return msg; - - std::string msgFmt; - for (auto it = msg.begin(); it != msg.end(); ) - if (*it == '\n') - { - msgFmt += "
\n"; - ++it; - - //skip duplicate newlines - for (; it != msg.end() && *it == L'\n'; ++it) - ; - - //preserve leading spaces - for (; it != msg.end() && *it == L' '; ++it) - msgFmt += " "; - } - else - msgFmt += *it++; - - return msgFmt; -} - -std::string htmlTxt(const std::wstring& str) { return htmlTxtImpl(utfTo(str)); } -std::string htmlTxt(const wchar_t* str) { return htmlTxtImpl(utfTo(str)); } - - -//Astyle screws up royally with the following raw string literals! -//*INDENT-OFF* -std::string formatMessageHtml(const LogEntry& entry) -{ - const std::string typeLabel = htmlTxt(getMessageTypeLabel(entry.type)); - const char* typeImage = nullptr; - switch (entry.type) - { - case MSG_TYPE_INFO: typeImage = "msg-info.png"; break; - case MSG_TYPE_WARNING: typeImage = "msg-warning.png"; break; - case MSG_TYPE_ERROR: - case MSG_TYPE_FATAL_ERROR: typeImage = "msg-error.png"; break; - } - - return R"( - )" + formatTime(FORMAT_TIME, getLocalTime(entry.time)) + R"( - ) - )" + htmlTxt(entry.message.c_str()) + R"( - -)"; -} - - -std::wstring generateLogTitle(const ProcessSummary& s) -{ - std::wstring jobNamesFmt; - for (const std::wstring& jobName : s.jobNames) - jobNamesFmt += (jobNamesFmt.empty() ? L"" : L" + ") + jobName; - - std::wstring title = L"[FreeFileSync] "; - - if (!jobNamesFmt.empty()) - title += jobNamesFmt + L' '; - - switch (s.resultStatus) - { - case SyncResult::finishedSuccess: title += utfTo("\xe2\x9c\x94\xef\xb8\x8f"); break; - case SyncResult::finishedWarning: title += utfTo("\xe2\x9a\xa0"); break; - case SyncResult::finishedError: - case SyncResult::aborted: title += utfTo("\xe2\x9d\x8c"); break; - } - return title; -} - - -std::string generateLogHeaderHtml(const ProcessSummary& s, const ErrorLog& log, int logFailsPreviewMax) -{ - std::string output = R"( - - - - - )" + htmlTxt(generateLogTitle(s)) + R"( - - - -)"; - - std::string jobNamesFmt; - for (const std::wstring& jobName : s.jobNames) - jobNamesFmt += (jobNamesFmt.empty() ? "" : " + ") + htmlTxt(jobName); - - const TimeComp tc = getLocalTime(std::chrono::system_clock::to_time_t(s.startTime)); //returns empty string on failure - output += R"(
)" + jobNamesFmt + R"(  )" + - formatTime(FORMAT_DATE, tc) + "  " + formatTime(FORMAT_TIME, tc) + "
\n"; - - std::string resultsStatusImage; - switch (s.resultStatus) - { - case SyncResult::finishedSuccess: resultsStatusImage = "result-succes.png"; break; - case SyncResult::finishedWarning: resultsStatusImage = "result-warning.png"; break; - case SyncResult::finishedError: - case SyncResult::aborted: resultsStatusImage = "result-error.png"; break; - } - output += R"( -
-
- - )" + htmlTxt(getResultsStatusLabel(s.resultStatus)) + R"( -
- )"; - - const int errorCount = log.getItemCount(MSG_TYPE_ERROR | MSG_TYPE_FATAL_ERROR); - const int warningCount = log.getItemCount(MSG_TYPE_WARNING); - - if (errorCount > 0) - output += R"( - - - - - )"; - - if (warningCount > 0) - output += R"( - - - - - )"; - - output += R"( - - - - - )"; - - if ((s.statsTotal.items < 0 && s.statsTotal.bytes < 0) || //no total items/bytes: e.g. for pure folder comparison - s.statsProcessed == s.statsTotal) //...if everything was processed successfully - ; - else - output += R"( - - - - - )"; - - const int64_t totalTimeSec = std::chrono::duration_cast(s.totalTime).count(); - output += R"( - - - - - - -
-)"; - - //------------ warnings/errors preview ---------------- - const int logFailTotal = errorCount + warningCount; - if (logFailTotal > 0) - { - output += R"( -
)" + htmlTxt(_("Errors and warnings:")) + R"(
-
- -)"; - int previewCount = 0; - for (const LogEntry& entry : log) - if (entry.type & (MSG_TYPE_WARNING | MSG_TYPE_ERROR | MSG_TYPE_FATAL_ERROR)) - { - output += formatMessageHtml(entry); - if (++previewCount >= logFailsPreviewMax) - break; - } - output += R"(
-)"; - if (logFailTotal > previewCount) - output += R"(
[…])" + - htmlTxt(replaceCpy(_P("Showing %y of 1 row", "Showing %y of %x rows", logFailTotal), //%x used as plural form placeholder! - L"%y", formatNumber(previewCount))) + "
\n"; - - output += R"(

-)"; - } - - output += R"( - -)"; - return output; -} - -std::string generateLogFooterHtml(const std::wstring& logFilePath, int logItemsTotal, int logItemsPreviewMax) //throw FileError -{ - const std::string osImage = "os-linux.png"; - const ComputerModel cm = getComputerModel(); //throw FileError - - std::string output = R"(
-)"; - - if (logItemsTotal > logItemsPreviewMax) - output += R"(
[…])" + - htmlTxt(replaceCpy(_P("Showing %y of 1 row", "Showing %y of %x rows", logItemsTotal), //%x used as plural form placeholder! - L"%y", formatNumber(logItemsPreviewMax))) + "
\n"; - - return output += R"(
- -
- - )" + htmlTxt(getOsDescription()) + /*throw FileError*/ + - " [" + htmlTxt(getUserName()) /*throw FileError*/ + "] – " + htmlTxt(cm.model) + " – " + htmlTxt(cm.vendor) + R"( -
-
-
- ) - )" + htmlTxt(logFilePath) + R"( -
- - -)"; -} - -//-> Astyle fucks up! => no INDENT-ON - - -void streamToLogFile(const ProcessSummary& summary, //throw FileError - const ErrorLog& log, - AFS::OutputStream& streamOut, - const AbstractPath& logFilePath) -{ -#if 0 - auto fmtForTxtFile = [needLbReplace = !equalString(LINE_BREAK, '\n')](const std::string& str) - { - if (needLbReplace) - return replaceCpy(str, '\n', LINE_BREAK); - return str; - }; - - std::string buffer = fmtForTxtFile(generateLogHeaderTxt(summary, log, LOG_FAIL_PREVIEW_MAX)); //don't replace line break any earlier - - //write log items in blocks instead of creating one big string: memory allocation might fail; think 1 million entries! - for (const LogEntry& entry : log) - { - buffer += fmtForTxtFile(utfTo(formatMessage(entry))); - - streamOut.write(&buffer[0], buffer.size()); //throw FileError, X - buffer.clear(); - } - - const int logItemsTotal = log.end() - log.begin(); - const int logItemsPreviewMax = std::numeric_limits::max(); - - buffer += fmtForTxtFile(generateLogFooterTxt(AFS::getDisplayPath(logFilePath), logItemsTotal, logItemsPreviewMax)); //throw FileError - - //don't forget to flush: - streamOut.write(&buffer[0], buffer.size()); //throw FileError, X - -#else - std::string buffer = generateLogHeaderHtml(summary, log, LOG_FAIL_PREVIEW_MAX); - - //write log items in blocks instead of creating one big string: memory allocation might fail; think 1 million entries! - for (const LogEntry& entry : log) - { - buffer += formatMessageHtml(entry); - - streamOut.write(&buffer[0], buffer.size()); //throw FileError, X - buffer.clear(); - } - - const int logItemsTotal = log.end() - log.begin(); - const int logItemsPreviewMax = std::numeric_limits::max(); - - buffer += generateLogFooterHtml(AFS::getDisplayPath(logFilePath), logItemsTotal, logItemsPreviewMax); //throw FileError - - //don't forget to flush: - streamOut.write(&buffer[0], buffer.size()); //throw FileError, X -#endif -} - - -void saveNewLogFile(const AbstractPath& logFilePath, //throw FileError, X - const ProcessSummary& summary, - const ErrorLog& log, - const std::function& notifyStatus /*throw X*/) -{ - //create logfile folder if required - if (const std::optional parentPath = AFS::getParentPath(logFilePath)) - try - { - AFS::createFolderIfMissingRecursion(*parentPath); //throw FileError - } - catch (const FileError& e) //add context info regarding log file! - { - throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(AFS::getDisplayPath(logFilePath))), e.toString()); - } - //----------------------------------------------------------------------- - - auto notifyUnbufferedIO = [notifyStatus, - bytesWritten_ = int64_t(0), - msg_ = replaceCpy(_("Saving file %x..."), L"%x", fmtPath(AFS::getDisplayPath(logFilePath)))] - (int64_t bytesDelta) mutable - { - if (notifyStatus) - notifyStatus(msg_ + L" (" + formatFilesizeShort(bytesWritten_ += bytesDelta) + L")"); //throw X - }; - - std::unique_ptr logFileStream = AFS::getOutputStream(logFilePath, std::nullopt /*streamSize*/, std::nullopt /*modTime*/, notifyUnbufferedIO); //throw FileError - streamToLogFile(summary, log, *logFileStream, logFilePath); //throw FileError, X - logFileStream->finalize(); //throw FileError, X -} - - -const int TIME_STAMP_LENGTH = 21; -const Zchar STATUS_BEGIN_TOKEN[] = Zstr(" ["); -const Zchar STATUS_END_TOKEN = Zstr(']'); - - -struct LogFileInfo -{ - AbstractPath filePath; - time_t timeStamp; - std::wstring jobNames; //may be empty -}; -std::vector getLogFiles(const AbstractPath& logFolderPath) //throw FileError -{ - std::vector logfiles; - - AFS::traverseFolderFlat(logFolderPath, [&](const AFS::FileInfo& fi) //throw FileError - { - //"Backup FreeFileSync 2013-09-15 015052.123.html" - //"Jobname1 + Jobname2 2013-09-15 015052.123.log" - //"2013-09-15 015052.123 [Error].log" - static_assert(TIME_STAMP_LENGTH == 21); - - if (endsWith(fi.itemName, Zstr(".log")) || //case-sensitive: e.g. ".LOG" is not from FFS, right? - endsWith(fi.itemName, Zstr(".html"))) - { - auto tsBegin = fi.itemName.begin(); - auto tsEnd = tsBegin + fi.itemName.rfind('.'); - - if (tsBegin != tsEnd && tsEnd[-1] == STATUS_END_TOKEN) - tsEnd = searchLast(tsBegin, tsEnd, - std::begin(STATUS_BEGIN_TOKEN), std::end(STATUS_BEGIN_TOKEN) - 1); - - if (tsEnd - tsBegin >= TIME_STAMP_LENGTH && - tsEnd[-4] == Zstr('.') && - isdigit(tsEnd[-3]) && - isdigit(tsEnd[-2]) && - isdigit(tsEnd[-1])) - { - tsBegin = tsEnd - TIME_STAMP_LENGTH; - const TimeComp tc = parseTime(Zstr("%Y-%m-%d %H%M%S"), makeStringView(tsBegin, 17)); //returns TimeComp() on error - const time_t t = localToTimeT(tc); //returns -1 on error - if (t != -1) - { - Zstring jobNames(fi.itemName.begin(), tsBegin); - if (!jobNames.empty()) - { - assert(jobNames.size() >= 2 && endsWith(jobNames, Zstr(' '))); - jobNames.pop_back(); - } - - logfiles.push_back({ AFS::appendRelPath(logFolderPath, fi.itemName), t, utfTo(jobNames) }); - } - } - } - }, - nullptr /*onFolder*/, //traverse only one level deep - nullptr /*onSymlink*/); - - return logfiles; -} - - -void limitLogfileCount(const AbstractPath& logFolderPath, //throw FileError, X - int logfilesMaxAgeDays, //<= 0 := no limit - const std::set& logFilePathsToKeep, - const std::function& notifyStatus /*throw X*/) -{ - if (logfilesMaxAgeDays > 0) - { - const std::wstring statusPrefix = _("Cleaning up log files:") + L" [" + _P("1 day", "%x days", logfilesMaxAgeDays) + L"] "; - - if (notifyStatus) notifyStatus(statusPrefix + fmtPath(AFS::getDisplayPath(logFolderPath))); //throw X - - std::vector logFiles = getLogFiles(logFolderPath); //throw FileError - - const time_t lastMidnightTime = [] - { - TimeComp tc = getLocalTime(); //returns TimeComp() on error - tc.second = 0; - tc.minute = 0; - tc.hour = 0; - return localToTimeT(tc); //returns -1 on error => swallow => no versions trimmed by versionMaxAgeDays - }(); - const time_t cutOffTime = lastMidnightTime - static_cast(logfilesMaxAgeDays) * 24 * 3600; - - std::exception_ptr firstError; - - for (const LogFileInfo& lfi : logFiles) - if (lfi.timeStamp < cutOffTime && - !contains(logFilePathsToKeep, lfi.filePath)) //don't trim latest log files corresponding to last used config files! - //nitpicker's corner: what about path differences due to case? e.g. user-overriden log file path changed in case - { - if (notifyStatus) notifyStatus(statusPrefix + fmtPath(AFS::getDisplayPath(lfi.filePath))); //throw X - try - { - AFS::removeFilePlain(lfi.filePath); //throw FileError - } - catch (const FileError&) { if (!firstError) firstError = std::current_exception(); }; - } - - if (firstError) //late failure! - std::rethrow_exception(firstError); - } -} -} - - -Zstring fff::getDefaultLogFolderPath() { return getConfigDirPathPf() + Zstr("Logs") ; } - - -//"Backup FreeFileSync 2013-09-15 015052.123.html" -//"Backup FreeFileSync 2013-09-15 015052.123 [Error].html" -AbstractPath fff::generateLogFilePath(const ProcessSummary& summary, const Zstring& altLogFolderPathPhrase /*optional*/) -{ - //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://freefilesync.org/forum/viewtopic.php?t=1679 - - //assemble logfile name - const TimeComp tc = getLocalTime(std::chrono::system_clock::to_time_t(summary.startTime)); - if (tc == TimeComp()) - throw FileError(L"Failed to determine current time: " + numberTo(summary.startTime.time_since_epoch().count())); - - const auto timeMs = std::chrono::duration_cast(summary.startTime.time_since_epoch()).count() % 1000; - assert(std::chrono::duration_cast(summary.startTime.time_since_epoch()).count() == std::chrono::system_clock::to_time_t(summary.startTime)); - - Zstring logFileName; - if (!summary.jobNames.empty()) - { - for (const std::wstring& jobName : summary.jobNames) - logFileName += utfTo(jobName) + Zstr(" + "); - logFileName.resize(logFileName.size() - 2); - } - - logFileName += formatTime(Zstr("%Y-%m-%d %H%M%S"), tc) + - Zstr(".") + printNumber(Zstr("%03d"), static_cast(timeMs)); //[ms] should yield a fairly unique name - static_assert(TIME_STAMP_LENGTH == 21); - - const std::wstring failStatus = [&] - { - switch (summary.resultStatus) - { - case SyncResult::finishedSuccess: break; - case SyncResult::finishedWarning: return _("Warning"); - case SyncResult::finishedError: return _("Error"); - case SyncResult::aborted: return _("Stopped"); - } - return std::wstring(); - }(); - - if (!failStatus.empty()) - logFileName += STATUS_BEGIN_TOKEN + utfTo(failStatus) + STATUS_END_TOKEN; - logFileName += Zstr(".html"); - - - AbstractPath logFolderPath = createAbstractPath(altLogFolderPathPhrase); - if (AFS::isNullPath(logFolderPath)) - logFolderPath = createAbstractPath(getDefaultLogFolderPath()); - - return AFS::appendRelPath(logFolderPath, logFileName); -} - - -void fff::saveLogFile(const AbstractPath& logFilePath, //throw FileError, X - const ProcessSummary& summary, - const ErrorLog& log, - int logfilesMaxAgeDays, - const std::set& logFilePathsToKeep, - const std::function& notifyStatus /*throw X*/) -{ - std::exception_ptr firstError; - try - { - saveNewLogFile(logFilePath, summary, log, notifyStatus); //throw FileError, X - } - catch (const FileError&) { if (!firstError) firstError = std::current_exception(); }; - - try - { - const std::optional logFolderPath = AFS::getParentPath(logFilePath); - assert(logFolderPath); - if (logFolderPath) //else: logFilePath == device root; not possible with generateLogFilePath() - limitLogfileCount(*logFolderPath, logfilesMaxAgeDays, logFilePathsToKeep, notifyStatus); //throw FileError, X - } - catch (const FileError&) { if (!firstError) firstError = std::current_exception(); }; - - if (firstError) //late failure! - std::rethrow_exception(firstError); -} - - - - -void fff::sendLogAsEmail(const Zstring& email, //throw FileError, X - const ProcessSummary& summary, - const ErrorLog& log, - const AbstractPath& logFilePath, - const std::function& notifyStatus /*throw X*/) -{ - try - { - throw SysError(_("Requires FreeFileSync Donation Edition")); - } - catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot send notification email to %x."), L"%x", utfTo(email)), e.toString()); } -} diff --git a/FreeFileSync/Source/base/log_file.h b/FreeFileSync/Source/base/log_file.h deleted file mode 100644 index 3a3a481c..00000000 --- a/FreeFileSync/Source/base/log_file.h +++ /dev/null @@ -1,38 +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 GENERATE_LOGFILE_H_931726432167489732164 -#define GENERATE_LOGFILE_H_931726432167489732164 - -#include -#include -#include "return_codes.h" -#include "status_handler.h" -#include "../afs/abstract.h" - - -namespace fff -{ -Zstring getDefaultLogFolderPath(); - - -AbstractPath generateLogFilePath(const ProcessSummary& summary, const Zstring& altLogFolderPathPhrase /*optional*/); - -void saveLogFile(const AbstractPath& logFilePath, //throw FileError, X - const ProcessSummary& summary, - const zen::ErrorLog& log, - int logfilesMaxAgeDays, - const std::set& logFilePathsToKeep, - const std::function& notifyStatus /*throw X*/); - -void sendLogAsEmail(const Zstring& email, //throw FileError, X - const ProcessSummary& summary, - const zen::ErrorLog& log, - const AbstractPath& logFilePath, - const std::function& notifyStatus /*throw X*/); -} - -#endif //GENERATE_LOGFILE_H_931726432167489732164 diff --git a/FreeFileSync/Source/base/parallel_scan.cpp b/FreeFileSync/Source/base/parallel_scan.cpp index df2839fb..0bac2483 100644 --- a/FreeFileSync/Source/base/parallel_scan.cpp +++ b/FreeFileSync/Source/base/parallel_scan.cpp @@ -167,7 +167,7 @@ private: filePath = currentFile_; } if (parallelOpsTotal >= 2) - return L"[" + _P("1 thread", "%x threads", parallelOpsTotal) + L"] " + filePath; + return L'[' + _P("1 thread", "%x threads", parallelOpsTotal) + L"] " + filePath; else return filePath; } @@ -202,8 +202,8 @@ struct TraverserConfig const FilterRef filter; const SymLinkHandling handleSymlinks; - std::map& failedDirReads; - std::map& failedItemReads; + std::map& failedDirReads; + std::map& failedItemReads; AsyncCallback& acb; const int threadIdx; @@ -387,9 +387,9 @@ DirCallback::HandleError DirCallback::reportError(const std::wstring& msg, size_ { case ON_ERROR_CONTINUE: if (itemName.empty()) - cfg_.failedDirReads.emplace(beforeLast(parentRelPathPf_, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE), msg); + cfg_.failedDirReads.emplace(beforeLast(parentRelPathPf_, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE), utfTo(msg)); else - cfg_.failedItemReads.emplace(parentRelPathPf_ + itemName, msg); + cfg_.failedItemReads.emplace(parentRelPathPf_ + itemName, utfTo(msg)); return ON_ERROR_CONTINUE; case ON_ERROR_RETRY: diff --git a/FreeFileSync/Source/base/parallel_scan.h b/FreeFileSync/Source/base/parallel_scan.h index 9aa4bc82..1852d3fd 100644 --- a/FreeFileSync/Source/base/parallel_scan.h +++ b/FreeFileSync/Source/base/parallel_scan.h @@ -44,10 +44,10 @@ struct DirectoryValue FolderContainer folderCont; //relative paths (or empty string for root) for directories that could not be read (completely), e.g. access denied, or temporary network drop - std::map failedFolderReads; //with corresponding error message + std::map failedFolderReads; //with corresponding error message //relative paths (never empty) for failure to read single file/dir/symlink with corresponding error message - std::map failedItemReads; + std::map failedItemReads; }; diff --git a/FreeFileSync/Source/base/parse_lng.h b/FreeFileSync/Source/base/parse_lng.h deleted file mode 100644 index 361628e4..00000000 --- a/FreeFileSync/Source/base/parse_lng.h +++ /dev/null @@ -1,824 +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 PARSE_LNG_H_46794693622675638 -#define PARSE_LNG_H_46794693622675638 - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "parse_plural.h" - - -namespace lng -{ -//singular forms -using TranslationMap = std::map ; //orig |-> translation - -//plural forms -using SingularPluralPair = std::pair; //1 house | %x houses -using PluralForms = std::vector; //1 dom | 2 domy | %x domów -using TranslationPluralMap = std::map; //(sing/plu) |-> pluralforms - -struct TransHeader -{ - std::string languageName; //display name: "English (UK)" - std::string translatorName; //"Zenju" - std::string localeName; //ISO 639 language code + ISO 3166 country code, e.g. "en_GB", or "en_US" - std::string flagFile; //"england.png" - int pluralCount = 0; //2 - std::string pluralDefinition; //"n == 1 ? 0 : 1" -}; - - -struct ParsingError -{ - std::wstring msg; - size_t row = 0; //starting with 0 - size_t col = 0; // -}; -TransHeader parseHeader(const std::string& fileStream); //throw ParsingError -void parseLng(const std::string& fileStream, TransHeader& header, TranslationMap& out, TranslationPluralMap& pluralOut); //throw ParsingError - -class TranslationUnorderedList; //unordered list of unique translation items -std::string generateLng(const TranslationUnorderedList& in, const TransHeader& header); - - - - - - - - - - - - - - - - - - - -//--------------------------- implementation --------------------------- -enum class TranslationNewItemPos -{ - REL, - TOP -}; - -class TranslationUnorderedList //unordered list of unique translation items -{ -public: - TranslationUnorderedList(TranslationNewItemPos newItemPos, TranslationMap&& transOld, TranslationPluralMap&& transPluralOld) : - newItemPos_(newItemPos), transOld_(std::move(transOld)), transPluralOld_(std::move(transPluralOld)) {} - - void addItem(const std::string& orig) - { - if (!transUnique_.insert(orig).second) return; - auto it = transOld_.find(orig); - if (it != transOld_.end() && !it->second.empty()) //preserve old translation from .lng file if existing - sequence_.push_back(std::make_shared(std::make_pair(orig, it->second))); - else - switch (newItemPos_) - { - case TranslationNewItemPos::REL: - sequence_.push_back(std::make_shared(std::make_pair(orig, std::string()))); - break; - case TranslationNewItemPos::TOP: - sequence_.push_front(std::make_shared(std::make_pair(orig, std::string()))); //put untranslated items to the front of the .lng filebreak; - break; - } - } - - void addItem(const SingularPluralPair& orig) - { - if (!pluralUnique_.insert(orig).second) return; - auto it = transPluralOld_.find(orig); - if (it != transPluralOld_.end() && !it->second.empty()) //preserve old translation from .lng file if existing - sequence_.push_back(std::make_shared(std::make_pair(orig, it->second))); - else - switch (newItemPos_) - { - case TranslationNewItemPos::REL: - sequence_.push_back(std::make_shared(std::make_pair(orig, PluralForms()))); - break; - case TranslationNewItemPos::TOP: - sequence_.push_front(std::make_shared(std::make_pair(orig, PluralForms()))); //put untranslated items to the front of the .lng file - break; - } - } - - bool untranslatedTextExists() const { return std::any_of(sequence_.begin(), sequence_.end(), [](const std::shared_ptr& item) { return !item->hasTranslation(); }); } - - template - void visitItems(Function onTrans, Function2 onPluralTrans) const //onTrans takes (const TranslationMap::value_type&), onPluralTrans takes (const TranslationPluralMap::value_type&) - { - for (const std::shared_ptr& item : sequence_) - if (auto regular = dynamic_cast(item.get())) - onTrans(regular->value); - else if (auto plural = dynamic_cast(item.get())) - onPluralTrans(plural->value); - else assert(false); - } - -private: - struct Item { virtual ~Item() {} virtual bool hasTranslation() const = 0; }; - struct RegularItem : public Item { RegularItem(const TranslationMap ::value_type& val) : value(val) {} bool hasTranslation() const override { return !value.second.empty(); } TranslationMap ::value_type value; }; - struct PluralItem : public Item { PluralItem (const TranslationPluralMap::value_type& val) : value(val) {} bool hasTranslation() const override { return !value.second.empty(); } TranslationPluralMap::value_type value; }; - - const TranslationNewItemPos newItemPos_; - std::list> sequence_; //ordered list of translation elements - - std::set transUnique_; //check uniqueness - std::set pluralUnique_; // - - const TranslationMap transOld_; //reuse existing translation - const TranslationPluralMap transPluralOld_; // -}; - - -struct Token -{ - enum Type - { - //header information - TK_HEADER_BEGIN, - TK_HEADER_END, - TK_LANG_NAME_BEGIN, - TK_LANG_NAME_END, - TK_TRANS_NAME_BEGIN, - TK_TRANS_NAME_END, - TK_LOCALE_NAME_BEGIN, - TK_LOCALE_NAME_END, - TK_FLAG_FILE_BEGIN, - TK_FLAG_FILE_END, - TK_PLURAL_COUNT_BEGIN, - TK_PLURAL_COUNT_END, - TK_PLURAL_DEF_BEGIN, - TK_PLURAL_DEF_END, - - //item level - TK_SRC_BEGIN, - TK_SRC_END, - TK_TRG_BEGIN, - TK_TRG_END, - TK_TEXT, - TK_PLURAL_BEGIN, - TK_PLURAL_END, - TK_END - }; - - Token(Type t) : type(t) {} - - Type type; - std::string text; -}; - - -class KnownTokens -{ -public: - KnownTokens() {} //clang wants it, clang gets it - - using TokenMap = std::map; - - const TokenMap& getList() const { return tokens_; } - - std::string text(Token::Type t) const - { - auto it = tokens_.find(t); - if (it != tokens_.end()) - return it->second; - assert(false); - return std::string(); - } - -private: - const TokenMap tokens_ = - { - //header information - { Token::TK_HEADER_BEGIN, "
" }, - { Token::TK_HEADER_END, "
" }, - { Token::TK_LANG_NAME_BEGIN, "" }, - { Token::TK_LANG_NAME_END, "" }, - { Token::TK_TRANS_NAME_BEGIN, "" }, - { Token::TK_TRANS_NAME_END, "" }, - { Token::TK_LOCALE_NAME_BEGIN, "" }, - { Token::TK_LOCALE_NAME_END, "" }, - { Token::TK_FLAG_FILE_BEGIN, "" }, - { Token::TK_FLAG_FILE_END, "" }, - { Token::TK_PLURAL_COUNT_BEGIN, "" }, - { Token::TK_PLURAL_COUNT_END, "" }, - { Token::TK_PLURAL_DEF_BEGIN, "" }, - { Token::TK_PLURAL_DEF_END, "" }, - - //item level - { Token::TK_SRC_BEGIN, "" }, - { Token::TK_SRC_END, "" }, - { Token::TK_TRG_BEGIN, "" }, - { Token::TK_TRG_END, "" }, - { Token::TK_PLURAL_BEGIN, "" }, - { Token::TK_PLURAL_END, "" }, - }; -}; - - -class Scanner -{ -public: - Scanner(const std::string& byteStream) : stream_(byteStream), pos_(stream_.begin()) - { - if (zen::startsWith(stream_, zen::BYTE_ORDER_MARK_UTF8)) - pos_ += zen::strLength(zen::BYTE_ORDER_MARK_UTF8); - } - - Token getNextToken() - { - //skip whitespace - pos_ = std::find_if_not(pos_, stream_.end(), zen::isWhiteSpace); - - if (pos_ == stream_.end()) - return Token(Token::TK_END); - - for (const auto& [tokenEnum, tokenString] : tokens_.getList()) - if (startsWith(tokenString)) - { - pos_ += tokenString.size(); - return Token(tokenEnum); - } - - //rest must be "text" - auto itBegin = pos_; - while (pos_ != stream_.end() && !startsWithKnownTag()) - pos_ = std::find(pos_ + 1, stream_.end(), '<'); - - std::string text(itBegin, pos_); - - normalize(text); //remove whitespace from end etc. - - if (text.empty() && pos_ == stream_.end()) - return Token(Token::TK_END); - - Token out(Token::TK_TEXT); - out.text = std::move(text); - return out; - } - - size_t posRow() const //current row beginning with 0 - { - //count line endings - const size_t crSum = std::count(stream_.begin(), pos_, '\r'); //carriage returns - const size_t nlSum = std::count(stream_.begin(), pos_, '\n'); //new lines - assert(crSum == 0 || nlSum == 0 || crSum == nlSum); - return std::max(crSum, nlSum); //be compatible with Linux/Mac/Win - } - - size_t posCol() const //current col beginning with 0 - { - //seek beginning of line - for (auto it = pos_; it != stream_.begin(); ) - { - --it; - if (zen::isLineBreak(*it)) - return pos_ - it - 1; - } - return pos_ - stream_.begin(); - } - -private: - bool startsWithKnownTag() const - { - return std::any_of(tokens_.getList().begin(), tokens_.getList().end(), - [&](const KnownTokens::TokenMap::value_type& p) { return startsWith(p.second); }); - } - - bool startsWith(const std::string& prefix) const - { - return zen::startsWith(zen::makeStringView(pos_, stream_.end()), prefix); - } - - static void normalize(std::string& text) - { - zen::trim(text); //remove whitespace from both ends - - //Delimiter: - //---------- - //Linux: 0xA \n - //Mac: 0xD \r - //Win: 0xD 0xA \r\n <- language files are in Windows format - zen::replace(text, "\r\n", '\n'); // - zen::replace(text, "\r", '\n'); //ensure c-style line breaks - } - - const std::string stream_; - std::string::const_iterator pos_; - const KnownTokens tokens_; //no need for static non-POD! -}; - - -class LngParser -{ -public: - LngParser(const std::string& fileStream) : scn_(fileStream), tk_(scn_.getNextToken()) {} - - void parse(TranslationMap& out, TranslationPluralMap& pluralOut, TransHeader& header) - { - parseHeader(header); - - try - { - plural::PluralFormInfo pi(header.pluralDefinition, header.pluralCount); - - //items - while (token().type != Token::TK_END) - parseRegular(out, pluralOut, pi); - } - catch (const plural::InvalidPluralForm&) - { - throw ParsingError({ L"Invalid plural form definition", scn_.posRow(), scn_.posCol() }); - } - } - - void parseHeader(TransHeader& header) - { - consumeToken(Token::TK_HEADER_BEGIN); //throw ParsingError - - consumeToken(Token::TK_LANG_NAME_BEGIN); //throw ParsingError - header.languageName = token().text; - consumeToken(Token::TK_TEXT); //throw ParsingError - consumeToken(Token::TK_LANG_NAME_END); // - - consumeToken(Token::TK_TRANS_NAME_BEGIN); //throw ParsingError - header.translatorName = token().text; - consumeToken(Token::TK_TEXT); //throw ParsingError - consumeToken(Token::TK_TRANS_NAME_END); // - - consumeToken(Token::TK_LOCALE_NAME_BEGIN); //throw ParsingError - header.localeName = token().text; - consumeToken(Token::TK_TEXT); //throw ParsingError - consumeToken(Token::TK_LOCALE_NAME_END); // - - consumeToken(Token::TK_FLAG_FILE_BEGIN); //throw ParsingError - header.flagFile = token().text; - consumeToken(Token::TK_TEXT); //throw ParsingError - consumeToken(Token::TK_FLAG_FILE_END); // - - consumeToken(Token::TK_PLURAL_COUNT_BEGIN); //throw ParsingError - header.pluralCount = zen::stringTo(token().text); - consumeToken(Token::TK_TEXT); //throw ParsingError - consumeToken(Token::TK_PLURAL_COUNT_END); // - - consumeToken(Token::TK_PLURAL_DEF_BEGIN); //throw ParsingError - header.pluralDefinition = token().text; - consumeToken(Token::TK_TEXT); //throw ParsingError - consumeToken(Token::TK_PLURAL_DEF_END); // - - consumeToken(Token::TK_HEADER_END); //throw ParsingError - } - -private: - void parseRegular(TranslationMap& out, TranslationPluralMap& pluralOut, const plural::PluralFormInfo& pluralInfo) - { - consumeToken(Token::TK_SRC_BEGIN); //throw ParsingError - - if (token().type == Token::TK_PLURAL_BEGIN) - return parsePlural(pluralOut, pluralInfo); - - std::string original = token().text; - consumeToken(Token::TK_TEXT); //throw ParsingError - consumeToken(Token::TK_SRC_END); // - - consumeToken(Token::TK_TRG_BEGIN); //throw ParsingError - std::string translation; - if (token().type == Token::TK_TEXT) - { - translation = token().text; - nextToken(); - } - validateTranslation(original, translation); //throw ParsingError - consumeToken(Token::TK_TRG_END); // - - out.emplace(original, translation); - } - - void parsePlural(TranslationPluralMap& pluralOut, const plural::PluralFormInfo& pluralInfo) - { - //Token::TK_SRC_BEGIN already consumed - - consumeToken(Token::TK_PLURAL_BEGIN); //throw ParsingError - std::string engSingular = token().text; - consumeToken(Token::TK_TEXT); //throw ParsingError - consumeToken(Token::TK_PLURAL_END); // - - consumeToken(Token::TK_PLURAL_BEGIN); //throw ParsingError - std::string engPlural = token().text; - consumeToken(Token::TK_TEXT); //throw ParsingError - consumeToken(Token::TK_PLURAL_END); // - - consumeToken(Token::TK_SRC_END); //throw ParsingError - const SingularPluralPair original(engSingular, engPlural); - - consumeToken(Token::TK_TRG_BEGIN); //throw ParsingError - - PluralForms pluralList; - while (token().type == Token::TK_PLURAL_BEGIN) - { - consumeToken(Token::TK_PLURAL_BEGIN); //throw ParsingError - std::string pluralForm = token().text; - consumeToken(Token::TK_TEXT); //throw ParsingError - consumeToken(Token::TK_PLURAL_END); // - pluralList.push_back(pluralForm); - } - validateTranslation(original, pluralList, pluralInfo); - consumeToken(Token::TK_TRG_END); //throw ParsingError - - pluralOut.emplace(original, pluralList); - } - - void validateTranslation(const std::string& original, const std::string& translation) //throw ParsingError - { - using namespace zen; - - if (original.empty()) - throw ParsingError({ L"Translation source text is empty", scn_.posRow(), scn_.posCol() }); - - if (!isValidUtf(original)) - throw ParsingError({ L"Translation source text contains UTF-8 encoding error", scn_.posRow(), scn_.posCol() }); - if (!isValidUtf(translation)) - throw ParsingError({ L"Translation text contains UTF-8 encoding error", scn_.posRow(), scn_.posCol() }); - - if (!translation.empty()) - { - //if original contains placeholder, so must translation! - auto checkPlaceholder = [&](const std::string& placeholder) - { - if (contains(original, placeholder) && - !contains(translation, placeholder)) - throw ParsingError({ replaceCpy(L"Placeholder %x missing in translation", L"%x", utfTo(placeholder)), scn_.posRow(), scn_.posCol() }); - }; - checkPlaceholder("%x"); - checkPlaceholder("%y"); - checkPlaceholder("%z"); - - //if source is a one-liner, so should be the translation - if (!contains(original, '\n') && contains(translation, '\n')) - throw ParsingError({ L"Source text is a one-liner, but translation consists of multiple lines", scn_.posRow(), scn_.posCol() }); - - //if source contains ampersand to mark menu accellerator key, so must translation - const size_t ampCount = ampersandTokenCount(original); - if (ampCount > 1 || ampCount != ampersandTokenCount(translation)) - throw ParsingError({ L"Source and translation both need exactly one & character to mark a menu item access key or none at all", scn_.posRow(), scn_.posCol() }); - - //ampersand at the end makes buggy wxWidgets crash miserably - if (endsWithSingleAmp(original) || endsWithSingleAmp(translation)) - throw ParsingError({ L"The & character to mark a menu item access key must not occur at the end of a string", scn_.posRow(), scn_.posCol() }); - - //if source ends with colon, so must translation (note: character seems to be universally used, even for asian and arabic languages) - if (endsWith(original, ":") && !endsWithColon(translation)) - throw ParsingError({ L"Source text ends with a colon character \":\", but translation does not", scn_.posRow(), scn_.posCol() }); - - //if source ends with a period, so must translation (note: character seems to be universally used, even for asian and arabic languages) - if (endsWithSingleDot(original) && !endsWithSingleDot(translation)) - throw ParsingError({ L"Source text ends with a punctuation mark character \".\", but translation does not", scn_.posRow(), scn_.posCol() }); - - //if source ends with an ellipsis, so must translation (note: character seems to be universally used, even for asian and arabic languages) - if (endsWithEllipsis(original) && !endsWithEllipsis(translation)) - throw ParsingError({ L"Source text ends with an ellipsis \"...\", but translation does not", scn_.posRow(), scn_.posCol() }); - - //check for not-to-be-translated texts - for (const char* fixedStr : { "FreeFileSync", "RealTimeSync", "ffs_gui", "ffs_batch", "ffs_tmp", "GlobalSettings.xml" }) - if (contains(original, fixedStr) && !contains(translation, fixedStr)) - throw ParsingError({ replaceCpy(L"Misspelled \"%x\" in translation", L"%x", utfTo(fixedStr)), scn_.posRow(), scn_.posCol() }); - - //some languages (French!) put a space before punctuation mark => must be a no-brake space! - for (const char punctChar : std::string(".!?:;$#")) - if (contains(original, std::string(" ") + punctChar) || - contains(translation, std::string(" ") + punctChar)) - throw ParsingError({ replaceCpy(L"Text contains a space before the \"%x\" character. Are line-breaks really allowed here?" - " Maybe this should be a \"non-breaking space\" (Windows: Alt 0160 UTF8: 0xC2 0xA0)?", - L"%x", utfTo(punctChar)), scn_.posRow(), scn_.posCol() }); - } - } - - void validateTranslation(const SingularPluralPair& original, const PluralForms& translation, const plural::PluralFormInfo& pluralInfo) //throw ParsingError - { - using namespace zen; - - if (original.first.empty() || original.second.empty()) - throw ParsingError({ L"Translation source text is empty", scn_.posRow(), scn_.posCol() }); - - const std::vector allTexts = [&] - { - std::vector at{ original.first, original.second }; - at.insert(at.end(), translation.begin(), translation.end()); - return at; - }(); - - for (const std::string& str : allTexts) - if (!isValidUtf(str)) - throw ParsingError({ L"Text contains UTF-8 encoding error", scn_.posRow(), scn_.posCol() }); - - //check the primary placeholder is existing at least for the second english text - if (!contains(original.second, "%x")) - throw ParsingError({ L"Plural form source text does not contain %x placeholder", scn_.posRow(), scn_.posCol() }); - - if (!translation.empty()) - { - //check for invalid number of plural forms - if (pluralInfo.getCount() != static_cast(translation.size())) - throw ParsingError({ replaceCpy(replaceCpy(L"Invalid number of plural forms; actual: %x, expected: %y", L"%x", numberTo(translation.size())), L"%y", numberTo(pluralInfo.getCount())), scn_.posRow(), scn_.posCol() }); - - //check for duplicate plural form translations (catch copy & paste errors for single-number form translations) - for (auto it = translation.begin(); it != translation.end(); ++it) - if (!contains(*it, "%x")) - { - auto it2 = std::find(it + 1, translation.end(), *it); - if (it2 != translation.end()) - throw ParsingError({ replaceCpy(L"Duplicate plural form translation at index position %x", L"%x", numberTo(it2 - translation.begin())), scn_.posRow(), scn_.posCol() }); - } - - for (int pos = 0; pos < static_cast(translation.size()); ++pos) - if (pluralInfo.isSingleNumberForm(pos)) - { - //translation needs to use decimal number if english source does so (e.g. frequently changing text like statistics) - if (contains(original.first, "%x") || - contains(original.first, "1")) - { - const int firstNumber = pluralInfo.getFirstNumber(pos); - if (!(contains(translation[pos], "%x") || - contains(translation[pos], numberTo(firstNumber)))) - throw ParsingError({ replaceCpy(replaceCpy(L"Plural form translation at index position %y needs to use the decimal number %z or the %x placeholder", - L"%y", numberTo(pos)), L"%z", numberTo(firstNumber)), scn_.posRow(), scn_.posCol() }); - } - } - else - { - //ensure the placeholder is used when needed - if (!contains(translation[pos], "%x")) - throw ParsingError({ replaceCpy(L"Plural form at index position %y is missing the %x placeholder", L"%y", numberTo(pos)), scn_.posRow(), scn_.posCol() }); - } - - auto checkSecondaryPlaceholder = [&](const std::string& placeholder) - { - //make sure secondary placeholder is used for both source texts (or none) and all plural forms - if (contains(original.first, placeholder) || - contains(original.second, placeholder)) - for (const std::string& str : allTexts) - if (!contains(str, placeholder)) - throw ParsingError({ zen::replaceCpy(L"Placeholder %x missing in text", L"%x", zen::utfTo(placeholder)), scn_.posRow(), scn_.posCol() }); - }; - checkSecondaryPlaceholder("%y"); - checkSecondaryPlaceholder("%z"); - - //if source is a one-liner, so should be the translation - if (!contains(original.first, '\n') && !contains(original.second, '\n') && - /**/std::any_of(translation.begin(), translation.end(), [](const std::string& pform) { return contains(pform, '\n'); })) - /**/throw ParsingError({ L"Source text is a one-liner, but at least one plural form translation consists of multiple lines", scn_.posRow(), scn_.posCol() }); - - //if source contains ampersand to mark menu accellerator key, so must translation - const size_t ampCount = ampersandTokenCount(original.first); - for (const std::string& str : allTexts) - if (ampCount > 1 || ampersandTokenCount(str) != ampCount) - throw ParsingError({ L"Source and translation both need exactly one & character to mark a menu item access key or none at all", scn_.posRow(), scn_.posCol() }); - - //ampersand at the end makes buggy wxWidgets crash miserably - for (const std::string& str : allTexts) - if (endsWithSingleAmp(str)) - throw ParsingError({ L"The & character to mark a menu item access key must not occur at the end of a string", scn_.posRow(), scn_.posCol() }); - - //if source ends with colon, so must translation (note: character seems to be universally used, even for asian and arabic languages) - if (endsWith(original.first, ":") || endsWith(original.second, ":")) - for (const std::string& str : allTexts) - if (!endsWithColon(str)) - throw ParsingError({ L"Source text ends with a colon character \":\", but translation does not", scn_.posRow(), scn_.posCol() }); - - //if source ends with a period, so must translation (note: character seems to be universally used, even for asian and arabic languages) - if (endsWithSingleDot(original.first) || endsWithSingleDot(original.second)) - for (const std::string& str : allTexts) - if (!endsWithSingleDot(str)) - throw ParsingError({ L"Source text ends with a punctuation mark character \".\", but translation does not", scn_.posRow(), scn_.posCol() }); - - //if source ends with an ellipsis, so must translation (note: character seems to be universally used, even for asian and arabic languages) - if (endsWithEllipsis(original.first) || endsWithEllipsis(original.second)) - for (const std::string& str : allTexts) - if (!endsWithEllipsis(str)) - throw ParsingError({ L"Source text ends with an ellipsis \"...\", but translation does not", scn_.posRow(), scn_.posCol() }); - - //check for not-to-be-translated texts - for (const char* fixedStr : { "FreeFileSync", "RealTimeSync", "ffs_gui", "ffs_batch", "ffs_tmp", "GlobalSettings.xml" }) - if (contains(original.first, fixedStr) || contains(original.second, fixedStr)) - for (const std::string& str : allTexts) - if (!contains(str, fixedStr)) - throw ParsingError({ replaceCpy(L"Misspelled \"%x\" in translation", L"%x", utfTo(fixedStr)), scn_.posRow(), scn_.posCol() }); - - //some languages (French!) put a space before punctuation mark => must be a no-brake space! - for (const char punctChar : std::string(".!?:;$#")) - for (const std::string& str : allTexts) - if (contains(str, std::string(" ") + punctChar)) - throw ParsingError({ replaceCpy(L"Text contains a space before the \"%x\" character. Are line-breaks really allowed here?" - " Maybe this should be a \"non-breaking space\" (Windows: Alt 0160 UTF8: 0xC2 0xA0)?", - L"%x", utfTo(punctChar)), scn_.posRow(), scn_.posCol() }); - } - } - - //helper - static size_t ampersandTokenCount(const std::string& str) - { - using namespace zen; - const std::string tmp = replaceCpy(str, "&&", ""); //make sure to not catch && which windows resolves as just one & for display! - return std::count(tmp.begin(), tmp.end(), '&'); - } - - static bool endsWithSingleAmp(const std::string& s) - { - using namespace zen; - return endsWith(s, "&") && !endsWith(s, "&&"); - } - - static bool endsWithEllipsis(const std::string& s) - { - using namespace zen; - return endsWith(s, "...") || - endsWith(s, "\xe2\x80\xa6"); //narrow ellipsis (spanish?) - } - - static bool endsWithColon(const std::string& s) - { - using namespace zen; - return endsWith(s, ":") || - endsWith(s, "\xef\xbc\x9a"); //chinese colon - } - - static bool endsWithSingleDot(const std::string& s) - { - using namespace zen; - return (endsWith(s, ".") || - endsWith(s, "\xe0\xa5\xa4") || //hindi period - endsWith(s, "\xe3\x80\x82")) //chinese period - && - (!endsWith(s, "..") && - !endsWith(s, "\xe0\xa5\xa4\xe0\xa5\xa4") && //hindi period - !endsWith(s, "\xe3\x80\x82\xe3\x80\x82")); //chinese period - } - - - const Token& token() const { return tk_; } - - void nextToken() { tk_ = scn_.getNextToken(); } - - void expectToken(Token::Type t) //throw ParsingError - { - if (token().type != t) - throw ParsingError({ L"Unexpected token", scn_.posRow(), scn_.posCol() }); - } - - void consumeToken(Token::Type t) //throw ParsingError - { - expectToken(t); //throw ParsingError - nextToken(); - } - - Scanner scn_; - Token tk_; -}; - - -inline -void parseLng(const std::string& fileStream, TransHeader& header, TranslationMap& out, TranslationPluralMap& pluralOut) //throw ParsingError -{ - out.clear(); - pluralOut.clear(); - - LngParser(fileStream).parse(out, pluralOut, header); -} - - -inline -TransHeader parseHeader(const std::string& fileStream) //throw ParsingError -{ - TransHeader header; - LngParser(fileStream).parseHeader(header); - return header; -} - - -inline -void formatMultiLineText(std::string& text) -{ - assert(!zen::contains(text, "\r\n")); - - if (zen::contains(text, '\n')) //multiple lines - { - if (*text.begin() != '\n') - text = '\n' + text; - if (*text.rbegin() != '\n') - text += '\n'; - } -} - - -std::string generateLng(const TranslationUnorderedList& in, const TransHeader& header) -{ - const KnownTokens tokens; //no need for static non-POD! - - std::string out; - //header - out += tokens.text(Token::TK_HEADER_BEGIN) + '\n'; - - out += '\t' + tokens.text(Token::TK_LANG_NAME_BEGIN); - out += header.languageName; - out += tokens.text(Token::TK_LANG_NAME_END) + '\n'; - - out += '\t' + tokens.text(Token::TK_TRANS_NAME_BEGIN); - out += header.translatorName; - out += tokens.text(Token::TK_TRANS_NAME_END) + '\n'; - - out += '\t' + tokens.text(Token::TK_LOCALE_NAME_BEGIN); - out += header.localeName; - out += tokens.text(Token::TK_LOCALE_NAME_END) + '\n'; - - out += '\t' + tokens.text(Token::TK_FLAG_FILE_BEGIN); - out += header.flagFile; - out += tokens.text(Token::TK_FLAG_FILE_END) + '\n'; - - out += '\t' + tokens.text(Token::TK_PLURAL_COUNT_BEGIN); - out += zen::numberTo(header.pluralCount); - out += tokens.text(Token::TK_PLURAL_COUNT_END) + '\n'; - - out += '\t' + tokens.text(Token::TK_PLURAL_DEF_BEGIN); - out += header.pluralDefinition; - out += tokens.text(Token::TK_PLURAL_DEF_END) + '\n'; - - out += tokens.text(Token::TK_HEADER_END) + '\n'; - - out += '\n'; - - - in.visitItems([&](const TranslationMap::value_type& trans) - { - std::string original = trans.first; - std::string translation = trans.second; - - formatMultiLineText(original); - formatMultiLineText(translation); - - out += tokens.text(Token::TK_SRC_BEGIN); - out += original; - out += tokens.text(Token::TK_SRC_END) + '\n'; - - out += tokens.text(Token::TK_TRG_BEGIN); - out += translation; - out += tokens.text(Token::TK_TRG_END) + '\n' + '\n'; - }, - [&](const TranslationPluralMap::value_type& transPlural) - { - std::string engSingular = transPlural.first.first; - std::string engPlural = transPlural.first.second; - const PluralForms& forms = transPlural.second; - - formatMultiLineText(engSingular); - formatMultiLineText(engPlural); - - out += tokens.text(Token::TK_SRC_BEGIN) + '\n'; - out += tokens.text(Token::TK_PLURAL_BEGIN); - out += engSingular; - out += tokens.text(Token::TK_PLURAL_END) + '\n'; - out += tokens.text(Token::TK_PLURAL_BEGIN); - out += engPlural; - out += tokens.text(Token::TK_PLURAL_END) + '\n'; - out += tokens.text(Token::TK_SRC_END) + '\n'; - - out += tokens.text(Token::TK_TRG_BEGIN); - if (!forms.empty()) //translators will be searching for "" - out += '\n'; - for (std::string plForm : forms) - { - formatMultiLineText(plForm); - - out += tokens.text(Token::TK_PLURAL_BEGIN); - out += plForm; - out += tokens.text(Token::TK_PLURAL_END) + '\n'; - } - out += tokens.text(Token::TK_TRG_END) + '\n' + '\n'; - }); - - assert(!zen::contains(out, "\r\n") && !zen::contains(out, "\r")); - return zen::replaceCpy(out, '\n', "\r\n"); //back to win line endings -} -} - -#endif //PARSE_LNG_H_46794693622675638 diff --git a/FreeFileSync/Source/base/parse_plural.h b/FreeFileSync/Source/base/parse_plural.h deleted file mode 100644 index 242dd4a6..00000000 --- a/FreeFileSync/Source/base/parse_plural.h +++ /dev/null @@ -1,482 +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 PARSE_PLURAL_H_180465845670839576 -#define PARSE_PLURAL_H_180465845670839576 - -#include -#include -#include -#include - - -namespace plural -{ -//expression interface -struct Expression { virtual ~Expression() {} }; - -template -struct Expr : public Expression -{ - virtual T eval() const = 0; -}; - - -class ParsingError {}; - -class PluralForm -{ -public: - PluralForm(const std::string& stream); //throw ParsingError - int getForm(int64_t n) const { n_ = std::abs(n) ; return static_cast(expr_->eval()); } - -private: - std::shared_ptr> expr_; - mutable int64_t n_ = 0; -}; - - -//validate plural form -class InvalidPluralForm {}; - -class PluralFormInfo -{ -public: - PluralFormInfo(const std::string& definition, int pluralCount); //throw InvalidPluralForm - - int getCount() const { return static_cast(forms_.size()); } - bool isSingleNumberForm(int index) const { return 0 <= index && index < static_cast(forms_.size()) ? forms_[index].count == 1 : false; } - int getFirstNumber (int index) const { return 0 <= index && index < static_cast(forms_.size()) ? forms_[index].firstNumber : -1; } - -private: - struct FormInfo - { - int count = 0; - int firstNumber = 0; //which maps to the plural form index position - }; - std::vector forms_; -}; - - - - - -//--------------------------- implementation --------------------------- - -//https://www.gnu.org/software/hello/manual/gettext/Plural-forms.html -//http://translate.sourceforge.net/wiki/l10n/pluralforms -/* -Grammar for Plural forms parser -------------------------------- -expression: - conditional-expression - -conditional-expression: - logical-or-expression - logical-or-expression ? expression : expression - -logical-or-expression: - logical-and-expression - logical-or-expression || logical-and-expression - -logical-and-expression: - equality-expression - logical-and-expression && equality-expression - -equality-expression: - relational-expression - relational-expression == relational-expression - relational-expression != relational-expression - -relational-expression: - multiplicative-expression - multiplicative-expression > multiplicative-expression - multiplicative-expression < multiplicative-expression - multiplicative-expression >= multiplicative-expression - multiplicative-expression <= multiplicative-expression - -multiplicative-expression: - pm-expression - multiplicative-expression % pm-expression - -pm-expression: - variable-number-n-expression - constant-number-expression - ( expression ) - - -.po format,e.g.: (n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2) -*/ - -namespace impl -{ -template -struct BinaryExp : public Expr -{ - using ExpLhs = std::shared_ptr>; - using ExpRhs = std::shared_ptr>; - - BinaryExp(const ExpLhs& lhs, const ExpRhs& rhs) : lhs_(lhs), rhs_(rhs) { assert(lhs && rhs); } - ResultType eval() const override { return BinaryOp()(lhs_->eval(), rhs_->eval()); } -private: - ExpLhs lhs_; - ExpRhs rhs_; -}; - - -template inline -std::shared_ptr makeBiExp(const std::shared_ptr& lhs, const std::shared_ptr& rhs) //throw ParsingError -{ - auto exLeft = std::dynamic_pointer_cast>(lhs); - auto exRight = std::dynamic_pointer_cast>(rhs); - if (!exLeft || !exRight) - throw ParsingError(); - - using ResultType = decltype(BinaryOp()(std::declval(), std::declval())); - return std::make_shared>(exLeft, exRight); -} - - -template -struct ConditionalExp : public Expr -{ - ConditionalExp(const std::shared_ptr>& ifExp, - const std::shared_ptr>& thenExp, - const std::shared_ptr>& elseExp) : ifExp_(ifExp), thenExp_(thenExp), elseExp_(elseExp) { assert(ifExp && thenExp && elseExp); } - - T eval() const override { return ifExp_->eval() ? thenExp_->eval() : elseExp_->eval(); } -private: - std::shared_ptr> ifExp_; - std::shared_ptr> thenExp_; - std::shared_ptr> elseExp_; -}; - - -struct ConstNumberExp : public Expr -{ - ConstNumberExp(int64_t n) : n_(n) {} - int64_t eval() const override { return n_; } -private: - int64_t n_; -}; - - -struct VariableNumberNExp : public Expr -{ - VariableNumberNExp(int64_t& n) : n_(n) {} - int64_t eval() const override { return n_; } -private: - int64_t& n_; -}; - -//------------------------------------------------------------------------------- - -struct Token -{ - enum Type - { - TK_TERNARY_QUEST, - TK_TERNARY_COLON, - TK_OR, - TK_AND, - TK_EQUAL, - TK_NOT_EQUAL, - TK_LESS, - TK_LESS_EQUAL, - TK_GREATER, - TK_GREATER_EQUAL, - TK_MODULUS, - TK_VARIABLE_N, - TK_CONST_NUMBER, - TK_BRACKET_LEFT, - TK_BRACKET_RIGHT, - TK_END - }; - - Token(Type t) : type(t) {} - Token(int64_t num) : number(num) {} - - Type type = TK_CONST_NUMBER; - int64_t number = 0; //if type == TK_CONST_NUMBER -}; - -class Scanner -{ -public: - Scanner(const std::string& stream) : stream_(stream), pos_(stream_.begin()) {} - - Token getNextToken() //throw ParsingError - { - //skip whitespace - pos_ = std::find_if_not(pos_, stream_.end(), zen::isWhiteSpace); - - if (pos_ == stream_.end()) - return Token::TK_END; - - for (const auto& [tokenString, tokenEnum] : tokens_) - if (startsWith(tokenString)) - { - pos_ += tokenString.size(); - return Token(tokenEnum); - } - - auto digitEnd = std::find_if_not(pos_, stream_.end(), zen::isDigit); - if (pos_ == digitEnd) - throw ParsingError(); //unknown token - - auto number = zen::stringTo(std::string(pos_, digitEnd)); - pos_ = digitEnd; - return number; - } - -private: - bool startsWith(const std::string& prefix) const - { - return zen::startsWith(zen::makeStringView(pos_, stream_.end()), prefix); - } - - using TokenList = std::vector>; - const TokenList tokens_ - { - { "?", Token::TK_TERNARY_QUEST }, - { ":", Token::TK_TERNARY_COLON }, - { "||", Token::TK_OR }, - { "&&", Token::TK_AND }, - { "==", Token::TK_EQUAL }, - { "!=", Token::TK_NOT_EQUAL }, - { "<=", Token::TK_LESS_EQUAL }, - { "<", Token::TK_LESS }, - { ">=", Token::TK_GREATER_EQUAL }, - { ">", Token::TK_GREATER }, - { "%", Token::TK_MODULUS }, - { "n", Token::TK_VARIABLE_N }, - { "N", Token::TK_VARIABLE_N }, - { "(", Token::TK_BRACKET_LEFT }, - { ")", Token::TK_BRACKET_RIGHT }, - }; - - const std::string stream_; - std::string::const_iterator pos_; -}; - -//------------------------------------------------------------------------------- - -class Parser -{ -public: - Parser(const std::string& stream, int64_t& n) : - scn_(stream), - tk_(scn_.getNextToken()), //throw ParsingError - n_(n) {} - - std::shared_ptr> parse() //throw ParsingError; return value always bound! - { - auto e = std::dynamic_pointer_cast>(parseExpression()); //throw ParsingError - if (!e) - throw ParsingError(); - expectToken(Token::TK_END); //throw ParsingError - return e; - } - -private: - std::shared_ptr parseExpression() { return parseConditional(); }//throw ParsingError - - std::shared_ptr parseConditional() //throw ParsingError - { - std::shared_ptr e = parseLogicalOr(); - - if (token().type == Token::TK_TERNARY_QUEST) - { - nextToken(); //throw ParsingError - - auto ifExp = std::dynamic_pointer_cast>(e); - auto thenExp = std::dynamic_pointer_cast>(parseExpression()); //associativity: <- - - consumeToken(Token::TK_TERNARY_COLON); //throw ParsingError - - auto elseExp = std::dynamic_pointer_cast>(parseExpression()); // - if (!ifExp || !thenExp || !elseExp) - throw ParsingError(); - return std::make_shared>(ifExp, thenExp, elseExp); - } - return e; - } - - std::shared_ptr parseLogicalOr() - { - std::shared_ptr e = parseLogicalAnd(); - while (token().type == Token::TK_OR) //associativity: -> - { - nextToken(); //throw ParsingError - - std::shared_ptr rhs = parseLogicalAnd(); - e = makeBiExp, bool>(e, rhs); //throw ParsingError - } - return e; - } - - std::shared_ptr parseLogicalAnd() - { - std::shared_ptr e = parseEquality(); - while (token().type == Token::TK_AND) //associativity: -> - { - nextToken(); //throw ParsingError - std::shared_ptr rhs = parseEquality(); - - e = makeBiExp, bool>(e, rhs); //throw ParsingError - } - return e; - } - - std::shared_ptr parseEquality() - { - std::shared_ptr e = parseRelational(); - - Token::Type t = token().type; - if (t == Token::TK_EQUAL || //associativity: n/a - t == Token::TK_NOT_EQUAL) - { - nextToken(); //throw ParsingError - std::shared_ptr rhs = parseRelational(); - - if (t == Token::TK_EQUAL) return makeBiExp, int64_t>(e, rhs); //throw ParsingError - if (t == Token::TK_NOT_EQUAL) return makeBiExp, int64_t>(e, rhs); // - } - return e; - } - - std::shared_ptr parseRelational() - { - std::shared_ptr e = parseMultiplicative(); - - Token::Type t = token().type; - if (t == Token::TK_LESS || //associativity: n/a - t == Token::TK_LESS_EQUAL || - t == Token::TK_GREATER || - t == Token::TK_GREATER_EQUAL) - { - nextToken(); //throw ParsingError - std::shared_ptr rhs = parseMultiplicative(); - - if (t == Token::TK_LESS) return makeBiExp, int64_t>(e, rhs); // - if (t == Token::TK_LESS_EQUAL) return makeBiExp, int64_t>(e, rhs); //throw ParsingError - if (t == Token::TK_GREATER) return makeBiExp, int64_t>(e, rhs); // - if (t == Token::TK_GREATER_EQUAL) return makeBiExp, int64_t>(e, rhs); // - } - return e; - } - - std::shared_ptr parseMultiplicative() - { - std::shared_ptr e = parsePrimary(); - - while (token().type == Token::TK_MODULUS) //associativity: -> - { - nextToken(); //throw ParsingError - std::shared_ptr rhs = parsePrimary(); - - //"compile-time" check: n % 0 - if (auto literal = std::dynamic_pointer_cast(rhs)) - if (literal->eval() == 0) - throw ParsingError(); - - e = makeBiExp, int64_t>(e, rhs); //throw ParsingError - } - return e; - } - - std::shared_ptr parsePrimary() - { - if (token().type == Token::TK_VARIABLE_N) - { - nextToken(); //throw ParsingError - return std::make_shared(n_); - } - else if (token().type == Token::TK_CONST_NUMBER) - { - const int64_t number = token().number; - nextToken(); //throw ParsingError - return std::make_shared(number); - } - else if (token().type == Token::TK_BRACKET_LEFT) - { - nextToken(); //throw ParsingError - std::shared_ptr e = parseExpression(); - - expectToken(Token::TK_BRACKET_RIGHT); //throw ParsingError - nextToken(); // - return e; - } - else - throw ParsingError(); - } - - const Token& token() const { return tk_; } - - void nextToken() { tk_ = scn_.getNextToken(); } //throw ParsingError - - void expectToken(Token::Type t) //throw ParsingError - { - if (token().type != t) - throw ParsingError(); - } - - void consumeToken(Token::Type t) //throw ParsingError - { - expectToken(t); //throw ParsingError - nextToken(); - } - - Scanner scn_; - Token tk_; - int64_t& n_; -}; -} - - -inline -PluralFormInfo::PluralFormInfo(const std::string& definition, int pluralCount) //throw InvalidPluralForm -{ - if (pluralCount < 1) - throw InvalidPluralForm(); - - forms_.resize(pluralCount); - try - { - PluralForm pf(definition); //throw ParsingError - //PERF_START - - //perf: 80ns per iteration max (for arabic) - //=> 1000 iterations should be fast enough and still detect all "single number forms" - for (int j = 0; j < 1000; ++j) - { - const int form = pf.getForm(j); - if (0 <= form && form < static_cast(forms_.size())) - { - if (forms_[form].count == 0) - forms_[form].firstNumber = j; - ++forms_[form].count; - } - else - throw InvalidPluralForm(); - } - } - catch (const plural::ParsingError&) - { - throw InvalidPluralForm(); - } - - //ensure each form is used at least once: - if (!std::all_of(forms_.begin(), forms_.end(), [](const FormInfo& fi) { return fi.count >= 1; })) - throw InvalidPluralForm(); -} - - -inline -PluralForm::PluralForm(const std::string& stream) : expr_(impl::Parser(stream, n_).parse()) {} //throw ParsingError -} - -#endif //PARSE_PLURAL_H_180465845670839576 diff --git a/FreeFileSync/Source/base/path_filter.h b/FreeFileSync/Source/base/path_filter.h index f1087722..af7d6dc2 100644 --- a/FreeFileSync/Source/base/path_filter.h +++ b/FreeFileSync/Source/base/path_filter.h @@ -215,7 +215,7 @@ FilterRef constructFilter(const Zstring& includePhrase, { if (NameFilter::isNull(includePhrase, Zstring())) { - auto filterTmp = zen::makeSharedRef(includePhrase2, excludePhrase + Zstr("\n") + excludePhrase2); + auto filterTmp = zen::makeSharedRef(includePhrase2, excludePhrase + Zstr('\n') + excludePhrase2); if (filterTmp.ref().isNull()) return zen::makeSharedRef(); @@ -224,9 +224,9 @@ FilterRef constructFilter(const Zstring& includePhrase, else { if (NameFilter::isNull(includePhrase2, Zstring())) - return zen::makeSharedRef(includePhrase, excludePhrase + Zstr("\n") + excludePhrase2); + return zen::makeSharedRef(includePhrase, excludePhrase + Zstr('\n') + excludePhrase2); else - return zen::makeSharedRef(NameFilter(includePhrase, excludePhrase + Zstr("\n") + excludePhrase2), NameFilter(includePhrase2, Zstring())); + return zen::makeSharedRef(NameFilter(includePhrase, excludePhrase + Zstr('\n') + excludePhrase2), NameFilter(includePhrase2, Zstring())); } } diff --git a/FreeFileSync/Source/base/perf_check.cpp b/FreeFileSync/Source/base/perf_check.cpp deleted file mode 100644 index 9493a8a2..00000000 --- a/FreeFileSync/Source/base/perf_check.cpp +++ /dev/null @@ -1,218 +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 "perf_check.h" -#include -#include -#include - -using namespace zen; -using namespace fff; - - -PerfCheck::PerfCheck(std::chrono::milliseconds windowSizeRemTime, - std::chrono::milliseconds windowSizeSpeed) : - windowSizeRemTime_(windowSizeRemTime), - windowSizeSpeed_ (windowSizeSpeed), - windowMax_(std::max(windowSizeRemTime, windowSizeSpeed)) {} - - -void PerfCheck::addSample(std::chrono::nanoseconds timeElapsed, int itemsCurrent, double bytesCurrent) -{ - samples_.insert(samples_.end(), { timeElapsed, { itemsCurrent, bytesCurrent }}); //use fact that time is monotonously ascending - - //remove all records earlier than "now - windowMax" - auto it = samples_.upper_bound(timeElapsed - windowMax_); - if (it != samples_.begin()) - samples_.erase(samples_.begin(), --it); //keep one point before newBegin in order to handle "measurement holes" -} - - -std::tuple PerfCheck::getBlockDeltas(std::chrono::milliseconds windowSize) const -{ - if (samples_.empty()) return {}; - - auto itBack = samples_.rbegin(); - //find start of records "window" - auto itFront = samples_.upper_bound(itBack->first - windowSize); - if (itFront != samples_.begin()) - --itFront; //one point before window begin in order to handle "measurement holes" - - const double timeDelta = std::chrono::duration(itBack->first - itFront->first).count(); - const int itemsDelta = itBack->second.items - itFront->second.items; - const double bytesDelta = itBack->second.bytes - itFront->second.bytes; - - return { timeDelta, itemsDelta, bytesDelta }; -} - - -std::optional PerfCheck::getRemainingTimeSec(double bytesRemaining) const -{ - const auto [timeDelta, itemsDelta, bytesDelta] = getBlockDeltas(windowSizeRemTime_); - - //objects model logical operations *NOT* disk accesses, so we better play safe and use "bytes" only! - - if (!numeric::isNull(bytesDelta)) //sign(dataRemaining) != sign(bytesDelta) usually an error, so show it! - return bytesRemaining * timeDelta / bytesDelta; - - return {}; -} - - -std::optional PerfCheck::getBytesPerSecond() const -{ - const auto [timeDelta, itemsDelta, bytesDelta] = getBlockDeltas(windowSizeSpeed_); - - if (!numeric::isNull(timeDelta)) - return replaceCpy(_("%x/sec"), L"%x", formatFilesizeShort(numeric::round(bytesDelta / timeDelta))); - - return {}; -} - - -std::optional PerfCheck::getItemsPerSecond() const -{ - const auto [timeDelta, itemsDelta, bytesDelta] = getBlockDeltas(windowSizeSpeed_); - - if (!numeric::isNull(timeDelta)) - return replaceCpy(_("%x/sec"), L"%x", replaceCpy(_("%x items"), L"%x", formatTwoDigitPrecision(itemsDelta / timeDelta))); - - return {}; -} - - -/* -class for calculation of remaining time: ----------------------------------------- -"filesize |-> time" is an affine linear function f(x) = z_1 + z_2 x - -For given n measurements, sizes x_0, ..., x_n and times f_0, ..., f_n, the function f (as a polynom of degree 1) can be lineary approximated by - -z_1 = (r - s * q / p) / ((n + 1) - s * s / p) -z_2 = (q - s * z_1) / p = (r - (n + 1) z_1) / s - -with -p := x_0^2 + ... + x_n^2 -q := f_0 x_0 + ... + f_n x_n -r := f_0 + ... + f_n -s := x_0 + ... + x_n - -=> the time to process N files with amount of data D is: N * z_1 + D * z_2 - -Problem: --------- -Times f_0, ..., f_n can be very small so that precision of the PC clock is poor. -=> Times have to be accumulated to enhance precision: -Copying of m files with sizes x_i and times f_i (i = 1, ..., m) takes sum_i f(x_i) := m * z_1 + z_2 * sum x_i = sum f_i -With X defined as the accumulated sizes and F the accumulated times this gives: (in theory...) -m * z_1 + z_2 * X = F <=> -z_1 + z_2 * X / m = F / m - -=> we obtain a new (artificial) measurement with size X / m and time F / m to be used in the linear approximation above - - -Statistics::Statistics(int totalObjectCount, double totalDataAmount, unsigned recordCount) : - itemsTotal(totalObjectCount), - bytesTotal(totalDataAmount), - recordsMax(recordCount), - objectsLast(0), - dataLast(0), - timeLast(wxGetLocalTimeMillis()), - z1_current(0), - z2_current(0), - dummyRecordPresent(false) {} - - -wxString Statistics::getRemainingTime(int objectsCurrent, double dataCurrent) -{ - //add new measurement point - const int m = objectsCurrent - objectsLast; - if (m != 0) - { - objectsLast = objectsCurrent; - - const double X = dataCurrent - dataLast; - dataLast = dataCurrent; - - const int64_t timeCurrent = wxGetLocalTimeMillis(); - const double F = (timeCurrent - timeLast).ToDouble(); - timeLast = timeCurrent; - - record newEntry; - newEntry.x_i = X / m; - newEntry.f_i = F / m; - - //remove dummy record - if (dummyRecordPresent) - { - measurements.pop_back(); - dummyRecordPresent = false; - } - - //insert new record - measurements.push_back(newEntry); - if (measurements.size() > recordsMax) - measurements.pop_front(); - } - else //dataCurrent increased without processing new objects: - { //modify last measurement until m != 0 - const double X = dataCurrent - dataLast; //do not set dataLast, timeLast variables here, but write dummy record instead - if (!isNull(X)) - { - const int64_t timeCurrent = wxGetLocalTimeMillis(); - const double F = (timeCurrent - timeLast).ToDouble(); - - record modifyEntry; - modifyEntry.x_i = X; - modifyEntry.f_i = F; - - //insert dummy record - if (!dummyRecordPresent) - { - measurements.push_back(modifyEntry); - if (measurements.size() > recordsMax) - measurements.pop_front(); - dummyRecordPresent = true; - } - else //modify dummy record - measurements.back() = modifyEntry; - } - } - - //calculate remaining time based on stored measurement points - double p = 0; - double q = 0; - double r = 0; - double s = 0; - for (const record& rec : measurements) - { - const double x_i = rec.x_i; - const double f_i = rec.f_i; - p += x_i * x_i; - q += f_i * x_i; - r += f_i; - s += x_i; - } - - if (!isNull(p)) - { - const double n = measurements.size(); - const double tmp = (n - s * s / p); - - if (!isNull(tmp) && !isNull(s)) - { - const double z1 = (r - s * q / p) / tmp; - const double z2 = (r - n * z1) / s; //not (n + 1) here, since n already is the number of measurements - - //refresh current values for z1, z2 - z1_current = z1; - z2_current = z2; - } - } - - return formatRemainingTime((itemsTotal - objectsCurrent) * z1_current + (bytesTotal - dataCurrent) * z2_current); -} -*/ diff --git a/FreeFileSync/Source/base/perf_check.h b/FreeFileSync/Source/base/perf_check.h deleted file mode 100644 index 2e9ccc6d..00000000 --- a/FreeFileSync/Source/base/perf_check.h +++ /dev/null @@ -1,47 +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 PERF_CHECK_H_87804217589312454 -#define PERF_CHECK_H_87804217589312454 - -#include -#include -#include -#include - - -namespace fff -{ -class PerfCheck -{ -public: - PerfCheck(std::chrono::milliseconds windowSizeRemTime, - std::chrono::milliseconds windowSizeSpeed); - - void addSample(std::chrono::nanoseconds timeElapsed, int itemsCurrent, double bytesCurrent); - - std::optional getRemainingTimeSec(double bytesRemaining) const; - std::optional getBytesPerSecond() const; //for window - std::optional getItemsPerSecond() const; // - -private: - struct Record - { - int items = 0; - double bytes = 0; - }; - - std::tuple getBlockDeltas(std::chrono::milliseconds windowSize) const; - - std::chrono::milliseconds windowSizeRemTime_; - std::chrono::milliseconds windowSizeSpeed_; - std::chrono::milliseconds windowMax_; - - std::map samples_; -}; -} - -#endif //PERF_CHECK_H_87804217589312454 diff --git a/FreeFileSync/Source/base/resolve_path.cpp b/FreeFileSync/Source/base/resolve_path.cpp index 83e6d226..f2737069 100644 --- a/FreeFileSync/Source/base/resolve_path.cpp +++ b/FreeFileSync/Source/base/resolve_path.cpp @@ -88,14 +88,14 @@ Zstring resolveRelativePath(const Zstring& relativePath) std::optional tryResolveMacro(const Zstring& macro) //macro without %-characters { //there exist environment variables named %TIME%, %DATE% so check for our internal macros first! - if (equalAsciiNoCase(macro, Zstr("time"))) - return formatTime(Zstr("%H%M%S")); + if (equalAsciiNoCase(macro, "time")) + return formatTime(Zstr("%H%M%S")); - if (equalAsciiNoCase(macro, Zstr("date"))) - return formatTime(FORMAT_ISO_DATE); + if (equalAsciiNoCase(macro, "date")) + return formatTime(formatIsoDateTag); - if (equalAsciiNoCase(macro, Zstr("timestamp"))) - return formatTime(Zstr("%Y-%m-%d %H%M%S")); //e.g. "2012-05-15 131513" + if (equalAsciiNoCase(macro, "timestamp")) + return formatTime(Zstr("%Y-%m-%d %H%M%S")); //e.g. "2012-05-15 131513" Zstring timeStr; auto resolveTimePhrase = [&](const Zchar* phrase, const Zchar* format) -> bool @@ -103,7 +103,7 @@ std::optional tryResolveMacro(const Zstring& macro) //macro without %-c if (!equalAsciiNoCase(macro, phrase)) return false; - timeStr = formatTime(format); + timeStr = formatTime(format); return true; }; @@ -161,9 +161,9 @@ Zstring expandVolumeName(Zstring pathPhrase) // [volname]:\folder [volnam //we only expect the [.*] pattern at the beginning => do not touch dir names like "C:\somedir\[stuff]" trim(pathPhrase, true, false); - if (startsWith(pathPhrase, Zstr("["))) + if (startsWith(pathPhrase, Zstr('['))) { - const size_t posEnd = pathPhrase.find(Zstr("]")); + const size_t posEnd = pathPhrase.find(Zstr(']')); if (posEnd != Zstring::npos) { Zstring volName = Zstring(pathPhrase.c_str() + 1, posEnd - 1); diff --git a/FreeFileSync/Source/base/return_codes.h b/FreeFileSync/Source/base/return_codes.h deleted file mode 100644 index 0fe72022..00000000 --- a/FreeFileSync/Source/base/return_codes.h +++ /dev/null @@ -1,80 +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 RETURN_CODES_H_81307482137054156 -#define RETURN_CODES_H_81307482137054156 - -#include - - -namespace fff -{ -enum FfsReturnCode //as returned after process exit -{ - FFS_RC_SUCCESS = 0, - FFS_RC_WARNING, - FFS_RC_ERROR, - FFS_RC_ABORTED, - FFS_RC_EXCEPTION, -}; - - -inline -void raiseReturnCode(FfsReturnCode& rc, FfsReturnCode rcProposed) -{ - if (rc < rcProposed) - rc = rcProposed; -} - - -enum class SyncResult -{ - finishedSuccess, - finishedWarning, - finishedError, - aborted, -}; - - -inline -FfsReturnCode mapToReturnCode(SyncResult syncStatus) -{ - switch (syncStatus) - { - case SyncResult::finishedSuccess: - return FFS_RC_SUCCESS; - case SyncResult::finishedWarning: - return FFS_RC_WARNING; - case SyncResult::finishedError: - return FFS_RC_ERROR; - case SyncResult::aborted: - return FFS_RC_ABORTED; - } - assert(false); - return FFS_RC_ABORTED; -} - - -inline -std::wstring getResultsStatusLabel(SyncResult resultStatus) -{ - switch (resultStatus) - { - case SyncResult::finishedSuccess: - return _("Completed successfully"); - case SyncResult::finishedWarning: - return _("Completed with warnings"); - case SyncResult::finishedError: - return _("Completed with errors"); - case SyncResult::aborted: - return _("Stopped"); - } - assert(false); - return std::wstring(); -} -} - -#endif //RETURN_CODES_H_81307482137054156 diff --git a/FreeFileSync/Source/base/status_handler.cpp b/FreeFileSync/Source/base/status_handler.cpp deleted file mode 100644 index 19d1e882..00000000 --- a/FreeFileSync/Source/base/status_handler.cpp +++ /dev/null @@ -1,28 +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 "status_handler.h" -#include -#include - - -namespace -{ -std::chrono::steady_clock::time_point lastExec; -} - - -bool fff::uiUpdateDue(bool force) -{ - const auto now = std::chrono::steady_clock::now(); - - if (now >= lastExec + UI_UPDATE_INTERVAL || force) - { - lastExec = now; - return true; - } - return false; -} diff --git a/FreeFileSync/Source/base/status_handler.h b/FreeFileSync/Source/base/status_handler.h deleted file mode 100644 index 339f9ebe..00000000 --- a/FreeFileSync/Source/base/status_handler.h +++ /dev/null @@ -1,192 +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_H_81704805908341534 -#define STATUS_HANDLER_H_81704805908341534 - -#include -#include -#include -#include -#include -#include -#include "process_callback.h" -#include "return_codes.h" - - -namespace fff -{ -bool uiUpdateDue(bool force = false); //test if a specific amount of time is over - -/* -Updating GUI is fast! - time per single call to ProcessCallback::forceUiRefresh() - - Comparison 0.025 ms - - Synchronization 0.74 ms (despite complex graph control!) -*/ - -//Exception class used to abort the "compare" and "sync" process -class AbortProcess {}; - - -enum class AbortTrigger -{ - user, - program, -}; - -//GUI may want to abort process -struct AbortCallback -{ - virtual ~AbortCallback() {} - virtual void userRequestAbort() = 0; -}; - - -struct ProgressStats -{ - int items = 0; - int64_t bytes = 0; -}; -inline bool operator==(const ProgressStats& lhs, const ProgressStats& rhs) { return lhs.items == rhs.items && lhs.bytes == rhs.bytes; } - - -//common statistics "everybody" needs -struct Statistics -{ - virtual ~Statistics() {} - - virtual ProcessPhase currentPhase() const = 0; - - virtual ProgressStats getStatsCurrent() const = 0; - virtual ProgressStats getStatsTotal () const = 0; - - virtual std::optional getAbortStatus() const = 0; - virtual const std::wstring& currentStatusText() const = 0; -}; - - -struct ProcessSummary -{ - std::chrono::system_clock::time_point startTime; - SyncResult resultStatus = SyncResult::aborted; - std::vector jobNames; //may be empty - ProgressStats statsProcessed; - ProgressStats statsTotal; - std::chrono::milliseconds totalTime{}; -}; - - -//partial callback implementation with common functionality for "batch", "GUI/Compare" and "GUI/Sync" -class StatusHandler : public ProcessCallback, public AbortCallback, public Statistics -{ -public: - //StatusHandler() {} - - //implement parts of ProcessCallback - void initNewPhase(int itemsTotal, int64_t bytesTotal, ProcessPhase phase) override //(throw X) - { - assert((itemsTotal < 0) == (bytesTotal < 0)); - currentPhase_ = phase; - statsCurrent_ = {}; - statsTotal_ = { itemsTotal, bytesTotal }; - } - - void updateDataProcessed(int itemsDelta, int64_t bytesDelta) override { updateData(statsCurrent_, itemsDelta, bytesDelta); } //note: these methods MUST NOT throw in order - void updateDataTotal (int itemsDelta, int64_t bytesDelta) override { updateData(statsTotal_, itemsDelta, bytesDelta); } //to allow usage within destructors! - - void requestUiUpdate(bool force) final //throw AbortProcess - { - if (uiUpdateDue(force)) - { - const bool abortRequestedBefore = static_cast(abortRequested_); - - forceUiUpdateNoThrow(); - - //triggered by userRequestAbort() - // => sufficient to evaluate occasionally when uiUpdateDue()! - // => refresh *before* throwing: support requestUiUpdate() during destruction - if (abortRequested_) - { - if (!abortRequestedBefore) - forceUiUpdateNoThrow(); //just once to immediately show the "Stop requested..." status after user clicks cancel - throw AbortProcess(); - } - } - } - - virtual void forceUiUpdateNoThrow() = 0; //noexcept - - void updateStatus(const std::wstring& msg) final //throw AbortProcess - { - //assert(!msg.empty()); -> possible, e.g. start of parallel scan - statusText_ = msg; //update *before* running operations that can throw - requestUiUpdate(false /*force*/); //throw AbortProcess - } - - [[noreturn]] void abortProcessNow(AbortTrigger trigger) - { - if (!abortRequested_ || trigger == AbortTrigger::user) //AbortTrigger::USER overwrites AbortTrigger::program - abortRequested_ = trigger; - - forceUiUpdateNoThrow(); //flush GUI to show new cancelled state - throw AbortProcess(); - } - - //implement AbortCallback - void userRequestAbort() final - { - abortRequested_ = AbortTrigger::user; //may overwrite AbortTrigger::program - } //called from GUI code: this does NOT call abortProcessNow() immediately, but later when we're out of the C GUI call stack - //=> don't call forceUiUpdateNoThrow() here! - - //implement Statistics - ProcessPhase currentPhase() const final { return currentPhase_; } - - ProgressStats getStatsCurrent() const override { return statsCurrent_; } - ProgressStats getStatsTotal () const override { return statsTotal_; } - - const std::wstring& currentStatusText() const override { return statusText_; } - - std::optional getAbortStatus() const override { return abortRequested_; } - -private: - void updateData(ProgressStats& stats, int itemsDelta, int64_t bytesDelta) - { - assert(stats.items >= 0); - assert(stats.bytes >= 0); - stats.items += itemsDelta; - stats.bytes += bytesDelta; - } - - ProcessPhase currentPhase_ = ProcessPhase::none; - ProgressStats statsCurrent_; - ProgressStats statsTotal_ { -1, -1 }; - std::wstring statusText_; - - std::optional abortRequested_; -}; - -//------------------------------------------------------------------------------------------ - -inline -void delayAndCountDown(const std::wstring& operationName, std::chrono::seconds delay, const std::function& notifyStatus) -{ - assert(notifyStatus && !zen::endsWith(operationName, L".")); - - const auto delayUntil = std::chrono::steady_clock::now() + delay; - for (auto now = std::chrono::steady_clock::now(); now < delayUntil; now = std::chrono::steady_clock::now()) - { - const auto timeMs = std::chrono::duration_cast(delayUntil - now).count(); - if (notifyStatus) - notifyStatus(operationName + L"... " + _P("1 sec", "%x sec", numeric::integerDivideRoundUp(timeMs, 1000))); - - std::this_thread::sleep_for(UI_UPDATE_INTERVAL / 2); - } -} -} - -#endif //STATUS_HANDLER_H_81704805908341534 diff --git a/FreeFileSync/Source/base/status_handler_impl.h b/FreeFileSync/Source/base/status_handler_impl.h index be6b6fa5..642b2cb0 100644 --- a/FreeFileSync/Source/base/status_handler_impl.h +++ b/FreeFileSync/Source/base/status_handler_impl.h @@ -207,7 +207,7 @@ private: const ThreadStatus* ts = getThreadStatus(); //call while holding "lockCurrentStatus_" lock!! return ts ? ts->taskIdx : static_cast(-2); }(); - return totalThreadCount_ > 1 ? L"[" + zen::numberTo(taskIdx + 1) + L"] " : L""; + return totalThreadCount_ > 1 ? L'[' + zen::numberTo(taskIdx + 1) + L"] " : L""; } #endif @@ -252,7 +252,7 @@ private: }(); } if (parallelOpsTotal >= 2) - return L"[" + _P("1 thread", "%x threads", parallelOpsTotal) + L"] " + statusMsg; + return L'[' + _P("1 thread", "%x threads", parallelOpsTotal) + L"] " + statusMsg; else return statusMsg; } @@ -402,7 +402,7 @@ void massParallelExecute(const std::vector>( 1, - threadGroupName + " " + utfTo(AFS::getDisplayPath(AbstractPath(afsDevice, AfsPath()))))).first->second; + threadGroupName + ' ' + utfTo(AFS::getDisplayPath(AbstractPath(afsDevice, AfsPath()))))).first->second; for (const std::pair* item : wl) threadGroup.run([&acb, statusPrio, &itemPath = item->first, &task = item->second] diff --git a/FreeFileSync/Source/base/structures.cpp b/FreeFileSync/Source/base/structures.cpp index cd275ac5..1e6758ac 100644 --- a/FreeFileSync/Source/base/structures.cpp +++ b/FreeFileSync/Source/base/structures.cpp @@ -10,69 +10,14 @@ #include #include #include -#include "path_filter.h" +//#include "path_filter.h" #include "../afs/concrete.h" using namespace zen; using namespace fff; -std::vector fff::fromTimeShiftPhrase(const std::wstring& timeShiftPhrase) -{ - std::wstring tmp = replaceCpy(timeShiftPhrase, L';', L','); //harmonize , ; and ' ' - replace(tmp, L' ', L','); // - replace(tmp, L'-', L""); //there is no negative shift => treat as positive! - - std::set minutes; - for (const std::wstring& part : split(tmp, L',', SplitType::SKIP_EMPTY)) - { - if (contains(part, L':')) - minutes.insert(stringTo(beforeFirst(part, L':', IF_MISSING_RETURN_NONE)) * 60 + - stringTo(afterFirst (part, L':', IF_MISSING_RETURN_NONE))); - else - minutes.insert(stringTo(part) * 60); - } - minutes.erase(0); - - return { minutes.begin(), minutes.end() }; -} - - -std::wstring fff::toTimeShiftPhrase(const std::vector& ignoreTimeShiftMinutes) -{ - std::wstring phrase; - for (auto it = ignoreTimeShiftMinutes.begin(); it != ignoreTimeShiftMinutes.end(); ++it) - { - if (it != ignoreTimeShiftMinutes.begin()) - phrase += L", "; - - phrase += numberTo(*it / 60); - if (*it % 60 != 0) - phrase += L':' + printNumber(L"%02d", static_cast(*it % 60)); - } - return phrase; -} - - -std::wstring fff::getVariantName(CompareVariant var) -{ - switch (var) - { - case CompareVariant::timeSize: - return _("File time and size"); - case CompareVariant::content: - return _("File content"); - case CompareVariant::size: - return _("File size"); - } - assert(false); - return _("Error"); -} - - -namespace -{ -std::wstring getVariantNameImpl(DirectionConfig::Variant var, const wchar_t* arrowLeft, const wchar_t* arrowRight, const wchar_t* angleRight) +std::wstring fff::getVariantNameImpl(DirectionConfig::Variant var, const wchar_t* arrowLeft, const wchar_t* arrowRight, const wchar_t* angleRight) { switch (var) { @@ -88,28 +33,6 @@ std::wstring getVariantNameImpl(DirectionConfig::Variant var, const wchar_t* arr assert(false); return _("Error"); } -} - - -std::wstring fff::getVariantName(DirectionConfig::Variant var) -{ -#if 1 - const wchar_t arrowLeft [] = L"<\u2013 "; - const wchar_t arrowRight[] = L" \u2013>"; - const wchar_t angleRight[] = L" >"; -#else - //const wchar_t arrowLeft [] = L"\u2190 "; //unicode arrows -> too small - //const wchar_t arrowRight[] = L" \u2192"; // - //const wchar_t arrowLeft [] = L"\u25C4\u2013 "; //black triangle pointer - //const wchar_t arrowRight[] = L" \u2013\u25BA"; // - const wchar_t arrowLeft [] = L"\uFF1C\u2013 "; //fullwidth less-than + en dash - const wchar_t arrowRight[] = L" \u2013\uFF1E"; //en dash + fullwidth greater-than - const wchar_t angleRight[] = L" \uFF1E"; - //=> drawback: - not drawn correctly before Vista - // - RTL: the full width less-than is not mirrored automatically (=> Windows Unicode bug!) -#endif - return getVariantNameImpl(var, arrowLeft, arrowRight, angleRight); -} //use in sync log files where users expect ANSI: https://freefilesync.org/forum/viewtopic.php?t=4647 @@ -125,7 +48,7 @@ DirectionSet fff::extractDirections(const DirectionConfig& cfg) switch (cfg.var) { case DirectionConfig::TWO_WAY: - throw std::logic_error("there are no predefined directions for automatic mode! " + std::string(__FILE__) + ":" + numberTo(__LINE__)); + throw std::logic_error("there are no predefined directions for automatic mode! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); case DirectionConfig::MIRROR: output.exLeftSideOnly = SyncDirection::RIGHT; @@ -185,48 +108,6 @@ DirectionSet fff::getTwoWayUpdateSet() } -std::wstring fff::getCompVariantName(const MainConfiguration& mainCfg) -{ - const CompareVariant firstVariant = mainCfg.firstPair.localCmpCfg ? - mainCfg.firstPair.localCmpCfg->compareVar : - mainCfg.cmpCfg.compareVar; //fallback to main sync cfg - - //test if there's a deviating variant within the additional folder pairs - for (const LocalPairConfig& lpc : mainCfg.additionalPairs) - { - const CompareVariant thisVariant = lpc.localCmpCfg ? - lpc.localCmpCfg->compareVar : - mainCfg.cmpCfg.compareVar; //fallback to main sync cfg - if (thisVariant != firstVariant) - return _("Multiple..."); - } - - //seems to be all in sync... - return getVariantName(firstVariant); -} - - -std::wstring fff::getSyncVariantName(const MainConfiguration& mainCfg) -{ - const DirectionConfig::Variant firstVariant = mainCfg.firstPair.localSyncCfg ? - mainCfg.firstPair.localSyncCfg->directionCfg.var : - mainCfg.syncCfg.directionCfg.var; //fallback to main sync cfg - - //test if there's a deviating variant within the additional folder pairs - for (const LocalPairConfig& lpc : mainCfg.additionalPairs) - { - const DirectionConfig::Variant thisVariant = lpc.localSyncCfg ? - lpc.localSyncCfg->directionCfg.var : - mainCfg.syncCfg.directionCfg.var; - if (thisVariant != firstVariant) - return _("Multiple..."); - } - - //seems to be all in sync... - return getVariantName(firstVariant); -} - - size_t fff::getDeviceParallelOps(const std::map& deviceParallelOps, const AfsDevice& afsDevice) { auto it = deviceParallelOps.find(afsDevice); @@ -398,205 +279,3 @@ void fff::resolveUnits(size_t timeSpan, UnitTime unitTimeSpan, sizeMinBy = resolve(sizeMin, unitSizeMin, 0U); sizeMaxBy = resolve(sizeMax, unitSizeMax, std::numeric_limits::max()); } - - -namespace -{ -FilterConfig mergeFilterConfig(const FilterConfig& global, const FilterConfig& local) -{ - FilterConfig out = local; - - //hard filter - if (NameFilter::isNull(local.includeFilter, Zstring())) //fancy way of checking for "*" include - out.includeFilter = global.includeFilter; - //else : if both global and local include filters are set, only local filter is preserved - - out.excludeFilter = trimCpy(trimCpy(global.excludeFilter) + Zstr("\n\n") + trimCpy(local.excludeFilter)); - - //soft filter - time_t loctimeFrom = 0; - uint64_t locSizeMinBy = 0; - uint64_t locSizeMaxBy = 0; - resolveUnits(out.timeSpan, out.unitTimeSpan, - out.sizeMin, out.unitSizeMin, - out.sizeMax, out.unitSizeMax, - loctimeFrom, //unit: UTC time, seconds - locSizeMinBy, //unit: bytes - locSizeMaxBy); //unit: bytes - - //soft filter - time_t glotimeFrom = 0; - uint64_t gloSizeMinBy = 0; - uint64_t gloSizeMaxBy = 0; - resolveUnits(global.timeSpan, global.unitTimeSpan, - global.sizeMin, global.unitSizeMin, - global.sizeMax, global.unitSizeMax, - glotimeFrom, - gloSizeMinBy, - gloSizeMaxBy); - - if (glotimeFrom > loctimeFrom) - { - out.timeSpan = global.timeSpan; - out.unitTimeSpan = global.unitTimeSpan; - } - if (gloSizeMinBy > locSizeMinBy) - { - out.sizeMin = global.sizeMin; - out.unitSizeMin = global.unitSizeMin; - } - if (gloSizeMaxBy < locSizeMaxBy) - { - out.sizeMax = global.sizeMax; - out.unitSizeMax = global.unitSizeMax; - } - return out; -} - - -inline -bool effectivelyEmpty(const LocalPairConfig& lpc) -{ - return trimCpy(lpc.folderPathPhraseLeft ).empty() && - trimCpy(lpc.folderPathPhraseRight).empty(); -} -} - - -MainConfiguration fff::merge(const std::vector& mainCfgs) -{ - assert(!mainCfgs.empty()); - if (mainCfgs.empty()) - return MainConfiguration(); - - if (mainCfgs.size() == 1) //mergeConfigFilesImpl relies on this! - return mainCfgs[0]; // - - //merge folder pair config - std::vector mergedCfgs; - for (const MainConfiguration& mainCfg : mainCfgs) - { - std::vector tmpCfgs; - - //skip empty folder pairs - if (!effectivelyEmpty(mainCfg.firstPair)) - tmpCfgs.push_back(mainCfg.firstPair); - - for (const LocalPairConfig& lpc : mainCfg.additionalPairs) - if (!effectivelyEmpty(lpc)) - tmpCfgs.push_back(lpc); - - //move all configuration down to item level - for (LocalPairConfig& lpc : tmpCfgs) - { - if (!lpc.localCmpCfg) - lpc.localCmpCfg = mainCfg.cmpCfg; - - if (!lpc.localSyncCfg) - lpc.localSyncCfg = mainCfg.syncCfg; - - lpc.localFilter = mergeFilterConfig(mainCfg.globalFilter, lpc.localFilter); - } - append(mergedCfgs, tmpCfgs); - } - - if (mergedCfgs.empty()) - return MainConfiguration(); - - //optimization: remove redundant configuration - - //######################################################################################################################## - //find out which comparison and synchronization setting are used most often and use them as new "header" - std::vector> cmpCfgStat; - std::vector> syncCfgStat; - for (const LocalPairConfig& lpc : mergedCfgs) - { - //a rather inefficient algorithm, but it does not require a less-than operator: - { - const CompConfig& cmpCfg = *lpc.localCmpCfg; - - auto it = std::find_if(cmpCfgStat.begin(), cmpCfgStat.end(), - [&](const std::pair& entry) { return effectivelyEqual(entry.first, cmpCfg); }); - if (it == cmpCfgStat.end()) - cmpCfgStat.emplace_back(cmpCfg, 1); - else - ++(it->second); - } - { - const SyncConfig& syncCfg = *lpc.localSyncCfg; - - auto it = std::find_if(syncCfgStat.begin(), syncCfgStat.end(), - [&](const std::pair& entry) { return effectivelyEqual(entry.first, syncCfg); }); - if (it == syncCfgStat.end()) - syncCfgStat.emplace_back(syncCfg, 1); - else - ++(it->second); - } - } - - //set most-used comparison and synchronization settings as new header options - const CompConfig cmpCfgHead = cmpCfgStat.empty() ? CompConfig() : - std::max_element(cmpCfgStat.begin(), cmpCfgStat.end(), - [](const std::pair& lhs, const std::pair& rhs) { return lhs.second < rhs.second; })->first; - - const SyncConfig syncCfgHead = syncCfgStat.empty() ? SyncConfig() : - std::max_element(syncCfgStat.begin(), syncCfgStat.end(), - [](const std::pair& lhs, const std::pair& rhs) { return lhs.second < rhs.second; })->first; - //######################################################################################################################## - - FilterConfig globalFilter; - const bool allFiltersEqual = std::all_of(mergedCfgs.begin(), mergedCfgs.end(), [&](const LocalPairConfig& lpc) { return lpc.localFilter == mergedCfgs[0].localFilter; }); - if (allFiltersEqual) - globalFilter = mergedCfgs[0].localFilter; - - //strip redundancy... - for (LocalPairConfig& lpc : mergedCfgs) - { - //if local config matches output global config we don't need local one - if (lpc.localCmpCfg && - effectivelyEqual(*lpc.localCmpCfg, cmpCfgHead)) - lpc.localCmpCfg = {}; - - if (lpc.localSyncCfg && - effectivelyEqual(*lpc.localSyncCfg, syncCfgHead)) - lpc.localSyncCfg = {}; - - if (allFiltersEqual) //use global filter in this case - lpc.localFilter = FilterConfig(); - } - - std::map mergedParallelOps; - for (const MainConfiguration& mainCfg : mainCfgs) - for (const auto& [rootPath, parallelOps] : mainCfg.deviceParallelOps) - mergedParallelOps[rootPath] = std::max(mergedParallelOps[rootPath], parallelOps); - - //final assembly - MainConfiguration cfgOut; - cfgOut.cmpCfg = cmpCfgHead; - cfgOut.syncCfg = syncCfgHead; - cfgOut.globalFilter = globalFilter; - cfgOut.firstPair = mergedCfgs[0]; - cfgOut.additionalPairs.assign(mergedCfgs.begin() + 1, mergedCfgs.end()); - cfgOut.deviceParallelOps = mergedParallelOps; - - cfgOut.ignoreErrors = std::all_of(mainCfgs.begin(), mainCfgs.end(), [](const MainConfiguration& mainCfg) { return mainCfg.ignoreErrors; }); - - cfgOut.automaticRetryCount = std::max_element(mainCfgs.begin(), mainCfgs.end(), - [](const MainConfiguration& lhs, const MainConfiguration& rhs) { return lhs.automaticRetryCount < rhs.automaticRetryCount; })->automaticRetryCount; - - cfgOut.automaticRetryDelay = std::max_element(mainCfgs.begin(), mainCfgs.end(), - [](const MainConfiguration& lhs, const MainConfiguration& rhs) { return lhs.automaticRetryDelay < rhs.automaticRetryDelay; })->automaticRetryDelay; - - for (const MainConfiguration& mainCfg : mainCfgs) - if (!mainCfg.altLogFolderPathPhrase.empty()) - { - cfgOut.altLogFolderPathPhrase = mainCfg.altLogFolderPathPhrase; - break; - } - - //cfgOut.postSyncCommand = -> better leave at default ... !? - //cfgOut.postSyncCondition = -> - //cfgOut.emailNotifyAddress = -> better leave at default ... !? - //cfgOut.emailNotifyCondition = -> - return cfgOut; -} diff --git a/FreeFileSync/Source/base/structures.h b/FreeFileSync/Source/base/structures.h index 5ee9708a..11e98948 100644 --- a/FreeFileSync/Source/base/structures.h +++ b/FreeFileSync/Source/base/structures.h @@ -25,8 +25,6 @@ enum class CompareVariant size }; -std::wstring getVariantName(CompareVariant var); - enum class SymLinkHandling { @@ -150,7 +148,7 @@ bool detectMovedFilesEnabled (const DirectionConfig& cfg); DirectionSet extractDirections(const DirectionConfig& cfg); //get sync directions: DON'T call for DirectionConfig::TWO_WAY! -std::wstring getVariantName (DirectionConfig::Variant var); +std::wstring getVariantNameImpl(DirectionConfig::Variant var, const wchar_t* arrowLeft, const wchar_t* arrowRight, const wchar_t* angleRight); std::wstring getVariantNameForLog(DirectionConfig::Variant var); inline @@ -191,10 +189,6 @@ inline bool operator!=(const CompConfig& lhs, const CompConfig& rhs) { return !( inline bool effectivelyEqual(const CompConfig& lhs, const CompConfig& rhs) { return lhs == rhs; } //no change in behavior -//convert "ignoreTimeShiftMinutes" into compact format: -std::vector fromTimeShiftPhrase(const std::wstring& timeShiftPhrase); -std::wstring toTimeShiftPhrase (const std::vector& ignoreTimeShiftMinutes); - enum class DeletionPolicy { @@ -423,12 +417,10 @@ struct MainConfiguration Zstring altLogFolderPathPhrase; //fill to use different log file folder (other than the default %appdata%\FreeFileSync\Logs) - Zstring emailNotifyAddress; //optional + std::string emailNotifyAddress; //optional ResultsNotification emailNotifyCondition = ResultsNotification::always; }; -std::wstring getCompVariantName(const MainConfiguration& mainCfg); -std::wstring getSyncVariantName(const MainConfiguration& mainCfg); size_t getDeviceParallelOps(const std::map& deviceParallelOps, const AfsDevice& afsDevice); void setDeviceParallelOps( std::map& deviceParallelOps, const AfsDevice& afsDevice, size_t parallelOps); @@ -455,8 +447,37 @@ bool operator==(const MainConfiguration& lhs, const MainConfiguration& rhs) } -//facilitate drag & drop config merge: -MainConfiguration merge(const std::vector& mainCfgs); +struct WarningDialogs +{ + bool warnFolderNotExisting = true; + bool warnFoldersDifferInCase = true; + bool warnDependentFolderPair = true; + bool warnDependentBaseFolders = true; + bool warnSignificantDifference = true; + bool warnNotEnoughDiskSpace = true; + bool warnUnresolvedConflicts = true; + bool warnModificationTimeError = true; + bool warnRecyclerMissing = true; + bool warnInputFieldEmpty = true; + bool warnDirectoryLockFailed = true; + bool warnVersioningFolderPartOfSync = true; +}; +inline bool operator==(const WarningDialogs& lhs, const WarningDialogs& rhs) +{ + return lhs.warnFolderNotExisting == rhs.warnFolderNotExisting && + lhs.warnFoldersDifferInCase == rhs.warnFoldersDifferInCase && + lhs.warnDependentFolderPair == rhs.warnDependentFolderPair && + lhs.warnDependentBaseFolders == rhs.warnDependentBaseFolders && + lhs.warnSignificantDifference == rhs.warnSignificantDifference && + lhs.warnNotEnoughDiskSpace == rhs.warnNotEnoughDiskSpace && + lhs.warnUnresolvedConflicts == rhs.warnUnresolvedConflicts && + lhs.warnModificationTimeError == rhs.warnModificationTimeError && + lhs.warnRecyclerMissing == rhs.warnRecyclerMissing && + lhs.warnInputFieldEmpty == rhs.warnInputFieldEmpty && + lhs.warnDirectoryLockFailed == rhs.warnDirectoryLockFailed && + lhs.warnVersioningFolderPartOfSync == rhs.warnVersioningFolderPartOfSync; +} +inline bool operator!=(const WarningDialogs& lhs, const WarningDialogs& rhs) { return !(lhs == rhs); } } #endif //STRUCTURES_H_8210478915019450901745 diff --git a/FreeFileSync/Source/base/synchronization.cpp b/FreeFileSync/Source/base/synchronization.cpp index 097e3cf0..38ad1771 100644 --- a/FreeFileSync/Source/base/synchronization.cpp +++ b/FreeFileSync/Source/base/synchronization.cpp @@ -124,7 +124,9 @@ void SyncStatistics::processFile(const FilePair& file) break; case SO_UNRESOLVED_CONFLICT: - conflictMsgs_.push_back({ file.getRelativePathAny(), file.getSyncOpConflict() }); + ++conflictCount_; + if (conflictsPreview_.size() < SYNC_STATS_CONFLICTS_MAX) + conflictsPreview_.push_back({ file.getRelativePathAny(), file.getSyncOpConflict() }); break; case SO_COPY_METADATA_TO_LEFT: @@ -178,7 +180,9 @@ void SyncStatistics::processLink(const SymlinkPair& link) break; case SO_UNRESOLVED_CONFLICT: - conflictMsgs_.push_back({ link.getRelativePathAny(), link.getSyncOpConflict() }); + ++conflictCount_; + if (conflictsPreview_.size() < SYNC_STATS_CONFLICTS_MAX) + conflictsPreview_.push_back({ link.getRelativePathAny(), link.getSyncOpConflict() }); break; case SO_MOVE_LEFT_FROM: @@ -218,7 +222,9 @@ void SyncStatistics::processFolder(const FolderPair& folder) break; case SO_UNRESOLVED_CONFLICT: - conflictMsgs_.push_back({ folder.getRelativePathAny(), folder.getSyncOpConflict() }); + ++conflictCount_; + if (conflictsPreview_.size() < SYNC_STATS_CONFLICTS_MAX) + conflictsPreview_.push_back({ folder.getRelativePathAny(), folder.getSyncOpConflict() }); break; case SO_OVERWRITE_LEFT: @@ -464,8 +470,8 @@ void verifyFiles(const AbstractPath& sourcePath, const AbstractPath& targetPath, if (!filesHaveSameContent(sourcePath, targetPath, notifyUnbufferedIO)) //throw FileError, X throw FileError(replaceCpy(replaceCpy(_("%x and %y have different content."), - L"%x", L"\n" + fmtPath(AFS::getDisplayPath(sourcePath))), - L"%y", L"\n" + fmtPath(AFS::getDisplayPath(targetPath)))); + L"%x", L'\n' + fmtPath(AFS::getDisplayPath(sourcePath))), + L"%y", L'\n' + fmtPath(AFS::getDisplayPath(targetPath)))); } catch (const FileError& e) //add some context to error message { @@ -747,7 +753,7 @@ void DeletionHandler::removeDirWithCallback(const AbstractPath& folderPath,//thr //callbacks run *outside* singleThread_ lock! => fine auto notifyMove = [&statReporter](const std::wstring& statusText, const std::wstring& displayPathFrom, const std::wstring& displayPathTo) { - statReporter.updateStatus(replaceCpy(replaceCpy(statusText, L"%x", L"\n" + fmtPath(displayPathFrom)), L"%y", L"\n" + fmtPath(displayPathTo))); //throw ThreadInterruption + statReporter.updateStatus(replaceCpy(replaceCpy(statusText, L"%x", L'\n' + fmtPath(displayPathFrom)), L"%y", L'\n' + fmtPath(displayPathTo))); //throw ThreadInterruption statReporter.reportDelta(1, 0); //it would be more correct to report *after* work was done! warn_static("=> indeed; fix!?") }; @@ -984,7 +990,7 @@ private: 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))); //throw ThreadInterruption + 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) @@ -1523,8 +1529,8 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) catch (const FileError& e2) //more relevant than previous exception (which could be "item not found") { throw FileError(replaceCpy(replaceCpy(_("Cannot copy file %x to %y."), - L"%x", L"\n" + fmtPath(AFS::getDisplayPath(file.getAbstractPath()))), - L"%y", L"\n" + fmtPath(AFS::getDisplayPath(targetPath))), replaceCpy(e2.toString(), L"\n\n", L"\n")); + L"%x", L'\n' + fmtPath(AFS::getDisplayPath(file.getAbstractPath()))), + L"%y", L'\n' + fmtPath(AFS::getDisplayPath(targetPath))), replaceCpy(e2.toString(), L"\n\n", L'\n')); } //do not check on type (symlink, file, folder) -> if there is a type change, FFS should not be quiet about it! if (!sourceExists) @@ -1745,8 +1751,8 @@ void FolderPairSyncer::synchronizeLinkInt(SymlinkPair& symlink, SyncOperation sy catch (const FileError& e2) //more relevant than previous exception (which could be "item not found") { throw FileError(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."), - L"%x", L"\n" + fmtPath(AFS::getDisplayPath(symlink.getAbstractPath()))), - L"%y", L"\n" + fmtPath(AFS::getDisplayPath(targetPath))), replaceCpy(e2.toString(), L"\n\n", L"\n")); + L"%x", L'\n' + fmtPath(AFS::getDisplayPath(symlink.getAbstractPath()))), + L"%y", L'\n' + fmtPath(AFS::getDisplayPath(targetPath))), replaceCpy(e2.toString(), L"\n\n", L'\n')); } //do not check on type (symlink, file, folder) -> if there is a type change, FFS should not be quiet about it! if (!sourceExists) @@ -2137,7 +2143,7 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime //PERF_START; if (syncConfig.size() != folderCmp.size()) - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); //aggregate basic information std::vector folderPairStats; @@ -2189,7 +2195,7 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime std::vector jobType(folderCmp.size(), FolderPairJobType::PROCESS); //folder pairs may be skipped after fatal errors were found - std::map> checkUnresolvedConflicts; + std::map>> checkUnresolvedConflicts; std::vector> checkReadWriteBaseFolders; @@ -2216,7 +2222,7 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime const AbstractPath versioningFolderPath = createAbstractPath(folderPairCfg.versioningFolderPhrase); //aggregate *all* conflicts: - checkUnresolvedConflicts[&baseFolder] = folderPairStat.getConflicts(); + checkUnresolvedConflicts[&baseFolder] = std::pair(folderPairStat.conflictCount(), folderPairStat.getConflictsPreview()); //consider *all* paths that might be used during versioning limit at some time if (folderPairCfg.handleDeletion == DeletionPolicy::versioning && @@ -2375,20 +2381,27 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime //----------------------------------------------------------------- //check if unresolved conflicts exist - if (std::any_of(checkUnresolvedConflicts.begin(), checkUnresolvedConflicts.end(), [](const auto& item) { return !item.second.empty(); })) + if (std::any_of(checkUnresolvedConflicts.begin(), checkUnresolvedConflicts.end(), [](const auto& item) { return item.second.first > 0; })) { std::wstring msg = _("The following items have unresolved conflicts and will not be synchronized:"); for (const auto& [baseFolder, conflicts] : checkUnresolvedConflicts) - if (!conflicts.empty()) + { + const auto& [conflictCount, conflictPreview] = conflicts; + if (conflictCount > 0) { - msg += L"\n\n" + _("Folder pair:") + L" " + + msg += L"\n\n" + _("Folder pair:") + L' ' + AFS::getDisplayPath(baseFolder->getAbstractPath< LEFT_SIDE>()) + L" <-> " + AFS::getDisplayPath(baseFolder->getAbstractPath()); - for (const SyncStatistics::ConflictInfo& item : conflicts) //show *all* conflicts in warning message - msg += L"\n" + utfTo(item.relPath) + L": " + item.msg; + for (const SyncStatistics::ConflictInfo& item : conflictPreview) + msg += L'\n' + utfTo(item.relPath) + L": " + item.msg; + + if (makeUnsigned(conflictCount) > conflictPreview.size()) + msg += L"\n [...] " + replaceCpy(_P("Showing %y of 1 row", "Showing %y of %x rows", conflictCount), //%x used as plural form placeholder! + L"%y", formatNumber(conflictPreview.size())); } + } callback.reportWarning(msg, warnings.warnUnresolvedConflicts); } @@ -2400,7 +2413,7 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime for (const auto& [folderPathL, folderPathR] : checkSignificantDiffPairs) msg += L"\n\n" + - AFS::getDisplayPath(folderPathL) + L" <-> " + L"\n" + + AFS::getDisplayPath(folderPathL) + L" <-> " + L'\n' + AFS::getDisplayPath(folderPathR); callback.reportWarning(msg, warnings.warnSignificantDifference); @@ -2412,9 +2425,9 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime std::wstring msg = _("Not enough free disk space available in:"); for (const auto& [folderPath, space] : checkDiskSpaceMissing) - msg += L"\n\n" + AFS::getDisplayPath(folderPath) + L"\n" + - _("Required:") + L" " + formatFilesizeShort(space.first) + L"\n" + - _("Available:") + L" " + formatFilesizeShort(space.second); + msg += L"\n\n" + AFS::getDisplayPath(folderPath) + L'\n' + + _("Required:") + L' ' + formatFilesizeShort(space.first) + L'\n' + + _("Available:") + L' ' + formatFilesizeShort(space.second); callback.reportWarning(msg, warnings.warnNotEnoughDiskSpace); } @@ -2424,10 +2437,10 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime std::wstring msg; for (const auto& [folderPath, supported] : recyclerSupported) if (!supported) - msg += L"\n" + AFS::getDisplayPath(folderPath); + msg += L'\n' + AFS::getDisplayPath(folderPath); if (!msg.empty()) - callback.reportWarning(_("The recycle bin is not supported by the following folders. Deleted or overwritten files will not be able to be restored:") + L"\n" + msg, + callback.reportWarning(_("The recycle bin is not supported by the following folders. Deleted or overwritten files will not be able to be restored:") + L'\n' + msg, warnings.warnRecyclerMissing); } @@ -2449,11 +2462,11 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime if (!dependentFolders.empty()) { - std::wstring msg = _("Some files will be synchronized as part of multiple base folders.") + L"\n" + - _("To avoid conflicts, set up exclude filters so that each updated file is included by only one base folder.") + L"\n"; + std::wstring msg = _("Some files will be synchronized as part of multiple base folders.") + L'\n' + + _("To avoid conflicts, set up exclude filters so that each updated file is included by only one base folder.") + L'\n'; for (const AbstractPath& baseFolderPath : dependentFolders) - msg += L"\n" + AFS::getDisplayPath(baseFolderPath); + msg += L'\n' + AFS::getDisplayPath(baseFolderPath); callback.reportWarning(msg, warnings.warnDependentBaseFolders); } @@ -2470,9 +2483,9 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime if (std::optional pd = getPathDependency(versioningFolderPath, NullFilter(), folderPath, *filter)) { std::wstring line = L"\n\n" + _("Versioning folder:") + L" \t" + AFS::getDisplayPath(versioningFolderPath) + - L"\n" + _("Base folder:") + L" \t" + AFS::getDisplayPath(folderPath); + L'\n' + _("Base folder:") + L" \t" + AFS::getDisplayPath(folderPath); if (pd->basePathParent == folderPath && !pd->relPath.empty()) - line += L"\n" + _("Exclude:") + L" \t" + utfTo(FILE_NAME_SEPARATOR + pd->relPath + FILE_NAME_SEPARATOR); + line += L'\n' + _("Exclude:") + L" \t" + utfTo(FILE_NAME_SEPARATOR + pd->relPath + FILE_NAME_SEPARATOR); uniqueMsgs[folderPath] = line; } @@ -2480,7 +2493,7 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime msg += perFolderMsg; } if (!msg.empty()) - callback.reportWarning(_("The versioning folder is contained in a base folder.") + L"\n" + + callback.reportWarning(_("The versioning folder is contained in a base folder.") + L'\n' + _("The folder should be excluded from synchronization via filter.") + msg, warnings.warnVersioningFolderPartOfSync); } @@ -2497,9 +2510,9 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime for (const auto& [key, aliases] : ciPathAliases) if (aliases.size() > 1) { - msg += L"\n"; + msg += L'\n'; for (const AbstractPath& aliasPath : aliases) - msg += L"\n" + AFS::getDisplayPath(aliasPath); + msg += L'\n' + AFS::getDisplayPath(aliasPath); } callback.reportWarning(msg, warnings.warnFoldersDifferInCase); //throw X } @@ -2521,7 +2534,7 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime std::wstring msg; for (const FileError& e : errorsModTime) { - std::wstring singleMsg = replaceCpy(e.toString(), L"\n\n", L"\n"); + std::wstring singleMsg = replaceCpy(e.toString(), L"\n\n", L'\n'); msg += singleMsg + L"\n\n"; } msg.resize(msg.size() - 2); @@ -2571,8 +2584,8 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime //------------------------------------------------------------------------------------------ if (folderCmp.size() > 1) - callback.reportInfo(_("Synchronizing folder pair:") + L" " + getVariantNameForLog(folderPairCfg.syncVariant) + L"\n" + //throw X - L" " + AFS::getDisplayPath(baseFolder.getAbstractPath< LEFT_SIDE>()) + 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())); //------------------------------------------------------------------------------------------ diff --git a/FreeFileSync/Source/base/synchronization.h b/FreeFileSync/Source/base/synchronization.h index c0c5df35..275918b4 100644 --- a/FreeFileSync/Source/base/synchronization.h +++ b/FreeFileSync/Source/base/synchronization.h @@ -8,7 +8,8 @@ #define SYNCHRONIZATION_H_8913470815943295 #include -#include "config.h" +//#include "config.h" +#include "structures.h" #include "file_hierarchy.h" #include "process_callback.h" @@ -38,8 +39,6 @@ public: template bool expectPhysicalDeletion() const { return SelectParam::ref(physicalDeleteLeft_, physicalDeleteRight_); } - int conflictCount() const { return static_cast(conflictMsgs_.size()); } - int64_t getBytesToProcess() const { return bytesToProcess_; } size_t rowCount () const { return rowsTotal_; } @@ -48,7 +47,8 @@ public: Zstring relPath; std::wstring msg; }; - const std::vector& getConflicts() const { return conflictMsgs_; } + const std::vector& getConflictsPreview() const { return conflictsPreview_; } + int conflictCount() const { return conflictCount_; } private: void recurse(const ContainerObject& hierObj); @@ -65,9 +65,14 @@ private: int deleteRight_ = 0; bool physicalDeleteLeft_ = false; //at least 1 item will be deleted; considers most "update" cases which also delete items bool physicalDeleteRight_ = false; // - std::vector conflictMsgs_; //conflict texts to display as a warning message + int64_t bytesToProcess_ = 0; size_t rowsTotal_ = 0; + + int conflictCount_ = 0; + std::vector conflictsPreview_; //conflict texts to display as a warning message + static const size_t SYNC_STATS_CONFLICTS_MAX = 25; //=> consider memory consumption, log file size, email size! + //limit conflict count! e.g. there may be hundred thousands of "same date but a different size" }; diff --git a/FreeFileSync/Source/base/versioning.cpp b/FreeFileSync/Source/base/versioning.cpp index c2b95f8d..bf596d11 100644 --- a/FreeFileSync/Source/base/versioning.cpp +++ b/FreeFileSync/Source/base/versioning.cpp @@ -422,14 +422,14 @@ void fff::applyVersioningLimit(const std::set& folderLimi if (!status.failedChecks.empty()) { - std::wstring msg = _("Cannot find the following folders:") + L"\n"; + std::wstring msg = _("Cannot find the following folders:") + L'\n'; for (const auto& [folderPath, error] : status.failedChecks) - msg += L"\n" + AFS::getDisplayPath(folderPath); + msg += L'\n' + AFS::getDisplayPath(folderPath); msg += L"\n___________________________________________"; for (const auto& [folderPath, error] : status.failedChecks) - msg += L"\n\n" + replaceCpy(error.toString(), L"\n\n", L"\n"); + msg += L"\n\n" + replaceCpy(error.toString(), L"\n\n", L'\n'); throw FileError(msg); } @@ -453,7 +453,7 @@ void fff::applyVersioningLimit(const std::set& folderLimi return AFS::TraverserCallback::ON_ERROR_CONTINUE; }; - const std::wstring textScanning = _("Searching for old file versions:") + L" "; + const std::wstring textScanning = _("Searching for old file versions:") + L' '; auto onStatusUpdate = [&](const std::wstring& statusLine, int itemsTotal) { @@ -538,7 +538,7 @@ void fff::applyVersioningLimit(const std::set& folderLimi //--------- remove excess file versions --------- Protected&> folderItemCountShared(folderItemCount); - const std::wstring txtRemoving = _("Removing old file versions:") + L" "; + const std::wstring txtRemoving = _("Removing old file versions:") + L' '; const std::wstring txtDeletingFolder = _("Deleting folder %x"); std::function deleteEmptyFolderTask; diff --git a/FreeFileSync/Source/base/versioning.h b/FreeFileSync/Source/base/versioning.h index 9c0a9b43..d5edb345 100644 --- a/FreeFileSync/Source/base/versioning.h +++ b/FreeFileSync/Source/base/versioning.h @@ -39,12 +39,12 @@ public: versioningFolderPath_(versioningFolderPath), versioningStyle_(versioningStyle), syncStartTime_(syncStartTime), - timeStamp_(zen::formatTime(Zstr("%Y-%m-%d %H%M%S"), zen::getLocalTime(syncStartTime))) //e.g. "2012-05-15 131513" + timeStamp_(zen::formatTime(Zstr("%Y-%m-%d %H%M%S"), zen::getLocalTime(syncStartTime))) //e.g. "2012-05-15 131513" { using namespace zen; if (AbstractFileSystem::isNullPath(versioningFolderPath_)) - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); if (timeStamp_.size() != 17) //formatTime() returns empty string on error; unexpected length: e.g. problem in year 10,000! throw FileError(_("Unable to create time stamp for versioning:") + L" \"" + utfTo(timeStamp_) + L'"'); diff --git a/FreeFileSync/Source/base_tools.cpp b/FreeFileSync/Source/base_tools.cpp new file mode 100644 index 00000000..3a7dabbd --- /dev/null +++ b/FreeFileSync/Source/base_tools.cpp @@ -0,0 +1,357 @@ +// ***************************************************************************** +// * 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 "base_tools.h" +#include +#include "base/path_filter.h" + +using namespace zen; +using namespace fff; + + +std::wstring fff::getVariantName(CompareVariant var) +{ + switch (var) + { + case CompareVariant::timeSize: + return _("File time and size"); + case CompareVariant::content: + return _("File content"); + case CompareVariant::size: + return _("File size"); + } + assert(false); + return _("Error"); +} + + +std::wstring fff::getVariantName(DirectionConfig::Variant var) +{ + const wchar_t* arrowLeft = L"\u25C4 "; //black triangle pointer + const wchar_t* arrowRight = L" \u25BA"; // + const wchar_t* angleRight = L" \uFF1E"; //fullwidth greater-than + //const wchar_t arrowLeft [] = L"\u2190 "; //unicode arrows -> too small + //const wchar_t arrowRight[] = L" \u2192"; // + + if (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft) + { + arrowLeft = L"\u25BA "; //not mirrored automatically: Windows/Linux Unicode bug!? + arrowRight = L" \u25C4"; // + } + return getVariantNameImpl(var, arrowLeft, arrowRight, angleRight); +} + + +std::wstring fff::getCompVariantName(const MainConfiguration& mainCfg) +{ + const CompareVariant firstVariant = mainCfg.firstPair.localCmpCfg ? + mainCfg.firstPair.localCmpCfg->compareVar : + mainCfg.cmpCfg.compareVar; //fallback to main sync cfg + + //test if there's a deviating variant within the additional folder pairs + for (const LocalPairConfig& lpc : mainCfg.additionalPairs) + { + const CompareVariant thisVariant = lpc.localCmpCfg ? + lpc.localCmpCfg->compareVar : + mainCfg.cmpCfg.compareVar; //fallback to main sync cfg + if (thisVariant != firstVariant) + return _("Multiple..."); + } + + //seems to be all in sync... + return getVariantName(firstVariant); +} + + +std::wstring fff::getSyncVariantName(const MainConfiguration& mainCfg) +{ + const DirectionConfig::Variant firstVariant = mainCfg.firstPair.localSyncCfg ? + mainCfg.firstPair.localSyncCfg->directionCfg.var : + mainCfg.syncCfg.directionCfg.var; //fallback to main sync cfg + + //test if there's a deviating variant within the additional folder pairs + for (const LocalPairConfig& lpc : mainCfg.additionalPairs) + { + const DirectionConfig::Variant thisVariant = lpc.localSyncCfg ? + lpc.localSyncCfg->directionCfg.var : + mainCfg.syncCfg.directionCfg.var; + if (thisVariant != firstVariant) + return _("Multiple..."); + } + + //seems to be all in sync... + return getVariantName(firstVariant); +} + + +std::vector fff::fromTimeShiftPhrase(const std::wstring& timeShiftPhrase) +{ + std::wstring tmp = replaceCpy(timeShiftPhrase, L';', L','); //harmonize , ; and ' ' + replace(tmp, L' ', L','); // + replace(tmp, L'-', L""); //there is no negative shift => treat as positive! + + std::set minutes; + for (const std::wstring& part : split(tmp, L',', SplitType::SKIP_EMPTY)) + { + if (contains(part, L':')) + minutes.insert(stringTo(beforeFirst(part, L':', IF_MISSING_RETURN_NONE)) * 60 + + stringTo(afterFirst (part, L':', IF_MISSING_RETURN_NONE))); + else + minutes.insert(stringTo(part) * 60); + } + minutes.erase(0); + + return { minutes.begin(), minutes.end() }; +} + + +std::wstring fff::toTimeShiftPhrase(const std::vector& ignoreTimeShiftMinutes) +{ + std::wstring phrase; + for (auto it = ignoreTimeShiftMinutes.begin(); it != ignoreTimeShiftMinutes.end(); ++it) + { + if (it != ignoreTimeShiftMinutes.begin()) + phrase += L", "; + + phrase += numberTo(*it / 60); + if (*it % 60 != 0) + phrase += L':' + printNumber(L"%02d", static_cast(*it % 60)); + } + return phrase; +} + + +void fff::logNonDefaultSettings(const XmlGlobalSettings& activeSettings, PhaseCallback& callback) +{ + const XmlGlobalSettings defaultSettings; + std::wstring changedSettingsMsg; + + if (activeSettings.failSafeFileCopy != defaultSettings.failSafeFileCopy) + changedSettingsMsg += L"\n " + _("Fail-safe file copy") + L" - " + (activeSettings.failSafeFileCopy ? _("Enabled") : _("Disabled")); + + if (activeSettings.copyLockedFiles != defaultSettings.copyLockedFiles) + changedSettingsMsg += L"\n " + _("Copy locked files") + L" - " + (activeSettings.copyLockedFiles ? _("Enabled") : _("Disabled")); + + if (activeSettings.copyFilePermissions != defaultSettings.copyFilePermissions) + changedSettingsMsg += L"\n " + _("Copy file access permissions") + L" - " + (activeSettings.copyFilePermissions ? _("Enabled") : _("Disabled")); + + if (activeSettings.fileTimeTolerance != defaultSettings.fileTimeTolerance) + changedSettingsMsg += L"\n " + _("File time tolerance") + L" - " + numberTo(activeSettings.fileTimeTolerance); + + if (activeSettings.runWithBackgroundPriority != defaultSettings.runWithBackgroundPriority) + changedSettingsMsg += L"\n " + _("Run with background priority") + L" - " + (activeSettings.runWithBackgroundPriority ? _("Enabled") : _("Disabled")); + + if (activeSettings.createLockFile != defaultSettings.createLockFile) + changedSettingsMsg += L"\n " + _("Lock directories during sync") + L" - " + (activeSettings.createLockFile ? _("Enabled") : _("Disabled")); + + if (activeSettings.verifyFileCopy != defaultSettings.verifyFileCopy) + changedSettingsMsg += L"\n " + _("Verify copied files") + L" - " + (activeSettings.verifyFileCopy ? _("Enabled") : _("Disabled")); + + if (!changedSettingsMsg.empty()) + callback.reportInfo(_("Using non-default global settings:") + changedSettingsMsg); //throw X +} + + +namespace +{ +FilterConfig mergeFilterConfig(const FilterConfig& global, const FilterConfig& local) +{ + FilterConfig out = local; + + //hard filter + if (NameFilter::isNull(local.includeFilter, Zstring())) //fancy way of checking for "*" include + out.includeFilter = global.includeFilter; + //else : if both global and local include filters are set, only local filter is preserved + + out.excludeFilter = trimCpy(trimCpy(global.excludeFilter) + Zstr("\n\n") + trimCpy(local.excludeFilter)); + + //soft filter + time_t loctimeFrom = 0; + uint64_t locSizeMinBy = 0; + uint64_t locSizeMaxBy = 0; + resolveUnits(out.timeSpan, out.unitTimeSpan, + out.sizeMin, out.unitSizeMin, + out.sizeMax, out.unitSizeMax, + loctimeFrom, //unit: UTC time, seconds + locSizeMinBy, //unit: bytes + locSizeMaxBy); //unit: bytes + + //soft filter + time_t glotimeFrom = 0; + uint64_t gloSizeMinBy = 0; + uint64_t gloSizeMaxBy = 0; + resolveUnits(global.timeSpan, global.unitTimeSpan, + global.sizeMin, global.unitSizeMin, + global.sizeMax, global.unitSizeMax, + glotimeFrom, + gloSizeMinBy, + gloSizeMaxBy); + + if (glotimeFrom > loctimeFrom) + { + out.timeSpan = global.timeSpan; + out.unitTimeSpan = global.unitTimeSpan; + } + if (gloSizeMinBy > locSizeMinBy) + { + out.sizeMin = global.sizeMin; + out.unitSizeMin = global.unitSizeMin; + } + if (gloSizeMaxBy < locSizeMaxBy) + { + out.sizeMax = global.sizeMax; + out.unitSizeMax = global.unitSizeMax; + } + return out; +} + + +inline +bool effectivelyEmpty(const LocalPairConfig& lpc) +{ + return trimCpy(lpc.folderPathPhraseLeft ).empty() && + trimCpy(lpc.folderPathPhraseRight).empty(); +} +} + + +MainConfiguration fff::merge(const std::vector& mainCfgs) +{ + assert(!mainCfgs.empty()); + if (mainCfgs.empty()) + return MainConfiguration(); + + if (mainCfgs.size() == 1) //mergeConfigFilesImpl relies on this! + return mainCfgs[0]; // + + //merge folder pair config + std::vector mergedCfgs; + for (const MainConfiguration& mainCfg : mainCfgs) + { + std::vector tmpCfgs; + + //skip empty folder pairs + if (!effectivelyEmpty(mainCfg.firstPair)) + tmpCfgs.push_back(mainCfg.firstPair); + + for (const LocalPairConfig& lpc : mainCfg.additionalPairs) + if (!effectivelyEmpty(lpc)) + tmpCfgs.push_back(lpc); + + //move all configuration down to item level + for (LocalPairConfig& lpc : tmpCfgs) + { + if (!lpc.localCmpCfg) + lpc.localCmpCfg = mainCfg.cmpCfg; + + if (!lpc.localSyncCfg) + lpc.localSyncCfg = mainCfg.syncCfg; + + lpc.localFilter = mergeFilterConfig(mainCfg.globalFilter, lpc.localFilter); + } + append(mergedCfgs, tmpCfgs); + } + + if (mergedCfgs.empty()) + return MainConfiguration(); + + //optimization: remove redundant configuration + + //######################################################################################################################## + //find out which comparison and synchronization setting are used most often and use them as new "header" + std::vector> cmpCfgStat; + std::vector> syncCfgStat; + for (const LocalPairConfig& lpc : mergedCfgs) + { + //a rather inefficient algorithm, but it does not require a less-than operator: + { + const CompConfig& cmpCfg = *lpc.localCmpCfg; + + auto it = std::find_if(cmpCfgStat.begin(), cmpCfgStat.end(), + [&](const std::pair& entry) { return effectivelyEqual(entry.first, cmpCfg); }); + if (it == cmpCfgStat.end()) + cmpCfgStat.emplace_back(cmpCfg, 1); + else + ++(it->second); + } + { + const SyncConfig& syncCfg = *lpc.localSyncCfg; + + auto it = std::find_if(syncCfgStat.begin(), syncCfgStat.end(), + [&](const std::pair& entry) { return effectivelyEqual(entry.first, syncCfg); }); + if (it == syncCfgStat.end()) + syncCfgStat.emplace_back(syncCfg, 1); + else + ++(it->second); + } + } + + //set most-used comparison and synchronization settings as new header options + const CompConfig cmpCfgHead = cmpCfgStat.empty() ? CompConfig() : + std::max_element(cmpCfgStat.begin(), cmpCfgStat.end(), + [](const std::pair& lhs, const std::pair& rhs) { return lhs.second < rhs.second; })->first; + + const SyncConfig syncCfgHead = syncCfgStat.empty() ? SyncConfig() : + std::max_element(syncCfgStat.begin(), syncCfgStat.end(), + [](const std::pair& lhs, const std::pair& rhs) { return lhs.second < rhs.second; })->first; + //######################################################################################################################## + + FilterConfig globalFilter; + const bool allFiltersEqual = std::all_of(mergedCfgs.begin(), mergedCfgs.end(), [&](const LocalPairConfig& lpc) { return lpc.localFilter == mergedCfgs[0].localFilter; }); + if (allFiltersEqual) + globalFilter = mergedCfgs[0].localFilter; + + //strip redundancy... + for (LocalPairConfig& lpc : mergedCfgs) + { + //if local config matches output global config we don't need local one + if (lpc.localCmpCfg && + effectivelyEqual(*lpc.localCmpCfg, cmpCfgHead)) + lpc.localCmpCfg = {}; + + if (lpc.localSyncCfg && + effectivelyEqual(*lpc.localSyncCfg, syncCfgHead)) + lpc.localSyncCfg = {}; + + if (allFiltersEqual) //use global filter in this case + lpc.localFilter = FilterConfig(); + } + + std::map mergedParallelOps; + for (const MainConfiguration& mainCfg : mainCfgs) + for (const auto& [rootPath, parallelOps] : mainCfg.deviceParallelOps) + mergedParallelOps[rootPath] = std::max(mergedParallelOps[rootPath], parallelOps); + + //final assembly + MainConfiguration cfgOut; + cfgOut.cmpCfg = cmpCfgHead; + cfgOut.syncCfg = syncCfgHead; + cfgOut.globalFilter = globalFilter; + cfgOut.firstPair = mergedCfgs[0]; + cfgOut.additionalPairs.assign(mergedCfgs.begin() + 1, mergedCfgs.end()); + cfgOut.deviceParallelOps = mergedParallelOps; + + cfgOut.ignoreErrors = std::all_of(mainCfgs.begin(), mainCfgs.end(), [](const MainConfiguration& mainCfg) { return mainCfg.ignoreErrors; }); + + cfgOut.automaticRetryCount = std::max_element(mainCfgs.begin(), mainCfgs.end(), + [](const MainConfiguration& lhs, const MainConfiguration& rhs) { return lhs.automaticRetryCount < rhs.automaticRetryCount; })->automaticRetryCount; + + cfgOut.automaticRetryDelay = std::max_element(mainCfgs.begin(), mainCfgs.end(), + [](const MainConfiguration& lhs, const MainConfiguration& rhs) { return lhs.automaticRetryDelay < rhs.automaticRetryDelay; })->automaticRetryDelay; + + for (const MainConfiguration& mainCfg : mainCfgs) + if (!mainCfg.altLogFolderPathPhrase.empty()) + { + cfgOut.altLogFolderPathPhrase = mainCfg.altLogFolderPathPhrase; + break; + } + + //cfgOut.postSyncCommand = -> better leave at default ... !? + //cfgOut.postSyncCondition = -> + //cfgOut.emailNotifyAddress = -> better leave at default ... !? + //cfgOut.emailNotifyCondition = -> + return cfgOut; +} diff --git a/FreeFileSync/Source/base_tools.h b/FreeFileSync/Source/base_tools.h new file mode 100644 index 00000000..e5ad9a2f --- /dev/null +++ b/FreeFileSync/Source/base_tools.h @@ -0,0 +1,33 @@ +// ***************************************************************************** +// * 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 STRUCTURE_TOOLS_H_7823097420397434 +#define STRUCTURE_TOOLS_H_7823097420397434 + +#include "base/structures.h" +#include "base/process_callback.h" +#include "config.h" + + +namespace fff +{ +std::wstring getVariantName(CompareVariant var); +std::wstring getVariantName(DirectionConfig::Variant var); +std::wstring getCompVariantName(const MainConfiguration& mainCfg); +std::wstring getSyncVariantName(const MainConfiguration& mainCfg); + +//convert "ignoreTimeShiftMinutes" into compact format: +std::vector fromTimeShiftPhrase(const std::wstring& timeShiftPhrase); +std::wstring toTimeShiftPhrase (const std::vector& ignoreTimeShiftMinutes); + +//inform about (important) non-default global settings related to comparison and synchronization +void logNonDefaultSettings(const XmlGlobalSettings& currentSettings, PhaseCallback& callback); + +//facilitate drag & drop config merge: +MainConfiguration merge(const std::vector& mainCfgs); +} + +#endif //STRUCTURE_TOOLS_H_7823097420397434 diff --git a/FreeFileSync/Source/config.cpp b/FreeFileSync/Source/config.cpp new file mode 100644 index 00000000..120ad466 --- /dev/null +++ b/FreeFileSync/Source/config.cpp @@ -0,0 +1,2395 @@ +// ***************************************************************************** +// * 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 "config.h" +#include +#include +#include +#include +#include +#include "ffs_paths.h" +#include "base_tools.h" + + +using namespace zen; +using namespace fff; //functionally needed for correct overload resolution!!! + + +namespace +{ +//------------------------------------------------------------------------------------------------------------------------------- +const int XML_FORMAT_GLOBAL_CFG = 16; //2020-01-30 +const int XML_FORMAT_SYNC_CFG = 15; //2020-01-30 +//------------------------------------------------------------------------------------------------------------------------------- +} + +XmlType getXmlTypeNoThrow(const XmlDoc& doc) //throw() +{ + if (doc.root().getNameAs() == "FreeFileSync") + { + std::string type; + if (doc.root().getAttribute("XmlType", type)) + { + if (type == "GUI") + return XmlType::gui; + else if (type == "BATCH") + return XmlType::batch; + else if (type == "GLOBAL") + return XmlType::global; + } + } + return XmlType::other; +} + + +XmlType fff::getXmlType(const Zstring& filePath) //throw FileError +{ + //quick exit if file is not an XML + XmlDoc doc = loadXml(filePath); //throw FileError + return ::getXmlTypeNoThrow(doc); +} + + +void setXmlType(XmlDoc& doc, XmlType type) //throw() +{ + switch (type) + { + case XmlType::gui: + doc.root().setAttribute("XmlType", "GUI"); + break; + case XmlType::batch: + doc.root().setAttribute("XmlType", "BATCH"); + break; + case XmlType::global: + doc.root().setAttribute("XmlType", "GLOBAL"); + break; + case XmlType::other: + assert(false); + break; + } +} + + +XmlGlobalSettings::XmlGlobalSettings() : + soundFileSyncFinished(getResourceDirPf() + Zstr("bell.wav")) +{ +} + +//################################################################################################################ + +Zstring fff::getGlobalConfigFile() +{ + return getConfigDirPathPf() + Zstr("GlobalSettings.xml"); +} + + +XmlGuiConfig fff::convertBatchToGui(const XmlBatchConfig& batchCfg) //noexcept +{ + XmlGuiConfig output; + output.mainCfg = batchCfg.mainCfg; + return output; +} + + +XmlBatchConfig fff::convertGuiToBatch(const XmlGuiConfig& guiCfg, const BatchExclusiveConfig& batchExCfg) //noexcept +{ + XmlBatchConfig output; + output.mainCfg = guiCfg.mainCfg; + output.batchExCfg = batchExCfg; + return output; +} + + +namespace +{ +std::vector splitFilterByLines(Zstring filterPhrase) +{ + trim(filterPhrase); + if (filterPhrase.empty()) + return {}; + + return split(filterPhrase, Zstr('\n'), SplitType::ALLOW_EMPTY); +} + +Zstring mergeFilterLines(const std::vector& filterLines) +{ + Zstring out; + for (const Zstring& line : filterLines) + { + out += line; + out += Zstr('\n'); + } + return trimCpy(out); +} +} + +namespace zen +{ +template <> inline +void writeText(const wxLanguage& value, std::string& output) +{ + //use description as unique wxLanguage identifier, see localization.cpp + //=> handle changes to wxLanguage enum between wxWidgets versions + if (const wxLanguageInfo* lngInfo = wxLocale::GetLanguageInfo(value)) + output = utfTo(lngInfo->Description); + else + { + assert(false); + output = "English (U.S.)"; + } +} + +template <> inline +bool readText(const std::string& input, wxLanguage& value) +{ + if (const wxLanguageInfo* lngInfo = wxLocale::FindLanguageInfo(utfTo(input))) + { + value = static_cast(lngInfo->Language); + return true; + } + return false; +} + + +template <> inline +void writeText(const CompareVariant& value, std::string& output) +{ + switch (value) + { + case CompareVariant::timeSize: + output = "TimeAndSize"; + break; + case CompareVariant::content: + output = "Content"; + break; + case CompareVariant::size: + output = "Size"; + break; + } +} + +template <> inline +bool readText(const std::string& input, CompareVariant& value) +{ + const std::string tmp = trimCpy(input); + if (tmp == "TimeAndSize") + value = CompareVariant::timeSize; + else if (tmp == "Content") + value = CompareVariant::content; + else if (tmp == "Size") + value = CompareVariant::size; + else + return false; + return true; +} + + +template <> inline +void writeText(const SyncDirection& value, std::string& output) +{ + switch (value) + { + case SyncDirection::LEFT: + output = "left"; + break; + case SyncDirection::RIGHT: + output = "right"; + break; + case SyncDirection::NONE: + output = "none"; + break; + } +} + +template <> inline +bool readText(const std::string& input, SyncDirection& value) +{ + const std::string tmp = trimCpy(input); + if (tmp == "left") + value = SyncDirection::LEFT; + else if (tmp == "right") + value = SyncDirection::RIGHT; + else if (tmp == "none") + value = SyncDirection::NONE; + else + return false; + return true; +} + + +template <> inline +void writeText(const BatchErrorHandling& value, std::string& output) +{ + switch (value) + { + case BatchErrorHandling::showPopup: + output = "Show"; + break; + case BatchErrorHandling::cancel: + output = "Cancel"; + break; + } +} + +template <> inline +bool readText(const std::string& input, ResultsNotification& value) +{ + const std::string tmp = trimCpy(input); + if (tmp == "Always") + value = ResultsNotification::always; + else if (tmp == "ErrorWarning") + value = ResultsNotification::errorWarning; + else if (tmp == "ErrorOnly") + value = ResultsNotification::errorOnly; + else + return false; + return true; +} + + +template <> inline +void writeText(const ResultsNotification& value, std::string& output) +{ + switch (value) + { + case ResultsNotification::always: + output = "Always"; + break; + case ResultsNotification::errorWarning: + output = "ErrorWarning"; + break; + case ResultsNotification::errorOnly: + output = "ErrorOnly"; + break; + } +} + + +template <> inline +bool readText(const std::string& input, BatchErrorHandling& value) +{ + const std::string tmp = trimCpy(input); + if (tmp == "Show") + value = BatchErrorHandling::showPopup; + else if (tmp == "Cancel") + value = BatchErrorHandling::cancel; + else + return false; + return true; +} + + +template <> inline +void writeText(const PostSyncCondition& value, std::string& output) +{ + switch (value) + { + case PostSyncCondition::COMPLETION: + output = "Completion"; + break; + case PostSyncCondition::ERRORS: + output = "Errors"; + break; + case PostSyncCondition::SUCCESS: + output = "Success"; + break; + } +} + +template <> inline +bool readText(const std::string& input, PostSyncCondition& value) +{ + const std::string tmp = trimCpy(input); + if (tmp == "Completion") + value = PostSyncCondition::COMPLETION; + else if (tmp == "Errors") + value = PostSyncCondition::ERRORS; + else if (tmp == "Success") + value = PostSyncCondition::SUCCESS; + else + return false; + return true; +} + + +template <> inline +void writeText(const PostSyncAction& value, std::string& output) +{ + switch (value) + { + case PostSyncAction::none: + output = "None"; + break; + case PostSyncAction::sleep: + output = "Sleep"; + break; + case PostSyncAction::shutdown: + output = "Shutdown"; + break; + } +} + +template <> inline +bool readText(const std::string& input, PostSyncAction& value) +{ + const std::string tmp = trimCpy(input); + if (tmp == "None") + value = PostSyncAction::none; + else if (tmp == "Sleep") + value = PostSyncAction::sleep; + else if (tmp == "Shutdown") + value = PostSyncAction::shutdown; + else + return false; + return true; +} + + +template <> inline +void writeText(const FileIconSize& value, std::string& output) +{ + switch (value) + { + case FileIconSize::SMALL: + output = "Small"; + break; + case FileIconSize::MEDIUM: + output = "Medium"; + break; + case FileIconSize::LARGE: + output = "Large"; + break; + } +} + +template <> inline +bool readText(const std::string& input, FileIconSize& value) +{ + const std::string tmp = trimCpy(input); + if (tmp == "Small") + value = FileIconSize::SMALL; + else if (tmp == "Medium") + value = FileIconSize::MEDIUM; + else if (tmp == "Large") + value = FileIconSize::LARGE; + else + return false; + return true; +} + + +template <> inline +void writeText(const DeletionPolicy& value, std::string& output) +{ + switch (value) + { + case DeletionPolicy::permanent: + output = "Permanent"; + break; + case DeletionPolicy::recycler: + output = "RecycleBin"; + break; + case DeletionPolicy::versioning: + output = "Versioning"; + break; + } +} + +template <> inline +bool readText(const std::string& input, DeletionPolicy& value) +{ + const std::string tmp = trimCpy(input); + if (tmp == "Permanent") + value = DeletionPolicy::permanent; + else if (tmp == "RecycleBin") + value = DeletionPolicy::recycler; + else if (tmp == "Versioning") + value = DeletionPolicy::versioning; + else + return false; + return true; +} + + +template <> inline +void writeText(const SymLinkHandling& value, std::string& output) +{ + switch (value) + { + case SymLinkHandling::EXCLUDE: + output = "Exclude"; + break; + case SymLinkHandling::DIRECT: + output = "Direct"; + break; + case SymLinkHandling::FOLLOW: + output = "Follow"; + break; + } +} + +template <> inline +bool readText(const std::string& input, SymLinkHandling& value) +{ + const std::string tmp = trimCpy(input); + if (tmp == "Exclude") + value = SymLinkHandling::EXCLUDE; + else if (tmp == "Direct") + value = SymLinkHandling::DIRECT; + else if (tmp == "Follow") + value = SymLinkHandling::FOLLOW; + else + return false; + return true; +} + + +template <> inline +void writeText(const ColumnTypeRim& value, std::string& output) +{ + switch (value) + { + case ColumnTypeRim::ITEM_PATH: + output = "Path"; + break; + case ColumnTypeRim::SIZE: + output = "Size"; + break; + case ColumnTypeRim::DATE: + output = "Date"; + break; + case ColumnTypeRim::EXTENSION: + output = "Ext"; + break; + } +} + +template <> inline +bool readText(const std::string& input, ColumnTypeRim& value) +{ + const std::string tmp = trimCpy(input); + if (tmp == "Path") + value = ColumnTypeRim::ITEM_PATH; + else if (tmp == "Size") + value = ColumnTypeRim::SIZE; + else if (tmp == "Date") + value = ColumnTypeRim::DATE; + else if (tmp == "Ext") + value = ColumnTypeRim::EXTENSION; + else + return false; + return true; +} + + +template <> inline +void writeText(const ItemPathFormat& value, std::string& output) +{ + switch (value) + { + case ItemPathFormat::FULL_PATH: + output = "Full"; + break; + case ItemPathFormat::RELATIVE_PATH: + output = "Relative"; + break; + case ItemPathFormat::ITEM_NAME: + output = "Item"; + break; + } +} + +template <> inline +bool readText(const std::string& input, ItemPathFormat& value) +{ + const std::string tmp = trimCpy(input); + if (tmp == "Full") + value = ItemPathFormat::FULL_PATH; + else if (tmp == "Relative") + value = ItemPathFormat::RELATIVE_PATH; + else if (tmp == "Item") + value = ItemPathFormat::ITEM_NAME; + else + return false; + return true; +} + +template <> inline +void writeText(const ColumnTypeCfg& value, std::string& output) +{ + switch (value) + { + case ColumnTypeCfg::name: + output = "Name"; + break; + case ColumnTypeCfg::lastSync: + output = "Last"; + break; + case ColumnTypeCfg::lastLog: + output = "Log"; + break; + } +} + +template <> inline +bool readText(const std::string& input, ColumnTypeCfg& value) +{ + const std::string tmp = trimCpy(input); + if (tmp == "Name") + value = ColumnTypeCfg::name; + else if (tmp == "Last") + value = ColumnTypeCfg::lastSync; + else if (tmp == "Log") + value = ColumnTypeCfg::lastLog; + else + return false; + return true; +} + + +template <> inline +void writeText(const ColumnTypeTree& value, std::string& output) +{ + switch (value) + { + case ColumnTypeTree::FOLDER_NAME: + output = "Tree"; + break; + case ColumnTypeTree::ITEM_COUNT: + output = "Count"; + break; + case ColumnTypeTree::BYTES: + output = "Bytes"; + break; + } +} + +template <> inline +bool readText(const std::string& input, ColumnTypeTree& value) +{ + const std::string tmp = trimCpy(input); + if (tmp == "Tree") + value = ColumnTypeTree::FOLDER_NAME; + else if (tmp == "Count") + value = ColumnTypeTree::ITEM_COUNT; + else if (tmp == "Bytes") + value = ColumnTypeTree::BYTES; + else + return false; + return true; +} + + +template <> inline +void writeText(const UnitSize& value, std::string& output) +{ + switch (value) + { + case UnitSize::NONE: + output = "None"; + break; + case UnitSize::BYTE: + output = "Byte"; + break; + case UnitSize::KB: + output = "KB"; + break; + case UnitSize::MB: + output = "MB"; + break; + } +} + +template <> inline +bool readText(const std::string& input, UnitSize& value) +{ + const std::string tmp = trimCpy(input); + if (tmp == "None") + value = UnitSize::NONE; + else if (tmp == "Byte") + value = UnitSize::BYTE; + else if (tmp == "KB") + value = UnitSize::KB; + else if (tmp == "MB") + value = UnitSize::MB; + else + return false; + return true; +} + +template <> inline +void writeText(const UnitTime& value, std::string& output) +{ + switch (value) + { + case UnitTime::NONE: + output = "None"; + break; + case UnitTime::TODAY: + output = "Today"; + break; + case UnitTime::THIS_MONTH: + output = "Month"; + break; + case UnitTime::THIS_YEAR: + output = "Year"; + break; + case UnitTime::LAST_X_DAYS: + output = "x-days"; + break; + } +} + +template <> inline +bool readText(const std::string& input, UnitTime& value) +{ + const std::string tmp = trimCpy(input); + if (tmp == "None") + value = UnitTime::NONE; + else if (tmp == "Today") + value = UnitTime::TODAY; + else if (tmp == "Month") + value = UnitTime::THIS_MONTH; + else if (tmp == "Year") + value = UnitTime::THIS_YEAR; + else if (tmp == "x-days") + value = UnitTime::LAST_X_DAYS; + else + return false; + return true; +} + + +template <> inline +void writeText(const LogFileFormat& value, std::string& output) +{ + switch (value) + { + case LogFileFormat::html: + output = "HTML"; + break; + case LogFileFormat::text: + output = "Text"; + break; + } +} + +template <> inline +bool readText(const std::string& input, LogFileFormat& value) +{ + const std::string tmp = trimCpy(input); + if (tmp == "HTML") + value = LogFileFormat::html; + else if (tmp == "Text") + value = LogFileFormat::text; + else + return false; + return true; +} + + +template <> inline +void writeText(const VersioningStyle& value, std::string& output) +{ + switch (value) + { + case VersioningStyle::replace: + output = "Replace"; + break; + case VersioningStyle::timestampFolder: + output = "TimeStamp-Folder"; + break; + case VersioningStyle::timestampFile: + output = "TimeStamp-File"; + break; + } +} + +template <> inline +bool readText(const std::string& input, VersioningStyle& value) +{ + const std::string tmp = trimCpy(input); + if (tmp == "Replace") + value = VersioningStyle::replace; + else if (tmp == "TimeStamp-Folder") + value = VersioningStyle::timestampFolder; + else if (tmp == "TimeStamp-File") + value = VersioningStyle::timestampFile; + else + return false; + return true; +} + + +template <> inline +void writeText(const DirectionConfig::Variant& value, std::string& output) +{ + switch (value) + { + case DirectionConfig::TWO_WAY: + output = "TwoWay"; + break; + case DirectionConfig::MIRROR: + output = "Mirror"; + break; + case DirectionConfig::UPDATE: + output = "Update"; + break; + case DirectionConfig::CUSTOM: + output = "Custom"; + break; + } +} + +template <> inline +bool readText(const std::string& input, DirectionConfig::Variant& value) +{ + const std::string tmp = trimCpy(input); + if (tmp == "TwoWay") + value = DirectionConfig::TWO_WAY; + else if (tmp == "Mirror") + value = DirectionConfig::MIRROR; + else if (tmp == "Update") + value = DirectionConfig::UPDATE; + else if (tmp == "Custom") + value = DirectionConfig::CUSTOM; + else + return false; + return true; +} + + +template <> inline +bool readStruc(const XmlElement& input, ColAttributesRim& value) +{ + XmlIn in(input); + bool rv1 = in.attribute("Type", value.type); + bool rv2 = in.attribute("Visible", value.visible); + bool rv3 = in.attribute("Width", value.offset); //offset == width if stretch is 0 + bool rv4 = in.attribute("Stretch", value.stretch); + return rv1 && rv2 && rv3 && rv4; +} + +template <> inline +void writeStruc(const ColAttributesRim& value, XmlElement& output) +{ + XmlOut out(output); + out.attribute("Type", value.type); + out.attribute("Visible", value.visible); + out.attribute("Width", value.offset); + out.attribute("Stretch", value.stretch); +} + + +template <> inline +bool readStruc(const XmlElement& input, ColAttributesCfg& value) +{ + XmlIn in(input); + bool rv1 = in.attribute("Type", value.type); + bool rv2 = in.attribute("Visible", value.visible); + bool rv3 = in.attribute("Width", value.offset); //offset == width if stretch is 0 + bool rv4 = in.attribute("Stretch", value.stretch); + return rv1 && rv2 && rv3 && rv4; +} + +template <> inline +void writeStruc(const ColAttributesCfg& value, XmlElement& output) +{ + XmlOut out(output); + out.attribute("Type", value.type); + out.attribute("Visible", value.visible); + out.attribute("Width", value.offset); + out.attribute("Stretch", value.stretch); +} + + +template <> inline +bool readStruc(const XmlElement& input, ColAttributesTree& value) +{ + XmlIn in(input); + bool rv1 = in.attribute("Type", value.type); + bool rv2 = in.attribute("Visible", value.visible); + bool rv3 = in.attribute("Width", value.offset); //offset == width if stretch is 0 + bool rv4 = in.attribute("Stretch", value.stretch); + return rv1 && rv2 && rv3 && rv4; +} + +template <> inline +void writeStruc(const ColAttributesTree& value, XmlElement& output) +{ + XmlOut out(output); + out.attribute("Type", value.type); + out.attribute("Visible", value.visible); + out.attribute("Width", value.offset); + out.attribute("Stretch", value.stretch); +} + + +template <> inline +bool readStruc(const XmlElement& input, ViewFilterDefault& value) +{ + XmlIn in(input); + + bool success = true; + auto readAttr = [&](XmlIn& elemIn, const char name[], bool& v) + { + if (!elemIn.attribute(name, v)) + success = false; + }; + + readAttr(in, "Equal", value.equal); + readAttr(in, "Conflict", value.conflict); + readAttr(in, "Excluded", value.excluded); + + XmlIn catView = in["CategoryView"]; + readAttr(catView, "LeftOnly", value.leftOnly); + readAttr(catView, "RightOnly", value.rightOnly); + readAttr(catView, "LeftNewer", value.leftNewer); + readAttr(catView, "RightNewer", value.rightNewer); + readAttr(catView, "Different", value.different); + + XmlIn actView = in["ActionView"]; + readAttr(actView, "CreateLeft", value.createLeft); + readAttr(actView, "CreateRight", value.createRight); + readAttr(actView, "UpdateLeft", value.updateLeft); + readAttr(actView, "UpdateRight", value.updateRight); + readAttr(actView, "DeleteLeft", value.deleteLeft); + readAttr(actView, "DeleteRight", value.deleteRight); + readAttr(actView, "DoNothing", value.doNothing); + + return success; //[!] avoid short-circuit evaluation above +} + +template <> inline +void writeStruc(const ViewFilterDefault& value, XmlElement& output) +{ + XmlOut out(output); + + out.attribute("Equal", value.equal); + out.attribute("Conflict", value.conflict); + out.attribute("Excluded", value.excluded); + + XmlOut catView = out["CategoryView"]; + catView.attribute("LeftOnly", value.leftOnly); + catView.attribute("RightOnly", value.rightOnly); + catView.attribute("LeftNewer", value.leftNewer); + catView.attribute("RightNewer", value.rightNewer); + catView.attribute("Different", value.different); + + XmlOut actView = out["ActionView"]; + actView.attribute("CreateLeft", value.createLeft); + actView.attribute("CreateRight", value.createRight); + actView.attribute("UpdateLeft", value.updateLeft); + actView.attribute("UpdateRight", value.updateRight); + actView.attribute("DeleteLeft", value.deleteLeft); + actView.attribute("DeleteRight", value.deleteRight); + actView.attribute("DoNothing", value.doNothing); +} + + +template <> inline +bool readStruc(const XmlElement& input, ExternalApp& value) +{ + XmlIn in(input); + const bool rv1 = in(value.cmdLine); + const bool rv2 = in.attribute("Label", value.description); + return rv1 && rv2; +} + +template <> inline +void writeStruc(const ExternalApp& value, XmlElement& output) +{ + XmlOut out(output); + out(value.cmdLine); + out.attribute("Label", value.description); +} + + +template <> inline +void writeText(const SyncResult& value, std::string& output) +{ + switch (value) + { + case SyncResult::finishedSuccess: + output = "Success"; + break; + case SyncResult::finishedWarning: + output = "Warning"; + break; + case SyncResult::finishedError: + output = "Error"; + break; + case SyncResult::aborted: + output = "Stopped"; + break; + } +} + +template <> inline +bool readText(const std::string& input, SyncResult& value) +{ + const std::string tmp = trimCpy(input); + if (tmp == "Success") + value = SyncResult::finishedSuccess; + else if (tmp == "Warning") + value = SyncResult::finishedWarning; + else if (tmp == "Error") + value = SyncResult::finishedError; + else if (tmp == "Stopped") + value = SyncResult::aborted; + else + return false; + return true; +} +} + + +namespace +{ + + +Zstring substituteFreeFileSyncDriveLetter(const Zstring& cfgFilePath) +{ + return cfgFilePath; +} + +Zstring resolveFreeFileSyncDriveMacro(const Zstring& cfgFilePhrase) +{ + return cfgFilePhrase; +} + + +Zstring substituteFfsResourcePath(const Zstring& filePath) +{ + const Zstring resPathPf = getResourceDirPf(); + if (startsWith(trimCpy(filePath, true, false), resPathPf)) + return Zstring(Zstr("%ffs_resource%")) + FILE_NAME_SEPARATOR + afterFirst(filePath, resPathPf, IF_MISSING_RETURN_NONE); + return filePath; +} + +Zstring resolveFfsResourceMacro(const Zstring& filePhrase) +{ + if (startsWith(trimCpy(filePhrase, true, false), Zstring(Zstr("%ffs_resource%")) + FILE_NAME_SEPARATOR)) + return getResourceDirPf() + afterFirst(filePhrase, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE); + return filePhrase; +} +} + + +namespace zen +{ +template <> inline +bool readStruc(const XmlElement& input, ConfigFileItem& value) +{ + XmlIn in(input); + + const bool rv1 = in.attribute("Result", value.logResult); + + Zstring cfgPathRaw; + bool rv2 = in.attribute("Config", cfgPathRaw); + if (!rv2) //TODO: remove after migration! 2020-02-09 + rv2 = in.attribute("CfgPath", cfgPathRaw); // + + //FFS portable: use special syntax for config file paths: e.g. "FFS:\SyncJob.ffs_gui" + if (rv2) value.cfgFilePath = resolveFreeFileSyncDriveMacro(cfgPathRaw); + + const bool rv3 = in.attribute("LastSync", value.lastSyncTime); + + Zstring logPathPhrase; + bool rv4 = in.attribute("Log", logPathPhrase); + if (!rv4) //TODO: remove after migration! 2020-02-09 + rv4 = in.attribute("LogPath", logPathPhrase); // + + if (rv4) value.logFilePath = createAbstractPath(resolveFreeFileSyncDriveMacro(logPathPhrase)); + + std::string hexColor; //optional XML attribute! + if (in.attribute("Color", hexColor) && hexColor.size() == 6) + value.backColor.Set(unhexify(hexColor[0], hexColor[1]), + unhexify(hexColor[2], hexColor[3]), + unhexify(hexColor[4], hexColor[5])); + + return rv1 && rv2 && rv3 && rv4; +} + +template <> inline +void writeStruc(const ConfigFileItem& value, XmlElement& output) +{ + XmlOut out(output); + out.attribute("Result", value.logResult); + out.attribute("Config", substituteFreeFileSyncDriveLetter(value.cfgFilePath)); + out.attribute("LastSync", value.lastSyncTime); + + if (std::optional nativePath = AFS::getNativeItemPath(value.logFilePath)) + out.attribute("Log", substituteFreeFileSyncDriveLetter(*nativePath)); + else + out.attribute("Log", AFS::getInitPathPhrase(value.logFilePath)); + + if (value.backColor.IsOk()) + { + const auto& [highR, lowR] = hexify(value.backColor.Red ()); + const auto& [highG, lowG] = hexify(value.backColor.Green()); + const auto& [highB, lowB] = hexify(value.backColor.Blue ()); + out.attribute("Color", std::string({ highR, lowR, highG, lowG, highB, lowB })); + } +} + +//TODO: remove after migration! 2018-07-27 +struct ConfigFileItemV9 +{ + Zstring filePath; + time_t lastSyncTime = 0; +}; +template <> inline +bool readStruc(const XmlElement& input, ConfigFileItemV9& value) +{ + XmlIn in(input); + + Zstring rawPath; + const bool rv1 = in(rawPath); + if (rv1) value.filePath = resolveFreeFileSyncDriveMacro(rawPath); + + const bool rv2 = in.attribute("LastSync", value.lastSyncTime); + return rv1 && rv2; +} +} + + +namespace +{ +void readConfig(const XmlIn& in, CompConfig& cmpCfg) +{ + in["Variant" ](cmpCfg.compareVar); + in["Symlinks"](cmpCfg.handleSymlinks); + + //TODO: remove old parameter after migration! 2015-11-05 + if (in["TimeShift"]) + { + std::wstring timeShiftPhrase; + if (in["TimeShift"](timeShiftPhrase)) + cmpCfg.ignoreTimeShiftMinutes = fromTimeShiftPhrase(timeShiftPhrase); + } + else + { + std::wstring timeShiftPhrase; + if (in["IgnoreTimeShift"](timeShiftPhrase)) + cmpCfg.ignoreTimeShiftMinutes = fromTimeShiftPhrase(timeShiftPhrase); + } +} + + +void readConfig(const XmlIn& in, DirectionConfig& dirCfg) +{ + in["Variant"](dirCfg.var); + + if (dirCfg.var == DirectionConfig::CUSTOM) + { + XmlIn inCustDir = in["CustomDirections"]; + inCustDir["LeftOnly" ](dirCfg.custom.exLeftSideOnly); + inCustDir["RightOnly" ](dirCfg.custom.exRightSideOnly); + inCustDir["LeftNewer" ](dirCfg.custom.leftNewer); + inCustDir["RightNewer"](dirCfg.custom.rightNewer); + inCustDir["Different" ](dirCfg.custom.different); + inCustDir["Conflict" ](dirCfg.custom.conflict); + } + //else + // dirCfg.custom = DirectionSet(); + + in["DetectMovedFiles"](dirCfg.detectMovedFiles); +} + + +void readConfig(const XmlIn& in, SyncConfig& syncCfg, std::map& deviceParallelOps, int formatVer) +{ + readConfig(in, syncCfg.directionCfg); + + in["DeletionPolicy" ](syncCfg.handleDeletion); + in["VersioningFolder"](syncCfg.versioningFolderPhrase); + + if (formatVer < 12) //TODO: remove if parameter migration after some time! 2018-06-21 + { + std::string tmp; + in["VersioningFolder"].attribute("Style", tmp); + + trim(tmp); + if (tmp == "Replace") + syncCfg.versioningStyle = VersioningStyle::replace; + else if (tmp == "TimeStamp") + syncCfg.versioningStyle = VersioningStyle::timestampFile; + + if (syncCfg.versioningStyle == VersioningStyle::replace) + { + if (endsWithAsciiNoCase(syncCfg.versioningFolderPhrase, "/%timestamp%") || + endsWithAsciiNoCase(syncCfg.versioningFolderPhrase, "\\%timestamp%")) + { + syncCfg.versioningFolderPhrase.resize(syncCfg.versioningFolderPhrase.size() - strLength(Zstr("/%timestamp%"))); + syncCfg.versioningStyle = VersioningStyle::timestampFolder; + + if (syncCfg.versioningFolderPhrase.size() == 2 && isAsciiAlpha(syncCfg.versioningFolderPhrase[0]) && syncCfg.versioningFolderPhrase[1] == Zstr(':')) + syncCfg.versioningFolderPhrase += Zstr('\\'); + } + } + } + else + { + size_t parallelOps = 1; + if (const XmlElement* e = in["VersioningFolder"].get()) e->getAttribute("Threads", parallelOps); //try to get attribute + + const size_t parallelOpsPrev = getDeviceParallelOps(deviceParallelOps, syncCfg.versioningFolderPhrase); + /**/ setDeviceParallelOps(deviceParallelOps, syncCfg.versioningFolderPhrase, std::max(parallelOps, parallelOpsPrev)); + + in["VersioningFolder"].attribute("Style", syncCfg.versioningStyle); + + if (syncCfg.versioningStyle != VersioningStyle::replace) + if (const XmlElement* e = in["VersioningFolder"].get()) + { + e->getAttribute("MaxAge", syncCfg.versionMaxAgeDays); //try to get attributes if available + + //TODO: remove if clause after migration! 2018-07-12 + if (formatVer < 13) + { + e->getAttribute("CountMin", syncCfg.versionCountMin); // => *no error* if not available + e->getAttribute("CountMax", syncCfg.versionCountMax); // + } + else + { + e->getAttribute("MinCount", syncCfg.versionCountMin); // => *no error* if not available + e->getAttribute("MaxCount", syncCfg.versionCountMax); // + } + } + } +} + + +void readConfig(const XmlIn& in, FilterConfig& filter, int formatVer) +{ + std::vector tmpIn = splitFilterByLines(filter.includeFilter); //consider default value + in["Include"](tmpIn); + filter.includeFilter = mergeFilterLines(tmpIn); + + std::vector tmpEx = splitFilterByLines(filter.excludeFilter); //consider default value + in["Exclude"](tmpEx); + filter.excludeFilter = mergeFilterLines(tmpEx); + + //TODO: remove macro migration after some time! 2017-02-16 + if (formatVer <= 6) replace(filter.includeFilter, Zstr(';'), Zstr('|')); + if (formatVer <= 6) replace(filter.excludeFilter, Zstr(';'), Zstr('|')); + + in["TimeSpan"](filter.timeSpan); + in["TimeSpan"].attribute("Type", filter.unitTimeSpan); + + in["SizeMin"](filter.sizeMin); + in["SizeMin"].attribute("Unit", filter.unitSizeMin); + + in["SizeMax"](filter.sizeMax); + in["SizeMax"].attribute("Unit", filter.unitSizeMax); +} + + +void readConfig(const XmlIn& in, LocalPairConfig& lpc, std::map& deviceParallelOps, int formatVer) +{ + //read folder pairs + in["Left" ](lpc.folderPathPhraseLeft); + in["Right"](lpc.folderPathPhraseRight); + + size_t parallelOpsL = 1; + size_t parallelOpsR = 1; + + //TODO: remove old parameter after migration! 2018-04-14 + if (formatVer < 11) + { + auto getParallelOps = [&](const Zstring& folderPathPhrase, size_t& parallelOps) + { + if (startsWithAsciiNoCase(folderPathPhrase, "sftp:") || + startsWithAsciiNoCase(folderPathPhrase, "ftp:")) + { + for (const Zstring& optPhrase : split(folderPathPhrase, Zstr("|"), SplitType::SKIP_EMPTY)) + if (startsWith(optPhrase, Zstr("con="))) + parallelOps = stringTo(afterFirst(optPhrase, Zstr("con="), IF_MISSING_RETURN_NONE)); + } + }; + getParallelOps(lpc.folderPathPhraseLeft, parallelOpsL); + getParallelOps(lpc.folderPathPhraseRight, parallelOpsR); + } + else + { + if (const XmlElement* e = in["Left" ].get()) e->getAttribute("Threads", parallelOpsL); //try to get attributes: + if (const XmlElement* e = in["Right"].get()) e->getAttribute("Threads", parallelOpsR); // => *no error* if not available + //in["Left" ].attribute("Threads", parallelOpsL); + //in["Right"].attribute("Threads", parallelOpsR); + } + + auto setParallelOps = [&](const Zstring& folderPathPhrase, size_t parallelOps) + { + const size_t parallelOpsPrev = getDeviceParallelOps(deviceParallelOps, folderPathPhrase); + /**/ setDeviceParallelOps(deviceParallelOps, folderPathPhrase, std::max(parallelOps, parallelOpsPrev)); + }; + setParallelOps(lpc.folderPathPhraseLeft, parallelOpsL); + setParallelOps(lpc.folderPathPhraseRight, parallelOpsR); + + //TODO: remove after migration - 2016-07-24 + auto ciReplace = [](Zstring& pathPhrase, const Zstring& oldTerm, const Zstring& newTerm) { pathPhrase = replaceCpyAsciiNoCase(pathPhrase, oldTerm, newTerm); }; + ciReplace(lpc.folderPathPhraseLeft, Zstr("%csidl_MyDocuments%"), Zstr("%csidl_Documents%")); + ciReplace(lpc.folderPathPhraseLeft, Zstr("%csidl_MyMusic%" ), Zstr("%csidl_Music%")); + ciReplace(lpc.folderPathPhraseLeft, Zstr("%csidl_MyPictures%" ), Zstr("%csidl_Pictures%")); + ciReplace(lpc.folderPathPhraseLeft, Zstr("%csidl_MyVideos%" ), Zstr("%csidl_Videos%")); + ciReplace(lpc.folderPathPhraseRight, Zstr("%csidl_MyDocuments%"), Zstr("%csidl_Documents%")); + ciReplace(lpc.folderPathPhraseRight, Zstr("%csidl_MyMusic%" ), Zstr("%csidl_Music%")); + ciReplace(lpc.folderPathPhraseRight, Zstr("%csidl_MyPictures%" ), Zstr("%csidl_Pictures%")); + ciReplace(lpc.folderPathPhraseRight, Zstr("%csidl_MyVideos%" ), Zstr("%csidl_Videos%")); + + //TODO: remove after migration 2016-09-27 + if (formatVer < 6) //the-base64-encoded password is now stored as an option at the string end + { + //sftp://username:[base64]c2VjcmV0c@private.example.com -> + //sftp://username@private.example.com|pass64=c2VjcmV0c + auto updateSftpSyntax = [](Zstring& pathPhrase) + { + const size_t pos = pathPhrase.find(Zstr(":[base64]")); + if (pos != Zstring::npos) + { + const size_t posEnd = pathPhrase.find(Zstr("@"), pos); + if (posEnd != Zstring::npos) + pathPhrase = Zstring(pathPhrase.begin(), pathPhrase.begin() + pos) + (pathPhrase.c_str() + posEnd) + + Zstr("|pass64=") + Zstring(pathPhrase.begin() + pos + strLength(Zstr(":[base64]")), pathPhrase.begin() + posEnd); + } + }; + updateSftpSyntax(lpc.folderPathPhraseLeft); + updateSftpSyntax(lpc.folderPathPhraseRight); + } + + //########################################################### + //alternate comp configuration (optional) + if (XmlIn inLocalCmp = in[formatVer < 10 ? "CompareConfig" : "Compare"]) //TODO: remove if parameter migration after some time! 2018-02-25 + { + CompConfig cmpCfg; + readConfig(inLocalCmp, cmpCfg); + + lpc.localCmpCfg = cmpCfg; + } + //########################################################### + //alternate sync configuration (optional) + if (XmlIn inLocalSync = in[formatVer < 10 ? "SyncConfig" : "Synchronize"]) //TODO: remove if parameter migration after some time! 2018-02-25 + { + SyncConfig syncCfg; + readConfig(inLocalSync, syncCfg, deviceParallelOps, formatVer); + + lpc.localSyncCfg = syncCfg; + } + + //########################################################### + //alternate filter configuration + if (XmlIn inLocFilter = in[formatVer < 10 ? "LocalFilter" : "Filter"]) //TODO: remove if parameter migration after some time! 2018-02-25 + readConfig(inLocFilter, lpc.localFilter, formatVer); +} + + +void readConfig(const XmlIn& in, MainConfiguration& mainCfg, int formatVer) +{ + XmlIn inMain = formatVer < 10 ? in["MainConfig"] : in; //TODO: remove if parameter migration after some time! 2018-02-25 + + if (formatVer < 10) //TODO: remove if parameter migration after some time! 2018-02-25 + readConfig(inMain["Comparison"], mainCfg.cmpCfg); + else + readConfig(inMain["Compare"], mainCfg.cmpCfg); + //########################################################### + + //read sync configuration + if (formatVer < 10) //TODO: remove if parameter migration after some time! 2018-02-25 + readConfig(inMain["SyncConfig"], mainCfg.syncCfg, mainCfg.deviceParallelOps, formatVer); + else + readConfig(inMain["Synchronize"], mainCfg.syncCfg, mainCfg.deviceParallelOps, formatVer); + + //########################################################### + + //read filter settings + if (formatVer < 10) //TODO: remove if parameter migration after some time! 2018-02-25 + readConfig(inMain["GlobalFilter"], mainCfg.globalFilter, formatVer); + else + readConfig(inMain["Filter"], mainCfg.globalFilter, formatVer); + + //########################################################### + //read folder pairs + bool firstItem = true; + for (XmlIn inPair = inMain["FolderPairs"]["Pair"]; inPair; inPair.next()) + { + LocalPairConfig lpc; + readConfig(inPair, lpc, mainCfg.deviceParallelOps, formatVer); + + if (firstItem) + { + firstItem = false; + mainCfg.firstPair = lpc; + mainCfg.additionalPairs.clear(); + } + else + mainCfg.additionalPairs.push_back(lpc); + } + + //TODO: remove if parameter migration after some time! 2017-10-24 + if (formatVer < 8) + ; + else + //TODO: remove if parameter migration after some time! 2018-02-24 + if (formatVer < 10) + inMain["IgnoreErrors"](mainCfg.ignoreErrors); + else + { + inMain["Errors"].attribute("Ignore", mainCfg.ignoreErrors); + inMain["Errors"].attribute("Retry", mainCfg.automaticRetryCount); + inMain["Errors"].attribute("Delay", mainCfg.automaticRetryDelay); + } + + //TODO: remove if parameter migration after some time! 2017-10-24 + if (formatVer < 8) + inMain["OnCompletion"](mainCfg.postSyncCommand); + else + { + inMain["PostSyncCommand"](mainCfg.postSyncCommand); + inMain["PostSyncCommand"].attribute("Condition", mainCfg.postSyncCondition); + } + + //TODO: remove if parameter migration after some time! 2018-08-13 + if (formatVer < 14) + ; //path will be extracted from BatchExclusiveConfig + else + inMain["LogFolder"](mainCfg.altLogFolderPathPhrase); + + //TODO: remove if parameter migration after some time! 2020-01-30 + if (formatVer < 15) + ; + else + { + inMain["EmailNotification"](mainCfg.emailNotifyAddress); + inMain["EmailNotification"].attribute("Condition", mainCfg.emailNotifyCondition); + } +} + + +void readConfig(const XmlIn& in, XmlGuiConfig& cfg, int formatVer) +{ + //read main config + readConfig(in, cfg.mainCfg, formatVer); + + //read GUI specific config data + XmlIn inGuiCfg = in[formatVer < 10 ? "GuiConfig" : "Gui"]; //TODO: remove if parameter migration after some time! 2018-02-25 + + std::string val; + if (inGuiCfg["MiddleGridView"](val)) //refactor into enum!? + cfg.highlightSyncAction = val == "Action"; + + //TODO: remove if clause after migration! 2017-10-24 + if (formatVer < 8) + { + std::string str; + if (inGuiCfg["HandleError"](str)) + cfg.mainCfg.ignoreErrors = str == "Ignore"; + + str = trimCpy(utfTo(cfg.mainCfg.postSyncCommand)); + if (equalAsciiNoCase(str, "Close progress dialog")) + cfg.mainCfg.postSyncCommand.clear(); + } +} + + +void readConfig(const XmlIn& in, BatchExclusiveConfig& cfg, int formatVer) +{ + XmlIn inBatchCfg = in[formatVer < 10 ? "BatchConfig" : "Batch"]; //TODO: remove if parameter migration after some time! 2018-02-25 + + //TODO: remove if clause after migration! 2018-02-01 + if (formatVer < 9) + inBatchCfg["RunMinimized"](cfg.runMinimized); + else + inBatchCfg["ProgressDialog"].attribute("Minimized", cfg.runMinimized); + + //TODO: remove if clause after migration! 2018-02-01 + if (formatVer < 9) + ; //n/a + else + inBatchCfg["ProgressDialog"].attribute("AutoClose", cfg.autoCloseSummary); + + //TODO: remove if clause after migration! 2017-10-24 + if (formatVer < 8) + { + std::string str; + if (inBatchCfg["HandleError"](str)) + cfg.batchErrorHandling = str == "Stop" ? BatchErrorHandling::cancel : BatchErrorHandling::showPopup; + } + else + inBatchCfg["ErrorDialog"](cfg.batchErrorHandling); + + //TODO: remove if clause after migration! 2017-10-24 + if (formatVer < 8) + ; //n/a + //TODO: remove if clause after migration! 2018-02-01 + else if (formatVer == 8) + { + std::string tmp; + if (inBatchCfg["PostSyncAction"](tmp)) + { + tmp = trimCpy(tmp); + if (tmp == "Summary") + cfg.postSyncAction = PostSyncAction::none; + else if (tmp == "Exit") + cfg.autoCloseSummary = true; + else if (tmp == "Sleep") + cfg.postSyncAction = PostSyncAction::sleep; + else if (tmp == "Shutdown") + cfg.postSyncAction = PostSyncAction::shutdown; + } + } + else + inBatchCfg["PostSyncAction"](cfg.postSyncAction); +} + + +void readConfig(const XmlIn& in, XmlBatchConfig& cfg, int formatVer) +{ + readConfig(in, cfg.mainCfg, formatVer); + readConfig(in, cfg.batchExCfg, formatVer); + + //TODO: remove if clause after migration! 2018-08-13 + if (formatVer < 14) + { + XmlIn inBatchCfg = in[formatVer < 10 ? "BatchConfig" : "Batch"]; + inBatchCfg["LogfileFolder"](cfg.mainCfg.altLogFolderPathPhrase); + } + + //TODO: remove if clause after migration! 2017-10-24 + if (formatVer < 8) + { + std::string str; + if (in["BatchConfig"]["HandleError"](str)) + cfg.mainCfg.ignoreErrors = str == "Ignore"; + + str = trimCpy(utfTo(cfg.mainCfg.postSyncCommand)); + if (equalAsciiNoCase(str, "Close progress dialog")) + { + cfg.batchExCfg.autoCloseSummary = true; + cfg.mainCfg.postSyncCommand.clear(); + } + else if (str == "rundll32.exe powrprof.dll,SetSuspendState Sleep" || + str == "rundll32.exe powrprof.dll,SetSuspendState" || + str == "systemctl suspend" || + str == "osascript -e 'tell application \"System Events\" to sleep'") + { + cfg.batchExCfg.postSyncAction = PostSyncAction::sleep; + cfg.mainCfg.postSyncCommand.clear(); + } + else if (str == "shutdown /s /t 60" || + str == "shutdown -s -t 60" || + str == "systemctl poweroff" || + str == "osascript -e 'tell application \"System Events\" to shut down'") + { + cfg.batchExCfg.postSyncAction = PostSyncAction::shutdown; + cfg.mainCfg.postSyncCommand.clear(); + } + else if (cfg.batchExCfg.runMinimized) + cfg.batchExCfg.autoCloseSummary = true; + } +} + + +void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer) +{ + XmlIn inGeneral = in["General"]; + + //TODO: remove old parameter after migration! 2016-01-18 + if (in["Shared"]) + inGeneral = in["Shared"]; + + inGeneral["Language"].attribute("Name", cfg.programLanguage); + + inGeneral["FailSafeFileCopy" ].attribute("Enabled", cfg.failSafeFileCopy); + inGeneral["CopyLockedFiles" ].attribute("Enabled", cfg.copyLockedFiles); + inGeneral["CopyFilePermissions" ].attribute("Enabled", cfg.copyFilePermissions); + inGeneral["FileTimeTolerance" ].attribute("Seconds", cfg.fileTimeTolerance); + inGeneral["RunWithBackgroundPriority"].attribute("Enabled", cfg.runWithBackgroundPriority); + inGeneral["LockDirectoriesDuringSync"].attribute("Enabled", cfg.createLockFile); + inGeneral["VerifyCopiedFiles" ].attribute("Enabled", cfg.verifyFileCopy); + inGeneral["LogFiles" ].attribute("MaxAge", cfg.logfilesMaxAgeDays); + inGeneral["LogFiles" ].attribute("Format", cfg.logFormat); + inGeneral["NotificationSound" ].attribute("CompareFinished", cfg.soundFileCompareFinished); + inGeneral["NotificationSound" ].attribute("SyncFinished", cfg.soundFileSyncFinished); + inGeneral["ProgressDialog" ].attribute("AutoClose", cfg.autoCloseProgressDialog); + + //TODO: remove if parameter migration after some time! 2019-05-29 + if (formatVer < 13) + { + if (!cfg.soundFileCompareFinished.empty()) cfg.soundFileCompareFinished = getResourceDirPf() + cfg.soundFileCompareFinished; + if (!cfg.soundFileSyncFinished .empty()) cfg.soundFileSyncFinished = getResourceDirPf() + cfg.soundFileSyncFinished; + } + else + { + cfg.soundFileCompareFinished = resolveFfsResourceMacro(cfg.soundFileCompareFinished); + cfg.soundFileSyncFinished = resolveFfsResourceMacro(cfg.soundFileSyncFinished); + } + + //TODO: remove if parameter migration after some time! 2018-08-13 + if (formatVer < 14) + if (cfg.logfilesMaxAgeDays == 14) //default value was too small + cfg.logfilesMaxAgeDays = XmlGlobalSettings().logfilesMaxAgeDays; + + //TODO: remove old parameter after migration! 2018-02-04 + if (formatVer < 8) + { + XmlIn inOpt = inGeneral["OptionalDialogs"]; + inOpt["ConfirmStartSync" ].attribute("Enabled", cfg.confirmDlgs.confirmSyncStart); + inOpt["ConfirmSaveConfig" ].attribute("Enabled", cfg.confirmDlgs.popupOnConfigChange); + inOpt["ConfirmExternalCommandMassInvoke"].attribute("Enabled", cfg.confirmDlgs.confirmCommandMassInvoke); + inOpt["WarnUnresolvedConflicts" ].attribute("Enabled", cfg.warnDlgs.warnUnresolvedConflicts); + inOpt["WarnNotEnoughDiskSpace" ].attribute("Enabled", cfg.warnDlgs.warnNotEnoughDiskSpace); + inOpt["WarnSignificantDifference" ].attribute("Enabled", cfg.warnDlgs.warnSignificantDifference); + inOpt["WarnRecycleBinNotAvailable" ].attribute("Enabled", cfg.warnDlgs.warnRecyclerMissing); + inOpt["WarnInputFieldEmpty" ].attribute("Enabled", cfg.warnDlgs.warnInputFieldEmpty); + inOpt["WarnModificationTimeError" ].attribute("Enabled", cfg.warnDlgs.warnModificationTimeError); + inOpt["WarnDependentFolderPair" ].attribute("Enabled", cfg.warnDlgs.warnDependentFolderPair); + inOpt["WarnDependentBaseFolders" ].attribute("Enabled", cfg.warnDlgs.warnDependentBaseFolders); + inOpt["WarnDirectoryLockFailed" ].attribute("Enabled", cfg.warnDlgs.warnDirectoryLockFailed); + inOpt["WarnVersioningFolderPartOfSync" ].attribute("Enabled", cfg.warnDlgs.warnVersioningFolderPartOfSync); + } + else + { + XmlIn inOpt = inGeneral["OptionalDialogs"]; + inOpt["ConfirmStartSync" ].attribute("Show", cfg.confirmDlgs.confirmSyncStart); + inOpt["ConfirmSaveConfig" ].attribute("Show", cfg.confirmDlgs.popupOnConfigChange); + if (formatVer < 12) //TODO: remove old parameter after migration! 2019-02-09 + inOpt["ConfirmExternalCommandMassInvoke"].attribute("Show", cfg.confirmDlgs.confirmCommandMassInvoke); + else + inOpt["ConfirmCommandMassInvoke"].attribute("Show", cfg.confirmDlgs.confirmCommandMassInvoke); + inOpt["WarnFolderNotExisting" ].attribute("Show", cfg.warnDlgs.warnFolderNotExisting); + inOpt["WarnFoldersDifferInCase" ].attribute("Show", cfg.warnDlgs.warnFoldersDifferInCase); + inOpt["WarnUnresolvedConflicts" ].attribute("Show", cfg.warnDlgs.warnUnresolvedConflicts); + inOpt["WarnNotEnoughDiskSpace" ].attribute("Show", cfg.warnDlgs.warnNotEnoughDiskSpace); + inOpt["WarnSignificantDifference" ].attribute("Show", cfg.warnDlgs.warnSignificantDifference); + inOpt["WarnRecycleBinNotAvailable" ].attribute("Show", cfg.warnDlgs.warnRecyclerMissing); + inOpt["WarnInputFieldEmpty" ].attribute("Show", cfg.warnDlgs.warnInputFieldEmpty); + inOpt["WarnModificationTimeError" ].attribute("Show", cfg.warnDlgs.warnModificationTimeError); + inOpt["WarnDependentFolderPair" ].attribute("Show", cfg.warnDlgs.warnDependentFolderPair); + inOpt["WarnDependentBaseFolders" ].attribute("Show", cfg.warnDlgs.warnDependentBaseFolders); + inOpt["WarnDirectoryLockFailed" ].attribute("Show", cfg.warnDlgs.warnDirectoryLockFailed); + inOpt["WarnVersioningFolderPartOfSync"].attribute("Show", cfg.warnDlgs.warnVersioningFolderPartOfSync); + } + + //GUI-specific global settings (optional) + XmlIn inGui = in["Gui"]; + XmlIn inWnd = inGui["MainDialog"]; + + //read application window size and position + inWnd.attribute("Width", cfg.gui.mainDlg.dlgSize.x); + inWnd.attribute("Height", cfg.gui.mainDlg.dlgSize.y); + inWnd.attribute("PosX", cfg.gui.mainDlg.dlgPos.x); + inWnd.attribute("PosY", cfg.gui.mainDlg.dlgPos.y); + inWnd.attribute("Maximized", cfg.gui.mainDlg.isMaximized); + + //########################################################### + + //TODO: remove old parameter after migration! 2018-02-04 + if (formatVer < 8) + inWnd["CaseSensitiveSearch"].attribute("Enabled", cfg.gui.mainDlg.textSearchRespectCase); + else + //TODO: remove if parameter migration after some time! 2018-09-09 + if (formatVer < 11) + inWnd["Search"].attribute("CaseSensitive", cfg.gui.mainDlg.textSearchRespectCase); + else + inWnd["SearchPanel"].attribute("CaseSensitive", cfg.gui.mainDlg.textSearchRespectCase); + + //TODO: remove if parameter migration after some time! 2018-09-09 + if (formatVer < 11) + inWnd["FolderPairsVisible" ].attribute("Max", cfg.gui.mainDlg.folderPairsVisibleMax); + + //########################################################### + + XmlIn inConfig = inWnd["ConfigPanel"]; + inConfig.attribute("ScrollPos", cfg.gui.mainDlg.cfgGridTopRowPos); + inConfig.attribute("SyncOverdue", cfg.gui.mainDlg.cfgGridSyncOverdueDays); + inConfig.attribute("SortByColumn", cfg.gui.mainDlg.cfgGridLastSortColumn); + inConfig.attribute("SortAscending", cfg.gui.mainDlg.cfgGridLastSortAscending); + + inConfig["Columns"](cfg.gui.mainDlg.cfgGridColumnAttribs); + + //TODO: remove after migration! 2018-07-27 + if (formatVer < 10) //reset once to show the new log column + cfg.gui.mainDlg.cfgGridColumnAttribs = XmlGlobalSettings().gui.mainDlg.cfgGridColumnAttribs; + + //TODO: remove parameter migration after some time! 2018-01-08 + if (formatVer < 6) + { + inGui["ConfigHistory"].attribute("MaxSize", cfg.gui.mainDlg.cfgHistItemsMax); + + std::vector cfgHist; + inGui["ConfigHistory"](cfgHist); + + for (const Zstring& cfgPath : cfgHist) + cfg.gui.mainDlg.cfgFileHistory.emplace_back( + cfgPath, + 0, getNullPath(), SyncResult::finishedSuccess, wxNullColour); + } + //TODO: remove after migration! 2018-07-27 + else if (formatVer < 10) + { + inConfig["Configurations"].attribute("MaxSize", cfg.gui.mainDlg.cfgHistItemsMax); + + std::vector cfgFileHistory; + inConfig["Configurations"](cfgFileHistory); + + for (const ConfigFileItemV9& item : cfgFileHistory) + cfg.gui.mainDlg.cfgFileHistory.emplace_back(item.filePath, item.lastSyncTime, getNullPath(), SyncResult::finishedSuccess, wxNullColour); + } + else + { + inConfig["Configurations"].attribute("MaxSize", cfg.gui.mainDlg.cfgHistItemsMax); + inConfig["Configurations"](cfg.gui.mainDlg.cfgFileHistory); + } + //TODO: remove after migration! 2019-11-30 + if (formatVer < 15) + { + const Zstring lastRunConfigPath = getConfigDirPathPf() + Zstr("LastRun.ffs_gui"); + for (ConfigFileItem& item : cfg.gui.mainDlg.cfgFileHistory) + if (equalNativePath(item.cfgFilePath, lastRunConfigPath)) + item.backColor = wxColor(0xdd, 0xdd, 0xdd); //light grey from onCfgGridContext() + } + + //TODO: remove parameter migration after some time! 2018-01-08 + if (formatVer < 6) + { + inGui["LastUsedConfig"](cfg.gui.mainDlg.lastUsedConfigFiles); + } + else + { + std::vector cfgPaths; + if (inConfig["LastUsed"](cfgPaths)) + { + for (Zstring& filePath : cfgPaths) + filePath = resolveFreeFileSyncDriveMacro(filePath); + + cfg.gui.mainDlg.lastUsedConfigFiles = cfgPaths; + } + } + + //########################################################### + + XmlIn inOverview = inWnd["OverviewPanel"]; + inOverview.attribute("ShowPercentage", cfg.gui.mainDlg.treeGridShowPercentBar); + inOverview.attribute("SortByColumn", cfg.gui.mainDlg.treeGridLastSortColumn); + inOverview.attribute("SortAscending", cfg.gui.mainDlg.treeGridLastSortAscending); + + //read column attributes + XmlIn inColTree = inOverview["Columns"]; + inColTree(cfg.gui.mainDlg.treeGridColumnAttribs); + + XmlIn inFileGrid = inWnd["FilePanel"]; + //TODO: remove parameter migration after some time! 2018-01-08 + if (formatVer < 6) + inFileGrid = inWnd["CenterPanel"]; + + inFileGrid.attribute("ShowIcons", cfg.gui.mainDlg.showIcons); + inFileGrid.attribute("IconSize", cfg.gui.mainDlg.iconSize); + inFileGrid.attribute("SashOffset", cfg.gui.mainDlg.sashOffset); + + //TODO: remove if parameter migration after some time! 2018-09-09 + if (formatVer < 11) + ; + //TODO: remove if parameter migration after some time! 2020-01-30 + else if (formatVer < 16) + inFileGrid.attribute("MaxFolderPairsShown", cfg.gui.mainDlg.folderPairsVisibleMax); + else + inFileGrid.attribute("FolderPairsMax", cfg.gui.mainDlg.folderPairsVisibleMax); + + inFileGrid["ColumnsLeft"].attribute("PathFormat", cfg.gui.mainDlg.itemPathFormatLeftGrid); + inFileGrid["ColumnsLeft"](cfg.gui.mainDlg.columnAttribLeft); + + inFileGrid["FolderHistoryLeft" ](cfg.gui.mainDlg.folderHistoryLeft); + + inFileGrid["ColumnsRight"].attribute("PathFormat", cfg.gui.mainDlg.itemPathFormatRightGrid); + inFileGrid["ColumnsRight"](cfg.gui.mainDlg.columnAttribRight); + + inFileGrid["FolderHistoryRight"](cfg.gui.mainDlg.folderHistoryRight); + + //TODO: remove parameter migration after some time! 2018-01-08 + if (formatVer < 6) + { + inGui["FolderHistoryLeft" ](cfg.gui.mainDlg.folderHistoryLeft); + inGui["FolderHistoryRight"](cfg.gui.mainDlg.folderHistoryRight); + } + + //########################################################### + XmlIn inCopyTo = inWnd["ManualCopyTo"]; + inCopyTo.attribute("KeepRelativePaths", cfg.gui.mainDlg.copyToCfg.keepRelPaths); + inCopyTo.attribute("OverwriteIfExists", cfg.gui.mainDlg.copyToCfg.overwriteIfExists); + + XmlIn inCopyToHistory = inCopyTo["FolderHistory"]; + inCopyToHistory(cfg.gui.mainDlg.copyToCfg.folderHistory); + inCopyToHistory.attribute("LastUsedPath", cfg.gui.mainDlg.copyToCfg.lastUsedPath); + //########################################################### + + inWnd["DefaultViewFilter"](cfg.gui.mainDlg.viewFilterDefault); + + //TODO: remove old parameter after migration! 2018-02-04 + if (formatVer < 8) + { + XmlIn sharedView = inWnd["DefaultViewFilter"]["Shared"]; + sharedView.attribute("Equal", cfg.gui.mainDlg.viewFilterDefault.equal); + sharedView.attribute("Conflict", cfg.gui.mainDlg.viewFilterDefault.conflict); + sharedView.attribute("Excluded", cfg.gui.mainDlg.viewFilterDefault.excluded); + } + + //TODO: remove old parameter after migration! 2018-01-16 + if (formatVer < 7) + inWnd["Perspective5"](cfg.gui.mainDlg.guiPerspectiveLast); + else + inWnd["Perspective"](cfg.gui.mainDlg.guiPerspectiveLast); + + //TODO: remove after migration! 2019-11-30 + auto splitEditMerge = [](wxString& perspective, wchar_t delim, const std::function& editItem) + { + std::vector v = split(perspective, delim, SplitType::ALLOW_EMPTY); + assert(!v.empty()); + perspective.clear(); + + std::for_each(v.begin(), v.end() - 1, [&](wxString& item) + { + editItem(item); + perspective += item; + perspective += delim; + }); + editItem(v.back()); + perspective += v.back(); + }; + + //TODO: remove after migration! 2018-07-27 + if (formatVer < 10) + splitEditMerge(cfg.gui.mainDlg.guiPerspectiveLast, L'|', [&](wxString& paneCfg) + { + if (contains(paneCfg, L"name=TopPanel")) + replace(paneCfg, L";row=2;", L";row=3;"); + }); + + //TODO: remove after migration! 2019-11-30 + if (formatVer < 15) + { + //set minimal TopPanel height => search and set actual height to 0 and let MainDialog's min-size handling kick in: + std::optional tpDir; + std::optional tpLayer; + std::optional tpRow; + splitEditMerge(cfg.gui.mainDlg.guiPerspectiveLast, L'|', [&](wxString& paneCfg) + { + if (contains(paneCfg, L"name=TopPanel")) + splitEditMerge(paneCfg, L';', [&](wxString& paneAttr) + { + if (startsWith(paneAttr, L"dir=")) + tpDir = stringTo(afterFirst(paneAttr, L'=', IF_MISSING_RETURN_NONE)); + else if (startsWith(paneAttr, L"layer=")) + tpLayer = stringTo(afterFirst(paneAttr, L'=', IF_MISSING_RETURN_NONE)); + else if (startsWith(paneAttr, L"row=")) + tpRow = stringTo(afterFirst(paneAttr, L'=', IF_MISSING_RETURN_NONE)); + }); + }); + + if (tpDir && tpLayer && tpRow) + { + const wxString tpSize = L"dock_size(" + + numberTo(*tpDir ) + L"," + + numberTo(*tpLayer) + L"," + + numberTo(*tpRow ) + L")="; + + splitEditMerge(cfg.gui.mainDlg.guiPerspectiveLast, L'|', [&](wxString& paneCfg) + { + if (startsWith(paneCfg, tpSize)) + paneCfg = tpSize + L"0"; + }); + } + } + + std::vector tmp = splitFilterByLines(cfg.gui.defaultExclusionFilter); //default value + inGui["DefaultExclusionFilter"](tmp); + cfg.gui.defaultExclusionFilter = mergeFilterLines(tmp); + + //TODO: remove parameter migration after some time! 2016-09-23 + if (formatVer < 4) + cfg.gui.mainDlg.cfgHistItemsMax = std::max(cfg.gui.mainDlg.cfgHistItemsMax, 100); + + //TODO: remove if parameter migration after some time! 2020-01-30 + if (formatVer < 16) + ; + else + inGui["FolderHistory" ].attribute("MaxSize", cfg.gui.folderHistoryMax); + + inGui["VersioningFolderHistory"](cfg.gui.versioningFolderHistory); + inGui["LogFolderHistory" ](cfg.gui.logFolderHistory); + + inGui["EmailHistory"](cfg.gui.emailHistory); + inGui["EmailHistory"].attribute("MaxSize", cfg.gui.emailHistoryMax); + + //TODO: remove if clause after migration! 2017-10-24 + if (formatVer < 5) + { + inGui["OnCompletionHistory"](cfg.gui.commandHistory); + inGui["OnCompletionHistory"].attribute("MaxSize", cfg.gui.commandHistoryMax); + } + else + { + inGui["CommandHistory"](cfg.gui.commandHistory); + inGui["CommandHistory"].attribute("MaxSize", cfg.gui.commandHistoryMax); + } + + //TODO: remove if parameter migration after some time! 2020-01-30 + if (formatVer < 15) + if (cfg.gui.commandHistoryMax <= 8) + cfg.gui.commandHistoryMax = XmlGlobalSettings().gui.commandHistoryMax; + + //external applications + //TODO: remove old parameter after migration! 2016-05-28 + if (inGui["ExternalApplications"]) + { + inGui["ExternalApplications"](cfg.gui.externalApps); + if (cfg.gui.externalApps.empty()) //who knows, let's repair some old failed data migrations + cfg.gui.externalApps = XmlGlobalSettings().gui.externalApps; + else + { + } + } + else + { + //TODO: remove old parameter after migration! 2018-01-16 + if (formatVer < 7) + { + std::vector> extApps; + if (inGui["ExternalApps"](extApps)) + { + cfg.gui.externalApps.clear(); + for (const auto& [description, cmdLine] : extApps) + cfg.gui.externalApps.push_back({ description, cmdLine }); + } + } + else + inGui["ExternalApps"](cfg.gui.externalApps); + } + + //TODO: remove macro migration after some time! 2016-06-30 + if (formatVer < 3) + for (ExternalApp& item : cfg.gui.externalApps) + { + replace(item.cmdLine, Zstr("%item2_path%"), Zstr("%item_path2%")); + replace(item.cmdLine, Zstr("%item_folder%"), Zstr("%folder_path%")); + replace(item.cmdLine, Zstr("%item2_folder%"), Zstr("%folder_path2%")); + + replace(item.cmdLine, Zstr("explorer /select, \"%item_path%\""), Zstr("explorer /select, \"%local_path%\"")); + replace(item.cmdLine, Zstr("\"%item_path%\""), Zstr("\"%local_path%\"")); + replace(item.cmdLine, Zstr("xdg-open \"%item_path%\""), Zstr("xdg-open \"%local_path%\"")); + replace(item.cmdLine, Zstr("open -R \"%item_path%\""), Zstr("open -R \"%local_path%\"")); + replace(item.cmdLine, Zstr("open \"%item_path%\""), Zstr("open \"%local_path%\"")); + + if (contains(makeUpperCopy(item.cmdLine), Zstr("WINMERGEU.EXE")) || + contains(makeUpperCopy(item.cmdLine), Zstr("PSPAD.EXE"))) + { + replace(item.cmdLine, Zstr("%item_path%"), Zstr("%local_path%")); + replace(item.cmdLine, Zstr("%item_path2%"), Zstr("%local_path2%")); + } + } + //TODO: remove macro migration after some time! 2016-07-18 + for (ExternalApp& item : cfg.gui.externalApps) + replace(item.cmdLine, Zstr("%item_folder%"), Zstr("%folder_path%")); + //TODO: remove after migration! 2019-11-30 + if (formatVer < 15) + for (ExternalApp& item : cfg.gui.externalApps) + { + replace(item.cmdLine, Zstr("%folder_path%"), Zstr("%parent_path%")); + replace(item.cmdLine, Zstr("%folder_path2%"), Zstr("%parent_path2%")); + } + + //last update check + inGui["LastOnlineCheck" ](cfg.gui.lastUpdateCheck); + inGui["LastOnlineVersion"](cfg.gui.lastOnlineVersion); + + //batch specific global settings + //XmlIn inBatch = in["Batch"]; + + + //TODO: remove parameter migration after some time! 2018-03-14 + if (formatVer < 9) + if (fastFromDIP(96) > 96) //high-DPI monitor => one-time migration + { + const XmlGlobalSettings defaultCfg; + cfg.gui.mainDlg.dlgSize = defaultCfg.gui.mainDlg.dlgSize; + cfg.gui.mainDlg.guiPerspectiveLast = defaultCfg.gui.mainDlg.guiPerspectiveLast; + cfg.gui.mainDlg.cfgGridColumnAttribs = defaultCfg.gui.mainDlg.cfgGridColumnAttribs; + cfg.gui.mainDlg.treeGridColumnAttribs = defaultCfg.gui.mainDlg.treeGridColumnAttribs; + cfg.gui.mainDlg.columnAttribLeft = defaultCfg.gui.mainDlg.columnAttribLeft; + cfg.gui.mainDlg.columnAttribRight = defaultCfg.gui.mainDlg.columnAttribRight; + } +} + + +template +void readConfig(const Zstring& filePath, XmlType type, ConfigType& cfg, int currentXmlFormatVer, std::wstring& warningMsg) //throw FileError +{ + XmlDoc doc = loadXml(filePath); //throw FileError + + if (getXmlTypeNoThrow(doc) != type) //noexcept + throw FileError(replaceCpy(_("File %x does not contain a valid configuration."), L"%x", fmtPath(filePath))); + + int formatVer = 0; + /*bool success =*/ doc.root().getAttribute("XmlFormat", formatVer); + + XmlIn in(doc); + ::readConfig(in, cfg, formatVer); + + try + { + checkXmlMappingErrors(in, filePath); //throw FileError + + //(try to) migrate old configuration automatically + if (formatVer < currentXmlFormatVer) + try { fff::writeConfig(cfg, filePath); /*throw FileError*/ } + catch (FileError&) { assert(false); } //don't bother user! + } + catch (const FileError& e) { warningMsg = e.toString(); } +} +} + + +void fff::readConfig(const Zstring& filePath, XmlGuiConfig& cfg, std::wstring& warningMsg) +{ + ::readConfig(filePath, XmlType::gui, cfg, XML_FORMAT_SYNC_CFG, warningMsg); //throw FileError +} + + +void fff::readConfig(const Zstring& filePath, XmlBatchConfig& cfg, std::wstring& warningMsg) +{ + ::readConfig(filePath, XmlType::batch, cfg, XML_FORMAT_SYNC_CFG, warningMsg); //throw FileError +} + + +void fff::readConfig(const Zstring& filePath, XmlGlobalSettings& cfg, std::wstring& warningMsg) +{ + ::readConfig(filePath, XmlType::global, cfg, XML_FORMAT_GLOBAL_CFG, warningMsg); //throw FileError +} + + +namespace +{ +template +XmlCfg parseConfig(const XmlDoc& doc, const Zstring& filePath, int currentXmlFormatVer, std::wstring& warningMsg) //nothrow +{ + int formatVer = 0; + /*bool success =*/ doc.root().getAttribute("XmlFormat", formatVer); + + XmlIn in(doc); + XmlCfg cfg; + ::readConfig(in, cfg, formatVer); + + try + { + checkXmlMappingErrors(in, filePath); //throw FileError + + //(try to) migrate old configuration if needed + if (formatVer < currentXmlFormatVer) + try { fff::writeConfig(cfg, filePath); /*throw FileError*/ } + catch (FileError&) { assert(false); } //don't bother user! + } + catch (const FileError& e) + { + if (warningMsg.empty()) + warningMsg = e.toString(); + } + return cfg; +} +} + + +void fff::readAnyConfig(const std::vector& filePaths, XmlGuiConfig& cfg, std::wstring& warningMsg) //throw FileError +{ + assert(!filePaths.empty()); + + std::vector mainCfgs; + + for (auto it = filePaths.begin(); it != filePaths.end(); ++it) + { + const Zstring& filePath = *it; + const bool firstItem = it == filePaths.begin(); //init all non-"mainCfg" settings with first config file + + XmlDoc doc = loadXml(filePath); //throw FileError + + switch (getXmlTypeNoThrow(doc)) + { + case XmlType::gui: + { + XmlGuiConfig guiCfg = parseConfig(doc, filePath, XML_FORMAT_SYNC_CFG, warningMsg); //nothrow + if (firstItem) + cfg = guiCfg; + mainCfgs.push_back(guiCfg.mainCfg); + } + break; + + case XmlType::batch: + { + XmlBatchConfig batchCfg = parseConfig(doc, filePath, XML_FORMAT_SYNC_CFG, warningMsg); //nothrow + if (firstItem) + cfg = convertBatchToGui(batchCfg); + mainCfgs.push_back(batchCfg.mainCfg); + } + break; + + case XmlType::global: + case XmlType::other: + throw FileError(replaceCpy(_("File %x does not contain a valid configuration."), L"%x", fmtPath(filePath))); + } + } + + cfg.mainCfg = merge(mainCfgs); +} + +//################################################################################################ + +namespace +{ +void writeConfig(const CompConfig& cmpCfg, XmlOut& out) +{ + out["Variant" ](cmpCfg.compareVar); + out["Symlinks"](cmpCfg.handleSymlinks); + out["IgnoreTimeShift"](toTimeShiftPhrase(cmpCfg.ignoreTimeShiftMinutes)); +} + + +void writeConfig(const DirectionConfig& dirCfg, XmlOut& out) +{ + out["Variant"](dirCfg.var); + + if (dirCfg.var == DirectionConfig::CUSTOM) + { + XmlOut outCustDir = out["CustomDirections"]; + outCustDir["LeftOnly" ](dirCfg.custom.exLeftSideOnly); + outCustDir["RightOnly" ](dirCfg.custom.exRightSideOnly); + outCustDir["LeftNewer" ](dirCfg.custom.leftNewer); + outCustDir["RightNewer"](dirCfg.custom.rightNewer); + outCustDir["Different" ](dirCfg.custom.different); + outCustDir["Conflict" ](dirCfg.custom.conflict); + } + + out["DetectMovedFiles"](dirCfg.detectMovedFiles); +} + + +void writeConfig(const SyncConfig& syncCfg, const std::map& deviceParallelOps, XmlOut& out) +{ + writeConfig(syncCfg.directionCfg, out); + + out["DeletionPolicy" ](syncCfg.handleDeletion); + out["VersioningFolder"](syncCfg.versioningFolderPhrase); + + const size_t parallelOps = getDeviceParallelOps(deviceParallelOps, syncCfg.versioningFolderPhrase); + if (parallelOps > 1) out["VersioningFolder"].attribute("Threads", parallelOps); + + out["VersioningFolder"].attribute("Style", syncCfg.versioningStyle); + + if (syncCfg.versioningStyle != VersioningStyle::replace) + { + if (syncCfg.versionMaxAgeDays > 0) out["VersioningFolder"].attribute("MaxAge", syncCfg.versionMaxAgeDays); + if (syncCfg.versionCountMin > 0) out["VersioningFolder"].attribute("MinCount", syncCfg.versionCountMin); + if (syncCfg.versionCountMax > 0) out["VersioningFolder"].attribute("MaxCount", syncCfg.versionCountMax); + } +} + + +void writeConfig(const FilterConfig& filter, XmlOut& out) +{ + out["Include"](splitFilterByLines(filter.includeFilter)); + out["Exclude"](splitFilterByLines(filter.excludeFilter)); + + out["TimeSpan"](filter.timeSpan); + out["TimeSpan"].attribute("Type", filter.unitTimeSpan); + + out["SizeMin"](filter.sizeMin); + out["SizeMin"].attribute("Unit", filter.unitSizeMin); + + out["SizeMax"](filter.sizeMax); + out["SizeMax"].attribute("Unit", filter.unitSizeMax); +} + + +void writeConfig(const LocalPairConfig& lpc, const std::map& deviceParallelOps, XmlOut& out) +{ + XmlOut outPair = out.ref().addChild("Pair"); + + //read folder pairs + outPair["Left" ](lpc.folderPathPhraseLeft); + outPair["Right"](lpc.folderPathPhraseRight); + + const size_t parallelOpsL = getDeviceParallelOps(deviceParallelOps, lpc.folderPathPhraseLeft); + const size_t parallelOpsR = getDeviceParallelOps(deviceParallelOps, lpc.folderPathPhraseRight); + + if (parallelOpsL > 1) outPair["Left" ].attribute("Threads", parallelOpsL); + if (parallelOpsR > 1) outPair["Right"].attribute("Threads", parallelOpsR); + + //avoid "fake" changed configs by only storing "real" parallel-enabled devices in deviceParallelOps + assert(std::all_of(deviceParallelOps.begin(), deviceParallelOps.end(), [](const auto& item) { return item.second > 1; })); + + //########################################################### + //alternate comp configuration (optional) + if (lpc.localCmpCfg) + { + XmlOut outLocalCmp = outPair["Compare"]; + writeConfig(*lpc.localCmpCfg, outLocalCmp); + } + //########################################################### + //alternate sync configuration (optional) + if (lpc.localSyncCfg) + { + XmlOut outLocalSync = outPair["Synchronize"]; + writeConfig(*lpc.localSyncCfg, deviceParallelOps, outLocalSync); + } + + //########################################################### + //alternate filter configuration + if (lpc.localFilter != FilterConfig()) //don't spam .ffs_gui file with default filter entries + { + XmlOut outFilter = outPair["Filter"]; + writeConfig(lpc.localFilter, outFilter); + } +} + + +void writeConfig(const MainConfiguration& mainCfg, XmlOut& out) +{ + XmlOut outMain = out; + + XmlOut outCmp = outMain["Compare"]; + + writeConfig(mainCfg.cmpCfg, outCmp); + //########################################################### + + XmlOut outSync = outMain["Synchronize"]; + + writeConfig(mainCfg.syncCfg, mainCfg.deviceParallelOps, outSync); + //########################################################### + + XmlOut outFilter = outMain["Filter"]; + //write filter settings + writeConfig(mainCfg.globalFilter, outFilter); + + //########################################################### + XmlOut outFp = outMain["FolderPairs"]; + //write folder pairs + writeConfig(mainCfg.firstPair, mainCfg.deviceParallelOps, outFp); + + for (const LocalPairConfig& lpc : mainCfg.additionalPairs) + writeConfig(lpc, mainCfg.deviceParallelOps, outFp); + + outMain["Errors"].attribute("Ignore", mainCfg.ignoreErrors); + outMain["Errors"].attribute("Retry", mainCfg.automaticRetryCount); + outMain["Errors"].attribute("Delay", mainCfg.automaticRetryDelay); + + outMain["PostSyncCommand"](mainCfg.postSyncCommand); + outMain["PostSyncCommand"].attribute("Condition", mainCfg.postSyncCondition); + + outMain["LogFolder"](mainCfg.altLogFolderPathPhrase); + + outMain["EmailNotification"](mainCfg.emailNotifyAddress); + outMain["EmailNotification"].attribute("Condition", mainCfg.emailNotifyCondition); +} + + +void writeConfig(const XmlGuiConfig& cfg, XmlOut& out) +{ + writeConfig(cfg.mainCfg, out); //write main config + + //write GUI specific config data + XmlOut outGuiCfg = out["Gui"]; + + outGuiCfg["MiddleGridView"](cfg.highlightSyncAction ? "Action" : "Category"); //refactor into enum!? +} + + +void writeConfig(const BatchExclusiveConfig& cfg, XmlOut& out) +{ + XmlOut outBatchCfg = out["Batch"]; + + outBatchCfg["ProgressDialog"].attribute("Minimized", cfg.runMinimized); + outBatchCfg["ProgressDialog"].attribute("AutoClose", cfg.autoCloseSummary); + outBatchCfg["ErrorDialog" ](cfg.batchErrorHandling); + outBatchCfg["PostSyncAction"](cfg.postSyncAction); +} + + +void writeConfig(const XmlBatchConfig& cfg, XmlOut& out) +{ + writeConfig(cfg.mainCfg, out); + writeConfig(cfg.batchExCfg, out); +} + + +void writeConfig(const XmlGlobalSettings& cfg, XmlOut& out) +{ + XmlOut outGeneral = out["General"]; + + outGeneral["Language"].attribute("Name", cfg.programLanguage); + + outGeneral["FailSafeFileCopy" ].attribute("Enabled", cfg.failSafeFileCopy); + outGeneral["CopyLockedFiles" ].attribute("Enabled", cfg.copyLockedFiles); + outGeneral["CopyFilePermissions" ].attribute("Enabled", cfg.copyFilePermissions); + outGeneral["FileTimeTolerance" ].attribute("Seconds", cfg.fileTimeTolerance); + outGeneral["RunWithBackgroundPriority"].attribute("Enabled", cfg.runWithBackgroundPriority); + outGeneral["LockDirectoriesDuringSync"].attribute("Enabled", cfg.createLockFile); + outGeneral["VerifyCopiedFiles" ].attribute("Enabled", cfg.verifyFileCopy); + outGeneral["LogFiles" ].attribute("MaxAge", cfg.logfilesMaxAgeDays); + outGeneral["LogFiles" ].attribute("Format", cfg.logFormat); + outGeneral["NotificationSound" ].attribute("CompareFinished", substituteFfsResourcePath(cfg.soundFileCompareFinished)); + outGeneral["NotificationSound" ].attribute("SyncFinished", substituteFfsResourcePath(cfg.soundFileSyncFinished)); + outGeneral["ProgressDialog" ].attribute("AutoClose", cfg.autoCloseProgressDialog); + + XmlOut outOpt = outGeneral["OptionalDialogs"]; + outOpt["ConfirmStartSync" ].attribute("Show", cfg.confirmDlgs.confirmSyncStart); + outOpt["ConfirmSaveConfig" ].attribute("Show", cfg.confirmDlgs.popupOnConfigChange); + outOpt["ConfirmCommandMassInvoke" ].attribute("Show", cfg.confirmDlgs.confirmCommandMassInvoke); + outOpt["WarnFolderNotExisting" ].attribute("Show", cfg.warnDlgs.warnFolderNotExisting); + outOpt["WarnFoldersDifferInCase" ].attribute("Show", cfg.warnDlgs.warnFoldersDifferInCase); + outOpt["WarnUnresolvedConflicts" ].attribute("Show", cfg.warnDlgs.warnUnresolvedConflicts); + outOpt["WarnNotEnoughDiskSpace" ].attribute("Show", cfg.warnDlgs.warnNotEnoughDiskSpace); + outOpt["WarnSignificantDifference" ].attribute("Show", cfg.warnDlgs.warnSignificantDifference); + outOpt["WarnRecycleBinNotAvailable" ].attribute("Show", cfg.warnDlgs.warnRecyclerMissing); + outOpt["WarnInputFieldEmpty" ].attribute("Show", cfg.warnDlgs.warnInputFieldEmpty); + outOpt["WarnModificationTimeError" ].attribute("Show", cfg.warnDlgs.warnModificationTimeError); + outOpt["WarnDependentFolderPair" ].attribute("Show", cfg.warnDlgs.warnDependentFolderPair); + outOpt["WarnDependentBaseFolders" ].attribute("Show", cfg.warnDlgs.warnDependentBaseFolders); + outOpt["WarnDirectoryLockFailed" ].attribute("Show", cfg.warnDlgs.warnDirectoryLockFailed); + outOpt["WarnVersioningFolderPartOfSync"].attribute("Show", cfg.warnDlgs.warnVersioningFolderPartOfSync); + + //gui specific global settings (optional) + XmlOut outGui = out["Gui"]; + XmlOut outWnd = outGui["MainDialog"]; + + //write application window size and position + outWnd.attribute("Width", cfg.gui.mainDlg.dlgSize.x); + outWnd.attribute("Height", cfg.gui.mainDlg.dlgSize.y); + outWnd.attribute("PosX", cfg.gui.mainDlg.dlgPos.x); + outWnd.attribute("PosY", cfg.gui.mainDlg.dlgPos.y); + outWnd.attribute("Maximized", cfg.gui.mainDlg.isMaximized); + + //########################################################### + outWnd["SearchPanel" ].attribute("CaseSensitive", cfg.gui.mainDlg.textSearchRespectCase); + //########################################################### + + XmlOut outConfig = outWnd["ConfigPanel"]; + outConfig.attribute("ScrollPos", cfg.gui.mainDlg.cfgGridTopRowPos); + outConfig.attribute("SyncOverdue", cfg.gui.mainDlg.cfgGridSyncOverdueDays); + outConfig.attribute("SortByColumn", cfg.gui.mainDlg.cfgGridLastSortColumn); + outConfig.attribute("SortAscending", cfg.gui.mainDlg.cfgGridLastSortAscending); + + outConfig["Columns"](cfg.gui.mainDlg.cfgGridColumnAttribs); + outConfig["Configurations"].attribute("MaxSize", cfg.gui.mainDlg.cfgHistItemsMax); + outConfig["Configurations"](cfg.gui.mainDlg.cfgFileHistory); + { + std::vector cfgPaths = cfg.gui.mainDlg.lastUsedConfigFiles; + for (Zstring& filePath : cfgPaths) + filePath = substituteFreeFileSyncDriveLetter(filePath); + + outConfig["LastUsed"](cfgPaths); + } + + //########################################################### + + XmlOut outOverview = outWnd["OverviewPanel"]; + outOverview.attribute("ShowPercentage", cfg.gui.mainDlg.treeGridShowPercentBar); + outOverview.attribute("SortByColumn", cfg.gui.mainDlg.treeGridLastSortColumn); + outOverview.attribute("SortAscending", cfg.gui.mainDlg.treeGridLastSortAscending); + + //write column attributes + XmlOut outColTree = outOverview["Columns"]; + outColTree(cfg.gui.mainDlg.treeGridColumnAttribs); + + XmlOut outFileGrid = outWnd["FilePanel"]; + outFileGrid.attribute("ShowIcons", cfg.gui.mainDlg.showIcons); + outFileGrid.attribute("IconSize", cfg.gui.mainDlg.iconSize); + outFileGrid.attribute("SashOffset", cfg.gui.mainDlg.sashOffset); + outFileGrid.attribute("FolderPairsMax", cfg.gui.mainDlg.folderPairsVisibleMax); + + outFileGrid["ColumnsLeft"].attribute("PathFormat", cfg.gui.mainDlg.itemPathFormatLeftGrid); + outFileGrid["ColumnsLeft"](cfg.gui.mainDlg.columnAttribLeft); + + outFileGrid["FolderHistoryLeft" ](cfg.gui.mainDlg.folderHistoryLeft); + + outFileGrid["ColumnsRight"].attribute("PathFormat", cfg.gui.mainDlg.itemPathFormatRightGrid); + outFileGrid["ColumnsRight"](cfg.gui.mainDlg.columnAttribRight); + + outFileGrid["FolderHistoryRight"](cfg.gui.mainDlg.folderHistoryRight); + + //########################################################### + XmlOut outCopyTo = outWnd["ManualCopyTo"]; + outCopyTo.attribute("KeepRelativePaths", cfg.gui.mainDlg.copyToCfg.keepRelPaths); + outCopyTo.attribute("OverwriteIfExists", cfg.gui.mainDlg.copyToCfg.overwriteIfExists); + + XmlOut outCopyToHistory = outCopyTo["FolderHistory"]; + outCopyToHistory(cfg.gui.mainDlg.copyToCfg.folderHistory); + outCopyToHistory.attribute("LastUsedPath", cfg.gui.mainDlg.copyToCfg.lastUsedPath); + //########################################################### + + outWnd["DefaultViewFilter"](cfg.gui.mainDlg.viewFilterDefault); + outWnd["Perspective" ](cfg.gui.mainDlg.guiPerspectiveLast); + + outGui["DefaultExclusionFilter"](splitFilterByLines(cfg.gui.defaultExclusionFilter)); + + outGui["FolderHistory" ].attribute("MaxSize", cfg.gui.folderHistoryMax); + + outGui["VersioningFolderHistory"](cfg.gui.versioningFolderHistory); + outGui["LogFolderHistory" ](cfg.gui.logFolderHistory); + + outGui["EmailHistory"](cfg.gui.emailHistory); + outGui["EmailHistory"].attribute("MaxSize", cfg.gui.emailHistoryMax); + + outGui["CommandHistory"](cfg.gui.commandHistory); + outGui["CommandHistory"].attribute("MaxSize", cfg.gui.commandHistoryMax); + + //external applications + outGui["ExternalApps"](cfg.gui.externalApps); + + //last update check + outGui["LastOnlineCheck" ](cfg.gui.lastUpdateCheck); + outGui["LastOnlineVersion"](cfg.gui.lastOnlineVersion); + + //batch specific global settings + //XmlOut outBatch = out["Batch"]; +} + + +template +void writeConfig(const ConfigType& cfg, XmlType type, int xmlFormatVer, const Zstring& filePath) +{ + XmlDoc doc("FreeFileSync"); + setXmlType(doc, type); //throw() + + doc.root().setAttribute("XmlFormat", xmlFormatVer); + + XmlOut out(doc); + writeConfig(cfg, out); + + saveXml(doc, filePath); //throw FileError +} +} + +void fff::writeConfig(const XmlGuiConfig& cfg, const Zstring& filePath) +{ + ::writeConfig(cfg, XmlType::gui, XML_FORMAT_SYNC_CFG, filePath); //throw FileError +} + + +void fff::writeConfig(const XmlBatchConfig& cfg, const Zstring& filePath) +{ + ::writeConfig(cfg, XmlType::batch, XML_FORMAT_SYNC_CFG, filePath); //throw FileError +} + + +void fff::writeConfig(const XmlGlobalSettings& cfg, const Zstring& filePath) +{ + ::writeConfig(cfg, XmlType::global, XML_FORMAT_GLOBAL_CFG, filePath); //throw FileError +} + + +std::wstring fff::extractJobName(const Zstring& cfgFilePath) +{ + const Zstring fileName = afterLast(cfgFilePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); + const Zstring jobName = beforeLast(fileName, Zstr('.'), IF_MISSING_RETURN_ALL); + return utfTo(jobName); +} diff --git a/FreeFileSync/Source/config.h b/FreeFileSync/Source/config.h new file mode 100644 index 00000000..e76d4770 --- /dev/null +++ b/FreeFileSync/Source/config.h @@ -0,0 +1,255 @@ +// ***************************************************************************** +// * 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 PROCESS_XML_H_28345825704254262435 +#define PROCESS_XML_H_28345825704254262435 + +#include +#include "localization.h" +#include "base/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" +#include "log_file.h" + + +namespace fff +{ +enum class XmlType +{ + gui, + batch, + global, + other +}; +XmlType getXmlType(const Zstring& filePath); //throw FileError + + +enum class BatchErrorHandling +{ + showPopup, + cancel +}; + + +enum class PostSyncAction +{ + none, + sleep, + shutdown +}; + +struct ExternalApp +{ + std::wstring description; + Zstring cmdLine; +}; + +//--------------------------------------------------------------------- +struct XmlGuiConfig +{ + MainConfiguration mainCfg; + bool highlightSyncAction = true; +}; + + +inline +bool operator==(const XmlGuiConfig& lhs, const XmlGuiConfig& rhs) +{ + return lhs.mainCfg == rhs.mainCfg && + lhs.highlightSyncAction == rhs.highlightSyncAction; +} +inline bool operator!=(const XmlGuiConfig& lhs, const XmlGuiConfig& rhs) { return !(lhs == rhs); } + + +struct BatchExclusiveConfig +{ + BatchErrorHandling batchErrorHandling = BatchErrorHandling::showPopup; + bool runMinimized = false; + bool autoCloseSummary = false; + PostSyncAction postSyncAction = PostSyncAction::none; +}; + + +struct XmlBatchConfig +{ + MainConfiguration mainCfg; + BatchExclusiveConfig batchExCfg; +}; + + +struct ConfirmationDialogs +{ + bool popupOnConfigChange = true; + bool confirmSyncStart = true; + bool confirmCommandMassInvoke = true; +}; +inline bool operator==(const ConfirmationDialogs& lhs, const ConfirmationDialogs& rhs) +{ + return lhs.popupOnConfigChange == rhs.popupOnConfigChange && + lhs.confirmSyncStart == rhs.confirmSyncStart && + lhs.confirmCommandMassInvoke == rhs.confirmCommandMassInvoke; +} +inline bool operator!=(const ConfirmationDialogs& lhs, const ConfirmationDialogs& rhs) { return !(lhs == rhs); } + + +enum class FileIconSize +{ + SMALL, + MEDIUM, + LARGE +}; + + +struct ViewFilterDefault +{ + //shared + bool equal = false; + bool conflict = true; + bool excluded = false; + //category view + bool leftOnly = true; + bool rightOnly = true; + bool leftNewer = true; + bool rightNewer = true; + bool different = true; + //action view + bool createLeft = true; + bool createRight = true; + bool updateLeft = true; + bool updateRight = true; + bool deleteLeft = true; + bool deleteRight = true; + bool doNothing = true; +}; + + +Zstring getGlobalConfigFile(); + + +struct XmlGlobalSettings +{ + XmlGlobalSettings(); //clang needs this anyway + + //--------------------------------------------------------------------- + //Shared (GUI/BATCH) settings + wxLanguage programLanguage = getSystemLanguage(); + bool failSafeFileCopy = true; + bool copyLockedFiles = false; //safer default: avoid copies of partially written files + bool copyFilePermissions = false; + + int fileTimeTolerance = 2; //max. allowed file time deviation; < 0 means unlimited tolerance; default 2s: FAT vs NTFS + bool runWithBackgroundPriority = false; + bool createLockFile = true; + bool verifyFileCopy = false; + int logfilesMaxAgeDays = 30; //<= 0 := no limit; for log files under %AppData%\FreeFileSync\Logs + LogFileFormat logFormat = LogFileFormat::html; + + Zstring soundFileCompareFinished; + Zstring soundFileSyncFinished; + + bool autoCloseProgressDialog = false; + ConfirmationDialogs confirmDlgs; + WarningDialogs warnDlgs; + + //--------------------------------------------------------------------- + struct Gui + { + Gui() {} //clang needs this anyway + struct + { + wxPoint dlgPos; + wxSize dlgSize; + bool isMaximized = false; + + bool textSearchRespectCase = false; //good default for Linux, too! + int folderPairsVisibleMax = 6; + + size_t cfgGridTopRowPos = 0; + int cfgGridSyncOverdueDays = 7; + ColumnTypeCfg cfgGridLastSortColumn = cfgGridLastSortColumnDefault; + bool cfgGridLastSortAscending = getDefaultSortDirection(cfgGridLastSortColumnDefault); + std::vector cfgGridColumnAttribs = getCfgGridDefaultColAttribs(); + size_t cfgHistItemsMax = 100; + std::vector cfgFileHistory; + std::vector lastUsedConfigFiles; + + bool treeGridShowPercentBar = treeGridShowPercentageDefault; + ColumnTypeTree treeGridLastSortColumn = treeGridLastSortColumnDefault; //remember sort on overview panel + bool treeGridLastSortAscending = getDefaultSortDirection(treeGridLastSortColumnDefault); // + std::vector treeGridColumnAttribs = getTreeGridDefaultColAttribs(); + + struct + { + bool keepRelPaths = false; + bool overwriteIfExists = false; + Zstring lastUsedPath; + std::vector folderHistory; + } copyToCfg; + + std::vector folderHistoryLeft; + std::vector folderHistoryRight; + bool showIcons = true; + FileIconSize iconSize = FileIconSize::SMALL; + int sashOffset = 0; + + ItemPathFormat itemPathFormatLeftGrid = defaultItemPathFormatLeftGrid; + ItemPathFormat itemPathFormatRightGrid = defaultItemPathFormatRightGrid; + + std::vector columnAttribLeft = getFileGridDefaultColAttribsLeft(); + std::vector columnAttribRight = getFileGridDefaultColAttribsRight(); + + ViewFilterDefault viewFilterDefault; + wxString guiPerspectiveLast; //used by wxAuiManager + } mainDlg; + + Zstring defaultExclusionFilter = "/.Trash-*/" "\n" + "/.recycle/"; + size_t folderHistoryMax = 20; + + std::vector versioningFolderHistory; + std::vector logFolderHistory; + + std::vector emailHistory; + size_t emailHistoryMax = 10; + + std::vector commandHistory; + size_t commandHistoryMax = 10; + + std::vector externalApps + { + //default external app descriptions will be translated "on the fly"!!! + //CONTRACT: first entry will be used for [Enter] or mouse double-click! + { L"Browse directory", Zstr("xdg-open \"%parent_path%\"") }, + { L"Open with default application", Zstr("xdg-open \"%local_path%\"") }, + //mark for extraction: _("Browse directory") Linux doesn't use the term "folder" + }; + + time_t lastUpdateCheck = 0; //number of seconds since 00:00 hours, Jan 1, 1970 UTC + std::string lastOnlineVersion; + } gui; +}; + +//read/write specific config types +void readConfig(const Zstring& filePath, XmlGuiConfig& cfg, std::wstring& warningMsg); // +void readConfig(const Zstring& filePath, XmlBatchConfig& cfg, std::wstring& warningMsg); //throw FileError +void readConfig(const Zstring& filePath, XmlGlobalSettings& cfg, std::wstring& warningMsg); // + +void writeConfig(const XmlGuiConfig& cfg, const Zstring& filePath); // +void writeConfig(const XmlBatchConfig& cfg, const Zstring& filePath); //throw FileError +void writeConfig(const XmlGlobalSettings& cfg, const Zstring& filePath); // + +//convert (multiple) *.ffs_gui, *.ffs_batch files or combinations of both into target config structure: +void readAnyConfig(const std::vector& filePaths, XmlGuiConfig& cfg, std::wstring& warningMsg); //throw FileError + +//config conversion utilities +XmlGuiConfig convertBatchToGui(const XmlBatchConfig& batchCfg); //noexcept +XmlBatchConfig convertGuiToBatch(const XmlGuiConfig& guiCfg, const BatchExclusiveConfig& batchExCfg); // + +std::wstring extractJobName(const Zstring& cfgFilePath); +} + +#endif //PROCESS_XML_H_28345825704254262435 diff --git a/FreeFileSync/Source/fatal_error.h b/FreeFileSync/Source/fatal_error.h new file mode 100644 index 00000000..84ceb6dc --- /dev/null +++ b/FreeFileSync/Source/fatal_error.h @@ -0,0 +1,45 @@ +// ***************************************************************************** +// * 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 ERROR_LOG_H_89734181783491324134 +#define ERROR_LOG_H_89734181783491324134 + +#include +#include +#include +#include "ffs_paths.h" + + +namespace fff +{ +//write error message to a file (even with corrupted stack)- call in desperate situations when no other means of error handling is available +void logFatalError(const std::string& msg); //noexcept + + + + + + + + + +//##################### implementation ############################ +inline +void logFatalError(const std::string& msg) //noexcept +{ + using namespace zen; + + assert(false); //this is stuff we like to debug + const std::string logEntry = '[' + utfTo(formatTime(formatDateTag) + Zstr(' ') + formatTime(formatTimeTag)) + "] " + msg; + try + { + saveBinContainer(getConfigDirPathPf() + Zstr("LastError.log"), logEntry, nullptr /*notifyUnbufferedIO*/); //throw FileError + } + catch (FileError&) {} +} +} + +#endif //ERROR_LOG_H_89734181783491324134 diff --git a/FreeFileSync/Source/ffs_paths.cpp b/FreeFileSync/Source/ffs_paths.cpp new file mode 100644 index 00000000..843e702e --- /dev/null +++ b/FreeFileSync/Source/ffs_paths.cpp @@ -0,0 +1,103 @@ +// ***************************************************************************** +// * 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 "ffs_paths.h" +#include +#include +#include +#include +#include + + +using namespace zen; + + +namespace +{ +Zstring getProcessParentFolderPath() +{ + //buffer getSymlinkResolvedPath()! + //note: compiler generates magic-statics code => fine, we don't expect accesses during shutdown => don't need FunStatGlobal<> + static const Zstring exeFolderParentPath = [] + { + Zstring exeFolderPath = beforeLast(utfTo(wxStandardPaths::Get().GetExecutablePath()), FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE); + try + { + //get rid of relative path fragments, e.g.: C:\Data\Projects\FreeFileSync\Source\..\Build\Bin + exeFolderPath = getSymlinkResolvedPath(exeFolderPath); //throw FileError + } + catch (FileError&) { assert(false); } + + return beforeLast(exeFolderPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE); + }(); + return exeFolderParentPath; +} +} + + + + +namespace +{ +//don't make this a function-scope static (avoid code-gen for "magic static") +//getFfsVolumeId() might be called during static destruction, e.g. async update check +std::once_flag onceFlagGetFfsVolumeId; +} + +VolumeId fff::getFfsVolumeId() //throw FileError +{ + static VolumeId volumeId; //POD => no "magic static" code gen + std::call_once(onceFlagGetFfsVolumeId, [] { volumeId = getVolumeId(getProcessParentFolderPath()); }); //throw FileError + return volumeId; +} + + +bool fff::isPortableVersion() +{ + return false; //users want local installation type: https://freefilesync.org/forum/viewtopic.php?t=5750 + +} + + +Zstring fff::getResourceDirPf() +{ + return getProcessParentFolderPath() + FILE_NAME_SEPARATOR + Zstr("Resources") + FILE_NAME_SEPARATOR; +} + + +Zstring fff::getConfigDirPathPf() +{ + //note: compiler generates magic-statics code => fine, we don't expect accesses during shutdown + static const Zstring cfgFolderPathPf = [] + { + //make independent from wxWidgets global variable "appname"; support being called by RealTimeSync + auto appName = wxTheApp->GetAppName(); + wxTheApp->SetAppName(L"FreeFileSync"); + ZEN_ON_SCOPE_EXIT(wxTheApp->SetAppName(appName)); + + //OS standard path (XDG layout): ~/.config/FreeFileSync + //wxBug: wxStandardPaths::GetUserDataDir() does not honor FileLayout_XDG flag + wxStandardPaths::Get().SetFileLayout(wxStandardPaths::FileLayout_XDG); + const Zstring cfgFolderPath = appendSeparator(utfTo(wxStandardPaths::Get().GetUserConfigDir())) + "FreeFileSync"; + + try //create the config folder if not existing + create "Logs" subfolder while we're at it + { + createDirectoryIfMissingRecursion(appendSeparator(cfgFolderPath) + Zstr("Logs")); //throw FileError + } + catch (FileError&) { assert(false); } + + return appendSeparator(cfgFolderPath); + }(); + return cfgFolderPathPf; +} + + +//this function is called by RealTimeSync!!! +Zstring fff::getFreeFileSyncLauncherPath() +{ + return getProcessParentFolderPath() + Zstr("/FreeFileSync"); + +} diff --git a/FreeFileSync/Source/ffs_paths.h b/FreeFileSync/Source/ffs_paths.h new file mode 100644 index 00000000..bf103e28 --- /dev/null +++ b/FreeFileSync/Source/ffs_paths.h @@ -0,0 +1,31 @@ +// ***************************************************************************** +// * 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 FFS_PATHS_H_842759083425342534253 +#define FFS_PATHS_H_842759083425342534253 + +#include +#include + + +namespace fff +{ +//------------------------------------------------------------------------------ +//global program directories +//------------------------------------------------------------------------------ +Zstring getResourceDirPf (); //resource directory WITH trailing path separator +Zstring getConfigDirPathPf(); // config directory WITH trailing path separator +//------------------------------------------------------------------------------ + +bool isPortableVersion(); + + +zen::VolumeId getFfsVolumeId(); //throw FileError + +Zstring getFreeFileSyncLauncherPath(); //full path to application launcher C:\...\FreeFileSync.exe +} + +#endif //FFS_PATHS_H_842759083425342534253 diff --git a/FreeFileSync/Source/help_provider.h b/FreeFileSync/Source/help_provider.h new file mode 100644 index 00000000..d9a1b3fc --- /dev/null +++ b/FreeFileSync/Source/help_provider.h @@ -0,0 +1,22 @@ +// ***************************************************************************** +// * 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 HELP_PROVIDER_H_85930427583421563126 +#define HELP_PROVIDER_H_85930427583421563126 + +#include + + +namespace fff +{ +inline +void displayHelpEntry(const wxString& topic, wxWindow* parent) +{ + wxLaunchDefaultBrowser(L"https://freefilesync.org/manual.php?topic=" + topic); +} +} + +#endif //HELP_PROVIDER_H_85930427583421563126 diff --git a/FreeFileSync/Source/icon_buffer.cpp b/FreeFileSync/Source/icon_buffer.cpp new file mode 100644 index 00000000..4b032bd3 --- /dev/null +++ b/FreeFileSync/Source/icon_buffer.cpp @@ -0,0 +1,413 @@ +// ***************************************************************************** +// * 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 "icon_buffer.h" +#include +#include +#include //includes +#include +#include +#include +#include "base/icon_loader.h" + + +using namespace zen; +using namespace fff; +using AFS = AbstractFileSystem; + + +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(runningMainThread()); + + if (!ih.getRgb()) + return wxNullBitmap; + + wxImage img(ih.getWidth(), ih.getHeight(), ih.releaseRgb(), false /*static_data*/); //pass ownership + if (ih.getAlpha()) + img.SetAlpha(ih.releaseAlpha(), false /*static_data*/); + return wxBitmap(img); +} + + +} + +//################################################################################################################################################ + +ImageHolder getDisplayIcon(const AbstractPath& itemPath, IconBuffer::IconSize sz) +{ + //1. try to load thumbnails + switch (sz) + { + case IconBuffer::SIZE_SMALL: + break; + case IconBuffer::SIZE_MEDIUM: + case IconBuffer::SIZE_LARGE: + if (ImageHolder img = AFS::getThumbnailImage(itemPath, IconBuffer::getSize(sz))) + return img; + //else: fallback to non-thumbnail icon + break; + } + + const Zstring& templateName = AFS::getItemName(itemPath); + + //2. retrieve file icons + if (ImageHolder ih = AFS::getFileIcon(itemPath, IconBuffer::getSize(sz))) + return ih; + + //3. fallbacks + if (ImageHolder ih = getIconByTemplatePath(templateName, IconBuffer::getSize(sz))) + return ih; + + return genericFileIcon(IconBuffer::getSize(sz)); +} + +//################################################################################################################################################ + +//---------------------- Shared Data ------------------------- +class WorkLoad +{ +public: + //context of main thread + void set(const std::vector& newLoad) + { + assert(runningMainThread()); + { + std::lock_guard dummy(lockFiles_); + + workLoad_.clear(); + for (const AbstractPath& filePath : newLoad) + workLoad_.emplace_back(filePath); + } + conditionNewWork_.notify_all(); //instead of notify_one(); workaround bug: https://svn.boost.org/trac/boost/ticket/7796 + //condition handling, see: https://www.boost.org/doc/libs/1_43_0/doc/html/thread/synchronization.html#thread.synchronization.condvar_ref + } + + void add(const AbstractPath& filePath) //context of main thread + { + assert(runningMainThread()); + { + std::lock_guard dummy(lockFiles_); + workLoad_.emplace_back(filePath); //set as next item to retrieve + } + conditionNewWork_.notify_all(); + } + + //context of worker thread, blocking: + AbstractPath extractNext() //throw ThreadInterruption + { + assert(!runningMainThread()); + std::unique_lock dummy(lockFiles_); + + interruptibleWait(conditionNewWork_, dummy, [this] { return !workLoad_.empty(); }); //throw ThreadInterruption + + AbstractPath filePath = workLoad_. back(); //yes, no strong exception guarantee (std::bad_alloc) + /**/ workLoad_.pop_back(); // + return filePath; + } + +private: + //AbstractPath is thread-safe like an int! + std::mutex lockFiles_; + std::condition_variable conditionNewWork_; //signal event: data for processing available + std::vector workLoad_; //processes last elements of vector first! +}; + + +class Buffer +{ +public: + //called by main and worker thread: + bool hasIcon(const AbstractPath& filePath) const + { + std::lock_guard dummy(lockIconList_); + return contains(iconList, filePath); + } + + //must be called by main thread only! => wxBitmap is NOT thread-safe like an int (non-atomic ref-count!!!) + std::optional retrieve(const AbstractPath& filePath) + { + assert(runningMainThread()); + std::lock_guard dummy(lockIconList_); + + auto it = iconList.find(filePath); + if (it == iconList.end()) + return {}; + + markAsHot(it); + + IconData& idata = refData(it); + if (idata.iconRaw) //if not yet converted... + { + idata.iconFmt = std::make_unique(extractWxBitmap(std::move(idata.iconRaw))); //convert in main thread! + assert(!idata.iconRaw); + } + return idata.iconFmt ? *idata.iconFmt : wxNullBitmap; //idata.iconRaw may be inserted as empty from worker thread! + } + + //called by main and worker thread: + void insert(const AbstractPath& filePath, ImageHolder&& icon) + { + std::lock_guard 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, IconData()); + assert(rc.second); //insertion took place + if (rc.second) + { + refData(rc.first).iconRaw = std::move(icon); + priorityListPushBack(rc.first); + } + } + + //must be called by main thread only! => ~wxBitmap() is NOT thread-safe! + //call at an appropriate time, e.g. after Workload::set() + void limitSize() + { + assert(runningMainThread()); + std::lock_guard dummy(lockIconList_); + + while (iconList.size() > BUFFER_SIZE_MAX) + { + auto itDelPos = firstInsertPos_; + priorityListPopFront(); + iconList.erase(itDelPos); //remove oldest element + } + } + +private: + struct IconData; + using FileIconMap = std::map; + IconData& refData(FileIconMap::iterator it) { return it->second; } + + //call while holding lock: + void priorityListPopFront() + { + assert(firstInsertPos_!= iconList.end()); + firstInsertPos_ = refData(firstInsertPos_).next; + + if (firstInsertPos_ != iconList.end()) + refData(firstInsertPos_).prev = iconList.end(); + else //priority list size > BUFFER_SIZE_MAX in this context, but still for completeness: + lastInsertPos_ = iconList.end(); + } + + //call while holding lock: + void priorityListPushBack(FileIconMap::iterator it) + { + if (lastInsertPos_ == iconList.end()) + { + assert(firstInsertPos_ == iconList.end()); + firstInsertPos_ = lastInsertPos_ = it; + refData(it).prev = refData(it).next = iconList.end(); + } + else + { + refData(it).next = iconList.end(); + refData(it).prev = lastInsertPos_; + refData(lastInsertPos_).next = it; + lastInsertPos_ = it; + } + } + + //call while holding lock: + void markAsHot(FileIconMap::iterator it) //mark existing buffer entry as if newly inserted + { + assert(it != iconList.end()); + if (refData(it).next != iconList.end()) + { + if (refData(it).prev != iconList.end()) + { + refData(refData(it).prev).next = refData(it).next; //remove somewhere from the middle + refData(refData(it).next).prev = refData(it).prev; // + } + else + { + assert(it == firstInsertPos_); + priorityListPopFront(); + } + priorityListPushBack(it); + } + else + { + if (refData(it).prev != iconList.end()) + assert(it == lastInsertPos_); //nothing to do + else + assert(iconList.size() == 1 && it == firstInsertPos_ && it == lastInsertPos_); //nothing to do + } + } + + struct IconData + { + IconData() {} + IconData(IconData&& tmp) noexcept : iconRaw(std::move(tmp.iconRaw)), iconFmt(std::move(tmp.iconFmt)), prev(tmp.prev), next(tmp.next) {} + + ImageHolder iconRaw; //native icon representation: may be used by any thread + + std::unique_ptr iconFmt; //use ONLY from main thread! + //wxBitmap is NOT thread-safe: non-atomic ref-count just to begin with... + //- prohibit implicit calls to wxBitmap(const wxBitmap&) + //- prohibit calls to ~wxBitmap() and transitively ~IconData() + //- prohibit even wxBitmap() default constructor - better be safe than sorry! + + FileIconMap::iterator prev; //store list sorted by time of insertion into buffer + FileIconMap::iterator next; // + }; + + mutable std::mutex lockIconList_; + FileIconMap iconList; //shared resource; Zstring is thread-safe like an int + FileIconMap::iterator firstInsertPos_ = iconList.end(); + FileIconMap::iterator lastInsertPos_ = iconList.end(); +}; + +//################################################################################################################################################ + + +//######################### redirect to impl ##################################################### + +struct IconBuffer::Impl +{ + //communication channel used by threads: + WorkLoad workload; //manage life time: enclose InterruptibleThread's (until joined)!!! + Buffer buffer; // + + InterruptibleThread worker; + //------------------------- + //------------------------- + std::map extensionIcons; //no item count limit!? Test case C:\ ~ 3800 unique file extensions +}; + + +IconBuffer::IconBuffer(IconSize sz) : pimpl_(std::make_unique()), iconSizeType_(sz) +{ + 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)); + } + }); +} + + +IconBuffer::~IconBuffer() +{ + setWorkload({}); //make sure interruption point is always reached! needed??? + pimpl_->worker.interrupt(); + pimpl_->worker.join(); +} + + +int IconBuffer::getSize(IconSize sz) +{ + //coordinate with getIconByIndexImpl() and linkOverlayIcon()! + switch (sz) + { + case IconBuffer::SIZE_SMALL: + return fastFromDIP(24); + case IconBuffer::SIZE_MEDIUM: + return fastFromDIP(48); + + case IconBuffer::SIZE_LARGE: + return fastFromDIP(128); + } + assert(false); + return 0; +} + + +bool IconBuffer::readyForRetrieval(const AbstractPath& filePath) +{ + return pimpl_->buffer.hasIcon(filePath); +} + + +std::optional IconBuffer::retrieveFileIcon(const AbstractPath& filePath) +{ + if (std::optional ico = pimpl_->buffer.retrieve(filePath)) + return ico; + + //since this icon seems important right now, we don't want to wait until next setWorkload() to start retrieving + pimpl_->workload.add(filePath); + pimpl_->buffer.limitSize(); + return {}; +} + + +void IconBuffer::setWorkload(const std::vector& 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! +} + + +wxBitmap IconBuffer::getIconByExtension(const Zstring& filePath) +{ + const Zstring& ext = getFileExtension(filePath); + + assert(runningMainThread()); + + auto it = pimpl_->extensionIcons.find(ext); + if (it == pimpl_->extensionIcons.end()) + { + 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, getSize(iconSizeType_)))).first; + } + //need buffer size limit??? + return it->second; +} + + +wxBitmap IconBuffer::genericFileIcon(IconSize sz) +{ + return extractWxBitmap(fff::genericFileIcon(IconBuffer::getSize(sz))); +} + + +wxBitmap IconBuffer::genericDirIcon(IconSize sz) +{ + return extractWxBitmap(fff::genericDirIcon(IconBuffer::getSize(sz))); +} + + +wxBitmap IconBuffer::linkOverlayIcon(IconSize sz) +{ + //coordinate with IconBuffer::getSize()! + return getResourceImage([sz] + { + const int pixelSize = IconBuffer::getSize(sz); + + if (pixelSize >= fastFromDIP(128)) return L"link_128"; + if (pixelSize >= fastFromDIP(48)) return L"link_48"; + if (pixelSize >= fastFromDIP(24)) return L"link_24"; + return L"link_16"; + }()); +} + + +bool fff::hasLinkExtension(const Zstring& filepath) +{ + const Zstring& ext = getFileExtension(filepath); + return ext == "desktop"; + +} diff --git a/FreeFileSync/Source/icon_buffer.h b/FreeFileSync/Source/icon_buffer.h new file mode 100644 index 00000000..b5ce9eae --- /dev/null +++ b/FreeFileSync/Source/icon_buffer.h @@ -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 * +// ***************************************************************************** + +#ifndef ICON_BUFFER_H_8425703245726394256 +#define ICON_BUFFER_H_8425703245726394256 + +#include +#include +#include +#include +#include "afs/abstract.h" + + +namespace fff +{ +class IconBuffer +{ +public: + enum IconSize + { + SIZE_SMALL, + SIZE_MEDIUM, + SIZE_LARGE + }; + + IconBuffer(IconSize sz); + ~IconBuffer(); + + static int getSize(IconSize sz); //expected and *maximum* icon size in pixel + int getSize() const { return getSize(iconSizeType_); } // + + void setWorkload (const std::vector& load); //(re-)set new workload of icons to be retrieved; + bool readyForRetrieval(const AbstractPath& filePath); + std::optional retrieveFileIcon (const AbstractPath& filePath); //... and mark as hot + 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); + static wxBitmap linkOverlayIcon(IconSize sz); + +private: + struct Impl; + const std::unique_ptr pimpl_; + + const IconSize iconSizeType_; +}; + +bool hasLinkExtension(const Zstring& filepath); +} + +#endif //ICON_BUFFER_H_8425703245726394256 diff --git a/FreeFileSync/Source/localization.cpp b/FreeFileSync/Source/localization.cpp new file mode 100644 index 00000000..ef1ee778 --- /dev/null +++ b/FreeFileSync/Source/localization.cpp @@ -0,0 +1,549 @@ +// ***************************************************************************** +// * 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 "localization.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "parse_plural.h" +#include "parse_lng.h" +#include "ffs_paths.h" + + #include //wcscasecmp + + +using namespace zen; +using namespace fff; + + +namespace +{ +class FFSTranslation : public TranslationHandler +{ +public: + FFSTranslation(const std::string& lngStream); //throw lng::ParsingError, plural::ParsingError + + std::wstring translate(const std::wstring& text) const override + { + //look for translation in buffer table + auto it = transMapping_.find(text); + if (it != transMapping_.end() && !it->second.empty()) + return it->second; + return text; //fallback + } + + std::wstring translate(const std::wstring& singular, const std::wstring& plural, int64_t n) const override + { + auto it = transMappingPl_.find({ singular, plural }); + if (it != transMappingPl_.end()) + { + const size_t formNo = pluralParser_->getForm(n); + assert(formNo < it->second.size()); + if (formNo < it->second.size()) + return replaceCpy(it->second[formNo], L"%x", formatNumber(n)); + } + return replaceCpy(std::abs(n) == 1 ? singular : plural, L"%x", formatNumber(n)); //fallback + } + +private: + using Translation = std::unordered_map; //hash_map is 15% faster than std::map on GCC + using TranslationPlural = std::map, std::vector>; + + Translation transMapping_; //map original text |-> translation + TranslationPlural transMappingPl_; + std::unique_ptr pluralParser_; //bound! +}; + + +FFSTranslation::FFSTranslation(const std::string& lngStream) //throw lng::ParsingError, plural::ParsingError +{ + lng::TransHeader header; + lng::TranslationMap transUtf; + lng::TranslationPluralMap transPluralUtf; + lng::parseLng(lngStream, header, transUtf, transPluralUtf); //throw ParsingError + + pluralParser_ = std::make_unique(header.pluralDefinition); //throw plural::ParsingError + + for (const auto& [original, translation] : transUtf) + transMapping_.emplace(utfTo(original), + utfTo(translation)); + + for (const auto& [singAndPlural, pluralForms] : transPluralUtf) + { + std::vector transPluralForms; + for (const std::string& pf : pluralForms) + transPluralForms.push_back(utfTo(pf)); + + transMappingPl_.insert( + { + { + utfTo(singAndPlural.first), + utfTo(singAndPlural.second) + }, + std::move(transPluralForms) }); + } +} + + +std::vector loadTranslations() +{ + const Zstring& zipPath = getResourceDirPf() + Zstr("Languages.zip"); + std::vector> streams; + + try //to load from ZIP first: + { + const std::string rawStream = loadBinContainer(zipPath, nullptr /*notifyUnbufferedIO*/); //throw FileError + wxMemoryInputStream memStream(rawStream.c_str(), rawStream.size()); //does not take ownership + wxZipInputStream zipStream(memStream, wxConvUTF8); + + while (const auto& entry = std::unique_ptr(zipStream.GetNextEntry())) //take ownership! + if (std::string stream(entry->GetSize(), '\0'); !stream.empty() && zipStream.ReadAll(&stream[0], stream.size())) + streams.emplace_back(utfTo(entry->GetName()), std::move(stream)); + else + assert(false); + } + catch (FileError&) //fall back to folder + { + traverseFolder(beforeLast(zipPath, Zstr(".zip"), IF_MISSING_RETURN_NONE), [&](const FileInfo& fi) + { + if (endsWith(fi.fullPath, Zstr(".lng"))) + try + { + std::string stream = loadBinContainer(fi.fullPath, nullptr /*notifyUnbufferedIO*/); //throw FileError + streams.emplace_back(fi.itemName, std::move(stream)); + } + catch (FileError&) { assert(false); } + }, nullptr, nullptr, [](const std::wstring& errorMsg) { assert(false); }); //errors are not really critical in this context + } + //-------------------------------------------------------------------- + + std::vector locMapping; + { + //default entry: + TranslationInfo newEntry; + newEntry.languageID = wxLANGUAGE_ENGLISH_US; + newEntry.languageName = std::wstring(L"English (US)") + LTR_MARK; //handle weak ")" for bidi-algorithm + newEntry.translatorName = L"Zenju"; + newEntry.languageFlag = L"flag_usa.png"; + newEntry.lngFileName = Zstr(""); + newEntry.lngStream = ""; + locMapping.push_back(newEntry); + } + + for (/*const*/ auto& [fileName, stream] : streams) + try + { + const lng::TransHeader lngHeader = lng::parseHeader(stream); //throw ParsingError + assert(!lngHeader.languageName .empty()); + assert(!lngHeader.translatorName.empty()); + assert(!lngHeader.localeName .empty()); + assert(!lngHeader.flagFile .empty()); + /* + Some ISO codes are used by multiple wxLanguage IDs which can lead to incorrect mapping by wxLocale::FindLanguageInfo()!!! + => Identify by description, e.g. "Chinese (Traditional)". The following IDs are affected: + wxLANGUAGE_CHINESE_TRADITIONAL + wxLANGUAGE_ENGLISH_UK + wxLANGUAGE_SPANISH //non-unique, but still mapped correctly (or is it incidentally???) + wxLANGUAGE_SERBIAN // + */ + if (const wxLanguageInfo* locInfo = wxLocale::FindLanguageInfo(utfTo(lngHeader.localeName))) + { + TranslationInfo newEntry; + newEntry.languageID = static_cast(locInfo->Language); + newEntry.languageName = utfTo(lngHeader.languageName); + newEntry.translatorName = utfTo(lngHeader.translatorName); + newEntry.languageFlag = utfTo(lngHeader.flagFile); + newEntry.lngFileName = fileName; + newEntry.lngStream = std::move(stream); + locMapping.push_back(newEntry); + } + else assert(false); + } + catch (lng::ParsingError&) { assert(false); } //better not show an error message here; scenario: batch jobs + + std::sort(locMapping.begin(), locMapping.end(), [](const TranslationInfo& lhs, const TranslationInfo& rhs) + { + return LessNaturalSort()(utfTo(lhs.languageName), + utfTo(rhs.languageName)); //use a more "natural" sort: ignore case and diacritics + }); + return locMapping; +} + + +wxLanguage mapLanguageDialect(wxLanguage language) +{ + switch (static_cast(language)) //avoid enumeration value wxLANGUAGE_*' not handled in switch [-Wswitch-enum] + { + //variants of wxLANGUAGE_ARABIC + case wxLANGUAGE_ARABIC_ALGERIA: + case wxLANGUAGE_ARABIC_BAHRAIN: + case wxLANGUAGE_ARABIC_EGYPT: + case wxLANGUAGE_ARABIC_IRAQ: + case wxLANGUAGE_ARABIC_JORDAN: + case wxLANGUAGE_ARABIC_KUWAIT: + case wxLANGUAGE_ARABIC_LEBANON: + case wxLANGUAGE_ARABIC_LIBYA: + case wxLANGUAGE_ARABIC_MOROCCO: + case wxLANGUAGE_ARABIC_OMAN: + case wxLANGUAGE_ARABIC_QATAR: + case wxLANGUAGE_ARABIC_SAUDI_ARABIA: + case wxLANGUAGE_ARABIC_SUDAN: + case wxLANGUAGE_ARABIC_SYRIA: + case wxLANGUAGE_ARABIC_TUNISIA: + case wxLANGUAGE_ARABIC_UAE: + case wxLANGUAGE_ARABIC_YEMEN: + return wxLANGUAGE_ARABIC; + + //variants of wxLANGUAGE_CHINESE_SIMPLIFIED + case wxLANGUAGE_CHINESE: + case wxLANGUAGE_CHINESE_SINGAPORE: + return wxLANGUAGE_CHINESE_SIMPLIFIED; + + //variants of wxLANGUAGE_CHINESE_TRADITIONAL + case wxLANGUAGE_CHINESE_TAIWAN: + case wxLANGUAGE_CHINESE_HONGKONG: + case wxLANGUAGE_CHINESE_MACAU: + return wxLANGUAGE_CHINESE_TRADITIONAL; + + //variants of wxLANGUAGE_DUTCH + case wxLANGUAGE_DUTCH_BELGIAN: + return wxLANGUAGE_DUTCH; + + //variants of wxLANGUAGE_ENGLISH_UK + case wxLANGUAGE_ENGLISH_AUSTRALIA: + case wxLANGUAGE_ENGLISH_NEW_ZEALAND: + case wxLANGUAGE_ENGLISH_TRINIDAD: + case wxLANGUAGE_ENGLISH_CARIBBEAN: + case wxLANGUAGE_ENGLISH_JAMAICA: + case wxLANGUAGE_ENGLISH_BELIZE: + case wxLANGUAGE_ENGLISH_EIRE: + case wxLANGUAGE_ENGLISH_SOUTH_AFRICA: + case wxLANGUAGE_ENGLISH_ZIMBABWE: + case wxLANGUAGE_ENGLISH_BOTSWANA: + case wxLANGUAGE_ENGLISH_DENMARK: + return wxLANGUAGE_ENGLISH_UK; + + //variants of wxLANGUAGE_ENGLISH_US + case wxLANGUAGE_ENGLISH: + case wxLANGUAGE_ENGLISH_CANADA: + case wxLANGUAGE_ENGLISH_PHILIPPINES: + return wxLANGUAGE_ENGLISH_US; + + //variants of wxLANGUAGE_FRENCH + case wxLANGUAGE_FRENCH_BELGIAN: + case wxLANGUAGE_FRENCH_CANADIAN: + case wxLANGUAGE_FRENCH_LUXEMBOURG: + case wxLANGUAGE_FRENCH_MONACO: + case wxLANGUAGE_FRENCH_SWISS: + return wxLANGUAGE_FRENCH; + + //variants of wxLANGUAGE_GERMAN + case wxLANGUAGE_GERMAN_AUSTRIAN: + case wxLANGUAGE_GERMAN_BELGIUM: + case wxLANGUAGE_GERMAN_LIECHTENSTEIN: + case wxLANGUAGE_GERMAN_LUXEMBOURG: + case wxLANGUAGE_GERMAN_SWISS: + return wxLANGUAGE_GERMAN; + + //variants of wxLANGUAGE_ITALIAN + case wxLANGUAGE_ITALIAN_SWISS: + return wxLANGUAGE_ITALIAN; + + //variants of wxLANGUAGE_NORWEGIAN_BOKMAL + case wxLANGUAGE_NORWEGIAN_NYNORSK: + return wxLANGUAGE_NORWEGIAN_BOKMAL; + + //variants of wxLANGUAGE_ROMANIAN + case wxLANGUAGE_MOLDAVIAN: + return wxLANGUAGE_ROMANIAN; + + //variants of wxLANGUAGE_RUSSIAN + case wxLANGUAGE_RUSSIAN_UKRAINE: + return wxLANGUAGE_RUSSIAN; + + //variants of wxLANGUAGE_SERBIAN + case wxLANGUAGE_SERBIAN_CYRILLIC: + case wxLANGUAGE_SERBIAN_LATIN: + case wxLANGUAGE_SERBO_CROATIAN: + return wxLANGUAGE_SERBIAN; + + //variants of wxLANGUAGE_SPANISH + case wxLANGUAGE_SPANISH_ARGENTINA: + case wxLANGUAGE_SPANISH_BOLIVIA: + case wxLANGUAGE_SPANISH_CHILE: + case wxLANGUAGE_SPANISH_COLOMBIA: + case wxLANGUAGE_SPANISH_COSTA_RICA: + case wxLANGUAGE_SPANISH_DOMINICAN_REPUBLIC: + case wxLANGUAGE_SPANISH_ECUADOR: + case wxLANGUAGE_SPANISH_EL_SALVADOR: + case wxLANGUAGE_SPANISH_GUATEMALA: + case wxLANGUAGE_SPANISH_HONDURAS: + case wxLANGUAGE_SPANISH_MEXICAN: + case wxLANGUAGE_SPANISH_MODERN: + case wxLANGUAGE_SPANISH_NICARAGUA: + case wxLANGUAGE_SPANISH_PANAMA: + case wxLANGUAGE_SPANISH_PARAGUAY: + case wxLANGUAGE_SPANISH_PERU: + case wxLANGUAGE_SPANISH_PUERTO_RICO: + case wxLANGUAGE_SPANISH_URUGUAY: + case wxLANGUAGE_SPANISH_US: + case wxLANGUAGE_SPANISH_VENEZUELA: + return wxLANGUAGE_SPANISH; + + //variants of wxLANGUAGE_SWEDISH + case wxLANGUAGE_SWEDISH_FINLAND: + return wxLANGUAGE_SWEDISH; + + //languages without variants: + //case wxLANGUAGE_BULGARIAN: + //case wxLANGUAGE_CROATIAN: + //case wxLANGUAGE_CZECH: + //case wxLANGUAGE_DANISH: + //case wxLANGUAGE_FINNISH: + //case wxLANGUAGE_GREEK: + //case wxLANGUAGE_HINDI: + //case wxLANGUAGE_HEBREW: + //case wxLANGUAGE_HUNGARIAN: + //case wxLANGUAGE_JAPANESE: + //case wxLANGUAGE_KOREAN: + //case wxLANGUAGE_LITHUANIAN: + //case wxLANGUAGE_POLISH: + //case wxLANGUAGE_PORTUGUESE: + //case wxLANGUAGE_PORTUGUESE_BRAZILIAN: + //case wxLANGUAGE_SCOTS_GAELIC: + //case wxLANGUAGE_SLOVAK: + //case wxLANGUAGE_SLOVENIAN: + //case wxLANGUAGE_TURKISH: + //case wxLANGUAGE_UKRAINIAN: + //case wxLANGUAGE_VIETNAMESE: + default: + return language; + } +} + + +//we need to interface with wxWidgets' translation handling for a few translations used in their internal source files +// => since there is no better API: dynamically generate a MO file and feed it to wxTranslation +class MemoryTranslationLoader : public wxTranslationsLoader +{ +public: + MemoryTranslationLoader(wxLanguage langId, std::map&& transMapping) : + canonicalName_(wxLocale::GetLanguageCanonicalName(langId)) + { + assert(!canonicalName_.empty()); + + //https://www.gnu.org/software/gettext/manual/html_node/MO-Files.html + transMapping[""] = L"Content-Type: text/plain; charset=UTF-8\n"; + + const int headerSize = 28; + writeNumber(moBuf_, 0x950412de); //magic number + writeNumber(moBuf_, 0); //format version + writeNumber(moBuf_, transMapping.size()); //string count + writeNumber(moBuf_, headerSize); //string references offset: original + writeNumber(moBuf_, headerSize + (2 * sizeof(uint32_t)) * transMapping.size()); //string references offset: translation + writeNumber(moBuf_, 0); //size of hashing table + writeNumber(moBuf_, 0); //offset of hashing table + + const int stringsOffset = headerSize + 2 * (2 * sizeof(uint32_t)) * transMapping.size(); + std::string stringsList; + + for (const auto& [original, translation] : transMapping) + { + writeNumber(moBuf_, original.size()); //string length + writeNumber(moBuf_, stringsOffset + stringsList.size()); //string offset + stringsList.append(original.c_str(), original.size() + 1); //include 0-termination + } + + for (const auto& [original, trans] : transMapping) + { + const auto& translation = utfTo(trans); + writeNumber(moBuf_, translation.size()); //string length + writeNumber(moBuf_, stringsOffset + stringsList.size()); //string offset + stringsList.append(translation.c_str(), translation.size() + 1); //include 0-termination + } + + writeArray(moBuf_, stringsList.c_str(), stringsList.size()); + } + + wxMsgCatalog* LoadCatalog(const wxString& domain, const wxString& lang) override + { + //"lang" is NOT (exactly) what we return from GetAvailableTranslations(), but has a little "extra", e.g.: de_DE.WINDOWS-1252 or ar.WINDOWS-1252 + if (equalAsciiNoCase(extractIsoLangCode(lang), extractIsoLangCode(canonicalName_))) + return wxMsgCatalog::CreateFromData(wxScopedCharBuffer::CreateNonOwned(moBuf_.ref().c_str(), moBuf_.ref().size()), domain); + assert(false); + return nullptr; + } + + wxArrayString GetAvailableTranslations(const wxString& domain) const override + { + wxArrayString available; + available.push_back(canonicalName_); + return available; + } + +private: + static wxString extractIsoLangCode(wxString langCode) + { + langCode = beforeLast(langCode, L".", IF_MISSING_RETURN_ALL); + return beforeLast(langCode, L"_", IF_MISSING_RETURN_ALL); + } + + const wxString canonicalName_; + MemoryStreamOut moBuf_; +}; + + +//global wxWidgets localization: sets up C localization runtime as well! +class wxWidgetsLocale +{ +public: + static wxWidgetsLocale& getInstance() + { + static wxWidgetsLocale inst; + return inst; + } + + void init(wxLanguage lng) + { + lng_ = lng; + + if (const wxLanguageInfo* selLngInfo = wxLocale::GetLanguageInfo(lng)) + layoutDir_ = selLngInfo->LayoutDirection; + else + layoutDir_ = wxLayout_LeftToRight; + + //use sys-lang to preserve sub-language specific rules (e.g. German Swiss number punctuation) + //beneficial even for Arabic locale: support user-specific date settings (instead of Hijri calendar year 1441 = Gregorian 2019) + if (!locale_) + { + //wxWidgets shows a modal dialog on error during wxLocale::Init() -> at least we can shut it up! + wxLog* oldLogTarget = wxLog::SetActiveTarget(new wxLogStderr); //transfer and receive ownership! + ZEN_ON_SCOPE_EXIT(delete wxLog::SetActiveTarget(oldLogTarget)); + + //locale_.reset(); //avoid global locale lifetime overlap! wxWidgets cannot handle this and will crash! + locale_ = std::make_unique(sysLng_, wxLOCALE_DONT_LOAD_DEFAULT /*we're not using wxwin.mo*/); + assert(locale_->IsOk()); + } + } + + void tearDown() { locale_.reset(); lng_ = wxLANGUAGE_UNKNOWN; layoutDir_ = wxLayout_Default; } + + wxLanguage getSysLanguage() const { return sysLng_; } + wxLanguage getLanguage () const { return lng_; } + wxLayoutDirection getLayoutDirection() const { return layoutDir_; } + +private: + wxWidgetsLocale() {} + ~wxWidgetsLocale() { assert(!locale_); } + + const wxLanguage sysLng_ = static_cast(wxLocale::GetSystemLanguage()); + wxLanguage lng_ = wxLANGUAGE_UNKNOWN; + wxLayoutDirection layoutDir_ = wxLayout_Default; + std::unique_ptr locale_; +}; +} + + +const std::vector& fff::getExistingTranslations() +{ + static const std::vector translations = loadTranslations(); + return translations; +} + + +void fff::releaseWxLocale() +{ + wxWidgetsLocale::getInstance().tearDown(); + setTranslator(nullptr); //good place for clean up rather than some time during static destruction: is this an actual benefit??? +} + + +void fff::setLanguage(wxLanguage lng) //throw FileError +{ + if (getLanguage() == lng) + return; //support polling + + //(try to) retrieve language file + std::string lngStream; + Zstring lngFileName; + + for (const TranslationInfo& e : getExistingTranslations()) + if (e.languageID == lng) + { + lngStream = e.lngStream; + lngFileName = e.lngFileName; + break; + } + + //load language file into buffer + if (lngStream.empty()) //if file stream is empty, texts will be English by default + { + setTranslator(nullptr); + lng = wxLANGUAGE_ENGLISH_US; + } + else + try + { + setTranslator(std::make_unique(lngStream)); //throw lng::ParsingError, plural::ParsingError + } + catch (lng::ParsingError& e) + { + throw FileError(replaceCpy(replaceCpy(replaceCpy(_("Error parsing file %x, row %y, column %z."), + L"%x", fmtPath(lngFileName)), + L"%y", numberTo(e.row + 1)), + L"%z", numberTo(e.col + 1)) + + L"\n\n" + e.msg); + } + catch (plural::ParsingError&) + { + throw FileError(L"Invalid plural form definition: " + fmtPath(lngFileName)); //user should never see this! + } + + //handle RTL swapping: we need wxWidgets to do this + wxWidgetsLocale::getInstance().init(lng); + + //add translation for wxWidgets-internal strings: + assert(wxTranslations::Get()); //already initialized by wxLocale + if (wxTranslations* wxtrans = wxTranslations::Get()) + { + std::map transMapping = + { + }; + wxtrans->SetLanguage(lng); //!= wxLocale's language, which could be wxLANGUAGE_DEFAULT (see wxWidgetsLocale) + wxtrans->SetLoader(new MemoryTranslationLoader(lng, std::move(transMapping))); + [[maybe_unused]] const bool catalogAdded = wxtrans->AddCatalog(wxString()); + assert(catalogAdded || lng == wxLANGUAGE_ENGLISH_US); + } +} + + +wxLanguage fff::getSystemLanguage() +{ + static const wxLanguage sysLng = mapLanguageDialect(wxWidgetsLocale::getInstance().getSysLanguage()); + return sysLng; +} + + +wxLanguage fff::getLanguage() +{ + return wxWidgetsLocale::getInstance().getLanguage(); +} + + +wxLayoutDirection fff::getLayoutDirection() +{ + return wxWidgetsLocale::getInstance().getLayoutDirection(); +} diff --git a/FreeFileSync/Source/localization.h b/FreeFileSync/Source/localization.h new file mode 100644 index 00000000..e635ac1e --- /dev/null +++ b/FreeFileSync/Source/localization.h @@ -0,0 +1,39 @@ +// ***************************************************************************** +// * 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 LOCALIZATION_H_8917342083178321534 +#define LOCALIZATION_H_8917342083178321534 + +#include +#include +#include +#include + + +namespace fff +{ +struct TranslationInfo +{ + wxLanguage languageID = wxLANGUAGE_UNKNOWN; + std::wstring languageName; + std::wstring translatorName; + std::wstring languageFlag; + Zstring lngFileName; + std::string lngStream; +}; +const std::vector& getExistingTranslations(); + +wxLanguage getSystemLanguage(); +wxLanguage getLanguage(); +wxLayoutDirection getLayoutDirection(); + +void setLanguage(wxLanguage lng); //throw FileError + +void releaseWxLocale(); //wxLocale crashes miserably on wxGTK when destructor runs during global cleanup => call in wxApp::OnExit +//"You should delete all wxWidgets object that you created by the time OnExit finishes. In particular, do not destroy them from application class' destructor!" +} + +#endif //LOCALIZATION_H_8917342083178321534 diff --git a/FreeFileSync/Source/log_file.cpp b/FreeFileSync/Source/log_file.cpp new file mode 100644 index 00000000..b7032137 --- /dev/null +++ b/FreeFileSync/Source/log_file.cpp @@ -0,0 +1,638 @@ +// ***************************************************************************** +// * 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 "log_file.h" +#include +#include +#include +#include +#include "ffs_paths.h" +#include "afs/concrete.h" + +using namespace zen; +using namespace fff; +using AFS = AbstractFileSystem; + + +namespace +{ +const int LOG_FAIL_PREVIEW_MAX = 25; +const int SEPARATION_LINE_LEN = 40; + + +std::string generateLogHeaderTxt(const ProcessSummary& s, const ErrorLog& log, int logFailsPreviewMax) +{ + const std::string tabSpace(4, ' '); //4, the one true space count for tabs + + std::string headerLine; + for (const std::wstring& jobName : s.jobNames) + headerLine += (headerLine.empty() ? "" : " + ") + utfTo(jobName); + + if (!headerLine.empty()) + headerLine += " "; + + const TimeComp tc = getLocalTime(std::chrono::system_clock::to_time_t(s.startTime)); //returns empty string on failure + headerLine += utfTo(formatTime(formatDateTag, tc) + Zstr(" [") + formatTime(formatTimeTag, tc) + Zstr(']')); + + //assemble summary box + std::vector summary; + summary.emplace_back(); + summary.push_back(tabSpace + utfTo(getSyncResultLabel(s.resultStatus))); + summary.emplace_back(); + + const ErrorLog::Stats logCount = log.getStats(); + + if (logCount.error + logCount.fatal > 0) summary.push_back(tabSpace + utfTo(_("Errors:") + L' ' + formatNumber(logCount.error + logCount.fatal))); + if (logCount.warning > 0) summary.push_back(tabSpace + utfTo(_("Warnings:") + L' ' + formatNumber(logCount.warning))); + + summary.push_back(tabSpace + utfTo(_("Items processed:") + L' ' + formatNumber(s.statsProcessed.items) + //show always, even if 0! + L" (" + formatFilesizeShort(s.statsProcessed.bytes) + L')')); + + if ((s.statsTotal.items < 0 && s.statsTotal.bytes < 0) || //no total items/bytes: e.g. for pure folder comparison + s.statsProcessed == s.statsTotal) //...if everything was processed successfully + ; + else + summary.push_back(tabSpace + utfTo(_("Items remaining:") + + L' ' + formatNumber (s.statsTotal.items - s.statsProcessed.items) + + L" (" + formatFilesizeShort(s.statsTotal.bytes - s.statsProcessed.bytes) + L')')); + + const int64_t totalTimeSec = std::chrono::duration_cast(s.totalTime).count(); + summary.push_back(tabSpace + utfTo(_("Total time:")) + ' ' + utfTo(wxTimeSpan::Seconds(totalTimeSec).Format())); + + size_t sepLineLen = 0; //calculate max width (considering Unicode!) + for (const std::string& str : summary) sepLineLen = std::max(sepLineLen, unicodeLength(str)); + + std::string output = headerLine + '\n'; + output += std::string(sepLineLen + 1, '_') + '\n'; + + for (const std::string& str : summary) + output += '|' + str + '\n'; + + output += '|' + std::string(sepLineLen, '_') + "\n\n"; + + //------------ warnings/errors preview ---------------- + const int logFailTotal = logCount.warning + logCount.error + logCount.fatal; + if (logFailTotal > 0) + { + output += '\n' + utfTo(_("Errors and warnings:")) + '\n'; + output += std::string(SEPARATION_LINE_LEN, '_') + '\n'; + + int previewCount = 0; + if (logFailsPreviewMax > 0) + for (const LogEntry& entry : log) + if (entry.type & (MSG_TYPE_WARNING | MSG_TYPE_ERROR | MSG_TYPE_FATAL_ERROR)) + { + output += utfTo(formatMessage(entry)); + if (++previewCount >= logFailsPreviewMax) + break; + } + if (logFailTotal > previewCount) + output += " [...] " + utfTo(replaceCpy(_P("Showing %y of 1 row", "Showing %y of %x rows", logFailTotal), //%x used as plural form placeholder! + L"%y", formatNumber(previewCount))) + '\n'; + output += std::string(SEPARATION_LINE_LEN, '_') + "\n\n\n"; + } + return output; +} + + +std::string generateLogFooterTxt(const std::wstring& logFilePath, int logItemsTotal, int logItemsPreviewMax) //throw FileError +{ + const ComputerModel cm = getComputerModel(); //throw FileError + + std::string output; + if (logItemsTotal > logItemsPreviewMax) + output += " [...] " + utfTo(replaceCpy(_P("Showing %y of 1 row", "Showing %y of %x rows", logItemsTotal), //%x used as plural form placeholder! + L"%y", formatNumber(logItemsPreviewMax))) + '\n'; + + return output += '\n' + std::string(SEPARATION_LINE_LEN, '_') + '\n' + + + utfTo(getOsDescription() + /*throw FileError*/ + + L" [" + getUserName() /*throw FileError*/ + L']' + + (!cm.model .empty() ? L" - " + cm.model : L"") + + (!cm.vendor.empty() ? L" - " + cm.vendor : L"") + L'\n' + + + _("Log file") + L": " + logFilePath) + '\n'; +} + + +std::string htmlTxt(const std::string_view& str) +{ + std::string msg = htmlSpecialChars(str); + trim(msg); + if (!contains(msg, '\n')) + return msg; + + std::string msgFmt; + for (auto it = msg.begin(); it != msg.end(); ) + if (*it == '\n') + { + msgFmt += "
\n"; + ++it; + + //skip duplicate newlines + for (; it != msg.end() && *it == L'\n'; ++it) + ; + + //preserve leading spaces + for (; it != msg.end() && *it == L' '; ++it) + msgFmt += " "; + } + else + msgFmt += *it++; + + return msgFmt; +} + +std::string htmlTxt(const Zstring& str) { return htmlTxt(utfTo(str)); } +std::string htmlTxt(const std::wstring& str) { return htmlTxt(utfTo(str)); } +std::string htmlTxt(const wchar_t* str) { return htmlTxt(utfTo(str)); } + + +//Astyle screws up royally with the following raw string literals! +//*INDENT-OFF* +std::string formatMessageHtml(const LogEntry& entry) +{ + const std::string typeLabel = htmlTxt(getMessageTypeLabel(entry.type)); + const char* typeImage = nullptr; + switch (entry.type) + { + case MSG_TYPE_INFO: typeImage = "msg-info.png"; break; + case MSG_TYPE_WARNING: typeImage = "msg-warning.png"; break; + case MSG_TYPE_ERROR: + case MSG_TYPE_FATAL_ERROR: typeImage = "msg-error.png"; break; + } + + return R"( + )" + htmlTxt(formatTime(formatTimeTag, getLocalTime(entry.time))) + R"( + ) + )" + htmlTxt(makeStringView(entry.message.begin(), entry.message.end())) + R"( + +)"; +} + + +std::wstring generateLogTitle(const ProcessSummary& s) +{ + std::wstring jobNamesFmt; + for (const std::wstring& jobName : s.jobNames) + jobNamesFmt += (jobNamesFmt.empty() ? L"" : L" + ") + jobName; + + std::wstring title = L"[FreeFileSync] "; + + if (!jobNamesFmt.empty()) + title += jobNamesFmt + L' '; + + switch (s.resultStatus) + { + case SyncResult::finishedSuccess: title += utfTo("\xe2\x9c\x94" "\xef\xb8\x8f"); break; //âœ”ï¸ + case SyncResult::finishedWarning: title += utfTo("\xe2\x9a\xa0" "\xef\xb8\x8f"); break; //âš ï¸ + case SyncResult::finishedError: //efb88f (U+FE0F): variation selector-16 to prefer emoji over text rendering + case SyncResult::aborted: title += utfTo("\xe2\x9d\x8c" "\xef\xb8\x8f"); break; //âŒï¸ + } + return title; +} + + +std::string generateLogHeaderHtml(const ProcessSummary& s, const ErrorLog& log, int logFailsPreviewMax) +{ + std::string output = R"( + + + + + )" + htmlTxt(generateLogTitle(s)) + R"( + + + +)"; + + std::string jobNamesFmt; + for (const std::wstring& jobName : s.jobNames) + jobNamesFmt += (jobNamesFmt.empty() ? "" : " + ") + htmlTxt(jobName); + + const TimeComp tc = getLocalTime(std::chrono::system_clock::to_time_t(s.startTime)); //returns empty string on failure + output += R"(
)" + jobNamesFmt + R"(  )" + + htmlTxt(formatTime(formatDateTag, tc)) + "  " + htmlTxt(formatTime(formatTimeTag, tc)) + "
\n"; + + std::string resultsStatusImage; + switch (s.resultStatus) + { + case SyncResult::finishedSuccess: resultsStatusImage = "result-succes.png"; break; + case SyncResult::finishedWarning: resultsStatusImage = "result-warning.png"; break; + case SyncResult::finishedError: + case SyncResult::aborted: resultsStatusImage = "result-error.png"; break; + } + output += R"( +
+
+ + )" + htmlTxt(getSyncResultLabel(s.resultStatus)) + R"( +
+ )"; + + const ErrorLog::Stats logCount = log.getStats(); + + if (logCount.error + logCount.fatal > 0) + output += R"( + + + + + )"; + + if (logCount.warning > 0) + output += R"( + + + + + )"; + + output += R"( + + + + + )"; + + if ((s.statsTotal.items < 0 && s.statsTotal.bytes < 0) || //no total items/bytes: e.g. for pure folder comparison + s.statsProcessed == s.statsTotal) //...if everything was processed successfully + ; + else + output += R"( + + + + + )"; + + const int64_t totalTimeSec = std::chrono::duration_cast(s.totalTime).count(); + output += R"( + + + + + + +
+)"; + + //------------ warnings/errors preview ---------------- + const int logFailTotal = logCount.warning + logCount.error + logCount.fatal; + if (logFailTotal > 0) + { + output += R"( +
)" + htmlTxt(_("Errors and warnings:")) + R"(
+
+ +)"; + int previewCount = 0; + if (logFailsPreviewMax > 0) + for (const LogEntry& entry : log) + if (entry.type & (MSG_TYPE_WARNING | MSG_TYPE_ERROR | MSG_TYPE_FATAL_ERROR)) + { + output += formatMessageHtml(entry); + if (++previewCount >= logFailsPreviewMax) + break; + } + output += R"(
+)"; + if (logFailTotal > previewCount) + output += R"(
[…])" + + htmlTxt(replaceCpy(_P("Showing %y of 1 row", "Showing %y of %x rows", logFailTotal), //%x used as plural form placeholder! + L"%y", formatNumber(previewCount))) + "
\n"; + + output += R"(

+)"; + } + + output += R"( + +)"; + return output; +} + +std::string generateLogFooterHtml(const std::wstring& logFilePath, int logItemsTotal, int logItemsPreviewMax) //throw FileError +{ + const std::string osImage = "os-linux.png"; + const ComputerModel cm = getComputerModel(); //throw FileError + + std::string output = R"(
+)"; + + if (logItemsTotal > logItemsPreviewMax) + output += R"(
[…])" + + htmlTxt(replaceCpy(_P("Showing %y of 1 row", "Showing %y of %x rows", logItemsTotal), //%x used as plural form placeholder! + L"%y", formatNumber(logItemsPreviewMax))) + "
\n"; + + return output += R"(
+ +
+
+ + )" + htmlTxt(getOsDescription()) + /*throw FileError*/ + + " [" + htmlTxt(getUserName()) /*throw FileError*/ + ']' + + (!cm.model .empty() ? " – " + htmlTxt(cm.model ) : "") + + (!cm.vendor.empty() ? " – " + htmlTxt(cm.vendor) : "") + R"( +
+
+ ) + )" + htmlTxt(logFilePath) + R"( +
+ + +)"; +} + +//-> Astyle fucks up! => no INDENT-ON + + +void streamToLogFile(const ProcessSummary& summary, //throw FileError + const ErrorLog& log, + AFS::OutputStream& streamOut, + const AbstractPath& logFilePath, + LogFileFormat logFormat) +{ + const int logItemsTotal = log.end() - log.begin(); + const int logItemsPreviewMax = std::numeric_limits::max(); + + std::string buffer = logFormat == LogFileFormat::html ? + generateLogHeaderHtml(summary, log, LOG_FAIL_PREVIEW_MAX) : + generateLogHeaderTxt (summary, log, LOG_FAIL_PREVIEW_MAX); + + //write log items in blocks instead of creating one big string: memory allocation might fail; think 1 million entries! + for (const LogEntry& entry : log) + { + buffer += logFormat == LogFileFormat::html ? + formatMessageHtml(entry) : + formatMessage (entry); + + streamOut.write(&buffer[0], buffer.size()); //throw FileError, X + buffer.clear(); + } + + buffer += logFormat == LogFileFormat::html ? + generateLogFooterHtml(AFS::getDisplayPath(logFilePath), logItemsTotal, logItemsPreviewMax) : //throw FileError + generateLogFooterTxt (AFS::getDisplayPath(logFilePath), logItemsTotal, logItemsPreviewMax); //throw FileError + + //don't forget to flush: + streamOut.write(&buffer[0], buffer.size()); //throw FileError, X +} + + +void saveNewLogFile(const AbstractPath& logFilePath, //throw FileError, X + LogFileFormat logFormat, + const ProcessSummary& summary, + const ErrorLog& log, + const std::function& notifyStatus /*throw X*/) +{ + //create logfile folder if required + if (const std::optional parentPath = AFS::getParentPath(logFilePath)) + try + { + AFS::createFolderIfMissingRecursion(*parentPath); //throw FileError + } + catch (const FileError& e) //add context info regarding log file! + { + throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(AFS::getDisplayPath(logFilePath))), e.toString()); + } + //----------------------------------------------------------------------- + + auto notifyUnbufferedIO = [notifyStatus, + bytesWritten_ = int64_t(0), + msg_ = replaceCpy(_("Saving file %x..."), L"%x", fmtPath(AFS::getDisplayPath(logFilePath)))] + (int64_t bytesDelta) mutable + { + if (notifyStatus) + notifyStatus(msg_ + L" (" + formatFilesizeShort(bytesWritten_ += bytesDelta) + L")"); //throw X + }; + + std::unique_ptr logFileStream = AFS::getOutputStream(logFilePath, std::nullopt /*streamSize*/, std::nullopt /*modTime*/, notifyUnbufferedIO); //throw FileError + streamToLogFile(summary, log, *logFileStream, logFilePath, logFormat); //throw FileError, X + logFileStream->finalize(); //throw FileError, X +} + + +const int TIME_STAMP_LENGTH = 21; +const Zchar STATUS_BEGIN_TOKEN[] = Zstr(" ["); +const Zchar STATUS_END_TOKEN = Zstr(']'); + + +struct LogFileInfo +{ + AbstractPath filePath; + time_t timeStamp; + std::wstring jobNames; //may be empty +}; +std::vector getLogFiles(const AbstractPath& logFolderPath) //throw FileError +{ + std::vector logfiles; + + AFS::traverseFolderFlat(logFolderPath, [&](const AFS::FileInfo& fi) //throw FileError + { + //"Backup FreeFileSync 2013-09-15 015052.123.html" + //"Jobname1 + Jobname2 2013-09-15 015052.123.log" + //"2013-09-15 015052.123 [Error].log" + static_assert(TIME_STAMP_LENGTH == 21); + + if (endsWith(fi.itemName, Zstr(".log")) || //case-sensitive: e.g. ".LOG" is not from FFS, right? + endsWith(fi.itemName, Zstr(".html"))) + { + auto tsBegin = fi.itemName.begin(); + auto tsEnd = tsBegin + fi.itemName.rfind('.'); + + if (tsBegin != tsEnd && tsEnd[-1] == STATUS_END_TOKEN) + tsEnd = searchLast(tsBegin, tsEnd, + std::begin(STATUS_BEGIN_TOKEN), std::end(STATUS_BEGIN_TOKEN) - 1); + + if (tsEnd - tsBegin >= TIME_STAMP_LENGTH && + tsEnd[-4] == Zstr('.') && + isdigit(tsEnd[-3]) && + isdigit(tsEnd[-2]) && + isdigit(tsEnd[-1])) + { + tsBegin = tsEnd - TIME_STAMP_LENGTH; + const TimeComp tc = parseTime(Zstr("%Y-%m-%d %H%M%S"), makeStringView(tsBegin, 17)); //returns TimeComp() on error + const time_t t = localToTimeT(tc); //returns -1 on error + if (t != -1) + { + Zstring jobNames(fi.itemName.begin(), tsBegin); + if (!jobNames.empty()) + { + assert(jobNames.size() >= 2 && endsWith(jobNames, Zstr(' '))); + jobNames.pop_back(); + } + + logfiles.push_back({ AFS::appendRelPath(logFolderPath, fi.itemName), t, utfTo(jobNames) }); + } + } + } + }, + nullptr /*onFolder*/, //traverse only one level deep + nullptr /*onSymlink*/); + + return logfiles; +} + + +void limitLogfileCount(const AbstractPath& logFolderPath, //throw FileError, X + int logfilesMaxAgeDays, //<= 0 := no limit + const std::set& logFilePathsToKeep, + const std::function& notifyStatus /*throw X*/) +{ + if (logfilesMaxAgeDays > 0) + { + const std::wstring statusPrefix = _("Cleaning up log files:") + L" [" + _P("1 day", "%x days", logfilesMaxAgeDays) + L"] "; + + if (notifyStatus) notifyStatus(statusPrefix + fmtPath(AFS::getDisplayPath(logFolderPath))); //throw X + + std::vector logFiles = getLogFiles(logFolderPath); //throw FileError + + const time_t lastMidnightTime = [] + { + TimeComp tc = getLocalTime(); //returns TimeComp() on error + tc.second = 0; + tc.minute = 0; + tc.hour = 0; + return localToTimeT(tc); //returns -1 on error => swallow => no versions trimmed by versionMaxAgeDays + }(); + const time_t cutOffTime = lastMidnightTime - static_cast(logfilesMaxAgeDays) * 24 * 3600; + + std::exception_ptr firstError; + + for (const LogFileInfo& lfi : logFiles) + if (lfi.timeStamp < cutOffTime && + !contains(logFilePathsToKeep, lfi.filePath)) //don't trim latest log files corresponding to last used config files! + //nitpicker's corner: what about path differences due to case? e.g. user-overriden log file path changed in case + { + if (notifyStatus) notifyStatus(statusPrefix + fmtPath(AFS::getDisplayPath(lfi.filePath))); //throw X + try + { + AFS::removeFilePlain(lfi.filePath); //throw FileError + } + catch (const FileError&) { if (!firstError) firstError = std::current_exception(); }; + } + + if (firstError) //late failure! + std::rethrow_exception(firstError); + } +} +} + + +Zstring fff::getDefaultLogFolderPath() { return getConfigDirPathPf() + Zstr("Logs") ; } + + +//"Backup FreeFileSync 2013-09-15 015052.123.html" +//"Backup FreeFileSync 2013-09-15 015052.123 [Error].html" +AbstractPath fff::generateLogFilePath(LogFileFormat logFormat, const ProcessSummary& summary, const Zstring& altLogFolderPathPhrase /*optional*/) +{ + //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://freefilesync.org/forum/viewtopic.php?t=1679 + + //assemble logfile name + const TimeComp tc = getLocalTime(std::chrono::system_clock::to_time_t(summary.startTime)); + if (tc == TimeComp()) + throw FileError(L"Failed to determine current time: " + numberTo(summary.startTime.time_since_epoch().count())); + + const auto timeMs = std::chrono::duration_cast(summary.startTime.time_since_epoch()).count() % 1000; + assert(std::chrono::duration_cast(summary.startTime.time_since_epoch()).count() == std::chrono::system_clock::to_time_t(summary.startTime)); + + Zstring logFileName; + if (!summary.jobNames.empty()) + { + for (const std::wstring& jobName : summary.jobNames) + logFileName += utfTo(jobName) + Zstr(" + "); + logFileName.resize(logFileName.size() - 2); + } + + logFileName += formatTime(Zstr("%Y-%m-%d %H%M%S"), tc) + + Zstr(".") + printNumber(Zstr("%03d"), static_cast(timeMs)); //[ms] should yield a fairly unique name + static_assert(TIME_STAMP_LENGTH == 21); + + const std::wstring failStatus = [&] + { + switch (summary.resultStatus) + { + case SyncResult::finishedSuccess: break; + case SyncResult::finishedWarning: return _("Warning"); + case SyncResult::finishedError: return _("Error"); + case SyncResult::aborted: return _("Stopped"); + } + return std::wstring(); + }(); + + if (!failStatus.empty()) + logFileName += STATUS_BEGIN_TOKEN + utfTo(failStatus) + STATUS_END_TOKEN; + + if (logFormat == LogFileFormat::html) + logFileName += Zstr(".html"); + else + logFileName += Zstr(".log"); + + AbstractPath logFolderPath = createAbstractPath(altLogFolderPathPhrase); + if (AFS::isNullPath(logFolderPath)) + logFolderPath = createAbstractPath(getDefaultLogFolderPath()); + + return AFS::appendRelPath(logFolderPath, logFileName); +} + + +void fff::saveLogFile(const AbstractPath& logFilePath, //throw FileError, X + const ProcessSummary& summary, + const ErrorLog& log, + int logfilesMaxAgeDays, + LogFileFormat logFormat, + const std::set& logFilePathsToKeep, + const std::function& notifyStatus /*throw X*/) +{ + std::exception_ptr firstError; + try + { + saveNewLogFile(logFilePath, logFormat, summary, log, notifyStatus); //throw FileError, X + } + catch (const FileError&) { if (!firstError) firstError = std::current_exception(); }; + + try + { + const std::optional logFolderPath = AFS::getParentPath(logFilePath); + assert(logFolderPath); + if (logFolderPath) //else: logFilePath == device root; not possible with generateLogFilePath() + limitLogfileCount(*logFolderPath, logfilesMaxAgeDays, logFilePathsToKeep, notifyStatus); //throw FileError, X + } + catch (const FileError&) { if (!firstError) firstError = std::current_exception(); }; + + if (firstError) //late failure! + std::rethrow_exception(firstError); +} + + + + +void fff::sendLogAsEmail(const std::string& email, //throw FileError, X + const ProcessSummary& summary, + const ErrorLog& log, + const AbstractPath& logFilePath, + const std::function& notifyStatus /*throw X*/) +{ + try + { + throw SysError(_("Requires FreeFileSync Donation Edition")); + } + catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot send notification email to %x."), L"%x", L'"' + utfTo(email) + L'"'), e.toString()); } +} diff --git a/FreeFileSync/Source/log_file.h b/FreeFileSync/Source/log_file.h new file mode 100644 index 00000000..5eec06ad --- /dev/null +++ b/FreeFileSync/Source/log_file.h @@ -0,0 +1,45 @@ +// ***************************************************************************** +// * 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 GENERATE_LOGFILE_H_931726432167489732164 +#define GENERATE_LOGFILE_H_931726432167489732164 + +#include +#include +#include "return_codes.h" +#include "status_handler.h" +#include "afs/abstract.h" + + +namespace fff +{ +Zstring getDefaultLogFolderPath(); + +enum class LogFileFormat +{ + html, + text +}; + + +AbstractPath generateLogFilePath(LogFileFormat logFormat, const ProcessSummary& summary, const Zstring& altLogFolderPathPhrase /*optional*/); + +void saveLogFile(const AbstractPath& logFilePath, //throw FileError, X + const ProcessSummary& summary, + const zen::ErrorLog& log, + int logfilesMaxAgeDays, + LogFileFormat logFormat, + const std::set& logFilePathsToKeep, + const std::function& notifyStatus /*throw X*/); + +void sendLogAsEmail(const std::string& email, //throw FileError, X + const ProcessSummary& summary, + const zen::ErrorLog& log, + const AbstractPath& logFilePath, + const std::function& notifyStatus /*throw X*/); +} + +#endif //GENERATE_LOGFILE_H_931726432167489732164 diff --git a/FreeFileSync/Source/parse_lng.h b/FreeFileSync/Source/parse_lng.h new file mode 100644 index 00000000..24f63d3a --- /dev/null +++ b/FreeFileSync/Source/parse_lng.h @@ -0,0 +1,824 @@ +// ***************************************************************************** +// * 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 PARSE_LNG_H_46794693622675638 +#define PARSE_LNG_H_46794693622675638 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "parse_plural.h" + + +namespace lng +{ +//singular forms +using TranslationMap = std::map ; //orig |-> translation + +//plural forms +using SingularPluralPair = std::pair; //1 house | %x houses +using PluralForms = std::vector; //1 dom | 2 domy | %x domów +using TranslationPluralMap = std::map; //(sing/plu) |-> pluralforms + +struct TransHeader +{ + std::string languageName; //display name: "English (UK)" + std::string translatorName; //"Zenju" + std::string localeName; //ISO 639 language code + ISO 3166 country code, e.g. "en_GB", or "en_US" + std::string flagFile; //"england.png" + int pluralCount = 0; //2 + std::string pluralDefinition; //"n == 1 ? 0 : 1" +}; + + +struct ParsingError +{ + std::wstring msg; + size_t row = 0; //starting with 0 + size_t col = 0; // +}; +TransHeader parseHeader(const std::string& fileStream); //throw ParsingError +void parseLng(const std::string& fileStream, TransHeader& header, TranslationMap& out, TranslationPluralMap& pluralOut); //throw ParsingError + +class TranslationUnorderedList; //unordered list of unique translation items +std::string generateLng(const TranslationUnorderedList& in, const TransHeader& header); + + + + + + + + + + + + + + + + + + + +//--------------------------- implementation --------------------------- +enum class TranslationNewItemPos +{ + REL, + TOP +}; + +class TranslationUnorderedList //unordered list of unique translation items +{ +public: + TranslationUnorderedList(TranslationNewItemPos newItemPos, TranslationMap&& transOld, TranslationPluralMap&& transPluralOld) : + newItemPos_(newItemPos), transOld_(std::move(transOld)), transPluralOld_(std::move(transPluralOld)) {} + + void addItem(const std::string& orig) + { + if (!transUnique_.insert(orig).second) return; + auto it = transOld_.find(orig); + if (it != transOld_.end() && !it->second.empty()) //preserve old translation from .lng file if existing + sequence_.push_back(std::make_shared(std::make_pair(orig, it->second))); + else + switch (newItemPos_) + { + case TranslationNewItemPos::REL: + sequence_.push_back(std::make_shared(std::make_pair(orig, std::string()))); + break; + case TranslationNewItemPos::TOP: + sequence_.push_front(std::make_shared(std::make_pair(orig, std::string()))); //put untranslated items to the front of the .lng filebreak; + break; + } + } + + void addItem(const SingularPluralPair& orig) + { + if (!pluralUnique_.insert(orig).second) return; + auto it = transPluralOld_.find(orig); + if (it != transPluralOld_.end() && !it->second.empty()) //preserve old translation from .lng file if existing + sequence_.push_back(std::make_shared(std::make_pair(orig, it->second))); + else + switch (newItemPos_) + { + case TranslationNewItemPos::REL: + sequence_.push_back(std::make_shared(std::make_pair(orig, PluralForms()))); + break; + case TranslationNewItemPos::TOP: + sequence_.push_front(std::make_shared(std::make_pair(orig, PluralForms()))); //put untranslated items to the front of the .lng file + break; + } + } + + bool untranslatedTextExists() const { return std::any_of(sequence_.begin(), sequence_.end(), [](const std::shared_ptr& item) { return !item->hasTranslation(); }); } + + template + void visitItems(Function onTrans, Function2 onPluralTrans) const //onTrans takes (const TranslationMap::value_type&), onPluralTrans takes (const TranslationPluralMap::value_type&) + { + for (const std::shared_ptr& item : sequence_) + if (auto regular = dynamic_cast(item.get())) + onTrans(regular->value); + else if (auto plural = dynamic_cast(item.get())) + onPluralTrans(plural->value); + else assert(false); + } + +private: + struct Item { virtual ~Item() {} virtual bool hasTranslation() const = 0; }; + struct RegularItem : public Item { RegularItem(const TranslationMap ::value_type& val) : value(val) {} bool hasTranslation() const override { return !value.second.empty(); } TranslationMap ::value_type value; }; + struct PluralItem : public Item { PluralItem (const TranslationPluralMap::value_type& val) : value(val) {} bool hasTranslation() const override { return !value.second.empty(); } TranslationPluralMap::value_type value; }; + + const TranslationNewItemPos newItemPos_; + std::list> sequence_; //ordered list of translation elements + + std::set transUnique_; //check uniqueness + std::set pluralUnique_; // + + const TranslationMap transOld_; //reuse existing translation + const TranslationPluralMap transPluralOld_; // +}; + + +struct Token +{ + enum Type + { + //header information + TK_HEADER_BEGIN, + TK_HEADER_END, + TK_LANG_NAME_BEGIN, + TK_LANG_NAME_END, + TK_TRANS_NAME_BEGIN, + TK_TRANS_NAME_END, + TK_LOCALE_NAME_BEGIN, + TK_LOCALE_NAME_END, + TK_FLAG_FILE_BEGIN, + TK_FLAG_FILE_END, + TK_PLURAL_COUNT_BEGIN, + TK_PLURAL_COUNT_END, + TK_PLURAL_DEF_BEGIN, + TK_PLURAL_DEF_END, + + //item level + TK_SRC_BEGIN, + TK_SRC_END, + TK_TRG_BEGIN, + TK_TRG_END, + TK_TEXT, + TK_PLURAL_BEGIN, + TK_PLURAL_END, + TK_END + }; + + Token(Type t) : type(t) {} + + Type type; + std::string text; +}; + + +class KnownTokens +{ +public: + KnownTokens() {} //clang wants it, clang gets it + + using TokenMap = std::map; + + const TokenMap& getList() const { return tokens_; } + + std::string text(Token::Type t) const + { + auto it = tokens_.find(t); + if (it != tokens_.end()) + return it->second; + assert(false); + return std::string(); + } + +private: + const TokenMap tokens_ = + { + //header information + { Token::TK_HEADER_BEGIN, "
" }, + { Token::TK_HEADER_END, "
" }, + { Token::TK_LANG_NAME_BEGIN, "" }, + { Token::TK_LANG_NAME_END, "" }, + { Token::TK_TRANS_NAME_BEGIN, "" }, + { Token::TK_TRANS_NAME_END, "" }, + { Token::TK_LOCALE_NAME_BEGIN, "" }, + { Token::TK_LOCALE_NAME_END, "" }, + { Token::TK_FLAG_FILE_BEGIN, "" }, + { Token::TK_FLAG_FILE_END, "" }, + { Token::TK_PLURAL_COUNT_BEGIN, "" }, + { Token::TK_PLURAL_COUNT_END, "" }, + { Token::TK_PLURAL_DEF_BEGIN, "" }, + { Token::TK_PLURAL_DEF_END, "" }, + + //item level + { Token::TK_SRC_BEGIN, "" }, + { Token::TK_SRC_END, "" }, + { Token::TK_TRG_BEGIN, "" }, + { Token::TK_TRG_END, "" }, + { Token::TK_PLURAL_BEGIN, "" }, + { Token::TK_PLURAL_END, "" }, + }; +}; + + +class Scanner +{ +public: + Scanner(const std::string& byteStream) : stream_(byteStream), pos_(stream_.begin()) + { + if (zen::startsWith(stream_, zen::BYTE_ORDER_MARK_UTF8)) + pos_ += zen::strLength(zen::BYTE_ORDER_MARK_UTF8); + } + + Token getNextToken() + { + //skip whitespace + pos_ = std::find_if_not(pos_, stream_.end(), zen::isWhiteSpace); + + if (pos_ == stream_.end()) + return Token(Token::TK_END); + + for (const auto& [tokenEnum, tokenString] : tokens_.getList()) + if (startsWith(tokenString)) + { + pos_ += tokenString.size(); + return Token(tokenEnum); + } + + //rest must be "text" + auto itBegin = pos_; + while (pos_ != stream_.end() && !startsWithKnownTag()) + pos_ = std::find(pos_ + 1, stream_.end(), '<'); + + std::string text(itBegin, pos_); + + normalize(text); //remove whitespace from end etc. + + if (text.empty() && pos_ == stream_.end()) + return Token(Token::TK_END); + + Token out(Token::TK_TEXT); + out.text = std::move(text); + return out; + } + + size_t posRow() const //current row beginning with 0 + { + //count line endings + const size_t crSum = std::count(stream_.begin(), pos_, '\r'); //carriage returns + const size_t nlSum = std::count(stream_.begin(), pos_, '\n'); //new lines + assert(crSum == 0 || nlSum == 0 || crSum == nlSum); + return std::max(crSum, nlSum); //be compatible with Linux/Mac/Win + } + + size_t posCol() const //current col beginning with 0 + { + //seek beginning of line + for (auto it = pos_; it != stream_.begin(); ) + { + --it; + if (zen::isLineBreak(*it)) + return pos_ - it - 1; + } + return pos_ - stream_.begin(); + } + +private: + bool startsWithKnownTag() const + { + return std::any_of(tokens_.getList().begin(), tokens_.getList().end(), + [&](const KnownTokens::TokenMap::value_type& p) { return startsWith(p.second); }); + } + + bool startsWith(const std::string& prefix) const + { + return zen::startsWith(zen::makeStringView(pos_, stream_.end()), prefix); + } + + static void normalize(std::string& text) + { + zen::trim(text); //remove whitespace from both ends + + //Delimiter: + //---------- + //Linux: 0xA \n + //Mac: 0xD \r + //Win: 0xD 0xA \r\n <- language files are in Windows format + zen::replace(text, "\r\n", '\n'); // + zen::replace(text, "\r", '\n'); //ensure c-style line breaks + } + + const std::string stream_; + std::string::const_iterator pos_; + const KnownTokens tokens_; //no need for static non-POD! +}; + + +class LngParser +{ +public: + LngParser(const std::string& fileStream) : scn_(fileStream), tk_(scn_.getNextToken()) {} + + void parse(TranslationMap& out, TranslationPluralMap& pluralOut, TransHeader& header) + { + parseHeader(header); + + try + { + plural::PluralFormInfo pi(header.pluralDefinition, header.pluralCount); + + //items + while (token().type != Token::TK_END) + parseRegular(out, pluralOut, pi); + } + catch (const plural::InvalidPluralForm&) + { + throw ParsingError({ L"Invalid plural form definition", scn_.posRow(), scn_.posCol() }); + } + } + + void parseHeader(TransHeader& header) + { + consumeToken(Token::TK_HEADER_BEGIN); //throw ParsingError + + consumeToken(Token::TK_LANG_NAME_BEGIN); //throw ParsingError + header.languageName = token().text; + consumeToken(Token::TK_TEXT); //throw ParsingError + consumeToken(Token::TK_LANG_NAME_END); // + + consumeToken(Token::TK_TRANS_NAME_BEGIN); //throw ParsingError + header.translatorName = token().text; + consumeToken(Token::TK_TEXT); //throw ParsingError + consumeToken(Token::TK_TRANS_NAME_END); // + + consumeToken(Token::TK_LOCALE_NAME_BEGIN); //throw ParsingError + header.localeName = token().text; + consumeToken(Token::TK_TEXT); //throw ParsingError + consumeToken(Token::TK_LOCALE_NAME_END); // + + consumeToken(Token::TK_FLAG_FILE_BEGIN); //throw ParsingError + header.flagFile = token().text; + consumeToken(Token::TK_TEXT); //throw ParsingError + consumeToken(Token::TK_FLAG_FILE_END); // + + consumeToken(Token::TK_PLURAL_COUNT_BEGIN); //throw ParsingError + header.pluralCount = zen::stringTo(token().text); + consumeToken(Token::TK_TEXT); //throw ParsingError + consumeToken(Token::TK_PLURAL_COUNT_END); // + + consumeToken(Token::TK_PLURAL_DEF_BEGIN); //throw ParsingError + header.pluralDefinition = token().text; + consumeToken(Token::TK_TEXT); //throw ParsingError + consumeToken(Token::TK_PLURAL_DEF_END); // + + consumeToken(Token::TK_HEADER_END); //throw ParsingError + } + +private: + void parseRegular(TranslationMap& out, TranslationPluralMap& pluralOut, const plural::PluralFormInfo& pluralInfo) + { + consumeToken(Token::TK_SRC_BEGIN); //throw ParsingError + + if (token().type == Token::TK_PLURAL_BEGIN) + return parsePlural(pluralOut, pluralInfo); + + std::string original = token().text; + consumeToken(Token::TK_TEXT); //throw ParsingError + consumeToken(Token::TK_SRC_END); // + + consumeToken(Token::TK_TRG_BEGIN); //throw ParsingError + std::string translation; + if (token().type == Token::TK_TEXT) + { + translation = token().text; + nextToken(); + } + validateTranslation(original, translation); //throw ParsingError + consumeToken(Token::TK_TRG_END); // + + out.emplace(original, translation); + } + + void parsePlural(TranslationPluralMap& pluralOut, const plural::PluralFormInfo& pluralInfo) + { + //Token::TK_SRC_BEGIN already consumed + + consumeToken(Token::TK_PLURAL_BEGIN); //throw ParsingError + std::string engSingular = token().text; + consumeToken(Token::TK_TEXT); //throw ParsingError + consumeToken(Token::TK_PLURAL_END); // + + consumeToken(Token::TK_PLURAL_BEGIN); //throw ParsingError + std::string engPlural = token().text; + consumeToken(Token::TK_TEXT); //throw ParsingError + consumeToken(Token::TK_PLURAL_END); // + + consumeToken(Token::TK_SRC_END); //throw ParsingError + const SingularPluralPair original(engSingular, engPlural); + + consumeToken(Token::TK_TRG_BEGIN); //throw ParsingError + + PluralForms pluralList; + while (token().type == Token::TK_PLURAL_BEGIN) + { + consumeToken(Token::TK_PLURAL_BEGIN); //throw ParsingError + std::string pluralForm = token().text; + consumeToken(Token::TK_TEXT); //throw ParsingError + consumeToken(Token::TK_PLURAL_END); // + pluralList.push_back(pluralForm); + } + validateTranslation(original, pluralList, pluralInfo); + consumeToken(Token::TK_TRG_END); //throw ParsingError + + pluralOut.emplace(original, pluralList); + } + + void validateTranslation(const std::string& original, const std::string& translation) //throw ParsingError + { + using namespace zen; + + if (original.empty()) + throw ParsingError({ L"Translation source text is empty", scn_.posRow(), scn_.posCol() }); + + if (!isValidUtf(original)) + throw ParsingError({ L"Translation source text contains UTF-8 encoding error", scn_.posRow(), scn_.posCol() }); + if (!isValidUtf(translation)) + throw ParsingError({ L"Translation text contains UTF-8 encoding error", scn_.posRow(), scn_.posCol() }); + + if (!translation.empty()) + { + //if original contains placeholder, so must translation! + auto checkPlaceholder = [&](const std::string& placeholder) + { + if (contains(original, placeholder) && + !contains(translation, placeholder)) + throw ParsingError({ replaceCpy(L"Placeholder %x missing in translation", L"%x", utfTo(placeholder)), scn_.posRow(), scn_.posCol() }); + }; + checkPlaceholder("%x"); + checkPlaceholder("%y"); + checkPlaceholder("%z"); + + //if source is a one-liner, so should be the translation + if (!contains(original, '\n') && contains(translation, '\n')) + throw ParsingError({ L"Source text is a one-liner, but translation consists of multiple lines", scn_.posRow(), scn_.posCol() }); + + //if source contains ampersand to mark menu accellerator key, so must translation + const size_t ampCount = ampersandTokenCount(original); + if (ampCount > 1 || ampCount != ampersandTokenCount(translation)) + throw ParsingError({ L"Source and translation both need exactly one & character to mark a menu item access key or none at all", scn_.posRow(), scn_.posCol() }); + + //ampersand at the end makes buggy wxWidgets crash miserably + if (endsWithSingleAmp(original) || endsWithSingleAmp(translation)) + throw ParsingError({ L"The & character to mark a menu item access key must not occur at the end of a string", scn_.posRow(), scn_.posCol() }); + + //if source ends with colon, so must translation (note: character seems to be universally used, even for asian and arabic languages) + if (endsWith(original, ':') && !endsWithColon(translation)) + throw ParsingError({ L"Source text ends with a colon character \":\", but translation does not", scn_.posRow(), scn_.posCol() }); + + //if source ends with a period, so must translation (note: character seems to be universally used, even for asian and arabic languages) + if (endsWithSingleDot(original) && !endsWithSingleDot(translation)) + throw ParsingError({ L"Source text ends with a punctuation mark character \".\", but translation does not", scn_.posRow(), scn_.posCol() }); + + //if source ends with an ellipsis, so must translation (note: character seems to be universally used, even for asian and arabic languages) + if (endsWithEllipsis(original) && !endsWithEllipsis(translation)) + throw ParsingError({ L"Source text ends with an ellipsis \"...\", but translation does not", scn_.posRow(), scn_.posCol() }); + + //check for not-to-be-translated texts + for (const char* fixedStr : { "FreeFileSync", "RealTimeSync", "ffs_gui", "ffs_batch", "ffs_tmp", "GlobalSettings.xml" }) + if (contains(original, fixedStr) && !contains(translation, fixedStr)) + throw ParsingError({ replaceCpy(L"Misspelled \"%x\" in translation", L"%x", utfTo(fixedStr)), scn_.posRow(), scn_.posCol() }); + + //some languages (French!) put a space before punctuation mark => must be a no-brake space! + for (const char punctChar : std::string(".!?:;$#")) + if (contains(original, std::string(" ") + punctChar) || + contains(translation, std::string(" ") + punctChar)) + throw ParsingError({ replaceCpy(L"Text contains a space before the \"%x\" character. Are line-breaks really allowed here?" + " Maybe this should be a \"non-breaking space\" (Windows: Alt 0160 UTF8: 0xC2 0xA0)?", + L"%x", utfTo(punctChar)), scn_.posRow(), scn_.posCol() }); + } + } + + void validateTranslation(const SingularPluralPair& original, const PluralForms& translation, const plural::PluralFormInfo& pluralInfo) //throw ParsingError + { + using namespace zen; + + if (original.first.empty() || original.second.empty()) + throw ParsingError({ L"Translation source text is empty", scn_.posRow(), scn_.posCol() }); + + const std::vector allTexts = [&] + { + std::vector at{ original.first, original.second }; + at.insert(at.end(), translation.begin(), translation.end()); + return at; + }(); + + for (const std::string& str : allTexts) + if (!isValidUtf(str)) + throw ParsingError({ L"Text contains UTF-8 encoding error", scn_.posRow(), scn_.posCol() }); + + //check the primary placeholder is existing at least for the second english text + if (!contains(original.second, "%x")) + throw ParsingError({ L"Plural form source text does not contain %x placeholder", scn_.posRow(), scn_.posCol() }); + + if (!translation.empty()) + { + //check for invalid number of plural forms + if (pluralInfo.getCount() != static_cast(translation.size())) + throw ParsingError({ replaceCpy(replaceCpy(L"Invalid number of plural forms; actual: %x, expected: %y", L"%x", numberTo(translation.size())), L"%y", numberTo(pluralInfo.getCount())), scn_.posRow(), scn_.posCol() }); + + //check for duplicate plural form translations (catch copy & paste errors for single-number form translations) + for (auto it = translation.begin(); it != translation.end(); ++it) + if (!contains(*it, "%x")) + { + auto it2 = std::find(it + 1, translation.end(), *it); + if (it2 != translation.end()) + throw ParsingError({ replaceCpy(L"Duplicate plural form translation at index position %x", L"%x", numberTo(it2 - translation.begin())), scn_.posRow(), scn_.posCol() }); + } + + for (int pos = 0; pos < static_cast(translation.size()); ++pos) + if (pluralInfo.isSingleNumberForm(pos)) + { + //translation needs to use decimal number if english source does so (e.g. frequently changing text like statistics) + if (contains(original.first, "%x") || + contains(original.first, "1")) + { + const int firstNumber = pluralInfo.getFirstNumber(pos); + if (!(contains(translation[pos], "%x") || + contains(translation[pos], numberTo(firstNumber)))) + throw ParsingError({ replaceCpy(replaceCpy(L"Plural form translation at index position %y needs to use the decimal number %z or the %x placeholder", + L"%y", numberTo(pos)), L"%z", numberTo(firstNumber)), scn_.posRow(), scn_.posCol() }); + } + } + else + { + //ensure the placeholder is used when needed + if (!contains(translation[pos], "%x")) + throw ParsingError({ replaceCpy(L"Plural form at index position %y is missing the %x placeholder", L"%y", numberTo(pos)), scn_.posRow(), scn_.posCol() }); + } + + auto checkSecondaryPlaceholder = [&](const std::string& placeholder) + { + //make sure secondary placeholder is used for both source texts (or none) and all plural forms + if (contains(original.first, placeholder) || + contains(original.second, placeholder)) + for (const std::string& str : allTexts) + if (!contains(str, placeholder)) + throw ParsingError({ zen::replaceCpy(L"Placeholder %x missing in text", L"%x", zen::utfTo(placeholder)), scn_.posRow(), scn_.posCol() }); + }; + checkSecondaryPlaceholder("%y"); + checkSecondaryPlaceholder("%z"); + + //if source is a one-liner, so should be the translation + if (!contains(original.first, '\n') && !contains(original.second, '\n') && + /**/std::any_of(translation.begin(), translation.end(), [](const std::string& pform) { return contains(pform, '\n'); })) + /**/throw ParsingError({ L"Source text is a one-liner, but at least one plural form translation consists of multiple lines", scn_.posRow(), scn_.posCol() }); + + //if source contains ampersand to mark menu accellerator key, so must translation + const size_t ampCount = ampersandTokenCount(original.first); + for (const std::string& str : allTexts) + if (ampCount > 1 || ampersandTokenCount(str) != ampCount) + throw ParsingError({ L"Source and translation both need exactly one & character to mark a menu item access key or none at all", scn_.posRow(), scn_.posCol() }); + + //ampersand at the end makes buggy wxWidgets crash miserably + for (const std::string& str : allTexts) + if (endsWithSingleAmp(str)) + throw ParsingError({ L"The & character to mark a menu item access key must not occur at the end of a string", scn_.posRow(), scn_.posCol() }); + + //if source ends with colon, so must translation (note: character seems to be universally used, even for asian and arabic languages) + if (endsWith(original.first, ':') || endsWith(original.second, ':')) + for (const std::string& str : allTexts) + if (!endsWithColon(str)) + throw ParsingError({ L"Source text ends with a colon character \":\", but translation does not", scn_.posRow(), scn_.posCol() }); + + //if source ends with a period, so must translation (note: character seems to be universally used, even for asian and arabic languages) + if (endsWithSingleDot(original.first) || endsWithSingleDot(original.second)) + for (const std::string& str : allTexts) + if (!endsWithSingleDot(str)) + throw ParsingError({ L"Source text ends with a punctuation mark character \".\", but translation does not", scn_.posRow(), scn_.posCol() }); + + //if source ends with an ellipsis, so must translation (note: character seems to be universally used, even for asian and arabic languages) + if (endsWithEllipsis(original.first) || endsWithEllipsis(original.second)) + for (const std::string& str : allTexts) + if (!endsWithEllipsis(str)) + throw ParsingError({ L"Source text ends with an ellipsis \"...\", but translation does not", scn_.posRow(), scn_.posCol() }); + + //check for not-to-be-translated texts + for (const char* fixedStr : { "FreeFileSync", "RealTimeSync", "ffs_gui", "ffs_batch", "ffs_tmp", "GlobalSettings.xml" }) + if (contains(original.first, fixedStr) || contains(original.second, fixedStr)) + for (const std::string& str : allTexts) + if (!contains(str, fixedStr)) + throw ParsingError({ replaceCpy(L"Misspelled \"%x\" in translation", L"%x", utfTo(fixedStr)), scn_.posRow(), scn_.posCol() }); + + //some languages (French!) put a space before punctuation mark => must be a no-brake space! + for (const char punctChar : std::string(".!?:;$#")) + for (const std::string& str : allTexts) + if (contains(str, std::string(" ") + punctChar)) + throw ParsingError({ replaceCpy(L"Text contains a space before the \"%x\" character. Are line-breaks really allowed here?" + " Maybe this should be a \"non-breaking space\" (Windows: Alt 0160 UTF8: 0xC2 0xA0)?", + L"%x", utfTo(punctChar)), scn_.posRow(), scn_.posCol() }); + } + } + + //helper + static size_t ampersandTokenCount(const std::string& str) + { + using namespace zen; + const std::string tmp = replaceCpy(str, "&&", ""); //make sure to not catch && which windows resolves as just one & for display! + return std::count(tmp.begin(), tmp.end(), '&'); + } + + static bool endsWithSingleAmp(const std::string& s) + { + using namespace zen; + return endsWith(s, "&") && !endsWith(s, "&&"); + } + + static bool endsWithEllipsis(const std::string& s) + { + using namespace zen; + return endsWith(s, "...") || + endsWith(s, "\xe2\x80\xa6"); //narrow ellipsis (spanish?) + } + + static bool endsWithColon(const std::string& s) + { + using namespace zen; + return endsWith(s, ':') || + endsWith(s, "\xef\xbc\x9a"); //chinese colon + } + + static bool endsWithSingleDot(const std::string& s) + { + using namespace zen; + return (endsWith(s, ".") || + endsWith(s, "\xe0\xa5\xa4") || //hindi period + endsWith(s, "\xe3\x80\x82")) //chinese period + && + (!endsWith(s, "..") && + !endsWith(s, "\xe0\xa5\xa4\xe0\xa5\xa4") && //hindi period + !endsWith(s, "\xe3\x80\x82\xe3\x80\x82")); //chinese period + } + + + const Token& token() const { return tk_; } + + void nextToken() { tk_ = scn_.getNextToken(); } + + void expectToken(Token::Type t) //throw ParsingError + { + if (token().type != t) + throw ParsingError({ L"Unexpected token", scn_.posRow(), scn_.posCol() }); + } + + void consumeToken(Token::Type t) //throw ParsingError + { + expectToken(t); //throw ParsingError + nextToken(); + } + + Scanner scn_; + Token tk_; +}; + + +inline +void parseLng(const std::string& fileStream, TransHeader& header, TranslationMap& out, TranslationPluralMap& pluralOut) //throw ParsingError +{ + out.clear(); + pluralOut.clear(); + + LngParser(fileStream).parse(out, pluralOut, header); +} + + +inline +TransHeader parseHeader(const std::string& fileStream) //throw ParsingError +{ + TransHeader header; + LngParser(fileStream).parseHeader(header); + return header; +} + + +inline +void formatMultiLineText(std::string& text) +{ + assert(!zen::contains(text, "\r\n")); + + if (zen::contains(text, '\n')) //multiple lines + { + if (*text.begin() != '\n') + text = '\n' + text; + if (*text.rbegin() != '\n') + text += '\n'; + } +} + + +std::string generateLng(const TranslationUnorderedList& in, const TransHeader& header) +{ + const KnownTokens tokens; //no need for static non-POD! + + std::string out; + //header + out += tokens.text(Token::TK_HEADER_BEGIN) + '\n'; + + out += '\t' + tokens.text(Token::TK_LANG_NAME_BEGIN); + out += header.languageName; + out += tokens.text(Token::TK_LANG_NAME_END) + '\n'; + + out += '\t' + tokens.text(Token::TK_TRANS_NAME_BEGIN); + out += header.translatorName; + out += tokens.text(Token::TK_TRANS_NAME_END) + '\n'; + + out += '\t' + tokens.text(Token::TK_LOCALE_NAME_BEGIN); + out += header.localeName; + out += tokens.text(Token::TK_LOCALE_NAME_END) + '\n'; + + out += '\t' + tokens.text(Token::TK_FLAG_FILE_BEGIN); + out += header.flagFile; + out += tokens.text(Token::TK_FLAG_FILE_END) + '\n'; + + out += '\t' + tokens.text(Token::TK_PLURAL_COUNT_BEGIN); + out += zen::numberTo(header.pluralCount); + out += tokens.text(Token::TK_PLURAL_COUNT_END) + '\n'; + + out += '\t' + tokens.text(Token::TK_PLURAL_DEF_BEGIN); + out += header.pluralDefinition; + out += tokens.text(Token::TK_PLURAL_DEF_END) + '\n'; + + out += tokens.text(Token::TK_HEADER_END) + '\n'; + + out += '\n'; + + + in.visitItems([&](const TranslationMap::value_type& trans) + { + std::string original = trans.first; + std::string translation = trans.second; + + formatMultiLineText(original); + formatMultiLineText(translation); + + out += tokens.text(Token::TK_SRC_BEGIN); + out += original; + out += tokens.text(Token::TK_SRC_END) + '\n'; + + out += tokens.text(Token::TK_TRG_BEGIN); + out += translation; + out += tokens.text(Token::TK_TRG_END) + '\n' + '\n'; + }, + [&](const TranslationPluralMap::value_type& transPlural) + { + std::string engSingular = transPlural.first.first; + std::string engPlural = transPlural.first.second; + const PluralForms& forms = transPlural.second; + + formatMultiLineText(engSingular); + formatMultiLineText(engPlural); + + out += tokens.text(Token::TK_SRC_BEGIN) + '\n'; + out += tokens.text(Token::TK_PLURAL_BEGIN); + out += engSingular; + out += tokens.text(Token::TK_PLURAL_END) + '\n'; + out += tokens.text(Token::TK_PLURAL_BEGIN); + out += engPlural; + out += tokens.text(Token::TK_PLURAL_END) + '\n'; + out += tokens.text(Token::TK_SRC_END) + '\n'; + + out += tokens.text(Token::TK_TRG_BEGIN); + if (!forms.empty()) //translators will be searching for "" + out += '\n'; + for (std::string plForm : forms) + { + formatMultiLineText(plForm); + + out += tokens.text(Token::TK_PLURAL_BEGIN); + out += plForm; + out += tokens.text(Token::TK_PLURAL_END) + '\n'; + } + out += tokens.text(Token::TK_TRG_END) + '\n' + '\n'; + }); + + assert(!zen::contains(out, "\r\n") && !zen::contains(out, "\r")); + return zen::replaceCpy(out, '\n', "\r\n"); //back to win line endings +} +} + +#endif //PARSE_LNG_H_46794693622675638 diff --git a/FreeFileSync/Source/parse_plural.h b/FreeFileSync/Source/parse_plural.h new file mode 100644 index 00000000..242dd4a6 --- /dev/null +++ b/FreeFileSync/Source/parse_plural.h @@ -0,0 +1,482 @@ +// ***************************************************************************** +// * 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 PARSE_PLURAL_H_180465845670839576 +#define PARSE_PLURAL_H_180465845670839576 + +#include +#include +#include +#include + + +namespace plural +{ +//expression interface +struct Expression { virtual ~Expression() {} }; + +template +struct Expr : public Expression +{ + virtual T eval() const = 0; +}; + + +class ParsingError {}; + +class PluralForm +{ +public: + PluralForm(const std::string& stream); //throw ParsingError + int getForm(int64_t n) const { n_ = std::abs(n) ; return static_cast(expr_->eval()); } + +private: + std::shared_ptr> expr_; + mutable int64_t n_ = 0; +}; + + +//validate plural form +class InvalidPluralForm {}; + +class PluralFormInfo +{ +public: + PluralFormInfo(const std::string& definition, int pluralCount); //throw InvalidPluralForm + + int getCount() const { return static_cast(forms_.size()); } + bool isSingleNumberForm(int index) const { return 0 <= index && index < static_cast(forms_.size()) ? forms_[index].count == 1 : false; } + int getFirstNumber (int index) const { return 0 <= index && index < static_cast(forms_.size()) ? forms_[index].firstNumber : -1; } + +private: + struct FormInfo + { + int count = 0; + int firstNumber = 0; //which maps to the plural form index position + }; + std::vector forms_; +}; + + + + + +//--------------------------- implementation --------------------------- + +//https://www.gnu.org/software/hello/manual/gettext/Plural-forms.html +//http://translate.sourceforge.net/wiki/l10n/pluralforms +/* +Grammar for Plural forms parser +------------------------------- +expression: + conditional-expression + +conditional-expression: + logical-or-expression + logical-or-expression ? expression : expression + +logical-or-expression: + logical-and-expression + logical-or-expression || logical-and-expression + +logical-and-expression: + equality-expression + logical-and-expression && equality-expression + +equality-expression: + relational-expression + relational-expression == relational-expression + relational-expression != relational-expression + +relational-expression: + multiplicative-expression + multiplicative-expression > multiplicative-expression + multiplicative-expression < multiplicative-expression + multiplicative-expression >= multiplicative-expression + multiplicative-expression <= multiplicative-expression + +multiplicative-expression: + pm-expression + multiplicative-expression % pm-expression + +pm-expression: + variable-number-n-expression + constant-number-expression + ( expression ) + + +.po format,e.g.: (n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2) +*/ + +namespace impl +{ +template +struct BinaryExp : public Expr +{ + using ExpLhs = std::shared_ptr>; + using ExpRhs = std::shared_ptr>; + + BinaryExp(const ExpLhs& lhs, const ExpRhs& rhs) : lhs_(lhs), rhs_(rhs) { assert(lhs && rhs); } + ResultType eval() const override { return BinaryOp()(lhs_->eval(), rhs_->eval()); } +private: + ExpLhs lhs_; + ExpRhs rhs_; +}; + + +template inline +std::shared_ptr makeBiExp(const std::shared_ptr& lhs, const std::shared_ptr& rhs) //throw ParsingError +{ + auto exLeft = std::dynamic_pointer_cast>(lhs); + auto exRight = std::dynamic_pointer_cast>(rhs); + if (!exLeft || !exRight) + throw ParsingError(); + + using ResultType = decltype(BinaryOp()(std::declval(), std::declval())); + return std::make_shared>(exLeft, exRight); +} + + +template +struct ConditionalExp : public Expr +{ + ConditionalExp(const std::shared_ptr>& ifExp, + const std::shared_ptr>& thenExp, + const std::shared_ptr>& elseExp) : ifExp_(ifExp), thenExp_(thenExp), elseExp_(elseExp) { assert(ifExp && thenExp && elseExp); } + + T eval() const override { return ifExp_->eval() ? thenExp_->eval() : elseExp_->eval(); } +private: + std::shared_ptr> ifExp_; + std::shared_ptr> thenExp_; + std::shared_ptr> elseExp_; +}; + + +struct ConstNumberExp : public Expr +{ + ConstNumberExp(int64_t n) : n_(n) {} + int64_t eval() const override { return n_; } +private: + int64_t n_; +}; + + +struct VariableNumberNExp : public Expr +{ + VariableNumberNExp(int64_t& n) : n_(n) {} + int64_t eval() const override { return n_; } +private: + int64_t& n_; +}; + +//------------------------------------------------------------------------------- + +struct Token +{ + enum Type + { + TK_TERNARY_QUEST, + TK_TERNARY_COLON, + TK_OR, + TK_AND, + TK_EQUAL, + TK_NOT_EQUAL, + TK_LESS, + TK_LESS_EQUAL, + TK_GREATER, + TK_GREATER_EQUAL, + TK_MODULUS, + TK_VARIABLE_N, + TK_CONST_NUMBER, + TK_BRACKET_LEFT, + TK_BRACKET_RIGHT, + TK_END + }; + + Token(Type t) : type(t) {} + Token(int64_t num) : number(num) {} + + Type type = TK_CONST_NUMBER; + int64_t number = 0; //if type == TK_CONST_NUMBER +}; + +class Scanner +{ +public: + Scanner(const std::string& stream) : stream_(stream), pos_(stream_.begin()) {} + + Token getNextToken() //throw ParsingError + { + //skip whitespace + pos_ = std::find_if_not(pos_, stream_.end(), zen::isWhiteSpace); + + if (pos_ == stream_.end()) + return Token::TK_END; + + for (const auto& [tokenString, tokenEnum] : tokens_) + if (startsWith(tokenString)) + { + pos_ += tokenString.size(); + return Token(tokenEnum); + } + + auto digitEnd = std::find_if_not(pos_, stream_.end(), zen::isDigit); + if (pos_ == digitEnd) + throw ParsingError(); //unknown token + + auto number = zen::stringTo(std::string(pos_, digitEnd)); + pos_ = digitEnd; + return number; + } + +private: + bool startsWith(const std::string& prefix) const + { + return zen::startsWith(zen::makeStringView(pos_, stream_.end()), prefix); + } + + using TokenList = std::vector>; + const TokenList tokens_ + { + { "?", Token::TK_TERNARY_QUEST }, + { ":", Token::TK_TERNARY_COLON }, + { "||", Token::TK_OR }, + { "&&", Token::TK_AND }, + { "==", Token::TK_EQUAL }, + { "!=", Token::TK_NOT_EQUAL }, + { "<=", Token::TK_LESS_EQUAL }, + { "<", Token::TK_LESS }, + { ">=", Token::TK_GREATER_EQUAL }, + { ">", Token::TK_GREATER }, + { "%", Token::TK_MODULUS }, + { "n", Token::TK_VARIABLE_N }, + { "N", Token::TK_VARIABLE_N }, + { "(", Token::TK_BRACKET_LEFT }, + { ")", Token::TK_BRACKET_RIGHT }, + }; + + const std::string stream_; + std::string::const_iterator pos_; +}; + +//------------------------------------------------------------------------------- + +class Parser +{ +public: + Parser(const std::string& stream, int64_t& n) : + scn_(stream), + tk_(scn_.getNextToken()), //throw ParsingError + n_(n) {} + + std::shared_ptr> parse() //throw ParsingError; return value always bound! + { + auto e = std::dynamic_pointer_cast>(parseExpression()); //throw ParsingError + if (!e) + throw ParsingError(); + expectToken(Token::TK_END); //throw ParsingError + return e; + } + +private: + std::shared_ptr parseExpression() { return parseConditional(); }//throw ParsingError + + std::shared_ptr parseConditional() //throw ParsingError + { + std::shared_ptr e = parseLogicalOr(); + + if (token().type == Token::TK_TERNARY_QUEST) + { + nextToken(); //throw ParsingError + + auto ifExp = std::dynamic_pointer_cast>(e); + auto thenExp = std::dynamic_pointer_cast>(parseExpression()); //associativity: <- + + consumeToken(Token::TK_TERNARY_COLON); //throw ParsingError + + auto elseExp = std::dynamic_pointer_cast>(parseExpression()); // + if (!ifExp || !thenExp || !elseExp) + throw ParsingError(); + return std::make_shared>(ifExp, thenExp, elseExp); + } + return e; + } + + std::shared_ptr parseLogicalOr() + { + std::shared_ptr e = parseLogicalAnd(); + while (token().type == Token::TK_OR) //associativity: -> + { + nextToken(); //throw ParsingError + + std::shared_ptr rhs = parseLogicalAnd(); + e = makeBiExp, bool>(e, rhs); //throw ParsingError + } + return e; + } + + std::shared_ptr parseLogicalAnd() + { + std::shared_ptr e = parseEquality(); + while (token().type == Token::TK_AND) //associativity: -> + { + nextToken(); //throw ParsingError + std::shared_ptr rhs = parseEquality(); + + e = makeBiExp, bool>(e, rhs); //throw ParsingError + } + return e; + } + + std::shared_ptr parseEquality() + { + std::shared_ptr e = parseRelational(); + + Token::Type t = token().type; + if (t == Token::TK_EQUAL || //associativity: n/a + t == Token::TK_NOT_EQUAL) + { + nextToken(); //throw ParsingError + std::shared_ptr rhs = parseRelational(); + + if (t == Token::TK_EQUAL) return makeBiExp, int64_t>(e, rhs); //throw ParsingError + if (t == Token::TK_NOT_EQUAL) return makeBiExp, int64_t>(e, rhs); // + } + return e; + } + + std::shared_ptr parseRelational() + { + std::shared_ptr e = parseMultiplicative(); + + Token::Type t = token().type; + if (t == Token::TK_LESS || //associativity: n/a + t == Token::TK_LESS_EQUAL || + t == Token::TK_GREATER || + t == Token::TK_GREATER_EQUAL) + { + nextToken(); //throw ParsingError + std::shared_ptr rhs = parseMultiplicative(); + + if (t == Token::TK_LESS) return makeBiExp, int64_t>(e, rhs); // + if (t == Token::TK_LESS_EQUAL) return makeBiExp, int64_t>(e, rhs); //throw ParsingError + if (t == Token::TK_GREATER) return makeBiExp, int64_t>(e, rhs); // + if (t == Token::TK_GREATER_EQUAL) return makeBiExp, int64_t>(e, rhs); // + } + return e; + } + + std::shared_ptr parseMultiplicative() + { + std::shared_ptr e = parsePrimary(); + + while (token().type == Token::TK_MODULUS) //associativity: -> + { + nextToken(); //throw ParsingError + std::shared_ptr rhs = parsePrimary(); + + //"compile-time" check: n % 0 + if (auto literal = std::dynamic_pointer_cast(rhs)) + if (literal->eval() == 0) + throw ParsingError(); + + e = makeBiExp, int64_t>(e, rhs); //throw ParsingError + } + return e; + } + + std::shared_ptr parsePrimary() + { + if (token().type == Token::TK_VARIABLE_N) + { + nextToken(); //throw ParsingError + return std::make_shared(n_); + } + else if (token().type == Token::TK_CONST_NUMBER) + { + const int64_t number = token().number; + nextToken(); //throw ParsingError + return std::make_shared(number); + } + else if (token().type == Token::TK_BRACKET_LEFT) + { + nextToken(); //throw ParsingError + std::shared_ptr e = parseExpression(); + + expectToken(Token::TK_BRACKET_RIGHT); //throw ParsingError + nextToken(); // + return e; + } + else + throw ParsingError(); + } + + const Token& token() const { return tk_; } + + void nextToken() { tk_ = scn_.getNextToken(); } //throw ParsingError + + void expectToken(Token::Type t) //throw ParsingError + { + if (token().type != t) + throw ParsingError(); + } + + void consumeToken(Token::Type t) //throw ParsingError + { + expectToken(t); //throw ParsingError + nextToken(); + } + + Scanner scn_; + Token tk_; + int64_t& n_; +}; +} + + +inline +PluralFormInfo::PluralFormInfo(const std::string& definition, int pluralCount) //throw InvalidPluralForm +{ + if (pluralCount < 1) + throw InvalidPluralForm(); + + forms_.resize(pluralCount); + try + { + PluralForm pf(definition); //throw ParsingError + //PERF_START + + //perf: 80ns per iteration max (for arabic) + //=> 1000 iterations should be fast enough and still detect all "single number forms" + for (int j = 0; j < 1000; ++j) + { + const int form = pf.getForm(j); + if (0 <= form && form < static_cast(forms_.size())) + { + if (forms_[form].count == 0) + forms_[form].firstNumber = j; + ++forms_[form].count; + } + else + throw InvalidPluralForm(); + } + } + catch (const plural::ParsingError&) + { + throw InvalidPluralForm(); + } + + //ensure each form is used at least once: + if (!std::all_of(forms_.begin(), forms_.end(), [](const FormInfo& fi) { return fi.count >= 1; })) + throw InvalidPluralForm(); +} + + +inline +PluralForm::PluralForm(const std::string& stream) : expr_(impl::Parser(stream, n_).parse()) {} //throw ParsingError +} + +#endif //PARSE_PLURAL_H_180465845670839576 diff --git a/FreeFileSync/Source/perf_check.cpp b/FreeFileSync/Source/perf_check.cpp new file mode 100644 index 00000000..9493a8a2 --- /dev/null +++ b/FreeFileSync/Source/perf_check.cpp @@ -0,0 +1,218 @@ +// ***************************************************************************** +// * 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 "perf_check.h" +#include +#include +#include + +using namespace zen; +using namespace fff; + + +PerfCheck::PerfCheck(std::chrono::milliseconds windowSizeRemTime, + std::chrono::milliseconds windowSizeSpeed) : + windowSizeRemTime_(windowSizeRemTime), + windowSizeSpeed_ (windowSizeSpeed), + windowMax_(std::max(windowSizeRemTime, windowSizeSpeed)) {} + + +void PerfCheck::addSample(std::chrono::nanoseconds timeElapsed, int itemsCurrent, double bytesCurrent) +{ + samples_.insert(samples_.end(), { timeElapsed, { itemsCurrent, bytesCurrent }}); //use fact that time is monotonously ascending + + //remove all records earlier than "now - windowMax" + auto it = samples_.upper_bound(timeElapsed - windowMax_); + if (it != samples_.begin()) + samples_.erase(samples_.begin(), --it); //keep one point before newBegin in order to handle "measurement holes" +} + + +std::tuple PerfCheck::getBlockDeltas(std::chrono::milliseconds windowSize) const +{ + if (samples_.empty()) return {}; + + auto itBack = samples_.rbegin(); + //find start of records "window" + auto itFront = samples_.upper_bound(itBack->first - windowSize); + if (itFront != samples_.begin()) + --itFront; //one point before window begin in order to handle "measurement holes" + + const double timeDelta = std::chrono::duration(itBack->first - itFront->first).count(); + const int itemsDelta = itBack->second.items - itFront->second.items; + const double bytesDelta = itBack->second.bytes - itFront->second.bytes; + + return { timeDelta, itemsDelta, bytesDelta }; +} + + +std::optional PerfCheck::getRemainingTimeSec(double bytesRemaining) const +{ + const auto [timeDelta, itemsDelta, bytesDelta] = getBlockDeltas(windowSizeRemTime_); + + //objects model logical operations *NOT* disk accesses, so we better play safe and use "bytes" only! + + if (!numeric::isNull(bytesDelta)) //sign(dataRemaining) != sign(bytesDelta) usually an error, so show it! + return bytesRemaining * timeDelta / bytesDelta; + + return {}; +} + + +std::optional PerfCheck::getBytesPerSecond() const +{ + const auto [timeDelta, itemsDelta, bytesDelta] = getBlockDeltas(windowSizeSpeed_); + + if (!numeric::isNull(timeDelta)) + return replaceCpy(_("%x/sec"), L"%x", formatFilesizeShort(numeric::round(bytesDelta / timeDelta))); + + return {}; +} + + +std::optional PerfCheck::getItemsPerSecond() const +{ + const auto [timeDelta, itemsDelta, bytesDelta] = getBlockDeltas(windowSizeSpeed_); + + if (!numeric::isNull(timeDelta)) + return replaceCpy(_("%x/sec"), L"%x", replaceCpy(_("%x items"), L"%x", formatTwoDigitPrecision(itemsDelta / timeDelta))); + + return {}; +} + + +/* +class for calculation of remaining time: +---------------------------------------- +"filesize |-> time" is an affine linear function f(x) = z_1 + z_2 x + +For given n measurements, sizes x_0, ..., x_n and times f_0, ..., f_n, the function f (as a polynom of degree 1) can be lineary approximated by + +z_1 = (r - s * q / p) / ((n + 1) - s * s / p) +z_2 = (q - s * z_1) / p = (r - (n + 1) z_1) / s + +with +p := x_0^2 + ... + x_n^2 +q := f_0 x_0 + ... + f_n x_n +r := f_0 + ... + f_n +s := x_0 + ... + x_n + +=> the time to process N files with amount of data D is: N * z_1 + D * z_2 + +Problem: +-------- +Times f_0, ..., f_n can be very small so that precision of the PC clock is poor. +=> Times have to be accumulated to enhance precision: +Copying of m files with sizes x_i and times f_i (i = 1, ..., m) takes sum_i f(x_i) := m * z_1 + z_2 * sum x_i = sum f_i +With X defined as the accumulated sizes and F the accumulated times this gives: (in theory...) +m * z_1 + z_2 * X = F <=> +z_1 + z_2 * X / m = F / m + +=> we obtain a new (artificial) measurement with size X / m and time F / m to be used in the linear approximation above + + +Statistics::Statistics(int totalObjectCount, double totalDataAmount, unsigned recordCount) : + itemsTotal(totalObjectCount), + bytesTotal(totalDataAmount), + recordsMax(recordCount), + objectsLast(0), + dataLast(0), + timeLast(wxGetLocalTimeMillis()), + z1_current(0), + z2_current(0), + dummyRecordPresent(false) {} + + +wxString Statistics::getRemainingTime(int objectsCurrent, double dataCurrent) +{ + //add new measurement point + const int m = objectsCurrent - objectsLast; + if (m != 0) + { + objectsLast = objectsCurrent; + + const double X = dataCurrent - dataLast; + dataLast = dataCurrent; + + const int64_t timeCurrent = wxGetLocalTimeMillis(); + const double F = (timeCurrent - timeLast).ToDouble(); + timeLast = timeCurrent; + + record newEntry; + newEntry.x_i = X / m; + newEntry.f_i = F / m; + + //remove dummy record + if (dummyRecordPresent) + { + measurements.pop_back(); + dummyRecordPresent = false; + } + + //insert new record + measurements.push_back(newEntry); + if (measurements.size() > recordsMax) + measurements.pop_front(); + } + else //dataCurrent increased without processing new objects: + { //modify last measurement until m != 0 + const double X = dataCurrent - dataLast; //do not set dataLast, timeLast variables here, but write dummy record instead + if (!isNull(X)) + { + const int64_t timeCurrent = wxGetLocalTimeMillis(); + const double F = (timeCurrent - timeLast).ToDouble(); + + record modifyEntry; + modifyEntry.x_i = X; + modifyEntry.f_i = F; + + //insert dummy record + if (!dummyRecordPresent) + { + measurements.push_back(modifyEntry); + if (measurements.size() > recordsMax) + measurements.pop_front(); + dummyRecordPresent = true; + } + else //modify dummy record + measurements.back() = modifyEntry; + } + } + + //calculate remaining time based on stored measurement points + double p = 0; + double q = 0; + double r = 0; + double s = 0; + for (const record& rec : measurements) + { + const double x_i = rec.x_i; + const double f_i = rec.f_i; + p += x_i * x_i; + q += f_i * x_i; + r += f_i; + s += x_i; + } + + if (!isNull(p)) + { + const double n = measurements.size(); + const double tmp = (n - s * s / p); + + if (!isNull(tmp) && !isNull(s)) + { + const double z1 = (r - s * q / p) / tmp; + const double z2 = (r - n * z1) / s; //not (n + 1) here, since n already is the number of measurements + + //refresh current values for z1, z2 + z1_current = z1; + z2_current = z2; + } + } + + return formatRemainingTime((itemsTotal - objectsCurrent) * z1_current + (bytesTotal - dataCurrent) * z2_current); +} +*/ diff --git a/FreeFileSync/Source/perf_check.h b/FreeFileSync/Source/perf_check.h new file mode 100644 index 00000000..2e9ccc6d --- /dev/null +++ b/FreeFileSync/Source/perf_check.h @@ -0,0 +1,47 @@ +// ***************************************************************************** +// * 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 PERF_CHECK_H_87804217589312454 +#define PERF_CHECK_H_87804217589312454 + +#include +#include +#include +#include + + +namespace fff +{ +class PerfCheck +{ +public: + PerfCheck(std::chrono::milliseconds windowSizeRemTime, + std::chrono::milliseconds windowSizeSpeed); + + void addSample(std::chrono::nanoseconds timeElapsed, int itemsCurrent, double bytesCurrent); + + std::optional getRemainingTimeSec(double bytesRemaining) const; + std::optional getBytesPerSecond() const; //for window + std::optional getItemsPerSecond() const; // + +private: + struct Record + { + int items = 0; + double bytes = 0; + }; + + std::tuple getBlockDeltas(std::chrono::milliseconds windowSize) const; + + std::chrono::milliseconds windowSizeRemTime_; + std::chrono::milliseconds windowSizeSpeed_; + std::chrono::milliseconds windowMax_; + + std::map samples_; +}; +} + +#endif //PERF_CHECK_H_87804217589312454 diff --git a/FreeFileSync/Source/return_codes.h b/FreeFileSync/Source/return_codes.h new file mode 100644 index 00000000..ad9318c6 --- /dev/null +++ b/FreeFileSync/Source/return_codes.h @@ -0,0 +1,80 @@ +// ***************************************************************************** +// * 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 RETURN_CODES_H_81307482137054156 +#define RETURN_CODES_H_81307482137054156 + +#include + + +namespace fff +{ +enum FfsReturnCode //as returned after process exit +{ + FFS_RC_SUCCESS = 0, + FFS_RC_WARNING, + FFS_RC_ERROR, + FFS_RC_ABORTED, + FFS_RC_EXCEPTION, +}; + + +inline +void raiseReturnCode(FfsReturnCode& rc, FfsReturnCode rcProposed) +{ + if (rc < rcProposed) + rc = rcProposed; +} + + +enum class SyncResult +{ + finishedSuccess, + finishedWarning, + finishedError, + aborted, +}; + + +inline +FfsReturnCode mapToReturnCode(SyncResult syncStatus) +{ + switch (syncStatus) + { + case SyncResult::finishedSuccess: + return FFS_RC_SUCCESS; + case SyncResult::finishedWarning: + return FFS_RC_WARNING; + case SyncResult::finishedError: + return FFS_RC_ERROR; + case SyncResult::aborted: + return FFS_RC_ABORTED; + } + assert(false); + return FFS_RC_ABORTED; +} + + +inline +std::wstring getSyncResultLabel(SyncResult resultStatus) +{ + switch (resultStatus) + { + case SyncResult::finishedSuccess: + return _("Completed successfully"); + case SyncResult::finishedWarning: + return _("Completed with warnings"); + case SyncResult::finishedError: + return _("Completed with errors"); + case SyncResult::aborted: + return _("Stopped"); + } + assert(false); + return std::wstring(); +} +} + +#endif //RETURN_CODES_H_81307482137054156 diff --git a/FreeFileSync/Source/status_handler.cpp b/FreeFileSync/Source/status_handler.cpp new file mode 100644 index 00000000..19d1e882 --- /dev/null +++ b/FreeFileSync/Source/status_handler.cpp @@ -0,0 +1,28 @@ +// ***************************************************************************** +// * 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 "status_handler.h" +#include +#include + + +namespace +{ +std::chrono::steady_clock::time_point lastExec; +} + + +bool fff::uiUpdateDue(bool force) +{ + const auto now = std::chrono::steady_clock::now(); + + if (now >= lastExec + UI_UPDATE_INTERVAL || force) + { + lastExec = now; + return true; + } + return false; +} diff --git a/FreeFileSync/Source/status_handler.h b/FreeFileSync/Source/status_handler.h new file mode 100644 index 00000000..9058033b --- /dev/null +++ b/FreeFileSync/Source/status_handler.h @@ -0,0 +1,192 @@ +// ***************************************************************************** +// * 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_H_81704805908341534 +#define STATUS_HANDLER_H_81704805908341534 + +#include +#include +#include +#include +#include +#include +#include "base/process_callback.h" +#include "return_codes.h" + + +namespace fff +{ +bool uiUpdateDue(bool force = false); //test if a specific amount of time is over + +/* +Updating GUI is fast! + time per single call to ProcessCallback::forceUiRefresh() + - Comparison 0.025 ms + - Synchronization 0.74 ms (despite complex graph control!) +*/ + +//Exception class used to abort the "compare" and "sync" process +class AbortProcess {}; + + +enum class AbortTrigger +{ + user, + program, +}; + +//GUI may want to abort process +struct AbortCallback +{ + virtual ~AbortCallback() {} + virtual void userRequestAbort() = 0; +}; + + +struct ProgressStats +{ + int items = 0; + int64_t bytes = 0; +}; +inline bool operator==(const ProgressStats& lhs, const ProgressStats& rhs) { return lhs.items == rhs.items && lhs.bytes == rhs.bytes; } + + +//common statistics "everybody" needs +struct Statistics +{ + virtual ~Statistics() {} + + virtual ProcessPhase currentPhase() const = 0; + + virtual ProgressStats getStatsCurrent() const = 0; + virtual ProgressStats getStatsTotal () const = 0; + + virtual std::optional getAbortStatus() const = 0; + virtual const std::wstring& currentStatusText() const = 0; +}; + + +struct ProcessSummary +{ + std::chrono::system_clock::time_point startTime; + SyncResult resultStatus = SyncResult::aborted; + std::vector jobNames; //may be empty + ProgressStats statsProcessed; + ProgressStats statsTotal; + std::chrono::milliseconds totalTime{}; +}; + + +//partial callback implementation with common functionality for "batch", "GUI/Compare" and "GUI/Sync" +class StatusHandler : public ProcessCallback, public AbortCallback, public Statistics +{ +public: + //StatusHandler() {} + + //implement parts of ProcessCallback + void initNewPhase(int itemsTotal, int64_t bytesTotal, ProcessPhase phase) override //(throw X) + { + assert((itemsTotal < 0) == (bytesTotal < 0)); + currentPhase_ = phase; + statsCurrent_ = {}; + statsTotal_ = { itemsTotal, bytesTotal }; + } + + void updateDataProcessed(int itemsDelta, int64_t bytesDelta) override { updateData(statsCurrent_, itemsDelta, bytesDelta); } //note: these methods MUST NOT throw in order + void updateDataTotal (int itemsDelta, int64_t bytesDelta) override { updateData(statsTotal_, itemsDelta, bytesDelta); } //to allow usage within destructors! + + void requestUiUpdate(bool force) final //throw AbortProcess + { + if (uiUpdateDue(force)) + { + const bool abortRequestedBefore = static_cast(abortRequested_); + + forceUiUpdateNoThrow(); + + //triggered by userRequestAbort() + // => sufficient to evaluate occasionally when uiUpdateDue()! + // => refresh *before* throwing: support requestUiUpdate() during destruction + if (abortRequested_) + { + if (!abortRequestedBefore) + forceUiUpdateNoThrow(); //just once to immediately show the "Stop requested..." status after user clicks cancel + throw AbortProcess(); + } + } + } + + virtual void forceUiUpdateNoThrow() = 0; //noexcept + + void updateStatus(const std::wstring& msg) final //throw AbortProcess + { + //assert(!msg.empty()); -> possible, e.g. start of parallel scan + statusText_ = msg; //update *before* running operations that can throw + requestUiUpdate(false /*force*/); //throw AbortProcess + } + + [[noreturn]] void abortProcessNow(AbortTrigger trigger) + { + if (!abortRequested_ || trigger == AbortTrigger::user) //AbortTrigger::USER overwrites AbortTrigger::program + abortRequested_ = trigger; + + forceUiUpdateNoThrow(); //flush GUI to show new cancelled state + throw AbortProcess(); + } + + //implement AbortCallback + void userRequestAbort() final + { + abortRequested_ = AbortTrigger::user; //may overwrite AbortTrigger::program + } //called from GUI code: this does NOT call abortProcessNow() immediately, but later when we're out of the C GUI call stack + //=> don't call forceUiUpdateNoThrow() here! + + //implement Statistics + ProcessPhase currentPhase() const final { return currentPhase_; } + + ProgressStats getStatsCurrent() const override { return statsCurrent_; } + ProgressStats getStatsTotal () const override { return statsTotal_; } + + const std::wstring& currentStatusText() const override { return statusText_; } + + std::optional getAbortStatus() const override { return abortRequested_; } + +private: + void updateData(ProgressStats& stats, int itemsDelta, int64_t bytesDelta) + { + assert(stats.items >= 0); + assert(stats.bytes >= 0); + stats.items += itemsDelta; + stats.bytes += bytesDelta; + } + + ProcessPhase currentPhase_ = ProcessPhase::none; + ProgressStats statsCurrent_; + ProgressStats statsTotal_ { -1, -1 }; + std::wstring statusText_; + + std::optional abortRequested_; +}; + +//------------------------------------------------------------------------------------------ + +inline +void delayAndCountDown(const std::wstring& operationName, std::chrono::seconds delay, const std::function& notifyStatus) +{ + assert(notifyStatus && !zen::endsWith(operationName, L".")); + + const auto delayUntil = std::chrono::steady_clock::now() + delay; + for (auto now = std::chrono::steady_clock::now(); now < delayUntil; now = std::chrono::steady_clock::now()) + { + const auto timeMs = std::chrono::duration_cast(delayUntil - now).count(); + if (notifyStatus) + notifyStatus(operationName + L"... " + _P("1 sec", "%x sec", numeric::integerDivideRoundUp(timeMs, 1000))); + + std::this_thread::sleep_for(UI_UPDATE_INTERVAL / 2); + } +} +} + +#endif //STATUS_HANDLER_H_81704805908341534 diff --git a/FreeFileSync/Source/ui/abstract_folder_picker.cpp b/FreeFileSync/Source/ui/abstract_folder_picker.cpp index 4c96e6f7..8f2fe782 100644 --- a/FreeFileSync/Source/ui/abstract_folder_picker.cpp +++ b/FreeFileSync/Source/ui/abstract_folder_picker.cpp @@ -11,7 +11,7 @@ #include #include #include "gui_generated.h" -#include "../base/icon_buffer.h" +#include "../icon_buffer.h" using namespace zen; using namespace fff; @@ -264,7 +264,7 @@ void AbstractFolderPickerDlg::findAndNavigateToExistingPath(const AbstractPath& if (!AFS::getParentPath(folderPath)) return m_staticTextStatus->SetLabel(L""); - m_staticTextStatus->SetLabel(_("Scanning...") + L" " + utfTo(FILE_NAME_SEPARATOR + folderPath.afsPath.value)); //keep it short! + m_staticTextStatus->SetLabel(_("Scanning...") + L' ' + utfTo(FILE_NAME_SEPARATOR + folderPath.afsPath.value)); //keep it short! guiQueue_.processAsync([folderPath]() -> std::optional { diff --git a/FreeFileSync/Source/ui/batch_config.cpp b/FreeFileSync/Source/ui/batch_config.cpp index b158a8ca..51e6eda3 100644 --- a/FreeFileSync/Source/ui/batch_config.cpp +++ b/FreeFileSync/Source/ui/batch_config.cpp @@ -14,7 +14,7 @@ #include #include "gui_generated.h" #include "folder_selector.h" -#include "../base/help_provider.h" +#include "../help_provider.h" using namespace zen; diff --git a/FreeFileSync/Source/ui/batch_config.h b/FreeFileSync/Source/ui/batch_config.h index 7bb406b3..35fb73fa 100644 --- a/FreeFileSync/Source/ui/batch_config.h +++ b/FreeFileSync/Source/ui/batch_config.h @@ -8,7 +8,7 @@ #define BATCH_CONFIG_H_3921674832168945 #include -#include "../base/config.h" +#include "../config.h" namespace fff diff --git a/FreeFileSync/Source/ui/batch_status_handler.cpp b/FreeFileSync/Source/ui/batch_status_handler.cpp index 28f1833c..40098bca 100644 --- a/FreeFileSync/Source/ui/batch_status_handler.cpp +++ b/FreeFileSync/Source/ui/batch_status_handler.cpp @@ -9,9 +9,9 @@ #include #include #include -#include "../base/resolve_path.h" -#include "../base/log_file.h" #include "../afs/concrete.h" +#include "../base/resolve_path.h" +#include "../log_file.h" using namespace zen; using namespace fff; @@ -61,8 +61,9 @@ BatchStatusHandler::~BatchStatusHandler() BatchStatusHandler::Result BatchStatusHandler::reportResults(const Zstring& postSyncCommand, PostSyncCondition postSyncCondition, - const Zstring& altLogFolderPathPhrase, int logfilesMaxAgeDays, const std::set& logFilePathsToKeep, - const Zstring& emailNotifyAddress, ResultsNotification emailNotifyCondition) //noexcept!! + const Zstring& altLogFolderPathPhrase, int logfilesMaxAgeDays, LogFileFormat logFormat, + const std::set& logFilePathsToKeep, + const std::string& emailNotifyAddress, ResultsNotification emailNotifyCondition) //noexcept!! { const auto totalTime = std::chrono::duration_cast(std::chrono::system_clock::now() - startTime_); @@ -76,9 +77,10 @@ BatchStatusHandler::Result BatchStatusHandler::reportResults(const Zstring& post errorLog_.logMsg(_("Stopped"), MSG_TYPE_ERROR); //= user cancel; *not* a MSG_TYPE_FATAL_ERROR! return SyncResult::aborted; } - else if (errorLog_.getItemCount(MSG_TYPE_ERROR | MSG_TYPE_FATAL_ERROR) > 0) + const ErrorLog::Stats logCount = errorLog_.getStats(); + if (logCount.error + logCount.fatal > 0) return SyncResult::finishedError; - else if (errorLog_.getItemCount(MSG_TYPE_WARNING) > 0) + else if (logCount.warning > 0) return SyncResult::finishedWarning; if (getStatsTotal() == ProgressStats()) @@ -96,7 +98,7 @@ BatchStatusHandler::Result BatchStatusHandler::reportResults(const Zstring& post totalTime }; - const AbstractPath logFilePath = generateLogFilePath(summary, altLogFolderPathPhrase); + const AbstractPath logFilePath = generateLogFilePath(logFormat, summary, altLogFolderPathPhrase); //e.g. %AppData%\FreeFileSync\Logs\Backup FreeFileSync 2013-09-15 015052.123 [Error].log if (const Zstring cmdLine = trimCpy(postSyncCommand); @@ -113,9 +115,8 @@ BatchStatusHandler::Result BatchStatusHandler::reportResults(const Zstring& post //::wxSetEnv(L"logfile_path", AFS::getDisplayPath(logFilePath)); ////---------------------------------------------------------------------- const Zstring cmdLineExp = expandMacros(cmdLine); - const int exitCode = shellExecute(cmdLineExp, ExecutionType::sync, false /*hideConsole*/); //throw FileError - errorLog_.logMsg(_("Executing command:") + L" " + utfTo(cmdLineExp) + L" [" + replaceCpy(_("Exit Code %x"), L"%x", numberTo(exitCode)) + L']', - exitCode == 0 ? MSG_TYPE_INFO : MSG_TYPE_ERROR); + errorLog_.logMsg(_("Executing command:") + L' ' + utfTo(cmdLineExp), MSG_TYPE_INFO); + shellExecute(cmdLineExp, ExecutionType::async, false /*hideConsole*/); //throw FileError } catch (const FileError& e) { errorLog_.logMsg(e.toString(), MSG_TYPE_ERROR); } } @@ -123,7 +124,7 @@ BatchStatusHandler::Result BatchStatusHandler::reportResults(const Zstring& post //---------------------------- save log file ------------------------------ auto notifyStatusNoThrow = [&](const std::wstring& msg) { try { updateStatus(msg); /*throw AbortProcess*/ } catch (AbortProcess&) {} }; - if (const Zstring notifyEmail = trimCpy(emailNotifyAddress); + if (const std::string notifyEmail = trimCpy(emailNotifyAddress); !notifyEmail.empty()) { if (getAbortStatus() && *getAbortStatus() == AbortTrigger::user) @@ -144,7 +145,7 @@ BatchStatusHandler::Result BatchStatusHandler::reportResults(const Zstring& post try //create not before destruction: 1. avoid issues with FFS trying to sync open log file 2. include status in log file name without extra rename { //do NOT use tryReportingError()! saving log files should not be cancellable! - saveLogFile(logFilePath, summary, errorLog_, logfilesMaxAgeDays, logFilePathsToKeep, notifyStatusNoThrow); //throw FileError + saveLogFile(logFilePath, summary, errorLog_, logfilesMaxAgeDays, logFormat, logFilePathsToKeep, notifyStatusNoThrow); //throw FileError } catch (const FileError& e) { errorLog_.logMsg(e.toString(), MSG_TYPE_ERROR); } @@ -299,7 +300,7 @@ ProcessCallback::Response BatchStatusHandler::reportError(const std::wstring& ms if (retryNumber < automaticRetryCount_) { errorLog_.logMsg(msg + L"\n-> " + _("Automatic retry"), MSG_TYPE_INFO); - delayAndCountDown(_("Automatic retry") + (automaticRetryCount_ <= 1 ? L"" : L" " + numberTo(retryNumber + 1) + L"/" + numberTo(automaticRetryCount_)), + delayAndCountDown(_("Automatic retry") + (automaticRetryCount_ <= 1 ? L"" : L' ' + numberTo(retryNumber + 1) + L"/" + numberTo(automaticRetryCount_)), automaticRetryDelay_, [&](const std::wstring& statusMsg) { this->updateStatus(_("Error") + L": " + statusMsg); }); //throw AbortProcess return ProcessCallback::retry; } diff --git a/FreeFileSync/Source/ui/batch_status_handler.h b/FreeFileSync/Source/ui/batch_status_handler.h index b56a4aed..2e59ac7e 100644 --- a/FreeFileSync/Source/ui/batch_status_handler.h +++ b/FreeFileSync/Source/ui/batch_status_handler.h @@ -10,8 +10,8 @@ #include #include #include "progress_indicator.h" -#include "../base/config.h" -#include "../base/status_handler.h" +#include "../config.h" +#include "../status_handler.h" namespace fff @@ -54,8 +54,8 @@ public: AbstractPath logFilePath; }; Result reportResults(const Zstring& postSyncCommand, PostSyncCondition postSyncCondition, - const Zstring& altLogFolderPathPhrase, int logfilesMaxAgeDays, const std::set& logFilePathsToKeep, - const Zstring& emailNotifyAddress, ResultsNotification emailNotifyCondition); //noexcept!! + const Zstring& altLogFolderPathPhrase, int logfilesMaxAgeDays, LogFileFormat logFormat, const std::set& logFilePathsToKeep, + const std::string& emailNotifyAddress, ResultsNotification emailNotifyCondition); //noexcept!! private: bool switchToGuiRequested_ = false; diff --git a/FreeFileSync/Source/ui/cfg_grid.cpp b/FreeFileSync/Source/ui/cfg_grid.cpp index 7ae27b42..fca67704 100644 --- a/FreeFileSync/Source/ui/cfg_grid.cpp +++ b/FreeFileSync/Source/ui/cfg_grid.cpp @@ -13,8 +13,8 @@ #include #include #include -#include "../base/icon_buffer.h" -#include "../base/ffs_paths.h" +#include "../icon_buffer.h" +#include "../ffs_paths.h" using namespace zen; using namespace fff; @@ -88,13 +88,13 @@ void ConfigView::addCfgFilesImpl(const std::vector& filePaths) std::tie(detail.name, detail.cfgType, detail.isLastRunCfg) = [&] { if (equalNativePath(filePath, lastRunConfigPath_)) - return std::make_tuple(utfTo(L"[" + _("Last session") + L"]"), Details::CFG_TYPE_GUI, true); + return std::make_tuple(utfTo(L'[' + _("Last session") + L']'), Details::CFG_TYPE_GUI, true); const Zstring fileName = afterLast(filePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); - if (endsWithAsciiNoCase(fileName, Zstr(".ffs_gui"))) + if (endsWithAsciiNoCase(fileName, ".ffs_gui")) return std::make_tuple(beforeLast(fileName, Zstr('.'), IF_MISSING_RETURN_NONE), Details::CFG_TYPE_GUI, false); - else if (endsWithAsciiNoCase(fileName, Zstr(".ffs_batch"))) + else if (endsWithAsciiNoCase(fileName, ".ffs_batch")) return std::make_tuple(beforeLast(fileName, Zstr('.'), IF_MISSING_RETURN_NONE), Details::CFG_TYPE_BATCH, false); else return std::make_tuple(fileName, Details::CFG_TYPE_NONE, false); @@ -320,14 +320,14 @@ private: const int daysPast = getDaysPast(item->cfgItem.lastSyncTime); return daysPast == 0 ? _("Today") : _P("1 day", "%x days", daysPast); - //return formatTime(FORMAT_DATE_TIME, getLocalTime(item->lastSyncTime)); + //return formatTime(formatDateTimeTag, getLocalTime(item->lastSyncTime)); } break; case ColumnTypeCfg::lastLog: if (!item->isLastRunCfg && !AFS::isNullPath(item->cfgItem.logFilePath)) - return getResultsStatusLabel(item->cfgItem.logResult); + return getSyncResultLabel(item->cfgItem.logResult); break; } return std::wstring(); @@ -570,7 +570,7 @@ private: if (!item->isLastRunCfg && !AFS::isNullPath(item->cfgItem.logFilePath)) - return getResultsStatusLabel(item->cfgItem.logResult) + SPACED_DASH + AFS::getDisplayPath(item->cfgItem.logFilePath); + return getSyncResultLabel(item->cfgItem.logResult) + SPACED_DASH + AFS::getDisplayPath(item->cfgItem.logFilePath); break; } return std::wstring(); @@ -630,7 +630,7 @@ ConfigView& cfggrid::getDataView(Grid& grid) { if (auto* prov = dynamic_cast(grid.getDataProvider())) return prov->getDataView(); - throw std::runtime_error(std::string(__FILE__) + "[" + numberTo(__LINE__) + "] cfggrid was not initialized."); + throw std::runtime_error(std::string(__FILE__) + '[' + numberTo(__LINE__) + "] cfggrid was not initialized."); } @@ -662,7 +662,7 @@ int cfggrid::getSyncOverdueDays(Grid& grid) { if (auto* prov = dynamic_cast(grid.getDataProvider())) return prov->getSyncOverdueDays(); - throw std::runtime_error(std::string(__FILE__) + "[" + numberTo(__LINE__) + "] cfggrid was not initialized."); + throw std::runtime_error(std::string(__FILE__) + '[' + numberTo(__LINE__) + "] cfggrid was not initialized."); } @@ -670,7 +670,7 @@ void cfggrid::setSyncOverdueDays(Grid& grid, int syncOverdueDays) { auto* prov = dynamic_cast(grid.getDataProvider()); if (!prov) - throw std::runtime_error(std::string(__FILE__) + "[" + numberTo(__LINE__) + "] cfggrid was not initialized."); + throw std::runtime_error(std::string(__FILE__) + '[' + numberTo(__LINE__) + "] cfggrid was not initialized."); prov->setSyncOverdueDays(syncOverdueDays); grid.Refresh(); diff --git a/FreeFileSync/Source/ui/cfg_grid.h b/FreeFileSync/Source/ui/cfg_grid.h index 3ace5ff7..5be3ce71 100644 --- a/FreeFileSync/Source/ui/cfg_grid.h +++ b/FreeFileSync/Source/ui/cfg_grid.h @@ -10,7 +10,7 @@ #include #include #include -#include "../base/return_codes.h" +#include "../return_codes.h" #include "../afs/concrete.h" diff --git a/FreeFileSync/Source/ui/file_grid.cpp b/FreeFileSync/Source/ui/file_grid.cpp index 12ff49f8..93708754 100644 --- a/FreeFileSync/Source/ui/file_grid.cpp +++ b/FreeFileSync/Source/ui/file_grid.cpp @@ -739,15 +739,15 @@ private: visitFSObject(*fsObj, [](const FolderPair& folder) {}, [&](const FilePair& file) { - toolTip += L"\n" + - _("Size:") + L" " + zen::formatFilesizeShort(file.getFileSize()) + L"\n" + - _("Date:") + L" " + zen::formatUtcToLocalTime(file.getLastWriteTime()); + toolTip += L'\n' + + _("Size:") + L' ' + zen::formatFilesizeShort(file.getFileSize()) + L'\n' + + _("Date:") + L' ' + zen::formatUtcToLocalTime(file.getLastWriteTime()); }, [&](const SymlinkPair& symlink) { - toolTip += L"\n" + - _("Date:") + L" " + zen::formatUtcToLocalTime(symlink.getLastWriteTime()); + toolTip += L'\n' + + _("Date:") + L' ' + zen::formatUtcToLocalTime(symlink.getLastWriteTime()); }); } return toolTip; @@ -1607,7 +1607,7 @@ FileView& filegrid::getDataView(Grid& grid) if (auto* prov = dynamic_cast(grid.getDataProvider())) return prov->getDataView(); - throw std::runtime_error("filegrid was not initialized! " + std::string(__FILE__) + ":" + numberTo(__LINE__)); + throw std::runtime_error("filegrid was not initialized! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); } diff --git a/FreeFileSync/Source/ui/file_grid.h b/FreeFileSync/Source/ui/file_grid.h index a416a0d0..2fadb7c8 100644 --- a/FreeFileSync/Source/ui/file_grid.h +++ b/FreeFileSync/Source/ui/file_grid.h @@ -10,7 +10,7 @@ #include #include "file_view.h" #include "file_grid_attr.h" -#include "../base/icon_buffer.h" +#include "../icon_buffer.h" namespace fff diff --git a/FreeFileSync/Source/ui/folder_pair.h b/FreeFileSync/Source/ui/folder_pair.h index 419fc751..c3ee65bb 100644 --- a/FreeFileSync/Source/ui/folder_pair.h +++ b/FreeFileSync/Source/ui/folder_pair.h @@ -17,7 +17,7 @@ #include "small_dlgs.h" #include "sync_cfg.h" #include "../base/norm_filter.h" -#include "../base/structures.h" +#include "../base_tools.h" namespace fff diff --git a/FreeFileSync/Source/ui/folder_selector.cpp b/FreeFileSync/Source/ui/folder_selector.cpp index 9afdfe3c..8e2037ba 100644 --- a/FreeFileSync/Source/ui/folder_selector.cpp +++ b/FreeFileSync/Source/ui/folder_selector.cpp @@ -15,7 +15,7 @@ #include "small_dlgs.h" //includes structures.h, which defines "AFS" #include "../afs/concrete.h" #include "../afs/native.h" -#include "../base/icon_buffer.h" +#include "../icon_buffer.h" @@ -49,6 +49,7 @@ void setFolderPathPhrase(const Zstring& folderPathPhrase, FolderHistoryBox* comb } } + } //############################################################################################################## diff --git a/FreeFileSync/Source/ui/gui_generated.cpp b/FreeFileSync/Source/ui/gui_generated.cpp index 80dd34d5..78092e06 100644 --- a/FreeFileSync/Source/ui/gui_generated.cpp +++ b/FreeFileSync/Source/ui/gui_generated.cpp @@ -619,14 +619,14 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const bSizer42 = new wxBoxSizer( wxHORIZONTAL ); - m_bitmapLogStatus = new wxStaticBitmap( m_panelLog, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), 0 ); - bSizer42->Add( m_bitmapLogStatus, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); + m_bitmapSyncResult = new wxStaticBitmap( m_panelLog, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), 0 ); + bSizer42->Add( m_bitmapSyncResult, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); - m_staticTextLogStatus = new wxStaticText( m_panelLog, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticTextLogStatus->Wrap( -1 ); - m_staticTextLogStatus->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) ); + m_staticTextSyncResult = new wxStaticText( m_panelLog, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticTextSyncResult->Wrap( -1 ); + m_staticTextSyncResult->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) ); - bSizer42->Add( m_staticTextLogStatus, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxRIGHT, 10 ); + bSizer42->Add( m_staticTextSyncResult, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxRIGHT, 10 ); bSizer42->Add( 10, 0, 0, 0, 5 ); @@ -1421,7 +1421,7 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer176->Add( m_radioBtnSymlinksDirect, 0, wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); - bSizer1721->Add( bSizer176, 0, wxLEFT|wxEXPAND, 18 ); + bSizer1721->Add( bSizer176, 0, wxLEFT|wxEXPAND, 15 ); bSizer1721->Add( 0, 0, 1, wxEXPAND, 5 ); @@ -2270,10 +2270,10 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer291 = new wxBoxSizer( wxHORIZONTAL ); m_bitmapEmail = new wxStaticBitmap( m_panelSyncSettings, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); - bSizer291->Add( m_bitmapEmail, 0, wxRIGHT|wxALIGN_CENTER_VERTICAL, 5 ); + bSizer291->Add( m_bitmapEmail, 0, wxALIGN_CENTER_VERTICAL, 5 ); m_checkBoxSendEmail = new wxCheckBox( m_panelSyncSettings, wxID_ANY, _("Send email notification:"), wxDefaultPosition, wxDefaultSize, 0 ); - bSizer291->Add( m_checkBoxSendEmail, 0, wxALIGN_CENTER_VERTICAL, 5 ); + bSizer291->Add( m_checkBoxSendEmail, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 ); bSizer290->Add( bSizer291, 0, 0, 5 ); @@ -2282,22 +2282,22 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer290->Add( m_comboBoxEmail, 0, wxEXPAND|wxTOP, 5 ); - bSizer287->Add( bSizer290, 1, 0, 5 ); + bSizer287->Add( bSizer290, 1, wxRIGHT, 5 ); wxBoxSizer* bSizer289; bSizer289 = new wxBoxSizer( wxVERTICAL ); m_bpButtonEmailAlways = new wxBitmapButton( m_panelSyncSettings, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW|0 ); - bSizer289->Add( m_bpButtonEmailAlways, 0, wxLEFT, 5 ); + bSizer289->Add( m_bpButtonEmailAlways, 0, 0, 5 ); m_bpButtonEmailErrorWarning = new wxBitmapButton( m_panelSyncSettings, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW|0 ); - bSizer289->Add( m_bpButtonEmailErrorWarning, 0, wxLEFT, 5 ); + bSizer289->Add( m_bpButtonEmailErrorWarning, 0, 0, 5 ); m_bpButtonEmailErrorOnly = new wxBitmapButton( m_panelSyncSettings, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW|0 ); - bSizer289->Add( m_bpButtonEmailErrorOnly, 0, wxLEFT, 5 ); + bSizer289->Add( m_bpButtonEmailErrorOnly, 0, 0, 5 ); - bSizer287->Add( bSizer289, 0, 0, 5 ); + bSizer287->Add( bSizer289, 0, wxLEFT, 5 ); bSizer292->Add( bSizer287, 0, wxEXPAND, 5 ); @@ -2328,11 +2328,11 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer279 = new wxBoxSizer( wxHORIZONTAL ); m_bitmapLogFile = new wxStaticBitmap( m_panelLogfile, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); - bSizer279->Add( m_bitmapLogFile, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + bSizer279->Add( m_bitmapLogFile, 0, wxALIGN_CENTER_VERTICAL, 5 ); m_checkBoxOverrideLogPath = new wxCheckBox( m_panelLogfile, wxID_ANY, _("&Override default log path:"), wxDefaultPosition, wxDefaultSize, 0 ); m_checkBoxOverrideLogPath->SetValue(true); - bSizer279->Add( m_checkBoxOverrideLogPath, 1, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); + bSizer279->Add( m_checkBoxOverrideLogPath, 1, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 ); m_buttonSelectLogFolder = new wxButton( m_panelLogfile, wxID_ANY, _("Browse"), wxDefaultPosition, wxDefaultSize, 0 ); m_buttonSelectLogFolder->SetToolTip( _("Select a folder") ); @@ -2443,7 +2443,7 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w m_checkBoxAutoRetry->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnToggleAutoRetry ), NULL, this ); m_hyperlink1711->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( ConfigDlgGenerated::OnHelpPerformance ), NULL, this ); m_textCtrlInclude->Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( ConfigDlgGenerated::OnChangeFilterOption ), NULL, this ); - m_hyperlink171->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( ConfigDlgGenerated::OnHelpShowExamples ), NULL, this ); + m_hyperlink171->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( ConfigDlgGenerated::OnHelpFilterSettings ), NULL, this ); m_textCtrlExclude->Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( ConfigDlgGenerated::OnChangeFilterOption ), NULL, this ); m_choiceUnitMinSize->Connect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( ConfigDlgGenerated::OnChangeFilterOption ), NULL, this ); m_choiceUnitMaxSize->Connect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( ConfigDlgGenerated::OnChangeFilterOption ), NULL, this ); @@ -2474,7 +2474,7 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w m_checkBoxVersionMaxDays->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnToggleVersioningLimit ), NULL, this ); m_checkBoxVersionCountMin->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnToggleVersioningLimit ), NULL, this ); m_checkBoxVersionCountMax->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnToggleVersioningLimit ), NULL, this ); - m_checkBoxSendEmail->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnToggleMiscOption ), NULL, this ); + m_checkBoxSendEmail->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnToggleMiscEmail ), NULL, this ); m_bpButtonEmailAlways->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnEmailAlways ), NULL, this ); m_bpButtonEmailErrorWarning->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnEmailErrorWarning ), NULL, this ); m_bpButtonEmailErrorOnly->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnEmailErrorOnly ), NULL, this ); @@ -3937,7 +3937,7 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS bSizer243->Add( m_checkBoxIgnoreErrors, 1, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 ); - bSizer242->Add( bSizer243, 0, wxTOP|wxRIGHT|wxLEFT|wxALIGN_CENTER_HORIZONTAL, 5 ); + bSizer242->Add( bSizer243, 0, wxTOP|wxRIGHT|wxLEFT, 5 ); wxBoxSizer* bSizer246; bSizer246 = new wxBoxSizer( wxVERTICAL ); @@ -3954,7 +3954,7 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS bSizer246->Add( m_radioBtnErrorDialogCancel, 0, wxBOTTOM|wxRIGHT|wxLEFT|wxEXPAND, 5 ); - bSizer242->Add( bSizer246, 0, wxALIGN_CENTER_HORIZONTAL, 5 ); + bSizer242->Add( bSizer246, 0, wxALIGN_CENTER_HORIZONTAL|wxLEFT, 15 ); bSizer180->Add( bSizer242, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); @@ -4372,11 +4372,17 @@ OptionsDlgGenerated::OptionsDlgGenerated( wxWindow* parent, wxWindowID id, const bSizer160->Add( bSizer178, 0, wxEXPAND, 5 ); - bSizer186->Add( bSizer160, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); + bSizer186->Add( bSizer160, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); m_staticline39 = new wxStaticLine( m_panel39, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL ); bSizer186->Add( m_staticline39, 0, wxEXPAND, 5 ); + wxBoxSizer* bSizer293; + bSizer293 = new wxBoxSizer( wxHORIZONTAL ); + + m_bitmapWarnings = new wxStaticBitmap( m_panel39, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); + bSizer293->Add( m_bitmapWarnings, 0, wxALL, 5 ); + wxBoxSizer* bSizer1881; bSizer1881 = new wxBoxSizer( wxVERTICAL ); @@ -4384,13 +4390,26 @@ OptionsDlgGenerated::OptionsDlgGenerated( wxWindow* parent, wxWindowID id, const m_staticTextResetDialogs->Wrap( -1 ); m_staticTextResetDialogs->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) ); - bSizer1881->Add( m_staticTextResetDialogs, 0, wxTOP|wxRIGHT|wxLEFT, 5 ); + bSizer1881->Add( m_staticTextResetDialogs, 0, 0, 5 ); + + wxBoxSizer* bSizer292; + bSizer292 = new wxBoxSizer( wxHORIZONTAL ); + + m_buttonRestoreDialogs = new wxButton( m_panel39, wxID_ANY, _("&Restore"), wxDefaultPosition, wxSize( -1, -1 ), 0 ); + bSizer292->Add( m_buttonRestoreDialogs, 0, wxALIGN_CENTER_VERTICAL, 5 ); + + m_staticTextAllDialogsShown = new wxStaticText( m_panel39, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticTextAllDialogsShown->Wrap( -1 ); + bSizer292->Add( m_staticTextAllDialogsShown, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 ); + + + bSizer1881->Add( bSizer292, 0, wxTOP, 5 ); + - m_buttonResetDialogs = new zen::BitmapTextButton( m_panel39, wxID_ANY, _("dummy"), wxDefaultPosition, wxSize( -1, -1 ), 0 ); - bSizer1881->Add( m_buttonResetDialogs, 0, wxALL, 5 ); + bSizer293->Add( bSizer1881, 0, wxTOP|wxBOTTOM|wxRIGHT, 5 ); - bSizer186->Add( bSizer1881, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); + bSizer186->Add( bSizer293, 0, wxALL, 5 ); bSizer166->Add( bSizer186, 0, wxEXPAND, 5 ); @@ -4405,32 +4424,78 @@ OptionsDlgGenerated::OptionsDlgGenerated( wxWindow* parent, wxWindowID id, const bSizer258 = new wxBoxSizer( wxHORIZONTAL ); m_bitmapLogFile = new wxStaticBitmap( m_panel39, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); - bSizer258->Add( m_bitmapLogFile, 0, wxALIGN_CENTER_VERTICAL, 5 ); + bSizer258->Add( m_bitmapLogFile, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); m_staticText163 = new wxStaticText( m_panel39, wxID_ANY, _("Default log path:"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText163->Wrap( -1 ); - bSizer258->Add( m_staticText163, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 ); + bSizer258->Add( m_staticText163, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxRIGHT, 5 ); m_hyperlinkLogFolder = new wxHyperlinkCtrl( m_panel39, wxID_ANY, _("dummy"), wxEmptyString, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); - bSizer258->Add( m_hyperlinkLogFolder, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 ); + bSizer258->Add( m_hyperlinkLogFolder, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxRIGHT, 5 ); - bSizer259->Add( bSizer258, 0, wxALL|wxEXPAND, 5 ); + bSizer259->Add( bSizer258, 0, wxALL, 5 ); + + wxBoxSizer* bSizer299; + bSizer299 = new wxBoxSizer( wxVERTICAL ); + + m_staticline83 = new wxStaticLine( m_panel39, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + bSizer299->Add( m_staticline83, 0, wxEXPAND, 5 ); + + wxBoxSizer* bSizer297; + bSizer297 = new wxBoxSizer( wxHORIZONTAL ); + + m_staticline82 = new wxStaticLine( m_panel39, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL ); + bSizer297->Add( m_staticline82, 0, wxEXPAND, 5 ); wxBoxSizer* bSizer282; bSizer282 = new wxBoxSizer( wxHORIZONTAL ); m_checkBoxLogFilesMaxAge = new wxCheckBox( m_panel39, wxID_ANY, _("&Delete logs after x days:"), wxDefaultPosition, wxDefaultSize, 0 ); - bSizer282->Add( m_checkBoxLogFilesMaxAge, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + bSizer282->Add( m_checkBoxLogFilesMaxAge, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); m_spinCtrlLogFilesMaxAge = new wxSpinCtrl( m_panel39, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( -1, -1 ), wxSP_ARROW_KEYS, 1, 2000000000, 1 ); - bSizer282->Add( m_spinCtrlLogFilesMaxAge, 0, wxALIGN_CENTER_VERTICAL, 5 ); + bSizer282->Add( m_spinCtrlLogFilesMaxAge, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxRIGHT, 5 ); + + + bSizer297->Add( bSizer282, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); + + m_staticline81 = new wxStaticLine( m_panel39, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL ); + bSizer297->Add( m_staticline81, 0, wxEXPAND, 5 ); + + wxBoxSizer* bSizer296; + bSizer296 = new wxBoxSizer( wxHORIZONTAL ); + + m_staticText184 = new wxStaticText( m_panel39, wxID_ANY, _("Log file format:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText184->Wrap( -1 ); + bSizer296->Add( m_staticText184, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); + + wxFlexGridSizer* fgSizer251; + fgSizer251 = new wxFlexGridSizer( 0, 1, 5, 0 ); + fgSizer251->SetFlexibleDirection( wxBOTH ); + fgSizer251->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED ); + m_radioBtnLogHtml = new wxRadioButton( m_panel39, wxID_ANY, _("&HTML"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP ); + m_radioBtnLogHtml->SetValue( true ); + fgSizer251->Add( m_radioBtnLogHtml, 0, wxEXPAND, 5 ); - bSizer259->Add( bSizer282, 0, wxALL, 5 ); + m_radioBtnLogText = new wxRadioButton( m_panel39, wxID_ANY, _("&Plain text"), wxDefaultPosition, wxDefaultSize, 0 ); + fgSizer251->Add( m_radioBtnLogText, 0, wxEXPAND, 5 ); - bSizer166->Add( bSizer259, 0, wxALL|wxEXPAND, 5 ); + bSizer296->Add( fgSizer251, 0, wxTOP|wxBOTTOM|wxRIGHT, 5 ); + + + bSizer297->Add( bSizer296, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); + + + bSizer299->Add( bSizer297, 0, 0, 5 ); + + + bSizer259->Add( bSizer299, 0, wxALIGN_RIGHT|wxLEFT, 15 ); + + + bSizer166->Add( bSizer259, 0, wxEXPAND, 5 ); m_staticline361 = new wxStaticLine( m_panel39, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); bSizer166->Add( m_staticline361, 0, wxEXPAND, 5 ); @@ -4438,33 +4503,38 @@ OptionsDlgGenerated::OptionsDlgGenerated( wxWindow* parent, wxWindowID id, const wxBoxSizer* bSizer288; bSizer288 = new wxBoxSizer( wxVERTICAL ); - wxBoxSizer* bSizer291; - bSizer291 = new wxBoxSizer( wxHORIZONTAL ); + wxBoxSizer* bSizer300; + bSizer300 = new wxBoxSizer( wxHORIZONTAL ); m_bitmapNotificationSounds = new wxStaticBitmap( m_panel39, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); - bSizer291->Add( m_bitmapNotificationSounds, 0, wxALIGN_CENTER_VERTICAL, 5 ); + bSizer300->Add( m_bitmapNotificationSounds, 0, wxALIGN_CENTER_VERTICAL, 5 ); m_staticText851 = new wxStaticText( m_panel39, wxID_ANY, _("Notification sounds:"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText851->Wrap( -1 ); - bSizer291->Add( m_staticText851, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 ); + bSizer300->Add( m_staticText851, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 ); + + bSizer288->Add( bSizer300, 0, wxTOP|wxRIGHT|wxLEFT, 5 ); - bSizer288->Add( bSizer291, 0, wxALL, 5 ); + wxBoxSizer* bSizer301; + bSizer301 = new wxBoxSizer( wxHORIZONTAL ); - ffgSizer11 = new wxFlexGridSizer( 0, 4, 0, 0 ); - ffgSizer11->AddGrowableCol( 3 ); + + bSizer301->Add( 15, 0, 0, 0, 5 ); + + ffgSizer11 = new wxFlexGridSizer( 0, 3, 0, 5 ); + ffgSizer11->AddGrowableCol( 2 ); ffgSizer11->SetFlexibleDirection( wxBOTH ); ffgSizer11->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED ); - - ffgSizer11->Add( 10, 0, 0, 0, 5 ); + m_bitmapCompareDone = new wxStaticBitmap( m_panel39, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); + ffgSizer11->Add( m_bitmapCompareDone, 0, wxALIGN_CENTER_VERTICAL, 5 ); m_staticText171 = new wxStaticText( m_panel39, wxID_ANY, _("Comparison finished:"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText171->Wrap( -1 ); - ffgSizer11->Add( m_staticText171, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT, 5 ); + m_staticText171->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) ); - m_bitmapCompareDone = new wxStaticBitmap( m_panel39, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); - ffgSizer11->Add( m_bitmapCompareDone, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 ); + ffgSizer11->Add( m_staticText171, 0, wxALIGN_CENTER_VERTICAL, 5 ); wxBoxSizer* bSizer290; bSizer290 = new wxBoxSizer( wxHORIZONTAL ); @@ -4483,15 +4553,14 @@ OptionsDlgGenerated::OptionsDlgGenerated( wxWindow* parent, wxWindowID id, const ffgSizer11->Add( bSizer290, 0, wxALIGN_CENTER_VERTICAL|wxEXPAND, 5 ); - - ffgSizer11->Add( 0, 0, 0, 0, 5 ); + m_bitmapSyncDone = new wxStaticBitmap( m_panel39, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); + ffgSizer11->Add( m_bitmapSyncDone, 0, wxALIGN_CENTER_VERTICAL, 5 ); m_staticText1711 = new wxStaticText( m_panel39, wxID_ANY, _("Synchronization finished:"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText1711->Wrap( -1 ); - ffgSizer11->Add( m_staticText1711, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT, 5 ); + m_staticText1711->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) ); - m_bitmapSyncDone = new wxStaticBitmap( m_panel39, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); - ffgSizer11->Add( m_bitmapSyncDone, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 ); + ffgSizer11->Add( m_staticText1711, 0, wxALIGN_CENTER_VERTICAL, 5 ); wxBoxSizer* bSizer2901; bSizer2901 = new wxBoxSizer( wxHORIZONTAL ); @@ -4511,10 +4580,13 @@ OptionsDlgGenerated::OptionsDlgGenerated( wxWindow* parent, wxWindowID id, const ffgSizer11->Add( bSizer2901, 0, wxALIGN_CENTER_VERTICAL|wxEXPAND, 5 ); - bSizer288->Add( ffgSizer11, 0, wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); + bSizer301->Add( ffgSizer11, 1, wxALL, 5 ); + + + bSizer288->Add( bSizer301, 0, wxEXPAND, 5 ); - bSizer166->Add( bSizer288, 0, wxALL|wxEXPAND, 5 ); + bSizer166->Add( bSizer288, 0, wxEXPAND|wxALL, 5 ); m_staticline3611 = new wxStaticLine( m_panel39, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); bSizer166->Add( m_staticline3611, 0, wxEXPAND, 5 ); @@ -4680,7 +4752,7 @@ OptionsDlgGenerated::OptionsDlgGenerated( wxWindow* parent, wxWindowID id, const // Connect Events this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( OptionsDlgGenerated::OnClose ) ); - m_buttonResetDialogs->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( OptionsDlgGenerated::OnResetDialogs ), NULL, this ); + m_buttonRestoreDialogs->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( OptionsDlgGenerated::OnRestoreDialogs ), NULL, this ); m_hyperlinkLogFolder->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( OptionsDlgGenerated::OnShowLogFolder ), NULL, this ); m_checkBoxLogFilesMaxAge->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( OptionsDlgGenerated::OnToggleLogfilesLimit ), NULL, this ); m_textCtrlSoundPathCompareDone->Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( OptionsDlgGenerated::OnChangeSoundFilePath ), NULL, this ); @@ -4691,7 +4763,7 @@ OptionsDlgGenerated::OptionsDlgGenerated( wxWindow* parent, wxWindowID id, const m_bpButtonPlaySyncDone->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( OptionsDlgGenerated::OnPlaySyncDone ), NULL, this ); m_bpButtonAddRow->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( OptionsDlgGenerated::OnAddRow ), NULL, this ); m_bpButtonRemoveRow->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( OptionsDlgGenerated::OnRemoveRow ), NULL, this ); - m_hyperlink17->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( OptionsDlgGenerated::OnHelpShowExamples ), NULL, this ); + m_hyperlink17->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( OptionsDlgGenerated::OnHelpExternalApps ), NULL, this ); m_buttonDefault->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( OptionsDlgGenerated::OnDefault ), NULL, this ); m_buttonOkay->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( OptionsDlgGenerated::OnOkay ), NULL, this ); m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( OptionsDlgGenerated::OnCancel ), NULL, this ); diff --git a/FreeFileSync/Source/ui/gui_generated.h b/FreeFileSync/Source/ui/gui_generated.h index 761dfb15..07867a58 100644 --- a/FreeFileSync/Source/ui/gui_generated.h +++ b/FreeFileSync/Source/ui/gui_generated.h @@ -260,8 +260,8 @@ public: wxPanel* m_panelTopRight; fff::FolderHistoryBox* m_folderPathRight; wxBitmapButton* m_bpButtonSelectAltFolderRight; - wxStaticBitmap* m_bitmapLogStatus; - wxStaticText* m_staticTextLogStatus; + wxStaticBitmap* m_bitmapSyncResult; + wxStaticText* m_staticTextSyncResult; wxStaticText* m_staticTextProcessed; wxStaticText* m_staticTextRemaining; wxPanel* m_panelItemStats; @@ -503,7 +503,7 @@ protected: virtual void OnToggleAutoRetry( wxCommandEvent& event ) { event.Skip(); } virtual void OnHelpPerformance( wxHyperlinkEvent& event ) { event.Skip(); } virtual void OnChangeFilterOption( wxCommandEvent& event ) { event.Skip(); } - virtual void OnHelpShowExamples( wxHyperlinkEvent& event ) { event.Skip(); } + virtual void OnHelpFilterSettings( wxHyperlinkEvent& event ) { event.Skip(); } virtual void OnFilterReset( wxCommandEvent& event ) { event.Skip(); } virtual void OnToggleLocalSyncSettings( wxCommandEvent& event ) { event.Skip(); } virtual void OnSyncTwoWayDouble( wxMouseEvent& event ) { event.Skip(); } @@ -528,10 +528,11 @@ protected: virtual void OnHelpVersioning( wxHyperlinkEvent& event ) { event.Skip(); } virtual void OnChanegVersioningStyle( wxCommandEvent& event ) { event.Skip(); } virtual void OnToggleVersioningLimit( wxCommandEvent& event ) { event.Skip(); } - virtual void OnToggleMiscOption( wxCommandEvent& event ) { event.Skip(); } + virtual void OnToggleMiscEmail( wxCommandEvent& event ) { event.Skip(); } virtual void OnEmailAlways( wxCommandEvent& event ) { event.Skip(); } virtual void OnEmailErrorWarning( wxCommandEvent& event ) { event.Skip(); } virtual void OnEmailErrorOnly( wxCommandEvent& event ) { event.Skip(); } + virtual void OnToggleMiscOption( wxCommandEvent& event ) { event.Skip(); } virtual void OnOkay( wxCommandEvent& event ) { event.Skip(); } virtual void OnCancel( wxCommandEvent& event ) { event.Skip(); } @@ -1025,25 +1026,33 @@ protected: wxStaticText* m_staticText93; wxStaticText* m_staticText932; wxStaticLine* m_staticline39; + wxStaticBitmap* m_bitmapWarnings; wxStaticText* m_staticTextResetDialogs; - zen::BitmapTextButton* m_buttonResetDialogs; + wxButton* m_buttonRestoreDialogs; + wxStaticText* m_staticTextAllDialogsShown; wxStaticLine* m_staticline191; wxStaticBitmap* m_bitmapLogFile; wxStaticText* m_staticText163; wxHyperlinkCtrl* m_hyperlinkLogFolder; + wxStaticLine* m_staticline83; + wxStaticLine* m_staticline82; wxCheckBox* m_checkBoxLogFilesMaxAge; wxSpinCtrl* m_spinCtrlLogFilesMaxAge; + wxStaticLine* m_staticline81; + wxStaticText* m_staticText184; + wxRadioButton* m_radioBtnLogHtml; + wxRadioButton* m_radioBtnLogText; wxStaticLine* m_staticline361; wxStaticBitmap* m_bitmapNotificationSounds; wxStaticText* m_staticText851; wxFlexGridSizer* ffgSizer11; - wxStaticText* m_staticText171; wxStaticBitmap* m_bitmapCompareDone; + wxStaticText* m_staticText171; wxTextCtrl* m_textCtrlSoundPathCompareDone; wxButton* m_buttonSelectSoundCompareDone; wxBitmapButton* m_bpButtonPlayCompareDone; - wxStaticText* m_staticText1711; wxStaticBitmap* m_bitmapSyncDone; + wxStaticText* m_staticText1711; wxTextCtrl* m_textCtrlSoundPathSyncDone; wxButton* m_buttonSelectSoundSyncDone; wxBitmapButton* m_bpButtonPlaySyncDone; @@ -1069,7 +1078,7 @@ protected: // Virtual event handlers, overide them in your derived class virtual void OnClose( wxCloseEvent& event ) { event.Skip(); } - virtual void OnResetDialogs( wxCommandEvent& event ) { event.Skip(); } + virtual void OnRestoreDialogs( wxCommandEvent& event ) { event.Skip(); } virtual void OnShowLogFolder( wxHyperlinkEvent& event ) { event.Skip(); } virtual void OnToggleLogfilesLimit( wxCommandEvent& event ) { event.Skip(); } virtual void OnChangeSoundFilePath( wxCommandEvent& event ) { event.Skip(); } @@ -1079,7 +1088,7 @@ protected: virtual void OnPlaySyncDone( wxCommandEvent& event ) { event.Skip(); } virtual void OnAddRow( wxCommandEvent& event ) { event.Skip(); } virtual void OnRemoveRow( wxCommandEvent& event ) { event.Skip(); } - virtual void OnHelpShowExamples( wxHyperlinkEvent& event ) { event.Skip(); } + virtual void OnHelpExternalApps( wxHyperlinkEvent& event ) { event.Skip(); } virtual void OnDefault( wxCommandEvent& event ) { event.Skip(); } virtual void OnOkay( wxCommandEvent& event ) { event.Skip(); } virtual void OnCancel( wxCommandEvent& event ) { event.Skip(); } diff --git a/FreeFileSync/Source/ui/gui_status_handler.cpp b/FreeFileSync/Source/ui/gui_status_handler.cpp index 8adf1f89..6ad01cd2 100644 --- a/FreeFileSync/Source/ui/gui_status_handler.cpp +++ b/FreeFileSync/Source/ui/gui_status_handler.cpp @@ -11,9 +11,9 @@ #include #include #include "main_dlg.h" -#include "../base/log_file.h" -#include "../base/resolve_path.h" #include "../afs/concrete.h" +#include "../base/resolve_path.h" +#include "../log_file.h" using namespace zen; using namespace fff; @@ -148,9 +148,11 @@ StatusHandlerTemporaryPanel::Result StatusHandlerTemporaryPanel::reportResults() errorLog_.logMsg(_("Stopped"), MSG_TYPE_ERROR); //= user cancel; *not* a MSG_TYPE_FATAL_ERROR! return SyncResult::aborted; } - else if (errorLog_.getItemCount(MSG_TYPE_ERROR | MSG_TYPE_FATAL_ERROR) > 0) + + const ErrorLog::Stats logCount = errorLog_.getStats(); + if (logCount.error + logCount.fatal > 0) return SyncResult::finishedError; - else if (errorLog_.getItemCount(MSG_TYPE_WARNING) > 0) + else if (logCount.warning > 0) return SyncResult::finishedWarning; else return SyncResult::finishedSuccess; @@ -228,7 +230,7 @@ ProcessCallback::Response StatusHandlerTemporaryPanel::reportError(const std::ws if (retryNumber < automaticRetryCount_) { errorLog_.logMsg(msg + L"\n-> " + _("Automatic retry"), MSG_TYPE_INFO); - delayAndCountDown(_("Automatic retry") + (automaticRetryCount_ <= 1 ? L"" : L" " + numberTo(retryNumber + 1) + L"/" + numberTo(automaticRetryCount_)), + delayAndCountDown(_("Automatic retry") + (automaticRetryCount_ <= 1 ? L"" : L' ' + numberTo(retryNumber + 1) + L"/" + numberTo(automaticRetryCount_)), automaticRetryDelay_, [&](const std::wstring& statusMsg) { this->updateStatus(_("Error") + L": " + statusMsg); }); //throw AbortProcess return ProcessCallback::retry; } @@ -354,8 +356,9 @@ StatusHandlerFloatingDialog::~StatusHandlerFloatingDialog() StatusHandlerFloatingDialog::Result StatusHandlerFloatingDialog::reportResults(const Zstring& postSyncCommand, PostSyncCondition postSyncCondition, - const Zstring& altLogFolderPathPhrase, int logfilesMaxAgeDays, const std::set& logFilePathsToKeep, - const Zstring& emailNotifyAddress, ResultsNotification emailNotifyCondition) + const Zstring& altLogFolderPathPhrase, int logfilesMaxAgeDays, LogFileFormat logFormat, + const std::set& logFilePathsToKeep, + const std::string& emailNotifyAddress, ResultsNotification emailNotifyCondition) { const auto totalTime = std::chrono::duration_cast(std::chrono::system_clock::now() - startTime_); @@ -369,9 +372,11 @@ StatusHandlerFloatingDialog::Result StatusHandlerFloatingDialog::reportResults(c errorLog_.logMsg(_("Stopped"), MSG_TYPE_ERROR); //= user cancel; *not* a MSG_TYPE_FATAL_ERROR! return SyncResult::aborted; } - else if (errorLog_.getItemCount(MSG_TYPE_ERROR | MSG_TYPE_FATAL_ERROR) > 0) + + const ErrorLog::Stats logCount = errorLog_.getStats(); + if (logCount.error + logCount.fatal > 0) return SyncResult::finishedError; - else if (errorLog_.getItemCount(MSG_TYPE_WARNING) > 0) + else if (logCount.warning > 0) return SyncResult::finishedWarning; if (getStatsTotal() == ProgressStats()) @@ -389,7 +394,7 @@ StatusHandlerFloatingDialog::Result StatusHandlerFloatingDialog::reportResults(c totalTime }; - const AbstractPath logFilePath = generateLogFilePath(summary, altLogFolderPathPhrase); + const AbstractPath logFilePath = generateLogFilePath(logFormat, summary, altLogFolderPathPhrase); //e.g. %AppData%\FreeFileSync\Logs\Backup FreeFileSync 2013-09-15 015052.123 [Error].log if (const Zstring cmdLine = trimCpy(postSyncCommand); @@ -406,9 +411,8 @@ StatusHandlerFloatingDialog::Result StatusHandlerFloatingDialog::reportResults(c //::wxSetEnv(L"logfile_path", AFS::getDisplayPath(logFilePath)); ////---------------------------------------------------------------------- const Zstring cmdLineExp = expandMacros(cmdLine); - const int exitCode = shellExecute(cmdLineExp, ExecutionType::sync, false /*hideConsole*/); //throw FileError - errorLog_.logMsg(_("Executing command:") + L" " + utfTo(cmdLineExp) + L" [" + replaceCpy(_("Exit Code %x"), L"%x", numberTo(exitCode)) + L']', - exitCode == 0 ? MSG_TYPE_INFO : MSG_TYPE_ERROR); + errorLog_.logMsg(_("Executing command:") + L' ' + utfTo(cmdLineExp), MSG_TYPE_INFO); + shellExecute(cmdLineExp, ExecutionType::async, false /*hideConsole*/); //throw FileError } catch (const FileError& e) { errorLog_.logMsg(e.toString(), MSG_TYPE_ERROR); } } @@ -416,7 +420,7 @@ StatusHandlerFloatingDialog::Result StatusHandlerFloatingDialog::reportResults(c //---------------------------- save log file ------------------------------ auto notifyStatusNoThrow = [&](const std::wstring& msg) { try { updateStatus(msg); /*throw AbortProcess*/ } catch (AbortProcess&) {} }; - if (const Zstring notifyEmail = trimCpy(emailNotifyAddress); + if (const std::string notifyEmail = trimCpy(emailNotifyAddress); !notifyEmail.empty()) { if (getAbortStatus() && *getAbortStatus() == AbortTrigger::user) @@ -437,7 +441,7 @@ StatusHandlerFloatingDialog::Result StatusHandlerFloatingDialog::reportResults(c try //create not before destruction: 1. avoid issues with FFS trying to sync open log file 2. include status in log file name without extra rename { //do NOT use tryReportingError()! saving log files should not be cancellable! - saveLogFile(logFilePath, summary, errorLog_, logfilesMaxAgeDays, logFilePathsToKeep, notifyStatusNoThrow); //throw FileError + saveLogFile(logFilePath, summary, errorLog_, logfilesMaxAgeDays, logFormat, logFilePathsToKeep, notifyStatusNoThrow); //throw FileError } catch (const FileError& e) { errorLog_.logMsg(e.toString(), MSG_TYPE_ERROR); } @@ -566,7 +570,7 @@ ProcessCallback::Response StatusHandlerFloatingDialog::reportError(const std::ws if (retryNumber < automaticRetryCount_) { errorLog_.logMsg(msg + L"\n-> " + _("Automatic retry"), MSG_TYPE_INFO); - delayAndCountDown(_("Automatic retry") + (automaticRetryCount_ <= 1 ? L"" : L" " + numberTo(retryNumber + 1) + L"/" + numberTo(automaticRetryCount_)), + delayAndCountDown(_("Automatic retry") + (automaticRetryCount_ <= 1 ? L"" : L' ' + numberTo(retryNumber + 1) + L"/" + numberTo(automaticRetryCount_)), automaticRetryDelay_, [&](const std::wstring& statusMsg) { this->updateStatus(_("Error") + L": " + statusMsg); }); //throw AbortProcess return ProcessCallback::retry; } diff --git a/FreeFileSync/Source/ui/gui_status_handler.h b/FreeFileSync/Source/ui/gui_status_handler.h index 82903078..5f671a82 100644 --- a/FreeFileSync/Source/ui/gui_status_handler.h +++ b/FreeFileSync/Source/ui/gui_status_handler.h @@ -11,7 +11,7 @@ #include #include "progress_indicator.h" #include "main_dlg.h" -#include "../base/status_handler.h" +#include "../status_handler.h" namespace fff @@ -92,8 +92,8 @@ public: AbstractPath logFilePath; }; Result reportResults(const Zstring& postSyncCommand, PostSyncCondition postSyncCondition, - const Zstring& altLogFolderPathPhrase, int logfilesMaxAgeDays, const std::set& logFilePathsToKeep, - const Zstring& emailNotifyAddress, ResultsNotification emailNotifyCondition); //noexcept!! + const Zstring& altLogFolderPathPhrase, int logfilesMaxAgeDays, LogFileFormat logFormat, const std::set& logFilePathsToKeep, + const std::string& emailNotifyAddress, ResultsNotification emailNotifyCondition); //noexcept!! private: SyncProgressDialog* progressDlg_; //managed to have the same lifetime as this handler! diff --git a/FreeFileSync/Source/ui/log_panel.cpp b/FreeFileSync/Source/ui/log_panel.cpp index 51cfa703..52322dc9 100644 --- a/FreeFileSync/Source/ui/log_panel.cpp +++ b/FreeFileSync/Source/ui/log_panel.cpp @@ -62,7 +62,7 @@ public: { time_t time = 0; MessageType type = MSG_TYPE_INFO; - Zstringw messageLine; + std::string_view messageLine; bool firstLine = false; //if LogEntry::message spans multiple rows }; @@ -73,10 +73,10 @@ public: const Line& line = viewRef_[row]; LogEntryView output; - output.time = line.logIt_->time; - output.type = line.logIt_->type; - output.messageLine = extractLine(line.logIt_->message, line.rowNumber_); - output.firstLine = line.rowNumber_ == 0; //this is virtually always correct, unless first line of the original message is empty! + output.time = line.logIt->time; + output.type = line.logIt->type; + output.messageLine = extractLine(line.logIt->message, line.row); + output.firstLine = line.row == 0; //this is virtually always correct, unless first line of the original message is empty! return output; } return {}; @@ -89,13 +89,12 @@ public: for (auto it = log_.ref().begin(); it != log_.ref().end(); ++it) if (it->type & includedTypes) { - static_assert(std::is_same_v, wchar_t>); - assert(!startsWith(it->message, L'\n')); + assert(!startsWith(it->message, '\n')); size_t rowNumber = 0; bool lastCharNewline = true; - for (const wchar_t c : it->message) - if (c == L'\n') + for (const char c : it->message) + if (c == '\n') { if (!lastCharNewline) //do not reference empty lines! viewRef_.emplace_back(it, rowNumber); @@ -111,19 +110,19 @@ public: } private: - static Zstringw extractLine(const Zstringw& message, size_t textRow) + static std::string_view extractLine(const Zstringc& message, size_t textRow) { auto it1 = message.begin(); for (;;) { - auto it2 = std::find_if(it1, message.end(), [](wchar_t c) { return c == L'\n'; }); + auto it2 = std::find_if(it1, message.end(), [](char c) { return c == '\n'; }); if (textRow == 0) - return it1 == message.end() ? Zstringw() : Zstringw(&*it1, it2 - it1); //must not dereference iterator pointing to "end"! + return makeStringView(it1, it2 - it1); if (it2 == message.end()) { assert(false); - return Zstringw(); + return makeStringView(it1, 0); } it1 = it2 + 1; //skip newline @@ -133,10 +132,10 @@ private: struct Line { - Line(ErrorLog::const_iterator logIt, size_t rowNumber) : logIt_(logIt), rowNumber_(rowNumber) {} + Line(ErrorLog::const_iterator it, size_t rowNum) : logIt(it), row(rowNum) {} - ErrorLog::const_iterator logIt_; //always bound! - size_t rowNumber_; //LogEntry::message may span multiple rows + ErrorLog::const_iterator logIt; //always bound! + size_t row; //LogEntry::message may span multiple rows }; std::vector viewRef_; //partial view on log_ @@ -161,12 +160,12 @@ public: std::wstring getValue(size_t row, ColumnType colType) const override { - if (std::optional entry = msgView_.getEntry(row)) + if (const std::optional entry = msgView_.getEntry(row)) switch (static_cast(colType)) { case ColumnTypeLog::time: if (entry->firstLine) - return formatTime(FORMAT_TIME, getLocalTime(entry->time)); + return utfTo(formatTime(formatTimeTag, getLocalTime(entry->time))); break; case ColumnTypeLog::category: @@ -185,7 +184,7 @@ public: break; case ColumnTypeLog::text: - return copyStringTo(entry->messageLine); + return utfTo(entry->messageLine); } return std::wstring(); } @@ -277,7 +276,7 @@ public: { wxClientDC dc(&grid.getMainWin()); dc.SetFont(grid.getMainWin().GetFont()); - return 2 * getColumnGapLeft() + dc.GetTextExtent(formatTime(FORMAT_TIME)).GetWidth(); + return 2 * getColumnGapLeft() + dc.GetTextExtent(utfTo(formatTime(formatTimeTag))).GetWidth(); } static int getColumnCategoryDefaultWidth() @@ -353,9 +352,7 @@ void LogPanel::setLog(const std::shared_ptr& log) return makeSharedRef(std::move(dummyLog)); }(); - const int errorCount = newLog.ref().getItemCount(MSG_TYPE_ERROR | MSG_TYPE_FATAL_ERROR); - const int warningCount = newLog.ref().getItemCount(MSG_TYPE_WARNING); - const int infoCount = newLog.ref().getItemCount(MSG_TYPE_INFO); + const ErrorLog::Stats logCount = newLog.ref().getStats(); auto initButton = [](ToggleButton& btn, const wchar_t* imgName, const wxString& tooltip) { @@ -363,17 +360,17 @@ void LogPanel::setLog(const std::shared_ptr& log) btn.SetToolTip(tooltip); }; - initButton(*m_bpButtonErrors, L"msg_error", _("Error" ) + L" (" + formatNumber(errorCount) + L")"); - initButton(*m_bpButtonWarnings, L"msg_warning", _("Warning") + L" (" + formatNumber(warningCount) + L")"); - initButton(*m_bpButtonInfo, L"msg_info", _("Info" ) + L" (" + formatNumber(infoCount) + L")"); + initButton(*m_bpButtonErrors, L"msg_error", _("Error" ) + L" (" + formatNumber(logCount.error + logCount.fatal) + L")"); + initButton(*m_bpButtonWarnings, L"msg_warning", _("Warning") + L" (" + formatNumber(logCount.warning ) + L")"); + initButton(*m_bpButtonInfo, L"msg_info", _("Info" ) + L" (" + formatNumber(logCount.info ) + L")"); m_bpButtonErrors ->setActive(true); m_bpButtonWarnings->setActive(true); - m_bpButtonInfo ->setActive(errorCount + warningCount == 0); + m_bpButtonInfo ->setActive(logCount.warning + logCount.error + logCount.fatal == 0); - m_bpButtonErrors ->Show(errorCount != 0); - m_bpButtonWarnings->Show(warningCount != 0); - m_bpButtonInfo ->Show(infoCount != 0); + m_bpButtonErrors ->Show(logCount.error + logCount.fatal != 0); + m_bpButtonWarnings->Show(logCount.warning != 0); + m_bpButtonInfo ->Show(logCount.info != 0); m_gridMessages->setDataProvider(std::make_shared(newLog)); @@ -385,7 +382,7 @@ MessageView& LogPanel::getDataView() { if (auto* prov = dynamic_cast(m_gridMessages->getDataProvider())) return prov->getDataView(); - throw std::runtime_error(std::string(__FILE__) + "[" + numberTo(__LINE__) + "] m_gridMessages was not initialized."); + throw std::runtime_error(std::string(__FILE__) + '[' + numberTo(__LINE__) + "] m_gridMessages was not initialized."); } @@ -540,7 +537,7 @@ void LogPanel::copySelectionToClipboard() { try { - Zstringw clipboardString; //guaranteed exponential growth, unlike wxString + std::wstring clipBuf; //guaranteed exponential growth, unlike wxString if (auto prov = m_gridMessages->getDataProvider()) { @@ -552,24 +549,24 @@ void LogPanel::copySelectionToClipboard() std::for_each(colAttr.begin(), --colAttr.end(), [&](const Grid::ColAttributes& ca) { - clipboardString += copyStringTo(prov->getValue(row, ca.type)); - clipboardString += L'\t'; + clipBuf += prov->getValue(row, ca.type); + clipBuf += L'\t'; }); - clipboardString += copyStringTo(prov->getValue(row, colAttr.back().type)); - clipboardString += L'\n'; + clipBuf += prov->getValue(row, colAttr.back().type); + clipBuf += L'\n'; } } //finally write to clipboard - if (!clipboardString.empty()) + if (!clipBuf.empty()) if (wxClipboard::Get()->Open()) { ZEN_ON_SCOPE_EXIT(wxClipboard::Get()->Close()); - wxClipboard::Get()->SetData(new wxTextDataObject(copyStringTo(clipboardString))); //ownership passed + wxClipboard::Get()->SetData(new wxTextDataObject(std::move(clipBuf))); //ownership passed } } catch (const std::bad_alloc& e) { - showNotificationDialog(nullptr, DialogInfoType::error, PopupDialogCfg().setMainInstructions(_("Out of memory.") + L" " + utfTo(e.what()))); + showNotificationDialog(nullptr, DialogInfoType::error, PopupDialogCfg().setMainInstructions(_("Out of memory.") + L' ' + utfTo(e.what()))); } } diff --git a/FreeFileSync/Source/ui/main_dlg.cpp b/FreeFileSync/Source/ui/main_dlg.cpp index d6fb64d4..4dcad8b3 100644 --- a/FreeFileSync/Source/ui/main_dlg.cpp +++ b/FreeFileSync/Source/ui/main_dlg.cpp @@ -44,10 +44,10 @@ #include "../base/synchronization.h" #include "../base/algorithm.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 "../ffs_paths.h" +#include "../help_provider.h" +#include "../localization.h" #include "../version/version.h" using namespace zen; @@ -82,8 +82,8 @@ bool acceptDialogFileDrop(const std::vector& shellItemPaths) return std::any_of(shellItemPaths.begin(), shellItemPaths.end(), [](const Zstring& shellItemPath) { const Zstring ext = getFileExtension(shellItemPath); - return equalAsciiNoCase(ext, Zstr("ffs_gui")) || - equalAsciiNoCase(ext, Zstr("ffs_batch")); + return equalAsciiNoCase(ext, "ffs_gui") || + equalAsciiNoCase(ext, "ffs_batch"); }); } @@ -324,8 +324,8 @@ void MainDialog::create(const Zstring& globalConfigFilePath) //add default exclusion filter: this is only ever relevant when creating new configurations! //a default XmlGuiConfig does not need these user-specific exclusions! Zstring& excludeFilter = guiCfg.mainCfg.globalFilter.excludeFilter; - if (!excludeFilter.empty() && !endsWith(excludeFilter, Zstr("\n"))) - excludeFilter += Zstr("\n"); + if (!excludeFilter.empty() && !endsWith(excludeFilter, Zstr('\n'))) + excludeFilter += Zstr('\n'); excludeFilter += globalSettings.gui.defaultExclusionFilter; if (!cfgFilePaths.empty()) @@ -463,7 +463,7 @@ MainDialog::MainDialog(const Zstring& globalConfigFilePath, const wxBitmap& bmpDir = IconBuffer::genericDirIcon (IconBuffer::SIZE_SMALL); //init log panel - setRelativeFontSize(*m_staticTextLogStatus, 1.5); + setRelativeFontSize(*m_staticTextSyncResult, 1.5); const wxBitmap& bmpTime = getResourceImage(L"cmp_file_time_sicon"); m_bitmapItemStat->SetBitmap(bmpFile); m_bitmapTimeStat->SetBitmap(bmpTime); @@ -701,7 +701,7 @@ MainDialog::MainDialog(const Zstring& globalConfigFilePath, menu->Append(newItem); //pass ownership const std::wstring blackStar = utfTo("\xE2\x98\x85"); //"BLACK STAR" - m_menubar->Append(menu, blackStar + L" " + replaceCpy(_("FreeFileSync %x is available!"), L"%x", utfTo(globalSettings.gui.lastOnlineVersion)) + L" " + blackStar); + m_menubar->Append(menu, blackStar + L' ' + replaceCpy(_("FreeFileSync %x is available!"), L"%x", utfTo(globalSettings.gui.lastOnlineVersion)) + L' ' + blackStar); } @@ -1246,7 +1246,7 @@ void MainDialog::copySelectionToClipboard(const std::vector& gridRe try { //perf: wxString doesn't model exponential growth and is unsuitable for large data sets - Zstringw clipboardString; + std::wstring clipBuf; for (const Grid* grid : gridRefs) if (auto prov = grid->getDataProvider()) @@ -1258,23 +1258,24 @@ void MainDialog::copySelectionToClipboard(const std::vector& gridRe { std::for_each(colAttr.begin(), colAttr.end() - 1, [&](const Grid::ColAttributes& ca) { - clipboardString += copyStringTo(prov->getValue(row, ca.type)); - clipboardString += L'\t'; + clipBuf += prov->getValue(row, ca.type); + clipBuf += L'\t'; }); - clipboardString += copyStringTo(prov->getValue(row, colAttr.back().type)); - clipboardString += L'\n'; + clipBuf += prov->getValue(row, colAttr.back().type); + clipBuf += L'\n'; } } - if (wxClipboard::Get()->Open()) - { - ZEN_ON_SCOPE_EXIT(wxClipboard::Get()->Close()); - wxClipboard::Get()->SetData(new wxTextDataObject(copyStringTo(clipboardString))); //ownership passed - } + if (!clipBuf.empty()) + if (wxClipboard::Get()->Open()) + { + ZEN_ON_SCOPE_EXIT(wxClipboard::Get()->Close()); + wxClipboard::Get()->SetData(new wxTextDataObject(std::move(clipBuf))); //ownership passed + } } catch (const std::bad_alloc& e) { - showNotificationDialog(this, DialogInfoType::error, PopupDialogCfg().setMainInstructions(_("Out of memory.") + L" " + utfTo(e.what()))); + showNotificationDialog(this, DialogInfoType::error, PopupDialogCfg().setMainInstructions(_("Out of memory.") + L' ' + utfTo(e.what()))); } } @@ -2403,7 +2404,7 @@ void MainDialog::onMainGridContextRim(bool leftSide, GridClickEvent& event) //translate default external apps on the fly: 1. "open in explorer" 2. "start directly" wxString description = translate(it->description); if (description.empty()) - description = L" "; //wxWidgets doesn't like empty labels + description = L' '; //wxWidgets doesn't like empty labels auto openApp = [this, command = it->cmdLine, leftSide, &selectionLeft, &selectionRight] { openExternalApplication(command, leftSide, selectionLeft, selectionRight); }; @@ -2450,7 +2451,7 @@ void MainDialog::addFilterPhrase(const Zstring& phrase, bool include, bool requi { trim(filterString, false, true, [](Zchar c) { return c == FILTER_ITEM_SEPARATOR || c == Zstr('\n') || c == Zstr(' '); }); if (!filterString.empty()) - filterString += Zstr("\n"); + filterString += Zstr('\n'); filterString += phrase; } else @@ -2460,9 +2461,9 @@ void MainDialog::addFilterPhrase(const Zstring& phrase, bool include, bool requi if (filterString.empty()) ; else if (endsWith(filterString, FILTER_ITEM_SEPARATOR)) - filterString += Zstr(" "); + filterString += Zstr(' '); else - filterString += Zstr("\n"); + filterString += Zstr('\n'); filterString += phrase + Zstr(' ') + FILTER_ITEM_SEPARATOR; //append FILTER_ITEM_SEPARATOR to 'mark' that next extension exclude should write to same line } @@ -2506,7 +2507,7 @@ void MainDialog::filterItems(const std::vector& selection, bo FileSystemObject* fsObj = *it; if (it != selection.begin()) - phrase += Zstr("\n"); + phrase += Zstr('\n'); //#pragma warning(suppress: 6011) -> fsObj bound in this context! phrase += FILE_NAME_SEPARATOR + fsObj->getRelativePathAny(); @@ -2832,7 +2833,7 @@ void MainDialog::updateUnsavedCfgStatus() std::vector jobNames; for (const Zstring& cfgFilePath : activeConfigFiles_) jobNames.push_back(equalNativePath(cfgFilePath, lastRunConfigPath_) ? - L"[" + _("Last session") + L"]" : + L'[' + _("Last session") + L']' : extractJobName(cfgFilePath)); const bool haveUnsavedCfg = lastSavedCfg_ != getConfig(); @@ -3181,8 +3182,8 @@ bool MainDialog::loadConfiguration(const std::vector& filePaths) //add default exclusion filter: this is only ever relevant when creating new configurations! //a default XmlGuiConfig does not need these user-specific exclusions! Zstring& excludeFilter = newGuiCfg.mainCfg.globalFilter.excludeFilter; - if (!excludeFilter.empty() && !endsWith(excludeFilter, Zstr("\n"))) - excludeFilter += Zstr("\n"); + if (!excludeFilter.empty() && !endsWith(excludeFilter, Zstr('\n'))) + excludeFilter += Zstr('\n'); excludeFilter += globalCfg_.gui.defaultExclusionFilter; if (!filePaths.empty()) //empty cfg file list means "use default" @@ -3886,7 +3887,7 @@ void MainDialog::OnCompare(wxCommandEvent& event) m_gridOverview->clearSelection(GridEventPolicy::ALLOW); //play (optional) sound notification - if (!globalCfg_.soundFileCompareFinished.empty() && fileAvailable(globalCfg_.soundFileCompareFinished)) + if (!globalCfg_.soundFileCompareFinished.empty()) { //wxWidgets shows modal error dialog by default => NO! wxLog* oldLogTarget = wxLog::SetActiveTarget(new wxLogStderr); //transfer and receive ownership! @@ -4052,7 +4053,7 @@ void MainDialog::OnStartSync(wxCommandEvent& event) std::vector jobNames; for (const Zstring& cfgFilePath : activeConfigFiles_) jobNames.push_back(equalNativePath(cfgFilePath, lastRunConfigPath_) ? - L"[" + _("Last session") + L"]" : + L'[' + _("Last session") + L']' : extractJobName(cfgFilePath)); using FinalRequest = StatusHandlerFloatingDialog::FinalRequest; @@ -4112,7 +4113,7 @@ void MainDialog::OnStartSync(wxCommandEvent& event) catch (AbortProcess&) {} StatusHandlerFloatingDialog::Result r = statusHandler.reportResults(guiCfg.mainCfg.postSyncCommand, guiCfg.mainCfg.postSyncCondition, - guiCfg.mainCfg.altLogFolderPathPhrase, globalCfg_.logfilesMaxAgeDays, logFilePathsToKeep, + guiCfg.mainCfg.altLogFolderPathPhrase, globalCfg_.logfilesMaxAgeDays, globalCfg_.logFormat, logFilePathsToKeep, guiCfg.mainCfg.emailNotifyAddress, guiCfg.mainCfg.emailNotifyCondition); //noexcept //--------------------------------------------------------------------------- @@ -4320,7 +4321,7 @@ void MainDialog::updateConfigLastRunStats(time_t lastRunTime, SyncResult result, void MainDialog::setLastOperationLog(const ProcessSummary& summary, const std::shared_ptr& errorLog) { - const wxBitmap statusImage = [&] + const wxBitmap syncResultImage = [&] { switch (summary.resultStatus) { @@ -4336,23 +4337,22 @@ void MainDialog::setLastOperationLog(const ProcessSummary& summary, const std::s return wxNullBitmap; }(); - const wxImage statusOverlayImage = [&] + const wxImage logOverlayImage = [&] { - switch (summary.resultStatus) + //don't use "resultStatus": There may be errors after sync, e.g. failure to save log file/send email! + if (errorLog) { - case SyncResult::finishedSuccess: - break; - case SyncResult::finishedWarning: - return getResourceImage(L"msg_warning_sicon").ConvertToImage(); - case SyncResult::finishedError: - case SyncResult::aborted: + const ErrorLog::Stats logCount = errorLog->getStats(); + if (logCount.error + logCount.fatal > 0) return getResourceImage(L"msg_error_sicon").ConvertToImage(); + if (logCount.warning > 0) + return getResourceImage(L"msg_warning_sicon").ConvertToImage(); } return wxNullImage; }(); - m_bitmapLogStatus->SetBitmap(statusImage); - m_staticTextLogStatus->SetLabel(getResultsStatusLabel(summary.resultStatus)); + m_bitmapSyncResult->SetBitmap(syncResultImage); + m_staticTextSyncResult->SetLabel(getSyncResultLabel(summary.resultStatus)); m_staticTextItemsProcessed->SetLabel(formatNumber(summary.statsProcessed.items)); @@ -4383,7 +4383,7 @@ void MainDialog::setLastOperationLog(const ProcessSummary& summary, const std::s //m_panelItemStats->Layout(); //needed? //m_panelTimeStats->Layout(); // - setImage(*m_bpButtonShowLog, layOver(getResourceImage(L"log_file").ConvertToImage(), statusOverlayImage, wxALIGN_BOTTOM | wxALIGN_RIGHT)); + setImage(*m_bpButtonShowLog, layOver(getResourceImage(L"log_file").ConvertToImage(), logOverlayImage, wxALIGN_BOTTOM | wxALIGN_RIGHT)); m_bpButtonShowLog->Show(static_cast(errorLog)); } @@ -5425,7 +5425,7 @@ void MainDialog::OnMenuExportFileList(wxCommandEvent& event) //write file FileOutput fileOut(FileOutput::ACC_OVERWRITE, filePath, nullptr /*notifyUnbufferedIO*/); //throw FileError - fileOut.write(&*header.begin(), header.size()); //throw FileError, (X) + fileOut.write(&header[0], header.size()); //throw FileError, (X) //main grid: write rows one after the other instead of creating one big string: memory allocation might fail; think 1 million rows! /* performance test case "export 600.000 rows" to CSV: diff --git a/FreeFileSync/Source/ui/main_dlg.h b/FreeFileSync/Source/ui/main_dlg.h index a18a3223..ceef4712 100644 --- a/FreeFileSync/Source/ui/main_dlg.h +++ b/FreeFileSync/Source/ui/main_dlg.h @@ -18,10 +18,10 @@ #include "sync_cfg.h" #include "log_panel.h" #include "folder_history_box.h" -#include "../base/status_handler.h" +#include "../config.h" +#include "../status_handler.h" #include "../base/algorithm.h" -#include "../base/return_codes.h" - +#include "../return_codes.h" namespace fff { diff --git a/FreeFileSync/Source/ui/progress_indicator.cpp b/FreeFileSync/Source/ui/progress_indicator.cpp index 8cb37ee5..04c20716 100644 --- a/FreeFileSync/Source/ui/progress_indicator.cpp +++ b/FreeFileSync/Source/ui/progress_indicator.cpp @@ -24,13 +24,13 @@ #include #include #include "gui_generated.h" -#include "../base/ffs_paths.h" -#include "../base/perf_check.h" -#include "../base/icon_buffer.h" #include "tray_icon.h" #include "taskbar.h" #include "log_panel.h" #include "app_icon.h" +#include "../ffs_paths.h" +#include "../perf_check.h" +#include "../icon_buffer.h" using namespace zen; @@ -998,9 +998,9 @@ template void SyncProgressDialogImpl::setExternalStatus(const wxString& status, const wxString& progress) //progress may be empty! { //sys tray: order "top-down": jobname, status, progress - wxString systrayTooltip = jobName_.empty() ? status : jobName_ + L"\n" + status; + wxString systrayTooltip = jobName_.empty() ? status : jobName_ + L'\n' + status; if (!progress.empty()) - systrayTooltip += L" " + progress; + systrayTooltip += L' ' + progress; //window caption/taskbar; inverse order: progress, status, jobname wxString title = progress.empty() ? status : progress + L" | " + status; @@ -1009,7 +1009,7 @@ void SyncProgressDialogImpl::setExternalStatus(const wxString& s title += L" | " + jobName_; const TimeComp tc = getLocalTime(std::chrono::system_clock::to_time_t(syncStartTime_)); //returns empty string on failure - title += L" | " + formatTime(FORMAT_DATE_TIME, tc); + title += L" | " + utfTo(formatTime(formatDateTimeTag, tc)); //--------------------------------------------------------------------------- //systray tooltip, if window is minimized @@ -1341,7 +1341,7 @@ void SyncProgressDialogImpl::showSummary(SyncResult resultStatus }(); pnl_.m_bitmapStatus->SetBitmap(statusImage); - pnl_.m_staticTextPhase->SetLabel(getResultsStatusLabel(resultStatus)); + pnl_.m_staticTextPhase->SetLabel(getSyncResultLabel(resultStatus)); //pnl_.m_bitmapStatus->SetToolTip(); -> redundant //show status on Windows 7 taskbar @@ -1360,7 +1360,7 @@ void SyncProgressDialogImpl::showSummary(SyncResult resultStatus } //---------------------------------- - setExternalStatus(getResultsStatusLabel(resultStatus), wxString()); + setExternalStatus(getSyncResultLabel(resultStatus), wxString()); //this->EnableCloseButton(true); @@ -1407,7 +1407,8 @@ void SyncProgressDialogImpl::showSummary(SyncResult resultStatus pnl_.m_notebookResult->AddPage(logPanel, _("Log"), false /*bSelect*/); //show log instead of graph if errors occurred! (not required for ignored warnings) - if (log.ref().getItemCount(MSG_TYPE_ERROR | MSG_TYPE_FATAL_ERROR) > 0) + const ErrorLog::Stats logCount = log.ref().getStats(); + if (logCount.error + logCount.fatal > 0) pnl_.m_notebookResult->ChangeSelection(pagePosLog); //fill image list to cope with wxNotebook image setting design desaster... @@ -1443,11 +1444,12 @@ void SyncProgressDialogImpl::showSummary(SyncResult resultStatus switch (resultStatus) { case SyncResult::aborted: + warn_static("we really should play sound if cancel on error is set, and only not play if user-aborted") break; case SyncResult::finishedError: case SyncResult::finishedWarning: case SyncResult::finishedSuccess: - if (!soundFileSyncComplete_.empty() && fileAvailable(soundFileSyncComplete_)) + if (!soundFileSyncComplete_.empty()) { //wxWidgets shows modal error dialog by default => NO! wxLog* oldLogTarget = wxLog::SetActiveTarget(new wxLogStderr); //transfer and receive ownership! diff --git a/FreeFileSync/Source/ui/progress_indicator.h b/FreeFileSync/Source/ui/progress_indicator.h index 5289f5e9..300078fa 100644 --- a/FreeFileSync/Source/ui/progress_indicator.h +++ b/FreeFileSync/Source/ui/progress_indicator.h @@ -11,9 +11,9 @@ #include #include #include -#include "../base/config.h" -#include "../base/status_handler.h" -#include "../base/return_codes.h" +#include "../config.h" +#include "../status_handler.h" +#include "../return_codes.h" namespace fff diff --git a/FreeFileSync/Source/ui/small_dlgs.cpp b/FreeFileSync/Source/ui/small_dlgs.cpp index d7bcebe9..39855f3c 100644 --- a/FreeFileSync/Source/ui/small_dlgs.cpp +++ b/FreeFileSync/Source/ui/small_dlgs.cpp @@ -31,19 +31,19 @@ #include "folder_selector.h" #include "version_check.h" #include "abstract_folder_picker.h" +#include "../afs/concrete.h" +#include "../afs/gdrive.h" +#include "../afs/ftp.h" +#include "../afs/sftp.h" #include "../base/algorithm.h" -#include "../base/ffs_paths.h" #include "../base/synchronization.h" -#include "../base/help_provider.h" #include "../base/path_filter.h" -#include "../base/status_handler.h" //uiUpdateDue() -#include "../base/log_file.h" -#include "../base/icon_buffer.h" +#include "../status_handler.h" //uiUpdateDue() #include "../version/version.h" -#include "../afs/concrete.h" -#include "../afs/gdrive.h" -#include "../afs/sftp.h" -#include "../afs/ftp.h" +#include "../log_file.h" +#include "../ffs_paths.h" +#include "../help_provider.h" +#include "../icon_buffer.h" @@ -98,7 +98,7 @@ AboutDlg::AboutDlg(wxWindow* parent) : AboutDlgGenerated(parent) #endif build += SPACED_BULLET; - build += formatTime(FORMAT_DATE, getCompileTime()); + build += utfTo(formatTime(formatDateTag, getCompileTime())); m_staticTextVersion->SetLabel(replaceCpy(_("Version: %x"), L"%x", build)); @@ -275,7 +275,7 @@ CloudSetupDlg::CloudSetupDlg(wxWindow* parent, Zstring& folderPathPhrase, size_t m_staticTextConnectionsLabelSub->SetLabel(L"(" + _("Connections") + L")"); //use spacer to keep dialog height stable, no matter if key file options are visible - bSizerAuthInner->Add(0, m_panelAuth->GetSize().GetHeight()); + bSizerAuthInner->Add(0, m_panelAuth->GetSize().y); //--------------------------------------------------------- wxArrayString googleUsers; @@ -480,10 +480,11 @@ bool CloudSetupDlg::acceptFileDrop(const std::vector& shellItemPaths) { if (shellItemPaths.empty()) return false; + const Zstring ext = getFileExtension(shellItemPaths[0]); return ext.empty() || - equalAsciiNoCase(ext, Zstr("pem")) || - equalAsciiNoCase(ext, Zstr("ppk")); + equalAsciiNoCase(ext, "pem") || + equalAsciiNoCase(ext, "ppk"); } @@ -1051,14 +1052,14 @@ public: OptionsDlg(wxWindow* parent, XmlGlobalSettings& globalCfg); private: - void OnOkay (wxCommandEvent& event) override; - void OnResetDialogs(wxCommandEvent& event) override; - void OnDefault (wxCommandEvent& event) override; - void OnCancel (wxCommandEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } - void OnClose (wxCloseEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } - void OnAddRow (wxCommandEvent& event) override; - void OnRemoveRow (wxCommandEvent& event) override; - void OnHelpShowExamples(wxHyperlinkEvent& event) override { displayHelpEntry(L"external-applications", this); } + void OnOkay (wxCommandEvent& event) override; + void OnRestoreDialogs(wxCommandEvent& event) override; + void OnDefault (wxCommandEvent& event) override; + void OnCancel (wxCommandEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } + void OnClose (wxCloseEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } + void OnAddRow (wxCommandEvent& event) override; + void OnRemoveRow (wxCommandEvent& event) override; + void OnHelpExternalApps(wxHyperlinkEvent& event) override { displayHelpEntry(L"external-applications", this); } void OnShowLogFolder (wxHyperlinkEvent& event) override; void OnToggleLogfilesLimit(wxCommandEvent& event) override { updateGui(); } @@ -1111,10 +1112,9 @@ OptionsDlg::OptionsDlg(wxWindow* parent, XmlGlobalSettings& globalSettings) : m_hyperlinkLogFolder->SetLabel(utfTo(getDefaultLogFolderPath())); setRelativeFontSize(*m_hyperlinkLogFolder, 1.2); - m_staticTextResetDialogs->Wrap(std::max(fastFromDIP(250), m_buttonResetDialogs->GetMinSize().x)); - m_bitmapSettings ->SetBitmap (getResourceImage(L"settings")); - m_bitmapLogFile ->SetBitmap(shrinkImage(getResourceImage(L"log_file").ConvertToImage(), fastFromDIP(20))); + m_bitmapWarnings ->SetBitmap(shrinkImage(getResourceImage(L"msg_warning").ConvertToImage(), fastFromDIP(20))); + m_bitmapLogFile ->SetBitmap(shrinkImage(getResourceImage(L"log_file" ).ConvertToImage(), fastFromDIP(20))); m_bitmapNotificationSounds->SetBitmap (getResourceImage(L"notification_sounds")); m_bitmapCompareDone ->SetBitmap (getResourceImage(L"compare_sicon")); m_bitmapSyncDone ->SetBitmap (getResourceImage(L"file_sync_sicon")); @@ -1123,6 +1123,12 @@ OptionsDlg::OptionsDlg(wxWindow* parent, XmlGlobalSettings& globalSettings) : m_bpButtonAddRow ->SetBitmapLabel(getResourceImage(L"item_add")); m_bpButtonRemoveRow ->SetBitmapLabel(getResourceImage(L"item_remove")); + m_staticTextAllDialogsShown->SetLabel(L"(" + _("All dialogs shown") + L")"); + + m_staticTextResetDialogs->Wrap(std::max(fastFromDIP(250), + m_buttonRestoreDialogs ->GetSize().x + + m_staticTextAllDialogsShown->GetSize().x)); + //-------------------------------------------------------------------------------- m_checkBoxFailSafe ->SetValue(globalSettings.failSafeFileCopy); m_checkBoxCopyLocked ->SetValue(globalSettings.copyLockedFiles); @@ -1131,19 +1137,29 @@ OptionsDlg::OptionsDlg(wxWindow* parent, XmlGlobalSettings& globalSettings) : m_checkBoxLogFilesMaxAge->SetValue(globalSettings.logfilesMaxAgeDays > 0); m_spinCtrlLogFilesMaxAge->SetValue(globalSettings.logfilesMaxAgeDays > 0 ? globalSettings.logfilesMaxAgeDays : XmlGlobalSettings().logfilesMaxAgeDays); + switch (globalSettings.logFormat) + { + case LogFileFormat::html: + m_radioBtnLogHtml->SetValue(true); + break; + case LogFileFormat::text: + m_radioBtnLogText->SetValue(true); + break; + } + m_textCtrlSoundPathCompareDone->ChangeValue(utfTo(globalSettings.soundFileCompareFinished)); m_textCtrlSoundPathSyncDone ->ChangeValue(utfTo(globalSettings.soundFileSyncFinished)); - - setExtApp(globalSettings.gui.externalApps); //-------------------------------------------------------------------------------- - updateGui(); - bSizerLockedFiles->Show(false); m_gridCustomCommand->SetMargins(0, 0); //temporarily set dummy value for window height calculations: setExtApp(std::vector(globalSettings.gui.externalApps.size() + 1)); + confirmDlgs_ = defaultCfg_.confirmDlgs; // + warnDlgs_ = defaultCfg_.warnDlgs; //make sure m_staticTextAllDialogsShown is shown + autoCloseProgressDialog_ = defaultCfg_.autoCloseProgressDialog; // + updateGui(); GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize() //=> works like a charm for GTK2 with window resizing problems and title bar corruption; e.g. Debian!!! @@ -1151,6 +1167,10 @@ OptionsDlg::OptionsDlg(wxWindow* parent, XmlGlobalSettings& globalSettings) : //restore actual value: setExtApp(globalSettings.gui.externalApps); + confirmDlgs_ = globalSettings.confirmDlgs; + warnDlgs_ = globalSettings.warnDlgs; + autoCloseProgressDialog_ = globalSettings.autoCloseProgressDialog; + updateGui(); //automatically fit column width to match total grid width Connect(wxEVT_SIZE, wxSizeEventHandler(OptionsDlg::onResize), nullptr, this); @@ -1183,10 +1203,9 @@ void OptionsDlg::updateGui() warnDlgs_ != defaultCfg_.warnDlgs || autoCloseProgressDialog_ != defaultCfg_.autoCloseProgressDialog; - setBitmapTextLabel(*m_buttonResetDialogs, shrinkImage(getResourceImage(L"msg_warning").ConvertToImage(), fastFromDIP(20)), - haveHiddenDialogs ? _("Show hidden dialogs again") : _("All dialogs shown")); + m_buttonRestoreDialogs->Enable(haveHiddenDialogs); + m_staticTextAllDialogsShown->Show(!haveHiddenDialogs); Layout(); - m_buttonResetDialogs->Enable(haveHiddenDialogs); m_spinCtrlLogFilesMaxAge->Enable(m_checkBoxLogFilesMaxAge->GetValue()); @@ -1195,7 +1214,7 @@ void OptionsDlg::updateGui() } -void OptionsDlg::OnResetDialogs(wxCommandEvent& event) +void OptionsDlg::OnRestoreDialogs(wxCommandEvent& event) { confirmDlgs_ = defaultCfg_.confirmDlgs; warnDlgs_ = defaultCfg_.warnDlgs; @@ -1248,6 +1267,16 @@ void OptionsDlg::OnDefault(wxCommandEvent& event) m_checkBoxLogFilesMaxAge->SetValue(defaultCfg_.logfilesMaxAgeDays > 0); m_spinCtrlLogFilesMaxAge->SetValue(defaultCfg_.logfilesMaxAgeDays > 0 ? defaultCfg_.logfilesMaxAgeDays : 14); + switch (defaultCfg_.logFormat) + { + case LogFileFormat::html: + m_radioBtnLogHtml->SetValue(true); + break; + case LogFileFormat::text: + m_radioBtnLogText->SetValue(true); + break; + } + m_textCtrlSoundPathCompareDone->ChangeValue(utfTo(defaultCfg_.soundFileCompareFinished)); m_textCtrlSoundPathSyncDone ->ChangeValue(utfTo(defaultCfg_.soundFileSyncFinished)); @@ -1265,6 +1294,7 @@ void OptionsDlg::OnOkay(wxCommandEvent& event) globalCfgOut_.copyFilePermissions = m_checkBoxCopyPermissions->GetValue(); globalCfgOut_.logfilesMaxAgeDays = m_checkBoxLogFilesMaxAge->GetValue() ? m_spinCtrlLogFilesMaxAge->GetValue() : -1; + globalCfgOut_.logFormat = m_radioBtnLogHtml->GetValue() ? LogFileFormat::html : LogFileFormat::text; globalCfgOut_.soundFileCompareFinished = utfTo(trimCpy(m_textCtrlSoundPathCompareDone->GetValue())); globalCfgOut_.soundFileSyncFinished = utfTo(trimCpy(m_textCtrlSoundPathSyncDone ->GetValue())); @@ -1555,7 +1585,7 @@ ActivationDlg::ActivationDlg(wxWindow* parent, { setStandardButtonLayout(*bSizerStdButtons, StdButtons().setCancel(m_buttonCancel)); - SetTitle(std::wstring(L"FreeFileSync ") + ffsVersion + L" [" + _("Donation Edition") + L"]"); + SetTitle(std::wstring(L"FreeFileSync ") + ffsVersion + L" [" + _("Donation Edition") + L']'); //setMainInstructionFont(*m_staticTextMain); @@ -1636,7 +1666,7 @@ private: void updateGui() { const double fraction = bytesTotal_ == 0 ? 0 : 1.0 * bytesCurrent_ / bytesTotal_; - m_staticTextHeader->SetLabel(_("Downloading update...") + L" " + + m_staticTextHeader->SetLabel(_("Downloading update...") + L' ' + numberTo(numeric::round(fraction * 100)) + L"% (" + formatFilesizeShort(bytesCurrent_) + L")"); m_gaugeProgress->SetValue(numeric::round(fraction * GAUGE_FULL_RANGE)); diff --git a/FreeFileSync/Source/ui/small_dlgs.h b/FreeFileSync/Source/ui/small_dlgs.h index ace76582..1849dc18 100644 --- a/FreeFileSync/Source/ui/small_dlgs.h +++ b/FreeFileSync/Source/ui/small_dlgs.h @@ -8,8 +8,8 @@ #define SMALL_DLGS_H_8321790875018750245 #include -#include "../base/config.h" #include "../base/synchronization.h" +#include "../config.h" namespace fff diff --git a/FreeFileSync/Source/ui/sync_cfg.cpp b/FreeFileSync/Source/ui/sync_cfg.cpp index 243fd315..318ec3b9 100644 --- a/FreeFileSync/Source/ui/sync_cfg.cpp +++ b/FreeFileSync/Source/ui/sync_cfg.cpp @@ -21,11 +21,12 @@ #include "gui_generated.h" #include "command_box.h" #include "folder_selector.h" -#include "../base/file_hierarchy.h" -#include "../base/help_provider.h" -#include "../base/log_file.h" #include "../base/norm_filter.h" +#include "../base/file_hierarchy.h" +#include "../help_provider.h" +#include "../log_file.h" #include "../afs/concrete.h" +#include "../base_tools.h" @@ -99,7 +100,7 @@ private: std::map deviceParallelOps_; // //------------- filter panel -------------------------- - void OnHelpShowExamples(wxHyperlinkEvent& event) override { displayHelpEntry(L"exclude-items", this); } + void OnHelpFilterSettings(wxHyperlinkEvent& event) override { displayHelpEntry(L"exclude-items", this); } void OnChangeFilterOption(wxCommandEvent& event) override { updateFilterGui(); } void OnFilterReset (wxCommandEvent& event) override { setFilterConfig(FilterConfig()); } @@ -143,7 +144,13 @@ private: void OnDeletionRecycler (wxCommandEvent& event) override { handleDeletion_ = DeletionPolicy::recycler; updateSyncGui(); } void OnDeletionVersioning (wxCommandEvent& event) override { handleDeletion_ = DeletionPolicy::versioning; updateSyncGui(); } - void OnToggleMiscOption (wxCommandEvent& event) override { updateMiscGui(); } + void OnToggleMiscOption(wxCommandEvent& event) override { updateMiscGui(); } + void OnToggleMiscEmail (wxCommandEvent& event) override + { + OnToggleMiscOption(event); + if (event.IsChecked()) //optimize UX + m_comboBoxEmail->SetFocus(); // + } void OnEmailAlways (wxCommandEvent& event) override { emailNotifyCondition_ = ResultsNotification::always; updateMiscGui(); } void OnEmailErrorWarning(wxCommandEvent& event) override { emailNotifyCondition_ = ResultsNotification::errorWarning; updateMiscGui(); } void OnEmailErrorOnly (wxCommandEvent& event) override { emailNotifyCondition_ = ResultsNotification::errorOnly; updateMiscGui(); } @@ -283,7 +290,7 @@ emailHistoryOut_(emailHistory), commandHistoryOut_(commandHistory), globalPairCfg_(globalPairCfg), localPairCfg_(localPairConfig), -enableExtraFeatures_(false), + enableExtraFeatures_(false), showMultipleCfgs_(showMultipleCfgs) { setStandardButtonLayout(*bSizerStdButtons, StdButtons().setAffirmative(m_buttonOkay).setCancel(m_buttonCancel)); @@ -393,8 +400,8 @@ showMultipleCfgs_(showMultipleCfgs) enumVersioningStyle_. add(VersioningStyle::replace, _("Replace"), _("Move files and replace if existing")). - add(VersioningStyle::timestampFolder, _("Time stamp") + L" [" + _("Folder") + L"]", _("Move files into a time-stamped subfolder")). - add(VersioningStyle::timestampFile, _("Time stamp") + L" [" + _("File") + L"]", _("Append a time stamp to each file name")); + add(VersioningStyle::timestampFolder, _("Time stamp") + L" [" + _("Folder") + L']', _("Move files into a time-stamped subfolder")). + add(VersioningStyle::timestampFile, _("Time stamp") + L" [" + _("File") + L']', _("Append a time stamp to each file name")); m_spinCtrlVersionMaxDays ->SetMinSize({fastFromDIP(60), -1}); // m_spinCtrlVersionCountMin->SetMinSize({fastFromDIP(60), -1}); //Hack: set size (why does wxWindow::Size() not work?) @@ -407,7 +414,6 @@ showMultipleCfgs_(showMultipleCfgs) m_comboBoxEmail->SetHint(/*_("Example:") + */ L"john.doe@example.com"); m_comboBoxEmail->setHistory(emailHistory, emailHistoryMax); - m_checkBoxSendEmail ->Enable(enableExtraFeatures_); m_comboBoxEmail ->Enable(enableExtraFeatures_); m_bpButtonEmailAlways ->Enable(enableExtraFeatures_); m_bpButtonEmailErrorWarning ->Enable(enableExtraFeatures_); @@ -455,7 +461,7 @@ showMultipleCfgs_(showMultipleCfgs) globalPairCfg_.syncCfg.versioningStyle = VersioningStyle::timestampFile; // globalPairCfg_.syncCfg.versionMaxAgeDays = 30; // globalPairCfg_.miscCfg.altLogFolderPathPhrase = Zstr("dummy"); // - globalPairCfg_.miscCfg.emailNotifyAddress = Zstr("dummy"); // + globalPairCfg_.miscCfg.emailNotifyAddress = "dummy"; // selectFolderPairConfig(-1); @@ -1196,13 +1202,13 @@ MiscSyncConfig ConfigDialog::getMiscSyncOptions() const //---------------------------------------------------------------------------- Zstring altLogPathPhrase = logfileDir_.getPath(); if (altLogPathPhrase.empty()) - altLogPathPhrase = Zstr(" "); //trigger error message on dialog close + altLogPathPhrase = Zstr(' '); //trigger error message on dialog close miscCfg.altLogFolderPathPhrase = m_checkBoxOverrideLogPath->GetValue() ? altLogPathPhrase : Zstring(); //---------------------------------------------------------------------------- - Zstring emailAddress = m_comboBoxEmail->getValue(); + std::string emailAddress = utfTo(m_comboBoxEmail->getValue()); if (emailAddress.empty()) - emailAddress = Zstr(" "); //trigger error message on dialog close - miscCfg.emailNotifyAddress = m_checkBoxSendEmail->GetValue() ? emailAddress : Zstring(); + emailAddress = ' '; //trigger error message on dialog close + miscCfg.emailNotifyAddress = m_checkBoxSendEmail->GetValue() ? emailAddress : std::string(); miscCfg.emailNotifyCondition = emailNotifyCondition_; //---------------------------------------------------------------------------- return miscCfg; @@ -1264,8 +1270,13 @@ void ConfigDialog::setMiscSyncOptions(const MiscSyncConfig& miscCfg) logfileDir_.setPath(m_checkBoxOverrideLogPath->GetValue() ? miscCfg.altLogFolderPathPhrase : getDefaultLogFolderPath()); //can't use logfileDir_.setBackgroundText(): no text shown when control is disabled! //---------------------------------------------------------------------------- + Zstring defaultEmail; + if (const std::vector& history = m_comboBoxEmail->getHistory(); + !history.empty()) + defaultEmail = history[0]; + m_checkBoxSendEmail->SetValue(!trimCpy(miscCfg.emailNotifyAddress).empty()); - m_comboBoxEmail->setValue(miscCfg.emailNotifyAddress); + m_comboBoxEmail->setValue(m_checkBoxSendEmail->GetValue() ? utfTo(miscCfg.emailNotifyAddress) : defaultEmail); emailNotifyCondition_ = miscCfg.emailNotifyCondition; //---------------------------------------------------------------------------- updateMiscGui(); @@ -1284,7 +1295,7 @@ void ConfigDialog::updateMiscGui() m_panelComparisonSettings->Layout(); //showing "retry count" can affect bSizerPerformance! //---------------------------------------------------------------------------- const bool sendEmailEnabled = m_checkBoxSendEmail->GetValue(); - m_bitmapEmail->SetBitmap(shrinkImage(greyScaleIfDisabled(getResourceImage(L"email"), sendEmailEnabled).ConvertToImage(), fastFromDIP(24))); + m_bitmapEmail->SetBitmap(greyScaleIfDisabled(getResourceImage(L"email"), sendEmailEnabled).ConvertToImage()); m_comboBoxEmail->Show(sendEmailEnabled); auto updateButton = [successIcon = getResourceImage(L"msg_success_sicon").ConvertToImage(), @@ -1334,7 +1345,9 @@ void ConfigDialog::updateMiscGui() m_bpButtonSelectAltLogFolder->Show(m_checkBoxOverrideLogPath->GetValue()); // m_panelSyncSettings->Layout(); //after showing/hiding m_buttonSelectLogFolder - m_panelLogfile->Refresh(); //removes a few artifacts when toggling email notifications + + m_panelSyncSettings->Refresh(); //removes a few artifacts when toggling email notifications + m_panelLogfile ->Refresh();// } diff --git a/FreeFileSync/Source/ui/sync_cfg.h b/FreeFileSync/Source/ui/sync_cfg.h index 568b1461..c9e481f7 100644 --- a/FreeFileSync/Source/ui/sync_cfg.h +++ b/FreeFileSync/Source/ui/sync_cfg.h @@ -41,7 +41,7 @@ struct MiscSyncConfig Zstring altLogFolderPathPhrase; - Zstring emailNotifyAddress; + std::string emailNotifyAddress; ResultsNotification emailNotifyCondition = ResultsNotification::always; }; diff --git a/FreeFileSync/Source/ui/tree_grid.cpp b/FreeFileSync/Source/ui/tree_grid.cpp index 963ebdd0..e9a4ab62 100644 --- a/FreeFileSync/Source/ui/tree_grid.cpp +++ b/FreeFileSync/Source/ui/tree_grid.cpp @@ -17,7 +17,7 @@ #include #include #include -#include "../base/icon_buffer.h" +#include "../icon_buffer.h" using namespace zen; using namespace fff; @@ -736,7 +736,7 @@ private: return dirRight; else if (dirRight.empty()) return dirLeft; - return dirLeft + L" " + EN_DASH + L"\n" + dirRight; + return dirLeft + L' ' + EN_DASH + L'\n' + dirRight; } break; @@ -1206,7 +1206,7 @@ TreeView& treegrid::getDataView(Grid& grid) { if (auto* prov = dynamic_cast(grid.getDataProvider())) return prov->getDataView(); - throw std::runtime_error(std::string(__FILE__) + "[" + numberTo(__LINE__) + "] treegrid was not initialized."); + throw std::runtime_error(std::string(__FILE__) + '[' + numberTo(__LINE__) + "] treegrid was not initialized."); } diff --git a/FreeFileSync/Source/ui/version_check.cpp b/FreeFileSync/Source/ui/version_check.cpp index 49a1c321..ac74401a 100644 --- a/FreeFileSync/Source/ui/version_check.cpp +++ b/FreeFileSync/Source/ui/version_check.cpp @@ -19,7 +19,7 @@ #include #include #include -#include "../base/ffs_paths.h" +#include "../ffs_paths.h" #include "../version/version.h" #include "small_dlgs.h" @@ -167,7 +167,7 @@ void showUpdateAvailableDialog(wxWindow* parent, const std::string& onlineVersio switch (showConfirmationDialog(parent, DialogInfoType::info, PopupDialogCfg(). setIcon(getResourceImage(L"update_available")). setTitle(_("Check for Program Updates")). - setMainInstructions(replaceCpy(_("FreeFileSync %x is available!"), L"%x", utfTo(onlineVersion)) + L" " + _("Download now?")). + setMainInstructions(replaceCpy(_("FreeFileSync %x is available!"), L"%x", utfTo(onlineVersion)) + L' ' + _("Download now?")). setDetailInstructions(updateDetailsMsg), _("&Download"))) { diff --git a/FreeFileSync/Source/version/version.h b/FreeFileSync/Source/version/version.h index 933b9f4c..5496cd99 100644 --- a/FreeFileSync/Source/version/version.h +++ b/FreeFileSync/Source/version/version.h @@ -3,7 +3,7 @@ namespace fff { -const char ffsVersion[] = "10.20"; //internal linkage! +const char ffsVersion[] = "10.21"; //internal linkage! const char FFS_VERSION_SEPARATOR = '.'; } diff --git a/libcurl/curl_wrap.h b/libcurl/curl_wrap.h index dca056fc..40694e71 100644 --- a/libcurl/curl_wrap.h +++ b/libcurl/curl_wrap.h @@ -137,10 +137,11 @@ std::wstring formatCurlStatusCode(CURLcode sc) ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_AUTH_ERROR); ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_HTTP3); ZEN_CHECK_CASE_FOR_CONSTANT(CURL_LAST); + ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_QUIC_CONNECT_ERROR); } - static_assert(CURL_LAST == CURLE_HTTP3 + 1); + static_assert(CURL_LAST == CURLE_QUIC_CONNECT_ERROR + 1); - return replaceCpy(L"Curl status %x.", L"%x", numberTo(static_cast(sc))); + return replaceCpy(L"Curl status %x", L"%x", numberTo(static_cast(sc))); } diff --git a/libcurl/rest.cpp b/libcurl/rest.cpp index 22583483..0d14dfc2 100644 --- a/libcurl/rest.cpp +++ b/libcurl/rest.cpp @@ -125,10 +125,10 @@ HttpSession::Result HttpSession::perform(const std::string& serverRelPath, } if (std::any_of(extraOptions.begin(), extraOptions.end(), [](const CurlOption& o) { return o.option == CURLOPT_WRITEFUNCTION || o.option == CURLOPT_READFUNCTION; })) - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo(__LINE__)); //Option already used here! + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); //Option already used here! if (readRequest && std::any_of(extraOptions.begin(), extraOptions.end(), [](const CurlOption& o) { return o.option == CURLOPT_POSTFIELDS; })) - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo(__LINE__)); //Contradicting options: CURLOPT_READFUNCTION, CURLOPT_POSTFIELDS + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); //Contradicting options: CURLOPT_READFUNCTION, CURLOPT_POSTFIELDS //--------------------------------------------------- curl_slist* headers = nullptr; //"libcurl will not copy the entire list so you must keep it!" @@ -160,15 +160,15 @@ HttpSession::Result HttpSession::perform(const std::string& serverRelPath, std::rethrow_exception(userCallbackException); //throw X //======================================================================================================= - long httpStatusCode = 0; //optional - /*const CURLcode rc = */ ::curl_easy_getinfo(easyHandle_, CURLINFO_RESPONSE_CODE, &httpStatusCode); + long httpStatus = 0; //optional + /*const CURLcode rc = */ ::curl_easy_getinfo(easyHandle_, CURLINFO_RESPONSE_CODE, &httpStatus); if (rcPerf != CURLE_OK) { std::wstring errorMsg = trimCpy(utfTo(curlErrorBuf)); //optional - if (httpStatusCode != 0) //optional - errorMsg += (errorMsg.empty() ? L"" : L"\n") + formatHttpStatusCode(httpStatusCode); + if (httpStatus != 0) //optional + errorMsg += (errorMsg.empty() ? L"" : L"\n") + formatHttpStatus(httpStatus); #if 0 //utfTo(::curl_easy_strerror(ec)) is uninteresting //use CURLINFO_OS_ERRNO ?? https://curl.haxx.se/libcurl/c/CURLINFO_OS_ERRNO.html @@ -181,5 +181,5 @@ HttpSession::Result HttpSession::perform(const std::string& serverRelPath, } lastSuccessfulUseTime_ = std::chrono::steady_clock::now(); - return { static_cast(httpStatusCode) /*, contentType ? contentType : ""*/ }; + return { static_cast(httpStatus) /*, contentType ? contentType : ""*/ }; } diff --git a/libssh2/libssh2_wrap.h b/libssh2/libssh2_wrap.h index 37a62a24..98b73af2 100644 --- a/libssh2/libssh2_wrap.h +++ b/libssh2/libssh2_wrap.h @@ -174,8 +174,10 @@ std::wstring formatSshStatusCode(int sc) ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_ERROR_KNOWN_HOSTS); ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_ERROR_CHANNEL_WINDOW_FULL); ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_ERROR_KEYFILE_AUTH_FAILED); + + default: + return replaceCpy(L"SSH status %x", L"%x", numberTo(sc)); } - return replaceCpy(L"SSH status %x.", L"%x", numberTo(sc)); } @@ -219,7 +221,7 @@ std::wstring formatSftpStatusCode(unsigned long sc) case 30: return L"SSH_FX_GROUP_INVALID"; case 31: return L"SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK"; - default: return replaceCpy(L"SFTP status %x.", L"%x", numberTo(sc)); + default: return replaceCpy(L"SFTP status %x", L"%x", numberTo(sc)); //*INDENT-ON* } } diff --git a/wx+/file_drop.cpp b/wx+/file_drop.cpp index 65d5d861..938f9dbd 100644 --- a/wx+/file_drop.cpp +++ b/wx+/file_drop.cpp @@ -23,9 +23,23 @@ namespace class WindowDropTarget : public wxFileDropTarget { public: - WindowDropTarget(wxWindow& dropWindow) : dropWindow_(dropWindow) {} + WindowDropTarget(const wxWindow& dropWindow) : dropWindow_(dropWindow) {} private: + wxDragResult OnDragOver(wxCoord x, wxCoord y, wxDragResult def) override + { + //why the FUCK I is drag & drop still working while showing another modal dialog!??? + //why the FUCK II is drag & drop working even when dropWindow is disabled!?? [Windows] => we can fix this + //why the FUCK III is dropWindow NOT disabled while showing another modal dialog!??? [macOS, Linux] => we CANNOT fix this: FUUUUUUUUUUUUUU... + if (!dropWindow_.IsEnabled()) + return wxDragNone; + + return wxFileDropTarget::OnDragOver(x, y, def); + } + + //"bool wxDropTarget::GetData() [...] This method may only be called from within OnData()." + //=> FUUUUUUUUUUUUUU........ a.k.a. no support for DragDropValidator during mouse hover! >:( + bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& fileArray) override { /*Linux, MTP: we get an empty file array @@ -35,6 +49,9 @@ private: /run/user/1000/gvfs/mtp:host=%5Busb%3A001%2C002%5D/Telefonspeicher/Folder/file.txt */ + if (!dropWindow_.IsEnabled()) + return false; + //wxPoint clientDropPos(x, y) std::vector filePaths; for (const wxString& file : fileArray) @@ -46,12 +63,12 @@ private: return true; } - wxWindow& dropWindow_; + const wxWindow& dropWindow_; }; } -void zen::setupFileDrop(wxWindow& wnd) +void zen::setupFileDrop(wxWindow& dropWindow) { - wnd.SetDropTarget(new WindowDropTarget(wnd)); /*takes ownership*/ + dropWindow.SetDropTarget(new WindowDropTarget(dropWindow)); /*takes ownership*/ } diff --git a/wx+/file_drop.h b/wx+/file_drop.h index 9826bf27..0a1089fc 100644 --- a/wx+/file_drop.h +++ b/wx+/file_drop.h @@ -58,8 +58,7 @@ using FileDropEventFunction = void (wxEvtHandler::*)(FileDropEvent&); - -void setupFileDrop(wxWindow& wnd); +void setupFileDrop(wxWindow& dropWindow); } #endif //FILE_DROP_H_09457802957842560325626 diff --git a/wx+/grid.cpp b/wx+/grid.cpp index fe168df6..c7b43d4a 100644 --- a/wx+/grid.cpp +++ b/wx+/grid.cpp @@ -159,7 +159,7 @@ wxSize GridData::drawCellText(wxDC& dc, const wxRect& rect, const std::wstring& */ //truncate large texts and add ellipsis - assert(!contains(text, L"\n")); + assert(!contains(text, L'\n')); std::wstring textTrunc = text; wxSize extentTrunc = dc.GetTextExtent(textTrunc); diff --git a/wx+/image_resources.cpp b/wx+/image_resources.cpp index 070b9112..255a352e 100644 --- a/wx+/image_resources.cpp +++ b/wx+/image_resources.cpp @@ -231,7 +231,7 @@ void GlobalBitmaps::init(const Zstring& zipPath) { wxMemoryInputStream wxstream(stream.c_str(), stream.size()); //stream does not take ownership of data //bonus: work around wxWidgets bug: wxAnimation::Load() requires seekable input stream (zip-input stream is not seekable) - + if (endsWith(fileName, L".png")) { wxImage img(wxstream, wxBITMAP_TYPE_PNG); diff --git a/wx+/image_tools.cpp b/wx+/image_tools.cpp index 8f94d1bc..9fcc5563 100644 --- a/wx+/image_tools.cpp +++ b/wx+/image_tools.cpp @@ -120,7 +120,7 @@ std::vector> getTextExtentInfo(const wxString& text, dc.SetFont(font); //the font parameter of GetMultiLineTextExtent() is not evalated on OS X, wxWidgets 2.9.5, so apply it to the DC directly! std::vector> lineInfo; //text + extent - for (const wxString& line : split(text, L"\n", SplitType::ALLOW_EMPTY)) + for (const wxString& line : split(text, L'\n', SplitType::ALLOW_EMPTY)) lineInfo.emplace_back(line, line.empty() ? wxSize() : dc.GetTextExtent(line)); return lineInfo; diff --git a/wx+/popup_dlg.cpp b/wx+/popup_dlg.cpp index 11c4511f..c677cf57 100644 --- a/wx+/popup_dlg.cpp +++ b/wx+/popup_dlg.cpp @@ -45,7 +45,7 @@ void setBestInitialSize(wxTextCtrl& ctrl, const wxString& text, wxSize maxSize) auto itEnd = std::find(it, text.end(), L'\n'); wxString line(it, itEnd); if (line.empty()) - line = L" "; //GetTextExtent() returns (0, 0) for empty strings! + line = L' '; //GetTextExtent() returns (0, 0) for empty strings! wxSize sz = ctrl.GetTextExtent(line); //exactly gives row height, but does *not* consider newlines if (evalLineExtent(sz)) @@ -135,8 +135,8 @@ public: { wxString text; if (!cfg.textMain.empty()) - text += L"\n"; - text += trimCpy(cfg.textDetail) + L"\n"; //add empty top/bottom lines *instead* of using border space! + text += L'\n'; + text += trimCpy(cfg.textDetail) + L'\n'; //add empty top/bottom lines *instead* of using border space! setBestInitialSize(*m_textCtrlTextDetail, text, wxSize(maxWidth, maxHeight)); m_textCtrlTextDetail->ChangeValue(text); } @@ -145,7 +145,7 @@ public: if (checkBoxValue_) { - assert(contains(cfg.checkBoxLabel, L"&")); + assert(contains(cfg.checkBoxLabel, L'&')); m_checkBoxCustom->SetLabel(cfg.checkBoxLabel); m_checkBoxCustom->SetValue(*checkBoxValue_); } diff --git a/xBRZ/src/xbrz.cpp b/xBRZ/src/xbrz.cpp index 5228073f..e2c25810 100644 --- a/xBRZ/src/xbrz.cpp +++ b/xBRZ/src/xbrz.cpp @@ -241,8 +241,8 @@ enum BlendType struct BlendResult { BlendType - /**/blend_f, blend_g, - /**/blend_j, blend_k; + blend_e, blend_f, + blend_h, blend_i; }; @@ -254,62 +254,57 @@ struct Kernel_3x3 g, h, i; }; -struct Kernel_4x4 //kernel for preprocessing step +struct Kernel_4x4 : Kernel_3x3 { - uint32_t - a, b, c, // - e, f, g, // support reinterpret_cast from Kernel_4x4 => Kernel_3x3 - i, j, k, // - m, n, o, - d, h, l, p; + uint32_t j, k, l, m, n, o, p; }; +/* input kernel for preprocessing step: + + ----------------- + | A | B | C | P | + |---|---|---|---| + | D | E | F | O | evaluate the four corners between E, F, H, I + |---|---|---|---| input pixel is at position E + | G | H | I | N | + |---|---|---|---| + | J | K | L | M | + ----------------- */ -/* input kernel area naming convention: ------------------ -| A | B | C | D | -|---|---|---|---| -| E | F | G | H | evaluate the four corners between F, G, J, K -|---|---|---|---| input pixel is at position F -| I | J | K | L | -|---|---|---|---| -| M | N | O | P | ------------------ -*/ template FORCE_INLINE //detect blend direction -BlendResult preProcessCorners(const Kernel_4x4& ker, const xbrz::ScalerCfg& cfg) //result: F, G, J, K corners of "GradientType" +BlendResult preProcessCorners(const Kernel_4x4& ker, const xbrz::ScalerCfg& cfg) //result: E, F, H, I corners of "GradientType" { BlendResult result = {}; - if ((ker.f == ker.g && - ker.j == ker.k) || - (ker.f == ker.j && - ker.g == ker.k)) + if ((ker.e == ker.f && + ker.h == ker.i) || + (ker.e == ker.h && + ker.f == ker.i)) return result; auto dist = [&](uint32_t pix1, uint32_t pix2) { return ColorDistance::dist(pix1, pix2, cfg.luminanceWeight); }; - double jg = dist(ker.i, ker.f) + dist(ker.f, ker.c) + dist(ker.n, ker.k) + dist(ker.k, ker.h) + cfg.centerDirectionBias * dist(ker.j, ker.g); - double fk = dist(ker.e, ker.j) + dist(ker.j, ker.o) + dist(ker.b, ker.g) + dist(ker.g, ker.l) + cfg.centerDirectionBias * dist(ker.f, ker.k); + const double hf = dist(ker.g, ker.e) + dist(ker.e, ker.c) + dist(ker.k, ker.i) + dist(ker.i, ker.o) + cfg.centerDirectionBias * dist(ker.h, ker.f); + const double ei = dist(ker.d, ker.h) + dist(ker.h, ker.l) + dist(ker.b, ker.f) + dist(ker.f, ker.n) + cfg.centerDirectionBias * dist(ker.e, ker.i); - if (jg < fk) //test sample: 70% of values max(jg, fk) / min(jg, fk) are between 1.1 and 3.7 with median being 1.8 + if (hf < ei) //test sample: 70% of values max(hf, ei) / min(hf, ei) are between 1.1 and 3.7 with median being 1.8 { - const bool dominantGradient = cfg.dominantDirectionThreshold * jg < fk; - if (ker.f != ker.g && ker.f != ker.j) - result.blend_f = dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL; + const bool dominantGradient = cfg.dominantDirectionThreshold * hf < ei; + if (ker.e != ker.f && ker.e != ker.h) + result.blend_e = dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL; - if (ker.k != ker.j && ker.k != ker.g) - result.blend_k = dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL; + if (ker.i != ker.h && ker.i != ker.f) + result.blend_i = dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL; } - else if (fk < jg) + else if (ei < hf) { - const bool dominantGradient = cfg.dominantDirectionThreshold * fk < jg; - if (ker.j != ker.f && ker.j != ker.k) - result.blend_j = dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL; + const bool dominantGradient = cfg.dominantDirectionThreshold * ei < hf; + if (ker.h != ker.e && ker.h != ker.i) + result.blend_h = dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL; - if (ker.g != ker.f && ker.g != ker.k) - result.blend_g = dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL; + if (ker.f != ker.e && ker.f != ker.i) + result.blend_f = dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL; } return result; } @@ -371,13 +366,13 @@ template <> inline unsigned char rotateBlendInfo(unsigned char b) { ret | D | E | F | input pixel is at position E |---|---|---| | G | H | I | -------------- -*/ +------------- */ + template FORCE_INLINE //perf: quite worth it! void blendPixel(const Kernel_3x3& ker, uint32_t* target, int trgWidth, - unsigned char blendInfo, //result of preprocessing all four corners of pixel "e" + unsigned char blendInfo, //result of preprocessing all four corners of pixel "E" const xbrz::ScalerCfg& cfg) { //#define a get_a(ker) @@ -469,21 +464,21 @@ public: s_p2(0 <= y + 2 && y + 2 < srcHeight ? src + srcWidth * (y + 2) : nullptr), srcWidth_(srcWidth) {} - void readDhlp(Kernel_4x4& ker, int x) const //(x, y) is at kernel position F + void readPonm(Kernel_4x4& ker, int x) const //(x, y) is at kernel position E { [[likely]] if (const int x_p2 = x + 2; 0 <= x_p2 && x_p2 < srcWidth_) { - ker.d = s_m1 ? s_m1[x_p2] : 0; - ker.h = s_0 ? s_0 [x_p2] : 0; - ker.l = s_p1 ? s_p1[x_p2] : 0; - ker.p = s_p2 ? s_p2[x_p2] : 0; + ker.p = s_m1 ? s_m1[x_p2] : 0; + ker.o = s_0 ? s_0 [x_p2] : 0; + ker.n = s_p1 ? s_p1[x_p2] : 0; + ker.m = s_p2 ? s_p2[x_p2] : 0; } else { - ker.d = 0; - ker.h = 0; - ker.l = 0; ker.p = 0; + ker.o = 0; + ker.n = 0; + ker.m = 0; } } @@ -506,13 +501,13 @@ public: s_p2(src + srcWidth * std::clamp(y + 2, 0, srcHeight - 1)), srcWidth_(srcWidth) {} - void readDhlp(Kernel_4x4& ker, int x) const //(x, y) is at kernel position F + void readPonm(Kernel_4x4& ker, int x) const //(x, y) is at kernel position E { const int x_p2 = std::clamp(x + 2, 0, srcWidth_ - 1); - ker.d = s_m1[x_p2]; - ker.h = s_0 [x_p2]; - ker.l = s_p1[x_p2]; - ker.p = s_p2[x_p2]; + ker.p = s_m1[x_p2]; + ker.o = s_0 [x_p2]; + ker.n = s_p1[x_p2]; + ker.m = s_p2[x_p2]; } private: @@ -545,61 +540,61 @@ void scaleImage(const uint32_t* src, uint32_t* trg, int srcWidth, int srcHeight, //initialize at position x = -1 Kernel_4x4 ker4 = {}; - oobReader.readDhlp(ker4, -4); //hack: read a, e, i, m at x = -1 - ker4.a = ker4.d; - ker4.e = ker4.h; - ker4.i = ker4.l; - ker4.m = ker4.p; - - oobReader.readDhlp(ker4, -3); - ker4.b = ker4.d; - ker4.f = ker4.h; - ker4.j = ker4.l; - ker4.n = ker4.p; - - oobReader.readDhlp(ker4, -2); - ker4.c = ker4.d; - ker4.g = ker4.h; - ker4.k = ker4.l; - ker4.o = ker4.p; - - oobReader.readDhlp(ker4, -1); + oobReader.readPonm(ker4, -4); //hack: read a, d, g, j at x = -1 + ker4.a = ker4.p; + ker4.d = ker4.o; + ker4.g = ker4.n; + ker4.j = ker4.m; + + oobReader.readPonm(ker4, -3); + ker4.b = ker4.p; + ker4.e = ker4.o; + ker4.h = ker4.n; + ker4.k = ker4.m; + + oobReader.readPonm(ker4, -2); + ker4.c = ker4.p; + ker4.f = ker4.o; + ker4.i = ker4.n; + ker4.l = ker4.m; + + oobReader.readPonm(ker4, -1); { const BlendResult res = preProcessCorners(ker4, cfg); - clearAddTopL(preProcBuf[0], res.blend_k); //set 1st known corner for (0, yFirst) + clearAddTopL(preProcBuf[0], res.blend_i); //set 1st known corner for (0, yFirst) } for (int x = 0; x < srcWidth; ++x) { ker4.a = ker4.b; //shift previous kernel to the left - ker4.e = ker4.f; // ----------------- - ker4.i = ker4.j; // | A | B | C | D | - ker4.m = ker4.n; // |---|---|---|---| - /**/ // | E | F | G | H | (x, yFirst - 1) is at position F - ker4.b = ker4.c; // |---|---|---|---| - ker4.f = ker4.g; // | I | J | K | L | + ker4.d = ker4.e; // ----------------- + ker4.g = ker4.h; // | A | B | C | P | ker4.j = ker4.k; // |---|---|---|---| - ker4.n = ker4.o; // | M | N | O | P | + /**/ // | D | E | F | O | (x, yFirst - 1) is at position E + ker4.b = ker4.c; // |---|---|---|---| + ker4.e = ker4.f; // | G | H | I | N | + ker4.h = ker4.i; // |---|---|---|---| + ker4.k = ker4.l; // | J | K | L | M | /**/ // ----------------- - ker4.c = ker4.d; - ker4.g = ker4.h; - ker4.k = ker4.l; - ker4.o = ker4.p; + ker4.c = ker4.p; + ker4.f = ker4.o; + ker4.i = ker4.n; + ker4.l = ker4.m; - oobReader.readDhlp(ker4, x); + oobReader.readPonm(ker4, x); /* preprocessing blend result: --------- - | F | G | evaluate corner between F, G, J, K - |---+---| current input pixel is at position F - | J | K | + | E | F | evaluate corner between E, F, H, I + |---+---| current input pixel is at position E + | H | I | --------- */ const BlendResult res = preProcessCorners(ker4, cfg); - addTopR(preProcBuf[x], res.blend_j); //set 2nd known corner for (x, yFirst) + addTopR(preProcBuf[x], res.blend_h); //set 2nd known corner for (x, yFirst) if (x + 1 < srcWidth) - clearAddTopL(preProcBuf[x + 1], res.blend_k); //set 1st known corner for (x + 1, yFirst) + clearAddTopL(preProcBuf[x + 1], res.blend_i); //set 1st known corner for (x + 1, yFirst) } } //------------------------------------------------------------------------------------ @@ -612,85 +607,85 @@ void scaleImage(const uint32_t* src, uint32_t* trg, int srcWidth, int srcHeight, //initialize at position x = -1 Kernel_4x4 ker4 = {}; - oobReader.readDhlp(ker4, -4); //hack: read a, e, i, m at x = -1 - ker4.a = ker4.d; - ker4.e = ker4.h; - ker4.i = ker4.l; - ker4.m = ker4.p; - - oobReader.readDhlp(ker4, -3); - ker4.b = ker4.d; - ker4.f = ker4.h; - ker4.j = ker4.l; - ker4.n = ker4.p; - - oobReader.readDhlp(ker4, -2); - ker4.c = ker4.d; - ker4.g = ker4.h; - ker4.k = ker4.l; - ker4.o = ker4.p; - - oobReader.readDhlp(ker4, -1); + oobReader.readPonm(ker4, -4); //hack: read a, d, g, j at x = -1 + ker4.a = ker4.p; + ker4.d = ker4.o; + ker4.g = ker4.n; + ker4.j = ker4.m; + + oobReader.readPonm(ker4, -3); + ker4.b = ker4.p; + ker4.e = ker4.o; + ker4.h = ker4.n; + ker4.k = ker4.m; + + oobReader.readPonm(ker4, -2); + ker4.c = ker4.p; + ker4.f = ker4.o; + ker4.i = ker4.n; + ker4.l = ker4.m; + + oobReader.readPonm(ker4, -1); unsigned char blend_xy1 = 0; //corner blending for current (x, y + 1) position { const BlendResult res = preProcessCorners(ker4, cfg); - clearAddTopL(blend_xy1, res.blend_k); //set 1st known corner for (0, y + 1) and buffer for use on next column + clearAddTopL(blend_xy1, res.blend_i); //set 1st known corner for (0, y + 1) and buffer for use on next column - addBottomL(preProcBuf[0], res.blend_g); //set 3rd known corner for (0, y) + addBottomL(preProcBuf[0], res.blend_f); //set 3rd known corner for (0, y) } for (int x = 0; x < srcWidth; ++x, out += Scaler::scale) { ker4.a = ker4.b; //shift previous kernel to the left - ker4.e = ker4.f; // ----------------- - ker4.i = ker4.j; // | A | B | C | D | - ker4.m = ker4.n; // |---|---|---|---| - /**/ // | E | F | G | H | (x, y) is at position F - ker4.b = ker4.c; // |---|---|---|---| - ker4.f = ker4.g; // | I | J | K | L | + ker4.d = ker4.e; // ----------------- + ker4.g = ker4.h; // | A | B | C | P | ker4.j = ker4.k; // |---|---|---|---| - ker4.n = ker4.o; // | M | N | O | P | + /**/ // | D | E | F | O | (x, y) is at position E + ker4.b = ker4.c; // |---|---|---|---| + ker4.e = ker4.f; // | G | H | I | N | + ker4.h = ker4.i; // |---|---|---|---| + ker4.k = ker4.l; // | J | K | L | M | /**/ // ----------------- - ker4.c = ker4.d; - ker4.g = ker4.h; - ker4.k = ker4.l; - ker4.o = ker4.p; + ker4.c = ker4.p; + ker4.f = ker4.o; + ker4.i = ker4.n; + ker4.l = ker4.m; - oobReader.readDhlp(ker4, x); + oobReader.readPonm(ker4, x); //evaluate the four corners on bottom-right of current pixel unsigned char blend_xy = preProcBuf[x]; //for current (x, y) position { /* preprocessing blend result: --------- - | F | G | evaluate corner between F, G, J, K - |---+---| current input pixel is at position F - | J | K | + | E | F | evaluate corner between E, F, H, I + |---+---| current input pixel is at position E + | H | I | --------- */ const BlendResult res = preProcessCorners(ker4, cfg); - addBottomR(blend_xy, res.blend_f); //all four corners of (x, y) have been determined at this point due to processing sequence! + addBottomR(blend_xy, res.blend_e); //all four corners of (x, y) have been determined at this point due to processing sequence! - addTopR(blend_xy1, res.blend_j); //set 2nd known corner for (x, y + 1) + addTopR(blend_xy1, res.blend_h); //set 2nd known corner for (x, y + 1) preProcBuf[x] = blend_xy1; //store on current buffer position for use on next row [[likely]] if (x + 1 < srcWidth) { //blend_xy1 -> blend_x1y1 - clearAddTopL(blend_xy1, res.blend_k); //set 1st known corner for (x + 1, y + 1) and buffer for use on next column + clearAddTopL(blend_xy1, res.blend_i); //set 1st known corner for (x + 1, y + 1) and buffer for use on next column - addBottomL(preProcBuf[x + 1], res.blend_g); //set 3rd known corner for (x + 1, y) + addBottomL(preProcBuf[x + 1], res.blend_f); //set 3rd known corner for (x + 1, y) } } //fill block of size scale * scale with the given color - fillBlock(out, trgWidth * sizeof(uint32_t), ker4.f, Scaler::scale, Scaler::scale); + fillBlock(out, trgWidth * sizeof(uint32_t), ker4.e, Scaler::scale, Scaler::scale); //place *after* preprocessing step, to not overwrite the results while processing the last pixel! //blend all four corners of current pixel if (blendingNeeded(blend_xy)) { - const auto& ker3 = reinterpret_cast(ker4); //"The Things We Do for Perf" + const Kernel_3x3& ker3 = ker4; //"The Things We Do for Perf" blendPixel(ker3, out, trgWidth, blend_xy, cfg); blendPixel(ker3, out, trgWidth, blend_xy, cfg); blendPixel(ker3, out, trgWidth, blend_xy, cfg); @@ -800,8 +795,8 @@ struct Scaler3x : public ColorGradient { //model a round corner alphaGrad<45, 100>(out.template ref<2, 2>(), col); //exact: 0.4545939598 - //alphaGrad<7, 256>(out.template ref<2, 1>(), col); //0.02826017254 -> negligible + avoid conflicts with other rotations for this odd scale - //alphaGrad<7, 256>(out.template ref<1, 2>(), col); //0.02826017254 + //alphaGrad<3, 100>(out.template ref<2, 1>(), col); //0.02826017254 -> negligible + avoid overlap with other rotations at this scale + //alphaGrad<3, 100>(out.template ref<1, 2>(), col); //0.02826017254 } }; @@ -957,8 +952,8 @@ struct Scaler5x : public ColorGradient alphaGrad<86, 100>(out.template ref<4, 4>(), col); //exact: 0.8631434088 alphaGrad<23, 100>(out.template ref<4, 3>(), col); //0.2306749731 alphaGrad<23, 100>(out.template ref<3, 4>(), col); //0.2306749731 - //alphaGrad<1, 64>(out.template ref<4, 2>(), col); //0.01676812367 -> negligible + avoid conflicts with other rotations for this odd scale - //alphaGrad<1, 64>(out.template ref<2, 4>(), col); //0.01676812367 + //alphaGrad<2, 100>(out.template ref<4, 2>(), col); //0.01676812367 -> negligible + avoid overlap with other rotations at this scale + //alphaGrad<2, 100>(out.template ref<2, 4>(), col); //0.01676812367 } }; diff --git a/zen/basic_math.h b/zen/basic_math.h index 8a32ee69..26dda9a6 100644 --- a/zen/basic_math.h +++ b/zen/basic_math.h @@ -14,6 +14,7 @@ #include #include #include "type_traits.h" +#include "legacy_compiler.h" namespace numeric @@ -53,17 +54,6 @@ double mad(RandomAccessIterator first, RandomAccessIterator last); //note: inval template double norm2(InputIterator first, InputIterator last); -//constants -const double pi = 3.14159265358979323846; -const double e = 2.71828182845904523536; -const double sqrt2 = 1.41421356237309504880; -const double ln2 = 0.693147180559945309417; - -#if __cpp_lib_math_constants //C++20 - #error implement math constants from header -#endif -//static_assert(pi + e + sqrt2 + ln2 == 7.9672352249818781, "whoopsie"); - //---------------------------------------------------------------------------------- @@ -213,14 +203,14 @@ T power(T value) inline double radToDeg(double rad) { - return rad * 180.0 / numeric::pi; + return rad * (180.0 / std::numbers::pi); } inline double degToRad(double degree) { - return degree * numeric::pi / 180.0; + return degree / (180.0 / std::numbers::pi); } diff --git a/zen/crc.h b/zen/crc.h index df460a03..0570cced 100644 --- a/zen/crc.h +++ b/zen/crc.h @@ -64,38 +64,32 @@ uint32_t getCrc32(ByteIterator first, ByteIterator last) //https://en.wikipedia. { constexpr uint32_t crcTable[] = { - 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, - 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, - 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, - 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, - 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, - 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, - 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, - 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, - 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, - 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, - 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, - 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, - 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, - 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, - 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, - 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, - 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, - 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, - 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, - 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, - 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, - 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, - 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, - 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, - 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, - 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, - 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, - 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, - 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, - 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, - 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, - 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, + 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, + 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, + 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, + 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, + 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, + 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, + 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, + 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, + 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, + 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, + 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, + 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, + 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d }; static_assert(arraySize(crcTable) == 256 && arrayAccumulate(crcTable) == 549755813760); static_assert(sizeof(typename std::iterator_traits::value_type) == 1); diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp index 94632ea4..307b48e5 100644 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -85,7 +85,8 @@ DirWatcher::DirWatcher(const Zstring& dirPath) : //throw FileError const ErrorCode ec = getLastError(); //copy before directly/indirectly making other system calls! if (ec == ENOSPC) //fix misleading system message "No space left on device" throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(subDirPath)), - formatSystemError(L"inotify_add_watch", numberTo(ec), L"The user limit on the total number of inotify watches was reached or the kernel failed to allocate a needed resource.")); + formatSystemError(L"inotify_add_watch", L"ENOSPC", + L"The user limit on the total number of inotify watches was reached or the kernel failed to allocate a needed resource.")); throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(subDirPath)), formatSystemError(L"inotify_add_watch", ec)); } diff --git a/zen/error_log.h b/zen/error_log.h index cc52fc6e..ab23e33a 100644 --- a/zen/error_log.h +++ b/zen/error_log.h @@ -8,9 +8,7 @@ #define ERROR_LOG_H_8917590832147915 #include -#include #include -//#include #include "time.h" #include "i18n.h" #include "utf.h" @@ -31,10 +29,10 @@ struct LogEntry { time_t time = 0; MessageType type = MSG_TYPE_FATAL_ERROR; - Zstringw message; //std::wstring may employ small string optimization: we cannot accept bloating the "ErrorLog::entries_" memory block below (think 1 million items) + Zstringc message; //conserve memory (=> avoid std::string SSO overhead!) }; -std::wstring formatMessage(const LogEntry& entry); +std::string formatMessage(const LogEntry& entry); class ErrorLog @@ -42,7 +40,14 @@ class ErrorLog public: void logMsg(const std::wstring& msg, MessageType type); - int getItemCount(int typeFilter = MSG_TYPE_INFO | MSG_TYPE_WARNING | MSG_TYPE_ERROR | MSG_TYPE_FATAL_ERROR) const; + struct Stats + { + int info = 0; + int warning = 0; + int error = 0; + int fatal = 0; + }; + Stats getStats() const; //subset of std::vector<> interface: using const_iterator = std::vector::const_iterator; @@ -65,59 +70,79 @@ private: inline void ErrorLog::logMsg(const std::wstring& msg, MessageType type) { - entries_.push_back({ std::time(nullptr), type, copyStringTo(msg) }); + entries_.push_back({ std::time(nullptr), type, utfTo(msg) }); } -inline -int ErrorLog::getItemCount(int typeFilter) const -{ - return static_cast(std::count_if(entries_.begin(), entries_.end(), [typeFilter](const LogEntry& e) { return e.type & typeFilter; })); -} - -inline -std::wstring getMessageTypeLabel(MessageType type) +inline +ErrorLog::Stats ErrorLog::getStats() const { - switch (type) + Stats count; + for (const LogEntry& entry : entries_) + switch (entry.type) { case MSG_TYPE_INFO: - return _("Info"); + ++count.info; + break; case MSG_TYPE_WARNING: - return _("Warning"); + ++count.warning; + break; case MSG_TYPE_ERROR: - return _("Error"); + ++count.error; + break; case MSG_TYPE_FATAL_ERROR: - return _("Serious Error"); + ++count.fatal; + break; } - assert(false); - return std::wstring(); + assert(static_cast(entries_.size()) == count.info + count.warning + count.error + count.fatal); + return count; +} + + +inline +std::wstring getMessageTypeLabel(MessageType type) +{ + switch (type) + { + case MSG_TYPE_INFO: + return _("Info"); + case MSG_TYPE_WARNING: + return _("Warning"); + case MSG_TYPE_ERROR: + return _("Error"); + case MSG_TYPE_FATAL_ERROR: + return _("Serious Error"); + } + assert(false); + return std::wstring(); } inline -std::wstring formatMessage(const LogEntry& entry) +std::string formatMessage(const LogEntry& entry) { - std::wstring msgFmt = L"[" + formatTime(FORMAT_TIME, getLocalTime(entry.time)) + L"] " + getMessageTypeLabel(entry.type) + L": "; + std::string msgFmt = '[' + utfTo(formatTime(formatTimeTag, getLocalTime(entry.time))) + "] " + utfTo(getMessageTypeLabel(entry.type)) + ": "; const size_t prefixLen = unicodeLength(msgFmt); //consider Unicode! - const Zstringw msg = trimCpy(entry.message); - static_assert(std::is_same_v, "don't worry about copying as long as we're using a ref-counted string!"); + const Zstringc msg = trimCpy(entry.message); + static_assert(std::is_same_v, "don't worry about copying as long as we're using a ref-counted string!"); for (auto it = msg.begin(); it != msg.end(); ) - if (*it == L'\n') + if (*it == '\n') { - msgFmt += L'\n'; - msgFmt.append(prefixLen, L' '); + msgFmt += '\n'; + msgFmt.append(prefixLen, ' '); ++it; //skip duplicate newlines - for (;it != msg.end() && *it == L'\n'; ++it) + for (; it != msg.end() && *it == '\n'; ++it) ; } else msgFmt += *it++; - return msgFmt += L'\n'; + msgFmt += '\n'; + return msgFmt; } } diff --git a/zen/file_access.cpp b/zen/file_access.cpp index e23d48be..4f6704d2 100644 --- a/zen/file_access.cpp +++ b/zen/file_access.cpp @@ -311,7 +311,7 @@ void moveAndRenameFileSub(const Zstring& pathFrom, const Zstring& pathTo, bool r { auto throwException = [&](int ec) { - const std::wstring errorMsg = replaceCpy(replaceCpy(_("Cannot move file %x to %y."), L"%x", L"\n" + fmtPath(pathFrom)), L"%y", L"\n" + fmtPath(pathTo)); + const std::wstring errorMsg = replaceCpy(replaceCpy(_("Cannot move file %x to %y."), L"%x", L'\n' + fmtPath(pathFrom)), L"%y", L'\n' + fmtPath(pathTo)); const std::wstring errorDescr = formatSystemError(L"rename", ec); if (ec == EXDEV) @@ -575,7 +575,7 @@ void zen::copySymlink(const Zstring& sourcePath, const Zstring& targetPath, bool const Zstring linkPath = getSymlinkTargetRaw(sourcePath); //throw FileError; accept broken symlinks if (::symlink(linkPath.c_str(), targetPath.c_str()) != 0) - THROW_LAST_FILE_ERROR(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."), L"%x", L"\n" + fmtPath(sourcePath)), L"%y", L"\n" + fmtPath(targetPath)), L"symlink"); + THROW_LAST_FILE_ERROR(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."), L"%x", L'\n' + fmtPath(sourcePath)), L"%y", L'\n' + fmtPath(targetPath)), L"symlink"); //allow only consistent objects to be created -> don't place before ::symlink(); targetPath may already exist! ZEN_ON_SCOPE_FAIL(try { removeSymlinkPlain(targetPath); /*throw FileError*/ } diff --git a/zen/file_io.cpp b/zen/file_io.cpp index e788bcfe..b78259e0 100644 --- a/zen/file_io.cpp +++ b/zen/file_io.cpp @@ -68,7 +68,7 @@ FileBase::FileHandle openHandleForRead(const Zstring& filePath) //throw FileErro return name + printNumber(L"0%06o", m & S_IFMT); }(); throw FileError(replaceCpy(_("Cannot open file %x."), L"%x", fmtPath(filePath)), - _("Unsupported item type.") + L" [" + typeName + L"]"); + _("Unsupported item type.") + L" [" + typeName + L']'); } } //else: let ::open() fail for errors like "not existing" @@ -100,7 +100,7 @@ FileInput::FileInput(const Zstring& filePath, const IOCallback& notifyUnbuffered size_t FileInput::tryRead(void* buffer, size_t bytesToRead) //throw FileError, ErrorFileLocked; 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(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); assert(bytesToRead == getBlockSize()); ssize_t bytesRead = 0; @@ -215,7 +215,7 @@ FileOutput::~FileOutput() size_t FileOutput::tryWrite(const void* buffer, size_t bytesToWrite) //throw FileError; may return short! CONTRACT: bytesToWrite > 0 { if (bytesToWrite == 0) - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); assert(bytesToWrite <= getBlockSize()); ssize_t bytesWritten = 0; diff --git a/zen/file_io.h b/zen/file_io.h index 54bde5aa..b47c6077 100644 --- a/zen/file_io.h +++ b/zen/file_io.h @@ -119,7 +119,7 @@ void saveBinContainer(const Zstring& filePath, const BinContainer& buffer, const if (!buffer.empty()) { /*snake oil?*/ fileOut.preAllocateSpaceBestEffort(buffer.size()); //throw FileError - fileOut.write(&*buffer.begin(), buffer.size()); //throw FileError, X + fileOut.write(&buffer[0], buffer.size()); //throw FileError, X } fileOut.finalize(); //throw FileError, X } diff --git a/zen/format_unit.cpp b/zen/format_unit.cpp index f2df4153..91a881dc 100644 --- a/zen/format_unit.cpp +++ b/zen/format_unit.cpp @@ -11,6 +11,7 @@ #include "i18n.h" #include "time.h" #include "globals.h" +#include "utf.h" #include //thousands separator #include "utf.h" // @@ -115,7 +116,7 @@ std::wstring roundToBlock(double timeInHigh, std::wstring output = formatUnitTime(roundedtimeInLow / unitLowPerHigh, unitHigh); if (unitLowPerHigh > blockSizeLow) - output += L" " + formatUnitTime(roundedtimeInLow % unitLowPerHigh, unitLow); + output += L' ' + formatUnitTime(roundedtimeInLow % unitLowPerHigh, unitLow); return output; } } @@ -194,7 +195,7 @@ std::wstring zen::formatUtcToLocalTime(time_t utcTime) TimeComp loc = getLocalTime(utcTime); - std::wstring dateString = formatTime(L"%x %X", loc); + std::wstring dateString = utfTo(formatTime(Zstr("%x %X"), loc)); return !dateString.empty() ? dateString : errorMsg(); } diff --git a/zen/guid.h b/zen/guid.h index 657ed07a..88059be8 100644 --- a/zen/guid.h +++ b/zen/guid.h @@ -26,8 +26,8 @@ std::string generateGUID() //creates a 16-byte GUID #if __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 25) //getentropy() requires glibc 2.25 (ldd --version) PS: CentOS 7 is on 2.17 if (::getentropy(&guid[0], guid.size()) != 0) //"The maximum permitted value for the length argument is 256" - throw std::runtime_error(std::string(__FILE__) + "[" + numberTo(__LINE__) + "] Failed to generate GUID." + - "\n" + utfTo(formatSystemError(L"getentropy", errno))); + throw std::runtime_error(std::string(__FILE__) + '[' + numberTo(__LINE__) + "] Failed to generate GUID." + "\n\n" + + utfTo(formatSystemError(L"getentropy", errno))); #else class RandomGeneratorPosix { @@ -35,8 +35,8 @@ std::string generateGUID() //creates a 16-byte GUID RandomGeneratorPosix() { if (fd_ == -1) - throw std::runtime_error(std::string(__FILE__) + "[" + numberTo(__LINE__) + "] Failed to generate GUID." + - "\n" + utfTo(formatSystemError(L"open", errno))); + throw std::runtime_error(std::string(__FILE__) + '[' + numberTo(__LINE__) + "] Failed to generate GUID." + "\n\n" + + utfTo(formatSystemError(L"open", errno))); } ~RandomGeneratorPosix() { ::close(fd_); } @@ -47,8 +47,8 @@ std::string generateGUID() //creates a 16-byte GUID { const ssize_t bytesRead = ::read(fd_, static_cast(buf) + offset, size - offset); if (bytesRead < 1) //0 means EOF => error in this context (should check for buffer overflow, too?) - throw std::runtime_error(std::string(__FILE__) + "[" + numberTo(__LINE__) + "] Failed to generate GUID." + - "\n" + utfTo(formatSystemError(L"read", bytesRead < 0 ? errno : EIO))); + throw std::runtime_error(std::string(__FILE__) + '[' + numberTo(__LINE__) + "] Failed to generate GUID." + "\n\n" + + utfTo(formatSystemError(L"read", bytesRead < 0 ? errno : EIO))); offset += bytesRead; assert(offset <= size); } diff --git a/zen/http.cpp b/zen/http.cpp index 8cd99d7a..c6a390de 100644 --- a/zen/http.cpp +++ b/zen/http.cpp @@ -19,7 +19,7 @@ class HttpInputStream::Impl public: Impl(const Zstring& url, const std::string* postBuf /*issue POST if bound, GET otherwise*/, - const Zstring& contentType, //required for POST + const std::string& contentType, //required for POST bool disableGetCache /*not relevant for POST (= never cached)*/, const Zstring& userAgent, const Zstring* caCertFilePath /*optional: enable certificate validation*/, @@ -37,9 +37,9 @@ public: const bool useTls = [&] { - if (startsWithAsciiNoCase(url, Zstr("http://"))) + if (startsWithAsciiNoCase(url, "http://")) return false; - if (startsWithAsciiNoCase(url, Zstr("https://"))) + if (startsWithAsciiNoCase(url, "https://")) return true; throw SysError(L"URL uses unexpected protocol."); }(); @@ -49,7 +49,7 @@ public: std::map headers; if (postBuf && !contentType.empty()) - headers["Content-Type"] = utfTo(contentType); + headers["Content-Type"] = contentType; if (useTls) //HTTP default port: 443, see %WINDIR%\system32\drivers\etc\services { @@ -96,7 +96,7 @@ public: const size_t blockSize = std::min(static_cast(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 + buf.resize(buf.size() - (blockSize - bytesReceived)); //caveat: unsigned arithmetics if (contains(buf, headerDelim)) { @@ -122,8 +122,8 @@ public: statusCode_ = stringTo(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)); + 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")) @@ -236,7 +236,7 @@ namespace { std::unique_ptr sendHttpRequestImpl(const Zstring& url, const std::string* postBuf /*issue POST if bound, GET otherwise*/, - const Zstring& contentType, //required for POST + const std::string& contentType, //required for POST const Zstring& userAgent, const Zstring* caCertFilePath /*optional: enable certificate validation*/, const IOCallback& notifyUnbufferedIO) //throw SysError, X @@ -248,8 +248,8 @@ std::unique_ptr sendHttpRequestImpl(const Zstring& url, auto response = std::make_unique(urlRed, postBuf, contentType, false /*disableGetCache*/, userAgent, caCertFilePath, notifyUnbufferedIO); //throw SysError, X //https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_Redirection - const int httpStatusCode = response->getStatusCode(); - if (httpStatusCode / 100 == 3) //e.g. 301, 302, 303, 307... we're not too greedy since we check location, too! + const int httpStatus = response->getStatusCode(); + if (httpStatus / 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()) @@ -259,8 +259,8 @@ std::unique_ptr sendHttpRequestImpl(const Zstring& url, } else { - if (httpStatusCode != 200) //HTTP_STATUS_OK(200) - throw SysError(formatHttpStatusCode(httpStatusCode)); //e.g. HTTP_STATUS_NOT_FOUND(404) + if (httpStatus != 200) //HTTP_STATUS_OK(200) + throw SysError(formatHttpStatus(httpStatus)); //e.g. HTTP_STATUS_NOT_FOUND(404) return response; } @@ -340,19 +340,19 @@ std::vector> zen::xWwwFormUrlDecode(const st HttpInputStream zen::sendHttpGet(const Zstring& url, const Zstring& userAgent, const Zstring* caCertFilePath, const IOCallback& notifyUnbufferedIO) //throw SysError, X { - return sendHttpRequestImpl(url, nullptr /*postBuf*/, Zstr("") /*contentType*/, userAgent, caCertFilePath, notifyUnbufferedIO); //throw SysError, X, X + return sendHttpRequestImpl(url, nullptr /*postBuf*/, "" /*contentType*/, userAgent, caCertFilePath, notifyUnbufferedIO); //throw SysError, X, X } HttpInputStream zen::sendHttpPost(const Zstring& url, const std::vector>& postParams, const Zstring& userAgent, const Zstring* caCertFilePath, const IOCallback& notifyUnbufferedIO) //throw SysError, X { - return sendHttpPost(url, xWwwFormUrlEncode(postParams), Zstr("application/x-www-form-urlencoded"), userAgent, caCertFilePath, notifyUnbufferedIO); //throw SysError, X + return sendHttpPost(url, xWwwFormUrlEncode(postParams), "application/x-www-form-urlencoded", userAgent, caCertFilePath, notifyUnbufferedIO); //throw SysError, X } -HttpInputStream zen::sendHttpPost(const Zstring& url, const std::string& postBuf, const Zstring& contentType, +HttpInputStream zen::sendHttpPost(const Zstring& url, const std::string& postBuf, const std::string& contentType, const Zstring& userAgent, const Zstring* caCertFilePath, const IOCallback& notifyUnbufferedIO) //throw SysError, X { return sendHttpRequestImpl(url, &postBuf, contentType, userAgent, caCertFilePath, notifyUnbufferedIO); //throw SysError, X @@ -365,7 +365,7 @@ bool zen::internetIsAlive() //noexcept { auto response = std::make_unique(Zstr("http://www.google.com/"), nullptr /*postParams*/, - Zstr("") /*contentType*/, + "" /*contentType*/, true /*disableGetCache*/, Zstr("FreeFileSync"), nullptr /*caCertFilePath*/, @@ -380,7 +380,7 @@ bool zen::internetIsAlive() //noexcept } -std::wstring zen::formatHttpStatusCode(int sc) +std::wstring zen::formatHttpStatus(int sc) { const wchar_t* statusText = [&] //https://en.wikipedia.org/wiki/List_of_HTTP_status_codes { @@ -462,13 +462,13 @@ std::wstring zen::formatHttpStatusCode(int sc) } -bool zen::isValidEmail(const Zstring& email) +bool zen::isValidEmail(const std::string& email) { //https://en.wikipedia.org/wiki/Email_address#Syntax //https://tools.ietf.org/html/rfc3696 => note errata! https://www.rfc-editor.org/errata_search.php?rfc=3696 //https://tools.ietf.org/html/rfc5321 - std::string local = utfTo(beforeLast(email, Zstr('@'), IF_MISSING_RETURN_NONE)); - std::string domain = utfTo( afterLast(email, Zstr('@'), IF_MISSING_RETURN_NONE)); + std::string local = beforeLast(email, '@', IF_MISSING_RETURN_NONE); + std::string domain = afterLast(email, '@', IF_MISSING_RETURN_NONE); //consider: "t@st"@email.com t\@st@email.com" auto stripComments = [](std::string& part) @@ -517,7 +517,7 @@ bool zen::isValidEmail(const Zstring& email) } -std::string zen::htmlSpecialChars(const std::string& str) +std::string zen::htmlSpecialChars(const std::string_view& str) { //mirror PHP: https://github.com/php/php-src/blob/e99d5d39239c611e1e7304e79e88545c4e71a073/ext/standard/html_tables.h#L6189 std::string output; diff --git a/zen/http.h b/zen/http.h index fbaa09de..09395f8f 100644 --- a/zen/http.h +++ b/zen/http.h @@ -48,15 +48,15 @@ HttpInputStream sendHttpPost(const Zstring& url, const IOCallback& notifyUnbufferedIO /*throw X*/); //throw SysError, X HttpInputStream sendHttpPost(const Zstring& url, - const std::string& postBuf, const Zstring& contentType, + const std::string& postBuf, const std::string& contentType, const Zstring& userAgent, const Zstring* caCertFilePath /*optional: enable certificate validation*/, const IOCallback& notifyUnbufferedIO /*throw X*/); //throw SysError, X bool internetIsAlive(); //noexcept -std::wstring formatHttpStatusCode(int httpStatusCode); -bool isValidEmail(const Zstring& email); -std::string htmlSpecialChars(const std::string& str); +std::wstring formatHttpStatus(int httpStatus); +bool isValidEmail(const std::string& email); +std::string htmlSpecialChars(const std::string_view& str); std::string xWwwFormUrlEncode(const std::vector>& paramPairs); std::vector> xWwwFormUrlDecode(const std::string& str); diff --git a/zen/legacy_compiler.h b/zen/legacy_compiler.h index 54dd7f59..8d44f3f7 100644 --- a/zen/legacy_compiler.h +++ b/zen/legacy_compiler.h @@ -8,11 +8,6 @@ #define LEGACY_COMPILER_H_839567308565656789 -#if !__cpp_lib_erase_if - #include - #include - #include -#endif //https://isocpp.org/std/standing-documents/sd-6-sg10-feature-test-recommendations @@ -58,8 +53,18 @@ private: T* const data_; }; -//--------------------------------------------------------------------------------- +#if __cpp_lib_math_constants + #error get rid of workaround: +#endif + +namespace numbers +{ +const double pi = 3.14159265358979323846; +const double e = 2.71828182845904523536; +const double sqrt2 = 1.41421356237309504880; +const double ln2 = 0.693147180559945309417; +} } diff --git a/zen/open_ssl.cpp b/zen/open_ssl.cpp index dc9c8a19..b823f8ca 100644 --- a/zen/open_ssl.cpp +++ b/zen/open_ssl.cpp @@ -322,8 +322,8 @@ std::string createSignature(const std::string& message, EVP_PKEY* privateKey) // reinterpret_cast(&signature[0]), //unsigned char* sigret, &sigLen) != 1) //size_t* siglen throw SysError(formatLastOpenSSLError(L"EVP_DigestSignFinal")); - signature.resize(sigLen); + signature.resize(sigLen); return signature; } @@ -373,7 +373,7 @@ void zen::verifySignature(const std::string& message, const std::string& signatu namespace { -std::wstring formatSslErrorRaw(int ec) +std::wstring formatSslErrorCode(int ec) { switch (ec) { @@ -388,12 +388,15 @@ std::wstring formatSslErrorRaw(int ec) ZEN_CHECK_CASE_FOR_CONSTANT(SSL_ERROR_WANT_ACCEPT); ZEN_CHECK_CASE_FOR_CONSTANT(SSL_ERROR_WANT_ASYNC); ZEN_CHECK_CASE_FOR_CONSTANT(SSL_ERROR_WANT_ASYNC_JOB); + ZEN_CHECK_CASE_FOR_CONSTANT(SSL_ERROR_WANT_CLIENT_HELLO_CB); + + default: + return replaceCpy(L"SSL error %x", L"%x", numberTo(ec)); } - return L"Unknown SSL error: " + numberTo(ec); } -std::wstring formatX509ErrorRaw(long ec) +std::wstring formatX509ErrorCode(long ec) { switch (ec) { @@ -473,8 +476,10 @@ std::wstring formatX509ErrorRaw(long ec) ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_OCSP_VERIFY_NEEDED); ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_OCSP_VERIFY_FAILED); ZEN_CHECK_CASE_FOR_CONSTANT(X509_V_ERR_OCSP_CERT_UNKNOWN); + + default: + return replaceCpy(L"X509 error %x", L"%x", numberTo(ec)); } - return L"Unknown X509 error: " + numberTo(ec); } } @@ -487,7 +492,7 @@ public: { ZEN_ON_SCOPE_FAIL(cleanup(); /*destructor call would lead to member double clean-up!!!*/); - ctx_ = ::SSL_CTX_new(TLS_client_method()); + ctx_ = ::SSL_CTX_new(::TLS_client_method()); if (!ctx_) throw SysError(formatLastOpenSSLError(L"SSL_CTX_new")); @@ -526,13 +531,13 @@ public: const int rv = ::SSL_connect(ssl_); //implicitly calls SSL_set_connect_state() if (rv != 1) - throw SysError(formatLastOpenSSLError(L"SSL_connect") + L" " + formatSslErrorRaw(::SSL_get_error(ssl_, rv))); + throw SysError(formatLastOpenSSLError(L"SSL_connect") + L' ' + formatSslErrorCode(::SSL_get_error(ssl_, rv))); if (caCertFilePath) { const long verifyResult = ::SSL_get_verify_result(ssl_); if (verifyResult != X509_V_OK) - throw SysError(formatSystemError(L"SSL_get_verify_result", formatX509ErrorRaw(verifyResult), L"")); + throw SysError(formatSystemError(L"SSL_get_verify_result", formatX509ErrorCode(verifyResult), L"")); } } @@ -554,7 +559,7 @@ public: 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(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); size_t bytesReceived = 0; const int rv = ::SSL_read_ex(ssl_, buffer, bytesToRead, &bytesReceived); @@ -564,7 +569,7 @@ public: if (sslError == SSL_ERROR_ZERO_RETURN || //EOF + close_notify alert (sslError == SSL_ERROR_SYSCALL && ::ERR_peek_last_error() == 0)) //EOF: only expected for HTTP/1.0 return 0; - throw SysError(formatLastOpenSSLError(L"SSL_read_ex") + L" " + formatSslErrorRaw(sslError)); + throw SysError(formatLastOpenSSLError(L"SSL_read_ex") + L' ' + formatSslErrorCode(sslError)); } assert(bytesReceived > 0); //SSL_read_ex() considers EOF an error! if (bytesReceived > bytesToRead) //better safe than sorry @@ -576,12 +581,12 @@ public: size_t tryWrite(const void* buffer, size_t bytesToWrite) //throw SysError; may return short! CONTRACT: bytesToWrite > 0 { if (bytesToWrite == 0) - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); size_t bytesWritten = 0; const int rv = ::SSL_write_ex(ssl_, buffer, bytesToWrite, &bytesWritten); if (rv != 1) - throw SysError(formatLastOpenSSLError(L"SSL_write_ex") + L" " + formatSslErrorRaw(::SSL_get_error(ssl_, rv))); + throw SysError(formatLastOpenSSLError(L"SSL_write_ex") + L' ' + formatSslErrorCode(::SSL_get_error(ssl_, rv))); if (bytesWritten > bytesToWrite) throw SysError(L"SSL_write_ex: buffer overflow."); @@ -759,7 +764,7 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: auto numToBeString = [](size_t n) -> std::string { - static_assert(usingLittleEndian()&& sizeof(n) >= 4); + static_assert(usingLittleEndian() && sizeof(n) >= 4); const char* numStr = reinterpret_cast(&n); return { numStr[3], numStr[2], numStr[1], numStr[0] }; //big endian! }; diff --git a/zen/perf.h b/zen/perf.h index b6cb5bb0..9f368016 100644 --- a/zen/perf.h +++ b/zen/perf.h @@ -98,7 +98,7 @@ public: const int64_t timeMs = std::chrono::duration_cast(watch_.elapsed()).count(); const std::string msg = numberTo(timeMs) + " ms"; - std::clog << "Perf: duration: " << msg << "\n"; + std::clog << "Perf: duration: " << msg << '\n'; resultShown_ = true; } diff --git a/zen/recycler.cpp b/zen/recycler.cpp index dc156a6f..f4fd870b 100644 --- a/zen/recycler.cpp +++ b/zen/recycler.cpp @@ -45,7 +45,9 @@ bool zen::recycleOrDeleteIfExists(const Zstring& itemPath) //throw FileError return true; } - throw FileError(errorMsg, formatSystemError(L"g_file_trash", replaceCpy(_("Error Code %x"), L"%x", numberTo(error->code)), utfTo(error->message))); + throw FileError(errorMsg, formatSystemError(L"g_file_trash", + replaceCpy(_("Error Code %x"), L"%x", numberTo(error->code)), + utfTo(error->message))); //g_quark_to_string(error->domain) } return true; diff --git a/zen/serialize.h b/zen/serialize.h index dd884e3b..1eabcdec 100644 --- a/zen/serialize.h +++ b/zen/serialize.h @@ -25,35 +25,6 @@ namespace zen binary container for data storage: must support "basic" std::vector interface (e.g. std::vector, std::string, Zbase) */ -//binary container reference implementations -using Utf8String = Zbase; //ref-counted + COW text stream + guaranteed performance: exponential growth -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 with ref-counted semantics, but no COW! => *almost* value type semantics, but not quite -{ -public: - using value_type = std::vector::value_type; - using iterator = std::vector::iterator; - using const_iterator = std::vector::const_iterator; - - iterator begin() { return buffer_.ref().begin(); } - iterator end () { return buffer_.ref().end (); } - - const_iterator begin() const { return buffer_.ref().begin(); } - const_iterator end () const { return buffer_.ref().end (); } - - void resize(size_t len) { buffer_.ref().resize(len); } - size_t size() const { return buffer_.ref().size(); } - bool empty() const { return buffer_.ref().empty(); } - - inline friend bool operator==(const ByteArray& lhs, const ByteArray& rhs) { return lhs.buffer_.ref() == rhs.buffer_.ref(); } - -private: - SharedRef> buffer_ = makeSharedRef>(); - //perf: shared_ptr indirection irrelevant: less than 1% slower! -}; - /* ------------------------------- |Buffered Input Stream Concept| @@ -158,6 +129,7 @@ struct MemoryStreamOut } const BinContainer& ref() const { return buffer_; } + /**/ BinContainer& ref() { return buffer_; } private: MemoryStreamOut (const MemoryStreamOut&) = delete; @@ -180,7 +152,7 @@ void bufferedStreamCopy(BufferedInputStream& streamIn, //throw X { const size_t blockSize = streamIn.getBlockSize(); if (blockSize == 0) - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); std::vector buffer(blockSize); for (;;) @@ -201,7 +173,7 @@ BinContainer bufferedLoad(BufferedInputStream& streamIn) //throw X const size_t blockSize = streamIn.getBlockSize(); if (blockSize == 0) - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); BinContainer buffer; for (;;) @@ -238,7 +210,7 @@ void writeContainer(BufferedOutputStream& stream, const C& cont) //don't even co const auto len = cont.size(); writeNumber(stream, static_cast(len)); if (len > 0) - writeArray(stream, &*cont.begin(), sizeof(typename C::value_type) * len); //don't use c_str(), but access uniformly via STL interface + writeArray(stream, &cont[0], sizeof(typename C::value_type) * len); //don't use c_str(), but access uniformly via STL interface } @@ -276,7 +248,7 @@ C readContainer(BufferedInputStream& stream) //throw UnexpectedEndOfStreamError catch (std::length_error&) { throw UnexpectedEndOfStreamError(); } //most likely this is due to data corruption! catch ( std::bad_alloc&) { throw UnexpectedEndOfStreamError(); } // - readArray(stream, &*cont.begin(), sizeof(typename C::value_type) * strLength); //throw UnexpectedEndOfStreamError + readArray(stream, &cont[0], sizeof(typename C::value_type) * strLength); //throw UnexpectedEndOfStreamError } return cont; } diff --git a/zen/shell_execute.h b/zen/shell_execute.h index 580c4558..faea4bd9 100644 --- a/zen/shell_execute.h +++ b/zen/shell_execute.h @@ -74,7 +74,7 @@ int shellExecute(const Zstring& command, ExecutionType type, bool hideConsole) / std::string getCommandOutput(const Zstring& command) //throw SysError { //https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/popen.3.html - FILE* pipe = ::popen(command.c_str(), "r"); + FILE* pipe = ::popen(command.c_str(), "r"); if (!pipe) THROW_LAST_SYS_ERROR(L"popen"); ZEN_ON_SCOPE_EXIT(::pclose(pipe)); @@ -97,7 +97,7 @@ std::string getCommandOutput(const Zstring& command) //throw SysError output.resize(output.size() - (blockSize - bytesRead)); //caveat: unsigned arithmetics } while (!::feof(pipe)); - + return output; } } diff --git a/zen/socket.h b/zen/socket.h index 827d446b..3bd0a2a0 100644 --- a/zen/socket.h +++ b/zen/socket.h @@ -89,7 +89,7 @@ namespace size_t tryReadSocket(SocketType socket, 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(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); int bytesReceived = 0; for (;;) @@ -114,7 +114,7 @@ size_t tryReadSocket(SocketType socket, void* buffer, size_t bytesToRead) //thro size_t tryWriteSocket(SocketType socket, const void* buffer, size_t bytesToWrite) //throw SysError; may return short! CONTRACT: bytesToWrite > 0 { if (bytesToWrite == 0) - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); int bytesWritten = 0; for (;;) diff --git a/zen/string_base.h b/zen/string_base.h index 6c835c72..d2e00baf 100644 --- a/zen/string_base.h +++ b/zen/string_base.h @@ -221,6 +221,7 @@ public: Zbase(); Zbase(const Char* str) : Zbase(str, str + strLength(str)) {} //implicit conversion from a C-string! Zbase(const Char* str, size_t len) : Zbase(str, str + len) {} + Zbase(size_t count, Char fillChar); Zbase(const Zbase& str); Zbase(Zbase&& tmp) noexcept; template @@ -251,7 +252,8 @@ public: size_t length() const; size_t size () const { return length(); } const Char* c_str() const { return rawStr_; } //C-string format with 0-termination - const Char operator[](size_t pos) const; + const Char& operator[](size_t pos) const; + /**/ Char& operator[](size_t pos); bool empty() const { return length() == 0; } void clear(); size_t find (const Zbase& str, size_t pos = 0) const; // @@ -283,10 +285,11 @@ public: static const size_t npos = static_cast(-1); private: - Zbase (int) = delete; // - Zbase& operator= (int) = delete; //detect usage errors by creating an intentional ambiguity with "Char" - Zbase& operator+=(int) = delete; // - void push_back (int) = delete; // + Zbase (int) = delete; // + Zbase(size_t count, int) = delete; // + Zbase& operator= (int) = delete; //detect usage errors by creating an intentional ambiguity with "Char" + Zbase& operator+= (int) = delete; // + void push_back (int) = delete; // Char* rawStr_; }; @@ -359,6 +362,15 @@ Zbase::Zbase(InputIterator first, InputIterator last) } +template class SP> inline +Zbase::Zbase(size_t count, Char fillChar) +{ + rawStr_ = this->create(count); + std::fill(rawStr_, rawStr_ + count, fillChar); + rawStr_[count] = 0; +} + + template class SP> inline Zbase::Zbase(const Zbase& str) { @@ -544,7 +556,15 @@ size_t Zbase::length() const template class SP> inline -const Char Zbase::operator[](size_t pos) const +const Char& Zbase::operator[](size_t pos) const +{ + assert(pos < length()); //design by contract! no runtime check! + return rawStr_[pos]; +} + + +template class SP> inline +Char& Zbase::operator[](size_t pos) { assert(pos < length()); //design by contract! no runtime check! return rawStr_[pos]; diff --git a/zen/string_tools.h b/zen/string_tools.h index 5c444830..40a4ea52 100644 --- a/zen/string_tools.h +++ b/zen/string_tools.h @@ -167,8 +167,8 @@ Char asciiToLower(Char c) } -template inline -Char asciiToUpper(Char c) + template inline + Char asciiToUpper(Char c) { if (static_cast('a') <= c && c <= static_cast('z')) return static_cast(c - static_cast('a') + static_cast('A')); @@ -182,13 +182,13 @@ inline int strcmpWithNulls(const char* ptr1, const char* ptr2, size_t num) inline int strcmpWithNulls(const wchar_t* ptr1, const wchar_t* ptr2, size_t num) { return std::wmemcmp(ptr1, ptr2, num); } // -template inline -int strcmpAsciiNoCase(const Char* lhs, const Char* rhs, size_t len) +template inline +int strcmpAsciiNoCase(const Char1* lhs, const Char2* rhs, size_t len) { while (len-- > 0) { - const Char charL = asciiToLower(*lhs++); //ordering: lower-case chars have higher code points than uppper-case - const Char charR = asciiToLower(*rhs++); // + const Char1 charL = asciiToLower(*lhs++); //ordering: lower-case chars have higher code points than uppper-case + const Char2 charR = asciiToLower(*rhs++); // if (charL != charR) return static_cast(charL) - static_cast(charR); //unsigned char-comparison is the convention! //unsigned underflow is well-defined! diff --git a/zen/string_traits.h b/zen/string_traits.h index f1269130..69d76b44 100644 --- a/zen/string_traits.h +++ b/zen/string_traits.h @@ -220,7 +220,8 @@ auto makeStringView(Iterator first, Iterator last) last - first); } -template inline auto makeStringView(Iterator first, size_t len) { return makeStringView(first, first + len); } +template inline +auto makeStringView(Iterator first, size_t len) { return makeStringView(first, first + len); } } #endif //STRING_TRAITS_H_813274321443234 diff --git a/zen/sys_error.cpp b/zen/sys_error.cpp index 2acaca1d..b802780b 100644 --- a/zen/sys_error.cpp +++ b/zen/sys_error.cpp @@ -4,3 +4,187 @@ // * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * // ***************************************************************************** +#include "sys_error.h" + #include + +using namespace zen; + + + + +std::wstring zen::getSystemErrorDescription(ErrorCode ec) //return empty string on error +{ + const ErrorCode currentError = getLastError(); //not necessarily == ec + ZEN_ON_SCOPE_EXIT(errno = currentError); + + std::wstring errorMsg; + errorMsg = utfTo(::strerror(ec)); + return trimCpy(errorMsg); //Windows messages seem to end with a space... +} + + +namespace +{ +std::wstring formatSystemErrorCode(ErrorCode ec) +{ + switch (ec) //pretty much all codes currently used on CentOS 7 and macOS 10.15 + { + ZEN_CHECK_CASE_FOR_CONSTANT(EPERM); + ZEN_CHECK_CASE_FOR_CONSTANT(ENOENT); + ZEN_CHECK_CASE_FOR_CONSTANT(ESRCH); + ZEN_CHECK_CASE_FOR_CONSTANT(EINTR); + ZEN_CHECK_CASE_FOR_CONSTANT(EIO); + ZEN_CHECK_CASE_FOR_CONSTANT(ENXIO); + ZEN_CHECK_CASE_FOR_CONSTANT(E2BIG); + ZEN_CHECK_CASE_FOR_CONSTANT(ENOEXEC); + ZEN_CHECK_CASE_FOR_CONSTANT(EBADF); + ZEN_CHECK_CASE_FOR_CONSTANT(ECHILD); + ZEN_CHECK_CASE_FOR_CONSTANT(EAGAIN); + ZEN_CHECK_CASE_FOR_CONSTANT(ENOMEM); + ZEN_CHECK_CASE_FOR_CONSTANT(EACCES); + ZEN_CHECK_CASE_FOR_CONSTANT(EFAULT); + ZEN_CHECK_CASE_FOR_CONSTANT(ENOTBLK); + ZEN_CHECK_CASE_FOR_CONSTANT(EBUSY); + ZEN_CHECK_CASE_FOR_CONSTANT(EEXIST); + ZEN_CHECK_CASE_FOR_CONSTANT(EXDEV); + ZEN_CHECK_CASE_FOR_CONSTANT(ENODEV); + ZEN_CHECK_CASE_FOR_CONSTANT(ENOTDIR); + ZEN_CHECK_CASE_FOR_CONSTANT(EISDIR); + ZEN_CHECK_CASE_FOR_CONSTANT(EINVAL); + ZEN_CHECK_CASE_FOR_CONSTANT(ENFILE); + ZEN_CHECK_CASE_FOR_CONSTANT(EMFILE); + ZEN_CHECK_CASE_FOR_CONSTANT(ENOTTY); + ZEN_CHECK_CASE_FOR_CONSTANT(ETXTBSY); + ZEN_CHECK_CASE_FOR_CONSTANT(EFBIG); + ZEN_CHECK_CASE_FOR_CONSTANT(ENOSPC); + ZEN_CHECK_CASE_FOR_CONSTANT(ESPIPE); + ZEN_CHECK_CASE_FOR_CONSTANT(EROFS); + ZEN_CHECK_CASE_FOR_CONSTANT(EMLINK); + ZEN_CHECK_CASE_FOR_CONSTANT(EPIPE); + ZEN_CHECK_CASE_FOR_CONSTANT(EDOM); + ZEN_CHECK_CASE_FOR_CONSTANT(ERANGE); + ZEN_CHECK_CASE_FOR_CONSTANT(EDEADLK); + ZEN_CHECK_CASE_FOR_CONSTANT(ENAMETOOLONG); + ZEN_CHECK_CASE_FOR_CONSTANT(ENOLCK); + ZEN_CHECK_CASE_FOR_CONSTANT(ENOSYS); + ZEN_CHECK_CASE_FOR_CONSTANT(ENOTEMPTY); + ZEN_CHECK_CASE_FOR_CONSTANT(ELOOP); + ZEN_CHECK_CASE_FOR_CONSTANT(ENOMSG); + ZEN_CHECK_CASE_FOR_CONSTANT(EIDRM); + ZEN_CHECK_CASE_FOR_CONSTANT(ENOSTR); + ZEN_CHECK_CASE_FOR_CONSTANT(ENODATA); + ZEN_CHECK_CASE_FOR_CONSTANT(ETIME); + ZEN_CHECK_CASE_FOR_CONSTANT(ENOSR); + ZEN_CHECK_CASE_FOR_CONSTANT(EREMOTE); + ZEN_CHECK_CASE_FOR_CONSTANT(ENOLINK); + ZEN_CHECK_CASE_FOR_CONSTANT(EPROTO); + ZEN_CHECK_CASE_FOR_CONSTANT(EMULTIHOP); + ZEN_CHECK_CASE_FOR_CONSTANT(EBADMSG); + ZEN_CHECK_CASE_FOR_CONSTANT(EOVERFLOW); + ZEN_CHECK_CASE_FOR_CONSTANT(EILSEQ); + ZEN_CHECK_CASE_FOR_CONSTANT(EUSERS); + ZEN_CHECK_CASE_FOR_CONSTANT(ENOTSOCK); + ZEN_CHECK_CASE_FOR_CONSTANT(EDESTADDRREQ); + ZEN_CHECK_CASE_FOR_CONSTANT(EMSGSIZE); + ZEN_CHECK_CASE_FOR_CONSTANT(EPROTOTYPE); + ZEN_CHECK_CASE_FOR_CONSTANT(ENOPROTOOPT); + ZEN_CHECK_CASE_FOR_CONSTANT(EPROTONOSUPPORT); + ZEN_CHECK_CASE_FOR_CONSTANT(ESOCKTNOSUPPORT); + ZEN_CHECK_CASE_FOR_CONSTANT(ENOTSUP); + ZEN_CHECK_CASE_FOR_CONSTANT(EPFNOSUPPORT); + ZEN_CHECK_CASE_FOR_CONSTANT(EAFNOSUPPORT); + ZEN_CHECK_CASE_FOR_CONSTANT(EADDRINUSE); + ZEN_CHECK_CASE_FOR_CONSTANT(EADDRNOTAVAIL); + ZEN_CHECK_CASE_FOR_CONSTANT(ENETDOWN); + ZEN_CHECK_CASE_FOR_CONSTANT(ENETUNREACH); + ZEN_CHECK_CASE_FOR_CONSTANT(ENETRESET); + ZEN_CHECK_CASE_FOR_CONSTANT(ECONNABORTED); + ZEN_CHECK_CASE_FOR_CONSTANT(ECONNRESET); + ZEN_CHECK_CASE_FOR_CONSTANT(ENOBUFS); + ZEN_CHECK_CASE_FOR_CONSTANT(EISCONN); + ZEN_CHECK_CASE_FOR_CONSTANT(ENOTCONN); + ZEN_CHECK_CASE_FOR_CONSTANT(ESHUTDOWN); + ZEN_CHECK_CASE_FOR_CONSTANT(ETOOMANYREFS); + ZEN_CHECK_CASE_FOR_CONSTANT(ETIMEDOUT); + ZEN_CHECK_CASE_FOR_CONSTANT(ECONNREFUSED); + ZEN_CHECK_CASE_FOR_CONSTANT(EHOSTDOWN); + ZEN_CHECK_CASE_FOR_CONSTANT(EHOSTUNREACH); + ZEN_CHECK_CASE_FOR_CONSTANT(EALREADY); + ZEN_CHECK_CASE_FOR_CONSTANT(EINPROGRESS); + ZEN_CHECK_CASE_FOR_CONSTANT(ESTALE); + ZEN_CHECK_CASE_FOR_CONSTANT(EDQUOT); + ZEN_CHECK_CASE_FOR_CONSTANT(ECANCELED); + ZEN_CHECK_CASE_FOR_CONSTANT(EOWNERDEAD); + ZEN_CHECK_CASE_FOR_CONSTANT(ENOTRECOVERABLE); + + ZEN_CHECK_CASE_FOR_CONSTANT(ECHRNG); + ZEN_CHECK_CASE_FOR_CONSTANT(EL2NSYNC); + ZEN_CHECK_CASE_FOR_CONSTANT(EL3HLT); + ZEN_CHECK_CASE_FOR_CONSTANT(EL3RST); + ZEN_CHECK_CASE_FOR_CONSTANT(ELNRNG); + ZEN_CHECK_CASE_FOR_CONSTANT(EUNATCH); + ZEN_CHECK_CASE_FOR_CONSTANT(ENOCSI); + ZEN_CHECK_CASE_FOR_CONSTANT(EL2HLT); + ZEN_CHECK_CASE_FOR_CONSTANT(EBADE); + ZEN_CHECK_CASE_FOR_CONSTANT(EBADR); + ZEN_CHECK_CASE_FOR_CONSTANT(EXFULL); + ZEN_CHECK_CASE_FOR_CONSTANT(ENOANO); + ZEN_CHECK_CASE_FOR_CONSTANT(EBADRQC); + ZEN_CHECK_CASE_FOR_CONSTANT(EBADSLT); + ZEN_CHECK_CASE_FOR_CONSTANT(EBFONT); + ZEN_CHECK_CASE_FOR_CONSTANT(ENONET); + ZEN_CHECK_CASE_FOR_CONSTANT(ENOPKG); + ZEN_CHECK_CASE_FOR_CONSTANT(EADV); + ZEN_CHECK_CASE_FOR_CONSTANT(ESRMNT); + ZEN_CHECK_CASE_FOR_CONSTANT(ECOMM); + ZEN_CHECK_CASE_FOR_CONSTANT(EDOTDOT); + ZEN_CHECK_CASE_FOR_CONSTANT(ENOTUNIQ); + ZEN_CHECK_CASE_FOR_CONSTANT(EBADFD); + ZEN_CHECK_CASE_FOR_CONSTANT(EREMCHG); + ZEN_CHECK_CASE_FOR_CONSTANT(ELIBACC); + ZEN_CHECK_CASE_FOR_CONSTANT(ELIBBAD); + ZEN_CHECK_CASE_FOR_CONSTANT(ELIBSCN); + ZEN_CHECK_CASE_FOR_CONSTANT(ELIBMAX); + ZEN_CHECK_CASE_FOR_CONSTANT(ELIBEXEC); + ZEN_CHECK_CASE_FOR_CONSTANT(ERESTART); + ZEN_CHECK_CASE_FOR_CONSTANT(ESTRPIPE); + ZEN_CHECK_CASE_FOR_CONSTANT(EUCLEAN); + ZEN_CHECK_CASE_FOR_CONSTANT(ENOTNAM); + ZEN_CHECK_CASE_FOR_CONSTANT(ENAVAIL); + ZEN_CHECK_CASE_FOR_CONSTANT(EISNAM); + ZEN_CHECK_CASE_FOR_CONSTANT(EREMOTEIO); + ZEN_CHECK_CASE_FOR_CONSTANT(ENOMEDIUM); + ZEN_CHECK_CASE_FOR_CONSTANT(EMEDIUMTYPE); + ZEN_CHECK_CASE_FOR_CONSTANT(ENOKEY); + ZEN_CHECK_CASE_FOR_CONSTANT(EKEYEXPIRED); + ZEN_CHECK_CASE_FOR_CONSTANT(EKEYREVOKED); + ZEN_CHECK_CASE_FOR_CONSTANT(EKEYREJECTED); + ZEN_CHECK_CASE_FOR_CONSTANT(ERFKILL); + ZEN_CHECK_CASE_FOR_CONSTANT(EHWPOISON); + default: + return replaceCpy(_("Error Code %x"), L"%x", numberTo(ec)); + } +} +} + + +std::wstring zen::formatSystemError(const std::wstring& functionName, ErrorCode ec) +{ + return formatSystemError(functionName, formatSystemErrorCode(ec), getSystemErrorDescription(ec)); +} + + +std::wstring zen::formatSystemError(const std::wstring& functionName, const std::wstring& errorCode, const std::wstring& errorMsg) +{ + std::wstring output = errorCode + L':'; + + const std::wstring errorMsgFmt = trimCpy(errorMsg); + if (!errorMsgFmt.empty()) + { + output += L' '; + output += errorMsgFmt; + } + + output += L" [" + functionName + L']'; + return output; +} diff --git a/zen/sys_error.h b/zen/sys_error.h index a9347bdd..6bef45ea 100644 --- a/zen/sys_error.h +++ b/zen/sys_error.h @@ -8,11 +8,10 @@ #define SYS_ERROR_H_3284791347018951324534 #include -#include "utf.h" -#include "i18n.h" #include "scope_guard.h" +#include "i18n.h" +#include "utf.h" - #include #include @@ -23,9 +22,8 @@ namespace zen ErrorCode getLastError(); -std::wstring formatSystemError(const std::wstring& functionName, ErrorCode ec); std::wstring formatSystemError(const std::wstring& functionName, const std::wstring& errorCode, const std::wstring& errorMsg); - +std::wstring formatSystemError(const std::wstring& functionName, ErrorCode ec); //A low-level exception class giving (non-translated) detail information only - same conceptional level like "GetLastError()"! @@ -47,11 +45,6 @@ private: do { const ErrorCode ecInternal = getLastError(); throw SysError(formatSystemError(functionName, ecInternal)); } while (false) -//helper for error checking macros: -inline bool validatBool(bool b) { return b; } -inline bool validatBool(void* b) { return b != nullptr; } -bool validatBool(int) = delete; //catch unintended bool conversions, e.g. HRESULT - @@ -59,56 +52,15 @@ bool validatBool(int) = delete; //catch unintended bool conversions, e.g. HRESUL inline ErrorCode getLastError() { - return errno; //don't use "::", errno is a macro! + return errno; //don't use "::" prefix, errno is a macro! } -std::wstring formatSystemErrorRaw(long long) = delete; //intentional overload ambiguity to catch usage errors - -inline -std::wstring formatSystemErrorRaw(ErrorCode ec) //return empty string on error -{ - const ErrorCode currentError = getLastError(); //not necessarily == lastError - - std::wstring errorMsg; - ZEN_ON_SCOPE_EXIT(errno = currentError); - - errorMsg = utfTo(::strerror(ec)); - trim(errorMsg); //Windows messages seem to end with a blank... - - return errorMsg; -} +std::wstring getSystemErrorDescription(ErrorCode ec); //return empty string on error +//intentional overload ambiguity to catch usage errors with HRESULT: +std::wstring getSystemErrorDescription(long long) = delete; -std::wstring formatSystemError(const std::wstring& functionName, long long lastError) = delete; //intentional overload ambiguity to catch usage errors with HRESULT! - -inline -std::wstring formatSystemError(const std::wstring& functionName, ErrorCode ec) -{ - const std::wstring errorCode = numberTo(ec); - const std::wstring errorDescr = formatSystemErrorRaw(ec); - - return formatSystemError(functionName, replaceCpy(_("Error Code %x"), L"%x", errorCode), errorDescr); -} - - -inline -std::wstring formatSystemError(const std::wstring& functionName, const std::wstring& errorCode, const std::wstring& errorMsg) -{ - std::wstring output = errorCode + L":"; - - const std::wstring errorMsgFmt = trimCpy(errorMsg); - if (!errorMsgFmt.empty()) - { - output += L" "; - output += errorMsgFmt; - } - - output += L" [" + functionName + L"]"; - - return output; -} - } #endif //SYS_ERROR_H_3284791347018951324534 diff --git a/zen/system.cpp b/zen/system.cpp index 5945484f..9401b94f 100644 --- a/zen/system.cpp +++ b/zen/system.cpp @@ -44,6 +44,7 @@ namespace ComputerModel zen::getComputerModel() //throw FileError { + ComputerModel cm; try { auto tryGetInfo = [](const Zstring& filePath) @@ -57,9 +58,33 @@ ComputerModel zen::getComputerModel() //throw FileError } catch (const FileError& e) { throw SysError(e.toString()); } //errors should be further enriched by context info => SysError }; - return { tryGetInfo("/sys/devices/virtual/dmi/id/product_name"), //throw SysError - tryGetInfo("/sys/devices/virtual/dmi/id/sys_vendor") }; // + cm.model = tryGetInfo("/sys/devices/virtual/dmi/id/product_name"); //throw SysError + cm.vendor = tryGetInfo("/sys/devices/virtual/dmi/id/sys_vendor"); // + + //clean up: + for (const char* dummyModel : + { + "To Be Filled By O.E.M.", "Default string", "empty", "O.E.M", "OEM", "NA", + "System Product Name", "Please change product name", "INVALID", + }) + if (equalAsciiNoCase(cm.model, dummyModel)) + { + cm.model.clear(); + break; + } + + for (const char* dummyVendor : + { + "To Be Filled By O.E.M.", "Default string", "empty", "O.E.M", "OEM", "NA", + "System manufacturer", "OEM Manufacturer", + }) + if (equalAsciiNoCase(cm.vendor, dummyVendor)) + { + cm.vendor.clear(); + break; + } + return cm; } catch (const SysError& e) { throw FileError(_("Cannot get process information."), e.toString()); } } @@ -73,7 +98,7 @@ std::wstring zen::getOsDescription() //throw FileError { const std::string osName = trimCpy(getCommandOutput("lsb_release --id -s" )); //throw SysError const std::string osVersion = trimCpy(getCommandOutput("lsb_release --release -s")); // - return utfTo(osName + " " + osVersion); //e.g. "CentOS 7.7.1908" + return utfTo(osName + ' ' + osVersion); //e.g. "CentOS 7.7.1908" } catch (const SysError& e) { throw FileError(_("Cannot get process information."), e.toString()); } diff --git a/zen/thread.cpp b/zen/thread.cpp index 49c6d9b3..6e8b8219 100644 --- a/zen/thread.cpp +++ b/zen/thread.cpp @@ -28,7 +28,7 @@ uint64_t getThreadIdNative() const pid_t tid = ::syscall(SYS_gettid); //no-fail //"Invalid thread and process IDs": https://devblogs.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(__LINE__) + "] Failed to get thread ID."); + // throw std::runtime_error(std::string(__FILE__) + '[' + numberTo(__LINE__) + "] Failed to get thread ID."); static_assert(sizeof(uint64_t) >= sizeof(tid)); return tid; } diff --git a/zen/thread.h b/zen/thread.h index d6cafab7..a1d5197c 100644 --- a/zen/thread.h +++ b/zen/thread.h @@ -149,7 +149,7 @@ class ThreadGroup { public: 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(__LINE__)); } + { if (threadCountMax == 0) throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); } ~ThreadGroup() { diff --git a/zen/time.h b/zen/time.h index 27ce518f..9718e5f6 100644 --- a/zen/time.h +++ b/zen/time.h @@ -8,7 +8,7 @@ #define TIME_H_8457092814324342453627 #include -#include "string_tools.h" +#include "zstring.h" namespace zen @@ -24,7 +24,7 @@ struct TimeComp //replaces std::tm and SYSTEMTIME }; inline bool operator==(const TimeComp& lhs, const TimeComp& rhs) { - return lhs.year == rhs.year && lhs.month == rhs.month && lhs.day == rhs.day && lhs.hour == rhs.hour && lhs.minute == rhs.minute && lhs.second == rhs.second; + return lhs.second == rhs.second && lhs.minute == rhs.minute && lhs.hour == rhs.hour && lhs.day == rhs.day && lhs.month == rhs.month && lhs.year == rhs.year; } inline bool operator!=(const TimeComp& lhs, const TimeComp& rhs) { return !(lhs == rhs); } @@ -37,34 +37,26 @@ time_t utcToTimeT(const TimeComp& tc); //convert UTC time compone TimeComp getCompileTime(); //returns TimeComp() on error //---------------------------------------------------------------------------------------------------------------------------------- - -/* -format (current) date and time; example: - formatTime(L"%Y|%m|%d"); -> "2011|10|29" - formatTime(FORMAT_DATE); -> "2011-10-29" - formatTime(FORMAT_TIME); -> "17:55:34" -*/ -template -String formatTime(const String2& format, const TimeComp& tc = getLocalTime()); //format as specified by "std::strftime", returns empty string on failure +/* format (current) date and time; example: + formatTime(Zstr("%Y|%m|%d")); -> "2011|10|29" + formatTime(formatDateTag); -> "2011-10-29" + formatTime(formatTimeTag); -> "17:55:34" */ +Zstring formatTime(const Zchar* format, const TimeComp& tc = getLocalTime()); //format as specified by "std::strftime", returns empty string on failure //the "format" parameter of formatTime() is partially specialized with the following type tags: -const struct FormatDateTag {} FORMAT_DATE = {}; //%x - locale dependent date representation: e.g. 8/23/2001 -const struct FormatTimeTag {} FORMAT_TIME = {}; //%X - locale dependent time representation: e.g. 2:55:02 PM -const struct FormatDateTimeTag {} FORMAT_DATE_TIME = {}; //%c - locale dependent date and time: e.g. 8/23/2001 2:55:02 PM +const Zchar* const formatDateTag = Zstr("%x"); //locale-dependent date representation: e.g. 8/23/2001 +const Zchar* const formatTimeTag = Zstr("%X"); //locale-dependent time representation: e.g. 2:55:02 PM +const Zchar* const formatDateTimeTag = Zstr("%c"); //locale-dependent date and time: e.g. 8/23/2001 2:55:02 PM -const struct FormatIsoDateTag {} FORMAT_ISO_DATE = {}; //%Y-%m-%d - e.g. 2001-08-23 -const struct FormatIsoTimeTag {} FORMAT_ISO_TIME = {}; //%H:%M:%S - e.g. 14:55:02 -const struct FormatIsoDateTimeTag {} FORMAT_ISO_DATE_TIME = {}; //%Y-%m-%d %H:%M:%S - e.g. 2001-08-23 14:55:02 +const Zchar* const formatIsoDateTag = Zstr("%Y-%m-%d"); //e.g. 2001-08-23 +const Zchar* const formatIsoTimeTag = Zstr("%H:%M:%S"); //e.g. 14:55:02 +const Zchar* const formatIsoDateTimeTag = Zstr("%Y-%m-%d %H:%M:%S"); //e.g. 2001-08-23 14:55:02 //---------------------------------------------------------------------------------------------------------------------------------- - -/* -example: parseTime("%Y-%m-%d %H:%M:%S", "2001-08-23 14:55:02"); - parseTime(FORMAT_ISO_DATE_TIME, "2001-08-23 14:55:02"); -*/ +//example: parseTime("%Y-%m-%d %H:%M:%S", "2001-08-23 14:55:02"); +// parseTime(formatIsoDateTimeTag, "2001-08-23 14:55:02"); template TimeComp parseTime(const String& format, const String2& str); //similar to ::strptime() - //---------------------------------------------------------------------------------------------------------------------------------- @@ -118,68 +110,6 @@ TimeComp toZenTimeComponents(const std::tm& ctc) } -template -struct GetFormat; //get default time formats as char* or wchar_t* - -template <> -struct GetFormat //%x - locale dependent date representation: e.g. 08/23/01 -{ - const char* format(char) const { return "%x"; } - const wchar_t* format(wchar_t) const { return L"%x"; } -}; - -template <> -struct GetFormat //%X - locale dependent time representation: e.g. 14:55:02 -{ - const char* format(char) const { return "%X"; } - const wchar_t* format(wchar_t) const { return L"%X"; } -}; - -template <> -struct GetFormat //%c - locale dependent date and time: e.g. Thu Aug 23 14:55:02 2001 -{ - const char* format(char) const { return "%c"; } - const wchar_t* format(wchar_t) const { return L"%c"; } -}; - -template <> -struct GetFormat //%Y-%m-%d - e.g. 2001-08-23 -{ - const char* format(char) const { return "%Y-%m-%d"; } - const wchar_t* format(wchar_t) const { return L"%Y-%m-%d"; } -}; - -template <> -struct GetFormat //%H:%M:%S - e.g. 14:55:02 -{ - const char* format(char) const { return "%H:%M:%S"; } - const wchar_t* format(wchar_t) const { return L"%H:%M:%S"; } -}; - -template <> -struct GetFormat //%Y-%m-%d %H:%M:%S - e.g. 2001-08-23 14:55:02 -{ - const char* format(char) const { return "%Y-%m-%d %H:%M:%S"; } - const wchar_t* format(wchar_t) const { return L"%Y-%m-%d %H:%M:%S"; } -}; - - -//strftime() craziness on invalid input: -// VS 2010: CRASH unless "_invalid_parameter_handler" is set: https://msdn.microsoft.com/en-us/library/ksazx244.aspx -// GCC: returns 0, apparently no crash. Still, considering some clib maintainer's comments, we should expect the worst! -inline -size_t strftimeWrap_impl(char* buffer, size_t bufferSize, const char* format, const std::tm* timeptr) -{ - return std::strftime(buffer, bufferSize, format, timeptr); -} - - -inline -size_t strftimeWrap_impl(wchar_t* buffer, size_t bufferSize, const wchar_t* format, const std::tm* timeptr) -{ - return std::wcsftime(buffer, bufferSize, format, timeptr); -} - /* inline bool isValid(const std::tm& t) @@ -203,35 +133,6 @@ bool isValid(const std::tm& t) //tm_isdst }; */ - -template inline -size_t strftimeWrap(CharType* buffer, size_t bufferSize, const CharType* format, const std::tm* timeptr) -{ - return strftimeWrap_impl(buffer, bufferSize, format, timeptr); -} - - -struct UserDefinedFormatTag {}; -struct PredefinedFormatTag {}; - -template inline -String formatTime(const String2& format, const TimeComp& tc, UserDefinedFormatTag) //format as specified by "std::strftime", returns empty string on failure -{ - 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 - - GetCharTypeT buffer[256] = {}; - const size_t charsWritten = strftimeWrap(buffer, 256, strBegin(format), &ctc); - return String(buffer, charsWritten); -} - - -template inline -String formatTime(FormatType, const TimeComp& tc, PredefinedFormatTag) -{ - return formatTime(GetFormat().format(GetCharTypeT()), tc, UserDefinedFormatTag()); -} } @@ -298,28 +199,29 @@ TimeComp getCompileTime() } -template inline -String formatTime(const String2& format, const TimeComp& tc) +inline +Zstring formatTime(const Zchar* format, const TimeComp& tc) { if (tc == TimeComp()) //failure code from getLocalTime() - return String(); + return Zstring(); - using FormatTag = std::conditional_t< - std::is_same_v || - std::is_same_v || - std::is_same_v || - std::is_same_v || - std::is_same_v || - std::is_same_v, impl::PredefinedFormatTag, impl::UserDefinedFormatTag>; + std::tm ctc = impl::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 - return impl::formatTime(format, tc, FormatTag()); + Zstring buffer(256, Zstr('\0')); + //strftime() craziness on invalid input: + // VS 2010: CRASH unless "_invalid_parameter_handler" is set: https://docs.microsoft.com/en-us/cpp/c-runtime-library/parameter-validation + // GCC: returns 0, apparently no crash. Still, considering some clib maintainer's comments, we should expect the worst! + // Windows: avoid char-based strftime() which uses ANSI encoding! (e.g. Greek letters for AM/PM) + const size_t charsWritten = std::strftime(&buffer[0], buffer.size(), format, &ctc); + buffer.resize(charsWritten); + return buffer; } -namespace impl -{ template -TimeComp parseTime(const String& format, const String2& str, UserDefinedFormatTag) +TimeComp parseTime(const String& format, const String2& str) { using CharType = GetCharTypeT; static_assert(std::is_same_v>); @@ -371,11 +273,9 @@ TimeComp parseTime(const String& format, const String2& str, UserDefinedFormatTa return TimeComp(); const char* months[] = { "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec" }; - auto itMonth = std::find_if(std::begin(months), std::end(months), [&](const char* name) + auto itMonth = std::find_if(std::begin(months), std::end(months), [&](const char* month) { - return asciiToLower(itStr[0]) == name[0] && - asciiToLower(itStr[1]) == name[1] && - asciiToLower(itStr[2]) == name[2]; + return equalAsciiNoCase(makeStringView(itStr, 3), month); }); if (itMonth == std::end(months)) return TimeComp(); @@ -422,26 +322,6 @@ TimeComp parseTime(const String& format, const String2& str, UserDefinedFormatTa return output; } - - -template inline -TimeComp parseTime(FormatType, const String& str, PredefinedFormatTag) -{ - return parseTime(GetFormat().format(GetCharTypeT()), str, UserDefinedFormatTag()); -} -} - - -template inline -TimeComp parseTime(const String& format, const String2& str) -{ - using FormatTag = std::conditional_t< - std::is_same_v || - std::is_same_v || - std::is_same_v, impl::PredefinedFormatTag, impl::UserDefinedFormatTag>; - - return impl::parseTime(format, str, FormatTag()); -} } #endif //TIME_H_8457092814324342453627 diff --git a/zen/zlib_wrap.cpp b/zen/zlib_wrap.cpp index 57a0f33c..685843c3 100644 --- a/zen/zlib_wrap.cpp +++ b/zen/zlib_wrap.cpp @@ -29,8 +29,10 @@ std::wstring formatZlibStatusCode(int sc) ZEN_CHECK_CASE_FOR_CONSTANT(Z_MEM_ERROR); ZEN_CHECK_CASE_FOR_CONSTANT(Z_BUF_ERROR); ZEN_CHECK_CASE_FOR_CONSTANT(Z_VERSION_ERROR); + + default: + return replaceCpy(L"zlib status %x", L"%x", numberTo(sc)); } - return replaceCpy(L"zlib status %x.", L"%x", numberTo(sc)); } } @@ -53,7 +55,7 @@ size_t zen::impl::zlib_compress(const void* src, size_t srcLen, void* trg, size_ // Z_MEM_ERROR: not enough memory // Z_BUF_ERROR: not enough room in the output buffer if (rv != Z_OK || bufferSize > trgLen) - throw SysError(formatSystemError(L"compress2", formatZlibStatusCode(rv), L"zlib error")); + throw SysError(formatSystemError(L"zlib compress2", formatZlibStatusCode(rv), L"")); return bufferSize; } @@ -71,7 +73,7 @@ size_t zen::impl::zlib_decompress(const void* src, size_t srcLen, void* trg, siz // Z_BUF_ERROR: not enough room in the output buffer // Z_DATA_ERROR: input data was corrupted or incomplete if (rv != Z_OK || bufferSize > trgLen) - throw SysError(formatSystemError(L"uncompress", formatZlibStatusCode(rv), L"zlib error")); + throw SysError(formatSystemError(L"zlib uncompress", formatZlibStatusCode(rv), L"")); return bufferSize; } @@ -96,7 +98,7 @@ public: memLevel, //int memLevel Z_DEFAULT_STRATEGY); //int strategy if (rv != Z_OK) - throw SysError(formatSystemError(L"deflateInit2", formatZlibStatusCode(rv), L"zlib error")); + throw SysError(formatSystemError(L"zlib deflateInit2", formatZlibStatusCode(rv), L"")); } ~Impl() @@ -108,7 +110,7 @@ public: size_t read(void* buffer, size_t bytesToRead) //throw SysError, X; return "bytesToRead" bytes unless end of stream! { 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(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); gzipStream_.next_out = static_cast(buffer); gzipStream_.avail_out = static_cast(bytesToRead); @@ -131,7 +133,7 @@ public: if (rv == Z_STREAM_END) return bytesToRead - gzipStream_.avail_out; if (rv != Z_OK) - throw SysError(formatSystemError(L"deflate", formatZlibStatusCode(rv), L"zlib error")); + throw SysError(formatSystemError(L"zlib deflate", formatZlibStatusCode(rv), L"")); if (gzipStream_.avail_out == 0) return bytesToRead; diff --git a/zen/zlib_wrap.h b/zen/zlib_wrap.h index b820a4f8..3db609da 100644 --- a/zen/zlib_wrap.h +++ b/zen/zlib_wrap.h @@ -63,7 +63,7 @@ BinContainer compress(const BinContainer& stream, int level) //throw SysError //save uncompressed stream size for decompression const uint64_t uncompressedSize = stream.size(); //use portable number type! contOut.resize(sizeof(uncompressedSize)); - std::memcpy(&*contOut.begin(), &uncompressedSize, sizeof(uncompressedSize)); + std::memcpy(&contOut[0], &uncompressedSize, sizeof(uncompressedSize)); const size_t bufferEstimate = impl::zlib_compressBound(stream.size()); //upper limit for buffer size, larger than input size!!! @@ -105,8 +105,8 @@ BinContainer decompress(const BinContainer& stream) //throw SysError contOut.resize(static_cast(uncompressedSize)); //throw std::bad_alloc } //most likely this is due to data corruption: - catch (const std::length_error& e) { throw SysError(L"zlib error: " + _("Out of memory.") + L" " + utfTo(e.what())); } - catch (const std::bad_alloc& e) { throw SysError(L"zlib error: " + _("Out of memory.") + L" " + utfTo(e.what())); } + catch (const std::length_error& e) { throw SysError(L"zlib error: " + _("Out of memory.") + L' ' + utfTo(e.what())); } + catch (const std::bad_alloc& e) { throw SysError(L"zlib error: " + _("Out of memory.") + L' ' + utfTo(e.what())); } const size_t bytesWritten = impl::zlib_decompress(&*stream.begin() + sizeof(uncompressedSize), stream.size() - sizeof(uncompressedSize), diff --git a/zen/zstring.cpp b/zen/zstring.cpp index ff20b8cf..046a3bd4 100644 --- a/zen/zstring.cpp +++ b/zen/zstring.cpp @@ -50,7 +50,8 @@ Zstring getUnicodeNormalForm(const Zstring& str) { //fast pre-check: if (isAsciiString(str)) //perf: in the range of 3.5ns - return str; //god bless our ref-counting! => save output string memory consumption! + return str; + static_assert(std::is_same_v&>, "god bless our ref-counting! => save output string memory consumption!"); //Example: const char* decomposed = "\x6f\xcc\x81"; // const char* precomposed = "\xc3\xb3"; diff --git a/zen/zstring.h b/zen/zstring.h index f24e4299..d5d8c588 100644 --- a/zen/zstring.h +++ b/zen/zstring.h @@ -19,8 +19,9 @@ //a high-performance string for interfacing with native OS APIs in multithreaded contexts using Zstring = zen::Zbase; -//for special UI-contexts: guaranteed exponential growth + ref-counting -using Zstringw = zen::Zbase; +//for special UI-contexts: guaranteed exponential growth + ref-counting + COW + no SSO overhead +using Zstringc = zen::Zbase; +//using Zstringw = zen::Zbase; //Caveat: don't expect input/output string sizes to match: @@ -34,7 +35,7 @@ Zstring makeUpperCopy(const Zstring& str); Zstring getUnicodeNormalForm(const Zstring& str); // "In fact, Unicode declares that there is an equivalence relationship between decomposed and composed sequences, // and conformant software should not treat canonically equivalent sequences, whether composed or decomposed or something in between, as different." -// http://www.win.tue.nl/~aeb/linux/uc/nfc_vs_nfd.html +// http://www.win.tue.nl/~aeb/linux/uc/nfc_vs_nfd.html struct LessUnicodeNormal { bool operator()(const Zstring& lhs, const Zstring& rhs) const { return getUnicodeNormalForm(lhs) < getUnicodeNormalForm(rhs);} }; diff --git a/zenXml/zenxml/cvrt_struc.h b/zenXml/zenxml/cvrt_struc.h index 9df1c7ba..45109187 100644 --- a/zenXml/zenxml/cvrt_struc.h +++ b/zenXml/zenxml/cvrt_struc.h @@ -57,11 +57,11 @@ ZEN_INIT_DETECT_MEMBER(insert) // template using IsStlContainer = std::bool_constant< - impl_2384343::HasMemberTypeV_value_type && - impl_2384343::HasMemberTypeV_iterator && - impl_2384343::HasMemberTypeV_const_iterator && - impl_2384343::HasMemberV_begin && - impl_2384343::HasMemberV_end && + impl_2384343::HasMemberTypeV_value_type && + impl_2384343::HasMemberTypeV_iterator && + impl_2384343::HasMemberTypeV_const_iterator&& + impl_2384343::HasMemberV_begin && + impl_2384343::HasMemberV_end && impl_2384343::HasMemberV_insert >; diff --git a/zenXml/zenxml/dom.h b/zenXml/zenxml/dom.h index 0f456822..35226fc7 100644 --- a/zenXml/zenxml/dom.h +++ b/zenXml/zenxml/dom.h @@ -230,7 +230,7 @@ public: \code auto iterPair = elem.getAttributes(); for (auto it = iterPair.first; it != iterPair.second; ++it) - std::cout << "name: " << it->name << " value: " << it->value << "\n"; + std::cout << "name: " << it->name << " value: " << it->value << '\n'; \endcode \return A pair of STL begin/end iterators to access all attributes sequentially as a list of name/value pairs of std::string. */ diff --git a/zenXml/zenxml/xml.h b/zenXml/zenxml/xml.h index 80b60730..4058f7bf 100644 --- a/zenXml/zenxml/xml.h +++ b/zenXml/zenxml/xml.h @@ -369,8 +369,8 @@ public: { std::vector output; - for (const std::string& str : log_.ref().elementList()) - output.push_back(utfTo(str)); + for (const std::string& str : log_.ref().elementList()) + output.push_back(utfTo(str)); return output; } @@ -382,7 +382,7 @@ private: static std::string getNameFormatted(const XmlElement& elem) //" " { - return (elem.parent() ? getNameFormatted(*elem.parent()) + " " : std::string()) + "<" + elem.getNameAs() + ">"; + return (elem.parent() ? getNameFormatted(*elem.parent()) + ' ' : std::string()) + '<' + elem.getNameAs() + '>'; } std::string getNameFormatted() const @@ -399,7 +399,7 @@ private: std::string getChildNameFormatted(const std::string& childName) const { std::string parentName = getNameFormatted(); - return (parentName.empty() ? std::string() : (parentName + " ")) + "<" + childName + ">"; + return (parentName.empty() ? std::string() : (parentName + ' ')) + '<' + childName + '>'; } class ErrorLog @@ -440,9 +440,9 @@ void checkXmlMappingErrors(const XmlIn& xmlInput, const Zstring& filePath) //thr { if (xmlInput.haveErrors()) { - std::wstring msg = _("The following XML elements could not be read:") + L"\n"; + std::wstring msg = _("The following XML elements could not be read:") + L'\n'; for (const std::wstring& elem : xmlInput.getErrorsAs()) - msg += L"\n" + elem; + msg += L'\n' + elem; throw FileError(replaceCpy(_("Configuration file %x is incomplete. The missing elements will be set to their default values."), L"%x", fmtPath(filePath)) + L"\n\n" + msg); } -- cgit