diff options
author | Daniel Wilhelm <shieldwed@outlook.com> | 2020-03-20 22:34:51 +0000 |
---|---|---|
committer | Daniel Wilhelm <shieldwed@outlook.com> | 2020-03-20 22:34:51 +0000 |
commit | 543ddcd795ba2fa44676b59135a745f51f28a8da (patch) | |
tree | 5c378aa54f4bb65c081cf9a92530d8af1f1f53dd | |
parent | Merge branch '10.20' into 'master' (diff) | |
parent | add upstream 10.21 (diff) | |
download | FreeFileSync-10.21.tar.gz FreeFileSync-10.21.tar.bz2 FreeFileSync-10.21.zip |
Merge branch '10.21' into 'master'10.21
add upstream 10.21
See merge request opensource-tracking/FreeFileSync!18
138 files changed, 2109 insertions, 1802 deletions
@@ -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 Binary files differindex 79a4dd7f..0a393bbc 100644 --- a/FreeFileSync/Build/Resources/Icons.zip +++ b/FreeFileSync/Build/Resources/Icons.zip diff --git a/FreeFileSync/Build/Resources/Languages.zip b/FreeFileSync/Build/Resources/Languages.zip Binary files differindex 81e8e960..3f00d604 100644 --- a/FreeFileSync/Build/Resources/Languages.zip +++ b/FreeFileSync/Build/Resources/Languages.zip diff --git a/FreeFileSync/Source/Makefile b/FreeFileSync/Source/Makefile index 87352ea4..5d4f6b20 100644..100755 --- 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 <wx+/popup_dlg.h> #include <wx+/image_resources.h> #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 <gtk/gtk.h> @@ -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<std::wstring>(error->code)), utfTo<std::wstring>(error->message))); + throw SysError(formatSystemError(L"gtk_css_provider_load_from_data", replaceCpy(_("Error Code %x"), L"%x", + numberTo<std::wstring>(error->code)), + utfTo<std::wstring>(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<std::string>(e.toString()) << "\n"; } + catch (const SysError& e) { std::cerr << utfTo<std::string>(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<std::wstring>(wxTheApp->GetAppDisplayName()) + SPACED_DASH + _("An exception occurred"); - std::cerr << utfTo<std::string>(titleFmt + SPACED_DASH) << e.what() << "\n"; + std::cerr << utfTo<std::string>(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 <zen/file_access.h> #include <zenxml/xml.h> #include <wx/intl.h> -#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 <wx/stattext.h> #include <wx/sizer.h> #include <wx/statbmp.h> +#include <wx/hyperlink.h> #include <wx/statline.h> #include <wx/bmpbuttn.h> #include <wx/button.h> @@ -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 <gtk/gtk.h> @@ -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<wxString>(FORMAT_DATE, getCompileTime()); + build += utfTo<wxString>(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<Zstring, LessNativePath> waitForMissingDirs(const std::vector<Zstring>& const std::function<void(const Zstring& folderPath)>& 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<std::wstring>(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<char>; //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 <write error> { 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<std::string>(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + zen::numberTo<std::string>(__LINE__)); auto it = static_cast<std::byte*>(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<std::wstring>(str) + L")"); //user should never see this - throw SysError(formatSystemError(L"g_convert", replaceCpy(_("Error Code %x"), L"%x", numberTo<std::wstring>(error->code)), - utfTo<std::wstring>(error->message)) + L" (" + utfTo<std::wstring>(str) + L")"); + throw SysError(formatSystemError(L"g_convert(" + utfTo<std::wstring>(str) + L")", + replaceCpy(_("Error Code %x"), L"%x", numberTo<std::wstring>(error->code)), utfTo<std::wstring>(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<std::wstring>(str) + L")"); //user should never see this - throw SysError(formatSystemError(L"g_convert", replaceCpy(_("Error Code %x"), L"%x", numberTo<std::wstring>(error->code)), - utfTo<std::wstring>(error->message)) + L" (" + utfTo<std::wstring>(str) + L")"); + throw SysError(formatSystemError(L"g_convert(" + utfTo<std::wstring>(str) + L")", + replaceCpy(_("Error Code %x"), L"%x", numberTo<std::wstring>(error->code)), utfTo<std::wstring>(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<std::wstring>(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<std::wstring>(::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<std::wstring>(::curl_easy_strerror(rc)))); + throw SysError(formatSystemError(L"curl_easy_getinfo(CURLINFO_ACTIVESOCKET)", formatCurlStatusCode(rc), utfTo<std::wstring>(::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<std::wstring>(rawLine) + L")" + (haveGroup ? L"" : L" [no-group]") + L" " + e.toString()); + throw SysError(L"Failed to parse FTP response. (" + utfTo<std::wstring>(rawLine) + L")" + (haveGroup ? L"" : L" [no-group]") + L' ' + e.toString()); } } @@ -1769,7 +1769,7 @@ private: if (modTime_) try { - const std::string isoTime = formatTime<std::string>("%Y%m%d%H%M%S", getUtcTime(*modTime_)); //returns empty string on failure + const std::string isoTime = utfTo<std::string>(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<std::wstring>(*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<Zstring>(login.port); + port = Zstr(':') + numberTo<Zstring>(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<std::string>("%Y-%m-%dT%H:%M:%S.000Z", getUtcTime(newModTime)); //returns empty string on failure + const std::string modTimeRfc = utfTo<std::string>(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<std::wstring>(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<std::string>("%Y-%m-%dT%H:%M:%S.000Z", getUtcTime(*modTime)); //returns empty string on failure + const std::string& modTimeRfc = utfTo<std::string>(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<std::wstring>(*modTime) + L")"); @@ -1345,7 +1345,7 @@ class GoogleAccessBuffer //per-user-session! => serialize access (perf: amortize public: GoogleAccessBuffer(const GoogleAccessInfo& accessInfo) : accessInfo_(accessInfo) {} - GoogleAccessBuffer(MemoryStreamIn<ByteArray>& stream) //throw UnexpectedEndOfStreamError + GoogleAccessBuffer(MemoryStreamIn<std::string>& stream) //throw UnexpectedEndOfStreamError { accessInfo_.accessToken.validUntil = readNumber<int64_t>(stream); // accessInfo_.accessToken.value = readContainer<std::string>(stream); // @@ -1354,7 +1354,7 @@ public: accessInfo_.userInfo.email = utfTo< Zstring>(readContainer<std::string>(stream)); // } - void serialize(MemoryStreamOut<ByteArray>& stream) const + void serialize(MemoryStreamOut<std::string>& stream) const { writeNumber<int64_t>(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<std::string>(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__)); accessInfo_ = accessInfo; } @@ -1402,7 +1402,7 @@ public: rootId_ (getRootItemId (accessBuf.getAccessToken())), //throw SysError accessBuf_(accessBuf) {} // - GoogleFileState(MemoryStreamIn<ByteArray>& stream, GoogleAccessBuffer& accessBuf, int dbVersion) : accessBuf_(accessBuf) //throw UnexpectedEndOfStreamError + GoogleFileState(MemoryStreamIn<std::string>& stream, GoogleAccessBuffer& accessBuf, int dbVersion) : accessBuf_(accessBuf) //throw UnexpectedEndOfStreamError { lastSyncToken_ = readContainer<std::string>(stream); //UnexpectedEndOfStreamError rootId_ = readContainer<std::string>(stream); // @@ -1441,14 +1441,14 @@ public: } } - void serialize(MemoryStreamOut<ByteArray>& stream) const + void serialize(MemoryStreamOut<std::string>& 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<std::string>(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__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<std::string>(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__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<std::string>(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__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<std::string>(__LINE__)); //WTF!? + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__)); //WTF!? std::vector<std::string> parentIdsNew = details->parentIds; std::vector<std::string> parentIdsRemoved = it->second.parentIds; @@ -1992,7 +1992,7 @@ private: static void saveSession(const Zstring& dbFilePath, const UserSession& userSession) //throw FileError { - MemoryStreamOut<ByteArray> streamOut; + MemoryStreamOut<std::string> streamOut; writeArray(streamOut, DB_FILE_DESCR, sizeof(DB_FILE_DESCR)); writeNumber<int32_t>(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<ByteArray>(dbFilePath, nullptr /*notifyUnbufferedIO*/); //throw FileError - ByteArray rawStream; + const std::string zstream = loadBinContainer<std::string>(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<GoogleFileState >(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<AFS::FileId>(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<AFS::FileId>(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<AFS::FileId>(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<FsItemRaw> 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<FsItemRaw> getDirContentFlat(const Zstring& dirPath) //throw FileErr Windows (NTFS, FAT) all <input> 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<const NativeFileSystem&>(pathTo.afsDevice.ref()).getNativePath(pathTo.afsPath); @@ -639,7 +638,7 @@ void RecycleSessionNative::recycleItemIfExists(const AbstractPath& itemPath, con std::optional<Zstring> itemPathNative = AFS::getNativeItemPath(itemPath); if (!itemPathNative) - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__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<std::wstring>(authList)); + L'\n' +_("Required:") + L' ' + utfTo<std::wstring>(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<std::wstring>(authList)); + L'\n' +_("Required:") + L' ' + utfTo<std::wstring>(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<std::wstring>(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<std::wstring>(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<std::wstring>(sshSession.sftpChannels_.size() + 1)); + return msg + L' ' + replaceCpy(_("Failed to open SFTP channel number %x."), L"%x", numberTo<std::wstring>(sshSession.sftpChannels_.size() + 1)); }; std::optional<SysError> 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<std::string>(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__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<std::string>(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__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<char> 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<Zstring>(login.port); + port = Zstr(':') + numberTo<Zstring>(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<std::wstring>(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/base/application.cpp b/FreeFileSync/Source/application.cpp index 657eac13..65755b0e 100644 --- a/FreeFileSync/Source/base/application.cpp +++ b/FreeFileSync/Source/application.cpp @@ -15,17 +15,18 @@ #include <wx+/popup_dlg.h> #include <wx+/image_resources.h> #include <wx/msgdlg.h> -#include "comparison.h" +#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 "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 <gtk/gtk.h> @@ -84,14 +85,15 @@ bool Application::OnInit() (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<std::wstring>(error->code)), utfTo<std::wstring>(error->message))); + throw SysError(formatSystemError(L"gtk_css_provider_load_from_data", replaceCpy(_("Error Code %x"), L"%x", + numberTo<std::wstring>(error->code)), + utfTo<std::wstring>(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<std::string>(e.toString()) << "\n"; } + catch (const SysError& e) { std::cerr << utfTo<std::string>(e.toString()) << '\n'; } #else #error unknown GTK version! #endif @@ -160,7 +162,7 @@ int Application::OnRun() 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<std::wstring>(wxTheApp->GetAppDisplayName()) + SPACED_DASH + _("An exception occurred"); - std::cerr << utfTo<std::string>(titleFmt + SPACED_DASH) << e.what() << "\n"; + std::cerr << utfTo<std::string>(titleFmt + SPACED_DASH) << e.what() << '\n'; return FFS_RC_EXCEPTION; } //catch (...) -> let it crash and create mini dump!!! @@ -197,7 +199,7 @@ void Application::launch(const std::vector<Zstring>& commandArgs) //error handling strategy unknown and no sync log output available at this point! auto titleFmt = copyStringTo<std::wstring>(wxTheApp->GetAppDisplayName()) + SPACED_DASH + title; - std::cerr << utfTo<std::string>(titleFmt + SPACED_DASH + msg) << "\n"; + std::cerr << utfTo<std::string>(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 @@ -210,9 +212,9 @@ void Application::launch(const std::vector<Zstring>& commandArgs) 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 + 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) { @@ -220,8 +222,8 @@ void Application::launch(const std::vector<Zstring>& commandArgs) 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")) || + return equalAsciiNoCase(argTmp, "help") || + equalAsciiNoCase(argTmp, "h") || argTmp == Zstr("?"); }; @@ -473,23 +475,23 @@ 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"./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" + + _("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" + + 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" + + L"-Edit" + "\n" + _("Open the selected configuration for editing only, without executing it.") + L"\n\n" + - _("global config file:") + L"\n" + + _("global config file:") + L'\n' + _("Path to an alternate GlobalSettings.xml file."))); } @@ -596,7 +598,7 @@ void runBatchMode(const Zstring& globalConfigFilePath, const XmlBatchConfig& bat 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.altLogFolderPathPhrase, globalCfg.logfilesMaxAgeDays, globalCfg.logFormat, logFilePathsToKeep, batchCfg.mainCfg.emailNotifyAddress, batchCfg.mainCfg.emailNotifyCondition); //noexcept //---------------------------------------------------------------------- diff --git a/FreeFileSync/Source/base/application.h b/FreeFileSync/Source/application.h index a52e6617..a52e6617 100644 --- a/FreeFileSync/Source/base/application.h +++ b/FreeFileSync/Source/application.h 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<Zstringc>(_("Both sides have changed since last synchronization.")); + const Zstringc txtNoSideChanged_ = utfTo<Zstringc>(_("Cannot determine sync-direction:") + L" \n" + _("No change since last synchronization.")); + const Zstringc txtDbNotInSync_ = utfTo<Zstringc>(_("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<std::pair<BaseFolderPair*, DirectionConfig>> fff::extractDirectionCf mainCfg.additionalPairs.end()); if (folderCmp.size() != allPairs.size()) - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__)); std::vector<std::pair<BaseFolderPair*, DirectionConfig>> output; @@ -779,7 +779,7 @@ void fff::redetermineSyncDirection(const std::vector<std::pair<BaseFolderPair*, { std::wstring msg = _("Setting default synchronization directions: Old files will be overwritten with newer files."); if (directCfgs.size() > 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<RIGHT_SIDE>()); 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<std::string>(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__)); //merge first and additional pairs std::vector<LocalPairConfig> allPairs; @@ -1610,11 +1610,11 @@ void fff::deleteFromGridAndHD(const std::vector<FileSystemObject*>& 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 <functional> -#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/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<std::string>(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__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<FolderPairCfg>& 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<FolderPairCfg>& 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<FolderPairCfg>& 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<DirectoryKey>& 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<DirectoryKey>& foldersToRead, const int64_t totalTimeSec = std::chrono::duration_cast<std::chrono::seconds>(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<std::wstring>(wxTimeSpan::Seconds(totalTimeSec).Format())); //throw X + _("Time elapsed:") + L' ' + copyStringTo<std::wstring>(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 <SelectedSide side, class FileOrLinkPair> inline -Zstringw getConflictInvalidDate(const FileOrLinkPair& file) +Zstringc getConflictInvalidDate(const FileOrLinkPair& file) { - return copyStringTo<Zstringw>(replaceCpy(_("File %x has an invalid date."), L"%x", fmtPath(AFS::getDisplayPath(file.template getAbstractPath<side>()))) + L"\n" + - _("Date:") + L" " + formatUtcToLocalTime(file.template getLastWriteTime<side>())); + return utfTo<Zstringc>(replaceCpy(_("File %x has an invalid date."), L"%x", fmtPath(AFS::getDisplayPath(file.template getAbstractPath<side>()))) + L'\n' + + _("Date:") + L' ' + formatUtcToLocalTime(file.template getLastWriteTime<side>())); } -Zstringw getConflictSameDateDiffSize(const FilePair& file) +Zstringc getConflictSameDateDiffSize(const FilePair& file) { - return copyStringTo<Zstringw>(_("Files have the same date but a different size.") + L"\n" + - arrowLeft + L" " + _("Date:") + L" " + formatUtcToLocalTime(file.getLastWriteTime< LEFT_SIDE>()) + L" " + _("Size:") + L" " + formatNumber(file.getFileSize<LEFT_SIDE>()) + L"\n" + - arrowRight + L" " + _("Date:") + L" " + formatUtcToLocalTime(file.getLastWriteTime<RIGHT_SIDE>()) + L" " + _("Size:") + L" " + formatNumber(file.getFileSize<RIGHT_SIDE>())); + return utfTo<Zstringc>(_("Files have the same date but a different size.") + L'\n' + + arrowLeft + L' ' + _("Date:") + L' ' + formatUtcToLocalTime(file.getLastWriteTime< LEFT_SIDE>()) + L" " + _("Size:") + L' ' + formatNumber(file.getFileSize<LEFT_SIDE>()) + L'\n' + + arrowRight + L' ' + _("Date:") + L' ' + formatUtcToLocalTime(file.getLastWriteTime<RIGHT_SIDE>()) + L" " + _("Size:") + L' ' + formatNumber(file.getFileSize<RIGHT_SIDE>())); } -Zstringw getConflictSkippedBinaryComparison() +Zstringc getConflictSkippedBinaryComparison() { - return copyStringTo<Zstringw>(_("Content comparison was skipped for excluded files.")); + return utfTo<Zstringc>(_("Content comparison was skipped for excluded files.")); } -Zstringw getDescrDiffMetaShortnameCase(const FileSystemObject& fsObj) +Zstringc getDescrDiffMetaShortnameCase(const FileSystemObject& fsObj) { - return copyStringTo<Zstringw>(_("Items differ in attributes only") + L"\n" + - arrowLeft + L" " + fmtPath(fsObj.getItemName< LEFT_SIDE>()) + L"\n" + - arrowRight + L" " + fmtPath(fsObj.getItemName<RIGHT_SIDE>())); + return utfTo<Zstringc>(_("Items differ in attributes only") + L'\n' + + arrowLeft + L' ' + fmtPath(fsObj.getItemName< LEFT_SIDE>()) + L'\n' + + arrowRight + L' ' + fmtPath(fsObj.getItemName<RIGHT_SIDE>())); } #if 0 template <class FileOrLinkPair> -Zstringw getDescrDiffMetaData(const FileOrLinkPair& file) +Zstringc getDescrDiffMetaData(const FileOrLinkPair& file) { - return copyStringTo<Zstringw>(_("Items differ in attributes only") + L"\n" + - arrowLeft + L" " + _("Date:") + L" " + formatUtcToLocalTime(file.template getLastWriteTime< LEFT_SIDE>()) + L"\n" + - arrowRight + L" " + _("Date:") + L" " + formatUtcToLocalTime(file.template getLastWriteTime<RIGHT_SIDE>())); + return utfTo<Zstringc>(_("Items differ in attributes only") + L'\n' + + arrowLeft + L' ' + _("Date:") + L' ' + formatUtcToLocalTime(file.template getLastWriteTime< LEFT_SIDE>()) + L'\n' + + arrowRight + L' ' + _("Date:") + L' ' + formatUtcToLocalTime(file.template getLastWriteTime<RIGHT_SIDE>())); } #endif -Zstringw getConflictAmbiguousItemName(const Zstring& itemName) +Zstringc getConflictAmbiguousItemName(const Zstring& itemName) { - return copyStringTo<Zstringw>(replaceCpy(_("The name %x is used by more than one item in the folder."), L"%x", fmtPath(itemName))); + return utfTo<Zstringc>(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<Zstringw>(errMsg)); + symlink.setCategoryConflict(utfTo<Zstringc>(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<Zstringw>(errMsg)); + file.setCategoryConflict(utfTo<Zstringc>(errMsg)); else { if (haveSameContent) @@ -542,7 +542,7 @@ std::list<std::shared_ptr<BaseFolderPair>> ComparisonBuffer::compareByContent(co //PERF_START; std::list<std::shared_ptr<BaseFolderPair>> 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<std::shared_ptr<BaseFolderPair>> ComparisonBuffer::compareByContent(co class MergeSides { public: - MergeSides(const std::map<ZstringNoCase, Zstringw>& errorsByRelPath, + MergeSides(const std::map<ZstringNoCase, Zstringc>& errorsByRelPath, std::vector<FilePair*>& undefinedFilesOut, std::vector<SymlinkPair*>& 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 <SelectedSide side> - 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<ZstringNoCase, Zstringw>& errorsByRelPath_; //base-relative paths or empty if read-error for whole base directory + const std::map<ZstringNoCase, Zstringc>& errorsByRelPath_; //base-relative paths or empty if read-error for whole base directory std::vector<FilePair*>& undefinedFiles_; std::vector<SymlinkPair*>& 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<const Zstringw&, decltype(*errorMsg)>); + fsObj.setCategoryConflict(*errorMsg); //peak memory: Zstringc is ref-counted, unlike std::string! + static_assert(std::is_same_v<const Zstringc&, decltype(*errorMsg)>); } return errorMsg; } template <SelectedSide side> -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<side>(folderName, attrAndSub.first); - const Zstringw* errorMsgNew = checkFailedRead(newFolder, errorMsg); + const Zstringc* errorMsgNew = checkFailedRead(newFolder, errorMsg); fillOneSide<side>(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<RIGHT_SIDE>(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<RIGHT_SIDE>(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<LEFT_SIDE>(dirLeft.first, dirLeft.second.first); - const Zstringw* errorMsgNew = checkFailedRead(newFolder, conflictMsg ? conflictMsg : errorMsg); + const Zstringc* errorMsgNew = checkFailedRead(newFolder, conflictMsg ? conflictMsg : errorMsg); this->fillOneSide<LEFT_SIDE>(dirLeft.second.second, errorMsgNew, newFolder); //recurse }, - [&](const FolderData& dirRight, const Zstringw* conflictMsg) + [&](const FolderData& dirRight, const Zstringc* conflictMsg) { FolderPair& newFolder = output.addSubFolder<RIGHT_SIDE>(dirRight.first, dirRight.second.first); - const Zstringw* errorMsgNew = checkFailedRead(newFolder, conflictMsg ? conflictMsg : errorMsg); + const Zstringc* errorMsgNew = checkFailedRead(newFolder, conflictMsg ? conflictMsg : errorMsg); this->fillOneSide<RIGHT_SIDE>(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<BaseFolderPair> ComparisonBuffer::performComparison(const Resolv const DirectoryValue* bufValueLeft = getDirValue(fp.folderPathLeft); const DirectoryValue* bufValueRight = getDirValue(fp.folderPathRight); - std::map<ZstringNoCase, Zstringw> failedReads; //base-relative paths or empty if read-error for whole base directory + std::map<ZstringNoCase, Zstringc> failedReads; //base-relative paths or empty if read-error for whole base directory { - auto append = [&](const std::map<Zstring, std::wstring>& c) + auto append = [&](const std::map<Zstring, Zstringc>& c) { for (const auto& [relPath, errorMsg] : c) - failedReads.emplace(relPath, copyStringTo<Zstringw>(errorMsg)); + failedReads.emplace(relPath, errorMsg); }; //mix failedFolderReads with failedItemReads: @@ -948,7 +948,7 @@ std::shared_ptr<BaseFolderPair> 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<BaseFolderPair> 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<std::wstring>(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<std::string>(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__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<std::wstring>(FILE_NAME_SEPARATOR + pd->relPath + FILE_NAME_SEPARATOR); + msg += L'\n' + _("Exclude:") + L' ' + utfTo<std::wstring>(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<std::wstring>(e.what())); + callback.reportFatalError(_("Out of memory.") + L' ' + utfTo<std::wstring>(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<FolderPairCfg> 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/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<std::string>(memStreamOut, sessionID); - writeNumber <int8_t >(memStreamOut, sessionData.isLeadStream); - writeContainer<ByteArray>(memStreamOut, sessionData.rawStream); + writeNumber<int8_t>(memStreamOut, sessionData.isLeadStream); + writeContainer (memStreamOut, sessionData.rawStream); } writeNumber<uint32_t>(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<std::string> crcStreamOut; - writeNumber<uint32_t>(crcStreamOut, getCrc32({ byteStream.begin(), byteStream.end() - sizeof(uint32_t) })); + writeNumber<uint32_t>(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<ByteArray>(memStreamIn); //throw UnexpectedEndOfStreamError + sessionData.rawStream = readContainer<std::string>(memStreamIn); //throw UnexpectedEndOfStreamError MemoryStreamIn streamIn(sessionData.rawStream); const int streamVersion = readNumber<int32_t>(streamIn); //throw UnexpectedEndOfStreamError @@ -167,8 +167,8 @@ DbStreams loadStreams(const AbstractPath& dbPath, const IOCallback& notifyUnbuff } else { - sessionData.isLeadStream = readNumber <int8_t >(memStreamIn) != 0; //throw UnexpectedEndOfStreamError - sessionData.rawStream = readContainer<ByteArray>(memStreamIn); // + sessionData.isLeadStream = readNumber <int8_t >(memStreamIn) != 0; //throw UnexpectedEndOfStreamError + sessionData.rawStream = readContainer<std::string>(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<ByteArray> outL; - MemoryStreamOut<ByteArray> outR; + MemoryStreamOut<std::string> outL; + MemoryStreamOut<std::string> outR; //save format version writeNumber<int32_t>(outL, DB_STREAM_VERSION); writeNumber<int32_t>(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<ByteArray> streamOut; + MemoryStreamOut<std::string> 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<uint64_t>(outL, size1stPart); writeNumber<uint64_t>(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<ByteArray>& streamOut, const Zstring& str) { writeContainer(streamOut, utfTo<Zbase<char>>(str)); } + static void writeUtf8(MemoryStreamOut<std::string>& streamOut, const Zstring& str) { writeContainer(streamOut, utfTo<std::string>(str)); } - static void writeFileDescr(MemoryStreamOut<ByteArray>& streamOut, const InSyncDescrFile& descr) + static void writeFileDescr(MemoryStreamOut<std::string>& streamOut, const InSyncDescrFile& descr) { writeNumber<int64_t>(streamOut, descr.modTime); writeContainer(streamOut, descr.fileId); - static_assert(std::is_same_v<decltype(descr.fileId), Zbase<char>>); + static_assert(std::is_same_v<decltype(descr.fileId), std::string>); } - static void writeLinkDescr(MemoryStreamOut<ByteArray>& streamOut, const InSyncDescrLink& descr) + static void writeLinkDescr(MemoryStreamOut<std::string>& streamOut, const InSyncDescrLink& descr) { writeNumber<int64_t>(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<ByteArray> streamOutText_; // - MemoryStreamOut<ByteArray> streamOutSmallNum_; //data with bias to lead side (= always left in this context) - MemoryStreamOut<ByteArray> streamOutBigNum_; // + MemoryStreamOut<std::string> streamOutText_; // + MemoryStreamOut<std::string> streamOutSmallNum_; //data with bias to lead side (= always left in this context) + MemoryStreamOut<std::string> streamOutBigNum_; // }; @@ -312,12 +312,12 @@ class StreamParser { public: static SharedRef<InSyncFolder> 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<int32_t>(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<int8_t>(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<ByteArray>& in1stPart = leadStreamLeft ? streamInL : streamInR; - MemoryStreamIn<ByteArray>& in2ndPart = leadStreamLeft ? streamInR : streamInL; + MemoryStreamIn<std::string>& in1stPart = leadStreamLeft ? streamInL : streamInR; + MemoryStreamIn<std::string>& in2ndPart = leadStreamLeft ? streamInR : streamInL; const size_t size1stPart = static_cast<size_t>(readNumber<uint64_t>(in1stPart)); const size_t size2ndPart = static_cast<size_t>(readNumber<uint64_t>(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<ByteArray>(streamInL); - const ByteArray tmpR = readContainer<ByteArray>(streamInR); + const std::string tmpL = readContainer<std::string>(streamInL); + const std::string tmpR = readContainer<std::string>(streamInR); auto output = makeSharedRef<InSyncFolder>(InSyncFolder::DIR_STATUS_IN_SYNC); StreamParserV2 parser(decompStream(tmpL), @@ -379,22 +378,20 @@ public: } else { - MemoryStreamIn<ByteArray>& streamInPart1 = leadStreamLeft ? streamInL : streamInR; - MemoryStreamIn<ByteArray>& streamInPart2 = leadStreamLeft ? streamInR : streamInL; + MemoryStreamIn<std::string>& streamInPart1 = leadStreamLeft ? streamInL : streamInR; + MemoryStreamIn<std::string>& streamInPart2 = leadStreamLeft ? streamInR : streamInL; const size_t sizePart1 = static_cast<size_t>(readNumber<uint64_t>(streamInPart1)); const size_t sizePart2 = static_cast<size_t>(readNumber<uint64_t>(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<ByteArray>(streamIn); // - const ByteArray bufSmallNum = readContainer<ByteArray>(streamIn); //throw UnexpectedEndOfStreamError - const ByteArray bufBigNum = readContainer<ByteArray>(streamIn); // + const std::string bufText = readContainer<std::string>(streamIn); // + const std::string bufSmallNum = readContainer<std::string>(streamIn); //throw UnexpectedEndOfStreamError + const std::string bufBigNum = readContainer<std::string>(streamIn); // auto output = makeSharedRef<InSyncFolder>(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<ByteArray>& streamIn) { return utfTo<Zstring>(readContainer<Zbase<char>>(streamIn)); } //throw UnexpectedEndOfStreamError + static Zstring readUtf8(MemoryStreamIn<std::string>& streamIn) { return utfTo<Zstring>(readContainer<std::string>(streamIn)); } //throw UnexpectedEndOfStreamError //optional: use null-termination: 5% overall size reduction //optional: split into streamInText_/streamInSmallNum_: overall size increase! (why?) - static InSyncDescrFile readFileDescr(MemoryStreamIn<ByteArray>& streamIn) //throw UnexpectedEndOfStreamError + static InSyncDescrFile readFileDescr(MemoryStreamIn<std::string>& streamIn) //throw UnexpectedEndOfStreamError { //attention: order of function argument evaluation is undefined! So do it one after the other... const auto modTime = readNumber<int64_t>(streamIn); //throw UnexpectedEndOfStreamError - const AFS::FileId fileId = readContainer<Zbase<char>>(streamIn); + const auto fileId = readContainer<AFS::FileId>(streamIn); return InSyncDescrFile(modTime, fileId); } - static InSyncDescrLink readLinkDescr(MemoryStreamIn<ByteArray>& streamIn) //throw UnexpectedEndOfStreamError + static InSyncDescrLink readLinkDescr(MemoryStreamIn<std::string>& streamIn) //throw UnexpectedEndOfStreamError { const auto modTime = readNumber<int64_t>(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<ByteArray> inputLeft_; //data related to one side only - MemoryStreamIn<ByteArray> inputRight_; // - MemoryStreamIn<ByteArray> inputBoth_; //data concerning both sides + MemoryStreamIn<std::string> inputLeft_; //data related to one side only + MemoryStreamIn<std::string> inputRight_; // + MemoryStreamIn<std::string> inputBoth_; //data concerning both sides }; const int streamVersion_; - MemoryStreamIn<ByteArray> streamInText_; // - MemoryStreamIn<ByteArray> streamInSmallNum_; //data with bias to lead side - MemoryStreamIn<ByteArray> streamInBigNum_; // + MemoryStreamIn<std::string> streamInText_; // + MemoryStreamIn<std::string> streamInSmallNum_; //data with bias to lead side + MemoryStreamIn<std::string> streamInBigNum_; // }; //####################################################################################################################################### @@ -757,7 +754,7 @@ std::pair<DbStreams::const_iterator, if (itL->second.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<AbstractPath>& 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<char> 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<uint64_t>(streamOut, lockInfo.processId); writeNumber<uint32_t>(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<std::string> crcStreamOut; - writeNumber<uint32_t>(crcStreamOut, getCrc32({ byteStreamTrm.begin(), byteStreamTrm.end() - sizeof(uint32_t) })); + writeNumber<uint32_t>(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/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<std::wstring>(fsObj. getItemNameAny()) + L"]"; + const std::wstring footer = L"\n[" + utfTo<std::wstring>(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<RIGHT_SIDE>()); + arrowLeft + L' ' + formatUtcToLocalTime(file.getLastWriteTime< LEFT_SIDE>()) + L'\n' + + arrowRight + L' ' + formatUtcToLocalTime(file.getLastWriteTime<RIGHT_SIDE>()); }, [&](const SymlinkPair& symlink) { descr += std::wstring(L"\n") + - arrowLeft + L" " + formatUtcToLocalTime(symlink.getLastWriteTime< LEFT_SIDE>()) + L"\n" + - arrowRight + L" " + formatUtcToLocalTime(symlink.getLastWriteTime<RIGHT_SIDE>()); + arrowLeft + L' ' + formatUtcToLocalTime(symlink.getLastWriteTime< LEFT_SIDE>()) + L'\n' + + arrowRight + L' ' + formatUtcToLocalTime(symlink.getLastWriteTime<RIGHT_SIDE>()); }); return descr + footer; } case FILE_DIFFERENT_METADATA: case FILE_CONFLICT: - return fsObj.getCatExtraDescription() + footer; + return utfTo<std::wstring>(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<std::wstring>(fsObj. getItemNameAny()) + L"]"; + const std::wstring footer = L"\n[" + utfTo<std::wstring>(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 <CompareFileResult res> 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<std::wstring>(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<Zstringw>(description); + syncDirectionConflict_ = description; notifySyncCfgChanged(); } @@ -755,7 +756,7 @@ inline std::wstring FileSystemObject::getSyncOpConflict() const { assert(getSyncOperation() == SO_UNRESOLVED_CONFLICT); - return zen::copyStringTo<std::wstring>(syncDirectionConflict_); + return zen::utfTo<std::wstring>(syncDirectionConflict_); } @@ -855,7 +856,7 @@ template <> void FileSystemObject::setCategory<FILE_LEFT_SIDE_ONLY> () = dele template <> void FileSystemObject::setCategory<FILE_RIGHT_SIDE_ONLY> () = 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/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 <zen/zstring.h> #include <zen/stl_tools.h> #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/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<Zstring, std::wstring>& failedDirReads; - std::map<Zstring, std::wstring>& failedItemReads; + std::map<Zstring, Zstringc>& failedDirReads; + std::map<Zstring, Zstringc>& 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<Zstringc>(msg)); else - cfg_.failedItemReads.emplace(parentRelPathPf_ + itemName, msg); + cfg_.failedItemReads.emplace(parentRelPathPf_ + itemName, utfTo<Zstringc>(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<Zstring, std::wstring> failedFolderReads; //with corresponding error message + std::map<Zstring, Zstringc> failedFolderReads; //with corresponding error message //relative paths (never empty) for failure to read single file/dir/symlink with corresponding error message - std::map<Zstring, std::wstring> failedItemReads; + std::map<Zstring, Zstringc> failedItemReads; }; 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<NameFilter>(includePhrase2, excludePhrase + Zstr("\n") + excludePhrase2); + auto filterTmp = zen::makeSharedRef<NameFilter>(includePhrase2, excludePhrase + Zstr('\n') + excludePhrase2); if (filterTmp.ref().isNull()) return zen::makeSharedRef<NullFilter>(); @@ -224,9 +224,9 @@ FilterRef constructFilter(const Zstring& includePhrase, else { if (NameFilter::isNull(includePhrase2, Zstring())) - return zen::makeSharedRef<NameFilter>(includePhrase, excludePhrase + Zstr("\n") + excludePhrase2); + return zen::makeSharedRef<NameFilter>(includePhrase, excludePhrase + Zstr('\n') + excludePhrase2); else - return zen::makeSharedRef<CombinedFilter>(NameFilter(includePhrase, excludePhrase + Zstr("\n") + excludePhrase2), NameFilter(includePhrase2, Zstring())); + return zen::makeSharedRef<CombinedFilter>(NameFilter(includePhrase, excludePhrase + Zstr('\n') + excludePhrase2), NameFilter(includePhrase2, Zstring())); } } 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<Zstring> 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<Zstring>(Zstr("%H%M%S")); + if (equalAsciiNoCase(macro, "time")) + return formatTime(Zstr("%H%M%S")); - if (equalAsciiNoCase(macro, Zstr("date"))) - return formatTime<Zstring>(FORMAT_ISO_DATE); + if (equalAsciiNoCase(macro, "date")) + return formatTime(formatIsoDateTag); - if (equalAsciiNoCase(macro, Zstr("timestamp"))) - return formatTime<Zstring>(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<Zstring> tryResolveMacro(const Zstring& macro) //macro without %-c if (!equalAsciiNoCase(macro, phrase)) return false; - timeStr = formatTime<Zstring>(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/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<size_t>(-2); }(); - return totalThreadCount_ > 1 ? L"[" + zen::numberTo<std::wstring>(taskIdx + 1) + L"] " : L""; + return totalThreadCount_ > 1 ? L'[' + zen::numberTo<std::wstring>(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<std::pair<AbstractPath, ParallelWorkI auto& threadGroup = deviceThreadGroups.emplace(afsDevice, ThreadGroup<std::function<void()>>( 1, - threadGroupName + " " + utfTo<std::string>(AFS::getDisplayPath(AbstractPath(afsDevice, AfsPath()))))).first->second; + threadGroupName + ' ' + utfTo<std::string>(AFS::getDisplayPath(AbstractPath(afsDevice, AfsPath()))))).first->second; for (const std::pair<AbstractPath, ParallelWorkItem>* 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 <ctime> #include <zen/i18n.h> #include <zen/time.h> -#include "path_filter.h" +//#include "path_filter.h" #include "../afs/concrete.h" using namespace zen; using namespace fff; -std::vector<unsigned int> 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<unsigned int> minutes; - for (const std::wstring& part : split(tmp, L',', SplitType::SKIP_EMPTY)) - { - if (contains(part, L':')) - minutes.insert(stringTo<unsigned int>(beforeFirst(part, L':', IF_MISSING_RETURN_NONE)) * 60 + - stringTo<unsigned int>(afterFirst (part, L':', IF_MISSING_RETURN_NONE))); - else - minutes.insert(stringTo<unsigned int>(part) * 60); - } - minutes.erase(0); - - return { minutes.begin(), minutes.end() }; -} - - -std::wstring fff::toTimeShiftPhrase(const std::vector<unsigned int>& ignoreTimeShiftMinutes) -{ - std::wstring phrase; - for (auto it = ignoreTimeShiftMinutes.begin(); it != ignoreTimeShiftMinutes.end(); ++it) - { - if (it != ignoreTimeShiftMinutes.begin()) - phrase += L", "; - - phrase += numberTo<std::wstring>(*it / 60); - if (*it % 60 != 0) - phrase += L':' + printNumber<std::wstring>(L"%02d", static_cast<int>(*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<std::string>(__LINE__)); + throw std::logic_error("there are no predefined directions for automatic mode! " + std::string(__FILE__) + ':' + numberTo<std::string>(__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<AfsDevice, size_t>& 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<uint64_t>::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<MainConfiguration>& 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<LocalPairConfig> mergedCfgs; - for (const MainConfiguration& mainCfg : mainCfgs) - { - std::vector<LocalPairConfig> 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<std::pair<CompConfig, int>> cmpCfgStat; - std::vector<std::pair<SyncConfig, int>> 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<CompConfig, int>& 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<SyncConfig, int>& 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<CompConfig, int>& lhs, const std::pair<CompConfig, int>& rhs) { return lhs.second < rhs.second; })->first; - - const SyncConfig syncCfgHead = syncCfgStat.empty() ? SyncConfig() : - std::max_element(syncCfgStat.begin(), syncCfgStat.end(), - [](const std::pair<SyncConfig, int>& lhs, const std::pair<SyncConfig, int>& 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<AfsDevice, size_t> 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<unsigned int> fromTimeShiftPhrase(const std::wstring& timeShiftPhrase); -std::wstring toTimeShiftPhrase (const std::vector<unsigned int>& 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<AfsDevice, size_t>& deviceParallelOps, const AfsDevice& afsDevice); void setDeviceParallelOps( std::map<AfsDevice, size_t>& 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<MainConfiguration>& 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<sideSrc>()))), - 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<sideSrc>()))), + 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<sideSrc>()))), - 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<sideSrc>()))), + 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<std::string>(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__)); //aggregate basic information std::vector<SyncStatistics> folderPairStats; @@ -2189,7 +2195,7 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime std::vector<FolderPairJobType> jobType(folderCmp.size(), FolderPairJobType::PROCESS); //folder pairs may be skipped after fatal errors were found - std::map<const BaseFolderPair*, std::vector<SyncStatistics::ConflictInfo>> checkUnresolvedConflicts; + std::map<const BaseFolderPair*, std::pair<int, std::vector<SyncStatistics::ConflictInfo>>> checkUnresolvedConflicts; std::vector<std::tuple<AbstractPath, const PathFilter*, bool /*write access*/>> 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<RIGHT_SIDE>()); - for (const SyncStatistics::ConflictInfo& item : conflicts) //show *all* conflicts in warning message - msg += L"\n" + utfTo<std::wstring>(item.relPath) + L": " + item.msg; + for (const SyncStatistics::ConflictInfo& item : conflictPreview) + msg += L'\n' + utfTo<std::wstring>(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<PathDependency> 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<std::wstring>(FILE_NAME_SEPARATOR + pd->relPath + FILE_NAME_SEPARATOR); + line += L'\n' + _("Exclude:") + L" \t" + utfTo<std::wstring>(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<RIGHT_SIDE>())); //------------------------------------------------------------------------------------------ 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 <chrono> -#include "config.h" +//#include "config.h" +#include "structures.h" #include "file_hierarchy.h" #include "process_callback.h" @@ -38,8 +39,6 @@ public: template <SelectedSide side> bool expectPhysicalDeletion() const { return SelectParam<side>::ref(physicalDeleteLeft_, physicalDeleteRight_); } - int conflictCount() const { return static_cast<int>(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<ConflictInfo>& getConflicts() const { return conflictMsgs_; } + const std::vector<ConflictInfo>& 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<ConflictInfo> conflictMsgs_; //conflict texts to display as a warning message + int64_t bytesToProcess_ = 0; size_t rowsTotal_ = 0; + + int conflictCount_ = 0; + std::vector<ConflictInfo> 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<VersioningLimitFolder>& 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<VersioningLimitFolder>& 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<VersioningLimitFolder>& folderLimi //--------- remove excess file versions --------- Protected<std::map<AbstractPath, size_t>&> 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<void(const AbstractPath& folderPath, AsyncCallback& acb)> 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<Zstring>(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<std::string>(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__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<std::wstring>(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 <wx/app.h> +#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<unsigned int> 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<unsigned int> minutes; + for (const std::wstring& part : split(tmp, L',', SplitType::SKIP_EMPTY)) + { + if (contains(part, L':')) + minutes.insert(stringTo<unsigned int>(beforeFirst(part, L':', IF_MISSING_RETURN_NONE)) * 60 + + stringTo<unsigned int>(afterFirst (part, L':', IF_MISSING_RETURN_NONE))); + else + minutes.insert(stringTo<unsigned int>(part) * 60); + } + minutes.erase(0); + + return { minutes.begin(), minutes.end() }; +} + + +std::wstring fff::toTimeShiftPhrase(const std::vector<unsigned int>& ignoreTimeShiftMinutes) +{ + std::wstring phrase; + for (auto it = ignoreTimeShiftMinutes.begin(); it != ignoreTimeShiftMinutes.end(); ++it) + { + if (it != ignoreTimeShiftMinutes.begin()) + phrase += L", "; + + phrase += numberTo<std::wstring>(*it / 60); + if (*it % 60 != 0) + phrase += L':' + printNumber<std::wstring>(L"%02d", static_cast<int>(*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<std::wstring>(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<MainConfiguration>& 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<LocalPairConfig> mergedCfgs; + for (const MainConfiguration& mainCfg : mainCfgs) + { + std::vector<LocalPairConfig> 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<std::pair<CompConfig, int>> cmpCfgStat; + std::vector<std::pair<SyncConfig, int>> 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<CompConfig, int>& 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<SyncConfig, int>& 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<CompConfig, int>& lhs, const std::pair<CompConfig, int>& rhs) { return lhs.second < rhs.second; })->first; + + const SyncConfig syncCfgHead = syncCfgStat.empty() ? SyncConfig() : + std::max_element(syncCfgStat.begin(), syncCfgStat.end(), + [](const std::pair<SyncConfig, int>& lhs, const std::pair<SyncConfig, int>& 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<AfsDevice, size_t> 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<unsigned int> fromTimeShiftPhrase(const std::wstring& timeShiftPhrase); +std::wstring toTimeShiftPhrase (const std::vector<unsigned int>& 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<MainConfiguration>& mainCfgs); +} + +#endif //STRUCTURE_TOOLS_H_7823097420397434 diff --git a/FreeFileSync/Source/base/config.cpp b/FreeFileSync/Source/config.cpp index 0a170bbb..120ad466 100644 --- a/FreeFileSync/Source/base/config.cpp +++ b/FreeFileSync/Source/config.cpp @@ -11,7 +11,7 @@ #include <zen/time.h> #include <wx/intl.h> #include "ffs_paths.h" -//#include "../afs/concrete.h" +#include "base_tools.h" using namespace zen; @@ -662,6 +662,35 @@ bool readText(const std::string& input, UnitTime& value) 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) { @@ -1092,8 +1121,8 @@ void readConfig(const XmlIn& in, SyncConfig& syncCfg, std::map<AfsDevice, size_t if (syncCfg.versioningStyle == VersioningStyle::replace) { - if (endsWithAsciiNoCase(syncCfg.versioningFolderPhrase, Zstr("/%timestamp%")) || - endsWithAsciiNoCase(syncCfg.versioningFolderPhrase, Zstr("\\%timestamp%"))) + if (endsWithAsciiNoCase(syncCfg.versioningFolderPhrase, "/%timestamp%") || + endsWithAsciiNoCase(syncCfg.versioningFolderPhrase, "\\%timestamp%")) { syncCfg.versioningFolderPhrase.resize(syncCfg.versioningFolderPhrase.size() - strLength(Zstr("/%timestamp%"))); syncCfg.versioningStyle = VersioningStyle::timestampFolder; @@ -1173,8 +1202,8 @@ void readConfig(const XmlIn& in, LocalPairConfig& lpc, std::map<AfsDevice, size_ { auto getParallelOps = [&](const Zstring& folderPathPhrase, size_t& parallelOps) { - if (startsWithAsciiNoCase(folderPathPhrase, Zstr("sftp:")) || - startsWithAsciiNoCase(folderPathPhrase, Zstr( "ftp:"))) + if (startsWithAsciiNoCase(folderPathPhrase, "sftp:") || + startsWithAsciiNoCase(folderPathPhrase, "ftp:")) { for (const Zstring& optPhrase : split(folderPathPhrase, Zstr("|"), SplitType::SKIP_EMPTY)) if (startsWith(optPhrase, Zstr("con="))) @@ -1481,6 +1510,7 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer) 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); @@ -2205,6 +2235,7 @@ void writeConfig(const XmlGlobalSettings& cfg, XmlOut& out) 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); diff --git a/FreeFileSync/Source/base/config.h b/FreeFileSync/Source/config.h index dcafb207..e76d4770 100644 --- a/FreeFileSync/Source/base/config.h +++ b/FreeFileSync/Source/config.h @@ -9,10 +9,11 @@ #include <wx/gdicmn.h> #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" +#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 @@ -95,40 +96,6 @@ inline bool operator==(const ConfirmationDialogs& lhs, const ConfirmationDialogs 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, @@ -179,6 +146,7 @@ struct XmlGlobalSettings 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; @@ -238,8 +206,8 @@ struct XmlGlobalSettings wxString guiPerspectiveLast; //used by wxAuiManager } mainDlg; - Zstring defaultExclusionFilter = Zstr("/.Trash-*/") Zstr("\n") - Zstr("/.recycle/"); + Zstring defaultExclusionFilter = "/.Trash-*/" "\n" + "/.recycle/"; size_t folderHistoryMax = 20; std::vector<Zstring> versioningFolderHistory; diff --git a/FreeFileSync/Source/base/fatal_error.h b/FreeFileSync/Source/fatal_error.h index a27e423b..84ceb6dc 100644 --- a/FreeFileSync/Source/base/fatal_error.h +++ b/FreeFileSync/Source/fatal_error.h @@ -33,7 +33,7 @@ void logFatalError(const std::string& msg) //noexcept using namespace zen; assert(false); //this is stuff we like to debug - const std::string logEntry = "[" + formatTime<std::string>(FORMAT_DATE) + " " + formatTime<std::string>(FORMAT_TIME) + "] " + msg; + const std::string logEntry = '[' + utfTo<std::string>(formatTime(formatDateTag) + Zstr(' ') + formatTime(formatTimeTag)) + "] " + msg; try { saveBinContainer(getConfigDirPathPf() + Zstr("LastError.log"), logEntry, nullptr /*notifyUnbufferedIO*/); //throw FileError diff --git a/FreeFileSync/Source/base/ffs_paths.cpp b/FreeFileSync/Source/ffs_paths.cpp index 843e702e..843e702e 100644 --- a/FreeFileSync/Source/base/ffs_paths.cpp +++ b/FreeFileSync/Source/ffs_paths.cpp diff --git a/FreeFileSync/Source/base/ffs_paths.h b/FreeFileSync/Source/ffs_paths.h index bf103e28..bf103e28 100644 --- a/FreeFileSync/Source/base/ffs_paths.h +++ b/FreeFileSync/Source/ffs_paths.h diff --git a/FreeFileSync/Source/base/help_provider.h b/FreeFileSync/Source/help_provider.h index d9a1b3fc..d9a1b3fc 100644 --- a/FreeFileSync/Source/base/help_provider.h +++ b/FreeFileSync/Source/help_provider.h diff --git a/FreeFileSync/Source/base/icon_buffer.cpp b/FreeFileSync/Source/icon_buffer.cpp index b10027a6..4b032bd3 100644 --- a/FreeFileSync/Source/base/icon_buffer.cpp +++ b/FreeFileSync/Source/icon_buffer.cpp @@ -11,7 +11,7 @@ #include <zen/scope_guard.h> #include <wx+/image_resources.h> #include <wx+/dc.h> -#include "icon_loader.h" +#include "base/icon_loader.h" using namespace zen; diff --git a/FreeFileSync/Source/base/icon_buffer.h b/FreeFileSync/Source/icon_buffer.h index d6dd504f..b5ce9eae 100644 --- a/FreeFileSync/Source/base/icon_buffer.h +++ b/FreeFileSync/Source/icon_buffer.h @@ -11,7 +11,7 @@ #include <memory> #include <zen/zstring.h> #include <wx/bitmap.h> -#include "../afs/abstract.h" +#include "afs/abstract.h" namespace fff diff --git a/FreeFileSync/Source/base/localization.cpp b/FreeFileSync/Source/localization.cpp index ef1ee778..ef1ee778 100644 --- a/FreeFileSync/Source/base/localization.cpp +++ b/FreeFileSync/Source/localization.cpp diff --git a/FreeFileSync/Source/base/localization.h b/FreeFileSync/Source/localization.h index e635ac1e..e635ac1e 100644 --- a/FreeFileSync/Source/base/localization.h +++ b/FreeFileSync/Source/localization.h diff --git a/FreeFileSync/Source/base/log_file.cpp b/FreeFileSync/Source/log_file.cpp index ac866e9a..b7032137 100644 --- a/FreeFileSync/Source/base/log_file.cpp +++ b/FreeFileSync/Source/log_file.cpp @@ -10,7 +10,7 @@ #include <zen/system.h> #include <wx/datetime.h> #include "ffs_paths.h" -#include "../afs/concrete.h" +#include "afs/concrete.h" using namespace zen; using namespace fff; @@ -35,21 +35,20 @@ std::string generateLogHeaderTxt(const ProcessSummary& s, const ErrorLog& log, i headerLine += " "; const TimeComp tc = getLocalTime(std::chrono::system_clock::to_time_t(s.startTime)); //returns empty string on failure - headerLine += formatTime<std::string>(FORMAT_DATE, tc) + " [" + formatTime<std::string>(FORMAT_TIME, tc) + ']'; + headerLine += utfTo<std::string>(formatTime(formatDateTag, tc) + Zstr(" [") + formatTime(formatTimeTag, tc) + Zstr(']')); //assemble summary box std::vector<std::string> summary; summary.emplace_back(); - summary.push_back(tabSpace + utfTo<std::string>(getResultsStatusLabel(s.resultStatus))); + summary.push_back(tabSpace + utfTo<std::string>(getSyncResultLabel(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); + const ErrorLog::Stats logCount = log.getStats(); - if (errorCount > 0) summary.push_back(tabSpace + utfTo<std::string>(_("Errors:") + L" " + formatNumber(errorCount))); - if (warningCount > 0) summary.push_back(tabSpace + utfTo<std::string>(_("Warnings:") + L" " + formatNumber(warningCount))); + if (logCount.error + logCount.fatal > 0) summary.push_back(tabSpace + utfTo<std::string>(_("Errors:") + L' ' + formatNumber(logCount.error + logCount.fatal))); + if (logCount.warning > 0) summary.push_back(tabSpace + utfTo<std::string>(_("Warnings:") + L' ' + formatNumber(logCount.warning))); - summary.push_back(tabSpace + utfTo<std::string>(_("Items processed:") + L" " + formatNumber(s.statsProcessed.items) + //show always, even if 0! + summary.push_back(tabSpace + utfTo<std::string>(_("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 @@ -57,11 +56,11 @@ std::string generateLogHeaderTxt(const ProcessSummary& s, const ErrorLog& log, i ; else summary.push_back(tabSpace + utfTo<std::string>(_("Items remaining:") + - L" " + formatNumber (s.statsTotal.items - s.statsProcessed.items) + + 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<std::chrono::seconds>(s.totalTime).count(); - summary.push_back(tabSpace + utfTo<std::string>(_("Total time:")) + " " + utfTo<std::string>(wxTimeSpan::Seconds(totalTimeSec).Format())); + summary.push_back(tabSpace + utfTo<std::string>(_("Total time:")) + ' ' + utfTo<std::string>(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)); @@ -75,20 +74,21 @@ std::string generateLogHeaderTxt(const ProcessSummary& s, const ErrorLog& log, i output += '|' + std::string(sepLineLen, '_') + "\n\n"; //------------ warnings/errors preview ---------------- - const int logFailTotal = errorCount + warningCount; + const int logFailTotal = logCount.warning + logCount.error + logCount.fatal; if (logFailTotal > 0) { output += '\n' + utfTo<std::string>(_("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<std::string>(formatMessage(entry)); - if (++previewCount >= logFailsPreviewMax) - break; - } + if (logFailsPreviewMax > 0) + for (const LogEntry& entry : log) + if (entry.type & (MSG_TYPE_WARNING | MSG_TYPE_ERROR | MSG_TYPE_FATAL_ERROR)) + { + output += utfTo<std::string>(formatMessage(entry)); + if (++previewCount >= logFailsPreviewMax) + break; + } if (logFailTotal > previewCount) output += " [...] " + utfTo<std::string>(replaceCpy(_P("Showing %y of 1 row", "Showing %y of %x rows", logFailTotal), //%x used as plural form placeholder! L"%y", formatNumber(previewCount))) + '\n'; @@ -107,17 +107,21 @@ std::string generateLogFooterTxt(const std::wstring& logFilePath, int logItemsTo output += " [...] " + utfTo<std::string>(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<std::string>(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'; + return output += '\n' + std::string(SEPARATION_LINE_LEN, '_') + '\n' + + + utfTo<std::string>(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 htmlTxtImpl(std::string&& str) +std::string htmlTxt(const std::string_view& str) { - trim(str); std::string msg = htmlSpecialChars(str); + trim(msg); if (!contains(msg, '\n')) return msg; @@ -142,8 +146,9 @@ std::string htmlTxtImpl(std::string&& str) return msgFmt; } -std::string htmlTxt(const std::wstring& str) { return htmlTxtImpl(utfTo<std::string>(str)); } -std::string htmlTxt(const wchar_t* str) { return htmlTxtImpl(utfTo<std::string>(str)); } +std::string htmlTxt(const Zstring& str) { return htmlTxt(utfTo<std::string>(str)); } +std::string htmlTxt(const std::wstring& str) { return htmlTxt(utfTo<std::string>(str)); } +std::string htmlTxt(const wchar_t* str) { return htmlTxt(utfTo<std::string>(str)); } //Astyle screws up royally with the following raw string literals! @@ -161,9 +166,9 @@ std::string formatMessageHtml(const LogEntry& entry) } return R"( <tr> - <td valign="top">)" + formatTime<std::string>(FORMAT_TIME, getLocalTime(entry.time)) + R"(</td> - <td valign="top"><img src="https://freefilesync.org/images/log/)" + typeImage + R"(" width="16" height="16" alt=")" + typeLabel + R"(:" title=")" + typeLabel + R"("></td> - <td>)" + htmlTxt(entry.message.c_str()) + R"(</td> + <td valign="top">)" + htmlTxt(formatTime(formatTimeTag, getLocalTime(entry.time))) + R"(</td> + <td valign="top"><img src="https://freefilesync.org/images/log/)" + typeImage + R"(" height="16" alt=")" + typeLabel + R"(:"></td> + <td>)" + htmlTxt(makeStringView(entry.message.begin(), entry.message.end())) + R"(</td> </tr> )"; } @@ -182,10 +187,10 @@ std::wstring generateLogTitle(const ProcessSummary& s) switch (s.resultStatus) { - case SyncResult::finishedSuccess: title += utfTo<std::wstring>("\xe2\x9c\x94\xef\xb8\x8f"); break; - case SyncResult::finishedWarning: title += utfTo<std::wstring>("\xe2\x9a\xa0"); break; - case SyncResult::finishedError: - case SyncResult::aborted: title += utfTo<std::wstring>("\xe2\x9d\x8c"); break; + case SyncResult::finishedSuccess: title += utfTo<std::wstring>("\xe2\x9c\x94" "\xef\xb8\x8f"); break; //✔️ + case SyncResult::finishedWarning: title += utfTo<std::wstring>("\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<std::wstring>("\xe2\x9d\x8c" "\xef\xb8\x8f"); break; //❌️ } return title; } @@ -207,7 +212,7 @@ std::string generateLogHeaderHtml(const ProcessSummary& s, const ErrorLog& log, .log-items img { display: block; } .log-items td { padding-bottom: 0.1em; } - .log-items td:nth-child(1) { padding-right: 10px; } + .log-items td:nth-child(1) { padding-right: 10px; white-space: nowrap; } .log-items td:nth-child(2) { padding-right: 10px; } </style> </head> @@ -220,7 +225,7 @@ std::string generateLogHeaderHtml(const ProcessSummary& s, const ErrorLog& log, const TimeComp tc = getLocalTime(std::chrono::system_clock::to_time_t(s.startTime)); //returns empty string on failure output += R"( <div><span style="font-weight:600; color:gray;">)" + jobNamesFmt + R"(</span> <span style="white-space:nowrap">)" + - formatTime<std::string>(FORMAT_DATE, tc) + " " + formatTime<std::string>(FORMAT_TIME, tc) + "</span></div>\n"; + htmlTxt(formatTime(formatDateTag, tc)) + " " + htmlTxt(formatTime(formatTimeTag, tc)) + "</span></div>\n"; std::string resultsStatusImage; switch (s.resultStatus) @@ -234,27 +239,26 @@ std::string generateLogHeaderHtml(const ProcessSummary& s, const ErrorLog& log, <div style="margin:10px 0; display:inline-block; border-radius:7px; background:#f8f8f8; box-shadow:1px 1px 4px #888; overflow:hidden;"> <div style="background-color:white; border-bottom:1px solid #AAA; font-size:larger; padding:10px;"> <img src="https://freefilesync.org/images/log/)" + resultsStatusImage + R"(" width="32" height="32" alt="" style="vertical-align:middle;"> - <span style="font-weight:600; vertical-align:middle;">)" + htmlTxt(getResultsStatusLabel(s.resultStatus)) + R"(</span> + <span style="font-weight:600; vertical-align:middle;">)" + htmlTxt(getSyncResultLabel(s.resultStatus)) + R"(</span> </div> <table role="presentation" class="summary-table" style="border-spacing:0; margin-left:10px; padding:5px 10px;">)"; - const int errorCount = log.getItemCount(MSG_TYPE_ERROR | MSG_TYPE_FATAL_ERROR); - const int warningCount = log.getItemCount(MSG_TYPE_WARNING); + const ErrorLog::Stats logCount = log.getStats(); - if (errorCount > 0) + if (logCount.error + logCount.fatal > 0) output += R"( <tr> <td>)" + htmlTxt(_("Errors:")) + R"(</td> <td><img src="https://freefilesync.org/images/log/msg-error.png" width="24" height="24" alt=""></td> - <td><span style="font-weight:600;">)" + htmlTxt(formatNumber(errorCount)) + R"(</span></td> + <td><span style="font-weight:600;">)" + htmlTxt(formatNumber(logCount.error + logCount.fatal)) + R"(</span></td> </tr>)"; - if (warningCount > 0) + if (logCount.warning > 0) output += R"( <tr> <td>)" + htmlTxt(_("Warnings:")) + R"(</td> <td><img src="https://freefilesync.org/images/log/msg-warning.png" width="24" height="24" alt=""></td> - <td><span style="font-weight:600;">)" + htmlTxt(formatNumber(warningCount)) + R"(</span></td> + <td><span style="font-weight:600;">)" + htmlTxt(formatNumber(logCount.warning)) + R"(</span></td> </tr>)"; output += R"( @@ -289,7 +293,7 @@ std::string generateLogHeaderHtml(const ProcessSummary& s, const ErrorLog& log, )"; //------------ warnings/errors preview ---------------- - const int logFailTotal = errorCount + warningCount; + const int logFailTotal = logCount.warning + logCount.error + logCount.fatal; if (logFailTotal > 0) { output += R"( @@ -298,13 +302,14 @@ std::string generateLogHeaderHtml(const ProcessSummary& s, const ErrorLog& log, <table class="log-items" style="line-height:1em; border-spacing:0;"> )"; 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; - } + 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"( </table> )"; if (logFailTotal > previewCount) @@ -337,14 +342,16 @@ std::string generateLogFooterHtml(const std::wstring& logFilePath, int logItemsT return output += R"( <br> - <div> + <div style="border-bottom:1px solid #AAA; margin:5px 0;"></div> + <div style="font-size:small;"> <img src="https://freefilesync.org/images/log/)" + osImage + R"(" width="24" height="24" alt="" style="vertical-align:middle;"> <span style="vertical-align:middle;">)" + htmlTxt(getOsDescription()) + /*throw FileError*/ + - " [" + htmlTxt(getUserName()) /*throw FileError*/ + "] – " + htmlTxt(cm.model) + " – " + htmlTxt(cm.vendor) + R"(</span> + " [" + htmlTxt(getUserName()) /*throw FileError*/ + ']' + + (!cm.model .empty() ? " – " + htmlTxt(cm.model ) : "") + + (!cm.vendor.empty() ? " – " + htmlTxt(cm.vendor) : "") + R"(</span> </div> - <div style="border-bottom:1px solid #AAA; margin:5px 0;"></div> - <div> - <img src="https://freefilesync.org/images/log/log.png" width="24" height="24" alt=")" + htmlTxt(_("Log file")) + R"(:" title=")" + htmlTxt(_("Log file")) + R"(" style="vertical-align:middle;"> + <div style="font-size:small;"> + <img src="https://freefilesync.org/images/log/log.png" width="24" height="24" alt=")" + htmlTxt(_("Log file")) + R"(:" style="vertical-align:middle;"> <span style="font-family: Consolas,'Courier New',Courier,monospace; vertical-align:middle;">)" + htmlTxt(logFilePath) + R"(</span> </div> </body> @@ -358,59 +365,38 @@ std::string generateLogFooterHtml(const std::wstring& logFilePath, int logItemsT void streamToLogFile(const ProcessSummary& summary, //throw FileError const ErrorLog& log, AFS::OutputStream& streamOut, - const AbstractPath& logFilePath) + const AbstractPath& logFilePath, + LogFileFormat logFormat) { -#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<std::string>(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<int>::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); + 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 += formatMessageHtml(entry); + buffer += logFormat == LogFileFormat::html ? + formatMessageHtml(entry) : + 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<int>::max(); - - buffer += generateLogFooterHtml(AFS::getDisplayPath(logFilePath), logItemsTotal, logItemsPreviewMax); //throw FileError + 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 -#endif } void saveNewLogFile(const AbstractPath& logFilePath, //throw FileError, X + LogFileFormat logFormat, const ProcessSummary& summary, const ErrorLog& log, const std::function<void(const std::wstring& msg)>& notifyStatus /*throw X*/) @@ -437,7 +423,7 @@ void saveNewLogFile(const AbstractPath& logFilePath, //throw FileError, X }; std::unique_ptr<AFS::OutputStream> logFileStream = AFS::getOutputStream(logFilePath, std::nullopt /*streamSize*/, std::nullopt /*modTime*/, notifyUnbufferedIO); //throw FileError - streamToLogFile(summary, log, *logFileStream, logFilePath); //throw FileError, X + streamToLogFile(summary, log, *logFileStream, logFilePath, logFormat); //throw FileError, X logFileStream->finalize(); //throw FileError, X } @@ -554,7 +540,7 @@ Zstring fff::getDefaultLogFolderPath() { return getConfigDirPathPf() + Zstr("Log //"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*/) +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 @@ -575,7 +561,7 @@ AbstractPath fff::generateLogFilePath(const ProcessSummary& summary, const Zstri logFileName.resize(logFileName.size() - 2); } - logFileName += formatTime<Zstring>(Zstr("%Y-%m-%d %H%M%S"), tc) + + logFileName += formatTime(Zstr("%Y-%m-%d %H%M%S"), tc) + Zstr(".") + printNumber<Zstring>(Zstr("%03d"), static_cast<int>(timeMs)); //[ms] should yield a fairly unique name static_assert(TIME_STAMP_LENGTH == 21); @@ -593,8 +579,11 @@ AbstractPath fff::generateLogFilePath(const ProcessSummary& summary, const Zstri if (!failStatus.empty()) logFileName += STATUS_BEGIN_TOKEN + utfTo<Zstring>(failStatus) + STATUS_END_TOKEN; - logFileName += Zstr(".html"); + if (logFormat == LogFileFormat::html) + logFileName += Zstr(".html"); + else + logFileName += Zstr(".log"); AbstractPath logFolderPath = createAbstractPath(altLogFolderPathPhrase); if (AFS::isNullPath(logFolderPath)) @@ -608,13 +597,14 @@ void fff::saveLogFile(const AbstractPath& logFilePath, //throw FileError, X const ProcessSummary& summary, const ErrorLog& log, int logfilesMaxAgeDays, + LogFileFormat logFormat, const std::set<AbstractPath>& logFilePathsToKeep, const std::function<void(const std::wstring& msg)>& notifyStatus /*throw X*/) { std::exception_ptr firstError; try { - saveNewLogFile(logFilePath, summary, log, notifyStatus); //throw FileError, X + saveNewLogFile(logFilePath, logFormat, summary, log, notifyStatus); //throw FileError, X } catch (const FileError&) { if (!firstError) firstError = std::current_exception(); }; @@ -634,7 +624,7 @@ void fff::saveLogFile(const AbstractPath& logFilePath, //throw FileError, X -void fff::sendLogAsEmail(const Zstring& email, //throw FileError, X +void fff::sendLogAsEmail(const std::string& email, //throw FileError, X const ProcessSummary& summary, const ErrorLog& log, const AbstractPath& logFilePath, @@ -644,5 +634,5 @@ void fff::sendLogAsEmail(const Zstring& email, //throw FileError, X { throw SysError(_("Requires FreeFileSync Donation Edition")); } - catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot send notification email to %x."), L"%x", utfTo<std::wstring>(email)), e.toString()); } + catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot send notification email to %x."), L"%x", L'"' + utfTo<std::wstring>(email) + L'"'), e.toString()); } } diff --git a/FreeFileSync/Source/base/log_file.h b/FreeFileSync/Source/log_file.h index 3a3a481c..5eec06ad 100644 --- a/FreeFileSync/Source/base/log_file.h +++ b/FreeFileSync/Source/log_file.h @@ -11,24 +11,31 @@ #include <zen/error_log.h> #include "return_codes.h" #include "status_handler.h" -#include "../afs/abstract.h" +#include "afs/abstract.h" namespace fff { Zstring getDefaultLogFolderPath(); +enum class LogFileFormat +{ + html, + text +}; + -AbstractPath generateLogFilePath(const ProcessSummary& summary, const Zstring& altLogFolderPathPhrase /*optional*/); +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<AbstractPath>& logFilePathsToKeep, const std::function<void(const std::wstring& msg)>& notifyStatus /*throw X*/); -void sendLogAsEmail(const Zstring& email, //throw FileError, X +void sendLogAsEmail(const std::string& email, //throw FileError, X const ProcessSummary& summary, const zen::ErrorLog& log, const AbstractPath& logFilePath, diff --git a/FreeFileSync/Source/base/parse_lng.h b/FreeFileSync/Source/parse_lng.h index 361628e4..24f63d3a 100644 --- a/FreeFileSync/Source/base/parse_lng.h +++ b/FreeFileSync/Source/parse_lng.h @@ -488,7 +488,7 @@ private: 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)) + 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) @@ -601,7 +601,7 @@ private: 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, ":")) + 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() }); @@ -659,7 +659,7 @@ private: static bool endsWithColon(const std::string& s) { using namespace zen; - return endsWith(s, ":") || + return endsWith(s, ':') || endsWith(s, "\xef\xbc\x9a"); //chinese colon } diff --git a/FreeFileSync/Source/base/parse_plural.h b/FreeFileSync/Source/parse_plural.h index 242dd4a6..242dd4a6 100644 --- a/FreeFileSync/Source/base/parse_plural.h +++ b/FreeFileSync/Source/parse_plural.h diff --git a/FreeFileSync/Source/base/perf_check.cpp b/FreeFileSync/Source/perf_check.cpp index 9493a8a2..9493a8a2 100644 --- a/FreeFileSync/Source/base/perf_check.cpp +++ b/FreeFileSync/Source/perf_check.cpp diff --git a/FreeFileSync/Source/base/perf_check.h b/FreeFileSync/Source/perf_check.h index 2e9ccc6d..2e9ccc6d 100644 --- a/FreeFileSync/Source/base/perf_check.h +++ b/FreeFileSync/Source/perf_check.h diff --git a/FreeFileSync/Source/base/return_codes.h b/FreeFileSync/Source/return_codes.h index 0fe72022..ad9318c6 100644 --- a/FreeFileSync/Source/base/return_codes.h +++ b/FreeFileSync/Source/return_codes.h @@ -59,7 +59,7 @@ FfsReturnCode mapToReturnCode(SyncResult syncStatus) inline -std::wstring getResultsStatusLabel(SyncResult resultStatus) +std::wstring getSyncResultLabel(SyncResult resultStatus) { switch (resultStatus) { diff --git a/FreeFileSync/Source/base/status_handler.cpp b/FreeFileSync/Source/status_handler.cpp index 19d1e882..19d1e882 100644 --- a/FreeFileSync/Source/base/status_handler.cpp +++ b/FreeFileSync/Source/status_handler.cpp diff --git a/FreeFileSync/Source/base/status_handler.h b/FreeFileSync/Source/status_handler.h index 339f9ebe..9058033b 100644 --- a/FreeFileSync/Source/base/status_handler.h +++ b/FreeFileSync/Source/status_handler.h @@ -13,7 +13,7 @@ #include <string> #include <zen/i18n.h> #include <zen/basic_math.h> -#include "process_callback.h" +#include "base/process_callback.h" #include "return_codes.h" 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 <wx+/popup_dlg.h> #include <wx+/image_tools.h> #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<std::wstring>(FILE_NAME_SEPARATOR + folderPath.afsPath.value)); //keep it short! + m_staticTextStatus->SetLabel(_("Scanning...") + L' ' + utfTo<std::wstring>(FILE_NAME_SEPARATOR + folderPath.afsPath.value)); //keep it short! guiQueue_.processAsync([folderPath]() -> std::optional<AFS::ItemType> { 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 <wx+/popup_dlg.h> #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 <wx/window.h> -#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 <zen/shutdown.h> #include <wx+/popup_dlg.h> #include <wx/app.h> -#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<AbstractPath>& logFilePathsToKeep, - const Zstring& emailNotifyAddress, ResultsNotification emailNotifyCondition) //noexcept!! + const Zstring& altLogFolderPathPhrase, int logfilesMaxAgeDays, LogFileFormat logFormat, + const std::set<AbstractPath>& logFilePathsToKeep, + const std::string& emailNotifyAddress, ResultsNotification emailNotifyCondition) //noexcept!! { const auto totalTime = std::chrono::duration_cast<std::chrono::milliseconds>(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<std::wstring>(cmdLineExp) + L" [" + replaceCpy(_("Exit Code %x"), L"%x", numberTo<std::wstring>(exitCode)) + L']', - exitCode == 0 ? MSG_TYPE_INFO : MSG_TYPE_ERROR); + errorLog_.logMsg(_("Executing command:") + L' ' + utfTo<std::wstring>(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<std::wstring>(retryNumber + 1) + L"/" + numberTo<std::wstring>(automaticRetryCount_)), + delayAndCountDown(_("Automatic retry") + (automaticRetryCount_ <= 1 ? L"" : L' ' + numberTo<std::wstring>(retryNumber + 1) + L"/" + numberTo<std::wstring>(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 <chrono> #include <zen/error_log.h> #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<AbstractPath>& logFilePathsToKeep, - const Zstring& emailNotifyAddress, ResultsNotification emailNotifyCondition); //noexcept!! + const Zstring& altLogFolderPathPhrase, int logfilesMaxAgeDays, LogFileFormat logFormat, const std::set<AbstractPath>& 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 <wx+/image_resources.h> #include <wx+/popup_dlg.h> #include <wx/settings.h> -#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<Zstring>& filePaths) std::tie(detail.name, detail.cfgType, detail.isLastRunCfg) = [&] { if (equalNativePath(filePath, lastRunConfigPath_)) - return std::make_tuple(utfTo<Zstring>(L"[" + _("Last session") + L"]"), Details::CFG_TYPE_GUI, true); + return std::make_tuple(utfTo<Zstring>(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<std::wstring>(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<GridDataCfg*>(grid.getDataProvider())) return prov->getDataView(); - throw std::runtime_error(std::string(__FILE__) + "[" + numberTo<std::string>(__LINE__) + "] cfggrid was not initialized."); + throw std::runtime_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] cfggrid was not initialized."); } @@ -662,7 +662,7 @@ int cfggrid::getSyncOverdueDays(Grid& grid) { if (auto* prov = dynamic_cast<GridDataCfg*>(grid.getDataProvider())) return prov->getSyncOverdueDays(); - throw std::runtime_error(std::string(__FILE__) + "[" + numberTo<std::string>(__LINE__) + "] cfggrid was not initialized."); + throw std::runtime_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] cfggrid was not initialized."); } @@ -670,7 +670,7 @@ void cfggrid::setSyncOverdueDays(Grid& grid, int syncOverdueDays) { auto* prov = dynamic_cast<GridDataCfg*>(grid.getDataProvider()); if (!prov) - throw std::runtime_error(std::string(__FILE__) + "[" + numberTo<std::string>(__LINE__) + "] cfggrid was not initialized."); + throw std::runtime_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] cfggrid was not initialized."); prov->setSyncOverdueDays(syncOverdueDays); grid.Refresh(); diff --git a/FreeFileSync/Source/ui/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 <wx+/grid.h> #include <wx+/dc.h> #include <zen/zstring.h> -#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<side>()) + L"\n" + - _("Date:") + L" " + zen::formatUtcToLocalTime(file.getLastWriteTime<side>()); + toolTip += L'\n' + + _("Size:") + L' ' + zen::formatFilesizeShort(file.getFileSize<side>()) + L'\n' + + _("Date:") + L' ' + zen::formatUtcToLocalTime(file.getLastWriteTime<side>()); }, [&](const SymlinkPair& symlink) { - toolTip += L"\n" + - _("Date:") + L" " + zen::formatUtcToLocalTime(symlink.getLastWriteTime<side>()); + toolTip += L'\n' + + _("Date:") + L' ' + zen::formatUtcToLocalTime(symlink.getLastWriteTime<side>()); }); } return toolTip; @@ -1607,7 +1607,7 @@ FileView& filegrid::getDataView(Grid& grid) if (auto* prov = dynamic_cast<GridDataBase*>(grid.getDataProvider())) return prov->getDataView(); - throw std::runtime_error("filegrid was not initialized! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); + throw std::runtime_error("filegrid was not initialized! " + std::string(__FILE__) + ':' + numberTo<std::string>(__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 <wx+/grid.h> #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 <wx/wupdlock.h> #include <wx+/popup_dlg.h> #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<std::wstring>(retryNumber + 1) + L"/" + numberTo<std::wstring>(automaticRetryCount_)), + delayAndCountDown(_("Automatic retry") + (automaticRetryCount_ <= 1 ? L"" : L' ' + numberTo<std::wstring>(retryNumber + 1) + L"/" + numberTo<std::wstring>(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<AbstractPath>& logFilePathsToKeep, - const Zstring& emailNotifyAddress, ResultsNotification emailNotifyCondition) + const Zstring& altLogFolderPathPhrase, int logfilesMaxAgeDays, LogFileFormat logFormat, + const std::set<AbstractPath>& logFilePathsToKeep, + const std::string& emailNotifyAddress, ResultsNotification emailNotifyCondition) { const auto totalTime = std::chrono::duration_cast<std::chrono::milliseconds>(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<std::wstring>(cmdLineExp) + L" [" + replaceCpy(_("Exit Code %x"), L"%x", numberTo<std::wstring>(exitCode)) + L']', - exitCode == 0 ? MSG_TYPE_INFO : MSG_TYPE_ERROR); + errorLog_.logMsg(_("Executing command:") + L' ' + utfTo<std::wstring>(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<std::wstring>(retryNumber + 1) + L"/" + numberTo<std::wstring>(automaticRetryCount_)), + delayAndCountDown(_("Automatic retry") + (automaticRetryCount_ <= 1 ? L"" : L' ' + numberTo<std::wstring>(retryNumber + 1) + L"/" + numberTo<std::wstring>(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 <wx/event.h> #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<AbstractPath>& logFilePathsToKeep, - const Zstring& emailNotifyAddress, ResultsNotification emailNotifyCondition); //noexcept!! + const Zstring& altLogFolderPathPhrase, int logfilesMaxAgeDays, LogFileFormat logFormat, const std::set<AbstractPath>& 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<GetCharTypeT<Zstringw>, 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<Line> viewRef_; //partial view on log_ @@ -161,12 +160,12 @@ public: std::wstring getValue(size_t row, ColumnType colType) const override { - if (std::optional<MessageView::LogEntryView> entry = msgView_.getEntry(row)) + if (const std::optional<MessageView::LogEntryView> entry = msgView_.getEntry(row)) switch (static_cast<ColumnTypeLog>(colType)) { case ColumnTypeLog::time: if (entry->firstLine) - return formatTime<std::wstring>(FORMAT_TIME, getLocalTime(entry->time)); + return utfTo<std::wstring>(formatTime(formatTimeTag, getLocalTime(entry->time))); break; case ColumnTypeLog::category: @@ -185,7 +184,7 @@ public: break; case ColumnTypeLog::text: - return copyStringTo<std::wstring>(entry->messageLine); + return utfTo<std::wstring>(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<wxString>(FORMAT_TIME)).GetWidth(); + return 2 * getColumnGapLeft() + dc.GetTextExtent(utfTo<wxString>(formatTime(formatTimeTag))).GetWidth(); } static int getColumnCategoryDefaultWidth() @@ -353,9 +352,7 @@ void LogPanel::setLog(const std::shared_ptr<const ErrorLog>& log) return makeSharedRef<const ErrorLog>(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<const ErrorLog>& 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<GridDataMessages>(newLog)); @@ -385,7 +382,7 @@ MessageView& LogPanel::getDataView() { if (auto* prov = dynamic_cast<GridDataMessages*>(m_gridMessages->getDataProvider())) return prov->getDataView(); - throw std::runtime_error(std::string(__FILE__) + "[" + numberTo<std::string>(__LINE__) + "] m_gridMessages was not initialized."); + throw std::runtime_error(std::string(__FILE__) + '[' + numberTo<std::string>(__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<Zstringw>(prov->getValue(row, ca.type)); - clipboardString += L'\t'; + clipBuf += prov->getValue(row, ca.type); + clipBuf += L'\t'; }); - clipboardString += copyStringTo<Zstringw>(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<wxString>(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<std::wstring>(e.what()))); + showNotificationDialog(nullptr, DialogInfoType::error, PopupDialogCfg().setMainInstructions(_("Out of memory.") + L' ' + utfTo<std::wstring>(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<Zstring>& 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<std::wstring>("\xE2\x98\x85"); //"BLACK STAR" - m_menubar->Append(menu, blackStar + L" " + replaceCpy(_("FreeFileSync %x is available!"), L"%x", utfTo<std::wstring>(globalSettings.gui.lastOnlineVersion)) + L" " + blackStar); + m_menubar->Append(menu, blackStar + L' ' + replaceCpy(_("FreeFileSync %x is available!"), L"%x", utfTo<std::wstring>(globalSettings.gui.lastOnlineVersion)) + L' ' + blackStar); } @@ -1246,7 +1246,7 @@ void MainDialog::copySelectionToClipboard(const std::vector<const Grid*>& 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<const Grid*>& gridRe { std::for_each(colAttr.begin(), colAttr.end() - 1, [&](const Grid::ColAttributes& ca) { - clipboardString += copyStringTo<Zstringw>(prov->getValue(row, ca.type)); - clipboardString += L'\t'; + clipBuf += prov->getValue(row, ca.type); + clipBuf += L'\t'; }); - clipboardString += copyStringTo<Zstringw>(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<wxString>(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<std::wstring>(e.what()))); + showNotificationDialog(this, DialogInfoType::error, PopupDialogCfg().setMainInstructions(_("Out of memory.") + L' ' + utfTo<std::wstring>(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<FileSystemObject*>& 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<std::wstring> 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<Zstring>& 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<std::wstring> 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<const zen::ErrorLog>& 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<bool>(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 <zen/perf.h> #include <wx+/choice_enum.h> #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 <class TopLevelDialog> void SyncProgressDialogImpl<TopLevelDialog>::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<TopLevelDialog>::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<std::wstring>(FORMAT_DATE_TIME, tc); + title += L" | " + utfTo<std::wstring>(formatTime(formatDateTimeTag, tc)); //--------------------------------------------------------------------------- //systray tooltip, if window is minimized @@ -1341,7 +1341,7 @@ void SyncProgressDialogImpl<TopLevelDialog>::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<TopLevelDialog>::showSummary(SyncResult resultStatus } //---------------------------------- - setExternalStatus(getResultsStatusLabel(resultStatus), wxString()); + setExternalStatus(getSyncResultLabel(resultStatus), wxString()); //this->EnableCloseButton(true); @@ -1407,7 +1407,8 @@ void SyncProgressDialogImpl<TopLevelDialog>::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<TopLevelDialog>::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 <zen/error_log.h> #include <zen/zstring.h> #include <wx/frame.h> -#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<wxString>(FORMAT_DATE, getCompileTime()); + build += utfTo<wxString>(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<Zstring>& 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<wxString>(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<wxString>(globalSettings.soundFileCompareFinished)); m_textCtrlSoundPathSyncDone ->ChangeValue(utfTo<wxString>(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<ExternalApp>(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<wxString>(defaultCfg_.soundFileCompareFinished)); m_textCtrlSoundPathSyncDone ->ChangeValue(utfTo<wxString>(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<Zstring>(trimCpy(m_textCtrlSoundPathCompareDone->GetValue())); globalCfgOut_.soundFileSyncFinished = utfTo<Zstring>(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<std::wstring>(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 <wx/window.h> -#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<AfsDevice, size_t> 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<std::string>(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<Zstring>& 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<Zstring>(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 <wx+/context_menu.h> #include <wx+/image_resources.h> #include <wx+/image_tools.h> -#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<GridDataTree*>(grid.getDataProvider())) return prov->getDataView(); - throw std::runtime_error(std::string(__FILE__) + "[" + numberTo<std::string>(__LINE__) + "] treegrid was not initialized."); + throw std::runtime_error(std::string(__FILE__) + '[' + numberTo<std::string>(__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 <zen/thread.h> #include <wx+/popup_dlg.h> #include <wx+/image_resources.h> -#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<std::wstring>(onlineVersion)) + L" " + _("Download now?")). + setMainInstructions(replaceCpy(_("FreeFileSync %x is available!"), L"%x", utfTo<std::wstring>(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<std::wstring>(L"Curl status %x.", L"%x", numberTo<std::wstring>(static_cast<int>(sc))); + return replaceCpy<std::wstring>(L"Curl status %x", L"%x", numberTo<std::wstring>(static_cast<int>(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<std::string>(__LINE__)); //Option already used here! + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__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<std::string>(__LINE__)); //Contradicting options: CURLOPT_READFUNCTION, CURLOPT_POSTFIELDS + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__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<std::wstring>(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<std::wstring>(::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<int>(httpStatusCode) /*, contentType ? contentType : ""*/ }; + return { static_cast<int>(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<std::wstring>(L"SSH status %x", L"%x", numberTo<std::wstring>(sc)); } - return replaceCpy<std::wstring>(L"SSH status %x.", L"%x", numberTo<std::wstring>(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<std::wstring>(L"SFTP status %x.", L"%x", numberTo<std::wstring>(sc)); + default: return replaceCpy<std::wstring>(L"SFTP status %x", L"%x", numberTo<std::wstring>(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<Zstring> 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<std::pair<wxString, wxSize>> 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<std::pair<wxString, wxSize>> 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 <class ColorDistance> 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<ROT_270>(unsigned char b) { ret | D | E | F | input pixel is at position E |---|---|---| | G | H | I | -------------- -*/ +------------- */ + template <class Scaler, class ColorDistance, RotationDegree rotDeg> 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<rotDeg>(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<ColorDistance>(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<ColorDistance>(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<ColorDistance>(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<ColorDistance>(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<const Kernel_3x3&>(ker4); //"The Things We Do for Perf" + const Kernel_3x3& ker3 = ker4; //"The Things We Do for Perf" blendPixel<Scaler, ColorDistance, ROT_0 >(ker3, out, trgWidth, blend_xy, cfg); blendPixel<Scaler, ColorDistance, ROT_90 >(ker3, out, trgWidth, blend_xy, cfg); blendPixel<Scaler, ColorDistance, ROT_180>(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 <functional> #include <cassert> #include "type_traits.h" +#include "legacy_compiler.h" namespace numeric @@ -53,17 +54,6 @@ double mad(RandomAccessIterator first, RandomAccessIterator last); //note: inval template <class InputIterator> 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 <numbers> 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); } @@ -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<uint64_t>(crcTable) == 549755813760); static_assert(sizeof(typename std::iterator_traits<ByteIterator>::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<std::wstring>(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 <cassert> -#include <algorithm> #include <vector> -//#include <string> #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<LogEntry>::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<Zstringw>(msg) }); + entries_.push_back({ std::time(nullptr), type, utfTo<Zstringc>(msg) }); } -inline -int ErrorLog::getItemCount(int typeFilter) const -{ - return static_cast<int>(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<int>(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<std::wstring>(FORMAT_TIME, getLocalTime(entry.time)) + L"] " + getMessageTypeLabel(entry.type) + L": "; + std::string msgFmt = '[' + utfTo<std::string>(formatTime(formatTimeTag, getLocalTime(entry.time))) + "] " + utfTo<std::string>(getMessageTypeLabel(entry.type)) + ": "; const size_t prefixLen = unicodeLength(msgFmt); //consider Unicode! - const Zstringw msg = trimCpy(entry.message); - static_assert(std::is_same_v<decltype(msg), const Zstringw>, "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<decltype(msg), const Zstringc>, "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<std::wstring>(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<std::string>(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__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<std::string>(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__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 <clocale> //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<std::wstring>(L"%x %X", loc); + std::wstring dateString = utfTo<std::wstring>(formatTime(Zstr("%x %X"), loc)); return !dateString.empty() ? dateString : errorMsg(); } @@ -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<std::string>(__LINE__) + "] Failed to generate GUID." + - "\n" + utfTo<std::string>(formatSystemError(L"getentropy", errno))); + throw std::runtime_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Failed to generate GUID." + "\n\n" + + utfTo<std::string>(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<std::string>(__LINE__) + "] Failed to generate GUID." + - "\n" + utfTo<std::string>(formatSystemError(L"open", errno))); + throw std::runtime_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Failed to generate GUID." + "\n\n" + + utfTo<std::string>(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<char*>(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<std::string>(__LINE__) + "] Failed to generate GUID." + - "\n" + utfTo<std::string>(formatSystemError(L"read", bytesRead < 0 ? errno : EIO))); + throw std::runtime_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Failed to generate GUID." + "\n\n" + + utfTo<std::string>(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<std::string, std::string, LessAsciiNoCase> headers; if (postBuf && !contentType.empty()) - headers["Content-Type"] = utfTo<std::string>(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<size_t>(1024), memBuf_.size()); //smaller block size: try to only read header part buf.resize(buf.size() + blockSize); const size_t bytesReceived = tryRead(&*(buf.end() - blockSize), blockSize); //throw SysError - buf.resize(buf.size() - blockSize + bytesReceived); //caveat: unsigned arithmetics + buf.resize(buf.size() - (blockSize - bytesReceived)); //caveat: unsigned arithmetics if (contains(buf, headerDelim)) { @@ -122,8 +122,8 @@ public: statusCode_ = stringTo<int>(statusItems[1]); for (const std::string& line : split(headersBuf, "\r\n", SplitType::SKIP_EMPTY)) - responseHeaders_[trimCpy(beforeFirst(line, ":", IF_MISSING_RETURN_ALL))] = - /**/ trimCpy(afterFirst (line, ":", IF_MISSING_RETURN_NONE)); + 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<HttpInputStream::Impl> 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<HttpInputStream::Impl> sendHttpRequestImpl(const Zstring& url, auto response = std::make_unique<HttpInputStream::Impl>(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<HttpInputStream::Impl> 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<std::pair<std::string, std::string>> 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<std::pair<std::string, std::string>>& 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<HttpInputStream::Impl>(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<std::string>(beforeLast(email, Zstr('@'), IF_MISSING_RETURN_NONE)); - std::string domain = utfTo<std::string>( 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; @@ -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<std::pair<std::string, std::string>>& paramPairs); std::vector<std::pair<std::string, std::string>> 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 <vector> - #include <set> - #include <map> -#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<unsigned char*>(&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<std::wstring>(L"SSL error %x", L"%x", numberTo<std::wstring>(ec)); } - return L"Unknown SSL error: " + numberTo<std::wstring>(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<std::wstring>(L"X509 error %x", L"%x", numberTo<std::wstring>(ec)); } - return L"Unknown X509 error: " + numberTo<std::wstring>(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<std::string>(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__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<std::string>(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__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<const char*>(&n); return { numStr[3], numStr[2], numStr[1], numStr[0] }; //big endian! }; @@ -98,7 +98,7 @@ public: const int64_t timeMs = std::chrono::duration_cast<std::chrono::milliseconds>(watch_.elapsed()).count(); const std::string msg = numberTo<std::string>(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<std::wstring>(error->code)), utfTo<std::wstring>(error->message))); + throw FileError(errorMsg, formatSystemError(L"g_file_trash", + replaceCpy(_("Error Code %x"), L"%x", numberTo<std::wstring>(error->code)), + utfTo<std::wstring>(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::byte>, std::string, Zbase<char>) */ -//binary container reference implementations -using Utf8String = Zbase<char>; //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<std::byte> with ref-counted semantics, but no COW! => *almost* value type semantics, but not quite -{ -public: - using value_type = std::vector<std::byte>::value_type; - using iterator = std::vector<std::byte>::iterator; - using const_iterator = std::vector<std::byte>::const_iterator; - - iterator begin() { return buffer_.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<std::vector<std::byte>> buffer_ = makeSharedRef<std::vector<std::byte>>(); - //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<std::string>(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__)); std::vector<std::byte> 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<std::string>(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__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<uint32_t>(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<std::string>(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__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<std::string>(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__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 <class InputIterator> @@ -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<size_t>(-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_; }; @@ -360,6 +363,15 @@ Zbase<Char, SP>::Zbase(InputIterator first, InputIterator last) template <class Char, template <class> class SP> inline +Zbase<Char, SP>::Zbase(size_t count, Char fillChar) +{ + rawStr_ = this->create(count); + std::fill(rawStr_, rawStr_ + count, fillChar); + rawStr_[count] = 0; +} + + +template <class Char, template <class> class SP> inline Zbase<Char, SP>::Zbase(const Zbase<Char, SP>& str) { rawStr_ = this->clone(str.rawStr_); @@ -544,7 +556,15 @@ size_t Zbase<Char, SP>::length() const template <class Char, template <class> class SP> inline -const Char Zbase<Char, SP>::operator[](size_t pos) const +const Char& Zbase<Char, SP>::operator[](size_t pos) const +{ + assert(pos < length()); //design by contract! no runtime check! + return rawStr_[pos]; +} + + +template <class Char, template <class> class SP> inline +Char& Zbase<Char, SP>::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 <class Char> inline -Char asciiToUpper(Char c) + template <class Char> inline + Char asciiToUpper(Char c) { if (static_cast<Char>('a') <= c && c <= static_cast<Char>('z')) return static_cast<Char>(c - static_cast<Char>('a') + static_cast<Char>('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 <class Char> inline -int strcmpAsciiNoCase(const Char* lhs, const Char* rhs, size_t len) +template <class Char1, class Char2> 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<unsigned int>(charL) - static_cast<unsigned int>(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 <class Iterator> inline auto makeStringView(Iterator first, size_t len) { return makeStringView(first, first + len); } +template <class Iterator> 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 <cstring> + +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<std::wstring>(::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<std::wstring>(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 <string> -#include "utf.h" -#include "i18n.h" #include "scope_guard.h" +#include "i18n.h" +#include "utf.h" - #include <cstring> #include <cerrno> @@ -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<std::wstring>(::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<std::wstring>(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<std::wstring>(osName + " " + osVersion); //e.g. "CentOS 7.7.1908" + return utfTo<std::wstring>(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<std::string>(__LINE__) + "] Failed to get thread ID."); + // throw std::runtime_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Failed to get thread ID."); static_assert(sizeof(uint64_t) >= sizeof(tid)); return tid; } 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<std::string>(__LINE__)); } + { if (threadCountMax == 0) throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__)); } ~ThreadGroup() { @@ -8,7 +8,7 @@ #define TIME_H_8457092814324342453627 #include <ctime> -#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<std::wstring>(L"%Y|%m|%d"); -> "2011|10|29" - formatTime<std::wstring>(FORMAT_DATE); -> "2011-10-29" - formatTime<std::wstring>(FORMAT_TIME); -> "17:55:34" -*/ -template <class String, class String2> -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 <class String, class String2> TimeComp parseTime(const String& format, const String2& str); //similar to ::strptime() - //---------------------------------------------------------------------------------------------------------------------------------- @@ -118,68 +110,6 @@ TimeComp toZenTimeComponents(const std::tm& ctc) } -template <class T> -struct GetFormat; //get default time formats as char* or wchar_t* - -template <> -struct GetFormat<FormatDateTag> //%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<FormatTimeTag> //%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<FormatDateTimeTag> //%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<FormatIsoDateTag> //%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<FormatIsoTimeTag> //%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<FormatIsoDateTimeTag> //%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 <class CharType> 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 <class String, class String2> inline -String formatTime(const String2& format, const TimeComp& tc, UserDefinedFormatTag) //format as specified by "std::strftime", returns empty string on failure -{ - 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<String> buffer[256] = {}; - const size_t charsWritten = strftimeWrap(buffer, 256, strBegin(format), &ctc); - return String(buffer, charsWritten); -} - - -template <class String, class FormatType> inline -String formatTime(FormatType, const TimeComp& tc, PredefinedFormatTag) -{ - return formatTime<String>(GetFormat<FormatType>().format(GetCharTypeT<String>()), tc, UserDefinedFormatTag()); -} } @@ -298,28 +199,29 @@ TimeComp getCompileTime() } -template <class String, class String2> 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<String2, FormatDateTag > || - std::is_same_v<String2, FormatTimeTag > || - std::is_same_v<String2, FormatDateTimeTag > || - std::is_same_v<String2, FormatIsoDateTag > || - std::is_same_v<String2, FormatIsoTimeTag > || - std::is_same_v<String2, FormatIsoDateTimeTag>, impl::PredefinedFormatTag, impl::UserDefinedFormatTag>; + 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<String>(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 <class String, class String2> -TimeComp parseTime(const String& format, const String2& str, UserDefinedFormatTag) +TimeComp parseTime(const String& format, const String2& str) { using CharType = GetCharTypeT<String>; static_assert(std::is_same_v<CharType, GetCharTypeT<String2>>); @@ -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 <class FormatType, class String> inline -TimeComp parseTime(FormatType, const String& str, PredefinedFormatTag) -{ - return parseTime(GetFormat<FormatType>().format(GetCharTypeT<String>()), str, UserDefinedFormatTag()); -} -} - - -template <class String, class String2> inline -TimeComp parseTime(const String& format, const String2& str) -{ - using FormatTag = std::conditional_t< - std::is_same_v<String, FormatIsoDateTag > || - std::is_same_v<String, FormatIsoTimeTag > || - std::is_same_v<String, FormatIsoDateTimeTag>, impl::PredefinedFormatTag, impl::UserDefinedFormatTag>; - - return impl::parseTime(format, str, FormatTag()); -} } #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<std::wstring>(L"zlib status %x", L"%x", numberTo<std::wstring>(sc)); } - return replaceCpy<std::wstring>(L"zlib status %x.", L"%x", numberTo<std::wstring>(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<std::string>(__LINE__)); + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__)); gzipStream_.next_out = static_cast<Bytef*>(buffer); gzipStream_.avail_out = static_cast<uInt>(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<size_t>(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<std::wstring>(e.what())); } - catch (const std::bad_alloc& e) { throw SysError(L"zlib error: " + _("Out of memory.") + L" " + utfTo<std::wstring>(e.what())); } + catch (const std::length_error& e) { throw SysError(L"zlib error: " + _("Out of memory.") + L' ' + utfTo<std::wstring>(e.what())); } + catch (const std::bad_alloc& e) { throw SysError(L"zlib error: " + _("Out of memory.") + L' ' + utfTo<std::wstring>(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<decltype(str), const Zbase<Zchar>&>, "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<Zchar>; -//for special UI-contexts: guaranteed exponential growth + ref-counting -using Zstringw = zen::Zbase<wchar_t>; +//for special UI-contexts: guaranteed exponential growth + ref-counting + COW + no SSO overhead +using Zstringc = zen::Zbase<char>; +//using Zstringw = zen::Zbase<wchar_t>; //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 <typename T> using IsStlContainer = std::bool_constant< - impl_2384343::HasMemberTypeV_value_type <T> && - impl_2384343::HasMemberTypeV_iterator <T> && - impl_2384343::HasMemberTypeV_const_iterator<T> && - impl_2384343::HasMemberV_begin <T> && - impl_2384343::HasMemberV_end <T> && + impl_2384343::HasMemberTypeV_value_type <T>&& + impl_2384343::HasMemberTypeV_iterator <T>&& + impl_2384343::HasMemberTypeV_const_iterator<T>&& + impl_2384343::HasMemberV_begin <T>&& + impl_2384343::HasMemberV_end <T>&& impl_2384343::HasMemberV_insert <T>>; 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<String> output; - for (const std::string& str : log_.ref().elementList()) - output.push_back(utfTo<String>(str)); + for (const std::string& str : log_.ref().elementList()) + output.push_back(utfTo<String>(str)); return output; } @@ -382,7 +382,7 @@ private: static std::string getNameFormatted(const XmlElement& elem) //"<Root> <Level1> <Level2>" { - return (elem.parent() ? getNameFormatted(*elem.parent()) + " " : std::string()) + "<" + elem.getNameAs<std::string>() + ">"; + return (elem.parent() ? getNameFormatted(*elem.parent()) + ' ' : std::string()) + '<' + elem.getNameAs<std::string>() + '>'; } 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<std::wstring>()) - 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); } |