summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xChangelog.txt19
-rwxr-xr-xFreeFileSync/Build/Languages/german.lng44
-rwxr-xr-xFreeFileSync/Source/Makefile5
-rwxr-xr-xFreeFileSync/Source/RealTimeSync/Makefile1
-rwxr-xr-xFreeFileSync/Source/RealTimeSync/application.cpp2
-rwxr-xr-xFreeFileSync/Source/RealTimeSync/folder_selector2.cpp6
-rwxr-xr-xFreeFileSync/Source/RealTimeSync/main_dlg.cpp23
-rwxr-xr-xFreeFileSync/Source/RealTimeSync/monitor.cpp32
-rwxr-xr-xFreeFileSync/Source/RealTimeSync/tray_menu.cpp2
-rwxr-xr-xFreeFileSync/Source/RealTimeSync/xml_proc.cpp21
-rwxr-xr-xFreeFileSync/Source/RealTimeSync/xml_proc.h1
-rwxr-xr-xFreeFileSync/Source/base/algorithm.cpp74
-rwxr-xr-xFreeFileSync/Source/base/algorithm.h2
-rwxr-xr-xFreeFileSync/Source/base/application.cpp46
-rwxr-xr-xFreeFileSync/Source/base/comparison.cpp231
-rwxr-xr-xFreeFileSync/Source/base/comparison.h1
-rwxr-xr-xFreeFileSync/Source/base/db_file.cpp43
-rwxr-xr-xFreeFileSync/Source/base/db_file.h12
-rwxr-xr-xFreeFileSync/Source/base/dir_exist_async.h39
-rwxr-xr-xFreeFileSync/Source/base/dir_lock.cpp4
-rwxr-xr-xFreeFileSync/Source/base/fatal_error.h45
-rwxr-xr-xFreeFileSync/Source/base/file_hierarchy.cpp23
-rwxr-xr-xFreeFileSync/Source/base/file_hierarchy.h21
-rwxr-xr-xFreeFileSync/Source/base/generate_logfile.cpp4
-rwxr-xr-xFreeFileSync/Source/base/hard_filter.cpp18
-rwxr-xr-xFreeFileSync/Source/base/hard_filter.h8
-rwxr-xr-xFreeFileSync/Source/base/icon_buffer.cpp2
-rwxr-xr-xFreeFileSync/Source/base/localization.cpp29
-rwxr-xr-xFreeFileSync/Source/base/lock_holder.h8
-rwxr-xr-xFreeFileSync/Source/base/parallel_scan.cpp35
-rwxr-xr-xFreeFileSync/Source/base/parallel_scan.h4
-rwxr-xr-xFreeFileSync/Source/base/parse_lng.h119
-rwxr-xr-xFreeFileSync/Source/base/parse_plural.h56
-rwxr-xr-xFreeFileSync/Source/base/perf_check.h2
-rwxr-xr-xFreeFileSync/Source/base/process_xml.cpp47
-rwxr-xr-xFreeFileSync/Source/base/process_xml.h2
-rwxr-xr-xFreeFileSync/Source/base/resolve_path.cpp21
-rwxr-xr-xFreeFileSync/Source/base/status_handler.h2
-rwxr-xr-xFreeFileSync/Source/base/status_handler_impl.h26
-rwxr-xr-xFreeFileSync/Source/base/structures.cpp4
-rwxr-xr-xFreeFileSync/Source/base/synchronization.cpp140
-rwxr-xr-xFreeFileSync/Source/base/synchronization.h1
-rwxr-xr-xFreeFileSync/Source/base/versioning.cpp74
-rwxr-xr-xFreeFileSync/Source/base/versioning.h3
-rwxr-xr-xFreeFileSync/Source/fs/abstract.cpp16
-rwxr-xr-xFreeFileSync/Source/fs/abstract.h17
-rwxr-xr-xFreeFileSync/Source/fs/concrete_impl.h10
-rwxr-xr-xFreeFileSync/Source/fs/native.cpp57
-rwxr-xr-xFreeFileSync/Source/ui/batch_status_handler.cpp3
-rwxr-xr-xFreeFileSync/Source/ui/cfg_grid.cpp22
-rwxr-xr-xFreeFileSync/Source/ui/cfg_grid.h2
-rwxr-xr-xFreeFileSync/Source/ui/command_box.cpp28
-rwxr-xr-xFreeFileSync/Source/ui/file_grid.cpp10
-rwxr-xr-xFreeFileSync/Source/ui/file_view.cpp4
-rwxr-xr-xFreeFileSync/Source/ui/folder_history_box.h4
-rwxr-xr-xFreeFileSync/Source/ui/folder_selector.cpp6
-rwxr-xr-xFreeFileSync/Source/ui/gui_generated.cpp66
-rwxr-xr-xFreeFileSync/Source/ui/gui_generated.h7
-rwxr-xr-xFreeFileSync/Source/ui/gui_status_handler.cpp7
-rwxr-xr-xFreeFileSync/Source/ui/gui_status_handler.h4
-rwxr-xr-xFreeFileSync/Source/ui/log_panel.cpp2
-rwxr-xr-xFreeFileSync/Source/ui/main_dlg.cpp94
-rwxr-xr-xFreeFileSync/Source/ui/main_dlg.h2
-rwxr-xr-xFreeFileSync/Source/ui/progress_indicator.cpp9
-rwxr-xr-xFreeFileSync/Source/ui/search_grid.cpp135
-rwxr-xr-xFreeFileSync/Source/ui/search_grid.h19
-rwxr-xr-xFreeFileSync/Source/ui/small_dlgs.cpp1
-rwxr-xr-xFreeFileSync/Source/ui/sorting.h13
-rwxr-xr-xFreeFileSync/Source/ui/sync_cfg.cpp5
-rwxr-xr-xFreeFileSync/Source/ui/tree_grid.cpp14
-rwxr-xr-xFreeFileSync/Source/version/version.h2
-rwxr-xr-xwx+/async_task.h2
-rwxr-xr-xwx+/choice_enum.h8
-rwxr-xr-xwx+/context_menu.h4
-rwxr-xr-xwx+/dc.h1
-rwxr-xr-xwx+/graph.cpp4
-rwxr-xr-xwx+/grid.h20
-rwxr-xr-xwx+/image_resources.cpp13
-rwxr-xr-xwx+/image_tools.cpp16
-rwxr-xr-xwx+/zlib_wrap.cpp7
-rwxr-xr-xzen/file_access.cpp2
-rwxr-xr-xzen/file_error.h2
-rwxr-xr-xzen/file_traverser.cpp2
-rwxr-xr-xzen/globals.h135
-rwxr-xr-xzen/guid.h1
-rwxr-xr-xzen/http.cpp57
-rwxr-xr-xzen/i18n.h4
-rwxr-xr-xzen/legacy_compiler.h3
-rwxr-xr-xzen/scope_guard.h7
-rwxr-xr-xzen/shell_execute.h4
-rwxr-xr-xzen/socket.h74
-rwxr-xr-xzen/stl_tools.h1
-rwxr-xr-xzen/string_tools.h171
-rwxr-xr-xzen/thread.cpp12
-rwxr-xr-xzen/time.h6
-rwxr-xr-xzen/type_traits.h1
-rwxr-xr-xzen/utf.h6
-rwxr-xr-xzen/zstring.cpp184
-rwxr-xr-xzen/zstring.h163
-rwxr-xr-xzenXml/zenxml/cvrt_text.h6
-rwxr-xr-xzenXml/zenxml/parser.h196
-rwxr-xr-xzenXml/zenxml/xml.h440
102 files changed, 2197 insertions, 1216 deletions
diff --git a/Changelog.txt b/Changelog.txt
index 90955af9..004aca9d 100755
--- a/Changelog.txt
+++ b/Changelog.txt
@@ -1,3 +1,22 @@
+FreeFileSync 10.5 [2018-10-11]
+------------------------------
+New file matching algorithm considering Unicode normalization
+User-configurable timeout for FTP and SFTP connections
+Obsoleted old CHM manual in favor of PDF
+Ignore case sensitivity during filter matching (Linux)
+Unicode-normalized and faster case-insensitive grid search
+New button to save current view filter settings as default
+Both slash and backslash can be used in filter expressions
+Improved Unicode case conversion routines
+Keyboard shortcuts for swap sides (F10) and view category (F11)
+Don't steal input focus when closing progress dialog (macOS)
+Fixed shutdown crash when accessing already destroyed state
+Fixed file grid column order not being preserved
+Fixed manual activation input fields being disabled (macOS)
+Fixed FTP parsing error due to invalid folder time
+Fixed statistics boxes background distortion (macOS)
+
+
FreeFileSync 10.4 [2018-09-09]
------------------------------
Allow overriding log folder path for gui and batch runs
diff --git a/FreeFileSync/Build/Languages/german.lng b/FreeFileSync/Build/Languages/german.lng
index 51d22174..4b79745a 100755
--- a/FreeFileSync/Build/Languages/german.lng
+++ b/FreeFileSync/Build/Languages/german.lng
@@ -7,11 +7,20 @@
<plural_definition>n == 1 ? 0 : 1</plural_definition>
</header>
-<source>Default log path:</source>
-<target>Standardprotokollpfad:</target>
+<source>Access timeout (in seconds):</source>
+<target>Zeitlimit für Zugriff(in Sekunden):</target>
-<source>&Override default log path:</source>
-<target>&Standardprotokollpfad überschreiben:</target>
+<source>The server returned an error:</source>
+<target>Der Server hat einen Fehler zurückgegeben:</target>
+
+<source>You may close this page now and continue with FreeFileSync.</source>
+<target>Sie können diese Seite nun schließen und mit FreeFileSync fortfahren.</target>
+
+<source>Authentication failed.</source>
+<target>Authentifizierung fehlgeschlagen.</target>
+
+<source>Authentication completed.</source>
+<target>Authentifizierung abgeschlossen.</target>
<source>Both sides have changed since last synchronization.</source>
<target>Beide Seiten wurden seit der letzten Synchronisation verändert.</target>
@@ -193,9 +202,6 @@
<source>File time tolerance</source>
<target>Dateizeittoleranz</target>
-<source>Folder access timeout</source>
-<target>Zeitlimit für Ordnerzugriff</target>
-
<source>Run with background priority</source>
<target>Mit Hintergrundpriorität ausführen</target>
@@ -591,6 +597,12 @@ Tatsächlich: %y bytes
<source>Unable to move %x to the recycle bin.</source>
<target>%x kann nicht in den Papierkorb verschoben werden.</target>
+<source>Unable to access %x.</source>
+<target>Auf %x kann nicht zugegriffen werden.</target>
+
+<source>Cannot determine free disk space for %x.</source>
+<target>Der freie Speicherplatz für %x konnte nicht ermittelt werden.</target>
+
<source>Cannot find %x.</source>
<target>%x wurde nicht gefunden.</target>
@@ -603,18 +615,12 @@ Tatsächlich: %y bytes
<source>Cannot delete symbolic link %x.</source>
<target>Die symbolischen Verknüpfung %x kann nicht gelöscht werden.</target>
-<source>Cannot determine free disk space for %x.</source>
-<target>Der freie Speicherplatz für %x konnte nicht ermittelt werden.</target>
-
<source>Incorrect command line:</source>
<target>Ungültige Befehlszeile:</target>
<source>The server does not support authentication via %x.</source>
<target>Der Server unterstützt keine Authentifizierung über %x.</target>
-<source>Unable to access %x.</source>
-<target>Auf %x kann nicht zugegriffen werden.</target>
-
<source>
<pluralform>Operation timed out after 1 second.</pluralform>
<pluralform>Operation timed out after %x seconds.</pluralform>
@@ -1144,6 +1150,9 @@ Die Befehlszeile wird ausgelöst, wenn:
<source>Last x days:</source>
<target>Letzte x Tage:</target>
+<source>&Override default log path:</source>
+<target>&Standardprotokollpfad überschreiben:</target>
+
<source>Run a command after synchronization:</source>
<target>Befehl nach Synchronisation ausführen:</target>
@@ -1309,6 +1318,9 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert.
<source>Show all permanently hidden dialogs and warning messages again</source>
<target>Alle dauerhaft versteckten Fenster und Warnmeldungen wieder anzeigen</target>
+<source>Default log path:</source>
+<target>Standardprotokollpfad:</target>
+
<source>Remove old log files after x days:</source>
<target>Alte Protokolldateien nach x Tagen entfernen:</target>
@@ -1954,12 +1966,6 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert.
<source>Checking recycle bin failed for folder %x.</source>
<target>Die Prüfung des Papierkorbs für Ordner %x ist fehlgeschlagen.</target>
-<source>The following XML elements could not be read:</source>
-<target>Die folgenden XML-Elemente konnten nicht gelesen werden:</target>
-
-<source>Configuration file %x is incomplete. The missing elements will be set to their default values.</source>
-<target>Die Konfigurationsdatei %x ist unvollständig. Die fehlenden Elemente werden auf ihre Standardwerte gesetzt.</target>
-
<source>Prepare installation</source>
<target>Installation vorbereiten</target>
diff --git a/FreeFileSync/Source/Makefile b/FreeFileSync/Source/Makefile
index efc73825..bd274138 100755
--- a/FreeFileSync/Source/Makefile
+++ b/FreeFileSync/Source/Makefile
@@ -68,14 +68,13 @@ CPP_FILES+=ui/gui_generated.cpp
CPP_FILES+=ui/gui_status_handler.cpp
CPP_FILES+=ui/main_dlg.cpp
CPP_FILES+=ui/progress_indicator.cpp
-CPP_FILES+=ui/search.cpp
+CPP_FILES+=ui/search_grid.cpp
CPP_FILES+=ui/small_dlgs.cpp
CPP_FILES+=ui/sync_cfg.cpp
CPP_FILES+=ui/taskbar.cpp
CPP_FILES+=ui/tray_icon.cpp
CPP_FILES+=ui/triple_splitter.cpp
CPP_FILES+=ui/version_check.cpp
-CPP_FILES+=../../zen/xml_io.cpp
CPP_FILES+=../../zen/recycler.cpp
CPP_FILES+=../../zen/file_access.cpp
CPP_FILES+=../../zen/file_io.cpp
@@ -118,11 +117,11 @@ install:
mkdir -p $(APPSHAREDIR)
cp -R ../Build/Languages/ \
- ../Build/Help/ \
../Build/ding.wav \
../Build/gong.wav \
../Build/harp.wav \
../Build/Resources.zip \
+ "../Build/User Manual.pdf" \
$(APPSHAREDIR)
mkdir -p $(DOCSHAREDIR)
diff --git a/FreeFileSync/Source/RealTimeSync/Makefile b/FreeFileSync/Source/RealTimeSync/Makefile
index 61aee2cb..900d2d22 100755
--- a/FreeFileSync/Source/RealTimeSync/Makefile
+++ b/FreeFileSync/Source/RealTimeSync/Makefile
@@ -23,7 +23,6 @@ CPP_FILES+=folder_selector2.cpp
CPP_FILES+=../base/localization.cpp
CPP_FILES+=../base/resolve_path.cpp
CPP_FILES+=../base/ffs_paths.cpp
-CPP_FILES+=../../../zen/xml_io.cpp
CPP_FILES+=../../../zen/dir_watcher.cpp
CPP_FILES+=../../../zen/file_access.cpp
CPP_FILES+=../../../zen/file_io.cpp
diff --git a/FreeFileSync/Source/RealTimeSync/application.cpp b/FreeFileSync/Source/RealTimeSync/application.cpp
index 69f7475f..6047468f 100755
--- a/FreeFileSync/Source/RealTimeSync/application.cpp
+++ b/FreeFileSync/Source/RealTimeSync/application.cpp
@@ -17,7 +17,7 @@
#include "../base/localization.h"
#include "../base/ffs_paths.h"
#include "../base/return_codes.h"
-#include "../base/error_log.h"
+#include "../base/fatal_error.h"
#include "../base/help_provider.h"
#include "../base/resolve_path.h"
diff --git a/FreeFileSync/Source/RealTimeSync/folder_selector2.cpp b/FreeFileSync/Source/RealTimeSync/folder_selector2.cpp
index 0f895246..298d55b6 100755
--- a/FreeFileSync/Source/RealTimeSync/folder_selector2.cpp
+++ b/FreeFileSync/Source/RealTimeSync/folder_selector2.cpp
@@ -36,7 +36,7 @@ void setFolderPath(const Zstring& dirpath, wxTextCtrl* txtCtrl, wxWindow& toolti
tooltipWnd.SetToolTip(utfTo<wxString>(folderPathFmt));
if (staticText) //change static box label only if there is a real difference to what is shown in wxTextCtrl anyway
- staticText->SetLabel(equalFilePath(appendSeparator(trimCpy(dirpath)), appendSeparator(folderPathFmt)) ? wxString(_("Drag && drop")) : utfTo<wxString>(folderPathFmt));
+ staticText->SetLabel(equalLocalPath(appendSeparator(trimCpy(dirpath)), appendSeparator(folderPathFmt)) ? wxString(_("Drag && drop")) : utfTo<wxString>(folderPathFmt));
}
}
@@ -150,9 +150,7 @@ void FolderSelector2::onSelectDir(wxCommandEvent& event)
Zstring FolderSelector2::getPath() const
{
- Zstring path = utfTo<Zstring>(folderPathCtrl_.GetValue());
-
- return path;
+ return utfTo<Zstring>(folderPathCtrl_.GetValue());
}
diff --git a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp
index 3ee8956b..e4dfcb56 100755
--- a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp
+++ b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp
@@ -18,7 +18,6 @@
#include "tray_menu.h"
#include "app_icon.h"
#include "../base/help_provider.h"
-//#include "../base/process_xml.h"
#include "../base/ffs_paths.h"
#include "../version/version.h"
@@ -35,8 +34,8 @@ namespace
std::wstring extractJobName(const Zstring& cfgFilePath)
{
- const Zstring shortName = afterLast(cfgFilePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL);
- const Zstring jobName = beforeLast(shortName, Zstr('.'), IF_MISSING_RETURN_ALL);
+ const Zstring fileName = afterLast(cfgFilePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL);
+ const Zstring jobName = beforeLast(fileName, Zstr('.'), IF_MISSING_RETURN_ALL);
return utfTo<std::wstring>(jobName);
}
@@ -213,7 +212,7 @@ void MainDialog::OnStart(wxCommandEvent& event)
Hide();
XmlRealConfig currentCfg = getConfiguration();
- const Zstring activeCfgFilePath = !equalFilePath(activeConfigFile_, lastRunConfigPath_) ? activeConfigFile_ : Zstring();
+ const Zstring activeCfgFilePath = !equalLocalPath(activeConfigFile_, lastRunConfigPath_) ? activeConfigFile_ : Zstring();
switch (runFolderMonitor(currentCfg, ::extractJobName(activeCfgFilePath)))
{
@@ -232,16 +231,16 @@ void MainDialog::OnStart(wxCommandEvent& event)
void MainDialog::OnConfigSave(wxCommandEvent& event)
{
- Zstring defaultFilePath = !activeConfigFile_.empty() && !equalFilePath(activeConfigFile_, lastRunConfigPath_) ? activeConfigFile_ : Zstr("Realtime.ffs_real");
+ const Zstring defaultFilePath = !activeConfigFile_.empty() && !equalLocalPath(activeConfigFile_, lastRunConfigPath_) ? activeConfigFile_ : Zstr("Realtime.ffs_real");
+ auto defaultFolder = utfTo<wxString>(beforeLast(defaultFilePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE));
+ auto defaultFileName = utfTo<wxString>(afterLast (defaultFilePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL));
+
//attention: currentConfigFileName may be an imported *.ffs_batch file! We don't want to overwrite it with a GUI config!
- if (endsWith(defaultFilePath, Zstr(".ffs_batch"), CmpFilePath()))
- defaultFilePath = beforeLast(defaultFilePath, Zstr("."), IF_MISSING_RETURN_NONE) + Zstr(".ffs_real");
+ defaultFileName = beforeLast(defaultFileName, L'.', IF_MISSING_RETURN_ALL) + L".ffs_real";
wxFileDialog filePicker(this,
wxString(),
- //OS X really needs dir/file separated like this:
- utfTo<wxString>(beforeLast(defaultFilePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE)), //default dir
- utfTo<wxString>(afterLast (defaultFilePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL)), //default file
+ defaultFolder, defaultFileName, //OS X really needs dir/file separated like this
wxString(L"RealTimeSync (*.ffs_real)|*.ffs_real") + L"|" +_("All files") + L" (*.*)|*",
wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if (filePicker.ShowModal() != wxID_OK)
@@ -291,7 +290,7 @@ void MainDialog::setLastUsedConfig(const Zstring& filepath)
{
activeConfigFile_ = filepath;
- const Zstring activeCfgFilePath = !equalFilePath(activeConfigFile_, lastRunConfigPath_) ? activeConfigFile_ : Zstring();
+ const Zstring activeCfgFilePath = !equalLocalPath(activeConfigFile_, lastRunConfigPath_) ? activeConfigFile_ : Zstring();
if (!activeCfgFilePath.empty())
SetTitle(utfTo<wxString>(activeCfgFilePath));
@@ -309,7 +308,7 @@ void MainDialog::OnConfigNew(wxCommandEvent& event)
void MainDialog::OnConfigLoad(wxCommandEvent& event)
{
- const Zstring activeCfgFilePath = !equalFilePath(activeConfigFile_, lastRunConfigPath_) ? activeConfigFile_ : Zstring();
+ const Zstring activeCfgFilePath = !equalLocalPath(activeConfigFile_, lastRunConfigPath_) ? activeConfigFile_ : Zstring();
wxFileDialog filePicker(this,
wxString(),
diff --git a/FreeFileSync/Source/RealTimeSync/monitor.cpp b/FreeFileSync/Source/RealTimeSync/monitor.cpp
index 84e3e363..8d12080b 100755
--- a/FreeFileSync/Source/RealTimeSync/monitor.cpp
+++ b/FreeFileSync/Source/RealTimeSync/monitor.cpp
@@ -22,14 +22,14 @@ const std::chrono::seconds FOLDER_EXISTENCE_CHECK_INTERVAL(1);
//wait until all directories become available (again) + logs in network share
-std::set<Zstring, LessFilePath> waitForMissingDirs(const std::vector<Zstring>& folderPathPhrases, //throw FileError
- const std::function<void(const Zstring& folderPath)>& requestUiRefresh, std::chrono::milliseconds cbInterval)
+std::set<Zstring, LessLocalPath> waitForMissingDirs(const std::vector<Zstring>& folderPathPhrases, //throw FileError
+ const std::function<void(const Zstring& folderPath)>& requestUiRefresh, std::chrono::milliseconds cbInterval)
{
//early failure! check for unsupported folder paths:
for (const Zstring& protoName : { Zstr("FTP"), Zstr("SFTP"), Zstr("MTP") })
for (const Zstring& phrase : folderPathPhrases)
//hopefully clear enough now: https://freefilesync.org/forum/viewtopic.php?t=4302
- if (startsWith(trimCpy(phrase), protoName + Zstr(":"), CmpAsciiNoCase()))
+ if (startsWithAsciiNoCase(trimCpy(phrase), protoName + Zstr(":")))
throw FileError(replaceCpy(_("The %x protocol does not support directory monitoring:"), L"%x", utfTo<std::wstring>(protoName)) + L"\n\n" + fmtPath(phrase));
for (;;)
@@ -39,7 +39,7 @@ std::set<Zstring, LessFilePath> waitForMissingDirs(const std::vector<Zstring>& f
Zstring folderPathPhrase;
std::future<bool> folderAvailable;
};
- std::map<Zstring, FolderInfo, LessFilePath> folderInfos; //folderPath => FolderInfo
+ std::map<Zstring, FolderInfo, LessLocalPath> folderInfos; //folderPath => FolderInfo
for (const Zstring& phrase : folderPathPhrases)
{
@@ -50,12 +50,11 @@ std::set<Zstring, LessFilePath> waitForMissingDirs(const std::vector<Zstring>& f
folderInfos[folderPath] = { phrase, runAsync([folderPath]{ return dirAvailable(folderPath); }) };
}
- std::set<Zstring, LessFilePath> availablePaths;
- std::set<Zstring, LessFilePath> missingPathPhrases;
- for (auto& item : folderInfos)
+ std::set<Zstring, LessLocalPath> availablePaths;
+ std::set<Zstring, LessLocalPath> missingPathPhrases;
+ for (auto& [folderPath, folderInfo] : folderInfos)
{
- const Zstring& folderPath = item.first;
- std::future<bool>& folderAvailable = item.second.folderAvailable;
+ std::future<bool>& folderAvailable = folderInfo.folderAvailable;
while (folderAvailable.wait_for(cbInterval) != std::future_status::ready)
requestUiRefresh(folderPath); //throw X
@@ -63,7 +62,7 @@ std::set<Zstring, LessFilePath> waitForMissingDirs(const std::vector<Zstring>& f
if (folderAvailable.get())
availablePaths.insert(folderPath);
else
- missingPathPhrases.insert(item.second.folderPathPhrase);
+ missingPathPhrases.insert(folderInfo.folderPathPhrase);
}
if (missingPathPhrases.empty())
return availablePaths; //only return when all folders were found on *first* try!
@@ -119,7 +118,7 @@ struct WaitResult
};
-WaitResult waitForChanges(const std::set<Zstring, LessFilePath>& folderPaths, //throw FileError
+WaitResult waitForChanges(const std::set<Zstring, LessLocalPath>& folderPaths, //throw FileError
const std::function<void(bool readyForSync)>& requestUiRefresh, std::chrono::milliseconds cbInterval)
{
assert(std::all_of(folderPaths.begin(), folderPaths.end(), [](const Zstring& folderPath) { return dirAvailable(folderPath); }));
@@ -154,19 +153,16 @@ WaitResult waitForChanges(const std::set<Zstring, LessFilePath>& folderPaths, //
return false;
}();
- for (const auto& item : watches)
+ for (const auto& [folderPath, watcher] : watches)
{
- const Zstring& folderPath = item.first;
- DirWatcher& watcher = *item.second;
-
//IMPORTANT CHECK: DirWatcher has problems detecting removal of top watched directories!
if (checkDirNow)
if (!dirAvailable(folderPath)) //catch errors related to directory removal, e.g. ERROR_NETNAME_DELETED
return WaitResult(folderPath);
try
{
- std::vector<DirWatcher::Entry> changedItems = watcher.getChanges([&] { requestUiRefresh(false /*readyForSync*/); /*throw X*/ },
- cbInterval); //throw FileError
+ std::vector<DirWatcher::Entry> changedItems = watcher->getChanges([&] { requestUiRefresh(false /*readyForSync*/); /*throw X*/ },
+ cbInterval); //throw FileError
erase_if(changedItems, [](const DirWatcher::Entry& e)
{
return
@@ -226,7 +222,7 @@ void rts::monitorDirectories(const std::vector<Zstring>& folderPathPhrases, std:
for (;;)
try
{
- std::set<Zstring, LessFilePath> folderPaths = waitForMissingDirs(folderPathPhrases, [&](const Zstring& folderPath) { requestUiRefresh(&folderPath); }, cbInterval); //throw FileError
+ std::set<Zstring, LessLocalPath> folderPaths = waitForMissingDirs(folderPathPhrases, [&](const Zstring& folderPath) { requestUiRefresh(&folderPath); }, cbInterval); //throw FileError
//schedule initial execution (*after* all directories have arrived)
auto nextExecTime = std::chrono::steady_clock::now() + delay;
diff --git a/FreeFileSync/Source/RealTimeSync/tray_menu.cpp b/FreeFileSync/Source/RealTimeSync/tray_menu.cpp
index ddc5ea1c..667b480d 100755
--- a/FreeFileSync/Source/RealTimeSync/tray_menu.cpp
+++ b/FreeFileSync/Source/RealTimeSync/tray_menu.cpp
@@ -114,7 +114,7 @@ private:
realtimeIcon.CopyFromBitmap(bmp);
wxString tooltip = L"RealTimeSync\n" + statusTxt;
if (!jobName_.empty())
- tooltip += L"\n\"" + jobName_ + L"\"";
+ tooltip += L"\n\"" + jobName_ + L'"';
SetIcon(realtimeIcon, tooltip);
}
diff --git a/FreeFileSync/Source/RealTimeSync/xml_proc.cpp b/FreeFileSync/Source/RealTimeSync/xml_proc.cpp
index dd933e0d..1e8ca476 100755
--- a/FreeFileSync/Source/RealTimeSync/xml_proc.cpp
+++ b/FreeFileSync/Source/RealTimeSync/xml_proc.cpp
@@ -6,6 +6,7 @@
#include "xml_proc.h"
#include <zen/file_access.h>
+#include <zenxml/xml.h>
#include <wx/intl.h>
#include "../base/ffs_paths.h"
#include "../base/localization.h"
@@ -75,7 +76,7 @@ void writeConfig(const XmlRealConfig& config, XmlOut& out)
template <class ConfigType>
void readConfig(const Zstring& filePath, RtsXmlType type, ConfigType& cfg, std::wstring& warningMsg) //throw FileError
{
- XmlDoc doc = loadXmlDocument(filePath); //throw FileError
+ XmlDoc doc = loadXml(filePath); //throw FileError
if (getXmlTypeNoThrow(doc) != type) //noexcept
throw FileError(replaceCpy(_("File %x does not contain a valid configuration."), L"%x", fmtPath(filePath)));
@@ -85,7 +86,7 @@ void readConfig(const Zstring& filePath, RtsXmlType type, ConfigType& cfg, std::
try
{
- checkForMappingErrors(in, filePath); //throw FileError
+ checkXmlMappingErrors(in, filePath); //throw FileError
}
catch (const FileError& e) { warningMsg = e.toString(); }
}
@@ -106,14 +107,14 @@ void rts::writeConfig(const XmlRealConfig& config, const Zstring& filepath) //th
XmlOut out(doc);
::writeConfig(config, out);
- saveXmlDocument(doc, filepath); //throw FileError
+ saveXml(doc, filepath); //throw FileError
}
void rts::readRealOrBatchConfig(const Zstring& filePath, XmlRealConfig& config, std::wstring& warningMsg) //throw FileError
{
- //do NOT use zen::loadStream as it will needlessly load even huge files!
- XmlDoc doc = loadXmlDocument(filePath); //throw FileError; quick exit if file is not an FFS XML
+ XmlDoc doc = loadXml(filePath); //throw FileError
+ //quick exit if file is not an FFS XML
const RtsXmlType xmlType = ::getXmlTypeNoThrow(doc);
@@ -123,7 +124,7 @@ void rts::readRealOrBatchConfig(const Zstring& filePath, XmlRealConfig& config,
XmlIn in(doc);
//read folder pairs
- std::set<Zstring, LessFilePath> uniqueFolders;
+ std::set<Zstring, LessLocalPath> uniqueFolders;
for (XmlIn inPair = in["FolderPairs"]["Pair"]; inPair; inPair.next())
{
@@ -137,13 +138,13 @@ void rts::readRealOrBatchConfig(const Zstring& filePath, XmlRealConfig& config,
}
//don't consider failure a warning only:
- checkForMappingErrors(in, filePath); //throw FileError
+ checkXmlMappingErrors(in, filePath); //throw FileError
//---------------------------------------------------------------------------------------
erase_if(uniqueFolders, [](const Zstring& str) { return trimCpy(str).empty(); });
config.directories.assign(uniqueFolders.begin(), uniqueFolders.end());
- config.commandline = Zstr("\"") + fff::getFreeFileSyncLauncherPath() + Zstr("\" \"") + filePath + Zstr("\"");
+ config.commandline = Zstr('"') + fff::getFreeFileSyncLauncherPath() + Zstr("\" \"") + filePath + Zstr('"');
}
else
return readConfig(filePath, config, warningMsg); //throw FileError
@@ -157,7 +158,7 @@ wxLanguage rts::getProgramLanguage() //throw FileError
XmlDoc doc;
try
{
- doc = loadXmlDocument(filePath); //throw FileError
+ doc = loadXml(filePath); //throw FileError
}
catch (FileError&)
{
@@ -174,6 +175,6 @@ wxLanguage rts::getProgramLanguage() //throw FileError
wxLanguage lng = wxLANGUAGE_UNKNOWN;
in["General"]["Language"].attribute("Name", lng);
- checkForMappingErrors(in, filePath); //throw FileError
+ checkXmlMappingErrors(in, filePath); //throw FileError
return lng;
}
diff --git a/FreeFileSync/Source/RealTimeSync/xml_proc.h b/FreeFileSync/Source/RealTimeSync/xml_proc.h
index 305ca76d..ebad3c7e 100755
--- a/FreeFileSync/Source/RealTimeSync/xml_proc.h
+++ b/FreeFileSync/Source/RealTimeSync/xml_proc.h
@@ -8,7 +8,6 @@
#define XML_PROC_H_0813748158321813490
#include <vector>
-#include <zen/xml_io.h>
#include <zen/zstring.h>
#include <wx/language.h>
diff --git a/FreeFileSync/Source/base/algorithm.cpp b/FreeFileSync/Source/base/algorithm.cpp
index 044349b3..ff1bc787 100755
--- a/FreeFileSync/Source/base/algorithm.cpp
+++ b/FreeFileSync/Source/base/algorithm.cpp
@@ -192,13 +192,6 @@ bool fff::allElementsEqual(const FolderComparison& folderCmp)
namespace
{
template <SelectedSide side> inline
-const InSyncDescrFile& getDescriptor(const InSyncFile& dbFile) { return dbFile.left; }
-
-template <> inline
-const InSyncDescrFile& getDescriptor<RIGHT_SIDE>(const InSyncFile& dbFile) { return dbFile.right; }
-
-
-template <SelectedSide side> inline
bool matchesDbEntry(const FilePair& file, const InSyncFolder::FileList::value_type* dbFile, const std::vector<unsigned int>& ignoreTimeShiftMinutes)
{
if (file.isEmpty<side>())
@@ -206,10 +199,10 @@ bool matchesDbEntry(const FilePair& file, const InSyncFolder::FileList::value_ty
else if (!dbFile)
return false;
- const Zstring& shortNameDb = dbFile->first;
- const InSyncDescrFile& descrDb = getDescriptor<side>(dbFile->second);
+ const Zstring& fileNameDb = dbFile->first;
+ const InSyncDescrFile& descrDb = SelectParam<side>::ref(dbFile->second.left, dbFile->second.right);
- return file.getItemName<side>() == shortNameDb && //detect changes in case (windows)
+ return getUnicodeNormalForm(file.getItemName<side>()) == getUnicodeNormalForm(fileNameDb) && //detect changes in case (but ignore Unicode normal forms)
//respect 2 second FAT/FAT32 precision! copying a file to a FAT32 drive changes it's modification date by up to 2 seconds
//we're not interested in "fileTimeTolerance" here!
sameFileTime(file.getLastWriteTime<side>(), descrDb.modTime, 2, ignoreTimeShiftMinutes) &&
@@ -244,13 +237,6 @@ bool stillInSync(const InSyncFile& dbFile, CompareVariant compareVar, int fileTi
//--------------------------------------------------------------------
-template <SelectedSide side> inline
-const InSyncDescrLink& getDescriptor(const InSyncSymlink& dbLink) { return dbLink.left; }
-
-template <> inline
-const InSyncDescrLink& getDescriptor<RIGHT_SIDE>(const InSyncSymlink& dbLink) { return dbLink.right; }
-
-
//check whether database entry and current item match: *irrespective* of current comparison settings
template <SelectedSide side> inline
bool matchesDbEntry(const SymlinkPair& symlink, const InSyncFolder::SymlinkList::value_type* dbSymlink, const std::vector<unsigned int>& ignoreTimeShiftMinutes)
@@ -260,10 +246,10 @@ bool matchesDbEntry(const SymlinkPair& symlink, const InSyncFolder::SymlinkList:
else if (!dbSymlink)
return false;
- const Zstring& shortNameDb = dbSymlink->first;
- const InSyncDescrLink& descrDb = getDescriptor<side>(dbSymlink->second);
+ const Zstring& linkNameDb = dbSymlink->first;
+ const InSyncDescrLink& descrDb = SelectParam<side>::ref(dbSymlink->second.left, dbSymlink->second.right);
- return symlink.getItemName<side>() == shortNameDb &&
+ return getUnicodeNormalForm(symlink.getItemName<side>()) == getUnicodeNormalForm(linkNameDb) &&
//respect 2 second FAT/FAT32 precision! copying a file to a FAT32 drive changes its modification date by up to 2 seconds
sameFileTime(symlink.getLastWriteTime<side>(), descrDb.modTime, 2, ignoreTimeShiftMinutes);
}
@@ -297,14 +283,14 @@ bool stillInSync(const InSyncSymlink& dbLink, CompareVariant compareVar, int fil
template <SelectedSide side> inline
bool matchesDbEntry(const FolderPair& folder, const InSyncFolder::FolderList::value_type* dbFolder)
{
- if (folder.isEmpty<side>())
- return !dbFolder || dbFolder->second.status == InSyncFolder::DIR_STATUS_STRAW_MAN;
- else if (!dbFolder || dbFolder->second.status == InSyncFolder::DIR_STATUS_STRAW_MAN)
+ if (!dbFolder || dbFolder->second.status == InSyncFolder::DIR_STATUS_STRAW_MAN)
+ return folder.isEmpty<side>();
+ else if (folder.isEmpty<side>())
return false;
- const Zstring& shortNameDb = dbFolder->first;
+ const Zstring& folderNameDb = dbFolder->first;
- return folder.getItemName<side>() == shortNameDb;
+ return getUnicodeNormalForm(folder.getItemName<side>()) == getUnicodeNormalForm(folderNameDb);
}
@@ -344,7 +330,7 @@ private:
{
if (dbFolder)
{
- auto it = dbFolder->files.find(file.getPairItemName());
+ auto it = dbFolder->files.find(file.getItemNameAny());
if (it != dbFolder->files.end())
return &it->second;
}
@@ -382,7 +368,7 @@ private:
const InSyncFolder* dbSubFolder = nullptr; //try to find corresponding database entry
if (dbFolder)
{
- auto it = dbFolder->folders.find(folder.getPairItemName());
+ auto it = dbFolder->folders.find(folder.getItemNameAny());
if (it != dbFolder->folders.end())
dbSubFolder = &it->second;
}
@@ -393,18 +379,18 @@ private:
void detectMovePairs(const InSyncFolder& container) const
{
- for (auto& dbFile : container.files)
- findAndSetMovePair(dbFile.second);
+ for (const auto& [fileName, dbAttrib] : container.files)
+ findAndSetMovePair(dbAttrib);
- for (auto& dbFolder : container.folders)
- detectMovePairs(dbFolder.second);
+ for (const auto& [folderName, subFolder] : container.folders)
+ detectMovePairs(subFolder);
}
template <SelectedSide side>
static bool sameSizeAndDate(const FilePair& file, const InSyncFile& dbFile)
{
return file.getFileSize<side>() == dbFile.fileSize &&
- sameFileTime(file.getLastWriteTime<side>(), getDescriptor<side>(dbFile).modTime, 2, {});
+ sameFileTime(file.getLastWriteTime<side>(), SelectParam<side>::ref(dbFile.left, dbFile.right).modTime, 2, {});
//- respect 2 second FAT/FAT32 precision! not user-configurable!
//- "ignoreTimeShiftMinutes" may lead to false positive move detections => let's be conservative and not allow it
// (time shift is only ever required during FAT DST switches)
@@ -427,7 +413,7 @@ private:
//- note: exOneSideById isn't filled in this case, see recurse()
}
- const AFS::FileId fileId = getDescriptor<side>(dbFile).fileId;
+ const AFS::FileId fileId = SelectParam<side>::ref(dbFile.left, dbFile.right).fileId;
if (!fileId.empty())
{
auto it = exOneSideById.find(fileId);
@@ -484,7 +470,7 @@ private:
FAT caveat: File Ids are generally not stable when file is either moved or renamed!
=> 1. Move/rename operations on FAT cannot be detected reliably.
- => 2. database generally contains wrong file ID on FAT after renaming from .ffs_tmp files => correct file Ids in database only after next sync
+ => 2. database generally contains wrong file ID on FAT after renaming from .ffs_tmp files => correct file IDs in database only after next sync
=> 3. even exFAT screws up (but less than FAT) and changes IDs after file move. Did they learn nothing from the past?
*/
};
@@ -535,7 +521,7 @@ private:
const InSyncFolder::FileList::value_type* dbEntry = nullptr;
if (dbFolder)
{
- auto it = dbFolder->files.find(file.getPairItemName());
+ auto it = dbFolder->files.find(file.getItemNameAny());
if (it != dbFolder->files.end())
dbEntry = &*it;
}
@@ -571,7 +557,7 @@ private:
const InSyncFolder::SymlinkList::value_type* dbEntry = nullptr;
if (dbFolder)
{
- auto it = dbFolder->symlinks.find(symlink.getPairItemName());
+ auto it = dbFolder->symlinks.find(symlink.getItemNameAny());
if (it != dbFolder->symlinks.end())
dbEntry = &*it;
}
@@ -612,7 +598,7 @@ private:
const InSyncFolder::FolderList::value_type* dbEntry = nullptr;
if (dbFolder)
{
- auto it = dbFolder->folders.find(folder.getPairItemName());
+ auto it = dbFolder->folders.find(folder.getItemNameAny());
if (it != dbFolder->folders.end())
dbEntry = &*it;
}
@@ -890,19 +876,19 @@ private:
void processFile(FilePair& file) const
{
if (Eval<strategy>::process(file))
- file.setActive(filterProc.passFileFilter(file.getPairRelativePath()));
+ file.setActive(filterProc.passFileFilter(file.getRelativePathAny()));
}
void processLink(SymlinkPair& symlink) const
{
if (Eval<strategy>::process(symlink))
- symlink.setActive(filterProc.passFileFilter(symlink.getPairRelativePath()));
+ symlink.setActive(filterProc.passFileFilter(symlink.getRelativePathAny()));
}
void processDir(FolderPair& folder) const
{
bool childItemMightMatch = true;
- const bool filterPassed = filterProc.passDirFilter(folder.getPairRelativePath(), &childItemMightMatch);
+ const bool filterPassed = filterProc.passDirFilter(folder.getRelativePathAny(), &childItemMightMatch);
if (Eval<strategy>::process(folder))
folder.setActive(filterPassed);
@@ -1138,7 +1124,7 @@ std::optional<PathDependency> fff::getPathDependency(const AbstractPath& basePat
const auto& relPathP = leftParent ? relPathL : relPathR;
const auto& relPathC = leftParent ? relPathR : relPathL;
- if (std::equal(relPathP.begin(), relPathP.end(), relPathC.begin(), [](const Zstring& lhs, const Zstring& rhs) { return equalFilePath(lhs, rhs); }))
+ if (std::equal(relPathP.begin(), relPathP.end(), relPathC.begin(), [](const Zstring& lhs, const Zstring& rhs) { return equalNoCase(lhs, rhs); }))
{
Zstring relDirPath;
std::for_each(relPathC.begin() + relPathP.size(), relPathC.end(), [&](const Zstring& itemName)
@@ -1591,9 +1577,9 @@ void fff::deleteFromGridAndHD(const std::vector<FileSystemObject*>& rowsToDelete
{
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& item : recyclerSupported)
- if (!item.second)
- msg += L"\n" + AFS::getDisplayPath(item.first);
+ for (const auto& [folderPath, supported] : recyclerSupported)
+ if (!supported)
+ 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 7e073945..a8facb9d 100755
--- a/FreeFileSync/Source/base/algorithm.h
+++ b/FreeFileSync/Source/base/algorithm.h
@@ -81,7 +81,7 @@ struct FileDescriptor
};
bool operator<(const FileDescriptor& lhs, const FileDescriptor& rhs);
-//get native Win32 paths or create temporary copy for SFTP/MTP, ect.
+//get native Win32 paths or create temporary copy for SFTP/MTP, etc.
class TempFileBuffer
{
public:
diff --git a/FreeFileSync/Source/base/application.cpp b/FreeFileSync/Source/base/application.cpp
index a160ff06..4707fd98 100755
--- a/FreeFileSync/Source/base/application.cpp
+++ b/FreeFileSync/Source/base/application.cpp
@@ -18,16 +18,14 @@
#include "synchronization.h"
#include "help_provider.h"
#include "process_xml.h"
-#include "error_log.h"
+#include "fatal_error.h"
#include "resolve_path.h"
#include "generate_logfile.h"
#include "../ui/batch_status_handler.h"
#include "../ui/main_dlg.h"
-//#include "../fs/concrete.h"
#include <gtk/gtk.h>
-
using namespace zen;
using namespace fff;
@@ -181,27 +179,27 @@ 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 strEqual(argTmp, Zstr("help"), CmpAsciiNoCase()) ||
- strEqual(argTmp, Zstr("h"), CmpAsciiNoCase()) ||
+ return equalAsciiNoCase(argTmp, Zstr("help")) ||
+ equalAsciiNoCase(argTmp, Zstr("h")) ||
argTmp == Zstr("?");
};
auto isCommandLineOption = [&](const Zstring& arg)
{
- return strEqual(arg, optionEdit, CmpAsciiNoCase()) ||
- strEqual(arg, optionLeftDir, CmpAsciiNoCase()) ||
- strEqual(arg, optionRightDir, CmpAsciiNoCase()) ||
- strEqual(arg, optionDirPair, CmpAsciiNoCase()) ||
- strEqual(arg, optionSendTo, CmpAsciiNoCase()) ||
+ return equalAsciiNoCase(arg, optionEdit ) ||
+ equalAsciiNoCase(arg, optionLeftDir ) ||
+ equalAsciiNoCase(arg, optionRightDir) ||
+ equalAsciiNoCase(arg, optionDirPair ) ||
+ equalAsciiNoCase(arg, optionSendTo ) ||
syntaxHelpRequested(arg);
};
for (auto it = commandArgs.begin(); it != commandArgs.end(); ++it)
if (syntaxHelpRequested(*it))
return showSyntaxHelp();
- else if (strEqual(*it, optionEdit, CmpAsciiNoCase()))
+ else if (equalAsciiNoCase(*it, optionEdit))
openForEdit = true;
- else if (strEqual(*it, optionLeftDir, CmpAsciiNoCase()))
+ else if (equalAsciiNoCase(*it, optionLeftDir))
{
if (++it == commandArgs.end() || isCommandLineOption(*it))
{
@@ -210,7 +208,7 @@ void Application::launch(const std::vector<Zstring>& commandArgs)
}
dirPathPhrasesLeft.push_back(*it);
}
- else if (strEqual(*it, optionRightDir, CmpAsciiNoCase()))
+ else if (equalAsciiNoCase(*it, optionRightDir))
{
if (++it == commandArgs.end() || isCommandLineOption(*it))
{
@@ -219,7 +217,7 @@ void Application::launch(const std::vector<Zstring>& commandArgs)
}
dirPathPhrasesRight.push_back(*it);
}
- else if (strEqual(*it, optionDirPair, CmpAsciiNoCase()))
+ else if (equalAsciiNoCase(*it, optionDirPair))
{
if (++it == commandArgs.end() || isCommandLineOption(*it))
{
@@ -235,7 +233,7 @@ void Application::launch(const std::vector<Zstring>& commandArgs)
}
dirPathPhrasePairs.back().second = *it;
}
- else if (strEqual(*it, optionSendTo, CmpAsciiNoCase()))
+ else if (equalAsciiNoCase(*it, optionSendTo))
{
for (size_t i = 0; ; ++i)
{
@@ -245,7 +243,7 @@ void Application::launch(const std::vector<Zstring>& commandArgs)
break;
}
- if (i < 2) //-SendTo with more than 2 paths? Doesn't make any sense, does it!?
+ if (i < 2) //else: -SendTo with more than 2 paths? Doesn't make any sense, does it!?
{
//for -SendTo we expect a list of full native paths, not "phrases" that need to be resolved!
auto getFolderPath = [](Zstring itemPath)
@@ -266,7 +264,7 @@ void Application::launch(const std::vector<Zstring>& commandArgs)
else
{
const Zstring folderPath = getFolderPath(*it);
- if (!equalFilePath(dirPathPhrasePairs.back().first, folderPath)) //user accidentally sending to two files, which each time yield the same parent folder
+ if (dirPathPhrasePairs.back().first != folderPath) //else: user accidentally sending to two files, which each time yield the same parent folder
dirPathPhrasePairs.back().second = folderPath;
}
}
@@ -439,15 +437,15 @@ void Application::launch(const std::vector<Zstring>& commandArgs)
return;
}
- std::vector<Zstring> filepaths;
- for (const auto& item : configFiles)
- filepaths.push_back(item.first);
+ std::vector<Zstring> filePaths;
+ for (const auto& [filePath, xmlType] : configFiles)
+ filePaths.push_back(filePath);
XmlGuiConfig guiCfg; //structure to receive gui settings with default values
try
{
std::wstring warningMsg;
- readAnyConfig(filepaths, guiCfg, warningMsg); //throw FileError
+ readAnyConfig(filePaths, guiCfg, warningMsg); //throw FileError
if (!warningMsg.empty())
showNotificationDialog(nullptr, DialogInfoType::WARNING, PopupDialogCfg().setDetailInstructions(warningMsg));
@@ -458,7 +456,7 @@ void Application::launch(const std::vector<Zstring>& commandArgs)
notifyFatalError(e.toString(), _("Error"));
return;
}
- runGuiMode(globalConfigFilePath, guiCfg, filepaths, !openForEdit /*startComparison*/);
+ runGuiMode(globalConfigFilePath, guiCfg, filePaths, !openForEdit /*startComparison*/);
}
}
@@ -580,7 +578,6 @@ void runBatchMode(const Zstring& globalConfigFilePath, const XmlBatchConfig& bat
globalCfg.fileTimeTolerance,
showPopupAllowed, //allowUserInteraction
globalCfg.runWithBackgroundPriority,
- globalCfg.folderAccessTimeout,
globalCfg.createLockFile,
dirLocks,
extractCompareCfg(batchCfg.mainCfg),
@@ -593,7 +590,6 @@ void runBatchMode(const Zstring& globalConfigFilePath, const XmlBatchConfig& bat
globalCfg.copyFilePermissions,
globalCfg.failSafeFileCopy,
globalCfg.runWithBackgroundPriority,
- globalCfg.folderAccessTimeout,
extractSyncCfg(batchCfg.mainCfg),
cmpResult,
deviceParallelOps,
@@ -609,7 +605,7 @@ void runBatchMode(const Zstring& globalConfigFilePath, const XmlBatchConfig& bat
//update last sync stats for the selected cfg file
for (ConfigFileItem& cfi : globalCfg.gui.mainDlg.cfgFileHistory)
- if (equalFilePath(cfi.cfgFilePath, cfgFilePath))
+ if (equalLocalPath(cfi.cfgFilePath, cfgFilePath))
{
if (r.finalStatus != SyncResult::ABORTED)
cfi.lastSyncTime = std::chrono::system_clock::to_time_t(syncStartTime);
diff --git a/FreeFileSync/Source/base/comparison.cpp b/FreeFileSync/Source/base/comparison.cpp
index 84b392cf..ce601ac8 100755
--- a/FreeFileSync/Source/base/comparison.cpp
+++ b/FreeFileSync/Source/base/comparison.cpp
@@ -69,7 +69,6 @@ struct ResolvedBaseFolders
ResolvedBaseFolders initializeBaseFolders(const std::vector<FolderPairCfg>& fpCfgList, const std::map<AbstractPath, size_t>& deviceParallelOps,
- std::chrono::seconds folderAccessTimeout,
bool allowUserInteraction,
bool& warnFolderNotExisting,
ProcessCallback& callback /*throw X*/)
@@ -95,19 +94,19 @@ ResolvedBaseFolders initializeBaseFolders(const std::vector<FolderPairCfg>& fpCf
}
const FolderStatus status = getFolderStatusNonBlocking(uniqueBaseFolders, deviceParallelOps, //re-check *all* directories on each try!
- folderAccessTimeout, allowUserInteraction, callback); //throw X
+ allowUserInteraction, callback); //throw X
output.existingBaseFolders = status.existing;
notExisting = status.notExisting;
if (!status.failedChecks.empty())
{
std::wstring msg = _("Cannot find the following folders:") + L"\n";
- for (const auto& fc : status.failedChecks)
- msg += L"\n" + AFS::getDisplayPath(fc.first);
+ for (const auto& [folderPath, error] : status.failedChecks)
+ msg += L"\n" + AFS::getDisplayPath(folderPath);
msg += L"\n___________________________________________";
- for (const auto& fc : status.failedChecks)
- msg += L"\n\n" + replaceCpy(fc.second.toString(), L"\n\n", L"\n");
+ for (const auto& [folderPath, error] : status.failedChecks)
+ msg += L"\n\n" + replaceCpy(error.toString(), L"\n\n", L"\n");
throw FileError(msg);
}
@@ -262,7 +261,8 @@ void categorizeSymlinkByTime(SymlinkPair& symlink)
//1. SYMLINK_EQUAL may only be set if short names match in case: InSyncFolder's mapping tables use short name as a key! see db_file.cpp
//2. harmonize with "bool stillInSync()" in algorithm.cpp
- if (symlink.getItemName<LEFT_SIDE>() == symlink.getItemName<RIGHT_SIDE>())
+ if (getUnicodeNormalForm(symlink.getItemName<LEFT_SIDE >()) ==
+ getUnicodeNormalForm(symlink.getItemName<RIGHT_SIDE>()))
symlink.setCategory<FILE_EQUAL>();
else
symlink.setCategoryDiffMetadata(getDescrDiffMetaShortnameCase(symlink));
@@ -311,7 +311,8 @@ std::shared_ptr<BaseFolderPair> ComparisonBuffer::compareByTimeSize(const Resolv
//3. harmonize with "bool stillInSync()" in algorithm.cpp, FilePair::setSyncedTo() in file_hierarchy.h
if (file->getFileSize<LEFT_SIDE>() == file->getFileSize<RIGHT_SIDE>())
{
- if (file->getItemName<LEFT_SIDE>() == file->getItemName<RIGHT_SIDE>())
+ if (getUnicodeNormalForm(file->getItemName<LEFT_SIDE >()) ==
+ getUnicodeNormalForm(file->getItemName<RIGHT_SIDE>()))
file->setCategory<FILE_EQUAL>();
else
file->setCategoryDiffMetadata(getDescrDiffMetaShortnameCase(*file));
@@ -368,7 +369,8 @@ void categorizeSymlinkByContent(SymlinkPair& symlink, ProcessCallback& callback)
//2. harmonize with "bool stillInSync()" in algorithm.cpp, FilePair::setSyncedTo() in file_hierarchy.h
//symlinks have same "content"
- if (symlink.getItemName<LEFT_SIDE>() != symlink.getItemName<RIGHT_SIDE>())
+ if (getUnicodeNormalForm(symlink.getItemName<LEFT_SIDE >()) !=
+ getUnicodeNormalForm(symlink.getItemName<RIGHT_SIDE>()))
symlink.setCategoryDiffMetadata(getDescrDiffMetaShortnameCase(symlink));
//else if (!sameFileTime(symlink.getLastWriteTime<LEFT_SIDE>(),
// symlink.getLastWriteTime<RIGHT_SIDE>(), symlink.base().getFileTimeTolerance(), symlink.base().getIgnoredTimeShift()))
@@ -404,7 +406,8 @@ std::shared_ptr<BaseFolderPair> ComparisonBuffer::compareBySize(const ResolvedFo
//3. harmonize with "bool stillInSync()" in algorithm.cpp, FilePair::setSyncedTo() in file_hierarchy.h
if (file->getFileSize<LEFT_SIDE>() == file->getFileSize<RIGHT_SIDE>())
{
- if (file->getItemName<LEFT_SIDE>() == file->getItemName<RIGHT_SIDE>())
+ if (getUnicodeNormalForm(file->getItemName< LEFT_SIDE>()) ==
+ getUnicodeNormalForm(file->getItemName<RIGHT_SIDE>()))
file->setCategory<FILE_EQUAL>();
else
file->setCategoryDiffMetadata(getDescrDiffMetaShortnameCase(*file));
@@ -433,7 +436,7 @@ namespace
{
void categorizeFileByContent(FilePair& file, const std::wstring& txtComparingContentOfFiles, AsyncCallback& acb, std::mutex& singleThread) //throw ThreadInterruption
{
- acb.reportStatus(replaceCpy(txtComparingContentOfFiles, L"%x", fmtPath(file.getPairRelativePath()))); //throw ThreadInterruption
+ acb.reportStatus(replaceCpy(txtComparingContentOfFiles, L"%x", fmtPath(file.getRelativePathAny()))); //throw ThreadInterruption
bool haveSameContent = false;
const std::wstring errMsg = tryReportingError([&]
@@ -462,7 +465,8 @@ void categorizeFileByContent(FilePair& file, const std::wstring& txtComparingCon
//1. FILE_EQUAL may only be set if short names match in case: InSyncFolder's mapping tables use short name as a key! see db_file.cpp
//2. FILE_EQUAL is expected to mean identical file sizes! See InSyncFile
//3. harmonize with "bool stillInSync()" in algorithm.cpp, FilePair::setSyncedTo() in file_hierarchy.h
- if (file.getItemName<LEFT_SIDE>() != file.getItemName<RIGHT_SIDE>())
+ if (getUnicodeNormalForm(file.getItemName< LEFT_SIDE>()) !=
+ getUnicodeNormalForm(file.getItemName<RIGHT_SIDE>()))
file.setCategoryDiffMetadata(getDescrDiffMetaShortnameCase(file));
#if 0 //don't synchronize modtime only see FolderPairSyncer::synchronizeFileInt(), SO_COPY_METADATA_TO_*
else if (!sameFileTime(file.getLastWriteTime<LEFT_SIDE>(),
@@ -520,12 +524,12 @@ std::list<std::shared_ptr<BaseFolderPair>> ComparisonBuffer::compareByContent(co
const Zstringw txtConflictSkippedBinaryComparison = getConflictSkippedBinaryComparison(); //avoid premature pess.: save memory via ref-counted string
- for (const auto& w : workLoad)
+ for (const auto& [folderPair, fpCfg] : workLoad)
{
std::vector<FilePair*> undefinedFiles;
std::vector<SymlinkPair*> uncategorizedLinks;
//run basis scan and retrieve candidates for binary comparison (files existing on both sides)
- output.push_back(performComparison(w.first, w.second, undefinedFiles, uncategorizedLinks));
+ output.push_back(performComparison(folderPair, fpCfg, undefinedFiles, uncategorizedLinks));
RingBuffer<FilePair*> filesToCompareBytewise;
//content comparison of file content happens AFTER finding corresponding files and AFTER filtering
@@ -638,19 +642,19 @@ std::list<std::shared_ptr<BaseFolderPair>> ComparisonBuffer::compareByContent(co
class MergeSides
{
public:
- MergeSides(const std::map<Zstring, Zstringw, LessFilePath>& failedItemReads,
+ MergeSides(const std::map<ZstringNoCase, Zstringw>& errorsByRelPath,
std::vector<FilePair*>& undefinedFilesOut,
std::vector<SymlinkPair*>& undefinedSymlinksOut) :
- failedItemReads_(failedItemReads),
+ errorsByRelPath_(errorsByRelPath),
undefinedFiles_(undefinedFilesOut),
undefinedSymlinks_(undefinedSymlinksOut) {}
void execute(const FolderContainer& lhs, const FolderContainer& rhs, ContainerObject& output)
{
- auto it = failedItemReads_.find(Zstring()); //empty path if read-error for whole base directory
+ auto it = errorsByRelPath_.find(Zstring()); //empty path if read-error for whole base directory
mergeTwoSides(lhs, rhs,
- it != failedItemReads_.end() ? &it->second : nullptr,
+ it != errorsByRelPath_.end() ? &it->second : nullptr,
output);
}
@@ -662,8 +666,8 @@ private:
const Zstringw* checkFailedRead(FileSystemObject& fsObj, const Zstringw* errorMsg);
- const std::map<Zstring, Zstringw, LessFilePath>& failedItemReads_; //base-relative paths or empty if read-error for whole base directory
- std::vector<FilePair*>& undefinedFiles_;
+ const std::map<ZstringNoCase, Zstringw>& errorsByRelPath_; //base-relative paths or empty if read-error for whole base directory
+ std::vector<FilePair*>& undefinedFiles_;
std::vector<SymlinkPair*>& undefinedSymlinks_;
};
@@ -673,8 +677,8 @@ const Zstringw* MergeSides::checkFailedRead(FileSystemObject& fsObj, const Zstri
{
if (!errorMsg)
{
- auto it = failedItemReads_.find(fsObj.getPairRelativePath());
- if (it != failedItemReads_.end())
+ auto it = errorsByRelPath_.find(fsObj.getRelativePathAny());
+ if (it != errorsByRelPath_.end())
errorMsg = &it->second;
}
@@ -691,65 +695,106 @@ const Zstringw* MergeSides::checkFailedRead(FileSystemObject& fsObj, const Zstri
template <SelectedSide side>
void MergeSides::fillOneSide(const FolderContainer& folderCont, const Zstringw* errorMsg, ContainerObject& output)
{
- for (const auto& file : folderCont.files)
+ for (const auto& [fileName, attrib] : folderCont.files)
{
- FilePair& newItem = output.addSubFile<side>(file.first, file.second);
+ FilePair& newItem = output.addSubFile<side>(fileName, attrib);
checkFailedRead(newItem, errorMsg);
}
- for (const auto& symlink : folderCont.symlinks)
+ for (const auto& [linkName, attrib] : folderCont.symlinks)
{
- SymlinkPair& newItem = output.addSubLink<side>(symlink.first, symlink.second);
+ SymlinkPair& newItem = output.addSubLink<side>(linkName, attrib);
checkFailedRead(newItem, errorMsg);
}
- for (const auto& dir : folderCont.folders)
+ for (const auto& [folderName, attrAndSub] : folderCont.folders)
{
- FolderPair& newFolder = output.addSubFolder<side>(dir.first, dir.second.first);
+ FolderPair& newFolder = output.addSubFolder<side>(folderName, attrAndSub.first);
const Zstringw* errorMsgNew = checkFailedRead(newFolder, errorMsg);
- fillOneSide<side>(dir.second.second, errorMsgNew, newFolder); //recurse
+ fillOneSide<side>(attrAndSub.second, errorMsgNew, newFolder); //recurse
}
}
-//perf: 70% faster than traversing over left and right containers + more natural default sequence
-//- 2 x lessKey vs 1 x cmpFilePath() => no significant difference
-//- simplify loop by placing the eob check at the beginning => slightly slower
template <class MapType, class ProcessLeftOnly, class ProcessRightOnly, class ProcessBoth> inline
-void linearMerge(const MapType& mapLeft, const MapType& mapRight, ProcessLeftOnly lo, ProcessRightOnly ro, ProcessBoth bo)
+void matchFolders(const MapType& mapLeft, const MapType& mapRight, ProcessLeftOnly lo, ProcessRightOnly ro, ProcessBoth bo)
{
- auto itL = mapLeft .begin();
- auto itR = mapRight.begin();
+ struct FileRef
+ {
+ Zstring normalName; //buffer expensive makeUpperCopy() calls!!
+ const typename MapType::value_type* ref;
+ bool leftSide;
+ };
+ std::vector<FileRef> fileList;
+ fileList.reserve(mapLeft.size() + mapRight.size()); //perf: ~5% shorter runtime
- auto finishLeft = [&] { std::for_each(itL, mapLeft .end(), lo); };
- auto finishRight = [&] { std::for_each(itR, mapRight.end(), ro); };
+ for (const auto& item : mapLeft ) fileList.push_back({ makeUpperCopy(item.first), &item, true });
+ for (const auto& item : mapRight) fileList.push_back({ makeUpperCopy(item.first), &item, false });
- if (itL == mapLeft .end()) return finishRight();
- if (itR == mapRight.end()) return finishLeft ();
+ std::sort(fileList.begin(), fileList.end(), [&](const FileRef& lhs, const FileRef& rhs)
+ {
+ int rv = compareString(lhs.normalName, rhs.normalName);
+ if (rv != 0)
+ return rv < 0; //primary sort key: ignore unicode normal form and case
- const auto lessKey = typename MapType::key_compare();
+ //perf: sorting by secondary/tertiary key here costs about 7% additional runtime
+ rv = compareString(getUnicodeNormalForm(lhs.ref->first), getUnicodeNormalForm(rhs.ref->first));
+ if (rv != 0)
+ return rv < 0; //secondary sort key: ignore unicode normal
- for (;;)
- if (lessKey(itL->first, itR->first))
- {
- lo(*itL);
- if (++itL == mapLeft.end())
- return finishRight();
- }
- else if (lessKey(itR->first, itL->first))
- {
- ro(*itR);
- if (++itR == mapRight.end())
- return finishLeft();
- }
- else
+ return lhs.ref->first < rhs.ref->first; //tertiary sort key: raw string value
+ });
+ //bonus: natural default sequence on file guid UI
+
+ auto tryMatchRange = [&](auto it, auto itLast)
+ {
+ const size_t equalCountL = std::count_if(it, itLast, [](const FileRef& fr) { return fr.leftSide; });
+ const size_t equalCountR = itLast - it - equalCountL;
+
+ if (equalCountL == 1 && equalCountR == 1) //we have a match
{
- bo(*itL, *itR);
- ++itL; //
- ++itR; //increment BOTH before checking for end of range!
- if (itL == mapLeft .end()) return finishRight();
- if (itR == mapRight.end()) return finishLeft ();
+ if (it->leftSide)
+ bo(*it[0].ref, *it[1].ref);
+ else
+ bo(*it[1].ref, *it[0].ref);
}
+ else if (equalCountL == 0) //no match
+ std::for_each(it, itLast, [&](const FileRef& fr) { ro(*fr.ref); });
+ else if (equalCountR == 0) //no match
+ std::for_each(it, itLast, [&](const FileRef& fr) { lo(*fr.ref); });
+ else //ambiguous
+ return false;
+ return true;
+ };
+
+ for (auto it = fileList.begin(); it != fileList.end();)
+ {
+ //find equal range: ignore case, ignore Unicode normalization
+ auto itEndEq = std::find_if(it + 1, fileList.end(), [&](const FileRef& fr) { return fr.normalName != it->normalName; });
+ if (!tryMatchRange(it, itEndEq))
+ for (auto itCase = it; itCase != itEndEq;)
+ {
+ //find equal range: respect case, ignore Unicode normalization
+ auto itEndCase = std::find_if(itCase + 1, itEndEq, [&](const FileRef& fr) { return getUnicodeNormalForm(fr.ref->first) != getUnicodeNormalForm(itCase->ref->first); });
+ if (!tryMatchRange(itCase, itEndCase))
+ for (auto itRaw = itCase; itRaw != itEndCase;)
+ {
+ //find equal range: respect case, respect Unicode normalization
+ auto itEndRaw = std::find_if(itRaw + 1, itEndCase, [&](const FileRef& fr) { return fr.ref->first != itRaw->ref->first; });
+ if (!tryMatchRange(itRaw, itEndRaw))
+ std::for_each(itRaw, itEndRaw, [&](const FileRef& fr)
+ {
+ if (fr.leftSide)
+ lo(*fr.ref);
+ else
+ ro(*fr.ref);
+ });
+ itRaw = itEndRaw;
+ }
+ itCase = itEndCase;
+ }
+ it = itEndEq;
+ }
}
@@ -757,7 +802,7 @@ void MergeSides::mergeTwoSides(const FolderContainer& lhs, const FolderContainer
{
using FileData = FolderContainer::FileList::value_type;
- linearMerge(lhs.files, rhs.files,
+ matchFolders(lhs.files, rhs.files,
[&](const FileData& fileLeft ) { FilePair& newItem = output.addSubFile< LEFT_SIDE>(fileLeft .first, fileLeft .second); checkFailedRead(newItem, errorMsg); }, //left only
[&](const FileData& fileRight) { FilePair& newItem = output.addSubFile<RIGHT_SIDE>(fileRight.first, fileRight.second); checkFailedRead(newItem, errorMsg); }, //right only
@@ -776,7 +821,7 @@ void MergeSides::mergeTwoSides(const FolderContainer& lhs, const FolderContainer
//-----------------------------------------------------------------------------------------------
using SymlinkData = FolderContainer::SymlinkList::value_type;
- linearMerge(lhs.symlinks, rhs.symlinks,
+ matchFolders(lhs.symlinks, rhs.symlinks,
[&](const SymlinkData& symlinkLeft ) { SymlinkPair& newItem = output.addSubLink< LEFT_SIDE>(symlinkLeft .first, symlinkLeft .second); checkFailedRead(newItem, errorMsg); }, //left only
[&](const SymlinkData& symlinkRight) { SymlinkPair& newItem = output.addSubLink<RIGHT_SIDE>(symlinkRight.first, symlinkRight.second); checkFailedRead(newItem, errorMsg); }, //right only
@@ -794,8 +839,8 @@ void MergeSides::mergeTwoSides(const FolderContainer& lhs, const FolderContainer
//-----------------------------------------------------------------------------------------------
using FolderData = FolderContainer::FolderList::value_type;
- linearMerge(lhs.folders, rhs.folders,
- [&](const FolderData& dirLeft) //left only
+ matchFolders(lhs.folders, rhs.folders,
+ [&](const FolderData& dirLeft) //left only
{
FolderPair& newFolder = output.addSubFolder<LEFT_SIDE>(dirLeft.first, dirLeft.second.first);
const Zstringw* errorMsgNew = checkFailedRead(newFolder, errorMsg);
@@ -814,7 +859,8 @@ void MergeSides::mergeTwoSides(const FolderContainer& lhs, const FolderContainer
const Zstringw* errorMsgNew = checkFailedRead(newFolder, errorMsg);
if (!errorMsgNew)
- if (dirLeft.first != dirRight.first)
+ if (getUnicodeNormalForm(dirLeft.first) !=
+ getUnicodeNormalForm(dirRight.first))
newFolder.setCategoryDiffMetadata(getDescrDiffMetaShortnameCase(newFolder));
mergeTwoSides(dirLeft.second.second, dirRight.second.second, errorMsgNew, newFolder); //recurse
@@ -836,7 +882,7 @@ void stripExcludedDirectories(ContainerObject& hierObj, const HardFilter& filter
hierObj.refSubFolders().remove_if([&](FolderPair& folder)
{
- const bool included = filterProc.passDirFilter(folder.getPairRelativePath(), nullptr); //childItemMightMatch is false, child items were already excluded during scanning
+ const bool included = filterProc.passDirFilter(folder.getRelativePathAny(), nullptr); //childItemMightMatch is false, child items were already excluded during scanning
if (!included) //falsify only! (e.g. might already be inactive due to read error!)
folder.setActive(false);
@@ -867,15 +913,16 @@ std::shared_ptr<BaseFolderPair> ComparisonBuffer::performComparison(const Resolv
const DirectoryValue* bufValueLeft = getDirValue(fp.folderPathLeft);
const DirectoryValue* bufValueRight = getDirValue(fp.folderPathRight);
- std::map<Zstring, Zstringw, LessFilePath> failedReads; //base-relative paths or empty if read-error for whole base directory
+ std::map<ZstringNoCase, Zstringw> failedReads; //base-relative paths or empty if read-error for whole base directory
{
- auto append = [&](const std::map<Zstring, std::wstring, LessFilePath>& c)
+ auto append = [&](const std::map<Zstring, std::wstring>& c)
{
- for (const auto& item : c)
- failedReads.emplace(item.first, copyStringTo<Zstringw>(item.second));
+ for (const auto& [relPath, errorMsg] : c)
+ failedReads.emplace(relPath, copyStringTo<Zstringw>(errorMsg));
};
+
//mix failedFolderReads with failedItemReads:
- //mark directory errors already at directory-level (instead for child items only) to show on GUI! See "MergeSides"
+ //associate folder traversing errors with folder (instead of child items only) to show on GUI! See "MergeSides"
//=> minor pessimization for "excludefilterFailedRead" which needlessly excludes parent folders, too
if (bufValueLeft ) append(bufValueLeft ->failedFolderReads);
if (bufValueRight) append(bufValueRight->failedFolderReads);
@@ -888,8 +935,8 @@ std::shared_ptr<BaseFolderPair> ComparisonBuffer::performComparison(const Resolv
if (failedReads.find(Zstring()) != failedReads.end()) //empty path if read-error for whole base directory
excludefilterFailedRead += Zstr("*\n");
else
- for (const auto& item : failedReads)
- excludefilterFailedRead += item.first + Zstr("\n"); //exclude item AND (potential) child items!
+ for (const auto& [relPath, errorMsg] : failedReads)
+ excludefilterFailedRead += relPath.upperCase + Zstr("\n"); //exclude item AND (potential) child items!
std::shared_ptr<BaseFolderPair> output = std::make_shared<BaseFolderPair>(fp.folderPathLeft,
bufValueLeft != nullptr, //dir existence must be checked only once: available iff buffer entry exists!
@@ -939,9 +986,6 @@ void fff::logNonDefaultSettings(const XmlGlobalSettings& activeSettings, Process
if (activeSettings.fileTimeTolerance != defaultSettings.fileTimeTolerance)
changedSettingsMsg += L"\n " + _("File time tolerance") + L" - " + numberTo<std::wstring>(activeSettings.fileTimeTolerance);
- if (activeSettings.folderAccessTimeout != defaultSettings.folderAccessTimeout)
- changedSettingsMsg += L"\n " + _("Folder access timeout") + L" - " + numberTo<std::wstring>(activeSettings.folderAccessTimeout.count());
-
if (activeSettings.runWithBackgroundPriority != defaultSettings.runWithBackgroundPriority)
changedSettingsMsg += L"\n " + _("Run with background priority") + L" - " + (activeSettings.runWithBackgroundPriority ? _("Enabled") : _("Disabled"));
@@ -960,7 +1004,6 @@ FolderComparison fff::compare(WarningDialogs& warnings,
int fileTimeTolerance,
bool allowUserInteraction,
bool runWithBackgroundPriority,
- std::chrono::seconds folderAccessTimeout,
bool createDirLocks,
std::unique_ptr<LockHolder>& dirLocks,
const std::vector<FolderPairCfg>& fpCfgList,
@@ -999,7 +1042,7 @@ FolderComparison fff::compare(WarningDialogs& warnings,
callback.reportInfo(e.toString()); //throw X
}
- const ResolvedBaseFolders& resInfo = initializeBaseFolders(fpCfgList, deviceParallelOps, folderAccessTimeout, allowUserInteraction, warnings.warnFolderNotExisting, callback); //throw X
+ const ResolvedBaseFolders& resInfo = initializeBaseFolders(fpCfgList, deviceParallelOps, allowUserInteraction, warnings.warnFolderNotExisting, 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__));
@@ -1034,13 +1077,13 @@ FolderComparison fff::compare(WarningDialogs& warnings,
{
std::wstring msg;
- for (const auto& w : workLoad)
- if (std::optional<PathDependency> pd = getPathDependency(w.first.folderPathLeft, *w.second.filter.nameFilter,
- w.first.folderPathRight, *w.second.filter.nameFilter))
+ for (const auto& [folderPair, fpCfg] : workLoad)
+ if (std::optional<PathDependency> pd = getPathDependency(folderPair.folderPathLeft, *fpCfg.filter.nameFilter,
+ folderPair.folderPathRight, *fpCfg.filter.nameFilter))
{
msg += L"\n\n" +
- AFS::getDisplayPath(w.first.folderPathLeft) + L"\n" +
- AFS::getDisplayPath(w.first.folderPathRight);
+ 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);
}
@@ -1068,12 +1111,12 @@ FolderComparison fff::compare(WarningDialogs& warnings,
//------------------- fill directory buffer ---------------------------------------------------
std::set<DirectoryKey> foldersToRead;
- for (const auto& w : workLoad)
+ for (const auto& [folderPair, fpCfg] : workLoad)
{
- if (basefolderExisting(w.first.folderPathLeft)) //only traverse *currently existing* folders: at this point user is aware that non-ex + empty string are seen as empty folder!
- foldersToRead.emplace(DirectoryKey({ w.first.folderPathLeft, w.second.filter.nameFilter, w.second.handleSymlinks }));
- if (basefolderExisting(w.first.folderPathRight))
- foldersToRead.emplace(DirectoryKey({ w.first.folderPathRight, w.second.filter.nameFilter, w.second.handleSymlinks }));
+ if (basefolderExisting(folderPair.folderPathLeft)) //only traverse *currently existing* folders: at this point user is aware that non-ex + empty string are seen as empty folder!
+ foldersToRead.emplace(DirectoryKey({ folderPair.folderPathLeft, fpCfg.filter.nameFilter, fpCfg.handleSymlinks }));
+ if (basefolderExisting(folderPair.folderPathRight))
+ foldersToRead.emplace(DirectoryKey({ folderPair.folderPathRight, fpCfg.filter.nameFilter, fpCfg.handleSymlinks }));
}
FolderComparison output;
@@ -1087,21 +1130,21 @@ FolderComparison fff::compare(WarningDialogs& warnings,
//process binary comparison as one junk
std::vector<std::pair<ResolvedFolderPair, FolderPairCfg>> workLoadByContent;
- for (const auto& w : workLoad)
- if (w.second.compareVar == CompareVariant::CONTENT)
- workLoadByContent.push_back(w);
+ for (const auto& [folderPair, fpCfg] : workLoad)
+ if (fpCfg.compareVar == CompareVariant::CONTENT)
+ workLoadByContent.push_back({ folderPair, fpCfg });
std::list<std::shared_ptr<BaseFolderPair>> outputByContent = cmpBuff.compareByContent(workLoadByContent);
//write output in expected order
- for (const auto& w : workLoad)
- switch (w.second.compareVar)
+ for (const auto& [folderPair, fpCfg] : workLoad)
+ switch (fpCfg.compareVar)
{
case CompareVariant::TIME_SIZE:
- output.push_back(cmpBuff.compareByTimeSize(w.first, w.second));
+ output.push_back(cmpBuff.compareByTimeSize(folderPair, fpCfg));
break;
case CompareVariant::SIZE:
- output.push_back(cmpBuff.compareBySize(w.first, w.second));
+ output.push_back(cmpBuff.compareBySize(folderPair, fpCfg));
break;
case CompareVariant::CONTENT:
assert(!outputByContent.empty());
diff --git a/FreeFileSync/Source/base/comparison.h b/FreeFileSync/Source/base/comparison.h
index 4c93a3ba..b956019b 100755
--- a/FreeFileSync/Source/base/comparison.h
+++ b/FreeFileSync/Source/base/comparison.h
@@ -55,7 +55,6 @@ FolderComparison compare(WarningDialogs& warnings,
int fileTimeTolerance,
bool allowUserInteraction,
bool runWithBackgroundPriority,
- std::chrono::seconds folderAccessTimeout,
bool createDirLocks,
std::unique_ptr<LockHolder>& dirLocks, //out
const std::vector<FolderPairCfg>& fpCfgList,
diff --git a/FreeFileSync/Source/base/db_file.cpp b/FreeFileSync/Source/base/db_file.cpp
index 5945918c..2dc88402 100755
--- a/FreeFileSync/Source/base/db_file.cpp
+++ b/FreeFileSync/Source/base/db_file.cpp
@@ -182,7 +182,7 @@ public:
{
try
{
- /* Zlib: optimal level - testcase 1 million files
+ /* Zlib: optimal level - test case 1 million files
level|size [MB]|time [ms]
0 49.54 272 (uncompressed)
1 14.53 1013
@@ -552,19 +552,16 @@ private:
void recurse(const ContainerObject& hierObj, InSyncFolder& dbFolder)
{
- process(hierObj.refSubFiles (), hierObj.getPairRelativePath(), dbFolder.files);
- process(hierObj.refSubLinks (), hierObj.getPairRelativePath(), dbFolder.symlinks);
- process(hierObj.refSubFolders(), hierObj.getPairRelativePath(), dbFolder.folders);
+ process(hierObj.refSubFiles (), hierObj.getRelativePathAny(), dbFolder.files);
+ process(hierObj.refSubLinks (), hierObj.getRelativePathAny(), dbFolder.symlinks);
+ process(hierObj.refSubFolders(), hierObj.getRelativePathAny(), dbFolder.folders);
}
template <class M, class V>
static V& mapAddOrUpdate(M& map, const Zstring& key, V&& value)
{
-#if defined ZEN_LINUX && !defined __cpp_lib_map_try_emplace
- auto rv = map.emplace(key, value); //C++11 emplace will move r-value arguments => don't use std::forward!
-#else //C++17's map::try_emplace() is faster than map::emplace() if key is already existing
- auto rv = map.try_emplace(key, std::forward<V>(value)); //and does NOT MOVE r-value arguments in this case!
-#endif
+ //C++17's map::try_emplace() is faster than map::emplace() if key is already existing
+ auto rv = map.try_emplace(key, std::forward<V>(value)); //and does NOT MOVE r-value arguments unlike map::emplace()!
if (rv.second)
return rv.first->second;
@@ -582,12 +579,12 @@ private:
{
//Caveat: If FILE_EQUAL, we *implicitly* assume equal left and right short names matching case: InSyncFolder's mapping tables use short name as a key!
//This makes us silently dependent from code in algorithm.h!!!
- assert(file.getItemName<LEFT_SIDE>() == file.getItemName<RIGHT_SIDE>());
+ assert(getUnicodeNormalForm(file.getItemName<LEFT_SIDE>()) == getUnicodeNormalForm(file.getItemName<RIGHT_SIDE>()));
//this should be taken for granted:
assert(file.getFileSize<LEFT_SIDE>() == file.getFileSize<RIGHT_SIDE>());
//create or update new "in-sync" state
- InSyncFile& dbFile = mapAddOrUpdate(dbFiles, file.getPairItemName(),
+ InSyncFile& dbFile = mapAddOrUpdate(dbFiles, file.getItemNameAny(),
InSyncFile(InSyncDescrFile(file.getLastWriteTime< LEFT_SIDE>(),
file.getFileId < LEFT_SIDE>()),
InSyncDescrFile(file.getLastWriteTime<RIGHT_SIDE>(),
@@ -598,7 +595,7 @@ private:
}
else //not in sync: preserve last synchronous state
{
- auto it = dbFiles.find(file.getPairItemName());
+ auto it = dbFiles.find(file.getItemNameAny());
if (it != dbFiles.end())
toPreserve.insert(&it->second);
}
@@ -625,10 +622,10 @@ private:
{
if (symlink.getLinkCategory() == SYMLINK_EQUAL) //data in sync: write current state
{
- assert(symlink.getItemName<LEFT_SIDE>() == symlink.getItemName<RIGHT_SIDE>());
+ assert(getUnicodeNormalForm(symlink.getItemName<LEFT_SIDE>()) == getUnicodeNormalForm(symlink.getItemName<RIGHT_SIDE>()));
//create or update new "in-sync" state
- InSyncSymlink& dbSymlink = mapAddOrUpdate(dbSymlinks, symlink.getPairItemName(),
+ InSyncSymlink& dbSymlink = mapAddOrUpdate(dbSymlinks, symlink.getItemNameAny(),
InSyncSymlink(InSyncDescrLink(symlink.getLastWriteTime<LEFT_SIDE>()),
InSyncDescrLink(symlink.getLastWriteTime<RIGHT_SIDE>()),
activeCmpVar_));
@@ -636,7 +633,7 @@ private:
}
else //not in sync: preserve last synchronous state
{
- auto it = dbSymlinks.find(symlink.getPairItemName());
+ auto it = dbSymlinks.find(symlink.getItemNameAny());
if (it != dbSymlinks.end())
toPreserve.insert(&it->second);
}
@@ -663,10 +660,10 @@ private:
{
case DIR_EQUAL:
{
- assert(folder.getItemName<LEFT_SIDE>() == folder.getItemName<RIGHT_SIDE>());
+ assert(getUnicodeNormalForm(folder.getItemName<LEFT_SIDE>()) == getUnicodeNormalForm(folder.getItemName<RIGHT_SIDE>()));
//update directory entry only (shallow), but do *not touch* exising child elements!!!
- const Zstring& key = folder.getPairItemName();
+ const Zstring& key = folder.getItemNameAny();
auto insertResult = dbFolders.emplace(key, InSyncFolder(InSyncFolder::DIR_STATUS_IN_SYNC)); //get or create
auto it = insertResult.first;
@@ -684,7 +681,7 @@ private:
//Example: directories on left and right differ in case while sub-files are equal
{
//reuse last "in-sync" if available or insert strawman entry (do not try to update and thereby remove child elements!!!)
- InSyncFolder& dbFolder = dbFolders.emplace(folder.getPairItemName(), InSyncFolder(InSyncFolder::DIR_STATUS_STRAW_MAN)).first->second;
+ InSyncFolder& dbFolder = dbFolders.emplace(folder.getItemNameAny(), InSyncFolder(InSyncFolder::DIR_STATUS_STRAW_MAN)).first->second;
toPreserve.insert(&dbFolder);
recurse(folder, dbFolder); //unconditional recursion without filter check! => no problem since "childItemMightMatch" is optional!!!
}
@@ -694,7 +691,7 @@ private:
case DIR_LEFT_SIDE_ONLY:
case DIR_RIGHT_SIDE_ONLY:
{
- auto it = dbFolders.find(folder.getPairItemName());
+ auto it = dbFolders.find(folder.getItemNameAny());
if (it != dbFolders.end())
{
toPreserve.insert(&it->second);
@@ -926,11 +923,11 @@ void fff::saveLastSynchronousState(const BaseFolderPair& baseFolder, const std::
//operation finished: rename temp files -> this should work (almost) transactionally:
//if there were no write access, creation of temp files would have failed
- AFS::removeFileIfExists(dbPathLeft); //throw FileError
- AFS::renameItem(dbPathLeftTmp, dbPathLeft); //throw FileError, (ErrorDifferentVolume)
+ AFS::removeFileIfExists(dbPathLeft); //throw FileError
+ AFS::moveAndRenameItem(dbPathLeftTmp, dbPathLeft); //throw FileError, (ErrorDifferentVolume)
guardTmpL.dismiss();
- AFS::removeFileIfExists(dbPathRight); //
- AFS::renameItem(dbPathRightTmp, dbPathRight); //
+ AFS::removeFileIfExists(dbPathRight); //
+ AFS::moveAndRenameItem(dbPathRightTmp, dbPathRight); //
guardTmpR.dismiss();
}
diff --git a/FreeFileSync/Source/base/db_file.h b/FreeFileSync/Source/base/db_file.h
index 17409c2b..ebbb0c01 100755
--- a/FreeFileSync/Source/base/db_file.h
+++ b/FreeFileSync/Source/base/db_file.h
@@ -75,19 +75,19 @@ struct InSyncFolder
SymlinkList symlinks; //non-followed symlinks
//convenience
- InSyncFolder& addFolder(const Zstring& shortName, InSyncStatus st)
+ InSyncFolder& addFolder(const Zstring& folderName, InSyncStatus st)
{
- return folders.emplace(shortName, InSyncFolder(st)).first->second;
+ return folders.emplace(folderName, InSyncFolder(st)).first->second;
}
- void addFile(const Zstring& shortName, const InSyncDescrFile& dataL, const InSyncDescrFile& dataR, CompareVariant cmpVar, uint64_t fileSize)
+ void addFile(const Zstring& fileName, const InSyncDescrFile& dataL, const InSyncDescrFile& dataR, CompareVariant cmpVar, uint64_t fileSize)
{
- files.emplace(shortName, InSyncFile(dataL, dataR, cmpVar, fileSize));
+ files.emplace(fileName, InSyncFile(dataL, dataR, cmpVar, fileSize));
}
- void addSymlink(const Zstring& shortName, const InSyncDescrLink& dataL, const InSyncDescrLink& dataR, CompareVariant cmpVar)
+ void addSymlink(const Zstring& linkName, const InSyncDescrLink& dataL, const InSyncDescrLink& dataR, CompareVariant cmpVar)
{
- symlinks.emplace(shortName, InSyncSymlink(dataL, dataR, cmpVar));
+ symlinks.emplace(linkName, InSyncSymlink(dataL, dataR, cmpVar));
}
};
diff --git a/FreeFileSync/Source/base/dir_exist_async.h b/FreeFileSync/Source/base/dir_exist_async.h
index acd79a69..c445a665 100755
--- a/FreeFileSync/Source/base/dir_exist_async.h
+++ b/FreeFileSync/Source/base/dir_exist_async.h
@@ -16,6 +16,8 @@
namespace fff
{
+const int DEFAULT_FOLDER_ACCESS_TIME_OUT_SEC = 20; //consider CD-ROM insert or hard disk spin up time from sleep
+
namespace
{
//directory existence checking may hang for non-existent network drives => run asynchronously and update UI!
@@ -30,8 +32,7 @@ struct FolderStatus
};
FolderStatus getFolderStatusNonBlocking(const std::set<AbstractPath>& folderPaths, const std::map<AbstractPath, size_t>& deviceParallelOps,
- std::chrono::seconds folderAccessTimeout, bool allowUserInteraction,
- ProcessCallback& procCallback /*throw X*/)
+ bool allowUserInteraction, ProcessCallback& procCallback /*throw X*/)
{
using namespace zen;
@@ -45,20 +46,19 @@ FolderStatus getFolderStatusNonBlocking(const std::set<AbstractPath>& folderPath
std::vector<std::pair<AbstractPath, std::future<bool>>> futureInfo;
std::vector<ThreadGroup<std::packaged_task<bool()>>> perDeviceThreads;
- for (const auto& item : perDevicePaths)
+ for (const auto& [rootPath, deviceFolderPaths] : perDevicePaths)
{
- const AbstractPath& rootPath = item.first;
const size_t parallelOps = getDeviceParallelOps(deviceParallelOps, rootPath);
perDeviceThreads.emplace_back(parallelOps, "DirExist: " + utfTo<std::string>(AFS::getDisplayPath(rootPath)));
auto& threadGroup = perDeviceThreads.back();
threadGroup.detach(); //don't wait on threads hanging longer than "folderAccessTimeout"
- for (const AbstractPath& folderPath : item.second)
+ for (const AbstractPath& folderPath : deviceFolderPaths)
{
std::packaged_task<bool()> pt([folderPath, allowUserInteraction] //AbstractPath is thread-safe like an int! :)
{
- //1. login to network share, open FTP connection, ect.
+ //1. login to network share, open FTP connection, etc.
AFS::connectNetworkFolder(folderPath, allowUserInteraction); //throw FileError
//2. check dir existence
@@ -77,30 +77,31 @@ FolderStatus getFolderStatusNonBlocking(const std::set<AbstractPath>& folderPath
FolderStatus output;
- for (auto& fi : futureInfo)
+ for (auto& [folderPath, future] : futureInfo)
{
- const std::wstring& displayPathFmt = fmtPath(AFS::getDisplayPath(fi.first));
+ const std::wstring& displayPathFmt = fmtPath(AFS::getDisplayPath(folderPath));
procCallback.reportStatus(replaceCpy(_("Searching for folder %x..."), L"%x", displayPathFmt)); //throw X
- while (std::chrono::steady_clock::now() < startTime + folderAccessTimeout &&
- fi.second.wait_for(UI_UPDATE_INTERVAL / 2) != std::future_status::ready)
+ const int deviceTimeOut = AFS::geAccessTimeout(folderPath); //0 if no timeout in force
+ const auto timeoutTime = startTime + std::chrono::seconds(deviceTimeOut > 0 ? deviceTimeOut : DEFAULT_FOLDER_ACCESS_TIME_OUT_SEC);
+
+ while (std::chrono::steady_clock::now() < timeoutTime &&
+ future.wait_for(UI_UPDATE_INTERVAL / 2) != std::future_status::ready)
procCallback.requestUiRefresh(); //throw X
- if (isReady(fi.second))
- {
+ if (!isReady(future))
+ output.failedChecks.emplace(folderPath, FileError(replaceCpy(_("Timeout while searching for folder %x."), L"%x", displayPathFmt)));
+ else
try
{
//call future::get() only *once*! otherwise: undefined behavior!
- if (fi.second.get()) //throw FileError
- output.existing.insert(fi.first);
+ if (future.get()) //throw FileError
+ output.existing.insert(folderPath);
else
- output.notExisting.insert(fi.first);
+ output.notExisting.insert(folderPath);
}
- catch (const FileError& e) { output.failedChecks.emplace(fi.first, e); }
- }
- else
- output.failedChecks.emplace(fi.first, FileError(replaceCpy(_("Timeout while searching for folder %x."), L"%x", displayPathFmt)));
+ catch (const FileError& e) { output.failedChecks.emplace(folderPath, e); }
}
return output;
}
diff --git a/FreeFileSync/Source/base/dir_lock.cpp b/FreeFileSync/Source/base/dir_lock.cpp
index 161fb35e..86006337 100755
--- a/FreeFileSync/Source/base/dir_lock.cpp
+++ b/FreeFileSync/Source/base/dir_lock.cpp
@@ -80,8 +80,6 @@ Zstring abandonedLockDeletionName(const Zstring& lockFilePath) //make sure to NO
}
-#if 0
-#endif
using ProcessId = pid_t;
@@ -160,7 +158,7 @@ LockInformation getLockInfoFromCurrentProcess() //throw FileError
LockInformation unserialize(MemoryStreamIn<ByteArray>& stream) //throw UnexpectedEndOfStreamError
{
char tmp[sizeof(LOCK_FORMAT_DESCR)] = {};
- readArray(stream, &tmp, sizeof(tmp)); //file format header
+ readArray(stream, &tmp, sizeof(tmp)); //file format header
const int lockFileVersion = readNumber<int32_t>(stream); //
if (!std::equal(std::begin(tmp), std::end(tmp), std::begin(LOCK_FORMAT_DESCR)) ||
diff --git a/FreeFileSync/Source/base/fatal_error.h b/FreeFileSync/Source/base/fatal_error.h
new file mode 100755
index 00000000..a27e423b
--- /dev/null
+++ b/FreeFileSync/Source/base/fatal_error.h
@@ -0,0 +1,45 @@
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef ERROR_LOG_H_89734181783491324134
+#define ERROR_LOG_H_89734181783491324134
+
+#include <cassert>
+#include <zen/file_io.h>
+#include <zen/time.h>
+#include "ffs_paths.h"
+
+
+namespace fff
+{
+//write error message to a file (even with corrupted stack)- call in desperate situations when no other means of error handling is available
+void logFatalError(const std::string& msg); //noexcept
+
+
+
+
+
+
+
+
+
+//##################### implementation ############################
+inline
+void logFatalError(const std::string& msg) //noexcept
+{
+ using namespace zen;
+
+ assert(false); //this is stuff we like to debug
+ const std::string logEntry = "[" + formatTime<std::string>(FORMAT_DATE) + " " + formatTime<std::string>(FORMAT_TIME) + "] " + msg;
+ try
+ {
+ saveBinContainer(getConfigDirPathPf() + Zstr("LastError.log"), logEntry, nullptr /*notifyUnbufferedIO*/); //throw FileError
+ }
+ catch (FileError&) {}
+}
+}
+
+#endif //ERROR_LOG_H_89734181783491324134
diff --git a/FreeFileSync/Source/base/file_hierarchy.cpp b/FreeFileSync/Source/base/file_hierarchy.cpp
index 88f7f152..d6e65ebd 100755
--- a/FreeFileSync/Source/base/file_hierarchy.cpp
+++ b/FreeFileSync/Source/base/file_hierarchy.cpp
@@ -27,7 +27,7 @@ std::wstring fff::getShortDisplayNameForFolderPair(const AbstractPath& itemPathL
const Zstring itemNameL = AFS::getItemName(tmpPathL);
const Zstring itemNameR = AFS::getItemName(tmpPathR);
- if (!strEqual(itemNameL, itemNameR, CmpNaturalSort())) //let's compare case-insensitively even on Linux!
+ if (!equalNoCase(itemNameL, itemNameR)) //let's compare case-insensitively even on Linux!
break;
tmpPathL = *parentPathL;
@@ -358,7 +358,7 @@ const wchar_t arrowRight[] = L"->";
std::wstring fff::getCategoryDescription(const FileSystemObject& fsObj)
{
- const std::wstring footer = L"\n[" + utfTo<std::wstring>(fsObj. getPairItemName()) + L"]";
+ const std::wstring footer = L"\n[" + utfTo<std::wstring>(fsObj. getItemNameAny()) + L"]";
const CompareFilesResult cmpRes = fsObj.getCategory();
switch (cmpRes)
@@ -439,7 +439,7 @@ std::wstring fff::getSyncOpDescription(SyncOperation op)
std::wstring fff::getSyncOpDescription(const FileSystemObject& fsObj)
{
- const std::wstring footer = L"\n[" + utfTo<std::wstring>(fsObj. getPairItemName()) + L"]";
+ const std::wstring footer = L"\n[" + utfTo<std::wstring>(fsObj. getItemNameAny()) + L"]";
const SyncOperation op = fsObj.getSyncOperation();
switch (op)
@@ -458,15 +458,16 @@ std::wstring fff::getSyncOpDescription(const FileSystemObject& fsObj)
case SO_COPY_METADATA_TO_RIGHT:
//harmonize with synchronization.cpp::FolderPairSyncer::synchronizeFileInt, ect!!
{
- Zstring shortNameOld = fsObj.getItemName<RIGHT_SIDE>();
- Zstring shortNameNew = fsObj.getItemName< LEFT_SIDE>();
+ Zstring itemNameOld = fsObj.getItemName<RIGHT_SIDE>();
+ Zstring itemNameNew = fsObj.getItemName< LEFT_SIDE>();
if (op == SO_COPY_METADATA_TO_LEFT)
- std::swap(shortNameOld, shortNameNew);
+ std::swap(itemNameOld, itemNameNew);
- if (shortNameOld != shortNameNew) //detected change in case
+ if (getUnicodeNormalForm(itemNameOld) !=
+ getUnicodeNormalForm(itemNameNew)) //detected change in case
return getSyncOpDescription(op) + L"\n" +
- fmtPath(shortNameOld) + L" " + arrowRight + L"\n" + //show short name only
- fmtPath(shortNameNew) /*+ footer -> redundant */;
+ fmtPath(itemNameOld) + L" " + arrowRight + L"\n" + //show short name only
+ fmtPath(itemNameNew) /*+ footer -> redundant */;
}
return getSyncOpDescription(op) + footer; //fallback
@@ -491,8 +492,8 @@ std::wstring fff::getSyncOpDescription(const FileSystemObject& fsObj)
//attention: ::SetWindowText() doesn't handle tab characters correctly in combination with certain file names, so don't use them
return getSyncOpDescription(op) + L"\n" +
- (equalFilePath(beforeLast(relSource, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE),
- beforeLast(relTarget, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE)) ?
+ (beforeLast(relSource, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE) ==
+ beforeLast(relTarget, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE) ?
//detected pure "rename"
fmtPath(afterLast(relSource, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL)) + L" " + arrowRight + L"\n" + //show short name only
fmtPath(afterLast(relTarget, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL)) :
diff --git a/FreeFileSync/Source/base/file_hierarchy.h b/FreeFileSync/Source/base/file_hierarchy.h
index 0ea4a3bb..4d06f73b 100755
--- a/FreeFileSync/Source/base/file_hierarchy.h
+++ b/FreeFileSync/Source/base/file_hierarchy.h
@@ -107,9 +107,9 @@ std::wstring getShortDisplayNameForFolderPair(const AbstractPath& itemPathL, con
struct FolderContainer
{
//------------------------------------------------------------------
- using FolderList = std::map<Zstring, std::pair<FolderAttributes, FolderContainer>, LessFilePath>; //
- using FileList = std::map<Zstring, FileAttributes, LessFilePath>; //key: file name
- using SymlinkList = std::map<Zstring, LinkAttributes, LessFilePath>; //
+ using FolderList = std::map<Zstring, std::pair<FolderAttributes, FolderContainer>>; //
+ using FileList = std::map<Zstring, FileAttributes>; //key: raw file name, without any (Unicode) normalization, preserving original upper-/lower-case
+ using SymlinkList = std::map<Zstring, LinkAttributes>; //
//------------------------------------------------------------------
FolderContainer() = default;
@@ -123,7 +123,7 @@ struct FolderContainer
void addSubFile(const Zstring& itemName, const FileAttributes& attr)
{
auto rv = files.emplace(itemName, attr);
- if (!rv.second) //update entry if already existing (e.g. during folder traverser "retry") => does not handle different item name case (irrelvant!..)
+ if (!rv.second) //update entry if already existing (e.g. during folder traverser "retry")
rv.first->second = attr;
}
@@ -174,8 +174,7 @@ struct PathInformation //diamond-shaped inheritence!
template <SelectedSide side> AbstractPath getAbstractPath() const;
template <SelectedSide side> Zstring getRelativePath() const; //get path relative to base sync dir (without leading/trailing FILE_NAME_SEPARATOR)
-
- Zstring getPairRelativePath() const;
+ Zstring getRelativePathAny() const { return getRelativePathL(); } //side doesn't matter
private:
virtual AbstractPath getAbstractPathL() const = 0; //implemented by FileSystemObject + BaseFolderPair
@@ -191,8 +190,6 @@ template <> inline AbstractPath PathInformation::getAbstractPath<RIGHT_SIDE>() c
template <> inline Zstring PathInformation::getRelativePath<LEFT_SIDE >() const { return getRelativePathL(); }
template <> inline Zstring PathInformation::getRelativePath<RIGHT_SIDE>() const { return getRelativePathR(); }
-inline Zstring PathInformation::getPairRelativePath() const { return getRelativePathL(); } //side doesn't matter
-
//------------------------------------------------------------------
class ContainerObject : public virtual PathInformation
@@ -421,12 +418,12 @@ class FileSystemObject : public ObjectMgr<FileSystemObject>, public virtual Path
public:
virtual void accept(FSObjectVisitor& visitor) const = 0;
- Zstring getPairItemName() const; //like getItemName() but without bias to which side is returned
bool isPairEmpty() const; //true, if both sides are empty
+ template <SelectedSide side> bool isEmpty() const;
//path getters always return valid values, even if isEmpty<side>()!
+ Zstring getItemNameAny() const; //like getItemName() but without bias to which side is returned
template <SelectedSide side> Zstring getItemName() const; //case sensitive!
- template <SelectedSide side> bool isEmpty() const;
//comparison result
CompareFilesResult getCategory() const { return cmpResult_; }
@@ -794,12 +791,12 @@ Zstring FileSystemObject::getItemName() const
const Zstring& itemName = SelectParam<side>::ref(itemNameL_, itemNameR_); //empty if not existing
if (!itemName.empty()) //avoid ternary-WTF! (implicit copy-constructor call!!!!!!)
return itemName;
- return SelectParam<OtherSide<side>::value>::ref(itemNameL_, itemNameR_); //empty if not existing
+ return SelectParam<OtherSide<side>::value>::ref(itemNameL_, itemNameR_);
}
inline
-Zstring FileSystemObject::getPairItemName() const
+Zstring FileSystemObject::getItemNameAny() const
{
return getItemName<LEFT_SIDE>(); //side doesn't matter
}
diff --git a/FreeFileSync/Source/base/generate_logfile.cpp b/FreeFileSync/Source/base/generate_logfile.cpp
index cbad8a4c..dbb8e42a 100755
--- a/FreeFileSync/Source/base/generate_logfile.cpp
+++ b/FreeFileSync/Source/base/generate_logfile.cpp
@@ -77,7 +77,7 @@ void streamToLogFile(const ProcessSummary& summary, //throw FileError
const std::wstring& finalStatusLabel,
AFS::OutputStream& streamOut)
{
- auto fmtForTxtFile = [needLbReplace = !strEqual(LINE_BREAK, '\n')](const std::wstring& str)
+ auto fmtForTxtFile = [needLbReplace = !equalString(LINE_BREAK, '\n')](const std::wstring& str)
{
std::string utfStr = utfTo<std::string>(str);
if (needLbReplace)
@@ -196,7 +196,7 @@ std::vector<LogFileInfo> getLogFiles(const AbstractPath& logFolderPath) //throw
//"2013-09-15 015052.123 [Error].log"
static_assert(TIME_STAMP_LENGTH == 21);
- if (endsWith(fi.itemName, Zstr(".log"), CmpFilePath()))
+ if (endsWith(fi.itemName, Zstr(".log"))) //case-sensitive: e.g. ".LOG" is not from FFS, right?
{
auto tsBegin = fi.itemName.begin();
auto tsEnd = fi.itemName.end() - 4;
diff --git a/FreeFileSync/Source/base/hard_filter.cpp b/FreeFileSync/Source/base/hard_filter.cpp
index 1c735c85..dda78b6b 100755
--- a/FreeFileSync/Source/base/hard_filter.cpp
+++ b/FreeFileSync/Source/base/hard_filter.cpp
@@ -35,7 +35,12 @@ static_assert(FILE_NAME_SEPARATOR == '/');
void addFilterEntry(const Zstring& filterPhrase, std::vector<Zstring>& masksFileFolder, std::vector<Zstring>& masksFolder)
{
- const Zstring& filterFmt = filterPhrase; //Linux DOES distinguish between upper/lower-case: nothing to do here
+ warn_static("3. ignore path separator => bug regarding copyFilterAddingExclusion() after failed directory reads when dir has path separator from other OS in name")
+
+ //normalize filter input: 1. ignore Unicode normalization form 2. ignore case 3. ignore path separator
+ Zstring filterFmt = makeUpperCopy(filterPhrase);
+ if constexpr (FILE_NAME_SEPARATOR != Zstr('/' )) replace(filterFmt, Zstr('/'), FILE_NAME_SEPARATOR);
+ if constexpr (FILE_NAME_SEPARATOR != Zstr('\\')) replace(filterFmt, Zstr('\\'), FILE_NAME_SEPARATOR);
/*
phrase | action
+---------+--------
@@ -218,12 +223,12 @@ bool matchesMaskBegin(const Zstring& name, const std::vector<Zstring>& masks)
}
-std::vector<Zstring> fff::splitByDelimiter(const Zstring& filterString)
+std::vector<Zstring> fff::splitByDelimiter(const Zstring& filterPhrase)
{
//delimiters may be FILTER_ITEM_SEPARATOR or '\n'
std::vector<Zstring> output;
- for (const Zstring& str : split(filterString, FILTER_ITEM_SEPARATOR, SplitType::SKIP_EMPTY)) //split by less common delimiter first (create few, large strings)
+ for (const Zstring& str : split(filterPhrase, FILTER_ITEM_SEPARATOR, SplitType::SKIP_EMPTY)) //split by less common delimiter first (create few, large strings)
for (Zstring entry : split(str, Zstr('\n'), SplitType::SKIP_EMPTY))
{
trim(entry);
@@ -261,7 +266,9 @@ void NameFilter::addExclusion(const Zstring& excludePhrase)
bool NameFilter::passFileFilter(const Zstring& relFilePath) const
{
assert(!startsWith(relFilePath, FILE_NAME_SEPARATOR));
- const Zstring& pathFmt = relFilePath; //nothing to do here
+
+ //normalize input: 1. ignore Unicode normalization form 2. ignore case
+ const Zstring& pathFmt = makeUpperCopy(relFilePath);
if (matchesMask<AnyMatch >(pathFmt, excludeMasksFileFolder) || //either full match on file or partial match on any parent folder
matchesMask<ParentFolderMatch>(pathFmt, excludeMasksFolder)) //partial match on any parent folder only
@@ -277,7 +284,8 @@ bool NameFilter::passDirFilter(const Zstring& relDirPath, bool* childItemMightMa
assert(!startsWith(relDirPath, FILE_NAME_SEPARATOR));
assert(!childItemMightMatch || *childItemMightMatch); //check correct usage
- const Zstring& pathFmt = relDirPath; //nothing to do here
+ //normalize input: 1. ignore Unicode normalization form 2. ignore case
+ const Zstring& pathFmt = makeUpperCopy(relDirPath);
if (matchesMask<AnyMatch>(pathFmt, excludeMasksFileFolder) ||
matchesMask<AnyMatch>(pathFmt, excludeMasksFolder))
diff --git a/FreeFileSync/Source/base/hard_filter.h b/FreeFileSync/Source/base/hard_filter.h
index 5e1f1702..682c2502 100755
--- a/FreeFileSync/Source/base/hard_filter.h
+++ b/FreeFileSync/Source/base/hard_filter.h
@@ -62,7 +62,7 @@ HardFilter::FilterRef combineFilters(const HardFilter::FilterRef& first,
const HardFilter::FilterRef& second);
-class NullFilter : public HardFilter //no filtering at all
+class NullFilter : public HardFilter //no filtering at all
{
public:
bool passFileFilter(const Zstring& relFilePath) const override { return true; }
@@ -75,7 +75,7 @@ private:
};
-class NameFilter : public HardFilter //standard filter by filepath
+class NameFilter : public HardFilter //filter by base-relative file path
{
public:
NameFilter(const Zstring& includePhrase, const Zstring& excludePhrase);
@@ -93,7 +93,7 @@ private:
bool cmpLessSameType(const HardFilter& other) const override;
std::vector<Zstring> includeMasksFileFolder; //
- std::vector<Zstring> includeMasksFolder; //upper case (windows) + unique items by construction
+ std::vector<Zstring> includeMasksFolder; //upper-case + Unicode-normalized by construction
std::vector<Zstring> excludeMasksFileFolder; //
std::vector<Zstring> excludeMasksFolder; //
};
@@ -235,7 +235,7 @@ HardFilter::FilterRef constructFilter(const Zstring& includePhrase,
}
-std::vector<Zstring> splitByDelimiter(const Zstring& filterString); //keep external linkage for unit test
+std::vector<Zstring> splitByDelimiter(const Zstring& filterPhrase); //keep external linkage for unit test
}
#endif //HARD_FILTER_H_825780275842758345
diff --git a/FreeFileSync/Source/base/icon_buffer.cpp b/FreeFileSync/Source/base/icon_buffer.cpp
index a784f9e0..39b5459b 100755
--- a/FreeFileSync/Source/base/icon_buffer.cpp
+++ b/FreeFileSync/Source/base/icon_buffer.cpp
@@ -283,7 +283,7 @@ struct IconBuffer::Impl
InterruptibleThread worker;
//-------------------------
//-------------------------
- std::map<Zstring, wxBitmap, LessFilePath> extensionIcons; //no item count limit!? Test case C:\ ~ 3800 unique file extensions
+ std::map<Zstring, wxBitmap, LessAsciiNoCase> extensionIcons; //no item count limit!? Test case C:\ ~ 3800 unique file extensions
};
diff --git a/FreeFileSync/Source/base/localization.cpp b/FreeFileSync/Source/base/localization.cpp
index 28f19e33..de1b7087 100755
--- a/FreeFileSync/Source/base/localization.cpp
+++ b/FreeFileSync/Source/base/localization.cpp
@@ -89,24 +89,23 @@ FFSTranslation::FFSTranslation(const Zstring& lngFilePath, wxLanguage langId) :
pluralParser_ = std::make_unique<plural::PluralForm>(header.pluralDefinition); //throw plural::ParsingError
- for (const auto& item : transUtf)
- {
- std::wstring original = utfTo<std::wstring>(item.first);
- std::wstring translation = utfTo<std::wstring>(item.second);
-
- transMapping_.emplace(std::move(original), std::move(translation));
- }
+ for (const auto& [original, translation] : transUtf)
+ transMapping_.emplace(utfTo<std::wstring>(original),
+ utfTo<std::wstring>(translation));
- for (const auto& item : transPluralUtf)
+ for (const auto& [singAndPlural, pluralForms] : transPluralUtf)
{
- std::wstring engSingular = utfTo<std::wstring>(item.first.first);
- std::wstring engPlural = utfTo<std::wstring>(item.first.second);
+ std::vector<std::wstring> transPluralForms;
+ for (const std::string& pf : pluralForms)
+ transPluralForms.push_back(utfTo<std::wstring>(pf));
- std::vector<std::wstring> pluralForms;
- for (const std::string& pf : item.second)
- pluralForms.push_back(utfTo<std::wstring>(pf));
-
- transMappingPl_.insert({ { std::move(engSingular), std::move(engPlural) }, std::move(pluralForms) });
+ transMappingPl_.insert(
+ {
+ {
+ utfTo<std::wstring>(singAndPlural.first),
+ utfTo<std::wstring>(singAndPlural.second)
+ },
+ std::move(transPluralForms) });
}
}
diff --git a/FreeFileSync/Source/base/lock_holder.h b/FreeFileSync/Source/base/lock_holder.h
index cfe6a4fb..d4e3371c 100755
--- a/FreeFileSync/Source/base/lock_holder.h
+++ b/FreeFileSync/Source/base/lock_holder.h
@@ -20,7 +20,7 @@ class LockHolder
public:
LockHolder(const std::set<Zstring, LessFilePath>& dirPathsExisting, //resolved paths
bool& warnDirectoryLockFailed,
- ProcessCallback& pcb)
+ ProcessCallback& pcb /*throw X*/)
{
using namespace zen;
@@ -40,10 +40,10 @@ public:
{
std::wstring msg = _("Cannot set directory locks for the following folders:");
- for (const auto& fl : failedLocks)
+ for (const auto& [folderPath, error] : failedLocks)
{
- msg += L"\n\n" + fmtPath(fl.first);
- msg += L"\n" + replaceCpy(fl.second.toString(), L"\n\n", L"\n");
+ msg += L"\n\n" + fmtPath(folderPath);
+ msg += L"\n" + 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 cc432462..9ffb5f67 100755
--- a/FreeFileSync/Source/base/parallel_scan.cpp
+++ b/FreeFileSync/Source/base/parallel_scan.cpp
@@ -116,18 +116,18 @@ DiskInfo retrieveDiskInfo(const Zstring& itemPath)
/*
PERF NOTE
---------------------------------------------
-|Testcase: Reading from two different disks|
---------------------------------------------
+---------------------------------------------
+|Test case: Reading from two different disks|
+---------------------------------------------
Windows 7:
1st(unbuffered) |2nd (OS buffered)
----------------------------------
1 Thread: 57s | 8s
2 Threads: 39s | 7s
---------------------------------------------------
-|Testcase: Reading two directories from same disk|
---------------------------------------------------
+---------------------------------------------------
+|Test case: Reading two directories from same disk|
+---------------------------------------------------
Windows 7: Windows XP:
1st(unbuffered) |2nd (OS buffered) 1st(unbuffered) |2nd (OS buffered)
---------------------------------- ----------------------------------
@@ -278,8 +278,8 @@ private:
{
std::lock_guard<std::mutex> dummy(lockCurrentStatus_);
- for (const auto& item : activeThreadIdxs_)
- parallelOpsTotal += item.second;
+ for (const auto& [threadIdx, parallelOps] : activeThreadIdxs_)
+ parallelOpsTotal += parallelOps;
filePath = currentFile_;
}
@@ -319,8 +319,8 @@ struct TraverserConfig
const HardFilter::FilterRef filter; //always bound!
const SymLinkHandling handleSymlinks;
- std::map<Zstring, std::wstring, LessFilePath>& failedDirReads;
- std::map<Zstring, std::wstring, LessFilePath>& failedItemReads;
+ std::map<Zstring, std::wstring>& failedDirReads;
+ std::map<Zstring, std::wstring>& failedItemReads;
AsyncCallback& acb;
const int threadIdx;
@@ -543,18 +543,17 @@ void fff::parallelDeviceTraversal(const std::set<DirectoryKey>& foldersToRead,
ZEN_ON_SCOPE_FAIL( for (InterruptibleThread& wt : worker) wt.interrupt(); ); //interrupt all first, then join
//init worker threads
- for (const auto& item : perDeviceFolders)
+ for (const auto& [rootPath, dirKeys] : perDeviceFolders)
{
- const AbstractPath& rootPath = item.first;
const int threadIdx = static_cast<int>(worker.size());
const size_t parallelOps = getDeviceParallelOps(deviceParallelOps, rootPath);
std::map<DirectoryKey, DirectoryValue*> workload;
- for (const DirectoryKey& key : item.second)
+ for (const DirectoryKey& key : dirKeys)
workload.emplace(key, &output[key]); //=> DirectoryValue* unshared for lock-free worker-thread access
- worker.emplace_back([rootPath, workload, threadIdx, &acb, parallelOps]() mutable
+ worker.emplace_back([rootPath = rootPath /*clang bug :>*/, workload, threadIdx, &acb, parallelOps]() mutable
{
setCurrentThreadName(("Comp Worker[" + numberTo<std::string>(threadIdx) + "]").c_str());
@@ -565,11 +564,11 @@ void fff::parallelDeviceTraversal(const std::set<DirectoryKey>& foldersToRead,
AFS::TraverserWorkload travWorkload;
- for (auto& wl : workload)
+ for (auto& [folderKey, folderVal] : workload)
{
- const std::vector<Zstring> relPath = split(AFS::getRootRelativePath(wl.first.folderPath), FILE_NAME_SEPARATOR, SplitType::SKIP_EMPTY);
- assert(AFS::getRootPath(wl.first.folderPath) == rootPath);
- travWorkload.emplace_back(relPath, std::make_shared<BaseDirCallback>(wl.first, *wl.second, acb, threadIdx, lastReportTime));
+ const std::vector<Zstring> relPath = split(AFS::getRootRelativePath(folderKey.folderPath), FILE_NAME_SEPARATOR, SplitType::SKIP_EMPTY);
+ assert(AFS::getRootPath(folderKey.folderPath) == rootPath);
+ travWorkload.emplace_back(relPath, std::make_shared<BaseDirCallback>(folderKey, *folderVal, acb, threadIdx, lastReportTime));
}
AFS::traverseFolderRecursive(rootPath, travWorkload, parallelOps); //throw ThreadInterruption
});
diff --git a/FreeFileSync/Source/base/parallel_scan.h b/FreeFileSync/Source/base/parallel_scan.h
index bbe1071f..dbb0c1d9 100755
--- 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, LessFilePath> failedFolderReads; //with corresponding error message
+ std::map<Zstring, std::wstring> 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, LessFilePath> failedItemReads;
+ std::map<Zstring, std::wstring> failedItemReads;
};
diff --git a/FreeFileSync/Source/base/parse_lng.h b/FreeFileSync/Source/base/parse_lng.h
index b48af8b2..c282c2de 100755
--- a/FreeFileSync/Source/base/parse_lng.h
+++ b/FreeFileSync/Source/base/parse_lng.h
@@ -127,7 +127,7 @@ public:
template <class Function, class Function2>
void visitItems(Function onTrans, Function2 onPluralTrans) const //onTrans takes (const TranslationMap::value_type&), onPluralTrans takes (const TranslationPluralMap::value_type&)
{
- for (const auto& item : sequence_)
+ for (const std::shared_ptr<Item>& item : sequence_)
if (auto regular = dynamic_cast<const RegularItem*>(item.get()))
onTrans(regular->value);
else if (auto plural = dynamic_cast<const PluralItem*>(item.get()))
@@ -183,8 +183,8 @@ struct Token
};
Token(Type t) : type(t) {}
- Type type;
+ Type type;
std::string text;
};
@@ -196,19 +196,19 @@ public:
using TokenMap = std::map<Token::Type, std::string>;
- const TokenMap& getList() const { return tokens; }
+ const TokenMap& getList() const { return tokens_; }
std::string text(Token::Type t) const
{
- auto it = tokens.find(t);
- if (it != tokens.end())
+ auto it = tokens_.find(t);
+ if (it != tokens_.end())
return it->second;
assert(false);
return std::string();
}
private:
- const TokenMap tokens =
+ const TokenMap tokens_ =
{
//header information
{ Token::TK_HEADER_BEGIN, "<header>" },
@@ -246,19 +246,19 @@ public:
pos_ += zen::strLength(zen::BYTE_ORDER_MARK_UTF8);
}
- Token nextToken()
+ Token getNextToken()
{
//skip whitespace
- pos_ = std::find_if(pos_, stream_.end(), [](char c) { return !zen::isWhiteSpace(c); });
+ pos_ = std::find_if(pos_, stream_.end(), std::not_fn(zen::isWhiteSpace<char>));
if (pos_ == stream_.end())
return Token(Token::TK_END);
- for (const auto& token : tokens_.getList())
- if (startsWith(token.second))
+ for (const auto& [tokenEnum, tokenString] : tokens_.getList())
+ if (startsWith(tokenString))
{
- pos_ += token.second.size();
- return Token(token.first);
+ pos_ += tokenString.size();
+ return Token(tokenEnum);
}
//rest must be "text"
@@ -268,7 +268,7 @@ public:
std::string text(itBegin, pos_);
- normalize(text); //remove whitespace from end ect.
+ normalize(text); //remove whitespace from end etc.
if (text.empty() && pos_ == stream_.end())
return Token(Token::TK_END);
@@ -308,9 +308,7 @@ private:
bool startsWith(const std::string& prefix) const
{
- if (stream_.end() - pos_ < static_cast<ptrdiff_t>(prefix.size()))
- return false;
- return std::equal(prefix.begin(), prefix.end(), pos_);
+ return zen::startsWith(zen::StringRef<const char>(pos_, stream_.end()), prefix);
}
static void normalize(std::string& text)
@@ -335,7 +333,7 @@ private:
class LngParser
{
public:
- LngParser(const std::string& fileStream) : scn_(fileStream), tk_(scn_.nextToken()) {}
+ LngParser(const std::string& fileStream) : scn_(fileStream), tk_(scn_.getNextToken()) {}
void parse(TranslationMap& out, TranslationPluralMap& pluralOut, TransHeader& header)
{
@@ -357,54 +355,54 @@ public:
void parseHeader(TransHeader& header)
{
- consumeToken(Token::TK_HEADER_BEGIN);
+ consumeToken(Token::TK_HEADER_BEGIN); //throw ParsingError
- consumeToken(Token::TK_LANG_NAME_BEGIN);
+ consumeToken(Token::TK_LANG_NAME_BEGIN); //throw ParsingError
header.languageName = token().text;
- consumeToken(Token::TK_TEXT);
- consumeToken(Token::TK_LANG_NAME_END);
+ consumeToken(Token::TK_TEXT); //throw ParsingError
+ consumeToken(Token::TK_LANG_NAME_END); //
- consumeToken(Token::TK_TRANS_NAME_BEGIN);
+ consumeToken(Token::TK_TRANS_NAME_BEGIN); //throw ParsingError
header.translatorName = token().text;
- consumeToken(Token::TK_TEXT);
- consumeToken(Token::TK_TRANS_NAME_END);
+ consumeToken(Token::TK_TEXT); //throw ParsingError
+ consumeToken(Token::TK_TRANS_NAME_END); //
- consumeToken(Token::TK_LOCALE_NAME_BEGIN);
+ consumeToken(Token::TK_LOCALE_NAME_BEGIN); //throw ParsingError
header.localeName = token().text;
- consumeToken(Token::TK_TEXT);
- consumeToken(Token::TK_LOCALE_NAME_END);
+ consumeToken(Token::TK_TEXT); //throw ParsingError
+ consumeToken(Token::TK_LOCALE_NAME_END); //
- consumeToken(Token::TK_FLAG_FILE_BEGIN);
+ consumeToken(Token::TK_FLAG_FILE_BEGIN); //throw ParsingError
header.flagFile = token().text;
- consumeToken(Token::TK_TEXT);
- consumeToken(Token::TK_FLAG_FILE_END);
+ consumeToken(Token::TK_TEXT); //throw ParsingError
+ consumeToken(Token::TK_FLAG_FILE_END); //
- consumeToken(Token::TK_PLURAL_COUNT_BEGIN);
+ consumeToken(Token::TK_PLURAL_COUNT_BEGIN); //throw ParsingError
header.pluralCount = zen::stringTo<int>(token().text);
- consumeToken(Token::TK_TEXT);
- consumeToken(Token::TK_PLURAL_COUNT_END);
+ consumeToken(Token::TK_TEXT); //throw ParsingError
+ consumeToken(Token::TK_PLURAL_COUNT_END); //
- consumeToken(Token::TK_PLURAL_DEF_BEGIN);
+ consumeToken(Token::TK_PLURAL_DEF_BEGIN); //throw ParsingError
header.pluralDefinition = token().text;
- consumeToken(Token::TK_TEXT);
- consumeToken(Token::TK_PLURAL_DEF_END);
+ consumeToken(Token::TK_TEXT); //throw ParsingError
+ consumeToken(Token::TK_PLURAL_DEF_END); //
- consumeToken(Token::TK_HEADER_END);
+ consumeToken(Token::TK_HEADER_END); //throw ParsingError
}
private:
void parseRegular(TranslationMap& out, TranslationPluralMap& pluralOut, const plural::PluralFormInfo& pluralInfo)
{
- consumeToken(Token::TK_SRC_BEGIN);
+ consumeToken(Token::TK_SRC_BEGIN); //throw ParsingError
if (token().type == Token::TK_PLURAL_BEGIN)
return parsePlural(pluralOut, pluralInfo);
std::string original = token().text;
- consumeToken(Token::TK_TEXT);
- consumeToken(Token::TK_SRC_END);
+ consumeToken(Token::TK_TEXT); //throw ParsingError
+ consumeToken(Token::TK_SRC_END); //
- consumeToken(Token::TK_TRG_BEGIN);
+ consumeToken(Token::TK_TRG_BEGIN); //throw ParsingError
std::string translation;
if (token().type == Token::TK_TEXT)
{
@@ -412,7 +410,7 @@ private:
nextToken();
}
validateTranslation(original, translation); //throw ParsingError
- consumeToken(Token::TK_TRG_END);
+ consumeToken(Token::TK_TRG_END); //
out.emplace(original, translation);
}
@@ -421,32 +419,32 @@ private:
{
//Token::TK_SRC_BEGIN already consumed
- consumeToken(Token::TK_PLURAL_BEGIN);
+ consumeToken(Token::TK_PLURAL_BEGIN); //throw ParsingError
std::string engSingular = token().text;
- consumeToken(Token::TK_TEXT);
- consumeToken(Token::TK_PLURAL_END);
+ consumeToken(Token::TK_TEXT); //throw ParsingError
+ consumeToken(Token::TK_PLURAL_END); //
- consumeToken(Token::TK_PLURAL_BEGIN);
+ consumeToken(Token::TK_PLURAL_BEGIN); //throw ParsingError
std::string engPlural = token().text;
- consumeToken(Token::TK_TEXT);
- consumeToken(Token::TK_PLURAL_END);
+ consumeToken(Token::TK_TEXT); //throw ParsingError
+ consumeToken(Token::TK_PLURAL_END); //
- consumeToken(Token::TK_SRC_END);
+ consumeToken(Token::TK_SRC_END); //throw ParsingError
const SingularPluralPair original(engSingular, engPlural);
- consumeToken(Token::TK_TRG_BEGIN);
+ consumeToken(Token::TK_TRG_BEGIN); //throw ParsingError
PluralForms pluralList;
while (token().type == Token::TK_PLURAL_BEGIN)
{
- consumeToken(Token::TK_PLURAL_BEGIN);
+ consumeToken(Token::TK_PLURAL_BEGIN); //throw ParsingError
std::string pluralForm = token().text;
- consumeToken(Token::TK_TEXT);
- consumeToken(Token::TK_PLURAL_END);
+ consumeToken(Token::TK_TEXT); //throw ParsingError
+ consumeToken(Token::TK_PLURAL_END); //
pluralList.push_back(pluralForm);
}
validateTranslation(original, pluralList, pluralInfo);
- consumeToken(Token::TK_TRG_END);
+ consumeToken(Token::TK_TRG_END); //throw ParsingError
pluralOut.emplace(original, pluralList);
}
@@ -678,14 +676,9 @@ private:
}
- void nextToken() { tk_ = scn_.nextToken(); }
const Token& token() const { return tk_; }
- void consumeToken(Token::Type t) //throw ParsingError
- {
- expectToken(t); //throw ParsingError
- nextToken();
- }
+ void nextToken() { tk_ = scn_.getNextToken(); }
void expectToken(Token::Type t) //throw ParsingError
{
@@ -693,6 +686,12 @@ private:
throw ParsingError({ L"Unexpected token", scn_.posRow(), scn_.posCol() });
}
+ void consumeToken(Token::Type t) //throw ParsingError
+ {
+ expectToken(t); //throw ParsingError
+ nextToken();
+ }
+
Scanner scn_;
Token tk_;
};
diff --git a/FreeFileSync/Source/base/parse_plural.h b/FreeFileSync/Source/base/parse_plural.h
index 8a9173e3..e735c421 100755
--- a/FreeFileSync/Source/base/parse_plural.h
+++ b/FreeFileSync/Source/base/parse_plural.h
@@ -208,22 +208,22 @@ class Scanner
public:
Scanner(const std::string& stream) : stream_(stream), pos_(stream_.begin()) {}
- Token nextToken()
+ Token getNextToken() //throw ParsingError
{
//skip whitespace
- pos_ = std::find_if(pos_, stream_.end(), [](char c) { return !zen::isWhiteSpace(c); });
+ pos_ = std::find_if(pos_, stream_.end(), std::not_fn(zen::isWhiteSpace<char>));
if (pos_ == stream_.end())
return Token::TK_END;
- for (const auto& item : tokens_)
- if (startsWith(item.first))
+ for (const auto& [tokenString, tokenEnum] : tokens_)
+ if (startsWith(tokenString))
{
- pos_ += item.first.size();
- return Token(item.second);
+ pos_ += tokenString.size();
+ return Token(tokenEnum);
}
- auto digitEnd = std::find_if(pos_, stream_.end(), [](char c) { return !zen::isDigit(c); });
+ auto digitEnd = std::find_if(pos_, stream_.end(), std::not_fn(zen::isDigit<char>));
if (pos_ == digitEnd)
throw ParsingError(); //unknown token
@@ -235,9 +235,7 @@ public:
private:
bool startsWith(const std::string& prefix) const
{
- if (stream_.end() - pos_ < static_cast<ptrdiff_t>(prefix.size()))
- return false;
- return std::equal(prefix.begin(), prefix.end(), pos_);
+ return zen::startsWith(zen::StringRef<const char>(pos_, stream_.end()), prefix);
}
using TokenList = std::vector<std::pair<std::string, Token::Type>>;
@@ -271,7 +269,7 @@ class Parser
public:
Parser(const std::string& stream, int64_t& n) :
scn_(stream),
- tk_(scn_.nextToken()),
+ tk_(scn_.getNextToken()), //throw ParsingError
n_(n) {}
std::shared_ptr<Expr<int64_t>> parse() //throw ParsingError; return value always bound!
@@ -279,7 +277,7 @@ public:
auto e = std::dynamic_pointer_cast<Expr<int64_t>>(parseExpression()); //throw ParsingError
if (!e)
throw ParsingError();
- expectToken(Token::TK_END);
+ expectToken(Token::TK_END); //throw ParsingError
return e;
}
@@ -292,13 +290,12 @@ private:
if (token().type == Token::TK_TERNARY_QUEST)
{
- nextToken();
+ nextToken(); //throw ParsingError
auto ifExp = std::dynamic_pointer_cast<Expr<bool>>(e);
auto thenExp = std::dynamic_pointer_cast<Expr<int64_t>>(parseExpression()); //associativity: <-
- expectToken(Token::TK_TERNARY_COLON);
- nextToken();
+ consumeToken(Token::TK_TERNARY_COLON); //throw ParsingError
auto elseExp = std::dynamic_pointer_cast<Expr<int64_t>>(parseExpression()); //
if (!ifExp || !thenExp || !elseExp)
@@ -313,7 +310,7 @@ private:
std::shared_ptr<Expression> e = parseLogicalAnd();
while (token().type == Token::TK_OR) //associativity: ->
{
- nextToken();
+ nextToken(); //throw ParsingError
std::shared_ptr<Expression> rhs = parseLogicalAnd();
e = makeBiExp<std::logical_or<>, bool>(e, rhs); //throw ParsingError
@@ -326,7 +323,7 @@ private:
std::shared_ptr<Expression> e = parseEquality();
while (token().type == Token::TK_AND) //associativity: ->
{
- nextToken();
+ nextToken(); //throw ParsingError
std::shared_ptr<Expression> rhs = parseEquality();
e = makeBiExp<std::logical_and<>, bool>(e, rhs); //throw ParsingError
@@ -342,7 +339,7 @@ private:
if (t == Token::TK_EQUAL || //associativity: n/a
t == Token::TK_NOT_EQUAL)
{
- nextToken();
+ nextToken(); //throw ParsingError
std::shared_ptr<Expression> rhs = parseRelational();
if (t == Token::TK_EQUAL) return makeBiExp<std::equal_to<>, int64_t>(e, rhs); //throw ParsingError
@@ -361,7 +358,7 @@ private:
t == Token::TK_GREATER ||
t == Token::TK_GREATER_EQUAL)
{
- nextToken();
+ nextToken(); //throw ParsingError
std::shared_ptr<Expression> rhs = parseMultiplicative();
if (t == Token::TK_LESS) return makeBiExp<std::less <>, int64_t>(e, rhs); //
@@ -378,7 +375,7 @@ private:
while (token().type == Token::TK_MODULUS) //associativity: ->
{
- nextToken();
+ nextToken(); //throw ParsingError
std::shared_ptr<Expression> rhs = parsePrimary();
//"compile-time" check: n % 0
@@ -395,37 +392,44 @@ private:
{
if (token().type == Token::TK_VARIABLE_N)
{
- nextToken();
+ nextToken(); //throw ParsingError
return std::make_shared<VariableNumberNExp>(n_);
}
else if (token().type == Token::TK_CONST_NUMBER)
{
const int64_t number = token().number;
- nextToken();
+ nextToken(); //throw ParsingError
return std::make_shared<ConstNumberExp>(number);
}
else if (token().type == Token::TK_BRACKET_LEFT)
{
- nextToken();
+ nextToken(); //throw ParsingError
std::shared_ptr<Expression> e = parseExpression();
- expectToken(Token::TK_BRACKET_RIGHT);
- nextToken();
+ expectToken(Token::TK_BRACKET_RIGHT); //throw ParsingError
+ nextToken(); //
return e;
}
else
throw ParsingError();
}
- void nextToken() { tk_ = scn_.nextToken(); }
const Token& token() const { return tk_; }
+ void nextToken() { tk_ = scn_.getNextToken(); } //throw ParsingError
+
void expectToken(Token::Type t) //throw ParsingError
{
if (token().type != t)
throw ParsingError();
}
+ void consumeToken(Token::Type t) //throw ParsingError
+ {
+ expectToken(t); //throw ParsingError
+ nextToken();
+ }
+
Scanner scn_;
Token tk_;
int64_t& n_;
diff --git a/FreeFileSync/Source/base/perf_check.h b/FreeFileSync/Source/base/perf_check.h
index a00aae84..2e9ccc6d 100755
--- a/FreeFileSync/Source/base/perf_check.h
+++ b/FreeFileSync/Source/base/perf_check.h
@@ -9,8 +9,8 @@
#include <map>
#include <chrono>
+#include <optional>
#include <string>
-#include <zen/legacy_compiler.h> //#includes <optional>
namespace fff
diff --git a/FreeFileSync/Source/base/process_xml.cpp b/FreeFileSync/Source/base/process_xml.cpp
index da7fa2e7..e3d1b89f 100755
--- a/FreeFileSync/Source/base/process_xml.cpp
+++ b/FreeFileSync/Source/base/process_xml.cpp
@@ -8,7 +8,6 @@
#include <zenxml/xml.h>
#include <zen/file_access.h>
#include <zen/file_io.h>
-#include <zen/xml_io.h>
#include <zen/time.h>
#include <wx/intl.h>
#include "ffs_paths.h"
@@ -49,8 +48,8 @@ XmlType getXmlTypeNoThrow(const XmlDoc& doc) //throw()
XmlType fff::getXmlType(const Zstring& filePath) //throw FileError
{
- //do NOT use zen::loadStream as it will needlessly load even huge files!
- XmlDoc doc = loadXmlDocument(filePath); //throw FileError; quick exit if file is not an FFS XML
+ //quick exit if file is not an XML
+ XmlDoc doc = loadXml(filePath); //throw FileError
return ::getXmlTypeNoThrow(doc);
}
@@ -1020,8 +1019,8 @@ void readConfig(const XmlIn& in, SyncConfig& syncCfg, std::map<AbstractPath, siz
if (syncCfg.versioningStyle == VersioningStyle::REPLACE)
{
- if (endsWith(syncCfg.versioningFolderPhrase, Zstr("/%timestamp%"), CmpAsciiNoCase()) ||
- endsWith(syncCfg.versioningFolderPhrase, Zstr("\\%timestamp%"), CmpAsciiNoCase()))
+ if (endsWithAsciiNoCase(syncCfg.versioningFolderPhrase, Zstr("/%timestamp%")) ||
+ endsWithAsciiNoCase(syncCfg.versioningFolderPhrase, Zstr("\\%timestamp%")))
{
syncCfg.versioningFolderPhrase.resize(syncCfg.versioningFolderPhrase.size() - strLength(Zstr("/%timestamp%")));
syncCfg.versioningStyle = VersioningStyle::TIMESTAMP_FOLDER;
@@ -1101,8 +1100,8 @@ void readConfig(const XmlIn& in, LocalPairConfig& lpc, std::map<AbstractPath, si
{
auto getParallelOps = [&](const Zstring& folderPathPhrase, size_t& parallelOps)
{
- if (startsWith(folderPathPhrase, Zstr("sftp:"), CmpAsciiNoCase()) ||
- startsWith(folderPathPhrase, Zstr( "ftp:"), CmpAsciiNoCase()))
+ if (startsWithAsciiNoCase(folderPathPhrase, Zstr("sftp:")) ||
+ startsWithAsciiNoCase(folderPathPhrase, Zstr( "ftp:")))
{
for (const Zstring& optPhrase : split(folderPathPhrase, Zstr("|"), SplitType::SKIP_EMPTY))
if (startsWith(optPhrase, Zstr("con=")))
@@ -1129,7 +1128,7 @@ void readConfig(const XmlIn& in, LocalPairConfig& lpc, std::map<AbstractPath, si
setParallelOps(lpc.folderPathPhraseRight, parallelOpsR);
//TODO: remove after migration - 2016-07-24
- auto ciReplace = [](Zstring& pathPhrase, const Zstring& oldTerm, const Zstring& newTerm) { pathPhrase = ciReplaceCpy(pathPhrase, oldTerm, newTerm); };
+ auto ciReplace = [](Zstring& pathPhrase, const Zstring& oldTerm, const Zstring& newTerm) { pathPhrase = replaceCpyAsciiNoCase(pathPhrase, oldTerm, newTerm); };
ciReplace(lpc.folderPathPhraseLeft, Zstr("%csidl_MyDocuments%"), Zstr("%csidl_Documents%"));
ciReplace(lpc.folderPathPhraseLeft, Zstr("%csidl_MyMusic%" ), Zstr("%csidl_Music%"));
ciReplace(lpc.folderPathPhraseLeft, Zstr("%csidl_MyPictures%" ), Zstr("%csidl_Pictures%"));
@@ -1278,7 +1277,7 @@ void readConfig(const XmlIn& in, XmlGuiConfig& cfg, int formatVer)
cfg.mainCfg.ignoreErrors = str == "Ignore";
str = trimCpy(utfTo<std::string>(cfg.mainCfg.postSyncCommand));
- if (strEqual(str, "Close progress dialog", CmpAsciiNoCase()))
+ if (equalAsciiNoCase(str, "Close progress dialog"))
cfg.mainCfg.postSyncCommand.clear();
}
}
@@ -1355,7 +1354,7 @@ void readConfig(const XmlIn& in, XmlBatchConfig& cfg, int formatVer)
cfg.mainCfg.ignoreErrors = str == "Ignore";
str = trimCpy(utfTo<std::string>(cfg.mainCfg.postSyncCommand));
- if (strEqual(str, "Close progress dialog", CmpAsciiNoCase()))
+ if (equalAsciiNoCase(str, "Close progress dialog"))
{
cfg.batchExCfg.autoCloseSummary = true;
cfg.mainCfg.postSyncCommand.clear();
@@ -1363,7 +1362,7 @@ void readConfig(const XmlIn& in, XmlBatchConfig& cfg, int formatVer)
else if (str == "rundll32.exe powrprof.dll,SetSuspendState Sleep" ||
str == "rundll32.exe powrprof.dll,SetSuspendState" ||
str == "systemctl suspend" ||
- str == "osascript -e \'tell application \"System Events\" to sleep\'")
+ str == "osascript -e 'tell application \"System Events\" to sleep'")
{
cfg.batchExCfg.postSyncAction = PostSyncAction::SLEEP;
cfg.mainCfg.postSyncCommand.clear();
@@ -1371,7 +1370,7 @@ void readConfig(const XmlIn& in, XmlBatchConfig& cfg, int formatVer)
else if (str == "shutdown /s /t 60" ||
str == "shutdown -s -t 60" ||
str == "systemctl poweroff" ||
- str == "osascript -e \'tell application \"System Events\" to shut down\'")
+ str == "osascript -e 'tell application \"System Events\" to shut down'")
{
cfg.batchExCfg.postSyncAction = PostSyncAction::SHUTDOWN;
cfg.mainCfg.postSyncCommand.clear();
@@ -1396,7 +1395,6 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer)
inGeneral["CopyLockedFiles" ].attribute("Enabled", cfg.copyLockedFiles);
inGeneral["CopyFilePermissions" ].attribute("Enabled", cfg.copyFilePermissions);
inGeneral["FileTimeTolerance" ].attribute("Seconds", cfg.fileTimeTolerance);
- inGeneral["FolderAccessTimeout" ].attribute("Seconds", cfg.folderAccessTimeout);
inGeneral["RunWithBackgroundPriority"].attribute("Enabled", cfg.runWithBackgroundPriority);
inGeneral["LockDirectoriesDuringSync"].attribute("Enabled", cfg.createLockFile);
inGeneral["VerifyCopiedFiles" ].attribute("Enabled", cfg.verifyFileCopy);
@@ -1673,8 +1671,8 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer)
if (inGui["ExternalApps"](extApps))
{
cfg.gui.externalApps.clear();
- for (const auto& item : extApps)
- cfg.gui.externalApps.push_back({ item.first, item.second });
+ for (const auto& [description, cmdLine] : extApps)
+ cfg.gui.externalApps.push_back({ description, cmdLine });
}
}
else
@@ -1683,7 +1681,7 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer)
//TODO: remove macro migration after some time! 2016-06-30
if (formatVer < 3)
- for (auto& item : cfg.gui.externalApps)
+ for (ExternalApp& item : cfg.gui.externalApps)
{
replace(item.cmdLine, Zstr("%item2_path%"), Zstr("%item_path2%"));
replace(item.cmdLine, Zstr("%item_folder%"), Zstr("%folder_path%"));
@@ -1703,7 +1701,7 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer)
}
}
//TODO: remove macro migration after some time! 2016-07-18
- for (auto& item : cfg.gui.externalApps)
+ for (ExternalApp& item : cfg.gui.externalApps)
replace(item.cmdLine, Zstr("%item_folder%"), Zstr("%folder_path%"));
//last update check
@@ -1740,7 +1738,7 @@ int getConfigFormatVersion(const XmlDoc& doc)
template <class ConfigType>
void readConfig(const Zstring& filePath, XmlType type, ConfigType& cfg, int currentXmlFormatVer, std::wstring& warningMsg) //throw FileError
{
- XmlDoc doc = loadXmlDocument(filePath); //throw FileError
+ XmlDoc doc = loadXml(filePath); //throw FileError
if (getXmlTypeNoThrow(doc) != type) //noexcept
throw FileError(replaceCpy(_("File %x does not contain a valid configuration."), L"%x", fmtPath(filePath)));
@@ -1752,7 +1750,7 @@ void readConfig(const Zstring& filePath, XmlType type, ConfigType& cfg, int curr
try
{
- checkForMappingErrors(in, filePath); //throw FileError
+ checkXmlMappingErrors(in, filePath); //throw FileError
//(try to) migrate old configuration automatically
if (formatVer < currentXmlFormatVer)
@@ -1795,7 +1793,7 @@ XmlCfg parseConfig(const XmlDoc& doc, const Zstring& filePath, int currentXmlFor
try
{
- checkForMappingErrors(in, filePath); //throw FileError
+ checkXmlMappingErrors(in, filePath); //throw FileError
//(try to) migrate old configuration if needed
if (formatVer < currentXmlFormatVer)
@@ -1824,7 +1822,7 @@ void fff::readAnyConfig(const std::vector<Zstring>& filePaths, XmlGuiConfig& cfg
const Zstring& filePath = *it;
const bool firstItem = it == filePaths.begin(); //init all non-"mainCfg" settings with first config file
- XmlDoc doc = loadXmlDocument(filePath); //throw FileError
+ XmlDoc doc = loadXml(filePath); //throw FileError
switch (getXmlTypeNoThrow(doc))
{
@@ -2041,7 +2039,6 @@ void writeConfig(const XmlGlobalSettings& cfg, XmlOut& out)
outGeneral["CopyLockedFiles" ].attribute("Enabled", cfg.copyLockedFiles);
outGeneral["CopyFilePermissions" ].attribute("Enabled", cfg.copyFilePermissions);
outGeneral["FileTimeTolerance" ].attribute("Seconds", cfg.fileTimeTolerance);
- outGeneral["FolderAccessTimeout" ].attribute("Seconds", cfg.folderAccessTimeout);
outGeneral["RunWithBackgroundPriority"].attribute("Enabled", cfg.runWithBackgroundPriority);
outGeneral["LockDirectoriesDuringSync"].attribute("Enabled", cfg.createLockFile);
outGeneral["VerifyCopiedFiles" ].attribute("Enabled", cfg.verifyFileCopy);
@@ -2167,7 +2164,7 @@ void writeConfig(const ConfigType& cfg, XmlType type, int xmlFormatVer, const Zs
XmlOut out(doc);
writeConfig(cfg, out);
- saveXmlDocument(doc, filePath); //throw FileError
+ saveXml(doc, filePath); //throw FileError
}
}
@@ -2191,7 +2188,7 @@ void fff::writeConfig(const XmlGlobalSettings& cfg, const Zstring& filePath)
std::wstring fff::extractJobName(const Zstring& cfgFilePath)
{
- const Zstring shortName = afterLast(cfgFilePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL);
- const Zstring jobName = beforeLast(shortName, Zstr('.'), IF_MISSING_RETURN_ALL);
+ const Zstring fileName = afterLast(cfgFilePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL);
+ const Zstring jobName = beforeLast(fileName, Zstr('.'), IF_MISSING_RETURN_ALL);
return utfTo<std::wstring>(jobName);
}
diff --git a/FreeFileSync/Source/base/process_xml.h b/FreeFileSync/Source/base/process_xml.h
index 17cb884a..85777a5c 100755
--- a/FreeFileSync/Source/base/process_xml.h
+++ b/FreeFileSync/Source/base/process_xml.h
@@ -7,7 +7,6 @@
#ifndef PROCESS_XML_H_28345825704254262435
#define PROCESS_XML_H_28345825704254262435
-#include <zen/xml_io.h>
#include <wx/gdicmn.h>
#include "localization.h"
#include "structures.h"
@@ -175,7 +174,6 @@ struct XmlGlobalSettings
bool copyFilePermissions = false;
int fileTimeTolerance = 2; //max. allowed file time deviation; < 0 means unlimited tolerance; default 2s: FAT vs NTFS
- std::chrono::seconds folderAccessTimeout{20}; //consider CD-ROM insert or hard disk spin up time from sleep
bool runWithBackgroundPriority = false;
bool createLockFile = true;
bool verifyFileCopy = false;
diff --git a/FreeFileSync/Source/base/resolve_path.cpp b/FreeFileSync/Source/base/resolve_path.cpp
index f8eae4d9..49699fa4 100755
--- a/FreeFileSync/Source/base/resolve_path.cpp
+++ b/FreeFileSync/Source/base/resolve_path.cpp
@@ -29,8 +29,8 @@ std::optional<Zstring> getEnvironmentVar(const Zstring& name)
trim(value); //remove leading, trailing blanks
//remove leading, trailing double-quotes
- if (startsWith(value, Zstr('\"')) &&
- endsWith (value, Zstr('\"')) &&
+ if (startsWith(value, Zstr('"')) &&
+ endsWith (value, Zstr('"')) &&
value.length() >= 2)
value = Zstring(value.c_str() + 1, value.length() - 2);
@@ -77,23 +77,24 @@ Zstring resolveRelativePath(const Zstring& relativePath)
+
//returns value if resolved
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 (strEqual(macro, Zstr("time"), CmpAsciiNoCase()))
+ if (equalAsciiNoCase(macro, Zstr("time")))
return formatTime<Zstring>(Zstr("%H%M%S"));
- if (strEqual(macro, Zstr("date"), CmpAsciiNoCase()))
+ if (equalAsciiNoCase(macro, Zstr("date")))
return formatTime<Zstring>(FORMAT_ISO_DATE);
- if (strEqual(macro, Zstr("timestamp"), CmpAsciiNoCase()))
+ if (equalAsciiNoCase(macro, Zstr("timestamp")))
return formatTime<Zstring>(Zstr("%Y-%m-%d %H%M%S")); //e.g. "2012-05-15 131513"
Zstring timeStr;
auto resolveTimePhrase = [&](const Zchar* phrase, const Zchar* format) -> bool
{
- if (!strEqual(macro, phrase, CmpAsciiNoCase()))
+ if (!equalAsciiNoCase(macro, phrase))
return false;
timeStr = formatTime<Zstring>(format);
@@ -188,12 +189,10 @@ void getDirectoryAliasesRecursive(const Zstring& pathPhrase, std::set<Zstring, L
addEnvVar("HOME"); //Linux: /home/<user> Mac: /Users/<user>
//addEnvVar("USER"); -> any benefit?
//substitute paths by symbolic names
- for (const auto& item : macroList)
+ for (const auto& [macroName, macroPath] : macroList)
{
- const Zstring& macroName = item.first;
- const Zstring& macroPath = item.second;
-
- const Zstring pathSubst = ciReplaceCpy(pathPhrase, macroPath, MACRO_SEP + macroName + MACRO_SEP); //ci on Linux, too? okay
+ //should use a replaceCpy() that considers "local path" case-sensitivity (if only we had one...)
+ const Zstring pathSubst = replaceCpyAsciiNoCase(pathPhrase, macroPath, MACRO_SEP + macroName + MACRO_SEP);
if (pathSubst != pathPhrase)
output.insert(pathSubst);
}
diff --git a/FreeFileSync/Source/base/status_handler.h b/FreeFileSync/Source/base/status_handler.h
index 9a3c0948..59d5c80c 100755
--- a/FreeFileSync/Source/base/status_handler.h
+++ b/FreeFileSync/Source/base/status_handler.h
@@ -86,7 +86,7 @@ public:
//implement parts of ProcessCallback
void initNewPhase(int itemsTotal, int64_t bytesTotal, Phase phase) override //(throw X)
{
- assert(itemsTotal < 0 == bytesTotal < 0);
+ assert((itemsTotal < 0) == (bytesTotal < 0));
currentPhase_ = phase;
refStats(statsTotal_, currentPhase_) = { itemsTotal, bytesTotal };
}
diff --git a/FreeFileSync/Source/base/status_handler_impl.h b/FreeFileSync/Source/base/status_handler_impl.h
index 21f108de..af578b82 100755
--- a/FreeFileSync/Source/base/status_handler_impl.h
+++ b/FreeFileSync/Source/base/status_handler_impl.h
@@ -131,9 +131,9 @@ public:
void notifyTaskBegin(size_t prio) //noexcept
{
assert(!zen::runningMainThread());
- assert(!getThreadStatus());
const uint64_t threadId = zen::getThreadId();
std::lock_guard<std::mutex> dummy(lockCurrentStatus_);
+ assert(!getThreadStatus());
//const size_t taskIdx = [&]() -> size_t
//{
@@ -160,7 +160,7 @@ public:
const uint64_t threadId = zen::getThreadId();
std::lock_guard<std::mutex> dummy(lockCurrentStatus_);
- for (auto& sbp : statusByPriority_)
+ for (std::vector<ThreadStatus>& sbp : statusByPriority_)
for (ThreadStatus& ts : sbp)
if (ts.threadId == threadId)
{
@@ -196,7 +196,7 @@ private:
assert(!zen::runningMainThread());
const uint64_t threadId = zen::getThreadId();
- for (auto& sbp : statusByPriority_)
+ for (std::vector<ThreadStatus>& sbp : statusByPriority_)
for (ThreadStatus& ts : sbp) //thread count is (hopefully) small enough so that linear search won't hurt perf
if (ts.threadId == threadId)
return &ts;
@@ -250,7 +250,7 @@ private:
statusMsg = [&]
{
- for (const auto& sbp : statusByPriority_)
+ for (const std::vector<ThreadStatus>& sbp : statusByPriority_)
for (const ThreadStatus& ts : sbp)
if (!ts.statusMsg.empty())
return ts.statusMsg;
@@ -386,10 +386,7 @@ struct ParallelContext
const AddTaskCallback& scheduleExtraTask; //throw ThreadInterruption
};
-#ifdef __GNUC__ //ugly, but we won't put the function into a cpp nor make it inline
- #pragma GCC diagnostic push
- #pragma GCC diagnostic ignored "-Wunused-function"
-#endif
+
namespace
{
void massParallelExecute(const std::vector<std::pair<AbstractPath, ParallelWorkItem>>& workload,
@@ -423,13 +420,12 @@ void massParallelExecute(const std::vector<std::pair<AbstractPath, ParallelWorkI
//---------------------------------------------------------------------------------------------------------
//Attention: carefully orchestrate access to deviceThreadGroups and its contained worker threads! e.g. synchronize potential access during ~DeviceThreadGroup!
- for (const auto& devItems : perDeviceWorkload)
+ for (const auto& [rootPath, wl] : perDeviceWorkload)
{
- const AbstractPath& rootPath = devItems.first;
const size_t parallelOps = getDeviceParallelOps(deviceParallelOps, rootPath);
const size_t statusPrio = deviceThreadGroups.size();
- auto scheduleExtraTask = [&acb, &deviceThreadGroupsShared, rootPath](const AfsPath& afsPath, const ParallelWorkItem& task)
+ auto scheduleExtraTask = [&acb, &deviceThreadGroupsShared, rootPath = rootPath /*clang bug :>*/](const AfsPath& afsPath, const ParallelWorkItem& task)
{
const AbstractPath& itemPath = AFS::appendRelPath(rootPath, afsPath.value);
@@ -459,12 +455,11 @@ void massParallelExecute(const std::vector<std::pair<AbstractPath, ParallelWorkI
//[!] deviceThreadGroups is shared with worker threads from here on!
ZEN_ON_SCOPE_EXIT(deviceThreadGroupsShared.access([&](auto*& deviceThreadGroupsPtr) { deviceThreadGroupsPtr = nullptr; }));
- for (const auto& devItems : perDeviceWorkload)
+ for (const auto& [rootPath, wl] : perDeviceWorkload)
{
- const AbstractPath& rootPath = devItems.first;
ThreadGroupContext& ctx = deviceThreadGroups.find(rootPath)->second; //exists after construction above!
- for (const std::pair<AbstractPath, ParallelWorkItem>* item : devItems.second)
+ for (const std::pair<AbstractPath, ParallelWorkItem>* item : wl)
ctx.threadGroup.run([&acb, statusPrio = ctx.statusPrio, &itemPath = item->first, &task = item->second, &scheduleExtraTask = ctx.scheduleExtraTask]
{
acb.notifyTaskBegin(statusPrio);
@@ -487,9 +482,6 @@ void massParallelExecute(const std::vector<std::pair<AbstractPath, ParallelWorkI
acb.waitUntilDone(UI_UPDATE_INTERVAL / 2 /*every ~50 ms*/, callback); //throw X
}
}
-#ifdef __GNUC__
- #pragma GCC diagnostic pop
-#endif
//=====================================================================================================================
diff --git a/FreeFileSync/Source/base/structures.cpp b/FreeFileSync/Source/base/structures.cpp
index 19c39aac..e39084ea 100755
--- a/FreeFileSync/Source/base/structures.cpp
+++ b/FreeFileSync/Source/base/structures.cpp
@@ -605,8 +605,8 @@ MainConfiguration fff::merge(const std::vector<MainConfiguration>& mainCfgs)
std::map<AbstractPath, size_t> mergedParallelOps;
for (const MainConfiguration& mainCfg : mainCfgs)
- for (const auto& item : mainCfg.deviceParallelOps)
- mergedParallelOps[item.first] = std::max(mergedParallelOps[item.first], item.second);
+ for (const auto& [rootPath, parallelOps] : mainCfg.deviceParallelOps)
+ mergedParallelOps[rootPath] = std::max(mergedParallelOps[rootPath], parallelOps);
//final assembly
MainConfiguration cfgOut;
diff --git a/FreeFileSync/Source/base/synchronization.cpp b/FreeFileSync/Source/base/synchronization.cpp
index d55a57a8..25723ce2 100755
--- a/FreeFileSync/Source/base/synchronization.cpp
+++ b/FreeFileSync/Source/base/synchronization.cpp
@@ -124,7 +124,7 @@ void SyncStatistics::processFile(const FilePair& file)
break;
case SO_UNRESOLVED_CONFLICT:
- conflictMsgs_.push_back({ file.getPairRelativePath(), file.getSyncOpConflict() });
+ conflictMsgs_.push_back({ file.getRelativePathAny(), file.getSyncOpConflict() });
break;
case SO_COPY_METADATA_TO_LEFT:
@@ -178,7 +178,7 @@ void SyncStatistics::processLink(const SymlinkPair& link)
break;
case SO_UNRESOLVED_CONFLICT:
- conflictMsgs_.push_back({ link.getPairRelativePath(), link.getSyncOpConflict() });
+ conflictMsgs_.push_back({ link.getRelativePathAny(), link.getSyncOpConflict() });
break;
case SO_MOVE_LEFT_FROM:
@@ -217,7 +217,7 @@ void SyncStatistics::processFolder(const FolderPair& folder)
break;
case SO_UNRESOLVED_CONFLICT:
- conflictMsgs_.push_back({ folder.getPairRelativePath(), folder.getSyncOpConflict() });
+ conflictMsgs_.push_back({ folder.getRelativePathAny(), folder.getSyncOpConflict() });
break;
case SO_OVERWRITE_LEFT:
@@ -497,8 +497,8 @@ bool removeSymlinkIfExists(const AbstractPath& ap, std::mutex& singleThread) //t
{ return parallelScope([ap] { return AFS::removeSymlinkIfExists(ap); /*throw FileError*/ }, singleThread); }
inline
-void renameItem(const AbstractPath& apSource, const AbstractPath& apTarget, std::mutex& singleThread) //throw FileError, ErrorDifferentVolume
-{ parallelScope([apSource, apTarget] { AFS::renameItem(apSource, apTarget); /*throw FileError, ErrorDifferentVolume*/ }, singleThread); }
+void moveAndRenameItem(const AbstractPath& apSource, const AbstractPath& apTarget, std::mutex& singleThread) //throw FileError, ErrorDifferentVolume
+{ parallelScope([apSource, apTarget] { AFS::moveAndRenameItem(apSource, apTarget); /*throw FileError, ErrorDifferentVolume*/ }, singleThread); }
inline
AbstractPath getSymlinkResolvedPath(const AbstractPath& ap, std::mutex& singleThread) //throw FileError
@@ -733,6 +733,7 @@ void DeletionHandler::removeDirWithCallback(const AbstractPath& folderPath,//thr
statReporter.reportStatus(replaceCpy(statusText, L"%x", fmtPath(displayPath))); //throw ThreadInterruption
statReporter.reportDelta(1, 0); //it would be more correct to report *after* work was done!
//OTOH: ThreadInterruption must not happen after last deletion was successful: allow for transactional file model update!
+ warn_static("=> indeed; fix!?")
};
static_assert(std::is_const_v<decltype(txtRemovingFile_)>, "callbacks better be thread-safe!");
auto onBeforeFileDeletion = [&](const std::wstring& displayPath) { notifyDeletion(txtRemovingFile_, displayPath); };
@@ -754,6 +755,7 @@ void DeletionHandler::removeDirWithCallback(const AbstractPath& folderPath,//thr
{
statReporter.reportStatus(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!?")
};
static_assert(std::is_const_v<decltype(txtMovingFileXtoY_)>, "callbacks better be thread-safe!");
auto onBeforeFileMove = [&](const std::wstring& displayPathFrom, const std::wstring& displayPathTo) { notifyMove(txtMovingFileXtoY_, displayPathFrom, displayPathTo); };
@@ -1056,7 +1058,7 @@ void FolderPairSyncer::runPass(PassNo pass, SyncCtx& syncCtx, BaseFolderPair& ba
workload.addWorkItems(fps.getFolderLevelWorkItems(pass, baseFolder, workload)); //initial workload: set *before* threads get access!
std::vector<InterruptibleThread> worker;
- ZEN_ON_SCOPE_EXIT( for (InterruptibleThread& wt : worker) wt.join (); );
+ ZEN_ON_SCOPE_EXIT( for (InterruptibleThread& wt : worker) wt.join (); ); //
ZEN_ON_SCOPE_EXIT( for (InterruptibleThread& wt : worker) wt.interrupt(); ); //interrupt all first, then join
for (size_t threadIdx = 0; threadIdx < threadCount; ++threadIdx)
@@ -1164,10 +1166,10 @@ III) c -> d caveat: move-sequence needs to be processed in correct order!
*/
template <class List> inline
-bool haveNameClash(const Zstring& shortname, const List& m)
+bool haveNameClash(const Zstring& itemName, const List& m)
{
return std::any_of(m.begin(), m.end(),
- [&](const typename List::value_type& obj) { return equalFilePath(obj.getPairItemName(), shortname); });
+ [&](const typename List::value_type& obj) { return equalNoCase(obj.getItemNameAny(), itemName); }); //equalNoCase: when in doubt => assume name clash!
}
@@ -1192,7 +1194,7 @@ void FolderPairSyncer::setup2StepMove(FilePair& sourceFile, //throw FileError, T
AFS::getDisplayPath(sourceFile.getAbstractPath<side>()),
AFS::getDisplayPath(sourcePathTmp));
- parallel::renameItem(sourceFile.getAbstractPath<side>(), sourcePathTmp, singleThread_); //throw FileError, (ErrorDifferentVolume)
+ parallel::moveAndRenameItem(sourceFile.getAbstractPath<side>(), sourcePathTmp, singleThread_); //throw FileError, (ErrorDifferentVolume)
//TODO: prepare2StepMove: consider ErrorDifferentVolume! e.g. symlink aliasing!
@@ -1226,9 +1228,9 @@ auto FolderPairSyncer::createMoveTargetFolder(FileSystemObject& fsObj) -> CmtfSt
return cmtfs;
//detect (and try to resolve) file type conflicts: 1. symlinks 2. files
- const Zstring& shortname = parentFolder->getPairItemName();
- if (haveNameClash(shortname, parentFolder->parent().refSubLinks()) ||
- haveNameClash(shortname, parentFolder->parent().refSubFiles()))
+ const Zstring& folderName = parentFolder->getItemNameAny();
+ if (haveNameClash(folderName, parentFolder->parent().refSubLinks()) ||
+ haveNameClash(folderName, parentFolder->parent().refSubFiles()))
return CmtfStatus::NAME_CLASH;
//-------- create parent folder if needed --------------
@@ -1266,7 +1268,7 @@ auto FolderPairSyncer::createMoveTargetFolder(FileSystemObject& fsObj) -> CmtfSt
}
else //source deleted meanwhile...
{
- //attention when fixing statistics due to missing folder: child items may be scheduled for move, so deletion will have move references flip back to copy + delete!
+ //attention when fixing statistics due to missing folder: child items may be scheduled for move, so deletion will have move-references flip back to copy + delete!
const SyncStatistics statsBefore(parentFolder->base()); //=> don't bother considering move operations, just calculate over the whole tree
parentFolder->removeObject<sideSrc>(); //DON'T physically delete child objects while we're still evaluating them, e.g. fsObj, and in caller code!!!
const SyncStatistics statsAfter(parentFolder->base());
@@ -1339,8 +1341,8 @@ void FolderPairSyncer::resolveMoveConflicts(FilePair& sourceFile, //throw FileEr
auto haveNameClash = [](const FilePair& file)
{
- return ::haveNameClash(file.getPairItemName(), file.parent().refSubLinks()) ||
- ::haveNameClash(file.getPairItemName(), file.parent().refSubFolders());
+ return ::haveNameClash(file.getItemNameAny(), file.parent().refSubLinks()) ||
+ ::haveNameClash(file.getItemNameAny(), file.parent().refSubFolders());
};
if (sourceWillBeDeleted || haveNameClash(sourceFile))
@@ -1450,7 +1452,7 @@ bool FolderPairSyncer::needZeroPass(const FilePair& file)
//1st, 2nd pass requirements:
// - avoid disk space shortage: 1. delete files, 2. overwrite big with small files first
-// - support change in type: overwrite file by directory, symlink by file, ect.
+// - support change in type: overwrite file by directory, symlink by file, etc.
inline
FolderPairSyncer::PassNo FolderPairSyncer::getPass(const FilePair& file)
@@ -1585,7 +1587,6 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp)
if (parentFolder->isEmpty<sideTrg>()) //BaseFolderPair OTOH is always non-empty and existing in this context => else: fatal error in zen::synchronize()
return; //if parent directory creation failed, there's no reason to show more errors!
- //can't use "getAbstractPath<sideTrg>()" as file name is not available!
const AbstractPath targetPath = file.getAbstractPath<sideTrg>();
reportInfo(txtCreatingFile_, AFS::getDisplayPath(targetPath)); //throw ThreadInterruption
@@ -1636,7 +1637,7 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp)
AsyncItemStatReporter statReporter(1, 0, acb_);
delHandlerTrg.removeFileWithCallback({ file.getAbstractPath<sideTrg>(), file.getAttributes<sideTrg>() },
- file.getPairRelativePath(), statReporter, singleThread_); //throw FileError, X
+ file.getRelativePath<sideTrg>(), statReporter, singleThread_); //throw FileError, X
file.removeObject<sideTrg>(); //update FilePair
}
break;
@@ -1660,7 +1661,7 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp)
//TODO: synchronizeFileInt: consider ErrorDifferentVolume! e.g. symlink aliasing!
- parallel::renameItem(pathFrom, pathTo, singleThread_); //throw FileError, (ErrorDifferentVolume)
+ parallel::moveAndRenameItem(pathFrom, pathTo, singleThread_); //throw FileError, (ErrorDifferentVolume)
statReporter.reportDelta(1, 0);
@@ -1694,8 +1695,9 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp)
AsyncItemStatReporter statReporter(1, file.getFileSize<sideSrc>(), acb_);
if (file.isFollowedSymlink<sideTrg>()) //since we follow the link, we need to sync case sensitivity of the link manually!
- if (file.getItemName<sideTrg>() != file.getItemName<sideSrc>()) //have difference in case?
- parallel::renameItem(file.getAbstractPath<sideTrg>(), targetPathLogical, singleThread_); //throw FileError, (ErrorDifferentVolume)
+ if (getUnicodeNormalForm(file.getItemName<sideTrg>()) !=
+ getUnicodeNormalForm(file.getItemName<sideSrc>())) //have difference in case?
+ parallel::moveAndRenameItem(file.getAbstractPath<sideTrg>(), targetPathLogical, singleThread_); //throw FileError, (ErrorDifferentVolume)
auto onDeleteTargetFile = [&] //delete target at appropriate time
{
@@ -1704,7 +1706,7 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp)
FileAttributes followedTargetAttr = file.getAttributes<sideTrg>();
followedTargetAttr.isFollowedSymlink = false;
- delHandlerTrg.removeFileWithCallback({ targetPathResolvedOld, followedTargetAttr }, file.getPairRelativePath(), statReporter, singleThread_); //throw FileError, X
+ delHandlerTrg.removeFileWithCallback({ targetPathResolvedOld, followedTargetAttr }, file.getRelativePath<sideTrg>(), statReporter, singleThread_); //throw FileError, X
//no (logical) item count update desired - but total byte count may change, e.g. move(copy) old file to versioning dir
statReporter.reportDelta(-1, 0); //undo item stats reporting within DeletionHandler::removeFileWithCallback()
@@ -1742,10 +1744,12 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp)
{
AsyncItemStatReporter statReporter(1, 0, acb_);
- assert(file.getItemName<sideTrg>() != file.getItemName<sideSrc>());
- if (file.getItemName<sideTrg>() != file.getItemName<sideSrc>()) //have difference in case?
- parallel::renameItem(file.getAbstractPath<sideTrg>(), //throw FileError, (ErrorDifferentVolume)
- AFS::appendRelPath(file.parent().getAbstractPath<sideTrg>(), file.getItemName<sideSrc>()), singleThread_);
+ if (getUnicodeNormalForm(file.getItemName<sideTrg>()) !=
+ getUnicodeNormalForm(file.getItemName<sideSrc>())) //have difference in case?
+ parallel::moveAndRenameItem(file.getAbstractPath<sideTrg>(), //throw FileError, (ErrorDifferentVolume)
+ AFS::appendRelPath(file.parent().getAbstractPath<sideTrg>(), file.getItemName<sideSrc>()), singleThread_);
+ else
+ assert(false);
#if 0 //changing file time without copying content is not justified after CompareVariant::SIZE finds "equal" files! similar issue with CompareVariant::TIME_SIZE and FileTimeTolerance == -1
//Bonus: some devices don't support setting (precise) file times anyway, e.g. FAT or MTP!
@@ -1852,7 +1856,7 @@ void FolderPairSyncer::synchronizeLinkInt(SymlinkPair& symlink, SyncOperation sy
{
AsyncItemStatReporter statReporter(1, 0, acb_);
- delHandlerTrg.removeLinkWithCallback(symlink.getAbstractPath<sideTrg>(), symlink.getPairRelativePath(), statReporter, singleThread_); //throw FileError, X
+ delHandlerTrg.removeLinkWithCallback(symlink.getAbstractPath<sideTrg>(), symlink.getRelativePath<sideTrg>(), statReporter, singleThread_); //throw FileError, X
symlink.removeObject<sideTrg>(); //update SymlinkPair
}
@@ -1865,7 +1869,7 @@ void FolderPairSyncer::synchronizeLinkInt(SymlinkPair& symlink, SyncOperation sy
AsyncItemStatReporter statReporter(1, 0, acb_);
//reportStatus(delHandlerTrg.getTxtRemovingSymLink(), AFS::getDisplayPath(symlink.getAbstractPath<sideTrg>()));
- delHandlerTrg.removeLinkWithCallback(symlink.getAbstractPath<sideTrg>(), symlink.getPairRelativePath(), statReporter, singleThread_); //throw FileError, X
+ delHandlerTrg.removeLinkWithCallback(symlink.getAbstractPath<sideTrg>(), symlink.getRelativePath<sideTrg>(), statReporter, singleThread_); //throw FileError, X
statReporter.reportDelta(-1, 0); //undo item stats reporting within DeletionHandler::removeLinkWithCallback()
//symlink.removeObject<sideTrg>(); -> "symlink, sideTrg" evaluated below!
@@ -1892,9 +1896,12 @@ void FolderPairSyncer::synchronizeLinkInt(SymlinkPair& symlink, SyncOperation sy
{
AsyncItemStatReporter statReporter(1, 0, acb_);
- if (symlink.getItemName<sideTrg>() != symlink.getItemName<sideSrc>()) //have difference in case?
- parallel::renameItem(symlink.getAbstractPath<sideTrg>(), //throw FileError, (ErrorDifferentVolume)
- AFS::appendRelPath(symlink.parent().getAbstractPath<sideTrg>(), symlink.getItemName<sideSrc>()), singleThread_);
+ if (getUnicodeNormalForm(symlink.getItemName<sideTrg>()) !=
+ getUnicodeNormalForm(symlink.getItemName<sideSrc>())) //have difference in case?
+ parallel::moveAndRenameItem(symlink.getAbstractPath<sideTrg>(), //throw FileError, (ErrorDifferentVolume)
+ AFS::appendRelPath(symlink.parent().getAbstractPath<sideTrg>(), symlink.getItemName<sideSrc>()), singleThread_);
+ else
+ assert(false);
//if (symlink.getLastWriteTime<sideTrg>() != symlink.getLastWriteTime<sideSrc>())
// //- no need to call sameFileTime() or respect 2 second FAT/FAT32 precision in this comparison
@@ -1984,7 +1991,7 @@ void FolderPairSyncer::synchronizeFolderInt(FolderPair& folder, SyncOperation sy
}
else //source deleted meanwhile...
{
- //attention when fixing statistics due to missing folder: child items may be scheduled for move, so deletion will have move references flip back to copy + delete!
+ //attention when fixing statistics due to missing folder: child items may be scheduled for move, so deletion will have move-references flip back to copy + delete!
const SyncStatistics statsBefore(folder.base()); //=> don't bother considering move operations, just calculate over the whole tree
folder.refSubFiles ().clear(); //
folder.refSubLinks ().clear(); //update FolderPair
@@ -2007,7 +2014,7 @@ void FolderPairSyncer::synchronizeFolderInt(FolderPair& folder, SyncOperation sy
const SyncStatistics subStats(folder); //counts sub-objects only!
AsyncItemStatReporter statReporter(1 + getCUD(subStats), subStats.getBytesToProcess(), acb_);
- delHandlerTrg.removeDirWithCallback(folder.getAbstractPath<sideTrg>(), folder.getPairRelativePath(), statReporter, singleThread_); //throw FileError, X
+ delHandlerTrg.removeDirWithCallback(folder.getAbstractPath<sideTrg>(), folder.getRelativePath<sideTrg>(), statReporter, singleThread_); //throw FileError, X
//TODO: implement parallel folder deletion
@@ -2026,10 +2033,12 @@ void FolderPairSyncer::synchronizeFolderInt(FolderPair& folder, SyncOperation sy
{
AsyncItemStatReporter statReporter(1, 0, acb_);
- assert(folder.getItemName<sideTrg>() != folder.getItemName<sideSrc>());
- if (folder.getItemName<sideTrg>() != folder.getItemName<sideSrc>()) //have difference in case?
- parallel::renameItem(folder.getAbstractPath<sideTrg>(), //throw FileError, (ErrorDifferentVolume)
- AFS::appendRelPath(folder.parent().getAbstractPath<sideTrg>(), folder.getItemName<sideSrc>()), singleThread_);
+ if (getUnicodeNormalForm(folder.getItemName<sideTrg>()) !=
+ getUnicodeNormalForm(folder.getItemName<sideSrc>())) //have difference in case?
+ parallel::moveAndRenameItem(folder.getAbstractPath<sideTrg>(), //throw FileError, (ErrorDifferentVolume)
+ AFS::appendRelPath(folder.parent().getAbstractPath<sideTrg>(), folder.getItemName<sideSrc>()), singleThread_);
+ else
+ assert(false);
//copyFileTimes -> useless: modification time changes with each child-object creation/deletion
statReporter.reportDelta(1, 0);
@@ -2109,7 +2118,7 @@ AFS::FileCopyResult FolderPairSyncer::copyFileWithCallback(const FileDescriptor&
//###########################################################################################
template <SelectedSide side>
-bool baseFolderDrop(BaseFolderPair& baseFolder, std::chrono::seconds folderAccessTimeout, ProcessCallback& callback)
+bool baseFolderDrop(BaseFolderPair& baseFolder, ProcessCallback& callback)
{
const AbstractPath folderPath = baseFolder.getAbstractPath<side>();
@@ -2118,7 +2127,7 @@ bool baseFolderDrop(BaseFolderPair& baseFolder, std::chrono::seconds folderAcces
const std::wstring errMsg = tryReportingError([&]
{
const FolderStatus status = getFolderStatusNonBlocking({ folderPath }, {} /*deviceParallelOps*/,
- folderAccessTimeout, false /*allowUserInteraction*/, callback);
+ false /*allowUserInteraction*/, callback);
static_assert(std::is_same_v<decltype(status.failedChecks.begin()->second), FileError>);
if (!status.failedChecks.empty())
@@ -2137,7 +2146,7 @@ bool baseFolderDrop(BaseFolderPair& baseFolder, std::chrono::seconds folderAcces
template <SelectedSide side> //create base directories first (if not yet existing) -> no symlink or attribute copying!
-bool createBaseFolder(BaseFolderPair& baseFolder, bool copyFilePermissions, std::chrono::seconds folderAccessTimeout, ProcessCallback& callback) //return false if fatal error occurred
+bool createBaseFolder(BaseFolderPair& baseFolder, bool copyFilePermissions, ProcessCallback& callback) //return false if fatal error occurred
{
static const SelectedSide sideSrc = OtherSide<side>::value;
const AbstractPath baseFolderPath = baseFolder.getAbstractPath<side>();
@@ -2151,7 +2160,7 @@ bool createBaseFolder(BaseFolderPair& baseFolder, bool copyFilePermissions, std:
const std::wstring errMsg = tryReportingError([&]
{
const FolderStatus status = getFolderStatusNonBlocking({ baseFolderPath }, {} /*deviceParallelOps*/,
- folderAccessTimeout, false /*allowUserInteraction*/, callback);
+ false /*allowUserInteraction*/, callback);
static_assert(std::is_same_v<decltype(status.failedChecks.begin()->second), FileError>);
if (!status.failedChecks.empty())
@@ -2206,7 +2215,6 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime
bool copyFilePermissions,
bool failSafeFileCopy,
bool runWithBackgroundPriority,
- std::chrono::seconds folderAccessTimeout,
const std::vector<FolderPairSyncCfg>& syncConfig,
FolderComparison& folderCmp,
const std::map<AbstractPath, size_t>& deviceParallelOps,
@@ -2329,8 +2337,8 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime
//check for network drops after comparison
// - convenience: exit sync right here instead of showing tons of errors during file copy
// - early failure! there's no point in evaluating subsequent warnings
- if (baseFolderDrop< LEFT_SIDE>(baseFolder, folderAccessTimeout, callback) ||
- baseFolderDrop<RIGHT_SIDE>(baseFolder, folderAccessTimeout, callback))
+ if (baseFolderDrop< LEFT_SIDE>(baseFolder, callback) ||
+ baseFolderDrop<RIGHT_SIDE>(baseFolder, callback))
{
jobType[folderIndex] = FolderPairJobType::SKIP;
continue;
@@ -2456,10 +2464,10 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime
{
std::wstring msg = _("The following folders are significantly different. Please check that the correct folders are selected for synchronization.");
- for (const auto& item : significantDiffPairs)
+ for (const auto& [folderPathL, folderPathR] : significantDiffPairs)
msg += L"\n\n" +
- AFS::getDisplayPath(item.first) + L" <-> " + L"\n" +
- AFS::getDisplayPath(item.second);
+ AFS::getDisplayPath(folderPathL) + L" <-> " + L"\n" +
+ AFS::getDisplayPath(folderPathR);
callback.reportWarning(msg, warnings.warnSignificantDifference);
}
@@ -2469,10 +2477,10 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime
{
std::wstring msg = _("Not enough free disk space available in:");
- for (const auto& item : diskSpaceMissing)
- msg += L"\n\n" + AFS::getDisplayPath(item.first) + L"\n" +
- _("Required:") + L" " + formatFilesizeShort(item.second.first) + L"\n" +
- _("Available:") + L" " + formatFilesizeShort(item.second.second);
+ for (const auto& [folderPath, space] : diskSpaceMissing)
+ 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);
}
@@ -2480,9 +2488,9 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime
//windows: check if recycle bin really exists; if not, Windows will silently delete, which is wrong
{
std::wstring msg;
- for (const auto& item : recyclerSupported)
- if (!item.second)
- msg += L"\n" + AFS::getDisplayPath(item.first);
+ for (const auto& [folderPath, supported] : recyclerSupported)
+ if (!supported)
+ 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,
@@ -2524,18 +2532,18 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime
{
std::map<AbstractPath, std::wstring> uniqueMsgs; //=> at most one msg per base folder (*and* per versioningFolderPath)
- for (const auto& item : verCheckBaseFolderPaths) //may contain duplicate paths, but with *different* hard filter!
- if (std::optional<PathDependency> pd = getPathDependency(versioningFolderPath, NullFilter(), item.first, *item.second))
+ for (const auto& [folderPath, filter] : verCheckBaseFolderPaths) //may contain duplicate paths, but with *different* hard filter!
+ 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(item.first);
- if (AFS::equalAbstractPath(pd->basePathParent, item.first) && !pd->relPath.empty())
+ L"\n" + _("Base folder:") + L" \t" + AFS::getDisplayPath(folderPath);
+ if (AFS::equalAbstractPath(pd->basePathParent, folderPath) && !pd->relPath.empty())
line += L"\n" + _("Exclude:") + L" \t" + utfTo<std::wstring>(FILE_NAME_SEPARATOR + pd->relPath + FILE_NAME_SEPARATOR);
- uniqueMsgs[item.first] = line;
+ uniqueMsgs[folderPath] = line;
}
- for (const auto& item : uniqueMsgs)
- msg += item.second;
+ for (const auto& [folderPath, perFolderMsg] : uniqueMsgs)
+ msg += perFolderMsg;
}
if (!msg.empty())
callback.reportWarning(_("The versioning folder is contained in a base folder.") + L"\n" +
@@ -2568,14 +2576,14 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime
//------------------------------------------------------------------------------------------
//checking a second time: (a long time may have passed since folder comparison!)
- if (baseFolderDrop< LEFT_SIDE>(baseFolder, folderAccessTimeout, callback) ||
- baseFolderDrop<RIGHT_SIDE>(baseFolder, folderAccessTimeout, callback))
+ if (baseFolderDrop< LEFT_SIDE>(baseFolder, callback) ||
+ baseFolderDrop<RIGHT_SIDE>(baseFolder, callback))
continue;
//create base folders if not yet existing
if (folderPairStat.createCount() > 0 || folderPairCfg.saveSyncDB) //else: temporary network drop leading to deletions already caught by "sourceFolderMissing" check!
- if (!createBaseFolder< LEFT_SIDE>(baseFolder, copyFilePermissions, folderAccessTimeout, callback) || //+ detect temporary network drop!!
- !createBaseFolder<RIGHT_SIDE>(baseFolder, copyFilePermissions, folderAccessTimeout, callback)) //
+ if (!createBaseFolder< LEFT_SIDE>(baseFolder, copyFilePermissions, callback) || //+ detect temporary network drop!!
+ !createBaseFolder<RIGHT_SIDE>(baseFolder, copyFilePermissions, callback)) //
continue;
//------------------------------------------------------------------------------------------
@@ -2699,7 +2707,7 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime
//-----------------------------------------------------------------------------------------------------
- applyVersioningLimit(versionLimitFolders, folderAccessTimeout, deviceParallelOps, callback); //throw X
+ applyVersioningLimit(versionLimitFolders, deviceParallelOps, callback); //throw X
//------------------- show warnings after end of synchronization --------------------------------------
diff --git a/FreeFileSync/Source/base/synchronization.h b/FreeFileSync/Source/base/synchronization.h
index 3ac1291e..7bd5db5e 100755
--- a/FreeFileSync/Source/base/synchronization.h
+++ b/FreeFileSync/Source/base/synchronization.h
@@ -92,7 +92,6 @@ void synchronize(const std::chrono::system_clock::time_point& syncStartTime,
bool copyFilePermissions,
bool failSafeFileCopy,
bool runWithBackgroundPriority,
- std::chrono::seconds folderAccessTimeout,
const std::vector<FolderPairSyncCfg>& syncConfig, //CONTRACT: syncConfig and folderCmp correspond row-wise!
FolderComparison& folderCmp, //
const std::map<AbstractPath, size_t>& deviceParallelOps,
diff --git a/FreeFileSync/Source/base/versioning.cpp b/FreeFileSync/Source/base/versioning.cpp
index c5bc2aa6..64536076 100755
--- a/FreeFileSync/Source/base/versioning.cpp
+++ b/FreeFileSync/Source/base/versioning.cpp
@@ -37,7 +37,7 @@ std::pair<time_t, Zstring> fff::impl::parseVersionedFileName(const Zstring& file
const auto itExt1 = fileName.end() - (2 * ext.length() + 18);
const auto itTs = itExt1 + ext.length();
- if (!strEqual(ext, StringRef<const Zchar>(itExt1, itTs), CmpFilePath()))
+ if (!equalString(ext, StringRef<const Zchar>(itExt1, itTs)))
return {};
const TimeComp tc = parseTime(Zstr(" %Y-%m-%d %H%M%S"), StringRef<const Zchar>(itTs, itTs + 18)); //returns TimeComp() on error
@@ -102,7 +102,7 @@ template <class Function>
void moveExistingItemToVersioning(const AbstractPath& sourcePath, const AbstractPath& targetPath, //throw FileError
Function copyNewItemPlain /*throw FileError*/)
{
- //start deleting existing target as required by copyFileTransactional()/renameItem():
+ //start deleting existing target as required by copyFileTransactional()/moveAndRenameItem():
//best amortized performance if "target existing" is the most common case
std::exception_ptr deletionError;
try { AFS::removeFilePlain(targetPath); /*throw FileError*/ }
@@ -152,7 +152,7 @@ void moveExistingItemToVersioning(const AbstractPath& sourcePath, const Abstract
try //first try to move directly without copying
{
- AFS::renameItem(sourcePath, targetPath); //throw FileError, ErrorDifferentVolume
+ AFS::moveAndRenameItem(sourcePath, targetPath); //throw FileError, ErrorDifferentVolume
//great, we get away cheaply!
}
catch (ErrorDifferentVolume&)
@@ -177,7 +177,7 @@ void moveExistingItemToVersioning(const AbstractPath& sourcePath, const Abstract
try //retry
{
- AFS::renameItem(sourcePath, targetPath); //throw FileError, ErrorDifferentVolume
+ AFS::moveAndRenameItem(sourcePath, targetPath); //throw FileError, ErrorDifferentVolume
}
catch (ErrorDifferentVolume&)
{
@@ -283,7 +283,7 @@ void FileVersioner::revisionFolderImpl(const AbstractPath& folderPath, const Zst
const Zstring relPathPf = appendSeparator(relativePath);
- for (const auto& fileInfo : files)
+ for (const AFS::FileInfo& fileInfo : files)
{
const FileDescriptor fileDescr{ AFS::appendRelPath(folderPath, fileInfo.itemName),
FileAttributes(fileInfo.modTime, fileInfo.fileSize, fileInfo.fileId, false /*isSymlink*/)};
@@ -291,12 +291,12 @@ void FileVersioner::revisionFolderImpl(const AbstractPath& folderPath, const Zst
revisionFileImpl(fileDescr, relPathPf + fileInfo.itemName, onBeforeFileMove, notifyUnbufferedIO); //throw FileError
}
- for (const auto& linkInfo : symlinks)
+ for (const AFS::SymlinkInfo& linkInfo : symlinks)
revisionSymlinkImpl(AFS::appendRelPath(folderPath, linkInfo.itemName),
relPathPf + linkInfo.itemName, onBeforeFileMove); //throw FileError
//move folders recursively
- for (const auto& folderInfo : folders)
+ for (const AFS::FolderInfo& folderInfo : folders)
revisionFolderImpl(AFS::appendRelPath(folderPath, folderInfo.itemName), //throw FileError
relPathPf + folderInfo.itemName,
onBeforeFileMove, onBeforeFolderMove, notifyUnbufferedIO);
@@ -348,23 +348,21 @@ void findFileVersions(VersionInfoMap& versions,
}
};
- for (const auto& item : folderCont.files)
- extractFileVersion(item.first, false /*isSymlink*/);
+ for (const auto& [fileName, attr] : folderCont.files)
+ extractFileVersion(fileName, false /*isSymlink*/);
- for (const auto& item : folderCont.symlinks)
- extractFileVersion(item.first, true /*isSymlink*/);
+ for (const auto& [linkName, attr] : folderCont.symlinks)
+ extractFileVersion(linkName, true /*isSymlink*/);
- for (const auto& item : folderCont.folders)
+ for (const auto& [folderName, attrAndSub] : folderCont.folders)
{
- const Zstring& folderName = item.first;
-
if (relPathOrigParent.empty() && !versionTimeParent) //VersioningStyle::TIMESTAMP_FOLDER?
{
assert(!versionTimeParent);
const time_t versionTime = fff::impl::parseVersionedFolderName(folderName);
if (versionTime != 0)
{
- findFileVersions(versions, item.second.second,
+ findFileVersions(versions, attrAndSub.second,
AFS::appendRelPath(parentFolderPath, folderName),
Zstring(), //[!] skip time-stamped folder
&versionTime);
@@ -372,7 +370,7 @@ void findFileVersions(VersionInfoMap& versions,
}
}
- findFileVersions(versions, item.second.second,
+ findFileVersions(versions, attrAndSub.second,
AFS::appendRelPath(parentFolderPath, folderName),
AFS::appendPaths(relPathOrigParent, folderName, FILE_NAME_SEPARATOR),
versionTimeParent);
@@ -387,8 +385,8 @@ void getFolderItemCount(std::map<AbstractPath, size_t>& folderItemCount, const F
//theoretically possible that the same folder is found in one case with items, in another case empty (due to an error)
//e.g. "subfolder" for versioning folders c:\folder and c:\folder\subfolder
- for (const auto& item : folderCont.folders)
- getFolderItemCount(folderItemCount, item.second.second, AFS::appendRelPath(parentFolderPath, item.first));
+ for (const auto& [folderName, attrAndSub] : folderCont.folders)
+ getFolderItemCount(folderItemCount, attrAndSub.second, AFS::appendRelPath(parentFolderPath, folderName));
}
}
@@ -413,7 +411,6 @@ bool fff::operator<(const VersioningLimitFolder& lhs, const VersioningLimitFolde
void fff::applyVersioningLimit(const std::set<VersioningLimitFolder>& limitFolders,
- std::chrono::seconds folderAccessTimeout,
const std::map<AbstractPath, size_t>& deviceParallelOps,
ProcessCallback& callback /*throw X*/)
{
@@ -429,7 +426,7 @@ void fff::applyVersioningLimit(const std::set<VersioningLimitFolder>& limitFolde
tryReportingError([&]
{
const FolderStatus status = getFolderStatusNonBlocking(folderPaths, deviceParallelOps, //re-check *all* directories on each try!
- folderAccessTimeout, false /*allowUserInteraction*/, callback); //throw X
+ false /*allowUserInteraction*/, callback); //throw X
foldersToRead.clear();
for (const AbstractPath& folderPath : status.existing)
@@ -439,12 +436,12 @@ void fff::applyVersioningLimit(const std::set<VersioningLimitFolder>& limitFolde
{
std::wstring msg = _("Cannot find the following folders:") + L"\n";
- for (const auto& fc : status.failedChecks)
- msg += L"\n" + AFS::getDisplayPath(fc.first);
+ for (const auto& [folderPath, error] : status.failedChecks)
+ msg += L"\n" + AFS::getDisplayPath(folderPath);
msg += L"\n___________________________________________";
- for (const auto& fc : status.failedChecks)
- msg += L"\n\n" + replaceCpy(fc.second.toString(), L"\n\n", L"\n");
+ for (const auto& [folderPath, error] : status.failedChecks)
+ msg += L"\n\n" + replaceCpy(error.toString(), L"\n\n", L"\n");
throw FileError(msg);
}
@@ -484,28 +481,29 @@ void fff::applyVersioningLimit(const std::set<VersioningLimitFolder>& limitFolde
std::map<AbstractPath, VersionInfoMap> versionDetails; //versioningFolderPath => <version details>
std::map<AbstractPath, size_t> folderItemCount; //<folder path> => <item count> for determination of empty folders
- for (const auto& item : folderBuf)
+ for (const auto& [folderKey, folderVal] : folderBuf)
{
- const AbstractPath versioningFolderPath = item.first.folderPath;
- const DirectoryValue& dirVal = item.second;
+ const AbstractPath versioningFolderPath = folderKey.folderPath;
assert(versionDetails.find(versioningFolderPath) == versionDetails.end());
findFileVersions(versionDetails[versioningFolderPath],
- dirVal.folderCont,
+ folderVal.folderCont,
versioningFolderPath,
Zstring() /*relPathOrigParent*/,
nullptr /*versionTimeParent*/);
//determine item count per folder for later detection and removal of empty folders:
- getFolderItemCount(folderItemCount, dirVal.folderCont, versioningFolderPath);
+ getFolderItemCount(folderItemCount, folderVal.folderCont, versioningFolderPath);
//make sure the versioning folder is never found empty and is not deleted:
++folderItemCount[versioningFolderPath];
+ warn_static("TODO: unicode normalization, case-sensitivity!")
+
//similarly, failed folder traversal should not make folders look empty:
- for (const auto& item2 : dirVal.failedFolderReads) ++folderItemCount[AFS::appendRelPath(versioningFolderPath, item2.first)];
- for (const auto& item2 : dirVal.failedItemReads ) ++folderItemCount[AFS::appendRelPath(versioningFolderPath, beforeLast(item2.first, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE))];
+ for (const auto& [relPath, errorMsg] : folderVal.failedFolderReads) ++folderItemCount[AFS::appendRelPath(versioningFolderPath, relPath)];
+ for (const auto& [relPath, errorMsg] : folderVal.failedItemReads ) ++folderItemCount[AFS::appendRelPath(versioningFolderPath, beforeLast(relPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE))];
}
//--------- calculate excess file versions ---------
@@ -524,10 +522,8 @@ void fff::applyVersioningLimit(const std::set<VersioningLimitFolder>& limitFolde
{
auto it = versionDetails.find(vlf.versioningFolderPath);
if (it != versionDetails.end())
- for (auto& item : it->second)
+ for (auto& [versioningFolderPath, versions] : it->second)
{
- std::vector<VersionInfo>& versions = item.second;
-
size_t versionsToKeep = versions.size();
if (vlf.versionMaxAgeDays > 0)
{
@@ -581,12 +577,12 @@ void fff::applyVersioningLimit(const std::set<VersioningLimitFolder>& limitFolde
std::vector<std::pair<AbstractPath, ParallelWorkItem>> parallelWorkload;
- for (const auto& item : folderItemCount)
- if (item.second == 0)
- parallelWorkload.emplace_back(item.first, deleteEmptyFolderTask);
+ for (const auto& [folderPath, itemCount] : folderItemCount)
+ if (itemCount == 0)
+ parallelWorkload.emplace_back(folderPath, deleteEmptyFolderTask);
- for (const auto& item : itemsToDelete)
- parallelWorkload.emplace_back(item.first, [isSymlink = item.second, &textRemoving, &folderItemCountShared, &deleteEmptyFolderTask](ParallelContext& ctx) //throw ThreadInterruption
+ for (const auto& [itemPath, isSymlink] : itemsToDelete)
+ parallelWorkload.emplace_back(itemPath, [isSymlink = isSymlink /*=> clang bug :>*/, &textRemoving, &folderItemCountShared, &deleteEmptyFolderTask](ParallelContext& ctx) //throw ThreadInterruption
{
const std::wstring errMsg = tryReportingError([&] //throw ThreadInterruption
{
diff --git a/FreeFileSync/Source/base/versioning.h b/FreeFileSync/Source/base/versioning.h
index a2743e94..f1cefcc5 100755
--- a/FreeFileSync/Source/base/versioning.h
+++ b/FreeFileSync/Source/base/versioning.h
@@ -47,7 +47,7 @@ public:
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"\"");
+ throw FileError(_("Unable to create time stamp for versioning:") + L" \"" + utfTo<std::wstring>(timeStamp_) + L'"');
}
//multi-threaded access: internally synchronized!
@@ -103,7 +103,6 @@ bool operator<(const VersioningLimitFolder& lhs, const VersioningLimitFolder& rh
void applyVersioningLimit(const std::set<VersioningLimitFolder>& limitFolders,
- std::chrono::seconds folderAccessTimeout,
const std::map<AbstractPath, size_t>& deviceParallelOps,
ProcessCallback& callback /*throw X*/);
diff --git a/FreeFileSync/Source/fs/abstract.cpp b/FreeFileSync/Source/fs/abstract.cpp
index e43de243..33b6ec0e 100755
--- a/FreeFileSync/Source/fs/abstract.cpp
+++ b/FreeFileSync/Source/fs/abstract.cpp
@@ -39,8 +39,7 @@ int AFS::compareAbstractPath(const AbstractPath& lhs, const AbstractPath& rhs)
if (rv != 0)
return rv;
- return CmpFilePath()(lhs.afsPath.value.c_str(), lhs.afsPath.value.size(),
- rhs.afsPath.value.c_str(), rhs.afsPath.value.size());
+ return compareFilePath(lhs.afsPath.value, rhs.afsPath.value);
}
@@ -65,17 +64,17 @@ std::optional<AfsPath> AFS::getParentAfsPath(const AfsPath& afsPath)
void AFS::traverseFolderRecursive(const AbstractPath& basePath, const AFS::TraverserWorkload& workload, size_t parallelOps)
{
TraverserWorkloadImpl wlImpl;
- for (const auto& item : workload)
+ for (const auto& [relPathComponents, cb] : workload)
{
AfsPath afsPath = basePath.afsPath;
- for (const Zstring& itemName : item.first)
+ for (const Zstring& itemName : relPathComponents)
{
assert(!contains(itemName, FILE_NAME_SEPARATOR));
if (!afsPath.value.empty())
afsPath.value += FILE_NAME_SEPARATOR;
afsPath.value += itemName;
}
- wlImpl.emplace_back(afsPath, item.second);
+ wlImpl.emplace_back(afsPath, cb);
}
basePath.afs->traverseFolderRecursive(wlImpl, parallelOps); //throw
}
@@ -156,7 +155,7 @@ AFS::FileCopyResult AFS::copyFileAsStream(const AfsPath& afsPathSource, const St
Native: no, needed for functional correctness, see file_access.cpp
MTP: maybe a minor one (need to determine objectId one more time)
SFTP: no, needed for functional correctness (synology server), just as for Native
- FTP: maybe a minor one: could set modtime via CURLOPT_POSTQUOTE (but this would internally trigger an extra round-trip anyway!)
+ FTP: no: could set modtime via CURLOPT_POSTQUOTE (but this would internally trigger an extra round-trip anyway!)
*/
setModTime(apTarget, attrSourceNew.modTime); //throw FileError, follows symlinks
}
@@ -210,6 +209,9 @@ AFS::FileCopyResult AFS::copyFileTransactional(const AbstractPath& apSource, con
if (transactionalCopy)
{
+ warn_static("doesnt make sense for Google Drive")
+
+
std::optional<AbstractPath> parentPath = AFS::getParentFolderPath(apTarget);
if (!parentPath)
throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(AFS::getDisplayPath(apTarget))), L"Path is device root.");
@@ -236,7 +238,7 @@ AFS::FileCopyResult AFS::copyFileTransactional(const AbstractPath& apSource, con
onDeleteTargetFile(); //throw X
//perf: this call is REALLY expensive on unbuffered volumes! ~40% performance decrease on FAT USB stick!
- renameItem(apTargetTmp, apTarget); //throw FileError, (ErrorDifferentVolume)
+ moveAndRenameItem(apTargetTmp, apTarget); //throw FileError, (ErrorDifferentVolume)
/*
CAVEAT on FAT/FAT32: the sequence of deleting the target file and renaming "file.txt.ffs_tmp" to "file.txt" does
diff --git a/FreeFileSync/Source/fs/abstract.h b/FreeFileSync/Source/fs/abstract.h
index 887f6b8f..d5de09c7 100755
--- a/FreeFileSync/Source/fs/abstract.h
+++ b/FreeFileSync/Source/fs/abstract.h
@@ -69,6 +69,11 @@ struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model t
static Zstring getRootRelativePath(const AbstractPath& ap) { return ap.afsPath.value; }
//----------------------------------------------------------------------------------------------------------------
+ static void connectNetworkFolder(const AbstractPath& ap, bool allowUserInteraction) { return ap.afs->connectNetworkFolder(ap.afsPath, allowUserInteraction); } //throw FileError
+
+ static int geAccessTimeout(const AbstractPath& ap) { return ap.afs->getAccessTimeout(); } //returns "0" if no timeout in force
+ //----------------------------------------------------------------------------------------------------------------
+
enum class ItemType
{
FILE,
@@ -115,8 +120,6 @@ struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model t
//noexcept; optional return value:
static zen::ImageHolder getFileIcon (const AbstractPath& ap, int pixelSize) { return ap.afs->getFileIcon (ap.afsPath, pixelSize); }
static zen::ImageHolder getThumbnailImage(const AbstractPath& ap, int pixelSize) { return ap.afs->getThumbnailImage(ap.afsPath, pixelSize); }
-
- static void connectNetworkFolder(const AbstractPath& ap, bool allowUserInteraction) { return ap.afs->connectNetworkFolder(ap.afsPath, allowUserInteraction); } //throw FileError
//----------------------------------------------------------------------------------------------------------------
using FileId = zen::Zbase<char>;
@@ -234,7 +237,7 @@ struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model t
static bool supportPermissionCopy(const AbstractPath& apSource, const AbstractPath& apTarget); //throw FileError
//target existing: undefined behavior! (fail/overwrite/auto-rename)
- static void renameItem(const AbstractPath& apSource, const AbstractPath& apTarget); //throw FileError, ErrorDifferentVolume
+ static void moveAndRenameItem(const AbstractPath& apSource, const AbstractPath& apTarget); //throw FileError, ErrorDifferentVolume
//Note: it MAY happen that copyFileTransactional() leaves temp files behind, e.g. temporary network drop.
// => clean them up at an appropriate time (automatically set sync directions to delete them). They have the following ending:
@@ -363,7 +366,7 @@ private:
virtual bool supportsPermissions(const AfsPath& afsPath) const = 0; //throw FileError
//target existing: undefined behavior! (fail/overwrite/auto-rename)
- virtual void renameItemForSameAfsType(const AfsPath& afsPathSource, const AbstractPath& apTarget) const = 0; //throw FileError, ErrorDifferentVolume
+ virtual void moveAndRenameItemForSameAfsType(const AfsPath& afsPathSource, const AbstractPath& apTarget) const = 0; //throw FileError, ErrorDifferentVolume
//symlink handling: follow link!
//target existing: undefined behavior! (fail/overwrite/auto-rename)
@@ -384,6 +387,8 @@ private:
virtual zen::ImageHolder getThumbnailImage(const AfsPath& afsPath, int pixelSize) const = 0; //
virtual void connectNetworkFolder(const AfsPath& afsPath, bool allowUserInteraction) const = 0; //throw FileError
+
+ virtual int getAccessTimeout() const = 0; //returns "0" if no timeout in force
//----------------------------------------------------------------------------------------------------------------
virtual uint64_t getFreeDiskSpace(const AfsPath& afsPath) const = 0; //throw FileError, returns 0 if not available
@@ -508,12 +513,12 @@ bool AbstractFileSystem::supportPermissionCopy(const AbstractPath& apSource, con
inline
-void AbstractFileSystem::renameItem(const AbstractPath& apSource, const AbstractPath& apTarget) //throw FileError, ErrorDifferentVolume
+void AbstractFileSystem::moveAndRenameItem(const AbstractPath& apSource, const AbstractPath& apTarget) //throw FileError, ErrorDifferentVolume
{
using namespace zen;
if (typeid(*apSource.afs) == typeid(*apTarget.afs))
- return apSource.afs->renameItemForSameAfsType(apSource.afsPath, apTarget); //throw FileError, ErrorDifferentVolume
+ return apSource.afs->moveAndRenameItemForSameAfsType(apSource.afsPath, apTarget); //throw FileError, ErrorDifferentVolume
throw ErrorDifferentVolume(replaceCpy(replaceCpy(_("Cannot move file %x to %y."),
L"%x", L"\n" + fmtPath(getDisplayPath(apSource))),
diff --git a/FreeFileSync/Source/fs/concrete_impl.h b/FreeFileSync/Source/fs/concrete_impl.h
index e08d9574..08e075a8 100755
--- a/FreeFileSync/Source/fs/concrete_impl.h
+++ b/FreeFileSync/Source/fs/concrete_impl.h
@@ -144,7 +144,7 @@ class GenericDirTraverser
public:
using Function1 = zen::GetFirstOfT<Functions...>;
- GenericDirTraverser(std::vector<Task<TravContext, Function1>>&& initialTasks, size_t parallelOps, const std::string& threadGroupName) :
+ GenericDirTraverser(std::vector<Task<TravContext, Function1>>&& initialTasks /*throw X*/, size_t parallelOps, const std::string& threadGroupName) :
scheduler_(parallelOps, threadGroupName)
{
//set the initial work load
@@ -163,18 +163,18 @@ private:
GenericDirTraverser& operator=(const GenericDirTraverser&) = delete;
template <class Function>
- void evalResultList(std::vector<TaskResult<TravContext, Function>>& results) //throw X
+ void evalResultList(std::vector<TaskResult<TravContext, Function>>& results /*throw X*/)
{
for (TaskResult<TravContext, Function>& result : results)
evalResult(result); //throw X
}
template <class Function>
- void evalResult(TaskResult<TravContext, Function>& result); //throw X
+ void evalResult(TaskResult<TravContext, Function>& result /*throw X*/);
//specialize!
template <class Function>
- void evalResultValue(const typename Function::Result& r, std::shared_ptr<AbstractFileSystem::TraverserCallback>& cb); //throw X
+ void evalResultValue(const typename Function::Result& r, std::shared_ptr<AbstractFileSystem::TraverserCallback>& cb /*throw X*/);
TaskScheduler<TravContext, Functions...> scheduler_;
};
@@ -182,7 +182,7 @@ private:
template <class... Functions>
template <class Function>
-void GenericDirTraverser<Functions...>::evalResult(TaskResult<TravContext, Function>& result) //throw X
+void GenericDirTraverser<Functions...>::evalResult(TaskResult<TravContext, Function>& result /*throw X*/)
{
auto& cb = result.wi.ctx.cb;
try
diff --git a/FreeFileSync/Source/fs/native.cpp b/FreeFileSync/Source/fs/native.cpp
index fc260d7f..c924777e 100755
--- a/FreeFileSync/Source/fs/native.cpp
+++ b/FreeFileSync/Source/fs/native.cpp
@@ -96,8 +96,33 @@ std::vector<FsItemRaw> getDirContentFlat(const Zstring& dirPath) //throw FileErr
if (itemNameRaw[0] == '.' &&
(itemNameRaw[1] == 0 || (itemNameRaw[1] == '.' && itemNameRaw[2] == 0)))
continue;
+
+ /*
+ Unicode normalization is file-system-dependent:
+
+ OS Accepts Gives back
+ ---------- ------- ----------
+ macOS (HFS+) all NFD
+ Linux all <input>
+ Windows (NTFS, FAT) all <input>
+
+ some file systems return precomposed others decomposed UTF8: http://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!
+ - 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)
+ => normalize only when needed during string comparison
+
+ Create sample files on Linux: touch decomposed-$'\x6f\xcc\x81'.txt
+ touch precomposed-$'\xc3\xb3'.txt
+
+ - SMB sharing case-sensitive or NFD file names is fundamentally broken on macOS:
+ => the macOS SMB manager internally buffers file names as case-insensitive and NFC (= just like NTFS on Windows)
+ => test: create SMB share from Linux => *boom* on macOS: "Error Code 2: No such file or directory [lstat]"
+ or WORSE: folders "test" and "Test" *both* incorrectly return the content of one of the two
+ */
const Zstring& itemName = itemNameRaw;
- if (itemName.empty()) //checks result of normalizeUtfForPosix, too!
+ if (itemName.empty())
throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), L"readdir: Data corruption; item with empty name.");
const Zstring& itemPath = appendSeparator(dirPath) + itemName;
@@ -126,7 +151,7 @@ ItemDetailsRaw getItemDetails(const Zstring& itemPath) //throw FileError
else if (S_ISDIR(statData.st_mode)) //a directory
return { ItemType::FOLDER, statData.st_mtime, 0, extractFileId(statData) };
- else //a file or named pipe, ect. => dont't check using S_ISREG(): see comment in file_traverser.cpp
+ else //a file or named pipe, etc. => dont't check using S_ISREG(): see comment in file_traverser.cpp
return { ItemType::FILE, statData.st_mtime, makeUnsigned(statData.st_size), extractFileId(statData) };
}
@@ -138,7 +163,7 @@ ItemDetailsRaw getSymlinkTargetDetails(const Zstring& linkPath) //throw FileErro
if (S_ISDIR(statData.st_mode)) //a directory
return { ItemType::FOLDER, statData.st_mtime, 0, extractFileId(statData) };
- else //a file or named pipe, ect.
+ else //a file or named pipe, etc.
return { ItemType::FILE, statData.st_mtime, makeUnsigned(statData.st_size), extractFileId(statData) };
}
@@ -198,13 +223,13 @@ private:
};
-void traverseFolderRecursiveNative(const std::vector<std::pair<Zstring, std::shared_ptr<AFS::TraverserCallback>>>& initialTasks, size_t parallelOps) //throw X
+void traverseFolderRecursiveNative(const std::vector<std::pair<Zstring, std::shared_ptr<AFS::TraverserCallback>>>& initialTasks /*throw X*/, size_t parallelOps)
{
std::vector<Task<TravContext, GetDirDetails>> genItems;
- for (const auto& item : initialTasks)
- genItems.push_back({ GetDirDetails(item.first),
- TravContext{ Zstring() /*errorItemName*/, 0 /*errorRetryCount*/, item.second /*TraverserCallback*/ }});
+ for (const auto& [folderPath, cb] : initialTasks)
+ genItems.push_back({ GetDirDetails(folderPath),
+ TravContext{ Zstring() /*errorItemName*/, 0 /*errorRetryCount*/, cb /*TraverserCallback*/ }});
GenericDirTraverser<GetDirDetails, GetItemDetails, GetLinkTargetDetails>(std::move(genItems), parallelOps, "Native Traverser"); //throw X
}
@@ -214,7 +239,7 @@ namespace fff //specialization in original namespace needed by GCC 6
{
template <>
template <>
-void GenericDirTraverser<GetDirDetails, GetItemDetails, GetLinkTargetDetails>::evalResultValue<GetDirDetails>(const GetDirDetails::Result& r, std::shared_ptr<AFS::TraverserCallback>& cb) //throw X
+void GenericDirTraverser<GetDirDetails, GetItemDetails, GetLinkTargetDetails>::evalResultValue<GetDirDetails>(const GetDirDetails::Result& r, std::shared_ptr<AFS::TraverserCallback>& cb /*throw X*/)
{
for (const FsItemRaw& rawItem : r)
scheduler_.run<GetItemDetails>({ GetItemDetails(rawItem), TravContext{ rawItem.itemName, 0 /*errorRetryCount*/, cb }});
@@ -223,7 +248,7 @@ void GenericDirTraverser<GetDirDetails, GetItemDetails, GetLinkTargetDetails>::e
template <>
template <>
-void GenericDirTraverser<GetDirDetails, GetItemDetails, GetLinkTargetDetails>::evalResultValue<GetItemDetails>(const GetItemDetails::Result& r, std::shared_ptr<AFS::TraverserCallback>& cb) //throw X
+void GenericDirTraverser<GetDirDetails, GetItemDetails, GetLinkTargetDetails>::evalResultValue<GetItemDetails>(const GetItemDetails::Result& r, std::shared_ptr<AFS::TraverserCallback>& cb /*throw X*/)
{
switch (r.details.type)
{
@@ -253,7 +278,7 @@ void GenericDirTraverser<GetDirDetails, GetItemDetails, GetLinkTargetDetails>::e
template <>
template <>
-void GenericDirTraverser<GetDirDetails, GetItemDetails, GetLinkTargetDetails>::evalResultValue<GetLinkTargetDetails>(const GetLinkTargetDetails::Result& r, std::shared_ptr<AFS::TraverserCallback>& cb) //throw X
+void GenericDirTraverser<GetDirDetails, GetItemDetails, GetLinkTargetDetails>::evalResultValue<GetLinkTargetDetails>(const GetLinkTargetDetails::Result& r, std::shared_ptr<AFS::TraverserCallback>& cb /*throw X*/)
{
assert(r.link.type == ItemType::SYMLINK && r.target.type != ItemType::SYMLINK);
@@ -264,7 +289,7 @@ void GenericDirTraverser<GetDirDetails, GetItemDetails, GetLinkTargetDetails>::e
if (std::shared_ptr<AFS::TraverserCallback> cbSub = cb->onFolder({ r.raw.itemName, &linkInfo })) //throw X
scheduler_.run<GetDirDetails>({ GetDirDetails(r.raw.itemPath), TravContext{ Zstring() /*errorItemName*/, 0 /*errorRetryCount*/, std::move(cbSub) }});
}
- else //a file or named pipe, ect.
+ else //a file or named pipe, etc.
cb->onFile({ r.raw.itemName, r.target.fileSize, r.target.modTime, convertToAbstractFileId(r.target.fileId), &linkInfo }); //throw X
}
}
@@ -376,8 +401,7 @@ private:
{
const Zstring& rootPathRhs = static_cast<const NativeFileSystem&>(afsRhs).rootPath_;
- return CmpFilePath()(rootPath_ .c_str(), rootPath_ .size(),
- rootPathRhs.c_str(), rootPathRhs.size());
+ return compareLocalPath(rootPath_, rootPathRhs);
}
//----------------------------------------------------------------------------------------------------------------
@@ -481,8 +505,8 @@ private:
//initComForThread() -> done on traverser worker threads
std::vector<std::pair<Zstring, std::shared_ptr<TraverserCallback>>> initialWorkItems;
- for (const auto& item : workload)
- initialWorkItems.emplace_back(getNativePath(item.first), item.second);
+ for (const auto& [folderPath, cb] : workload)
+ initialWorkItems.emplace_back(getNativePath(folderPath), cb);
traverseFolderRecursiveNative(initialWorkItems, parallelOps); //throw X
}
@@ -540,7 +564,7 @@ private:
}
//target existing: undefined behavior! (fail/overwrite/auto-rename) => Native will fail and give a clear error message
- void renameItemForSameAfsType(const AfsPath& afsPathSource, const AbstractPath& apTarget) const override //throw FileError, ErrorDifferentVolume
+ void moveAndRenameItemForSameAfsType(const AfsPath& afsPathSource, const AbstractPath& apTarget) const override //throw FileError, ErrorDifferentVolume
{
//perf test: detecting different volumes by path is ~30 times faster than having MoveFileEx fail with ERROR_NOT_SAME_DEVICE (6µs vs 190µs)
//=> maybe we can even save some actual I/O in some cases?
@@ -588,6 +612,7 @@ private:
}
+ int getAccessTimeout() const override { return 0; } //returns "0" if no timeout in force
//----------------------------------------------------------------------------------------------------------------
uint64_t getFreeDiskSpace(const AfsPath& afsPath) const override //throw FileError, returns 0 if not available
diff --git a/FreeFileSync/Source/ui/batch_status_handler.cpp b/FreeFileSync/Source/ui/batch_status_handler.cpp
index a4e45c50..8b52bacf 100755
--- a/FreeFileSync/Source/ui/batch_status_handler.cpp
+++ b/FreeFileSync/Source/ui/batch_status_handler.cpp
@@ -323,7 +323,8 @@ ProcessCallback::Response BatchStatusHandler::reportError(const std::wstring& ms
if (retryNumber < automaticRetryCount_)
{
errorLog_.logMsg(msg + L"\n-> " + _("Automatic retry"), MSG_TYPE_INFO);
- delayAndCountDown(_("Automatic retry"), automaticRetryDelay_, [&](const std::wstring& statusMsg) { this->reportStatus(_("Error") + L": " + statusMsg); });
+ delayAndCountDown(_("Automatic retry") + (automaticRetryCount_ <= 1 ? L"" : L" " + numberTo<std::wstring>(retryNumber + 1) + L"/" + numberTo<std::wstring>(automaticRetryCount_)),
+ automaticRetryDelay_, [&](const std::wstring& statusMsg) { this->reportStatus(_("Error") + L": " + statusMsg); });
return ProcessCallback::RETRY;
}
diff --git a/FreeFileSync/Source/ui/cfg_grid.cpp b/FreeFileSync/Source/ui/cfg_grid.cpp
index 72acf4a6..db49bee4 100755
--- a/FreeFileSync/Source/ui/cfg_grid.cpp
+++ b/FreeFileSync/Source/ui/cfg_grid.cpp
@@ -32,12 +32,12 @@ std::vector<ConfigFileItem> ConfigView::get() const
{
std::map<int, ConfigFileItem, std::greater<>> itemsSorted; //sort by last use; put most recent items *first* (looks better in XML than reverted)
- for (const auto& item : cfgList_)
- itemsSorted.emplace(item.second.lastUseIndex, item.second.cfgItem);
+ for (const auto& [filePath, details] : cfgList_)
+ itemsSorted.emplace(details.lastUseIndex, details.cfgItem);
std::vector<ConfigFileItem> cfgHistory;
- for (const auto& item : itemsSorted)
- cfgHistory.emplace_back(item.second);
+ for (const auto& [lastUseIndex, cfgItem] : itemsSorted)
+ cfgHistory.emplace_back(cfgItem);
return cfgHistory;
}
@@ -70,8 +70,8 @@ void ConfigView::addCfgFiles(const std::vector<Zstring>& filePaths)
{
//determine highest "last use" index number of m_listBoxHistory
int lastUseIndexMax = 0;
- for (const auto& item : cfgList_)
- lastUseIndexMax = std::max(lastUseIndexMax, item.second.lastUseIndex);
+ for (const auto& [filePath, details] : cfgList_)
+ lastUseIndexMax = std::max(lastUseIndexMax, details.lastUseIndex);
for (const Zstring& filePath : filePaths)
{
@@ -84,14 +84,14 @@ void ConfigView::addCfgFiles(const std::vector<Zstring>& filePaths)
std::tie(detail.name, detail.cfgType, detail.isLastRunCfg) = [&]
{
- if (equalFilePath(filePath, lastRunConfigPath_))
+ if (equalLocalPath(filePath, lastRunConfigPath_))
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 (endsWith(fileName, Zstr(".ffs_gui"), CmpFilePath()))
+ if (endsWithAsciiNoCase(fileName, Zstr(".ffs_gui")))
return std::make_tuple(beforeLast(fileName, Zstr('.'), IF_MISSING_RETURN_NONE), Details::CFG_TYPE_GUI, false);
- else if (endsWith(fileName, Zstr(".ffs_batch"), CmpFilePath()))
+ else if (endsWithAsciiNoCase(fileName, Zstr(".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);
@@ -110,7 +110,7 @@ void ConfigView::addCfgFiles(const std::vector<Zstring>& filePaths)
void ConfigView::removeItems(const std::vector<Zstring>& filePaths)
{
- const std::set<Zstring, LessFilePath> pathsSorted(filePaths.begin(), filePaths.end());
+ const std::set<Zstring, LessLocalPath> pathsSorted(filePaths.begin(), filePaths.end());
erase_if(cfgListView_, [&](auto it) { return pathsSorted.find(it->first) != pathsSorted.end(); });
@@ -581,7 +581,7 @@ void cfggrid::addAndSelect(Grid& grid, const std::vector<Zstring>& filePaths, bo
grid.clearSelection(GridEventPolicy::DENY);
- const std::set<Zstring, LessFilePath> pathsSorted(filePaths.begin(), filePaths.end());
+ const std::set<Zstring, LessLocalPath> pathsSorted(filePaths.begin(), filePaths.end());
std::optional<size_t> selectionTopRow;
for (size_t i = 0; i < grid.getRowCount(); ++i)
diff --git a/FreeFileSync/Source/ui/cfg_grid.h b/FreeFileSync/Source/ui/cfg_grid.h
index 37947867..66cd4dd8 100755
--- a/FreeFileSync/Source/ui/cfg_grid.h
+++ b/FreeFileSync/Source/ui/cfg_grid.h
@@ -134,7 +134,7 @@ private:
const Zstring lastRunConfigPath_ = getLastRunConfigPath(); //let's not use another static...
- using CfgFileList = std::map<Zstring /*file path*/, Details, LessFilePath>;
+ using CfgFileList = std::map<Zstring /*file path*/, Details, LessLocalPath>;
CfgFileList cfgList_;
std::vector<CfgFileList::iterator> cfgListView_; //sorted view on cfgList_
diff --git a/FreeFileSync/Source/ui/command_box.cpp b/FreeFileSync/Source/ui/command_box.cpp
index 81a0aa6f..9d8c7a9b 100755
--- a/FreeFileSync/Source/ui/command_box.cpp
+++ b/FreeFileSync/Source/ui/command_box.cpp
@@ -22,7 +22,7 @@ inline
std::wstring getSeparationLine() { return std::wstring(50, EM_DASH); } //no space between dashes!
-std::vector<std::pair<std::wstring, Zstring>> getDefaultCommands() //(gui name/command) pairs
+std::vector<std::pair<std::wstring, Zstring>> getDefaultCommands() //(description/command) pairs
{
return
{
@@ -63,21 +63,21 @@ CommandBox::CommandBox(wxWindow* parent,
void CommandBox::addItemHistory()
{
- const Zstring command = trimCpy(getValue());
+ const Zstring newCommand = trimCpy(getValue());
- if (command == utfTo<Zstring>(getSeparationLine()) || //do not add sep. line
- command.empty())
+ if (newCommand == utfTo<Zstring>(getSeparationLine()) || //do not add sep. line
+ newCommand.empty())
return;
//do not add built-in commands to history
- for (const auto& item : defaultCommands_)
- if (command == utfTo<Zstring>(item.first) ||
- equalFilePath(command, item.second))
+ for (const auto& [description, cmd] : defaultCommands_)
+ if (newCommand == utfTo<Zstring>(description) ||
+ equalNoCase(newCommand, cmd))
return;
- erase_if(history_, [&](const Zstring& item) { return equalFilePath(command, item); });
+ erase_if(history_, [&](const Zstring& item) { return equalNoCase(newCommand, item); });
- history_.insert(history_.begin(), command);
+ history_.insert(history_.begin(), newCommand);
if (history_.size() > historyMax_)
history_.resize(historyMax_);
@@ -104,8 +104,8 @@ void CommandBox::setValueAndUpdateList(const std::wstring& value)
std::deque<std::wstring> items;
//1. built in commands
- for (const auto& item : defaultCommands_)
- items.push_back(item.first);
+ for (const auto& [description, cmd] : defaultCommands_)
+ items.push_back(description);
//2. history elements
auto histSorted = history_;
@@ -154,9 +154,9 @@ void CommandBox::OnValidateSelection(wxCommandEvent& event)
if (value == getSeparationLine())
return setValueAndUpdateList(std::wstring());
- for (const auto& item : defaultCommands_)
- if (item.first == value)
- return setValueAndUpdateList(utfTo<std::wstring>(item.second)); //replace GUI name by actual command string
+ for (const auto& [description, cmd] : defaultCommands_)
+ if (description == value)
+ return setValueAndUpdateList(utfTo<std::wstring>(cmd)); //replace GUI name by actual command string
}
diff --git a/FreeFileSync/Source/ui/file_grid.cpp b/FreeFileSync/Source/ui/file_grid.cpp
index 1f634cd4..a1af13a3 100755
--- a/FreeFileSync/Source/ui/file_grid.cpp
+++ b/FreeFileSync/Source/ui/file_grid.cpp
@@ -1084,9 +1084,9 @@ private:
case ColumnTypeCenter::CHECKBOX:
break;
case ColumnTypeCenter::CMP_CATEGORY:
- return _("Category") + L" (F10)";
+ return _("Category") + L" (F11)";
case ColumnTypeCenter::SYNC_ACTION:
- return _("Action") + L" (F10)";
+ return _("Action") + L" (F11)";
}
return std::wstring();
}
@@ -1531,7 +1531,7 @@ private:
return mainWin.GetVirtualSize().GetWidth() > mainWin.GetClientSize().GetWidth();
//assuming Grid::updateWindowSizes() does its job well, this should suffice!
//CAVEAT: if horizontal and vertical scrollbar are circular dependent from each other
- //(h-scrollbar is shown due to v-scrollbar consuming horizontal width, ect...)
+ //(h-scrollbar is shown due to v-scrollbar consuming horizontal width, etc...)
//while in fact both are NOT needed, this special case results in a bogus need for scrollbars!
//see https://sourceforge.net/tracker/?func=detail&aid=3514183&group_id=234430&atid=1093083
// => since we're outside the Grid abstraction, we should not duplicate code to handle this special case as it seems to be insignificant
@@ -1636,8 +1636,8 @@ private:
//last inserted items are processed first in icon buffer:
std::vector<AbstractPath> newLoad;
- for (const auto& item : prefetchLoad)
- newLoad.push_back(item.second);
+ for (const auto& [priority, filePath] : prefetchLoad)
+ newLoad.push_back(filePath);
provRight_.updateNewAndGetUnbufferedIcons(newLoad);
provLeft_ .updateNewAndGetUnbufferedIcons(newLoad);
diff --git a/FreeFileSync/Source/ui/file_view.cpp b/FreeFileSync/Source/ui/file_view.cpp
index 1d602540..fdf23760 100755
--- a/FreeFileSync/Source/ui/file_view.cpp
+++ b/FreeFileSync/Source/ui/file_view.cpp
@@ -282,7 +282,7 @@ private:
Test case: 690.000 item pairs, Windows 7 x64 (C:\ vs I:\)
----------------------
CmpNaturalSort: 850 ms
- CmpFilePath: 233 ms
+ CmpLocalPath: 233 ms
CmpAsciiNoCase: 189 ms
No sorting: 30 ms
*/
@@ -293,7 +293,7 @@ private:
for (ItemPair& item : itemList)
output.push_back(&item);
- std::sort(output.begin(), output.end(), [](const ItemPair* lhs, const ItemPair* rhs) { return LessNaturalSort()(lhs->getPairItemName(), rhs->getPairItemName()); });
+ std::sort(output.begin(), output.end(), [](const ItemPair* lhs, const ItemPair* rhs) { return LessNaturalSort()(lhs->getItemNameAny(), rhs->getItemNameAny()); });
return output;
}
#endif
diff --git a/FreeFileSync/Source/ui/folder_history_box.h b/FreeFileSync/Source/ui/folder_history_box.h
index 0252d7cd..bcbe343b 100755
--- a/FreeFileSync/Source/ui/folder_history_box.h
+++ b/FreeFileSync/Source/ui/folder_history_box.h
@@ -41,7 +41,7 @@ public:
const Zstring nameTmp = zen::trimCpy(folderPathPhrase);
//insert new folder or put it to the front if already existing
- zen::erase_if(folderPathPhrases_, [&](const Zstring& item) { return equalFilePath(item, nameTmp); });
+ zen::erase_if(folderPathPhrases_, [&](const Zstring& item) { return equalNoCase(item, nameTmp); });
folderPathPhrases_.insert(folderPathPhrases_.begin(), nameTmp);
@@ -49,7 +49,7 @@ public:
folderPathPhrases_.resize(maxSize_);
}
- void delItem(const Zstring& folderPathPhrase) { zen::erase_if(folderPathPhrases_, [&](const Zstring& item) { return equalFilePath(item, folderPathPhrase); }); }
+ void delItem(const Zstring& folderPathPhrase) { zen::erase_if(folderPathPhrases_, [&](const Zstring& item) { return equalNoCase(item, folderPathPhrase); }); }
private:
size_t maxSize_ = 0;
diff --git a/FreeFileSync/Source/ui/folder_selector.cpp b/FreeFileSync/Source/ui/folder_selector.cpp
index 0d68ccad..c050994b 100755
--- a/FreeFileSync/Source/ui/folder_selector.cpp
+++ b/FreeFileSync/Source/ui/folder_selector.cpp
@@ -44,7 +44,7 @@ void setFolderPathPhrase(const Zstring& folderPathPhrase, FolderHistoryBox* comb
if (staticText)
{
//change static box label only if there is a real difference to what is shown in wxTextCtrl anyway
- staticText->SetLabel(equalFilePath(appendSeparator(trimCpy(folderPathPhrase)), appendSeparator(folderPathPhraseFmt)) ?
+ staticText->SetLabel(equalNoCase(appendSeparator(trimCpy(folderPathPhrase)), appendSeparator(folderPathPhraseFmt)) ?
wxString(_("Drag && drop")) : utfTo<wxString>(folderPathPhraseFmt));
}
}
@@ -230,9 +230,7 @@ void FolderSelector::onSelectAltFolder(wxCommandEvent& event)
Zstring FolderSelector::getPath() const
{
- Zstring path = utfTo<Zstring>(folderComboBox_.GetValue());
-
- return path;
+ return utfTo<Zstring>(folderComboBox_.GetValue());
}
diff --git a/FreeFileSync/Source/ui/gui_generated.cpp b/FreeFileSync/Source/ui/gui_generated.cpp
index 00b3ff64..24c86623 100755
--- a/FreeFileSync/Source/ui/gui_generated.cpp
+++ b/FreeFileSync/Source/ui/gui_generated.cpp
@@ -312,7 +312,7 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const
bSizer1771 = new wxBoxSizer( wxVERTICAL );
m_bpButtonSwapSides = new wxBitmapButton( m_panelTopCenter, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), wxBU_AUTODRAW );
- m_bpButtonSwapSides->SetToolTip( _("Swap sides") );
+ m_bpButtonSwapSides->SetToolTip( _("dummy") );
bSizer1771->Add( m_bpButtonSwapSides, 0, wxEXPAND, 5 );
@@ -859,6 +859,11 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const
bSizerViewFilter->Add( 0, 0, 1, wxEXPAND, 5 );
+ m_bpButtonViewFilterSave = new wxBitmapButton( m_panelViewFilter, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW );
+ m_bpButtonViewFilterSave->SetToolTip( _("Save as default") );
+
+ bSizerViewFilter->Add( m_bpButtonViewFilterSave, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT, 5 );
+
m_staticTextSelectView = new wxStaticText( m_panelViewFilter, wxID_ANY, _("Select view:"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticTextSelectView->Wrap( -1 );
bSizerViewFilter->Add( m_staticTextSelectView, 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 );
@@ -1169,35 +1174,21 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const
m_bpButtonShowLog->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnShowLog ), NULL, this );
m_bpButtonViewTypeSyncAction->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewType ), NULL, this );
m_bpButtonShowExcluded->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this );
- m_bpButtonShowExcluded->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::OnViewButtonRightClick ), NULL, this );
+ m_bpButtonViewFilterSave->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnViewFilterSave ), NULL, this );
m_bpButtonShowDeleteLeft->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this );
- m_bpButtonShowDeleteLeft->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::OnViewButtonRightClick ), NULL, this );
m_bpButtonShowUpdateLeft->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this );
- m_bpButtonShowUpdateLeft->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::OnViewButtonRightClick ), NULL, this );
m_bpButtonShowCreateLeft->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this );
- m_bpButtonShowCreateLeft->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::OnViewButtonRightClick ), NULL, this );
m_bpButtonShowLeftOnly->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this );
- m_bpButtonShowLeftOnly->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::OnViewButtonRightClick ), NULL, this );
m_bpButtonShowLeftNewer->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this );
- m_bpButtonShowLeftNewer->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::OnViewButtonRightClick ), NULL, this );
m_bpButtonShowEqual->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this );
- m_bpButtonShowEqual->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::OnViewButtonRightClick ), NULL, this );
m_bpButtonShowDoNothing->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this );
- m_bpButtonShowDoNothing->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::OnViewButtonRightClick ), NULL, this );
m_bpButtonShowDifferent->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this );
- m_bpButtonShowDifferent->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::OnViewButtonRightClick ), NULL, this );
m_bpButtonShowRightNewer->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this );
- m_bpButtonShowRightNewer->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::OnViewButtonRightClick ), NULL, this );
m_bpButtonShowRightOnly->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this );
- m_bpButtonShowRightOnly->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::OnViewButtonRightClick ), NULL, this );
m_bpButtonShowCreateRight->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this );
- m_bpButtonShowCreateRight->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::OnViewButtonRightClick ), NULL, this );
m_bpButtonShowUpdateRight->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this );
- m_bpButtonShowUpdateRight->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::OnViewButtonRightClick ), NULL, this );
m_bpButtonShowDeleteRight->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this );
- m_bpButtonShowDeleteRight->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::OnViewButtonRightClick ), NULL, this );
m_bpButtonShowConflict->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this );
- m_bpButtonShowConflict->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::OnViewButtonRightClick ), NULL, this );
}
MainDialogGenerated::~MainDialogGenerated()
@@ -2645,23 +2636,54 @@ CloudSetupDlgGenerated::CloudSetupDlgGenerated( wxWindow* parent, wxWindowID id,
m_staticline581 = new wxStaticLine( m_panel41, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
bSizer185->Add( m_staticline581, 0, wxEXPAND, 5 );
- wxBoxSizer* bSizer217;
- bSizer217 = new wxBoxSizer( wxHORIZONTAL );
+ wxBoxSizer* bSizer269;
+ bSizer269 = new wxBoxSizer( wxVERTICAL );
+
+ wxBoxSizer* bSizer270;
+ bSizer270 = new wxBoxSizer( wxHORIZONTAL );
+
+ m_bitmapServerDir = new wxStaticBitmap( m_panel41, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 );
+ bSizer270->Add( m_bitmapServerDir, 0, wxALIGN_BOTTOM|wxTOP|wxBOTTOM|wxLEFT, 5 );
m_staticText1232 = new wxStaticText( m_panel41, wxID_ANY, _("Directory on server:"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticText1232->Wrap( -1 );
- bSizer217->Add( m_staticText1232, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT, 5 );
+ bSizer270->Add( m_staticText1232, 1, wxALL|wxALIGN_BOTTOM, 5 );
+
+ m_staticline72 = new wxStaticLine( m_panel41, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL );
+ bSizer270->Add( m_staticline72, 0, wxEXPAND|wxLEFT, 5 );
+
+ wxBoxSizer* bSizer271;
+ bSizer271 = new wxBoxSizer( wxHORIZONTAL );
+
+ m_staticTextTimeout = new wxStaticText( m_panel41, wxID_ANY, _("Access timeout (in seconds):"), wxDefaultPosition, wxDefaultSize, 0 );
+ m_staticTextTimeout->Wrap( -1 );
+ bSizer271->Add( m_staticTextTimeout, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT, 5 );
+
+ m_spinCtrlTimeout = new wxSpinCtrl( m_panel41, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( -1, -1 ), wxSP_ARROW_KEYS, 1, 2000000000, 1 );
+ bSizer271->Add( m_spinCtrlTimeout, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 );
+
+
+ bSizer270->Add( bSizer271, 0, wxALL, 5 );
+
+
+ bSizer269->Add( bSizer270, 0, wxEXPAND|wxLEFT, 5 );
+
+ wxBoxSizer* bSizer217;
+ bSizer217 = new wxBoxSizer( wxHORIZONTAL );
m_textCtrlServerPath = new wxTextCtrl( m_panel41, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
- bSizer217->Add( m_textCtrlServerPath, 1, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT, 5 );
+ bSizer217->Add( m_textCtrlServerPath, 1, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxLEFT, 5 );
m_buttonSelectFolder = new wxButton( m_panel41, wxID_ANY, _("Browse"), wxDefaultPosition, wxDefaultSize, 0 );
m_buttonSelectFolder->SetToolTip( _("Select a folder") );
- bSizer217->Add( m_buttonSelectFolder, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxRIGHT, 5 );
+ bSizer217->Add( m_buttonSelectFolder, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT, 5 );
+
+
+ bSizer269->Add( bSizer217, 0, wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT, 5 );
- bSizer185->Add( bSizer217, 0, wxALL|wxEXPAND, 5 );
+ bSizer185->Add( bSizer269, 0, wxEXPAND, 5 );
m_panel41->SetSizer( bSizer185 );
diff --git a/FreeFileSync/Source/ui/gui_generated.h b/FreeFileSync/Source/ui/gui_generated.h
index 757634e8..f0b05c75 100755
--- a/FreeFileSync/Source/ui/gui_generated.h
+++ b/FreeFileSync/Source/ui/gui_generated.h
@@ -173,6 +173,7 @@ protected:
wxStaticText* m_staticTextViewType;
zen::ToggleButton* m_bpButtonViewTypeSyncAction;
zen::ToggleButton* m_bpButtonShowExcluded;
+ wxBitmapButton* m_bpButtonViewFilterSave;
wxStaticText* m_staticTextSelectView;
zen::ToggleButton* m_bpButtonShowDeleteLeft;
zen::ToggleButton* m_bpButtonShowUpdateLeft;
@@ -244,7 +245,7 @@ protected:
virtual void OnSearchGridEnter( wxCommandEvent& event ) { event.Skip(); }
virtual void OnToggleViewType( wxCommandEvent& event ) { event.Skip(); }
virtual void OnToggleViewButton( wxCommandEvent& event ) { event.Skip(); }
- virtual void OnViewButtonRightClick( wxMouseEvent& event ) { event.Skip(); }
+ virtual void OnViewFilterSave( wxCommandEvent& event ) { event.Skip(); }
public:
@@ -578,7 +579,11 @@ protected:
wxTextCtrl* m_textCtrlPasswordHidden;
wxCheckBox* m_checkBoxShowPassword;
wxStaticLine* m_staticline581;
+ wxStaticBitmap* m_bitmapServerDir;
wxStaticText* m_staticText1232;
+ wxStaticLine* m_staticline72;
+ wxStaticText* m_staticTextTimeout;
+ wxSpinCtrl* m_spinCtrlTimeout;
wxTextCtrl* m_textCtrlServerPath;
wxButton* m_buttonSelectFolder;
wxBoxSizer* bSizer255;
diff --git a/FreeFileSync/Source/ui/gui_status_handler.cpp b/FreeFileSync/Source/ui/gui_status_handler.cpp
index 53391ea3..0a52e18a 100755
--- a/FreeFileSync/Source/ui/gui_status_handler.cpp
+++ b/FreeFileSync/Source/ui/gui_status_handler.cpp
@@ -90,6 +90,7 @@ StatusHandlerTemporaryPanel::StatusHandlerTemporaryPanel(MainDialog& dlg,
statusPanel.Show();
mainDlg_.auiMgr_.Update();
+ mainDlg_.compareStatus_->getAsWindow()->Refresh(); //macOS: fix background corruption for the statistics boxes (call *after* wxAuiManager::Update()
}
mainDlg_.Update(); //don't wait until idle event!
@@ -214,7 +215,8 @@ ProcessCallback::Response StatusHandlerTemporaryPanel::reportError(const std::ws
if (retryNumber < automaticRetryCount_)
{
errorLog_.logMsg(msg + L"\n-> " + _("Automatic retry"), MSG_TYPE_INFO);
- delayAndCountDown(_("Automatic retry"), automaticRetryDelay_, [&](const std::wstring& statusMsg) { this->reportStatus(_("Error") + L": " + statusMsg); });
+ delayAndCountDown(_("Automatic retry") + (automaticRetryCount_ <= 1 ? L"" : L" " + numberTo<std::wstring>(retryNumber + 1) + L"/" + numberTo<std::wstring>(automaticRetryCount_)),
+ automaticRetryDelay_, [&](const std::wstring& statusMsg) { this->reportStatus(_("Error") + L": " + statusMsg); });
return ProcessCallback::RETRY;
}
@@ -565,7 +567,8 @@ ProcessCallback::Response StatusHandlerFloatingDialog::reportError(const std::ws
if (retryNumber < automaticRetryCount_)
{
errorLog_.logMsg(msg + L"\n-> " + _("Automatic retry"), MSG_TYPE_INFO);
- delayAndCountDown(_("Automatic retry"), automaticRetryDelay_, [&](const std::wstring& statusMsg) { this->reportStatus(_("Error") + L": " + statusMsg); });
+ delayAndCountDown(_("Automatic retry") + (automaticRetryCount_ <= 1 ? L"" : L" " + numberTo<std::wstring>(retryNumber + 1) + L"/" + numberTo<std::wstring>(automaticRetryCount_)),
+ automaticRetryDelay_, [&](const std::wstring& statusMsg) { this->reportStatus(_("Error") + L": " + statusMsg); });
return ProcessCallback::RETRY;
}
diff --git a/FreeFileSync/Source/ui/gui_status_handler.h b/FreeFileSync/Source/ui/gui_status_handler.h
index 768d4d2e..7ba3878b 100755
--- a/FreeFileSync/Source/ui/gui_status_handler.h
+++ b/FreeFileSync/Source/ui/gui_status_handler.h
@@ -53,7 +53,7 @@ private:
//StatusHandlerFloatingDialog(SyncProgressDialog) will internally process Window messages! disable GUI controls to avoid unexpected callbacks!
-class StatusHandlerFloatingDialog : public StatusHandler //throw AbortProcess
+class StatusHandlerFloatingDialog : public StatusHandler
{
public:
StatusHandlerFloatingDialog(wxFrame* parentDlg,
@@ -65,7 +65,7 @@ public:
const Zstring& soundFileSyncComplete,
const Zstring& postSyncCommand,
PostSyncCondition postSyncCondition,
- bool& autoCloseDialog);
+ bool& autoCloseDialog); //noexcept!
~StatusHandlerFloatingDialog();
void initNewPhase (int itemsTotal, int64_t bytesTotal, Phase phaseID) override; //
diff --git a/FreeFileSync/Source/ui/log_panel.cpp b/FreeFileSync/Source/ui/log_panel.cpp
index 7f84a9ed..fc7810e0 100755
--- a/FreeFileSync/Source/ui/log_panel.cpp
+++ b/FreeFileSync/Source/ui/log_panel.cpp
@@ -81,7 +81,7 @@ public:
return {};
}
- void updateView(int includedTypes) //MSG_TYPE_INFO | MSG_TYPE_WARNING, ect. see error_log.h
+ void updateView(int includedTypes) //MSG_TYPE_INFO | MSG_TYPE_WARNING, etc. see error_log.h
{
viewRef_.clear();
diff --git a/FreeFileSync/Source/ui/main_dlg.cpp b/FreeFileSync/Source/ui/main_dlg.cpp
index 5d608955..1cb67b60 100755
--- a/FreeFileSync/Source/ui/main_dlg.cpp
+++ b/FreeFileSync/Source/ui/main_dlg.cpp
@@ -32,7 +32,7 @@
#include "small_dlgs.h"
#include "progress_indicator.h"
#include "folder_pair.h"
-#include "search.h"
+#include "search_grid.h"
#include "batch_config.h"
#include "triple_splitter.h"
#include "app_icon.h"
@@ -79,8 +79,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 strEqual(ext, Zstr("ffs_gui"), CmpFilePath()) ||
- strEqual(ext, Zstr("ffs_batch"), CmpFilePath());
+ return equalAsciiNoCase(ext, Zstr("ffs_gui")) ||
+ equalAsciiNoCase(ext, Zstr("ffs_batch"));
});
}
}
@@ -403,6 +403,8 @@ MainDialog::MainDialog(const Zstring& globalConfigFilePath,
m_bpButtonHideSearch ->SetBitmapLabel(getResourceImage(L"close_panel"));
m_bpButtonShowLog ->SetBitmapLabel(getResourceImage(L"log_file"));
+ m_bpButtonViewFilterSave->SetBitmapLabel(getResourceImage(L"file_save_sicon"));
+
m_textCtrlSearchTxt->SetMinSize(wxSize(fastFromDIP(220), -1));
initViewFilterButtons();
@@ -563,6 +565,7 @@ MainDialog::MainDialog(const Zstring& globalConfigFilePath,
m_bpButtonCmpConfig ->SetToolTip(replaceCpy(_("C&omparison settings"), L"&", L"") + L" (F6)"); //
m_bpButtonSyncConfig->SetToolTip(replaceCpy(_("S&ynchronization settings"), L"&", L"") + L" (F8)"); //
m_buttonSync ->SetToolTip(replaceCpy(_("Start &synchronization"), L"&", L"") + L" (F9)"); //
+ m_bpButtonSwapSides ->SetToolTip(_("Swap sides") + L" (F10)");
m_bpButtonCmpContext ->SetToolTip(m_bpButtonCmpConfig ->GetToolTipText());
m_bpButtonSyncContext->SetToolTip(m_bpButtonSyncConfig->GetToolTipText());
@@ -957,8 +960,8 @@ void MainDialog::setGlobalCfgOnInit(const XmlGlobalSettings& globalSettings)
auiMgr_.LoadPerspective(globalSettings.gui.mainDlg.guiPerspectiveLast);
//restore original captions
- for (const auto& item : captionNameMap)
- auiMgr_.GetPane(item.second).Caption(item.first);
+ for (const auto& [caption, name] : captionNameMap)
+ auiMgr_.GetPane(name).Caption(caption);
//--------------------------------------------------------------------------------
//if MainDialog::onQueryEndSession() is called while comparison is active, this panel is saved and restored as "visible"
@@ -1945,6 +1948,13 @@ void MainDialog::onLocalKeyEvent(wxKeyEvent& event) //process key events without
//return; //-> swallow event!
case WXK_F10:
+ {
+ wxCommandEvent dummy(wxEVT_COMMAND_BUTTON_CLICKED);
+ m_bpButtonSwapSides->Command(dummy); //simulate click
+ }
+ return; //-> swallow event!
+
+ case WXK_F11:
setViewTypeSyncAction(!m_bpButtonViewTypeSyncAction->isActive());
return; //-> swallow event!
@@ -2103,13 +2113,13 @@ void MainDialog::onTreeGridContext(GridClickEvent& event)
const bool isFolder = dynamic_cast<const FolderPair*>(selection[0]) != nullptr;
//by short name
- Zstring labelShort = Zstring(Zstr("*")) + FILE_NAME_SEPARATOR + selection[0]->getPairItemName();
+ Zstring labelShort = Zstring(Zstr("*")) + FILE_NAME_SEPARATOR + selection[0]->getItemNameAny();
if (isFolder)
labelShort += FILE_NAME_SEPARATOR;
submenu.addItem(utfTo<wxString>(labelShort), [this, &selection, include] { filterShortname(*selection[0], include); });
//by relative path
- Zstring labelRel = FILE_NAME_SEPARATOR + selection[0]->getPairRelativePath();
+ Zstring labelRel = FILE_NAME_SEPARATOR + selection[0]->getRelativePathAny();
if (isFolder)
labelRel += FILE_NAME_SEPARATOR;
submenu.addItem(utfTo<wxString>(labelRel), [this, &selection, include] { filterItems(selection, include); });
@@ -2231,20 +2241,20 @@ void MainDialog::onMainGridContextRim(bool leftSide)
//by extension
if (!isFolder)
{
- const Zstring extension = getFileExtension(selection[0]->getPairItemName());
+ const Zstring extension = getFileExtension(selection[0]->getItemNameAny());
if (!extension.empty())
submenu.addItem(L"*." + utfTo<wxString>(extension),
[this, extension, include] { filterExtension(extension, include); });
}
//by short name
- Zstring labelShort = Zstring(Zstr("*")) + FILE_NAME_SEPARATOR + selection[0]->getPairItemName();
+ Zstring labelShort = Zstring(Zstr("*")) + FILE_NAME_SEPARATOR + selection[0]->getItemNameAny();
if (isFolder)
labelShort += FILE_NAME_SEPARATOR;
submenu.addItem(utfTo<wxString>(labelShort), [this, &selection, include] { filterShortname(*selection[0], include); });
//by relative path
- Zstring labelRel = FILE_NAME_SEPARATOR + selection[0]->getPairRelativePath();
+ Zstring labelRel = FILE_NAME_SEPARATOR + selection[0]->getRelativePathAny();
if (isFolder)
labelRel += FILE_NAME_SEPARATOR;
submenu.addItem(utfTo<wxString>(labelRel), [this, &selection, include] { filterItems(selection, include); });
@@ -2380,7 +2390,7 @@ void MainDialog::filterExtension(const Zstring& extension, bool include)
void MainDialog::filterShortname(const FileSystemObject& fsObj, bool include)
{
- Zstring phrase = Zstring(Zstr("*")) + FILE_NAME_SEPARATOR + fsObj.getPairItemName();
+ Zstring phrase = Zstring(Zstr("*")) + FILE_NAME_SEPARATOR + fsObj.getItemNameAny();
const bool isFolder = dynamic_cast<const FolderPair*>(&fsObj) != nullptr;
if (isFolder)
phrase += FILE_NAME_SEPARATOR;
@@ -2402,7 +2412,7 @@ void MainDialog::filterItems(const std::vector<FileSystemObject*>& selection, bo
phrase += Zstr("\n");
//#pragma warning(suppress: 6011) -> fsObj bound in this context!
- phrase += FILE_NAME_SEPARATOR + fsObj->getPairRelativePath();
+ phrase += FILE_NAME_SEPARATOR + fsObj->getRelativePathAny();
const bool isFolder = dynamic_cast<const FolderPair*>(fsObj) != nullptr;
if (isFolder)
@@ -2418,8 +2428,8 @@ void MainDialog::onGridLabelContextC(GridLabelClickEvent& event)
ContextMenu menu;
const bool actionView = m_bpButtonViewTypeSyncAction->isActive();
- menu.addRadio(_("Category") + (actionView ? L"\tF10" : L""), [&] { setViewTypeSyncAction(false); }, !actionView);
- menu.addRadio(_("Action") + (!actionView ? L"\tF10" : L""), [&] { setViewTypeSyncAction(true ); }, actionView);
+ menu.addRadio(_("Category") + (actionView ? L"\tF11" : L""), [&] { setViewTypeSyncAction(false); }, !actionView);
+ menu.addRadio(_("Action") + (!actionView ? L"\tF11" : L""), [&] { setViewTypeSyncAction(true ); }, actionView);
menu.popup(*this);
}
@@ -2723,7 +2733,7 @@ void MainDialog::cfgHistoryRemoveObsolete(const std::vector<Zstring>& filePaths)
void MainDialog::updateUnsavedCfgStatus()
{
- const Zstring activeCfgFilePath = activeConfigFiles_.size() == 1 && !equalFilePath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstring();
+ const Zstring activeCfgFilePath = activeConfigFiles_.size() == 1 && !equalLocalPath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstring();
const bool haveUnsavedCfg = lastSavedCfg_ != getConfig();
@@ -2770,7 +2780,7 @@ void MainDialog::updateUnsavedCfgStatus()
void MainDialog::OnConfigSave(wxCommandEvent& event)
{
- const Zstring activeCfgFilePath = activeConfigFiles_.size() == 1 && !equalFilePath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstring();
+ const Zstring activeCfgFilePath = activeConfigFiles_.size() == 1 && !equalLocalPath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstring();
//if we work on a single named configuration document: save directly if changed
//else: always show file dialog
@@ -2824,16 +2834,16 @@ bool MainDialog::trySaveConfig(const Zstring* guiFilename) //return true if save
}
else
{
- Zstring defaultFilePath = activeConfigFiles_.size() == 1 && !equalFilePath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstr("SyncSettings.ffs_gui");
+ const Zstring defaultFilePath = activeConfigFiles_.size() == 1 && !equalLocalPath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstr("SyncSettings.ffs_gui");
+ auto defaultFolder = utfTo<wxString>(beforeLast(defaultFilePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE));
+ auto defaultFileName = utfTo<wxString>(afterLast (defaultFilePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL));
+
//attention: activeConfigFiles may be an imported *.ffs_batch file! We don't want to overwrite it with a GUI config!
- if (endsWith(defaultFilePath, Zstr(".ffs_batch"), CmpFilePath()))
- defaultFilePath = beforeLast(defaultFilePath, Zstr("."), IF_MISSING_RETURN_NONE) + Zstr(".ffs_gui");
+ defaultFileName = beforeLast(defaultFileName, L'.', IF_MISSING_RETURN_ALL) + L".ffs_gui";
wxFileDialog filePicker(this, //put modal dialog on stack: creating this on freestore leads to memleak!
wxString(),
- //OS X really needs dir/file separated like this:
- utfTo<wxString>(beforeLast(defaultFilePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE)), //default dir
- utfTo<wxString>(afterLast (defaultFilePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL)), //default file
+ defaultFolder, defaultFileName, //OS X really needs dir/file separated like this
wxString(L"FreeFileSync (*.ffs_gui)|*.ffs_gui") + L"|" +_("All files") + L" (*.*)|*",
wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if (filePicker.ShowModal() != wxID_OK)
@@ -2863,7 +2873,7 @@ bool MainDialog::trySaveBatchConfig(const Zstring* batchFileToUpdate)
{
//essentially behave like trySaveConfig(): the collateral damage of not saving GUI-only settings "m_bpButtonViewTypeSyncAction" is negligible
- const Zstring activeCfgFilePath = activeConfigFiles_.size() == 1 && !equalFilePath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstring();
+ const Zstring activeCfgFilePath = activeConfigFiles_.size() == 1 && !equalLocalPath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstring();
//prepare batch config: reuse existing batch-specific settings from file if available
BatchExclusiveConfig batchExCfg;
@@ -2907,16 +2917,16 @@ bool MainDialog::trySaveBatchConfig(const Zstring* batchFileToUpdate)
return false;
updateUnsavedCfgStatus(); //nothing else to update on GUI!
- Zstring defaultFilePath = !activeCfgFilePath.empty() ? activeCfgFilePath : Zstr("BatchRun.ffs_batch");
+ const Zstring defaultFilePath = !activeCfgFilePath.empty() ? activeCfgFilePath : Zstr("BatchRun.ffs_batch");
+ auto defaultFolder = utfTo<wxString>(beforeLast(defaultFilePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE));
+ auto defaultFileName = utfTo<wxString>(afterLast (defaultFilePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL));
+
//attention: activeConfigFiles may be a *.ffs_gui file! We don't want to overwrite it with a BATCH config!
- if (endsWith(defaultFilePath, Zstr(".ffs_gui"), CmpFilePath()))
- defaultFilePath = beforeLast(defaultFilePath, Zstr("."), IF_MISSING_RETURN_NONE) + Zstr(".ffs_batch");
+ defaultFileName = beforeLast(defaultFileName, L'.', IF_MISSING_RETURN_ALL) + L".ffs_batch";
wxFileDialog filePicker(this, //put modal dialog on stack: creating this on freestore leads to memleak!
wxString(),
- //OS X really needs dir/file separated like this:
- utfTo<wxString>(beforeLast(defaultFilePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE)), //default dir
- utfTo<wxString>(afterLast (defaultFilePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL)), //default file
+ defaultFolder, defaultFileName, //OS X really needs dir/file separated like this
_("FreeFileSync batch") + L" (*.ffs_batch)|*.ffs_batch" + L"|" +_("All files") + L" (*.*)|*",
wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if (filePicker.ShowModal() != wxID_OK)
@@ -2947,7 +2957,7 @@ bool MainDialog::saveOldConfig() //return false on user abort
{
if (lastSavedCfg_ != getConfig())
{
- const Zstring activeCfgFilePath = activeConfigFiles_.size() == 1 && !equalFilePath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstring();
+ const Zstring activeCfgFilePath = activeConfigFiles_.size() == 1 && !equalLocalPath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstring();
//notify user about changed settings
if (globalCfg_.confirmDlgs.popupOnConfigChange)
@@ -3005,7 +3015,7 @@ bool MainDialog::saveOldConfig() //return false on user abort
void MainDialog::OnConfigLoad(wxCommandEvent& event)
{
- const Zstring activeCfgFilePath = activeConfigFiles_.size() == 1 && !equalFilePath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstring();
+ const Zstring activeCfgFilePath = activeConfigFiles_.size() == 1 && !equalLocalPath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstring();
wxFileDialog filePicker(this,
wxString(),
@@ -3594,7 +3604,7 @@ void MainDialog::setViewFilterDefault()
}
-void MainDialog::OnViewButtonRightClick(wxMouseEvent& event)
+void MainDialog::OnViewFilterSave(wxCommandEvent& event)
{
auto setButtonDefault = [](const ToggleButton* tb, bool& defaultValue)
{
@@ -3690,7 +3700,6 @@ void MainDialog::OnCompare(wxCommandEvent& event)
globalCfg_.fileTimeTolerance,
true, //allowUserInteraction
globalCfg_.runWithBackgroundPriority,
- globalCfg_.folderAccessTimeout,
globalCfg_.createLockFile,
dirLocks,
extractCompareCfg(guiCfg.mainCfg),
@@ -3850,7 +3859,7 @@ void MainDialog::OnStartSync(wxCommandEvent& event)
wxCommandEvent dummy(wxEVT_COMMAND_BUTTON_CLICKED);
m_buttonCompare->Command(dummy); //simulate click
- if (folderCmp_.empty()) //check if user aborted or error occurred, ect...
+ if (folderCmp_.empty()) //check if user aborted or error occurred, etc...
return;
}
@@ -3876,7 +3885,7 @@ void MainDialog::OnStartSync(wxCommandEvent& event)
for (const ConfigFileItem& item : cfggrid::getDataView(*m_gridCfgHistory).get())
logFilePathsToKeep.insert(item.logFilePath);
- const Zstring activeCfgFilePath = activeConfigFiles_.size() == 1 && !equalFilePath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstring();
+ const Zstring activeCfgFilePath = activeConfigFiles_.size() == 1 && !equalLocalPath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstring();
const std::chrono::system_clock::time_point syncStartTime = std::chrono::system_clock::now();
bool exitAfterSync = false;
@@ -3886,7 +3895,7 @@ void MainDialog::OnStartSync(wxCommandEvent& event)
//run this->enableAllElements() BEFORE "exitAfterSync" buf AFTER StatusHandlerFloatingDialog::reportFinalStatus()
//class handling status updates and error messages
- StatusHandlerFloatingDialog statusHandler(this, //throw AbortProcess
+ StatusHandlerFloatingDialog statusHandler(this,
syncStartTime,
guiCfg.mainCfg.ignoreErrors,
guiCfg.mainCfg.automaticRetryCount,
@@ -3906,7 +3915,7 @@ void MainDialog::OnStartSync(wxCommandEvent& event)
//wxBusyCursor dummy; -> redundant: progress already shown in progress dialog!
- //GUI mode: place directory locks on directories isolated(!) during both comparison and synchronization
+ //GUI mode: end directory lock lifetime after comparion and start new locking right before sync
std::unique_ptr<LockHolder> dirLocks;
if (globalCfg_.createLockFile)
{
@@ -3931,7 +3940,6 @@ void MainDialog::OnStartSync(wxCommandEvent& event)
globalCfg_.copyFilePermissions,
globalCfg_.failSafeFileCopy,
globalCfg_.runWithBackgroundPriority,
- globalCfg_.folderAccessTimeout,
extractSyncCfg(guiCfg.mainCfg),
folderCmp_,
deviceParallelOps,
@@ -4099,7 +4107,9 @@ void MainDialog::showLogPanel(bool show)
}
logPane.Hide();
}
+
auiMgr_.Update();
+ m_panelLog->Refresh(); //macOS: fix background corruption for the statistics boxes (call *after* wxAuiManager::Update()
}
@@ -4329,6 +4339,7 @@ void MainDialog::updateGridViewData()
m_staticTextViewType ->Show(anyViewButtonShown);
m_bpButtonViewTypeSyncAction->Show(anyViewButtonShown);
m_staticTextSelectView ->Show(anySelectViewButtonShown);
+ m_bpButtonViewFilterSave ->Show(anySelectViewButtonShown);
m_panelViewFilter->Layout();
@@ -4454,8 +4465,7 @@ void MainDialog::hideFindPanel()
void MainDialog::startFindNext(bool searchAscending) //F3 or ENTER in m_textCtrlSearchTxt
{
- Zstring searchString = utfTo<Zstring>(trimCpy(m_textCtrlSearchTxt->GetValue()));
-
+ const std::wstring& searchString = utfTo<std::wstring>(trimCpy(m_textCtrlSearchTxt->GetValue()));
if (searchString.empty())
showFindPanel();
@@ -4871,7 +4881,7 @@ void MainDialog::OnMenuExportFileList(wxCommandEvent& event)
std::string&& tmp = utfTo<std::string>(val);
if (contains(tmp, CSV_SEP))
- return '\"' + tmp + '\"';
+ return '"' + tmp + '"';
else
return std::move(tmp);
};
@@ -5080,7 +5090,7 @@ void MainDialog::setViewTypeSyncAction(bool value)
//if (m_bpButtonViewTypeSyncAction->isActive() == value) return; support polling -> what about initialization?
m_bpButtonViewTypeSyncAction->setActive(value);
- m_bpButtonViewTypeSyncAction->SetToolTip((value ? _("Action") : _("Category")) + L" (F10)");
+ m_bpButtonViewTypeSyncAction->SetToolTip((value ? _("Action") : _("Category")) + L" (F11)");
//toggle display of sync preview in middle grid
filegrid::highlightSyncAction(*m_gridMainC, value);
diff --git a/FreeFileSync/Source/ui/main_dlg.h b/FreeFileSync/Source/ui/main_dlg.h
index 70a5fdb7..dbb0d136 100755
--- a/FreeFileSync/Source/ui/main_dlg.h
+++ b/FreeFileSync/Source/ui/main_dlg.h
@@ -153,7 +153,7 @@ private:
void OnSyncSettingsContext(wxEvent& event);
void OnGlobalFilterContext(wxEvent& event);
- void OnViewButtonRightClick(wxMouseEvent& event) override;
+ void OnViewFilterSave(wxCommandEvent& event) override;
void applyCompareConfig(bool setDefaultViewType);
diff --git a/FreeFileSync/Source/ui/progress_indicator.cpp b/FreeFileSync/Source/ui/progress_indicator.cpp
index 6737996c..a2cfa0fa 100755
--- a/FreeFileSync/Source/ui/progress_indicator.cpp
+++ b/FreeFileSync/Source/ui/progress_indicator.cpp
@@ -325,7 +325,7 @@ void CompareProgressDialog::Impl::updateProgressGui()
warn_static("harmonize phase handling!")
- //write status information to taskbar, parent title ect.
+ //write status information to taskbar, parent title etc.
switch (syncStat_->currentPhase())
{
case ProcessCallback::PHASE_NONE:
@@ -922,12 +922,13 @@ SyncProgressDialogImpl<TopLevelDialog>::~SyncProgressDialogImpl()
parentFrame_->Disconnect(wxEVT_CHAR_HOOK, wxKeyEventHandler(SyncProgressDialogImpl::onParentKeyEvent), nullptr, this);
parentFrame_->SetTitle(parentTitleBackup_); //restore title text
-
+
//make sure main dialog is shown again if still "minimized to systray"! see SyncProgressDialog::closeDirectly()
parentFrame_->Show();
//if (parentFrame_->IsIconized()) //caveat: if window is maximized calling Iconize(false) will erroneously un-maximize!
// parentFrame_->Iconize(false);
}
+ //else: don't call "TransformProcessType": consider "switch to main dialog" option during silent batch run
//our client is NOT expecting a second call via notifyWindowTerminate_()!
}
@@ -1014,14 +1015,14 @@ 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 : L"\"" + jobName_ + L"\"\n" + status;
+ wxString systrayTooltip = jobName_.empty() ? status : L'"' + jobName_ + L"\"\n" + status;
if (!progress.empty())
systrayTooltip += L" " + progress;
//window caption/taskbar; inverse order: progress, status, jobname
wxString title = progress.empty() ? status : progress + SPACED_DASH + status;
if (!jobName_.empty())
- title += wxString(SPACED_DASH) + L"\"" + jobName_ + L"\"";
+ title += wxString(SPACED_DASH) + L'"' + jobName_ + L'"';
//systray tooltip, if window is minimized
if (trayIcon_.get())
diff --git a/FreeFileSync/Source/ui/search_grid.cpp b/FreeFileSync/Source/ui/search_grid.cpp
new file mode 100755
index 00000000..50ee3ab0
--- /dev/null
+++ b/FreeFileSync/Source/ui/search_grid.cpp
@@ -0,0 +1,135 @@
+// *****************************************************************************
+// * 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 "search_grid.h"
+#include <zen/zstring.h>
+#include <zen/utf.h>
+#include <zen/perf.h>
+
+using namespace zen;
+using namespace fff;
+
+
+namespace
+{
+inline std::wstring getUnicodeNormalFormWide(const std::wstring& str) { return utfTo<std::wstring>(getUnicodeNormalForm(utfTo<Zstring>(str))); }
+inline std::wstring makeUpperCopyWide (const std::wstring& str) { return utfTo<std::wstring>(makeUpperCopy (utfTo<Zstring>(str))); }
+
+
+template <bool respectCase>
+class MatchFound
+{
+public:
+ MatchFound(const std::wstring& textToFind) : textToFind_(getUnicodeNormalFormWide(textToFind)) {}
+ bool operator()(const std::wstring& phrase) const
+ {
+ if (isAsciiString(phrase.c_str())) //perf: save Zstring conversion for getUnicodeNormalFormWide() when not needed
+ return contains(phrase, textToFind_);
+ else
+ return contains(getUnicodeNormalFormWide(phrase), textToFind_);
+ }
+
+private:
+ const std::wstring textToFind_;
+};
+
+
+template <>
+class MatchFound<false>
+{
+public:
+ MatchFound(const std::wstring& textToFind) : textToFind_(makeUpperCopyWide(textToFind)) {}
+ bool operator()(std::wstring&& phrase) const
+ {
+ if (isAsciiString(phrase.c_str())) //perf: save Zstring conversion for makeUpperCopyWide() when not needed
+ {
+ for (wchar_t& c : phrase) c = asciiToUpper(c);
+ return contains(phrase, textToFind_);
+ }
+ else
+ return contains(makeUpperCopyWide(phrase), textToFind_); //getUnicodeNormalForm() is implied by makeUpperCopy()
+ }
+
+private:
+ const std::wstring textToFind_;
+};
+
+//###########################################################################################
+
+template <bool respectCase>
+ptrdiff_t findRow(const Grid& grid, //return -1 if no matching row found
+ const std::wstring& searchString,
+ bool searchAscending,
+ size_t rowFirst, //specify area to search:
+ size_t rowLast) // [rowFirst, rowLast)
+{
+ if (auto prov = grid.getDataProvider())
+ {
+ std::vector<Grid::ColAttributes> colAttr = grid.getColumnConfig();
+ erase_if(colAttr, [](const Grid::ColAttributes& ca) { return !ca.visible; });
+ if (!colAttr.empty())
+ {
+ const MatchFound<respectCase> matchFound(searchString);
+
+ if (searchAscending)
+ {
+ for (size_t row = rowFirst; row < rowLast; ++row)
+ for (const Grid::ColAttributes& ca : colAttr)
+ if (matchFound(prov->getValue(row, ca.type)))
+ return row;
+ }
+ else
+ for (size_t row = rowLast; row-- > rowFirst;)
+ for (const Grid::ColAttributes& ca : colAttr)
+ if (matchFound(prov->getValue(row, ca.type)))
+ return row;
+ }
+ }
+ return -1;
+}
+}
+
+
+std::pair<const Grid*, ptrdiff_t> fff::findGridMatch(const Grid& grid1, const Grid& grid2, const std::wstring& searchString, bool respectCase, bool searchAscending)
+{
+ //PERF_START
+
+ const size_t rowCount1 = grid1.getRowCount();
+ const size_t rowCount2 = grid2.getRowCount();
+
+ size_t cursorRow1 = grid1.getGridCursor();
+ if (cursorRow1 >= rowCount1)
+ cursorRow1 = 0;
+
+ std::pair<const Grid*, ptrdiff_t> result(nullptr, -1);
+
+ auto finishSearch = [&](const Grid& grid, size_t rowFirst, size_t rowLast)
+ {
+ const ptrdiff_t targetRow = respectCase ?
+ findRow<true >(grid, searchString, searchAscending, rowFirst, rowLast) :
+ findRow<false>(grid, searchString, searchAscending, rowFirst, rowLast);
+ if (targetRow >= 0)
+ {
+ result = { &grid, targetRow };
+ return true;
+ }
+ return false;
+ };
+
+ if (searchAscending)
+ {
+ if (!finishSearch(grid1, cursorRow1 + 1, rowCount1))
+ if (!finishSearch(grid2, 0, rowCount2))
+ finishSearch(grid1, 0, cursorRow1 + 1);
+ }
+ else
+ {
+ if (!finishSearch(grid1, 0, cursorRow1))
+ if (!finishSearch(grid2, 0, rowCount2))
+ finishSearch(grid1, cursorRow1, rowCount1);
+ }
+ return result;
+}
diff --git a/FreeFileSync/Source/ui/search_grid.h b/FreeFileSync/Source/ui/search_grid.h
new file mode 100755
index 00000000..81560f7c
--- /dev/null
+++ b/FreeFileSync/Source/ui/search_grid.h
@@ -0,0 +1,19 @@
+// *****************************************************************************
+// * 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 SEARCH_H_423905762345342526587
+#define SEARCH_H_423905762345342526587
+
+#include <wx+/grid.h>
+
+
+namespace fff
+{
+std::pair<const zen::Grid*, ptrdiff_t> findGridMatch(const zen::Grid& grid1, const zen::Grid& grid2, const std::wstring& searchString, bool respectCase, bool searchAscending);
+//returns (grid/row) where the value was found, (nullptr, -1) if not found
+}
+
+#endif //SEARCH_H_423905762345342526587
diff --git a/FreeFileSync/Source/ui/small_dlgs.cpp b/FreeFileSync/Source/ui/small_dlgs.cpp
index 08eefc02..7b71fe1e 100755
--- a/FreeFileSync/Source/ui/small_dlgs.cpp
+++ b/FreeFileSync/Source/ui/small_dlgs.cpp
@@ -31,6 +31,7 @@
#include "../base/hard_filter.h"
#include "../base/status_handler.h" //updateUiIsAllowed()
#include "../base/generate_logfile.h"
+#include "../base/icon_buffer.h"
#include "../version/version.h"
diff --git a/FreeFileSync/Source/ui/sorting.h b/FreeFileSync/Source/ui/sorting.h
index 04bdc743..e9432907 100755
--- a/FreeFileSync/Source/ui/sorting.h
+++ b/FreeFileSync/Source/ui/sorting.h
@@ -76,17 +76,16 @@ bool lessRelativeFolder(const FileSystemObject& a, const FileSystemObject& b)
{
const bool isDirectoryA = isDirectoryPair(a);
const Zstring& relFolderA = isDirectoryA ?
- a.getPairRelativePath() :
- a.parent().getPairRelativePath();
+ a.getRelativePathAny() :
+ a.parent().getRelativePathAny();
const bool isDirectoryB = isDirectoryPair(b);
const Zstring& relFolderB = isDirectoryB ?
- b.getPairRelativePath() :
- b.parent().getPairRelativePath();
+ b.getRelativePathAny() :
+ b.parent().getRelativePathAny();
//compare relative names without filepaths first
- const int rv = CmpNaturalSort()(relFolderA.c_str(), relFolderA.size(),
- relFolderB.c_str(), relFolderB.size());
+ const int rv = compareNatural(relFolderA, relFolderB);
if (rv != 0)
return zen::makeSortDirection(std::less<int>(), std::bool_constant<ascending>())(rv, 0);
@@ -96,7 +95,7 @@ bool lessRelativeFolder(const FileSystemObject& a, const FileSystemObject& b)
else if (isDirectoryA)
return true;
- return zen::makeSortDirection(LessNaturalSort(), std::bool_constant<ascending>())(a.getPairItemName(), b.getPairItemName());
+ return zen::makeSortDirection(LessNaturalSort(), std::bool_constant<ascending>())(a.getItemNameAny(), b.getItemNameAny());
}
diff --git a/FreeFileSync/Source/ui/sync_cfg.cpp b/FreeFileSync/Source/ui/sync_cfg.cpp
index f5bb6399..e3b6a7bc 100755
--- a/FreeFileSync/Source/ui/sync_cfg.cpp
+++ b/FreeFileSync/Source/ui/sync_cfg.cpp
@@ -687,9 +687,8 @@ void ConfigDialog::onFilterKeyEvent(wxKeyEvent& event)
FilterConfig ConfigDialog::getFilterConfig() const
{
- Zstring includeFilter = utfTo<Zstring>(m_textCtrlInclude->GetValue());
- Zstring exludeFilter = utfTo<Zstring>(m_textCtrlExclude->GetValue());
-
+ const Zstring& includeFilter = utfTo<Zstring>(m_textCtrlInclude->GetValue());
+ const Zstring& exludeFilter = utfTo<Zstring>(m_textCtrlExclude->GetValue());
return FilterConfig(includeFilter, exludeFilter,
m_spinCtrlTimespan->GetValue(),
diff --git a/FreeFileSync/Source/ui/tree_grid.cpp b/FreeFileSync/Source/ui/tree_grid.cpp
index 268cb6e4..bf3833b5 100755
--- a/FreeFileSync/Source/ui/tree_grid.cpp
+++ b/FreeFileSync/Source/ui/tree_grid.cpp
@@ -132,16 +132,16 @@ void calcPercentage(std::vector<std::pair<uint64_t, int*>>& workList)
if (total == 0U) //this case doesn't work with the error minimizing algorithm below
{
- for (auto& pair : workList)
- *pair.second = 0;
+ for (auto& [bytes, percent] : workList)
+ *percent = 0;
return;
}
int remainingPercent = 100;
- for (auto& pair : workList)
+ for (auto& [bytes, percent] : workList)
{
- *pair.second = static_cast<int>(pair.first * 100U / total); //round down
- remainingPercent -= *pair.second;
+ *percent = static_cast<int>(bytes * 100U / total); //round down
+ remainingPercent -= *percent;
}
assert(remainingPercent >= 0);
assert(remainingPercent < static_cast<int>(workList.size()));
@@ -193,7 +193,7 @@ struct TreeView::LessShortName
else if (!folderR)
return true;
- return makeSortDirection(LessNaturalSort() /*even on Linux*/, std::bool_constant<ascending>())(folderL->getPairItemName(), folderR->getPairItemName());
+ return makeSortDirection(LessNaturalSort(), std::bool_constant<ascending>())(folderL->getItemNameAny(), folderR->getItemNameAny());
}
case TreeView::TYPE_FILES:
@@ -755,7 +755,7 @@ private:
if (const TreeView::RootNode* root = dynamic_cast<const TreeView::RootNode*>(node.get()))
return root->displayName;
else if (const TreeView::DirNode* dir = dynamic_cast<const TreeView::DirNode*>(node.get()))
- return utfTo<std::wstring>(dir->folder.getPairItemName());
+ return utfTo<std::wstring>(dir->folder.getItemNameAny());
else if (dynamic_cast<const TreeView::FilesNode*>(node.get()))
return _("Files");
break;
diff --git a/FreeFileSync/Source/version/version.h b/FreeFileSync/Source/version/version.h
index 0c47fff0..0a978b6c 100755
--- a/FreeFileSync/Source/version/version.h
+++ b/FreeFileSync/Source/version/version.h
@@ -3,7 +3,7 @@
namespace fff
{
-const char ffsVersion[] = "10.4"; //internal linkage!
+const char ffsVersion[] = "10.5"; //internal linkage!
const char FFS_VERSION_SEPARATOR = '.';
}
diff --git a/wx+/async_task.h b/wx+/async_task.h
index d1f30fec..df4c3ec6 100755
--- a/wx+/async_task.h
+++ b/wx+/async_task.h
@@ -95,7 +95,7 @@ public:
return false;
});
- for (auto& task : readyTasks)
+ for (std::unique_ptr<Task>& task : readyTasks)
task->evaluateResult();
}
}
diff --git a/wx+/choice_enum.h b/wx+/choice_enum.h
index 2c424b9f..f2c93927 100755
--- a/wx+/choice_enum.h
+++ b/wx+/choice_enum.h
@@ -103,11 +103,11 @@ Enum getEnumVal(const EnumDescrList<Enum>& mapping, const wxChoice& ctrl)
template <class Enum> void updateTooltipEnumVal(const EnumDescrList<Enum>& mapping, wxChoice& ctrl)
{
- const Enum value = getEnumVal(mapping, ctrl);
+ const Enum currentValue = getEnumVal(mapping, ctrl);
- for (const auto& item : mapping.descrList)
- if (item.first == value)
- ctrl.SetToolTip(item.second.second);
+ for (const auto& [enumValue, textAndTooltip] : mapping.descrList)
+ if (currentValue == enumValue)
+ ctrl.SetToolTip(textAndTooltip.second);
}
}
diff --git a/wx+/context_menu.h b/wx+/context_menu.h
index 7f459aca..d856db03 100755
--- a/wx+/context_menu.h
+++ b/wx+/context_menu.h
@@ -72,8 +72,8 @@ public:
void popup(wxWindow& wnd, const wxPoint& pos = wxDefaultPosition) //show popup menu + process lambdas
{
//eventually all events from submenu items will be received by this menu
- for (const auto& item : commandList_)
- menu_->Connect(item.first, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(ContextMenu::onSelection), new GenericCommand(item.second) /*pass ownership*/, this);
+ for (const auto& [itemId, command] : commandList_)
+ menu_->Connect(itemId, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(ContextMenu::onSelection), new GenericCommand(command) /*pass ownership*/, this);
wnd.PopupMenu(menu_.get(), pos);
wxTheApp->ProcessPendingEvents(); //make sure lambdas are evaluated before going out of scope;
diff --git a/wx+/dc.h b/wx+/dc.h
index ff2f81bd..e1d803cb 100755
--- a/wx+/dc.h
+++ b/wx+/dc.h
@@ -8,6 +8,7 @@
#define DC_H_4987123956832143243214
#include <unordered_map>
+#include <optional>
#include <zen/basic_math.h>
#include <wx/dcbuffer.h> //for macro: wxALWAYS_NATIVE_DOUBLE_BUFFER
#include <wx/dcscreen.h>
diff --git a/wx+/graph.cpp b/wx+/graph.cpp
index 38846523..e00bed86 100755
--- a/wx+/graph.cpp
+++ b/wx+/graph.cpp
@@ -846,8 +846,8 @@ void Graph2D::render(wxDC& dc) const
}
//5. draw corner texts
- for (const auto& ct : attr_.cornerTexts)
- drawCornerText(dc, graphArea, ct.second, ct.first, attr_.backgroundColor);
+ for (const auto& [cornerPos, text] : attr_.cornerTexts)
+ drawCornerText(dc, graphArea, text, cornerPos, attr_.backgroundColor);
}
}
}
diff --git a/wx+/grid.h b/wx+/grid.h
index 732a4fcb..ccf7ad64 100755
--- a/wx+/grid.h
+++ b/wx+/grid.h
@@ -9,9 +9,11 @@
#include <memory>
#include <numeric>
+#include <optional>
+#include <set>
#include <vector>
-#include <wx/scrolwin.h>
#include <zen/basic_math.h>
+#include <wx/scrolwin.h>
//a user-friendly, extensible and high-performance grid control
@@ -362,10 +364,18 @@ private:
template <class ColAttrReal>
std::vector<ColAttrReal> makeConsistent(const std::vector<ColAttrReal>& attribs, const std::vector<ColAttrReal>& defaults)
{
- std::vector<ColAttrReal> output = attribs;
- //make sure each type is existing!
- output.insert(output.end(), defaults.begin(), defaults.end());
- removeDuplicates(output, [](const ColAttrReal& lhs, const ColAttrReal& rhs) { return lhs.type < rhs.type; });
+ using ColTypeReal = decltype(ColAttrReal().type);
+ std::vector<ColAttrReal> output;
+
+ std::set<ColTypeReal> usedTypes; //remove duplicates
+ auto appendUnique = [&](const std::vector<ColAttrReal>& attr)
+ {
+ std::copy_if(attr.begin(), attr.end(), std::back_inserter(output),
+ [&](const ColAttrReal& a) { return usedTypes.insert(a.type).second; });
+ };
+ appendUnique(attribs);
+ appendUnique(defaults); //make sure each type is existing!
+
return output;
}
diff --git a/wx+/image_resources.cpp b/wx+/image_resources.cpp
index d0449fe5..d09a188b 100755
--- a/wx+/image_resources.cpp
+++ b/wx+/image_resources.cpp
@@ -122,14 +122,12 @@ public:
result_.access([&](std::vector<std::pair<std::wstring, ImageHolder>>& r)
{
- for (auto& item : r)
+ for (auto& [imageName, ih] : r)
{
- ImageHolder& ih = item.second;
-
wxImage img(ih.getWidth(), ih.getHeight(), ih.releaseRgb(), false /*static_data*/); //pass ownership
img.SetAlpha(ih.releaseAlpha(), false /*static_data*/);
- output.emplace(item.first, std::move(img));
+ output.emplace(imageName, std::move(img));
}
});
return output;
@@ -171,7 +169,8 @@ class GlobalBitmaps
public:
static std::shared_ptr<GlobalBitmaps> instance()
{
- static Global<GlobalBitmaps> inst(std::make_unique<GlobalBitmaps>());
+ static FunStatGlobal<GlobalBitmaps> inst;
+ inst.initOnce([] { return std::make_unique<GlobalBitmaps>(); });
assert(runningMainThread()); //wxWidgets is not thread-safe!
return inst.get();
}
@@ -179,7 +178,7 @@ public:
GlobalBitmaps() {}
~GlobalBitmaps() { assert(bitmaps_.empty() && anims_.empty()); } //don't leave wxWidgets objects for static destruction!
- void init(const Zstring& filepath);
+ void init(const Zstring& filePath);
void cleanup()
{
bitmaps_.clear();
@@ -239,6 +238,8 @@ void GlobalBitmaps::init(const Zstring& filePath)
}
else if (endsWith(name, L".gif"))
loadAnimFromZip(streamIn, anims_[name]);
+ else
+ assert(false);
}
}
}
diff --git a/wx+/image_tools.cpp b/wx+/image_tools.cpp
index 4748a590..b314e801 100755
--- a/wx+/image_tools.cpp
+++ b/wx+/image_tools.cpp
@@ -141,10 +141,10 @@ wxImage zen::createImageFromText(const wxString& text, const wxFont& font, const
int maxWidth = 0;
int lineHeight = 0;
- for (const auto& li : lineInfo)
+ for (const auto& [lineText, lineSize] : lineInfo)
{
- maxWidth = std::max(maxWidth, li.second.GetWidth());
- lineHeight = std::max(lineHeight, li.second.GetHeight()); //wxWidgets comment "GetTextExtent will return 0 for empty string"
+ maxWidth = std::max(maxWidth, lineSize.GetWidth());
+ lineHeight = std::max(lineHeight, lineSize.GetHeight()); //wxWidgets comment "GetTextExtent will return 0 for empty string"
}
if (maxWidth == 0 || lineHeight == 0)
return wxImage();
@@ -160,19 +160,19 @@ wxImage zen::createImageFromText(const wxString& text, const wxFont& font, const
dc.SetFont(font);
int posY = 0;
- for (const auto& li : lineInfo)
+ for (const auto& [lineText, lineSize] : lineInfo)
{
- if (!li.first.empty())
+ if (!lineText.empty())
switch (textAlign)
{
case ImageStackAlignment::LEFT:
- dc.DrawText(li.first, wxPoint(0, posY));
+ dc.DrawText(lineText, wxPoint(0, posY));
break;
case ImageStackAlignment::RIGHT:
- dc.DrawText(li.first, wxPoint(maxWidth - li.second.GetWidth(), posY));
+ dc.DrawText(lineText, wxPoint(maxWidth - lineSize.GetWidth(), posY));
break;
case ImageStackAlignment::CENTER:
- dc.DrawText(li.first, wxPoint((maxWidth - li.second.GetWidth()) / 2, posY));
+ dc.DrawText(lineText, wxPoint((maxWidth - lineSize.GetWidth()) / 2, posY));
break;
}
diff --git a/wx+/zlib_wrap.cpp b/wx+/zlib_wrap.cpp
index cb6e3083..fbbe2f09 100755
--- a/wx+/zlib_wrap.cpp
+++ b/wx+/zlib_wrap.cpp
@@ -5,9 +5,10 @@
// *****************************************************************************
#include "zlib_wrap.h"
-//include the SAME zlib version that wxWidgets is linking against!
- //#include <wx/../../../../../Source/src/zlib/zlib.h> //wxWidgets compiled with: --with-zlib=builtin
- #include <zlib.h> //use same library as used by Curl (zlib is required for HTTP)
+//Windows: use the SAME zlib version that wxWidgets is linking against! //C:\Data\Projects\wxWidgets\Source\src\zlib\zlib.h
+//Linux/macOS: use zlib system header for both wxWidgets and Curl (zlib is required for HTTP)
+// => don't compile wxWidgets with: --with-zlib=builtin
+#include <zlib.h>
using namespace zen;
diff --git a/zen/file_access.cpp b/zen/file_access.cpp
index a81fdae0..88b70b14 100755
--- a/zen/file_access.cpp
+++ b/zen/file_access.cpp
@@ -381,7 +381,7 @@ void zen::renameFile(const Zstring& pathSource, const Zstring& pathTarget) //thr
const Zstring parentPathSrc = beforeLast(pathSource, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE);
const Zstring parentPathTrg = beforeLast(pathTarget, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE);
//some (broken) devices may fail to rename case directly:
- if (equalFilePath(parentPathSrc, parentPathTrg))
+ if (equalLocalPath(parentPathSrc, parentPathTrg))
{
if (fileNameSrc == fileNameTrg)
return; //non-sensical request
diff --git a/zen/file_error.h b/zen/file_error.h
index 086d0998..101d6543 100755
--- a/zen/file_error.h
+++ b/zen/file_error.h
@@ -45,7 +45,7 @@ DEFINE_NEW_FILE_ERROR(ErrorDifferentVolume);
//----------- facilitate usage of std::wstring for error messages --------------------
-inline std::wstring fmtPath(const std::wstring& displayPath) { return L'\"' + displayPath + L'\"'; }
+inline std::wstring fmtPath(const std::wstring& displayPath) { return L'"' + displayPath + L'"'; }
inline std::wstring fmtPath(const Zstring& displayPath) { return fmtPath(utfTo<std::wstring>(displayPath)); }
inline std::wstring fmtPath(const wchar_t* displayPath) { return fmtPath(std::wstring(displayPath)); } //resolve overload ambiguity
}
diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp
index e342c8ec..cc6e0c0b 100755
--- a/zen/file_traverser.cpp
+++ b/zen/file_traverser.cpp
@@ -78,7 +78,7 @@ void zen::traverseFolder(const Zstring& dirPath,
if (onFolder)
onFolder({ itemName, itemPath });
}
- else //a file or named pipe, ect.
+ else //a file or named pipe, etc.
{
if (onFile)
onFile({ itemName, itemPath, makeUnsigned(statData.st_size), statData.st_mtime });
diff --git a/zen/globals.h b/zen/globals.h
index 10975414..024147fa 100755
--- a/zen/globals.h
+++ b/zen/globals.h
@@ -14,14 +14,22 @@
namespace zen
{
-//solve static destruction order fiasco by providing shared ownership and serialized access to global variables
+/*
+Solve static destruction order fiasco by providing shared ownership and serialized access to global variables
+
+=>there may be accesses to "Global<T>::get()" during process shutdown e.g. _("") used by message in debug_minidump.cpp or by some detached thread assembling an error message!
+=> use trivially-destructible POD only!!!
+
+ATTENTION: function-static globals have the compiler generate "magic statics" == compiler-genenerated locking code which will crash or leak memory when accessed after global is "dead"
+ => "solved" by FunStatGlobal, but we can't have "too many" of these...
+*/
template <class T>
-class Global
+class Global //don't use for function-scope statics!
{
public:
Global()
{
- static_assert(std::is_trivially_destructible_v<Pod>, "this memory needs to live forever");
+ static_assert(std::is_trivially_constructible_v<Pod>&& std::is_trivially_destructible_v<Pod>, "this memory needs to live forever");
assert(!pod_.inst && !pod_.spinLock); //we depend on static zero-initialization!
}
@@ -52,16 +60,131 @@ public:
}
private:
- //avoid static destruction order fiasco: there may be accesses to "Global<T>::get()" during process shutdown
- //e.g. _("") used by message in debug_minidump.cpp or by some detached thread assembling an error message!
- //=> use trivially-destructible POD only!!!
struct Pod
{
+ std::atomic<bool> spinLock; // { false }; rely entirely on static zero-initialization! => avoid potential contention with worker thread during Global<> construction!
+ //serialize access; can't use std::mutex: has non-trival destructor
std::shared_ptr<T>* inst; // = nullptr;
+ } pod_;
+};
+
+//===================================================================================================================
+//===================================================================================================================
+
+struct CleanUpEntry
+{
+ using CleanUpFunction = void (*)(void* callbackData);
+ CleanUpFunction cleanUpFun;
+ void* callbackData;
+ CleanUpEntry* prev;
+};
+void registerGlobalForDestruction(CleanUpEntry& entry);
+
+
+template <class T>
+class FunStatGlobal
+{
+public:
+ //No FunStatGlobal() or ~FunStatGlobal()!
+
+ std::shared_ptr<T> get()
+ {
+ static_assert(std::is_trivially_constructible_v<FunStatGlobal>&&
+ std::is_trivially_destructible_v<FunStatGlobal>, "this class must not generate code for magic statics!");
+
+ while (pod_.spinLock.exchange(true)) ;
+ ZEN_ON_SCOPE_EXIT(pod_.spinLock = false);
+ if (pod_.inst)
+ return *pod_.inst;
+ return nullptr;
+ }
+
+ void set(std::unique_ptr<T>&& newInst)
+ {
+ std::shared_ptr<T>* tmpInst = nullptr;
+ if (newInst)
+ tmpInst = new std::shared_ptr<T>(std::move(newInst));
+ {
+ while (pod_.spinLock.exchange(true)) ;
+ ZEN_ON_SCOPE_EXIT(pod_.spinLock = false);
+
+ std::swap(pod_.inst, tmpInst);
+ registerDestruction();
+ }
+ delete tmpInst;
+ }
+
+ void initOnce(std::unique_ptr<T> (*getInitialValue)())
+ {
+ while (pod_.spinLock.exchange(true)) ;
+ ZEN_ON_SCOPE_EXIT(pod_.spinLock = false);
+
+ if (!pod_.cleanUpEntry.cleanUpFun)
+ {
+ assert(!pod_.inst);
+ if (std::unique_ptr<T> newInst = (*getInitialValue)())
+ pod_.inst = new std::shared_ptr<T>(std::move(newInst));
+ registerDestruction();
+ }
+ }
+
+private:
+ //call while holding pod_.spinLock
+ void registerDestruction()
+ {
+ assert(pod_.spinLock);
+
+ if (!pod_.cleanUpEntry.cleanUpFun)
+ {
+ pod_.cleanUpEntry.callbackData = this;
+ pod_.cleanUpEntry.cleanUpFun = [](void* callbackData)
+ {
+ auto thisPtr = static_cast<FunStatGlobal*>(callbackData);
+ thisPtr->set(nullptr);
+ };
+
+ registerGlobalForDestruction(pod_.cleanUpEntry);
+ }
+ }
+
+ struct Pod
+ {
std::atomic<bool> spinLock; // { false }; rely entirely on static zero-initialization! => avoid potential contention with worker thread during Global<> construction!
//serialize access; can't use std::mutex: has non-trival destructor
+ std::shared_ptr<T>* inst; // = nullptr;
+ CleanUpEntry cleanUpEntry;
} pod_;
};
+
+
+inline
+void registerGlobalForDestruction(CleanUpEntry& entry)
+{
+ static struct
+ {
+ std::atomic<bool> spinLock;
+ CleanUpEntry* head;
+ } cleanUpList;
+
+ static_assert(std::is_trivially_constructible_v<decltype(cleanUpList)>&&
+ std::is_trivially_destructible_v<decltype(cleanUpList)>, "we must not generate code for magic statics!");
+
+ while (cleanUpList.spinLock.exchange(true)) ;
+ ZEN_ON_SCOPE_EXIT(cleanUpList.spinLock = false);
+
+ std::atexit([]
+ {
+ while (cleanUpList.spinLock.exchange(true)) ;
+ ZEN_ON_SCOPE_EXIT(cleanUpList.spinLock = false);
+
+ (*cleanUpList.head->cleanUpFun)(cleanUpList.head->callbackData);
+ cleanUpList.head = cleanUpList.head->prev; //nicely clean up in reverse order of construction
+ });
+
+ entry.prev = cleanUpList.head;
+ cleanUpList.head = &entry;
+
+}
}
#endif //GLOBALS_H_8013740213748021573485
diff --git a/zen/guid.h b/zen/guid.h
index 89e800b5..a26688f8 100755
--- a/zen/guid.h
+++ b/zen/guid.h
@@ -9,6 +9,7 @@
#include <fcntl.h> //open
#include <unistd.h> //close
+ #include <zen/sys_error.h>
//#include <uuid/uuid.h> -> uuid_generate(), uuid_unparse(); avoid additional dependency for "sudo apt-get install uuid-dev"
diff --git a/zen/http.cpp b/zen/http.cpp
index d06d3309..1f89bf20 100755
--- a/zen/http.cpp
+++ b/zen/http.cpp
@@ -26,9 +26,9 @@ public:
const bool useTls = [&]
{
- if (startsWith(url, Zstr("http://"), CmpAsciiNoCase()))
+ if (startsWithAsciiNoCase(url, Zstr("http://")))
return false;
- if (startsWith(url, Zstr("https://"), CmpAsciiNoCase()))
+ if (startsWithAsciiNoCase(url, Zstr("https://")))
return true;
throw SysError(L"URL uses unexpected protocol.");
}();
@@ -57,35 +57,16 @@ public:
//https://www.w3.org/Protocols/HTTP/1.0/spec.html#Request-Line
std::string msg = (postParams ? "POST " : "GET ") + utfTo<std::string>(page) + " HTTP/1.0\r\n";
- for (const auto& item : headers)
- msg += item.first + ": " + item.second + "\r\n";
+ for (const auto& [name, value] : headers)
+ msg += name + ": " + value + "\r\n";
msg += "\r\n";
msg += postBuf;
//send request
for (size_t bytesToSend = msg.size(); bytesToSend > 0;)
- {
- int bytesSent = 0;
- for (;;)
- {
- bytesSent = ::send(socket_->get(), //_In_ SOCKET s,
- &*(msg.end() - bytesToSend), //_In_ const char *buf,
- static_cast<int>(bytesToSend), //_In_ int len,
- 0); //_In_ int flags
- if (bytesSent >= 0 || errno != EINTR)
- break;
- }
- if (bytesSent < 0)
- THROW_LAST_SYS_ERROR_WSA(L"send");
- if (bytesSent > static_cast<int>(bytesToSend))
- throw SysError(L"send: buffer overflow.");
- if (bytesSent == 0)
- throw SysError(L"send: zero bytes processed");
-
- bytesToSend -= bytesSent;
- }
- if (::shutdown(socket_->get(), SHUT_WR) != 0)
- THROW_LAST_SYS_ERROR_WSA(L"shutdown");
+ bytesToSend -= tryWriteSocket(socket_->get(), &*(msg.end() - bytesToSend), bytesToSend); //throw SysError
+
+ shutdownSocketSend(socket_->get()); //throw SysError
//receive response:
std::string headBuf;
@@ -116,7 +97,7 @@ public:
const std::vector<std::string> statusItems = split(statusBuf, ' ', SplitType::ALLOW_EMPTY); //HTTP-Version SP Status-Code SP Reason-Phrase CRLF
if (statusItems.size() < 2 || !startsWith(statusItems[0], "HTTP/"))
- throw SysError(L"Invalid HTTP response: \"" + utfTo<std::wstring>(statusBuf) + L"\"");
+ throw SysError(L"Invalid HTTP response: \"" + utfTo<std::wstring>(statusBuf) + L'"');
statusCode_ = stringTo<int>(statusItems[1]);
@@ -175,8 +156,6 @@ public:
private:
size_t tryRead(void* buffer, size_t bytesToRead) //throw SysError; may return short, only 0 means EOF!
{
- if (bytesToRead == 0) //"read() with a count of 0 returns zero" => indistinguishable from end of file! => check!
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
assert(bytesToRead <= getBlockSize()); //block size might be 1000 while reading HTTP header
if (contentRemaining_ >= 0)
@@ -185,21 +164,7 @@ private:
return 0;
bytesToRead = static_cast<size_t>(std::min(static_cast<int64_t>(bytesToRead), contentRemaining_)); //[!] contentRemaining_ > 4 GB possible!
}
- int bytesReceived = 0;
- for (;;)
- {
- bytesReceived = ::recv(socket_->get(), //_In_ SOCKET s,
- static_cast<char*>(buffer), //_Out_ char *buf,
- static_cast<int>(bytesToRead), //_In_ int len,
- 0); //_In_ int flags
- if (bytesReceived >= 0 || errno != EINTR)
- break;
- }
- if (bytesReceived < 0)
- THROW_LAST_SYS_ERROR_WSA(L"recv");
- if (static_cast<size_t>(bytesReceived) > bytesToRead) //better safe than sorry
- throw SysError(L"HttpInputStream::tryRead: buffer overflow.");
-
+ const size_t bytesReceived = tryReadSocket(socket_->get(), buffer, bytesToRead); //throw SysError; may return short, only 0 means EOF!
if (contentRemaining_ >= 0)
contentRemaining_ -= bytesReceived;
@@ -325,8 +290,8 @@ std::string urldecode(const std::string& str)
std::string zen::xWwwFormUrlEncode(const std::vector<std::pair<std::string, std::string>>& paramPairs)
{
std::string output;
- for (const auto& pair : paramPairs)
- output += urlencode(pair.first) + '=' + urlencode(pair.second) + '&';
+ for (const auto& [name, value] : paramPairs)
+ output += urlencode(name) + '=' + urlencode(value) + '&';
//encode both key and value: https://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1
if (!output.empty())
output.pop_back();
diff --git a/zen/i18n.h b/zen/i18n.h
index 45762861..2ecee45a 100755
--- a/zen/i18n.h
+++ b/zen/i18n.h
@@ -59,10 +59,10 @@ std::shared_ptr<const TranslationHandler> getTranslator();
namespace impl
{
inline
-Global<const TranslationHandler>& refGlobalTranslationHandler()
+FunStatGlobal<const TranslationHandler>& refGlobalTranslationHandler()
{
//getTranslator() may be called even after static objects of this translation unit are destroyed!
- static Global<const TranslationHandler> inst; //external linkage even in header!
+ static FunStatGlobal<const TranslationHandler> inst; //external linkage even in header!
return inst;
}
}
diff --git a/zen/legacy_compiler.h b/zen/legacy_compiler.h
index e9d50b97..16d87c53 100755
--- a/zen/legacy_compiler.h
+++ b/zen/legacy_compiler.h
@@ -7,14 +7,11 @@
#ifndef LEGACY_COMPILER_H_839567308565656789
#define LEGACY_COMPILER_H_839567308565656789
- #include <optional>
-
namespace std
{
//https://gcc.gnu.org/onlinedocs/libstdc++/manual/status.html
//https://isocpp.org/std/standing-documents/sd-6-sg10-feature-test-recommendations
-
}
#endif //LEGACY_COMPILER_H_839567308565656789
diff --git a/zen/scope_guard.h b/zen/scope_guard.h
index d056bb2a..9eff6c1f 100755
--- a/zen/scope_guard.h
+++ b/zen/scope_guard.h
@@ -10,6 +10,7 @@
#include <cassert>
#include <exception>
#include "type_traits.h"
+#include "legacy_compiler.h" //std::uncaught_exceptions
//best of Zen, Loki and C++17
@@ -103,8 +104,8 @@ auto makeGuard(F&& fun) { return ScopeGuard<runMode, std::decay_t<F>>(std::forwa
#define ZEN_CHECK_CASE_FOR_CONSTANT_IMPL(X) L ## X
-#define ZEN_ON_SCOPE_EXIT(X) auto ZEN_CONCAT(dummy, __LINE__) = zen::makeGuard<zen::ScopeGuardRunMode::ON_EXIT >([&]{ X; }); (void)ZEN_CONCAT(dummy, __LINE__);
-#define ZEN_ON_SCOPE_FAIL(X) auto ZEN_CONCAT(dummy, __LINE__) = zen::makeGuard<zen::ScopeGuardRunMode::ON_FAIL >([&]{ X; }); (void)ZEN_CONCAT(dummy, __LINE__);
-#define ZEN_ON_SCOPE_SUCCESS(X) auto ZEN_CONCAT(dummy, __LINE__) = zen::makeGuard<zen::ScopeGuardRunMode::ON_SUCCESS>([&]{ X; }); (void)ZEN_CONCAT(dummy, __LINE__);
+#define ZEN_ON_SCOPE_EXIT(X) auto ZEN_CONCAT(scopeGuard, __LINE__) = zen::makeGuard<zen::ScopeGuardRunMode::ON_EXIT >([&]{ X; }); (void)ZEN_CONCAT(scopeGuard, __LINE__);
+#define ZEN_ON_SCOPE_FAIL(X) auto ZEN_CONCAT(scopeGuard, __LINE__) = zen::makeGuard<zen::ScopeGuardRunMode::ON_FAIL >([&]{ X; }); (void)ZEN_CONCAT(scopeGuard, __LINE__);
+#define ZEN_ON_SCOPE_SUCCESS(X) auto ZEN_CONCAT(scopeGuard, __LINE__) = zen::makeGuard<zen::ScopeGuardRunMode::ON_SUCCESS>([&]{ X; }); (void)ZEN_CONCAT(scopeGuard, __LINE__);
#endif //SCOPE_GUARD_H_8971632487321434
diff --git a/zen/shell_execute.h b/zen/shell_execute.h
index 0802fbcb..98824d70 100755
--- a/zen/shell_execute.h
+++ b/zen/shell_execute.h
@@ -39,7 +39,7 @@ void shellExecute(const Zstring& command, ExecutionType type) //throw FileError
if (type == ExecutionType::SYNC)
{
//Posix ::system() - execute a shell command
- const int rv = ::system(command.c_str()); //do NOT use std::system as its documentation says nothing about "WEXITSTATUS(rv)", ect...
+ const int rv = ::system(command.c_str()); //do NOT use std::system as its documentation says nothing about "WEXITSTATUS(rv)", etc...
if (rv == -1 || WEXITSTATUS(rv) == 127)
throw FileError(_("Incorrect command line:") + L"\n" + utfTo<std::wstring>(command));
//http://linux.die.net/man/3/system "In case /bin/sh could not be executed, the exit status will be that of a command that does exit(127)"
@@ -73,7 +73,7 @@ void shellExecute(const Zstring& command, ExecutionType type) //throw FileError
inline
void openWithDefaultApplication(const Zstring& itemPath) //throw FileError
{
- shellExecute("xdg-open \"" + itemPath + "\"", ExecutionType::ASYNC); //
+ shellExecute("xdg-open \"" + itemPath + '"', ExecutionType::ASYNC); //
}
}
diff --git a/zen/socket.h b/zen/socket.h
index e551a5ba..33ac2e50 100755
--- a/zen/socket.h
+++ b/zen/socket.h
@@ -20,6 +20,12 @@ namespace zen
do { const ErrorCode ecInternal = getLastError(); throw SysError(formatSystemError(functionName, ecInternal)); } while (false)
+//patch up socket portability:
+using SocketType = int;
+const SocketType invalidSocket = -1;
+inline void closeSocket(SocketType s) { ::close(s); }
+
+
//Winsock needs to be initialized before calling any of these functions! (WSAStartup/WSACleanup)
class Socket //throw SysError
@@ -67,18 +73,78 @@ public:
~Socket() { closeSocket(socket_); }
- using SocketType = int;
SocketType get() const { return socket_; }
private:
Socket (const Socket&) = delete;
Socket& operator=(const Socket&) = delete;
- static const SocketType invalidSocket = -1;
- static void closeSocket(SocketType s) { ::close(s); }
-
SocketType socket_ = invalidSocket;
};
+
+
+//more socket helper functions:
+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__));
+
+ int bytesReceived = 0;
+ for (;;)
+ {
+ bytesReceived = ::recv(socket, //_In_ SOCKET s,
+ static_cast<char*>(buffer), //_Out_ char *buf,
+ static_cast<int>(bytesToRead), //_In_ int len,
+ 0); //_In_ int flags
+ if (bytesReceived >= 0 || errno != EINTR)
+ break;
+ }
+ if (bytesReceived < 0)
+ THROW_LAST_SYS_ERROR_WSA(L"recv");
+
+ if (static_cast<size_t>(bytesReceived) > bytesToRead) //better safe than sorry
+ throw SysError(L"HttpInputStream::tryRead: buffer overflow.");
+
+ return bytesReceived; //"zero indicates end of file"
+}
+
+
+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__));
+
+ int bytesWritten = 0;
+ for (;;)
+ {
+ bytesWritten = ::send(socket, //_In_ SOCKET s,
+ static_cast<const char*>(buffer), //_In_ const char *buf,
+ static_cast<int>(bytesToWrite), //_In_ int len,
+ 0); //_In_ int flags
+ if (bytesWritten >= 0 || errno != EINTR)
+ break;
+ }
+ if (bytesWritten < 0)
+ THROW_LAST_SYS_ERROR_WSA(L"send");
+ if (bytesWritten > static_cast<int>(bytesToWrite))
+ throw SysError(L"send: buffer overflow.");
+ if (bytesWritten == 0)
+ throw SysError(L"send: zero bytes processed");
+
+ return bytesWritten;
+}
+}
+
+
+inline
+void shutdownSocketSend(SocketType socket) //throw SysError
+{
+ if (::shutdown(socket, SHUT_WR) != 0)
+ THROW_LAST_SYS_ERROR_WSA(L"shutdown");
+}
+
}
#endif //SOCKET_H_23498325972583947678456437
diff --git a/zen/stl_tools.h b/zen/stl_tools.h
index be9bf710..c3a9bf8f 100755
--- a/zen/stl_tools.h
+++ b/zen/stl_tools.h
@@ -12,6 +12,7 @@
#include <vector>
#include <memory>
#include <algorithm>
+#include <optional>
#include "string_traits.h"
#include "build_info.h"
diff --git a/zen/string_tools.h b/zen/string_tools.h
index 8746722a..657c70d5 100755
--- a/zen/string_tools.h
+++ b/zen/string_tools.h
@@ -26,32 +26,30 @@ template <class Char> bool isWhiteSpace(Char c);
template <class Char> bool isDigit (Char c); //not exactly the same as "std::isdigit" -> we consider '0'-'9' only!
template <class Char> bool isHexDigit (Char c);
template <class Char> bool isAsciiAlpha(Char c);
+template <class Char> bool isAsciiString(const Char* str);
template <class Char> Char asciiToLower(Char c);
template <class Char> Char asciiToUpper(Char c);
-//case-sensitive comparison (compile-time correctness: use different number of arguments as STL comparison predicates!)
-struct CmpBinary { template <class Char> int operator()(const Char* lhs, size_t lhsLen, const Char* rhs, size_t rhsLen) const; };
+//both S and T can be strings or char/wchar_t arrays or single char/wchar_t
+template <class S, class T> bool contains(const S& str, const T& term);
-//basic case-insensitive comparison (considering A-Z only!)
-struct CmpAsciiNoCase { template <class Char> int operator()(const Char* lhs, size_t lhsLen, const Char* rhs, size_t rhsLen) const; };
+template <class S, class T> bool startsWith (const S& str, const T& prefix);
+template <class S, class T> bool startsWithAsciiNoCase(const S& str, const T& prefix);
-struct LessAsciiNoCase
-{
- template <class S> //don't support heterogenous input! => use as container predicate only!
- bool operator()(const S& lhs, const S& rhs) const { return CmpAsciiNoCase()(strBegin(lhs), strLength(lhs), strBegin(rhs), strLength(rhs)) < 0; }
-};
+template <class S, class T> bool endsWith (const S& str, const T& postfix);
+template <class S, class T> bool endsWithAsciiNoCase(const S& str, const T& postfix);
-//both S and T can be strings or char/wchar_t arrays or simple char/wchar_t
-template <class S, class T> bool contains(const S& str, const T& term);
+template <class S, class T> bool equalString (const S& lhs, const T& rhs);
+template <class S, class T> bool equalAsciiNoCase(const S& lhs, const T& rhs);
-template <class S, class T> bool startsWith(const S& str, const T& prefix);
-template <class S, class T, class Function> bool startsWith(const S& str, const T& prefix, Function cmpStringFun);
+template <class S, class T> int compareString (const S& lhs, const T& rhs);
+template <class S, class T> int compareAsciiNoCase(const S& lhs, const T& rhs); //basic case-insensitive comparison (considering A-Z only!)
-template <class S, class T> bool endsWith (const S& str, const T& postfix);
-template <class S, class T, class Function> bool endsWith (const S& str, const T& postfix, Function cmpStringFun);
+struct LessAsciiNoCase //STL container predicate
+{
+ template <class S> bool operator()(const S& lhs, const S& rhs) const { return compareAsciiNoCase(lhs, rhs) < 0; }
+};
-template <class S, class T> bool strEqual(const S& lhs, const T& rhs);
-template <class S, class T, class Function> bool strEqual(const S& lhs, const T& rhs, Function cmpStringFun);
enum FailureReturnVal
{
@@ -152,6 +150,17 @@ bool isAsciiAlpha(Char c)
template <class Char> inline
+bool isAsciiString(const Char* str)
+{
+ static_assert(std::is_same_v<Char, char> || std::is_same_v<Char, wchar_t>);
+ for (Char c = *str; c != 0; c = *++str)
+ if (zen::makeUnsigned(c) >= 128)
+ return false;
+ return true;
+}
+
+
+template <class Char> inline
Char asciiToLower(Char c)
{
if (static_cast<Char>('A') <= c && c <= static_cast<Char>('Z'))
@@ -169,41 +178,103 @@ Char asciiToUpper(Char c)
}
-template <class S, class T, class Function> inline
-bool startsWith(const S& str, const T& prefix, Function cmpStringFun)
+namespace impl
+{
+inline int strcmpWithNulls(const char* ptr1, const char* ptr2, size_t num) { return std:: memcmp(ptr1, ptr2, num); } //support embedded 0, unlike strncmp/wcsncmp!
+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)
+{
+ while (len-- > 0)
+ {
+ const Char charL = asciiToLower(*lhs++); //ordering: lower-case chars have higher code points than uppper-case
+ const Char 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!
+ }
+ return 0;
+}
+}
+
+
+template <class S, class T> inline
+bool startsWith(const S& str, const T& prefix)
{
const size_t pfLen = strLength(prefix);
- if (strLength(str) < pfLen)
- return false;
+ return strLength(str) >= pfLen && impl::strcmpWithNulls(strBegin(str), strBegin(prefix), pfLen) == 0;
+}
+
- return cmpStringFun(strBegin(str), pfLen,
- strBegin(prefix), pfLen) == 0;
+template <class S, class T> inline
+bool startsWithAsciiNoCase(const S& str, const T& prefix)
+{
+ const size_t pfLen = strLength(prefix);
+ return strLength(str) >= pfLen && impl::strcmpAsciiNoCase(strBegin(str), strBegin(prefix), pfLen) == 0;
}
-template <class S, class T, class Function> inline
-bool endsWith(const S& str, const T& postfix, Function cmpStringFun)
+template <class S, class T> inline
+bool endsWith(const S& str, const T& postfix)
{
const size_t strLen = strLength(str);
const size_t pfLen = strLength(postfix);
- if (strLen < pfLen)
- return false;
+ return strLen >= pfLen && impl::strcmpWithNulls(strBegin(str) + strLen - pfLen, strBegin(postfix), pfLen) == 0;
+}
+
+
+template <class S, class T> inline
+bool endsWithAsciiNoCase(const S& str, const T& postfix)
+{
+ const size_t strLen = strLength(str);
+ const size_t pfLen = strLength(postfix);
+ return strLen >= pfLen && impl::strcmpAsciiNoCase(strBegin(str) + strLen - pfLen, strBegin(postfix), pfLen) == 0;
+}
+
- return cmpStringFun(strBegin(str) + strLen - pfLen, pfLen,
- strBegin(postfix), pfLen) == 0;
+template <class S, class T> inline
+bool equalString(const S& lhs, const T& rhs)
+{
+ const size_t lhsLen = strLength(lhs);
+ return lhsLen == strLength(rhs) && impl::strcmpWithNulls(strBegin(lhs), strBegin(rhs), lhsLen) == 0;
}
-template <class S, class T, class Function> inline
-bool strEqual(const S& lhs, const T& rhs, Function cmpStringFun)
+template <class S, class T> inline
+bool equalAsciiNoCase(const S& lhs, const T& rhs)
+{
+ const size_t lhsLen = strLength(lhs);
+ return lhsLen == strLength(rhs) && impl::strcmpAsciiNoCase(strBegin(lhs), strBegin(rhs), lhsLen) == 0;
+}
+
+
+template <class S, class T> inline
+int compareString(const S& lhs, const T& rhs)
{
- return cmpStringFun(strBegin(lhs), strLength(lhs), strBegin(rhs), strLength(rhs)) == 0;
+ const size_t lhsLen = strLength(lhs);
+ const size_t rhsLen = strLength(rhs);
+
+ //length check *after* strcmpWithNulls(): we do care about natural ordering: e.g. for "compareString(makeUpperCopy(lhs), makeUpperCopy(rhs))"
+ const int rv = impl::strcmpWithNulls(strBegin(lhs), strBegin(rhs), std::min(lhsLen, rhsLen));
+ if (rv != 0)
+ return rv;
+ return static_cast<int>(lhsLen) - static_cast<int>(rhsLen);
}
-template <class S, class T> inline bool startsWith(const S& str, const T& prefix ) { return startsWith(str, prefix, CmpBinary()); }
-template <class S, class T> inline bool endsWith (const S& str, const T& postfix) { return endsWith (str, postfix, CmpBinary()); }
-template <class S, class T> inline bool strEqual (const S& lhs, const T& rhs ) { return strEqual (lhs, rhs, CmpBinary()); }
+template <class S, class T> inline
+int compareAsciiNoCase(const S& lhs, const T& rhs)
+{
+ const size_t lhsLen = strLength(lhs);
+ const size_t rhsLen = strLength(rhs);
+
+ const int rv = impl::strcmpAsciiNoCase(strBegin(lhs), strBegin(rhs), std::min(lhsLen, rhsLen));
+ if (rv != 0)
+ return rv;
+ return static_cast<int>(lhsLen) - static_cast<int>(rhsLen);
+}
template <class S, class T> inline
@@ -464,42 +535,12 @@ struct CopyStringToString<T, T> //perf: we don't need a deep copy if string type
template <class S>
T copy(S&& str) const { return std::forward<S>(str); }
};
-
-inline int strcmpWithNulls(const char* ptr1, const char* ptr2, size_t num) { return std::memcmp (ptr1, ptr2, num); }
-inline int strcmpWithNulls(const wchar_t* ptr1, const wchar_t* ptr2, size_t num) { return std::wmemcmp(ptr1, ptr2, num); }
}
template <class T, class S> inline
T copyStringTo(S&& str) { return impl::CopyStringToString<std::decay_t<S>, T>().copy(std::forward<S>(str)); }
-template <class Char> inline
-int CmpBinary::operator()(const Char* lhs, size_t lhsLen, const Char* rhs, size_t rhsLen) const
-{
- //support embedded 0, unlike strncmp/wcsncmp!
- const int rv = impl::strcmpWithNulls(lhs, rhs, std::min(lhsLen, rhsLen));
- if (rv != 0)
- return rv;
- return static_cast<int>(lhsLen) - static_cast<int>(rhsLen);
-}
-
-
-template <class Char> inline
-int CmpAsciiNoCase::operator()(const Char* lhs, size_t lhsLen, const Char* rhs, size_t rhsLen) const
-{
- const auto* const lhsLast = lhs + std::min(lhsLen, rhsLen);
- while (lhs != lhsLast)
- {
- const Char charL = asciiToLower(*lhs++); //ordering: lower-case chars have higher code points than uppper-case
- const Char 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!
- }
- return static_cast<int>(lhsLen) - static_cast<int>(rhsLen);
-}
-
-
namespace impl
{
template <class Num> inline
diff --git a/zen/thread.cpp b/zen/thread.cpp
index 8016d4a9..08bfaa25 100755
--- a/zen/thread.cpp
+++ b/zen/thread.cpp
@@ -34,10 +34,7 @@ uint64_t getThreadIdNative()
}
-struct InitMainThreadIdOnStartup
-{
- InitMainThreadIdOnStartup() { getMainThreadId(); }
-} startupInitMainThreadId;
+const uint64_t globalMainThreadId = getThreadId(); //avoid code-gen for "magic static"!
}
@@ -50,6 +47,9 @@ uint64_t zen::getThreadId()
uint64_t zen::getMainThreadId()
{
- static const uint64_t mainThreadId = getThreadId();
- return mainThreadId;
+ //don't make this a function-scope static (avoid code-gen for "magic static")
+ if (globalMainThreadId == 0) //might be called during static initialization
+ return getThreadId();
+
+ return globalMainThreadId;
}
diff --git a/zen/time.h b/zen/time.h
index b06d3d15..a32e28e3 100755
--- a/zen/time.h
+++ b/zen/time.h
@@ -327,13 +327,13 @@ TimeComp parseTime(const String& format, const String2& str, UserDefinedFormatTa
const CharType* itStr = strBegin(str);
const CharType* const strLast = itStr + strLength(str);
- auto extractNumber = [&](int& result, size_t digitCount) -> bool
+ auto extractNumber = [&](int& result, size_t digitCount)
{
if (strLast - itStr < makeSigned(digitCount))
return false;
- if (std::any_of(itStr, itStr + digitCount, [](CharType c) { return !isDigit(c); }))
- return false;
+ if (!std::all_of(itStr, itStr + digitCount, isDigit<CharType>))
+ return false;
result = zen::stringTo<int>(StringRef<const CharType>(itStr, itStr + digitCount));
itStr += digitCount;
diff --git a/zen/type_traits.h b/zen/type_traits.h
index 2d4e7a97..8783cb6a 100755
--- a/zen/type_traits.h
+++ b/zen/type_traits.h
@@ -8,7 +8,6 @@
#define TYPE_TRAITS_H_3425628658765467
#include <type_traits>
-#include "legacy_compiler.h"
//http://en.cppreference.com/w/cpp/header/type_traits
diff --git a/zen/utf.h b/zen/utf.h
index da6aaf97..5a095874 100755
--- a/zen/utf.h
+++ b/zen/utf.h
@@ -192,7 +192,7 @@ public:
std::optional<CodePoint> getNext()
{
if (it_ == last_)
- return std::nullopt; //GCC 8.2 bug: -Wmaybe-uninitialized for "return {};"
+ return std::nullopt;
const Char8 ch = *it_++;
CodePoint cp = ch;
@@ -313,7 +313,7 @@ bool isValidUtf(const UtfString& str)
using namespace impl;
UtfDecoder<GetCharTypeT<UtfString>> decoder(strBegin(str), strLength(str));
- while (std::optional<CodePoint> cp = decoder.getNext())
+ while (const std::optional<CodePoint> cp = decoder.getNext())
if (*cp == REPLACEMENT_CHAR)
return false;
@@ -367,7 +367,7 @@ TargetString utfTo(const SourceString& str, std::false_type)
TargetString output;
UtfDecoder<CharSrc> decoder(strBegin(str), strLength(str));
- while (std::optional<CodePoint> cp = decoder.getNext())
+ while (const std::optional<CodePoint> cp = decoder.getNext())
codePointToUtf<CharTrg>(*cp, [&](CharTrg c) { output += c; });
return output;
diff --git a/zen/zstring.cpp b/zen/zstring.cpp
index 8bf77a0b..68609030 100755
--- a/zen/zstring.cpp
+++ b/zen/zstring.cpp
@@ -8,9 +8,102 @@
#include <stdexcept>
#include "utf.h"
+ #include <gtk/gtk.h>
+ #include "sys_error.h"
using namespace zen;
+
+Zstring makeUpperCopy(const Zstring& str)
+{
+ //fast pre-check:
+ if (isAsciiString(str.c_str())) //perf: in the range of 3.5ns
+ {
+ Zstring output = str;
+ for (Zchar& c : output) c = asciiToUpper(c);
+ return output;
+ }
+
+ Zstring strNorm = getUnicodeNormalForm(str);
+ try
+ {
+ static_assert(sizeof(impl::CodePoint) == sizeof(gunichar));
+ Zstring output;
+ output.reserve(strNorm.size());
+
+ impl::UtfDecoder<char> decoder(strNorm.c_str(), strNorm.size());
+ while (const std::optional<impl::CodePoint> cp = decoder.getNext())
+ impl::codePointToUtf<char>(::g_unichar_toupper(*cp), [&](char c) { output += c; }); //don't use std::towupper: *incomplete* and locale-dependent!
+
+ return output;
+
+ }
+ catch (const SysError& e)
+ {
+ (void)e;
+ assert(false);
+ return str;
+ }
+}
+
+
+Zstring getUnicodeNormalForm(const Zstring& str)
+{
+ //fast pre-check:
+ if (isAsciiString(str.c_str())) //perf: in the range of 3.5ns
+ return str; //god bless our ref-counting! => save output string memory consumption!
+
+ //Example: const char* decomposed = "\x6f\xcc\x81";
+ // const char* precomposed = "\xc3\xb3";
+ try
+ {
+ gchar* outStr = ::g_utf8_normalize (str.c_str(), str.length(), G_NORMALIZE_DEFAULT_COMPOSE);
+ if (!outStr)
+ throw SysError(L"g_utf8_normalize: conversion failed. (" + utfTo<std::wstring>(str) + L")");
+ ZEN_ON_SCOPE_EXIT(::g_free(outStr));
+ return outStr;
+
+ }
+ catch (const SysError& e)
+ {
+ (void)e;
+ assert(false);
+ return str;
+ }
+}
+
+
+Zstring replaceCpyAsciiNoCase(const Zstring& str, const Zstring& oldTerm, const Zstring& newTerm)
+{
+ if (oldTerm.empty())
+ return str;
+
+ Zstring strU = str;
+ Zstring oldU = oldTerm;
+
+ for (Zchar& c : strU) c = asciiToUpper(c); //can't use makeUpperCopy(): input/output sizes may differ!
+ for (Zchar& c : oldU) c = asciiToUpper(c); //
+
+ Zstring output;
+
+ for (size_t pos = 0;;)
+ {
+ const size_t posFound = strU.find(oldU, pos);
+ if (posFound == Zstring::npos)
+ {
+ if (pos == 0) //optimize "oldTerm not found": return ref-counted copy
+ return str;
+ output.append(str.begin() + pos, str.end());
+ return output;
+ }
+
+ output.append(str.begin() + pos, str.begin() + posFound);
+ output += newTerm;
+ pos = posFound + oldTerm.size();
+ }
+}
+
+
/*
MSDN "Handling Sorting in Your Applications": https://msdn.microsoft.com/en-us/library/windows/desktop/dd318144
@@ -33,8 +126,14 @@ OS X (UTF8 char)
________________________
time per call | function
*/
+int compareLocalPath(const Zstring& lhs, const Zstring& rhs)
+{
+ assert(lhs.find(Zchar('\0')) == Zstring::npos); //don't expect embedded nulls!
+ assert(rhs.find(Zchar('\0')) == Zstring::npos); //
+ return compareString(lhs, rhs);
+}
namespace
@@ -43,7 +142,7 @@ int compareNoCaseUtf8(const char* lhs, size_t lhsLen, const char* rhs, size_t rh
{
//- strncasecmp implements ASCII CI-comparsion only! => signature is broken for UTF8-input; toupper() similarly doesn't support Unicode
//- wcsncasecmp: https://opensource.apple.com/source/Libc/Libc-763.12/string/wcsncasecmp-fbsd.c
- // => re-implement comparison based on towlower() to avoid memory allocations
+ // => re-implement comparison based on g_unichar_tolower() to avoid memory allocations
impl::UtfDecoder<char> decL(lhs, lhsLen);
impl::UtfDecoder<char> decR(rhs, rhsLen);
@@ -54,23 +153,35 @@ int compareNoCaseUtf8(const char* lhs, size_t lhsLen, const char* rhs, size_t rh
if (!cpL || !cpR)
return static_cast<int>(!cpR) - static_cast<int>(!cpL);
- //support unit-testing on Windows: CodePoint is truncated to wchar_t
- static_assert(sizeof(wchar_t) == sizeof(impl::CodePoint));
+ static_assert(sizeof(gunichar) == sizeof(impl::CodePoint));
- const wchar_t charL = ::towlower(static_cast<wchar_t>(*cpL)); //ordering: towlower() converts to higher code points than towupper()
- const wchar_t charR = ::towlower(static_cast<wchar_t>(*cpR)); //uses LC_CTYPE category of current locale
+ const gunichar charL = ::g_unichar_toupper(*cpL); //note: tolower can be ambiguous, so don't use:
+ const gunichar charR = ::g_unichar_toupper(*cpR); //e.g. "Σ" (upper case) can be lower-case "ς" in the end of the word or "σ" in the middle.
if (charL != charR)
+ //ordering: "to lower" converts to higher code points than "to upper"
return static_cast<unsigned int>(charL) - static_cast<unsigned int>(charR); //unsigned char-comparison is the convention!
//unsigned underflow is well-defined!
}
}
+
}
-int cmpStringNaturalLinuxTest(const char* lhs, size_t lhsLen, const char* rhs, size_t rhsLen)
+int compareNatural(const Zstring& lhs, const Zstring& rhs)
{
- const char* const lhsEnd = lhs + lhsLen;
- const char* const rhsEnd = rhs + rhsLen;
+ //Unicode normal forms:
+ // Windows: CompareString() already ignores NFD/NFC differences: nice...
+ // Linux: g_unichar_toupper() can't ignore differences
+ // macOS: CFStringCompare() considers differences
+
+ const Zstring& lhsNorm = getUnicodeNormalForm(lhs);
+ const Zstring& rhsNorm = getUnicodeNormalForm(rhs);
+
+ const char* strL = lhsNorm.c_str();
+ const char* strR = rhsNorm.c_str();
+
+ const char* const strEndL = strL + lhsNorm.size();
+ const char* const strEndR = strR + rhsNorm.size();
/*
- compare strings after conceptually creating blocks of whitespace/numbers/text
- implement strict weak ordering!
@@ -84,43 +195,43 @@ int cmpStringNaturalLinuxTest(const char* lhs, size_t lhsLen, const char* rhs, s
*/
for (;;)
{
- if (lhs == lhsEnd || rhs == rhsEnd)
- return static_cast<int>(lhs != lhsEnd) - static_cast<int>(rhs != rhsEnd); //"nothing" before "something"
+ if (strL == strEndL || strR == strEndR)
+ return static_cast<int>(strL != strEndL) - static_cast<int>(strR != strEndR); //"nothing" before "something"
//note: "something" never would have been condensed to "nothing" further below => can finish evaluation here
- const bool wsL = isWhiteSpace(*lhs);
- const bool wsR = isWhiteSpace(*rhs);
+ const bool wsL = isWhiteSpace(*strL);
+ const bool wsR = isWhiteSpace(*strR);
if (wsL != wsR)
return static_cast<int>(!wsL) - static_cast<int>(!wsR); //whitespace before non-ws!
if (wsL)
{
- ++lhs, ++rhs;
- while (lhs != lhsEnd && isWhiteSpace(*lhs)) ++lhs;
- while (rhs != rhsEnd && isWhiteSpace(*rhs)) ++rhs;
+ ++strL, ++strR;
+ while (strL != strEndL && isWhiteSpace(*strL)) ++strL;
+ while (strR != strEndR && isWhiteSpace(*strR)) ++strR;
continue;
}
- const bool digitL = isDigit(*lhs);
- const bool digitR = isDigit(*rhs);
+ const bool digitL = isDigit(*strL);
+ const bool digitR = isDigit(*strR);
if (digitL != digitR)
return static_cast<int>(!digitL) - static_cast<int>(!digitR); //number before chars!
if (digitL)
{
- while (lhs != lhsEnd && *lhs == '0') ++lhs;
- while (rhs != rhsEnd && *rhs == '0') ++rhs;
+ while (strL != strEndL && *strL == '0') ++strL;
+ while (strR != strEndR && *strR == '0') ++strR;
int rv = 0;
- for (;; ++lhs, ++rhs)
+ for (;; ++strL, ++strR)
{
- const bool endL = lhs == lhsEnd || !isDigit(*lhs);
- const bool endR = rhs == rhsEnd || !isDigit(*rhs);
+ const bool endL = strL == strEndL || !isDigit(*strL);
+ const bool endR = strR == strEndR || !isDigit(*strR);
if (endL != endR)
return static_cast<int>(!endL) - static_cast<int>(!endR); //more digits means bigger number
if (endL)
break; //same number of digits
- if (rv == 0 && *lhs != *rhs)
- rv = *lhs - *rhs; //found first digit difference comparing from left
+ if (rv == 0 && *strL != *strR)
+ rv = *strL - *strR; //found first digit difference comparing from left
}
if (rv != 0)
return rv;
@@ -128,28 +239,19 @@ int cmpStringNaturalLinuxTest(const char* lhs, size_t lhsLen, const char* rhs, s
}
//compare full junks of text: consider unicode encoding!
- const char* textBeginL = lhs++;
- const char* textBeginR = rhs++; //current char is neither white space nor digit at this point!
- while (lhs != lhsEnd && !isWhiteSpace(*lhs) && !isDigit(*lhs)) ++lhs;
- while (rhs != rhsEnd && !isWhiteSpace(*rhs) && !isDigit(*rhs)) ++rhs;
+ const char* textBeginL = strL++;
+ const char* textBeginR = strR++; //current char is neither white space nor digit at this point!
+ while (strL != strEndL && !isWhiteSpace(*strL) && !isDigit(*strL)) ++strL;
+ while (strR != strEndR && !isWhiteSpace(*strR) && !isDigit(*strR)) ++strR;
- const int rv = compareNoCaseUtf8(textBeginL, lhs - textBeginL, textBeginR, rhs - textBeginR);
+ const int rv = compareNoCaseUtf8(textBeginL, strL - textBeginL, textBeginR, strR - textBeginR);
if (rv != 0)
return rv;
}
-}
-
-namespace
-{
}
-int CmpNaturalSort::operator()(const Zchar* lhs, size_t lhsLen, const Zchar* rhs, size_t rhsLen) const
-{
- //auto strL = utfTo<std::string>(Zstring(lhs, lhsLen));
- //auto strR = utfTo<std::string>(Zstring(rhs, rhsLen));
- //return cmpStringNaturalLinux(strL.c_str(), strL.size(), strR.c_str(), strR.size());
- return cmpStringNaturalLinux(lhs, lhsLen, rhs, rhsLen);
-
-} \ No newline at end of file
+warn_static("clean up implementation of these two:")
+//template <> inline bool isWhiteSpace(char c)
+//template <> inline bool isWhiteSpace(wchar_t c)
diff --git a/zen/zstring.h b/zen/zstring.h
index 7fa21335..20cf968d 100755
--- a/zen/zstring.h
+++ b/zen/zstring.h
@@ -14,6 +14,7 @@
#define Zstr(x) x
const Zchar FILE_NAME_SEPARATOR = '/';
+
//"The reason for all the fuss above" - Loki/SmartPtr
//a high-performance string for interfacing with native OS APIs in multithreaded contexts
using Zstring = zen::Zbase<Zchar>;
@@ -22,43 +23,71 @@ using Zstring = zen::Zbase<Zchar>;
using Zstringw = zen::Zbase<wchar_t>;
-//Compare filepaths: Windows/OS X does NOT distinguish between upper/lower-case, while Linux DOES
-struct CmpFilePath
-{
- int operator()(const Zchar* lhs, size_t lhsLen, const Zchar* rhs, size_t rhsLen) const;
-};
+//Caveat: don't expect input/output string sizes to match:
+// - different UTF-8 encoding length of upper-case chars
+// - different number of upper case chars (e.g. "ß" => "SS" on macOS)
+// - output is Unicode-normalized
+Zstring makeUpperCopy(const Zstring& str);
-struct CmpNaturalSort
-{
- int operator()(const Zchar* lhs, size_t lhsLen, const Zchar* rhs, size_t rhsLen) const;
-};
+//Windows, Linux: precomposed
+//macOS: decomposed
+Zstring getUnicodeNormalForm(const Zstring& str);
+Zstring replaceCpyAsciiNoCase(const Zstring& str, const Zstring& oldTerm, const Zstring& newTerm);
-struct LessFilePath
-{
- template <class S> //don't support heterogenous input! => use as container predicate only!
- bool operator()(const S& lhs, const S& rhs) const { using namespace zen; return CmpFilePath()(strBegin(lhs), strLength(lhs), strBegin(rhs), strLength(rhs)) < 0; }
-};
+//------------------------------------------------------------------------------------------
+//inline
+//int compareNoCase(const Zstring& lhs, const Zstring& rhs)
+//{
+// return zen::compareString(makeUpperCopy(lhs), makeUpperCopy(rhs));
+// //avoid eager optimization bugs: e.g. "if (isAsciiString()) compareAsciiNoCase()" might model a different order!
+//}
+
+inline bool equalNoCase(const Zstring& lhs, const Zstring& rhs) { return makeUpperCopy(lhs) == makeUpperCopy(rhs); }
-struct LessNaturalSort
+struct ZstringNoCase //use as STL container key: avoid needless upper-case conversions during std::map<>::find()
{
- template <class S> //don't support heterogenous input! => use as container predicate only!
- bool operator()(const S& lhs, const S& rhs) const { using namespace zen; return CmpNaturalSort()(strBegin(lhs), strLength(lhs), strBegin(rhs), strLength(rhs)) < 0; }
+ ZstringNoCase(const Zstring& str) : upperCase(makeUpperCopy(str)) {}
+ Zstring upperCase;
};
+inline bool operator<(const ZstringNoCase& lhs, const ZstringNoCase& rhs) { return lhs.upperCase < rhs.upperCase; }
+
+//struct LessNoCase { bool operator()(const Zstring& lhs, const Zstring& rhs) const { return compareNoCase(lhs, rhs) < 0; } };
+
+//------------------------------------------------------------------------------------------
+
+//Compare *local* file paths:
+// Windows: igore case
+// Linux: byte-wise comparison
+// macOS: igore case + Unicode normalization forms
+int compareLocalPath(const Zstring& lhs, const Zstring& rhs);
+
+inline bool equalLocalPath(const Zstring& lhs, const Zstring& rhs) { return compareLocalPath(lhs, rhs) == 0; }
+struct LessLocalPath { bool operator()(const Zstring& lhs, const Zstring& rhs) const { return compareLocalPath(lhs, rhs) < 0; } };
-template <class S>
-S makeUpperCopy(S str);
+//------------------------------------------------------------------------------------------
+int compareNatural(const Zstring& lhs, const Zstring& rhs);
+struct LessNaturalSort { bool operator()(const Zstring& lhs, const Zstring rhs) const { return compareNatural(lhs, rhs) < 0; } };
+//------------------------------------------------------------------------------------------
+
+warn_static("get rid:")
+inline int compareFilePath(const Zstring& lhs, const Zstring& rhs) { return compareLocalPath(lhs, rhs); }
+
+inline bool equalFilePath(const Zstring& lhs, const Zstring& rhs) { return compareLocalPath(lhs, rhs) == 0; }
+
+struct LessFilePath { bool operator()(const Zstring& lhs, const Zstring& rhs) const { return compareLocalPath(lhs, rhs) < 0; } };
+//------------------------------------------------------------------------------------------
-template <class S, class T> inline
-bool equalFilePath(const S& lhs, const T& rhs) { using namespace zen; return strEqual(lhs, rhs, CmpFilePath()); }
inline
Zstring appendSeparator(Zstring path) //support rvalue references!
{
- return zen::endsWith(path, FILE_NAME_SEPARATOR) ? path : (path += FILE_NAME_SEPARATOR); //returning a by-value parameter implicitly converts to r-value!
+ if (!zen::endsWith(path, FILE_NAME_SEPARATOR))
+ path += FILE_NAME_SEPARATOR;
+ return path; //returning a by-value parameter => RVO if possible, r-value otherwise!
}
@@ -82,12 +111,7 @@ Zstring getFileExtension(const Zstring& filePath)
}
-template <class S, class T, class U>
-S ciReplaceCpy(const S& str, const T& oldTerm, const U& newTerm);
-
-
-
-//common unicode sequences
+//common unicode characters
const wchar_t EM_DASH = L'\u2014';
const wchar_t EN_DASH = L'\u2013';
const wchar_t* const SPACED_DASH = L" \u2013 "; //using 'EN DASH'
@@ -99,89 +123,6 @@ const wchar_t MULT_SIGN = L'\u00D7'; //fancy "x"
-
-
-//################################# inline implementation ########################################
-inline
-void makeUpperInPlace(wchar_t* str, size_t strLen)
-{
- std::for_each(str, str + strLen, [](wchar_t& c) { c = std::towupper(c); }); //locale-dependent!
-}
-
-
-inline
-void makeUpperInPlace(char* str, size_t strLen)
-{
- std::for_each(str, str + strLen, [](char& c) { c = std::toupper(static_cast<unsigned char>(c)); }); //locale-dependent!
- //result of toupper() is an unsigned char mapped to int range: the char representation is in the last 8 bits and we need not care about signedness!
- //this should work for UTF-8, too: all chars >= 128 are mapped upon themselves!
-}
-
-
-template <class S> inline
-S makeUpperCopy(S str)
-{
- const size_t len = str.length(); //we assert S is a string type!
- if (len > 0)
- makeUpperInPlace(&*str.begin(), len);
-
- return str;
-}
-
-
-inline
-int CmpFilePath::operator()(const Zchar* lhs, size_t lhsLen, const Zchar* rhs, size_t rhsLen) const
-{
- assert(std::find(lhs, lhs + lhsLen, 0) == lhs + lhsLen); //don't expect embedded nulls!
- assert(std::find(rhs, rhs + rhsLen, 0) == rhs + rhsLen); //
-
- const int rv = std::strncmp(lhs, rhs, std::min(lhsLen, rhsLen));
- if (rv != 0)
- return rv;
- return static_cast<int>(lhsLen) - static_cast<int>(rhsLen);
-}
-
-
-template <class S, class T, class U> inline
-S ciReplaceCpy(const S& str, const T& oldTerm, const U& newTerm)
-{
- using namespace zen;
- static_assert(std::is_same_v<GetCharTypeT<S>, GetCharTypeT<T>>);
- static_assert(std::is_same_v<GetCharTypeT<T>, GetCharTypeT<U>>);
- const size_t oldLen = strLength(oldTerm);
- if (oldLen == 0)
- return str;
-
- const S strU = makeUpperCopy(str); //S required to be a string class
- const S oldU = makeUpperCopy<S>(oldTerm); //[!] T not required to be a string class
- assert(strLength(strU) == strLength(str ));
- assert(strLength(oldU) == strLength(oldTerm));
-
- const auto* const newBegin = strBegin(newTerm);
- const auto* const newEnd = newBegin + strLength(newTerm);
-
- S output;
-
- for (size_t pos = 0;;)
- {
- const auto itFound = std::search(strU.begin() + pos, strU.end(),
- oldU.begin(), oldU.end());
- if (itFound == strU.end() && pos == 0)
- return str; //optimize "oldTerm not found": return ref-counted copy
-
- impl::stringAppend(output, str.begin() + pos, str.begin() + (itFound - strU.begin()));
- if (itFound == strU.end())
- return output;
-
- impl::stringAppend(output, newBegin, newEnd);
- pos = (itFound - strU.begin()) + oldLen;
- }
-}
-
-//expose for unit tests
-int cmpStringNaturalLinuxTest(const char* lhs, size_t lhsLen, const char* rhs, size_t rhsLen);
-inline int cmpStringNaturalLinux(const char* lhs, size_t lhsLen, const char* rhs, size_t rhsLen) { return cmpStringNaturalLinuxTest(lhs, lhsLen, rhs, rhsLen); }
-
//---------------------------------------------------------------------------
//ZEN macro consistency checks:
diff --git a/zenXml/zenxml/cvrt_text.h b/zenXml/zenxml/cvrt_text.h
index c6dab8c2..51b23173 100755
--- a/zenXml/zenxml/cvrt_text.h
+++ b/zenXml/zenxml/cvrt_text.h
@@ -22,9 +22,9 @@ It is \b not required to call these functions directly. They are implicitly used
zen::XmlElement::setValue(), zen::XmlElement::getAttribute() and zen::XmlElement::setAttribute().
\n\n
Conversions for the following user types are supported by default:
- - strings - std::string, std::wstring, char*, wchar_t*, char, wchar_t, ect..., all STL-compatible-string-classes
- - numbers - int, double, float, bool, long, ect..., all built-in numbers
- - STL containers - std::map, std::set, std::vector, std::list, ect..., all STL-compatible-containers
+ - strings - std::string, std::wstring, char*, wchar_t*, char, wchar_t, etc..., all STL-compatible-string-classes
+ - numbers - int, double, float, bool, long, etc..., all built-in numbers
+ - STL containers - std::map, std::set, std::vector, std::list, etc..., all STL-compatible-containers
- std::pair
You can add support for additional types via template specialization. \n\n
diff --git a/zenXml/zenxml/parser.h b/zenXml/zenxml/parser.h
index a3975297..a90d163a 100755
--- a/zenXml/zenxml/parser.h
+++ b/zenXml/zenxml/parser.h
@@ -11,7 +11,6 @@
#include <cstddef> //ptrdiff_t; req. on Linux
#include <zen/string_tools.h>
#include "dom.h"
-#include "error.h"
namespace zen
@@ -28,12 +27,13 @@ namespace zen
\param indent Indentation, default: four space characters
\return Output byte stream
*/
-std::string serialize(const XmlDoc& doc,
- const std::string& lineBreak = "\r\n",
- const std::string& indent = " "); //noexcept
+std::string serializeXml(const XmlDoc& doc,
+ const std::string& lineBreak = "\r\n",
+ const std::string& indent = " "); //noexcept
+
///Exception thrown due to an XML parsing error
-struct XmlParsingError : public XmlError
+struct XmlParsingError
{
XmlParsingError(size_t rowNo, size_t colNo) : row(rowNo), col(colNo) {}
///Input file row where the parsing error occured (zero-based)
@@ -42,14 +42,13 @@ struct XmlParsingError : public XmlError
const size_t col; //
};
-
///Load XML document from a byte stream
/**
\param stream Input byte stream
\returns Output XML document
\throw XmlParsingError
*/
-XmlDoc parse(const std::string& stream); //throw XmlParsingError
+XmlDoc parseXml(const std::string& stream); //throw XmlParsingError
@@ -71,9 +70,9 @@ XmlDoc parse(const std::string& stream); //throw XmlParsingError
//---------------------------- implementation ----------------------------
-//see: http://www.w3.org/TR/xml/
+//see: https://www.w3.org/TR/xml/
-namespace impl
+namespace xml_impl
{
template <class Predicate> inline
std::string normalize(const std::string& str, Predicate pred) //pred: unary function taking a char, return true if value shall be encoded as hex
@@ -91,7 +90,7 @@ std::string normalize(const std::string& str, Predicate pred) //pred: unary func
{
if (c == '\'')
output += "&apos;";
- else if (c == '\"')
+ else if (c == '"')
output += "&quot;";
else
{
@@ -111,7 +110,7 @@ std::string normalize(const std::string& str, Predicate pred) //pred: unary func
inline
std::string normalizeName(const std::string& str)
{
- const std::string nameFmt = normalize(str, [](char c) { return isWhiteSpace(c) || c == '=' || c == '/' || c == '\'' || c == '\"'; });
+ const std::string nameFmt = normalize(str, [](char c) { return isWhiteSpace(c) || c == '=' || c == '/' || c == '\'' || c == '"'; });
assert(!nameFmt.empty());
return nameFmt;
}
@@ -125,7 +124,7 @@ std::string normalizeElementValue(const std::string& str)
inline
std::string normalizeAttribValue(const std::string& str)
{
- return normalize(str, [](char c) { return static_cast<unsigned char>(c) < 32 || c == '\'' || c == '\"'; });
+ return normalize(str, [](char c) { return static_cast<unsigned char>(c) < 32 || c == '\'' || c == '"'; });
}
@@ -163,10 +162,12 @@ std::string denormalize(const std::string& str)
else if (checkEntity(it, str.end(), "&apos;"))
output += '\'';
else if (checkEntity(it, str.end(), "&quot;"))
- output += '\"';
+ output += '"';
else if (str.end() - it >= 6 &&
it[1] == '#' &&
it[2] == 'x' &&
+ isHexDigit(it[3]) &&
+ isHexDigit(it[4]) &&
it[5] == ';')
{
output += unhexify(it[3], it[4]);
@@ -203,7 +204,7 @@ void serialize(const XmlElement& element, std::string& stream,
auto attr = element.getAttributes();
for (auto it = attr.first; it != attr.second; ++it)
- stream += ' ' + normalizeName(it->name) + "=\"" + normalizeAttribValue(it->value) + '\"';
+ stream += ' ' + normalizeName(it->name) + "=\"" + normalizeAttribValue(it->value) + '"';
auto iterPair = element.getChildren();
if (iterPair.first != iterPair.second) //structured element
@@ -229,34 +230,30 @@ void serialize(const XmlElement& element, std::string& stream,
stream += "/>" + lineBreak;
}
}
+}
+}
-std::string serialize(const XmlDoc& doc,
- const std::string& lineBreak,
- const std::string& indent)
+inline
+std::string serializeXml(const XmlDoc& doc,
+ const std::string& lineBreak,
+ const std::string& indent)
{
std::string version = doc.getVersionAs<std::string>();
if (!version.empty())
- version = " version=\"" + normalizeAttribValue(version) + '\"';
+ version = " version=\"" + xml_impl::normalizeAttribValue(version) + '"';
std::string encoding = doc.getEncodingAs<std::string>();
if (!encoding.empty())
- encoding = " encoding=\"" + normalizeAttribValue(encoding) + '\"';
+ encoding = " encoding=\"" + xml_impl::normalizeAttribValue(encoding) + '"';
std::string standalone = doc.getStandaloneAs<std::string>();
if (!standalone.empty())
- standalone = " standalone=\"" + normalizeAttribValue(standalone) + '\"';
+ standalone = " standalone=\"" + xml_impl::normalizeAttribValue(standalone) + '"';
std::string output = "<?xml" + version + encoding + standalone + "?>" + lineBreak;
- serialize(doc.root(), output, lineBreak, indent, 0);
+ xml_impl::serialize(doc.root(), output, lineBreak, indent, 0);
return output;
}
-}
-}
-
-inline
-std::string serialize(const XmlDoc& doc,
- const std::string& lineBreak,
- const std::string& indent) { return impl::serialize(doc, lineBreak, indent); }
/*
Grammar for XML parser
@@ -282,7 +279,7 @@ pm-expression:
element-list-expression
*/
-namespace impl
+namespace xml_impl
{
struct Token
{
@@ -310,53 +307,53 @@ struct Token
class Scanner
{
public:
- Scanner(const std::string& stream) : stream_(stream), pos(stream_.begin())
+ Scanner(const std::string& stream) : stream_(stream), pos_(stream_.begin())
{
if (zen::startsWith(stream_, BYTE_ORDER_MARK_UTF8))
- pos += strLength(BYTE_ORDER_MARK_UTF8);
+ pos_ += strLength(BYTE_ORDER_MARK_UTF8);
}
- Token nextToken() //throw XmlParsingError
+ Token getNextToken() //throw XmlParsingError
{
//skip whitespace
- pos = std::find_if(pos, stream_.end(), [](char c) { return !zen::isWhiteSpace(c); });
+ pos_ = std::find_if(pos_, stream_.end(), std::not_fn(isWhiteSpace<char>));
- if (pos == stream_.end())
+ if (pos_ == stream_.end())
return Token::TK_END;
//skip XML comments
- if (startsWith(xmlCommentBegin))
+ if (startsWith(xmlCommentBegin_))
{
- auto it = std::search(pos + xmlCommentBegin.size(), stream_.end(), xmlCommentEnd.begin(), xmlCommentEnd.end());
+ auto it = std::search(pos_ + xmlCommentBegin_.size(), stream_.end(), xmlCommentEnd_.begin(), xmlCommentEnd_.end());
if (it != stream_.end())
{
- pos = it + xmlCommentEnd.size();
- return nextToken();
+ pos_ = it + xmlCommentEnd_.size();
+ return getNextToken(); //throw XmlParsingError
}
}
- for (auto it = tokens.begin(); it != tokens.end(); ++it)
+ for (auto it = tokens_.begin(); it != tokens_.end(); ++it)
if (startsWith(it->first))
{
- pos += it->first.size();
+ pos_ += it->first.size();
return it->second;
}
- auto nameEnd = std::find_if(pos, stream_.end(), [](char c)
+ const auto itNameEnd = std::find_if(pos_, stream_.end(), [](char c)
{
return c == '<' ||
c == '>' ||
c == '=' ||
c == '/' ||
c == '\'' ||
- c == '\"' ||
- zen::isWhiteSpace(c);
+ c == '"' ||
+ isWhiteSpace(c);
});
- if (nameEnd != pos)
+ if (itNameEnd != pos_)
{
- std::string name(&*pos, nameEnd - pos);
- pos = nameEnd;
+ std::string name(pos_, itNameEnd);
+ pos_ = itNameEnd;
return denormalize(name);
}
@@ -366,34 +363,34 @@ public:
std::string extractElementValue()
{
- auto it = std::find_if(pos, stream_.end(), [](char c)
+ auto it = std::find_if(pos_, stream_.end(), [](char c)
{
return c == '<' ||
c == '>';
});
- std::string output(pos, it);
- pos = it;
+ std::string output(pos_, it);
+ pos_ = it;
return denormalize(output);
}
std::string extractAttributeValue()
{
- auto it = std::find_if(pos, stream_.end(), [](char c)
+ auto it = std::find_if(pos_, stream_.end(), [](char c)
{
return c == '<' ||
c == '>' ||
c == '\'' ||
- c == '\"';
+ c == '"';
});
- std::string output(pos, it);
- pos = it;
+ std::string output(pos_, it);
+ pos_ = it;
return denormalize(output);
}
size_t posRow() const //current row beginning with 0
{
- const size_t crSum = std::count(stream_.begin(), pos, '\r'); //carriage returns
- const size_t nlSum = std::count(stream_.begin(), pos, '\n'); //new lines
+ const size_t crSum = std::count(stream_.begin(), pos_, '\r'); //carriage returns
+ const size_t nlSum = std::count(stream_.begin(), pos_, '\n'); //new lines
assert(crSum == 0 || nlSum == 0 || crSum == nlSum);
return std::max(crSum, nlSum); //be compatible with Linux/Mac/Win
}
@@ -401,13 +398,13 @@ public:
size_t posCol() const //current col beginning with 0
{
//seek beginning of line
- for (auto it = pos; it != stream_.begin(); )
+ for (auto it = pos_; it != stream_.begin(); )
{
--it;
if (*it == '\r' || *it == '\n')
- return pos - it - 1;
+ return pos_ - it - 1;
}
- return pos - stream_.begin();
+ return pos_ - stream_.begin();
}
private:
@@ -416,13 +413,11 @@ private:
bool startsWith(const std::string& prefix) const
{
- if (stream_.end() - pos < static_cast<ptrdiff_t>(prefix.size()))
- return false;
- return std::equal(prefix.begin(), prefix.end(), pos);
+ return zen::startsWith(StringRef<const char>(pos_, stream_.end()), prefix);
}
using TokenList = std::vector<std::pair<std::string, Token::Type>>;
- const TokenList tokens
+ const TokenList tokens_
{
{ "<?xml", Token::TK_DECL_BEGIN },
{ "?>", Token::TK_DECL_END },
@@ -435,11 +430,11 @@ private:
{ "\'", Token::TK_QUOTE },
};
- const std::string xmlCommentBegin = "<!--";
- const std::string xmlCommentEnd = "-->";
+ const std::string xmlCommentBegin_ = "<!--";
+ const std::string xmlCommentEnd_ = "-->";
const std::string stream_;
- std::string::const_iterator pos;
+ std::string::const_iterator pos_;
};
@@ -448,7 +443,7 @@ class XmlParser
public:
XmlParser(const std::string& stream) :
scn_(stream),
- tk_(scn_.nextToken()) {}
+ tk_(scn_.getNextToken()) {} //throw XmlParsingError
XmlDoc parse() //throw XmlParsingError
{
@@ -457,19 +452,19 @@ public:
//declaration (optional)
if (token().type == Token::TK_DECL_BEGIN)
{
- nextToken();
+ nextToken(); //throw XmlParsingError
while (token().type == Token::TK_NAME)
{
std::string attribName = token().name;
- nextToken();
+ nextToken(); //throw XmlParsingError
- consumeToken(Token::TK_EQUAL);
- expectToken(Token::TK_QUOTE);
+ consumeToken(Token::TK_EQUAL); //throw XmlParsingError
+ expectToken (Token::TK_QUOTE); //
std::string attribValue = scn_.extractAttributeValue();
- nextToken();
+ nextToken(); //throw XmlParsingError
- consumeToken(Token::TK_QUOTE);
+ consumeToken(Token::TK_QUOTE); //throw XmlParsingError
if (attribName == "version")
doc.setVersion(attribValue);
@@ -478,7 +473,7 @@ public:
else if (attribName == "standalone")
doc.setStandalone(attribValue);
}
- consumeToken(Token::TK_DECL_END);
+ consumeToken(Token::TK_DECL_END); //throw XmlParsingError
}
XmlElement dummy;
@@ -488,7 +483,7 @@ public:
if (itPair.first != itPair.second)
doc.root().swapSubtree(*itPair.first);
- expectToken(Token::TK_END);
+ expectToken(Token::TK_END); //throw XmlParsingError
return doc;
}
@@ -500,11 +495,11 @@ private:
{
while (token().type == Token::TK_LESS)
{
- nextToken();
+ nextToken(); //throw XmlParsingError
- expectToken(Token::TK_NAME);
+ expectToken(Token::TK_NAME); //throw XmlParsingError
std::string elementName = token().name;
- nextToken();
+ nextToken(); //throw XmlParsingError
XmlElement& newElement = parent.addChild(elementName);
@@ -512,28 +507,28 @@ private:
if (token().type == Token::TK_SLASH_GREATER) //empty element
{
- nextToken();
+ nextToken(); //throw XmlParsingError
continue;
}
- expectToken(Token::TK_GREATER);
+ expectToken(Token::TK_GREATER); //throw XmlParsingError
std::string elementValue = scn_.extractElementValue();
- nextToken();
+ nextToken(); //throw XmlParsingError
//no support for mixed-mode content
- if (token().type == Token::TK_LESS) //structured element
+ if (token().type == Token::TK_LESS) //structure-element
parseChildElements(newElement);
- else //value element
+ else //value-element
newElement.setValue(elementValue);
- consumeToken(Token::TK_LESS_SLASH);
+ consumeToken(Token::TK_LESS_SLASH); //throw XmlParsingError
- if (token().type != Token::TK_NAME ||
- elementName != token().name)
+ expectToken(Token::TK_NAME); //throw XmlParsingError
+ if (token().name != elementName)
throw XmlParsingError(scn_.posRow(), scn_.posCol());
- nextToken();
+ nextToken(); //throw XmlParsingError
- consumeToken(Token::TK_GREATER);
+ consumeToken(Token::TK_GREATER); //throw XmlParsingError
}
}
@@ -542,26 +537,21 @@ private:
while (token().type == Token::TK_NAME)
{
std::string attribName = token().name;
- nextToken();
+ nextToken(); //throw XmlParsingError
- consumeToken(Token::TK_EQUAL);
- expectToken(Token::TK_QUOTE);
+ consumeToken(Token::TK_EQUAL); //throw XmlParsingError
+ expectToken (Token::TK_QUOTE); //
std::string attribValue = scn_.extractAttributeValue();
- nextToken();
+ nextToken(); //throw XmlParsingError
- consumeToken(Token::TK_QUOTE);
+ consumeToken(Token::TK_QUOTE); //throw XmlParsingError
element.setAttribute(attribName, attribValue);
}
}
const Token& token() const { return tk_; }
- void nextToken() { tk_ = scn_.nextToken(); }
- void consumeToken(Token::Type t) //throw XmlParsingError
- {
- expectToken(t); //throw XmlParsingError
- nextToken();
- }
+ void nextToken() { tk_ = scn_.getNextToken(); } //throw XmlParsingError
void expectToken(Token::Type t) //throw XmlParsingError
{
@@ -569,15 +559,21 @@ private:
throw XmlParsingError(scn_.posRow(), scn_.posCol());
}
+ void consumeToken(Token::Type t) //throw XmlParsingError
+ {
+ expectToken(t); //throw XmlParsingError
+ nextToken(); //
+ }
+
Scanner scn_;
Token tk_;
};
}
inline
-XmlDoc parse(const std::string& stream) //throw XmlParsingError
+XmlDoc parseXml(const std::string& stream) //throw XmlParsingError
{
- return impl::XmlParser(stream).parse(); //throw XmlParsingError
+ return xml_impl::XmlParser(stream).parse(); //throw XmlParsingError
}
}
diff --git a/zenXml/zenxml/xml.h b/zenXml/zenxml/xml.h
index 9510645a..5eddc462 100755
--- a/zenXml/zenxml/xml.h
+++ b/zenXml/zenxml/xml.h
@@ -1,4 +1,4 @@
-// *****************************************************************************
+// *****************************************************************************
// * 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 *
@@ -7,10 +7,444 @@
#ifndef XML_H_349578228034572457454554
#define XML_H_349578228034572457454554
-#include "bind.h"
+#include <set>
+#include <zen/file_io.h>
+#include <zen/file_access.h>
+#include "cvrt_struc.h"
+#include "parser.h"
/// The zen::Xml namespace
-namespace zen {}
+namespace zen
+{
+/**
+\file
+\brief Save and load byte streams from files
+*/
+
+///Load XML document from a file
+/**
+Load and parse XML byte stream. Quick-exit if (potentially large) input file is not an XML.
+
+\param filePath Input file path
+\returns The loaded XML document
+\throw FileError
+*/
+namespace
+{
+XmlDoc loadXml(const Zstring& filePath) //throw FileError
+{
+ FileInput fileIn(filePath, nullptr /*notifyUnbufferedIO*/); //throw FileError, ErrorFileLocked
+ const size_t blockSize = fileIn.getBlockSize();
+ const std::string xmlPrefix = "<?xml version=";
+ bool xmlPrefixChecked = false;
+
+ std::string buffer;
+ for (;;)
+ {
+ buffer.resize(buffer.size() + blockSize);
+ const size_t bytesRead = fileIn.read(&*(buffer.end() - blockSize), blockSize); //throw FileError, ErrorFileLocked, (X); return "bytesToRead" bytes unless end of stream!
+ buffer.resize(buffer.size() - blockSize + bytesRead); //caveat: unsigned arithmetics
+
+ //quick test whether input is an XML: avoid loading large binary files up front!
+ if (!xmlPrefixChecked && buffer.size() >= xmlPrefix.size() + strLength(BYTE_ORDER_MARK_UTF8))
+ {
+ xmlPrefixChecked = true;
+ if (!startsWith(buffer, xmlPrefix) &&
+ !startsWith(buffer, BYTE_ORDER_MARK_UTF8 + xmlPrefix)) //allow BOM!
+ throw FileError(replaceCpy(_("File %x does not contain a valid configuration."), L"%x", fmtPath(filePath)));
+ }
+
+ if (bytesRead < blockSize) //end of file
+ break;
+ }
+
+ try
+ {
+ return parseXml(buffer); //throw XmlParsingError
+ }
+ catch (const XmlParsingError& e)
+ {
+ throw FileError(
+ replaceCpy(replaceCpy(replaceCpy(_("Error parsing file %x, row %y, column %z."),
+ L"%x", fmtPath(filePath)),
+ L"%y", numberTo<std::wstring>(e.row + 1)),
+ L"%z", numberTo<std::wstring>(e.col + 1)));
+ }
+}
+}
+
+
+///Save XML document to a file
+/**
+Serialize XML to byte stream and save to file.
+
+\param doc The XML document to save
+\param filePath Output file path
+\throw FileError
+*/
+inline
+void saveXml(const XmlDoc& doc, const Zstring& filePath) //throw FileError
+{
+ const std::string stream = serializeXml(doc); //noexcept
+
+ try //only update XML file if there are changes
+ {
+ if (getFileSize(filePath) == stream.size()) //throw FileError
+ if (loadBinContainer<std::string>(filePath, nullptr /*notifyUnbufferedIO*/) == stream) //throw FileError
+ return;
+ }
+ catch (FileError&) {}
+
+ saveBinContainer(filePath, stream, nullptr /*notifyUnbufferedIO*/); //throw FileError
+}
+
+
+///Proxy class to conveniently convert user data into XML structure
+class XmlOut
+{
+public:
+ ///Construct an output proxy for an XML document
+ /**
+ \code
+ zen::XmlDoc doc;
+
+ zen::XmlOut out(doc);
+ out["elem1"]( 1); //
+ out["elem2"]( 2); //write data into XML elements
+ out["elem3"](-3); //
+
+ saveXml(doc, "out.xml"); //throw FileError
+ \endcode
+ Output:
+ \verbatim
+ <?xml version="1.0" encoding="utf-8"?>
+ <Root>
+ <elem1>1</elem1>
+ <elem2>2</elem2>
+ <elem3>-3</elem3>
+ </Root>
+ \endverbatim
+ */
+ XmlOut(XmlDoc& doc) : ref_(&doc.root()) {}
+ ///Construct an output proxy for a single XML element
+ /**
+ \sa XmlOut(XmlDoc& doc)
+ */
+ XmlOut(XmlElement& element) : ref_(&element) {}
+
+ ///Retrieve a handle to an XML child element for writing
+ /**
+ The child element will be created if it is not yet existing.
+ \tparam String Arbitrary string-like type: e.g. std::string, wchar_t*, char[], wchar_t, wxString, MyStringClass, ...
+ \param name The name of the child element
+ */
+ template <class String>
+ XmlOut operator[](const String& name) const
+ {
+ const std::string utf8name = utfTo<std::string>(name);
+ XmlElement* child = ref_->getChild(utf8name);
+ return child ? *child : ref_->addChild(utf8name);
+ }
+
+ ///Write user data to the underlying XML element
+ /**
+ This conversion requires a specialization of zen::writeText() or zen::writeStruc() for type T.
+ \tparam T User type that is converted into an XML element value.
+ */
+ template <class T>
+ void operator()(const T& value) { writeStruc(value, *ref_); }
+
+ ///Write user data to an XML attribute
+ /**
+ This conversion requires a specialization of zen::writeText() for type T.
+ \code
+ zen::XmlDoc doc;
+
+ zen::XmlOut out(doc);
+ out["elem"].attribute("attr1", 1); //
+ out["elem"].attribute("attr2", 2); //write data into XML attributes
+ out["elem"].attribute("attr3", -3); //
+
+ saveXml(doc, "out.xml"); //throw FileError
+ \endcode
+ Output:
+ \verbatim
+ <?xml version="1.0" encoding="utf-8"?>
+ <Root>
+ <elem attr1="1" attr2="2" attr3="-3"/>
+ </Root>
+ \endverbatim
+
+ \tparam String Arbitrary string-like type: e.g. std::string, wchar_t*, char[], wchar_t, wxString, MyStringClass, ...
+ \tparam T String-convertible user data type: e.g. any string-like type, all built-in arithmetic numbers
+ \sa XmlElement::setAttribute()
+ */
+ template <class String, class T>
+ void attribute(const String& name, const T& value) { ref_->setAttribute(name, value); }
+
+ ///Return a reference to the underlying Xml element
+ XmlElement& ref() { return *ref_; }
+
+private:
+ XmlElement* ref_; //always bound!
+};
+
+
+///Proxy class to conveniently convert XML structure to user data
+class XmlIn
+{
+ class ErrorLog;
+
+public:
+ ///Construct an input proxy for an XML document
+ /**
+ \code
+ zen::XmlDoc doc;
+ ... //load document
+ zen::XmlIn in(doc);
+ in["elem1"](value1); //
+ in["elem2"](value2); //read data from XML elements into variables "value1", "value2", "value3"
+ in["elem3"](value3); //
+ \endcode
+ */
+ XmlIn(const XmlDoc& doc) { refList_.push_back(&doc.root()); }
+ ///Construct an input proxy for a single XML element, may be nullptr
+ /**
+ \sa XmlIn(const XmlDoc& doc)
+ */
+ XmlIn(const XmlElement* element) { refList_.push_back(element); }
+ ///Construct an input proxy for a single XML element
+ /**
+ \sa XmlIn(const XmlDoc& doc)
+ */
+ XmlIn(const XmlElement& element) { refList_.push_back(&element); }
+
+ ///Retrieve a handle to an XML child element for reading
+ /**
+ It is \b not an error if the child element does not exist, but only later if a conversion to user data is attempted.
+ \tparam String Arbitrary string-like type: e.g. std::string, wchar_t*, char[], wchar_t, wxString, MyStringClass, ...
+ \param name The name of the child element
+ */
+ template <class String>
+ XmlIn operator[](const String& name) const
+ {
+ std::vector<const XmlElement*> childList;
+
+ if (refIndex_ < refList_.size())
+ {
+ auto iterPair = refList_[refIndex_]->getChildren(name);
+ std::for_each(iterPair.first, iterPair.second,
+ [&](const XmlElement& child) { childList.push_back(&child); });
+ }
+
+ return XmlIn(childList, childList.empty() ? getChildNameFormatted(name) : std::string(), log_);
+ }
+
+ ///Refer to next sibling element with the same name
+ /**
+ <b>Example:</b> Loop over all XML child elements named "Item"
+ \verbatim
+ <?xml version="1.0" encoding="utf-8"?>
+ <Root>
+ <Item>1</Item>
+ <Item>3</Item>
+ <Item>5</Item>
+ </Root>
+ \endverbatim
+
+ \code
+ zen::XmlIn in(doc);
+ ...
+ for (zen::XmlIn child = in["Item"]; child; child.next())
+ {
+ ...
+ }
+ \endcode
+ */
+ void next() { ++refIndex_; }
+
+ ///Read user data from the underlying XML element
+ /**
+ This conversion requires a specialization of zen::readText() or zen::readStruc() for type T.
+ \tparam T User type that receives the data
+ \return "true" if data was read successfully
+ */
+ template <class T>
+ bool operator()(T& value) const
+ {
+ if (refIndex_ < refList_.size())
+ {
+ bool success = readStruc(*refList_[refIndex_], value);
+ if (!success)
+ log_->notifyConversionError(getNameFormatted());
+ return success;
+ }
+ else
+ {
+ log_->notifyMissingElement(getNameFormatted());
+ return false;
+ }
+ }
+
+ ///Read user data from an XML attribute
+ /**
+ This conversion requires a specialization of zen::readText() for type T.
+
+ \code
+ zen::XmlDoc doc;
+ ... //load document
+ zen::XmlIn in(doc);
+ in["elem"].attribute("attr1", value1); //
+ in["elem"].attribute("attr2", value2); //read data from XML attributes into variables "value1", "value2", "value3"
+ in["elem"].attribute("attr3", value3); //
+ \endcode
+
+ \tparam String Arbitrary string-like type: e.g. std::string, wchar_t*, char[], wchar_t, wxString, MyStringClass, ...
+ \tparam T String-convertible user data type: e.g. any string-like type, all built-in arithmetic numbers
+ \returns "true" if the attribute was found and the conversion to the output value was successful.
+ \sa XmlElement::getAttribute()
+ */
+ template <class String, class T>
+ bool attribute(const String& name, T& value) const
+ {
+ if (refIndex_ < refList_.size())
+ {
+ bool success = refList_[refIndex_]->getAttribute(name, value);
+ if (!success)
+ log_->notifyMissingAttribute(getNameFormatted(), utfTo<std::string>(name));
+ return success;
+ }
+ else
+ {
+ log_->notifyMissingElement(getNameFormatted());
+ return false;
+ }
+ }
+
+ ///Return a pointer to the underlying Xml element, may be nullptr
+ const XmlElement* get() const { return refIndex_ < refList_.size() ? refList_[refIndex_] : nullptr; }
+
+ ///Test whether the underlying XML element exists
+ /**
+ \code
+ XmlIn in(doc);
+ XmlIn child = in["elem1"];
+ if (child)
+ ...
+ \endcode
+ Use member pointer as implicit conversion to bool (C++ Templates - Vandevoorde/Josuttis; chapter 20)
+ */
+ explicit operator bool() const { return get() != nullptr; }
+
+ ///Notifies errors while mapping the XML to user data
+ /**
+ Error logging is shared by each hiearchy of XmlIn proxy instances that are created from each other. Consequently it doesn't matter which instance you query for errors:
+ \code
+ XmlIn in(doc);
+ XmlIn inItem = in["item1"];
+
+ int value = 0;
+ inItem(value); //let's assume this conversion failed
+
+ assert(in.haveErrors() == inItem.haveErrors());
+ assert(in.getErrorsAs<std::string>() == inItem.getErrorsAs<std::string>());
+ \endcode
+
+ Note that error logging is \b NOT global, but owned by all instances of a hierarchy of XmlIn proxies.
+ Therefore it's safe to use unrelated XmlIn proxies in multiple threads.
+ \n\n
+ However be aware that the chain of connected proxy instances will be broken once you call XmlIn::get() to retrieve the underlying pointer.
+ Errors that occur when working with this pointer are not logged by the original set of related instances.
+ */
+ bool haveErrors() const { return !log_->elementList().empty(); }
+
+ ///Get a list of XML element and attribute names which failed to convert to user data.
+ /**
+ \tparam String Arbitrary string class: e.g. std::string, std::wstring, wxString, MyStringClass, ...
+ \returns A list of XML element and attribute names, empty list if no errors occured.
+ */
+ template <class String>
+ std::vector<String> getErrorsAs() const
+ {
+ std::vector<String> output;
+ const auto& elements = log_->elementList();
+ std::transform(elements.begin(), elements.end(), std::back_inserter(output), [](const std::string& str) { return utfTo<String>(str); });
+ return output;
+ }
+
+private:
+ XmlIn(const std::vector<const XmlElement*>& siblingList, const std::string& elementNameFmt, const std::shared_ptr<ErrorLog>& sharedlog) :
+ refList_(siblingList), formattedName_(elementNameFmt), log_(sharedlog)
+ { assert((!siblingList.empty() && elementNameFmt.empty()) || (siblingList.empty() && !elementNameFmt.empty())); }
+
+ static std::string getNameFormatted(const XmlElement& elem) //"<Root> <Level1> <Level2>"
+ {
+ return (elem.parent() ? getNameFormatted(*elem.parent()) + " " : std::string()) + "<" + elem.getNameAs<std::string>() + ">";
+ }
+
+ std::string getNameFormatted() const
+ {
+ if (refIndex_ < refList_.size())
+ {
+ assert(formattedName_.empty());
+ return getNameFormatted(*refList_[refIndex_]);
+ }
+ else
+ return formattedName_;
+ }
+
+ std::string getChildNameFormatted(const std::string& childName) const
+ {
+ std::string parentName = getNameFormatted();
+ return (parentName.empty() ? std::string() : (parentName + " ")) + "<" + childName + ">";
+ }
+
+ class ErrorLog
+ {
+ public:
+ void notifyConversionError (const std::string& displayName) { insert(displayName); }
+ void notifyMissingElement (const std::string& displayName) { insert(displayName); }
+ void notifyMissingAttribute(const std::string& displayName, const std::string& attribName) { insert(displayName + " @" + attribName); }
+
+ const std::vector<std::string>& elementList() const { return failedElements; }
+
+ private:
+ void insert(const std::string& newVal)
+ {
+ if (usedElements.insert(newVal).second)
+ failedElements.push_back(newVal);
+ }
+
+ std::vector<std::string> failedElements; //unique list of failed elements
+ std::set<std::string> usedElements;
+ };
+
+ std::vector<const XmlElement*> refList_; //all sibling elements with same name (all pointers bound!)
+ size_t refIndex_ = 0; //this sibling's index in refList_
+ std::string formattedName_; //contains full and formatted element name if (and only if) refList_ is empty
+ std::shared_ptr<ErrorLog> log_{ std::make_shared<ErrorLog>() }; //always bound
+};
+
+
+///Check XML input proxy for errors and map to FileError exception
+/**
+\param xmlInput XML input proxy
+\param filePath Input file path
+\throw FileError
+*/
+inline
+void checkXmlMappingErrors(const XmlIn& xmlInput, const Zstring& filePath) //throw FileError
+{
+ if (xmlInput.haveErrors())
+ {
+ 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;
+
+ 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);
+ }
+}
+}
#endif //XML_H_349578228034572457454554
bgstack15