From f43972d665c95b2148636c86a5b648e719901101 Mon Sep 17 00:00:00 2001 From: B Stack Date: Sun, 12 May 2019 16:34:13 -0400 Subject: 10.12 --- Changelog.txt | 17 +- FreeFileSync/Build/FreeFileSync.desktop | 0 FreeFileSync/Build/Languages/german.lng | 354 +-- FreeFileSync/Build/Misc/Icons.zip | Bin 0 -> 286094 bytes FreeFileSync/Build/RealTimeSync.desktop | 0 FreeFileSync/Build/Resources.zip | Bin 286899 -> 0 bytes FreeFileSync/Source/Makefile | 20 +- FreeFileSync/Source/RealTimeSync/Makefile | 4 +- FreeFileSync/Source/RealTimeSync/application.cpp | 4 +- FreeFileSync/Source/RealTimeSync/config.cpp | 193 ++ FreeFileSync/Source/RealTimeSync/config.h | 34 + .../Source/RealTimeSync/folder_selector2.cpp | 1 - FreeFileSync/Source/RealTimeSync/gui_generated.cpp | 31 +- FreeFileSync/Source/RealTimeSync/gui_generated.h | 7 +- FreeFileSync/Source/RealTimeSync/main_dlg.cpp | 17 +- FreeFileSync/Source/RealTimeSync/tray_menu.cpp | 2 +- FreeFileSync/Source/RealTimeSync/tray_menu.h | 2 +- FreeFileSync/Source/RealTimeSync/xml_proc.cpp | 180 -- FreeFileSync/Source/RealTimeSync/xml_proc.h | 33 - FreeFileSync/Source/afs/abstract.cpp | 448 +++ FreeFileSync/Source/afs/abstract.h | 537 ++++ FreeFileSync/Source/afs/abstract_impl.h | 274 ++ FreeFileSync/Source/afs/concrete.cpp | 48 + FreeFileSync/Source/afs/concrete.h | 25 + FreeFileSync/Source/afs/ftp.cpp | 2246 ++++++++++++++ FreeFileSync/Source/afs/ftp.h | 45 + FreeFileSync/Source/afs/ftp_common.h | 68 + FreeFileSync/Source/afs/gdrive.cpp | 3122 +++++++++++++++++++ FreeFileSync/Source/afs/gdrive.h | 39 + FreeFileSync/Source/afs/init_curl_libssh2.cpp | 165 ++ FreeFileSync/Source/afs/init_curl_libssh2.h | 51 + FreeFileSync/Source/afs/libcurl/curl_wrap.h | 130 + FreeFileSync/Source/afs/libssh2/init_libssh2.cpp | 44 + FreeFileSync/Source/afs/libssh2/init_libssh2.h | 16 + FreeFileSync/Source/afs/libssh2/init_open_ssl.cpp | 45 + FreeFileSync/Source/afs/libssh2/init_open_ssl.h | 16 + FreeFileSync/Source/afs/native.cpp | 685 +++++ FreeFileSync/Source/afs/native.h | 23 + FreeFileSync/Source/afs/sftp.cpp | 2082 +++++++++++++ FreeFileSync/Source/afs/sftp.h | 57 + FreeFileSync/Source/base/algorithm.cpp | 6 +- FreeFileSync/Source/base/algorithm.h | 2 +- FreeFileSync/Source/base/application.cpp | 8 +- FreeFileSync/Source/base/binary.h | 2 +- FreeFileSync/Source/base/cmp_filetime.h | 2 +- FreeFileSync/Source/base/comparison.cpp | 14 +- FreeFileSync/Source/base/comparison.h | 2 +- FreeFileSync/Source/base/config.cpp | 2192 ++++++++++++++ FreeFileSync/Source/base/config.h | 282 ++ FreeFileSync/Source/base/db_file.cpp | 2 +- FreeFileSync/Source/base/dir_exist_async.h | 2 +- FreeFileSync/Source/base/dir_lock.cpp | 3 +- FreeFileSync/Source/base/ffs_paths.cpp | 19 +- FreeFileSync/Source/base/file_hierarchy.h | 4 +- FreeFileSync/Source/base/generate_logfile.cpp | 20 +- FreeFileSync/Source/base/generate_logfile.h | 3 +- FreeFileSync/Source/base/icon_buffer.cpp | 1 + FreeFileSync/Source/base/icon_buffer.h | 2 +- FreeFileSync/Source/base/icon_loader.cpp | 5 +- FreeFileSync/Source/base/localization.cpp | 3 +- FreeFileSync/Source/base/parallel_scan.cpp | 6 +- FreeFileSync/Source/base/parse_lng.h | 2 +- FreeFileSync/Source/base/parse_plural.h | 2 +- FreeFileSync/Source/base/process_callback.h | 2 +- FreeFileSync/Source/base/process_xml.cpp | 2199 -------------- FreeFileSync/Source/base/process_xml.h | 283 -- FreeFileSync/Source/base/resolve_path.cpp | 6 + FreeFileSync/Source/base/status_handler.h | 7 +- FreeFileSync/Source/base/structures.cpp | 2 +- FreeFileSync/Source/base/structures.h | 2 +- FreeFileSync/Source/base/synchronization.cpp | 6 +- FreeFileSync/Source/base/synchronization.h | 2 +- FreeFileSync/Source/base/versioning.cpp | 8 +- FreeFileSync/Source/base/versioning.h | 2 +- FreeFileSync/Source/fs/abstract.cpp | 448 --- FreeFileSync/Source/fs/abstract.h | 537 ---- FreeFileSync/Source/fs/abstract_impl.h | 274 -- FreeFileSync/Source/fs/concrete.cpp | 48 - FreeFileSync/Source/fs/concrete.h | 25 - FreeFileSync/Source/fs/ftp.cpp | 2247 -------------- FreeFileSync/Source/fs/ftp.h | 45 - FreeFileSync/Source/fs/ftp_common.h | 68 - FreeFileSync/Source/fs/gdrive.cpp | 3124 -------------------- FreeFileSync/Source/fs/gdrive.h | 39 - FreeFileSync/Source/fs/init_curl_libssh2.cpp | 166 -- FreeFileSync/Source/fs/init_curl_libssh2.h | 51 - FreeFileSync/Source/fs/libcurl/curl_wrap.h | 130 - FreeFileSync/Source/fs/libssh2/init_libssh2.cpp | 45 - FreeFileSync/Source/fs/libssh2/init_libssh2.h | 16 - FreeFileSync/Source/fs/libssh2/init_open_ssl.cpp | 45 - FreeFileSync/Source/fs/libssh2/init_open_ssl.h | 16 - FreeFileSync/Source/fs/native.cpp | 685 ----- FreeFileSync/Source/fs/native.h | 23 - FreeFileSync/Source/fs/sftp.cpp | 2082 ------------- FreeFileSync/Source/fs/sftp.h | 57 - FreeFileSync/Source/ui/abstract_folder_picker.h | 2 +- FreeFileSync/Source/ui/batch_config.h | 2 +- FreeFileSync/Source/ui/batch_status_handler.cpp | 12 +- FreeFileSync/Source/ui/batch_status_handler.h | 3 +- FreeFileSync/Source/ui/cfg_grid.cpp | 2 +- FreeFileSync/Source/ui/cfg_grid.h | 2 +- FreeFileSync/Source/ui/folder_selector.cpp | 6 +- FreeFileSync/Source/ui/folder_selector.h | 2 +- FreeFileSync/Source/ui/gui_status_handler.cpp | 26 +- FreeFileSync/Source/ui/main_dlg.cpp | 33 +- FreeFileSync/Source/ui/main_dlg.h | 1 - FreeFileSync/Source/ui/progress_indicator.cpp | 36 +- FreeFileSync/Source/ui/progress_indicator.h | 5 +- FreeFileSync/Source/ui/small_dlgs.cpp | 11 +- FreeFileSync/Source/ui/small_dlgs.h | 3 +- FreeFileSync/Source/ui/sync_cfg.cpp | 2 +- FreeFileSync/Source/ui/taskbar.cpp | 5 +- FreeFileSync/Source/ui/taskbar.h | 2 +- FreeFileSync/Source/version/version.h | 2 +- wx+/graph.cpp | 10 +- wx+/grid.cpp | 7 +- wx+/popup_dlg.h | 2 +- wx+/tooltip.cpp | 2 +- xBRZ/src/xbrz.cpp | 6 +- zen/file_access.cpp | 4 +- zen/file_access.h | 1 + zen/file_traverser.h | 2 +- zen/perf.h | 1 - zen/recycler.h | 2 +- zen/ring_buffer.h | 2 +- zen/scope_guard.h | 10 +- zen/shell_execute.h | 7 +- zen/shutdown.cpp | 4 +- zen/shutdown.h | 2 +- zen/socket.h | 2 +- zen/utf.h | 2 +- zen/zlib_wrap.cpp | 3 +- zen/zstring.cpp | 9 +- zen/zstring.h | 6 +- zenXml/zenxml/dom.h | 2 +- 135 files changed, 13315 insertions(+), 13267 deletions(-) mode change 100644 => 100755 FreeFileSync/Build/FreeFileSync.desktop create mode 100755 FreeFileSync/Build/Misc/Icons.zip mode change 100644 => 100755 FreeFileSync/Build/RealTimeSync.desktop delete mode 100755 FreeFileSync/Build/Resources.zip create mode 100644 FreeFileSync/Source/RealTimeSync/config.cpp create mode 100644 FreeFileSync/Source/RealTimeSync/config.h delete mode 100644 FreeFileSync/Source/RealTimeSync/xml_proc.cpp delete mode 100644 FreeFileSync/Source/RealTimeSync/xml_proc.h create mode 100644 FreeFileSync/Source/afs/abstract.cpp create mode 100644 FreeFileSync/Source/afs/abstract.h create mode 100644 FreeFileSync/Source/afs/abstract_impl.h create mode 100644 FreeFileSync/Source/afs/concrete.cpp create mode 100644 FreeFileSync/Source/afs/concrete.h create mode 100644 FreeFileSync/Source/afs/ftp.cpp create mode 100644 FreeFileSync/Source/afs/ftp.h create mode 100644 FreeFileSync/Source/afs/ftp_common.h create mode 100644 FreeFileSync/Source/afs/gdrive.cpp create mode 100644 FreeFileSync/Source/afs/gdrive.h create mode 100644 FreeFileSync/Source/afs/init_curl_libssh2.cpp create mode 100644 FreeFileSync/Source/afs/init_curl_libssh2.h create mode 100644 FreeFileSync/Source/afs/libcurl/curl_wrap.h create mode 100644 FreeFileSync/Source/afs/libssh2/init_libssh2.cpp create mode 100644 FreeFileSync/Source/afs/libssh2/init_libssh2.h create mode 100644 FreeFileSync/Source/afs/libssh2/init_open_ssl.cpp create mode 100644 FreeFileSync/Source/afs/libssh2/init_open_ssl.h create mode 100644 FreeFileSync/Source/afs/native.cpp create mode 100644 FreeFileSync/Source/afs/native.h create mode 100644 FreeFileSync/Source/afs/sftp.cpp create mode 100644 FreeFileSync/Source/afs/sftp.h create mode 100644 FreeFileSync/Source/base/config.cpp create mode 100644 FreeFileSync/Source/base/config.h delete mode 100644 FreeFileSync/Source/base/process_xml.cpp delete mode 100644 FreeFileSync/Source/base/process_xml.h delete mode 100644 FreeFileSync/Source/fs/abstract.cpp delete mode 100644 FreeFileSync/Source/fs/abstract.h delete mode 100644 FreeFileSync/Source/fs/abstract_impl.h delete mode 100644 FreeFileSync/Source/fs/concrete.cpp delete mode 100644 FreeFileSync/Source/fs/concrete.h delete mode 100644 FreeFileSync/Source/fs/ftp.cpp delete mode 100644 FreeFileSync/Source/fs/ftp.h delete mode 100644 FreeFileSync/Source/fs/ftp_common.h delete mode 100644 FreeFileSync/Source/fs/gdrive.cpp delete mode 100644 FreeFileSync/Source/fs/gdrive.h delete mode 100644 FreeFileSync/Source/fs/init_curl_libssh2.cpp delete mode 100644 FreeFileSync/Source/fs/init_curl_libssh2.h delete mode 100644 FreeFileSync/Source/fs/libcurl/curl_wrap.h delete mode 100644 FreeFileSync/Source/fs/libssh2/init_libssh2.cpp delete mode 100644 FreeFileSync/Source/fs/libssh2/init_libssh2.h delete mode 100644 FreeFileSync/Source/fs/libssh2/init_open_ssl.cpp delete mode 100644 FreeFileSync/Source/fs/libssh2/init_open_ssl.h delete mode 100644 FreeFileSync/Source/fs/native.cpp delete mode 100644 FreeFileSync/Source/fs/native.h delete mode 100644 FreeFileSync/Source/fs/sftp.cpp delete mode 100644 FreeFileSync/Source/fs/sftp.h diff --git a/Changelog.txt b/Changelog.txt index f4a1c172..62f5a49d 100755 --- a/Changelog.txt +++ b/Changelog.txt @@ -1,5 +1,18 @@ -FreeFileSync 10.11 ------------------- +FreeFileSync 10.12 [2019-05-12] +------------------------------- +Show sync start time and date in progress dialog title +Added duration of comparison to log +Show all total times in full HH:MM:SS format +Added sync start time to log file header +Add Windows Defender exclusions to fix CURLE_OPERATION_TIMEDOUT +New RealTimeSync option to hide console window +Support launching through symlink (Windows) +Dropped support for Windows XP, Server 2003, and Vista +Reduced installation size by 25% + + +FreeFileSync 10.11 [2019-04-11] +------------------------------- Last FreeFileSync version supporting Windows XP and Vista Fixed crash on multi-monitor set up Fixed dialogs not showing after opening UAC prompt diff --git a/FreeFileSync/Build/FreeFileSync.desktop b/FreeFileSync/Build/FreeFileSync.desktop old mode 100644 new mode 100755 diff --git a/FreeFileSync/Build/Languages/german.lng b/FreeFileSync/Build/Languages/german.lng index c77d82c4..929a8eb1 100755 --- a/FreeFileSync/Build/Languages/german.lng +++ b/FreeFileSync/Build/Languages/german.lng @@ -7,6 +7,155 @@ n == 1 ? 0 : 1 +Cannot read file %x. +Die Datei %x kann nicht gelesen werden. + + +Unexpected size of data stream. +Expected: %x bytes +Actual: %y bytes + + +Unerwartete Größe des Datenstroms. +Erwartet: %x bytes +Tatsächlich: %y bytes + + +Cannot write file %x. +Die Datei %x kann nicht geschrieben werden. + +Cannot write permissions of %x. +Die Berechtigungen von %x können nicht geschrieben werden. + +Operation not supported between different devices. +Der Vorgang wird zwischen unterschiedlichen Geräten nicht unterstützt. + +Cannot delete file %x. +Die Datei %x kann nicht gelöscht werden. + +Cannot delete symbolic link %x. +Die symbolischen Verknüpfung %x kann nicht gelöscht werden. + +Cannot delete directory %x. +Das Verzeichnis %x kann nicht gelöscht werden. + +Cannot move file %x to %y. +Die Datei %x kann nicht nach %y verschoben werden. + +Cannot copy symbolic link %x to %y. +Die symbolische Verknüpfung %x kann nicht nach %y kopiert werden. + +Error Code %x +Fehlercode %x + +Cannot read directory %x. +Das Verzeichnis %x kann nicht gelesen werden. + +Cannot write modification time of %x. +Die Änderungszeit von %x kann nicht geschrieben werden. + +Cannot read file attributes of %x. +Die Dateiattribute von %x können nicht gelesen werden. + +Cannot create directory %x. +Das Verzeichnis %x kann nicht erstellt werden. + +Cannot determine final path for %x. +Der endgültige Pfad für %x kann nicht ermittelt werden. + +Operation not supported by device. +Der Vorgang wird vom Gerät nicht unterstützt. + +Cannot resolve symbolic link %x. +Die symbolische Verknüpfung %x kann nicht aufgelöst werden. + +Unable to move %x to the recycle bin. +%x kann nicht in den Papierkorb verschoben werden. + +Authentication completed. +Authentifizierung abgeschlossen. + +You may close this page now and continue with FreeFileSync. +Sie können diese Seite nun schließen und mit FreeFileSync fortfahren. + +Authentication failed. +Authentifizierung fehlgeschlagen. + +Unable to connect to %x. +Es kann keine Verbindung zu %x aufgebaut werden. + +Cannot find %x. +%x wurde nicht gefunden. + +The name %x is used by more than one item in the folder. +Der Name %x wird von mehr als einem Element im Ordner verwendet. + +Please authorize access to user account %x. +Bitte autorisieren Sie den Zugriff auf das Nutzerkonto %x. + +Cannot open file %x. +Die Datei %x kann nicht geöffnet werden. + +The name %x is already used by another item. +Der Name %x wird bereits von einem anderen Element verwendet. + +Cannot determine free disk space for %x. +Der freie Speicherplatz für %x konnte nicht ermittelt werden. + +Unable to disconnect from %x. +Die Verbindung zu %x kann nicht getrennt werden. + +Unable to access %x. +Auf %x kann nicht zugegriffen werden. + +Failed to get information about server %x. +Informationen über den Server %x konnten nicht abgerufen werden. + +Cannot monitor directory %x. +Das Verzeichnis %x kann nicht überwacht werden. + +Cannot find device %x. +Das Gerät %x wurde nicht gefunden. + +Cannot open directory %x. +Das Verzeichnis %x kann nicht geöffnet werden. + +Unsupported item type. +Der Elementstyp wird nicht unterstützt. + +Incorrect command line: +Ungültige Befehlszeile: + +The server does not support authentication via %x. +Der Server unterstützt keine Authentifizierung über %x. + +Required: +Benötigt: + + +Operation timed out after 1 second. +Operation timed out after %x seconds. + + +Der Vorgang hat das Zeitlimit von 1 Sekunde überschritten. +Der Vorgang hat das Zeitlimit von %x Sekunden überschritten. + + + +Cannot wait on more than 1 connection at a time. +Cannot wait on more than %x connections at a time. + + +Auf mehr als 1 Verbindung kann nicht gleichzeitig gewartet werden. +Auf mehr als %x Verbindungen kann nicht gleichzeitig gewartet werden. + + +Active connections: %x +Aktive Verbindungen: %x + +Failed to open SFTP channel number %x. +Der SFTP Kanal Nummer %x konnte nicht geöffnet werden. + Both sides have changed since last synchronization. Beide Seiten wurden seit der letzten Synchronisation verändert. @@ -109,9 +258,6 @@ Installation files are corrupted. Please reinstall FreeFileSync. Die Installationsdateien sind beschädigt. Bitte installieren Sie FreeFileSync neu. -Cannot load file %x. -Die Datei %x kann nicht geladen werden. - Cannot find the following folders: Die folgenden Ordner wurden nicht gefunden: @@ -139,6 +285,9 @@ %x Elemente gefunden +Time elapsed: +Vergangene Zeit: + File %x has an invalid date. Die Datei %x hat ein ungültiges Datum. @@ -157,9 +306,6 @@ Items differ in attributes only Die Elemente unterscheiden sich nur in Attributen -The name %x is used by more than one item in the folder. -Der Name %x wird von mehr als einem Element im Ordner verwendet. - Resolving symbolic link %x Folge der symbolischen Verknüpfung %x @@ -220,6 +366,15 @@ Out of memory. Nicht genügend Arbeitsspeicher. +Show in Explorer +Im Explorer anzeigen + +Open with default application +Mit Standardanwendung öffnen + +Browse directory +Verzeichnis öffnen + Database file %x is incompatible. Die Datenbankdatei %x ist nicht kompatibel. @@ -232,12 +387,6 @@ Database file is corrupted: Die Datenbankdatei ist beschädigt: -Cannot write file %x. -Die Datei %x kann nicht geschrieben werden. - -Cannot read file %x. -Die Datei %x kann nicht gelesen werden. - The database files do not yet contain information about the last synchronization. Die Datenbankdateien beinhalten noch keine Informationen zur letzten Synchronisation. @@ -256,9 +405,6 @@ Cannot get process information. Prozessinformationen können nicht gelesen werden. -Cannot read file attributes of %x. -Die Dateiattribute von %x können nicht gelesen werden. - Waiting while directory is locked: Warte während das Verzeichnis gesperrt ist: @@ -385,27 +531,12 @@ %x Threads -Cannot read directory %x. -Das Verzeichnis %x kann nicht gelesen werden. - %x/sec %x/sek %x items %x Elemente -Show in Explorer -Im Explorer anzeigen - -Open with default application -Mit Standardanwendung öffnen - -Browse directory -Verzeichnis öffnen - -Unable to connect to %x. -Es kann keine Verbindung zu %x aufgebaut werden. - Completed successfully Erfolgreich abgeschlossen @@ -451,9 +582,6 @@ Cannot write file attributes of %x. Die Dateiattribute von %x können nicht geschrieben werden. -Cannot open file %x. -Die Datei %x kann nicht geöffnet werden. - %x and %y have different content. %x und %y haben unterschiedlichen Inhalt. @@ -484,21 +612,12 @@ Source item %x not found Das Quellelement %x wurde nicht gefunden. -Cannot move file %x to %y. -Die Datei %x kann nicht nach %y verschoben werden. - Parent folder %x is not existing. Der übergeordnete Ordner %x existiert nicht. -The name %x is already used by another item. -Der Name %x wird bereits von einem anderen Element verwendet. - Cannot copy file %x to %y. Die Datei %x kann nicht nach %y kopiert werden. -Cannot copy symbolic link %x to %y. -Die symbolische Verknüpfung %x kann nicht nach %y kopiert werden. - Creating a Volume Shadow Copy for %x... Erstelle eine Volumenschattenkopie für %x... @@ -529,9 +648,6 @@ Not enough free disk space available in: Nicht genügend freier Speicher verfügbar für: -Required: -Benötigt: - Available: Verfügbar: @@ -565,122 +681,6 @@ Unable to create time stamp for versioning: Der Zeitstempel für die Versionierung kann nicht erstellt werden: - -Unexpected size of data stream. -Expected: %x bytes -Actual: %y bytes - - -Unerwartete Größe des Datenstroms. -Erwartet: %x bytes -Tatsächlich: %y bytes - - -Cannot write permissions of %x. -Die Berechtigungen von %x können nicht geschrieben werden. - -Operation not supported between different devices. -Der Vorgang wird zwischen unterschiedlichen Geräten nicht unterstützt. - -Cannot delete file %x. -Die Datei %x kann nicht gelöscht werden. - -Cannot delete symbolic link %x. -Die symbolischen Verknüpfung %x kann nicht gelöscht werden. - -Cannot delete directory %x. -Das Verzeichnis %x kann nicht gelöscht werden. - -Error Code %x -Fehlercode %x - -Cannot write modification time of %x. -Die Änderungszeit von %x kann nicht geschrieben werden. - -Cannot create directory %x. -Das Verzeichnis %x kann nicht erstellt werden. - -Cannot determine final path for %x. -Der endgültige Pfad für %x kann nicht ermittelt werden. - -Operation not supported by device. -Der Vorgang wird vom Gerät nicht unterstützt. - -Cannot resolve symbolic link %x. -Die symbolische Verknüpfung %x kann nicht aufgelöst werden. - -Unable to move %x to the recycle bin. -%x kann nicht in den Papierkorb verschoben werden. - -Authentication completed. -Authentifizierung abgeschlossen. - -You may close this page now and continue with FreeFileSync. -Sie können diese Seite nun schließen und mit FreeFileSync fortfahren. - -Authentication failed. -Authentifizierung fehlgeschlagen. - -Cannot find %x. -%x wurde nicht gefunden. - -Please authorize access to user account %x. -Bitte autorisieren Sie den Zugriff auf das Nutzerkonto %x. - -Cannot determine free disk space for %x. -Der freie Speicherplatz für %x konnte nicht ermittelt werden. - -Unable to disconnect from %x. -Die Verbindung zu %x kann nicht getrennt werden. - -Unable to access %x. -Auf %x kann nicht zugegriffen werden. - -Failed to get information about server %x. -Informationen über den Server %x konnten nicht abgerufen werden. - -Cannot monitor directory %x. -Das Verzeichnis %x kann nicht überwacht werden. - -Cannot find device %x. -Das Gerät %x wurde nicht gefunden. - -Cannot open directory %x. -Das Verzeichnis %x kann nicht geöffnet werden. - -Unsupported item type. -Der Elementstyp wird nicht unterstützt. - -Incorrect command line: -Ungültige Befehlszeile: - -The server does not support authentication via %x. -Der Server unterstützt keine Authentifizierung über %x. - - -Operation timed out after 1 second. -Operation timed out after %x seconds. - - -Der Vorgang hat das Zeitlimit von 1 Sekunde überschritten. -Der Vorgang hat das Zeitlimit von %x Sekunden überschritten. - - - -Cannot wait on more than 1 connection at a time. -Cannot wait on more than %x connections at a time. - - -Auf mehr als 1 Verbindung kann nicht gleichzeitig gewartet werden. -Auf mehr als %x Verbindungen kann nicht gleichzeitig gewartet werden. - - -Active connections: %x -Aktive Verbindungen: %x - -Failed to open SFTP channel number %x. -Der SFTP Kanal Nummer %x konnte nicht geöffnet werden. - Drag && drop Drag && Drop @@ -747,6 +747,9 @@ Tatsächlich: %y bytes Command line: Befehlszeile: +&Hide console window +&Konsolenfenster verbergen + The command is triggered if: - files or subfolders change @@ -782,8 +785,8 @@ Die Befehlszeile wird ausgelöst, wenn: Waiting until directory is available: Warte bis Verzeichnis verfügbar ist: -&Restore -&Wiederherstellen +&Configure +&Konfigurieren &Show error message &Fehlermeldung zeigen @@ -830,8 +833,8 @@ Die Befehlszeile wird ausgelöst, wenn: Nothing to synchronize Es gibt nichts zu synchronisieren -Executing command %x -Führe Befehl aus: %x +Executing command: +Führe Befehl aus: You can switch to FreeFileSync's main window to resolve this issue. Sie können auf FreeFileSyncs Hauptfenster wechseln, um das Problem zu beheben. @@ -1273,9 +1276,6 @@ Die Befehlszeile wird ausgelöst, wenn: Time remaining: Verbleibende Zeit: -Time elapsed: -Vergangene Zeit: - Bytes Bytes @@ -1567,12 +1567,6 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. &Delete &Löschen -Include all -Alle einschließen - -Exclude all -Alle ausschließen - Show icons: Symbole anzeigen: @@ -1885,6 +1879,9 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. Minimum version count must be smaller than maximum count. Die minimale Anzahl der Versionen muss kleiner als die maximale sein. +&Restore +&Wiederherstellen + Files Dateien @@ -1951,6 +1948,9 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. The file is locked by another process: Die Datei wird von einem anderen Prozess gesperrt: +Checking file permissions failed for folder %x. +Die Prüfung der Dateiberechtigungen für Ordner %x ist fehlgeschlagen. + Cannot read security context of %x. Der Sicherheitskontext von %x kann nicht gelesen werden. @@ -2002,12 +2002,12 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. Cannot change process I/O priorities. Die Eingabe/Ausgabe Prioritäten für den Prozess können nicht geändert werden. -Unable to shut down the system. -Das System kann nicht heruntergefahren werden. - Checking recycle bin failed for folder %x. Die Prüfung des Papierkorbs für Ordner %x ist fehlgeschlagen. +Unable to shut down the system. +Das System kann nicht heruntergefahren werden. + Prepare installation Installation vorbereiten diff --git a/FreeFileSync/Build/Misc/Icons.zip b/FreeFileSync/Build/Misc/Icons.zip new file mode 100755 index 00000000..052ce17c Binary files /dev/null and b/FreeFileSync/Build/Misc/Icons.zip differ diff --git a/FreeFileSync/Build/RealTimeSync.desktop b/FreeFileSync/Build/RealTimeSync.desktop old mode 100644 new mode 100755 diff --git a/FreeFileSync/Build/Resources.zip b/FreeFileSync/Build/Resources.zip deleted file mode 100755 index 12931409..00000000 Binary files a/FreeFileSync/Build/Resources.zip and /dev/null differ diff --git a/FreeFileSync/Source/Makefile b/FreeFileSync/Source/Makefile index 00411a86..89cb74a8 100755 --- a/FreeFileSync/Source/Makefile +++ b/FreeFileSync/Source/Makefile @@ -40,6 +40,7 @@ CPP_FILES+=base/algorithm.cpp CPP_FILES+=base/application.cpp CPP_FILES+=base/binary.cpp CPP_FILES+=base/comparison.cpp +CPP_FILES+=base/config.cpp CPP_FILES+=base/db_file.cpp CPP_FILES+=base/dir_lock.cpp CPP_FILES+=base/ffs_paths.cpp @@ -50,22 +51,21 @@ CPP_FILES+=base/icon_loader.cpp CPP_FILES+=base/localization.cpp CPP_FILES+=base/parallel_scan.cpp CPP_FILES+=base/path_filter.cpp -CPP_FILES+=base/process_xml.cpp CPP_FILES+=base/perf_check.cpp CPP_FILES+=base/resolve_path.cpp CPP_FILES+=base/status_handler.cpp CPP_FILES+=base/structures.cpp CPP_FILES+=base/synchronization.cpp CPP_FILES+=base/versioning.cpp -CPP_FILES+=fs/abstract.cpp -CPP_FILES+=fs/concrete.cpp -CPP_FILES+=fs/ftp.cpp -CPP_FILES+=fs/gdrive.cpp -CPP_FILES+=fs/init_curl_libssh2.cpp -CPP_FILES+=fs/native.cpp -CPP_FILES+=fs/sftp.cpp -CPP_FILES+=fs/libssh2/init_libssh2.cpp -CPP_FILES+=fs/libssh2/init_open_ssl.cpp +CPP_FILES+=afs/abstract.cpp +CPP_FILES+=afs/concrete.cpp +CPP_FILES+=afs/ftp.cpp +CPP_FILES+=afs/gdrive.cpp +CPP_FILES+=afs/init_curl_libssh2.cpp +CPP_FILES+=afs/native.cpp +CPP_FILES+=afs/sftp.cpp +CPP_FILES+=afs/libssh2/init_libssh2.cpp +CPP_FILES+=afs/libssh2/init_open_ssl.cpp CPP_FILES+=ui/batch_config.cpp CPP_FILES+=ui/abstract_folder_picker.cpp CPP_FILES+=ui/batch_status_handler.cpp diff --git a/FreeFileSync/Source/RealTimeSync/Makefile b/FreeFileSync/Source/RealTimeSync/Makefile index 1d15ab16..8ac2b0b9 100755 --- a/FreeFileSync/Source/RealTimeSync/Makefile +++ b/FreeFileSync/Source/RealTimeSync/Makefile @@ -14,13 +14,13 @@ CXXFLAGS += -isystem/usr/include/gtk-2.0 CPP_FILES= CPP_FILES+=application.cpp +CPP_FILES+=config.cpp CPP_FILES+=gui_generated.cpp CPP_FILES+=main_dlg.cpp CPP_FILES+=tray_menu.cpp CPP_FILES+=monitor.cpp -CPP_FILES+=xml_proc.cpp CPP_FILES+=folder_selector2.cpp -CPP_FILES+=../fs/abstract.cpp +CPP_FILES+=../afs/abstract.cpp CPP_FILES+=../base/icon_buffer.cpp CPP_FILES+=../base/icon_loader.cpp CPP_FILES+=../base/localization.cpp diff --git a/FreeFileSync/Source/RealTimeSync/application.cpp b/FreeFileSync/Source/RealTimeSync/application.cpp index 2366e521..9318bcce 100644 --- a/FreeFileSync/Source/RealTimeSync/application.cpp +++ b/FreeFileSync/Source/RealTimeSync/application.cpp @@ -14,7 +14,7 @@ #include #include #include -#include "xml_proc.h" +#include "config.h" #include "../base/localization.h" #include "../base/ffs_paths.h" #include "../base/return_codes.h" @@ -49,7 +49,7 @@ bool Application::OnInit() SetAppName(L"RealTimeSync"); - initResourceImages(fff::getResourceDirPf() + Zstr("Resources.zip")); + initResourceImages(fff::getResourceDirPf() + Zstr("Misc") + FILE_NAME_SEPARATOR + Zstr("Icons.zip")); try { diff --git a/FreeFileSync/Source/RealTimeSync/config.cpp b/FreeFileSync/Source/RealTimeSync/config.cpp new file mode 100644 index 00000000..bc28e767 --- /dev/null +++ b/FreeFileSync/Source/RealTimeSync/config.cpp @@ -0,0 +1,193 @@ +// ***************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * +// ***************************************************************************** + +#include "config.h" +#include +#include +#include +#include "../base/ffs_paths.h" +#include "../base/localization.h" + +using namespace zen; +using namespace rts; + +//------------------------------------------------------------------------------------------------------------------------------- +const int XML_FORMAT_RTS_CFG = 1; //2019-05-10 +//------------------------------------------------------------------------------------------------------------------------------- + + +namespace zen +{ +template <> inline +bool readText(const std::string& input, wxLanguage& value) +{ + if (const wxLanguageInfo* lngInfo = wxLocale::FindLanguageInfo(utfTo(input))) + { + value = static_cast(lngInfo->Language); + return true; + } + return false; +} +} + + +namespace +{ +enum class RtsXmlType +{ + REAL, + BATCH, + GLOBAL, + OTHER +}; +RtsXmlType getXmlTypeNoThrow(const XmlDoc& doc) //throw() +{ + if (doc.root().getNameAs() == "FreeFileSync") + { + std::string type; + if (doc.root().getAttribute("XmlType", type)) + { + if (type == "REAL") + return RtsXmlType::REAL; + else if (type == "BATCH") + return RtsXmlType::BATCH; + else if (type == "GLOBAL") + return RtsXmlType::GLOBAL; + } + } + return RtsXmlType::OTHER; +} + + +void readConfig(const XmlIn& in, XmlRealConfig& cfg, int formatVer) +{ + in["Directories"](cfg.directories); + in["Delay" ](cfg.delay); + in["Commandline"](cfg.commandline); + + //TODO: remove if clause after migration! 2019-05-10 + if (formatVer < 1) + ; + else + in["Commandline"].attribute("HideConsole", cfg.hideConsoleWindow); +} + + +void writeConfig(const XmlRealConfig& cfg, XmlOut& out) +{ + out["Directories"](cfg.directories); + out["Delay" ](cfg.delay); + out["Commandline"](cfg.commandline); + out["Commandline"].attribute("HideConsole", cfg.hideConsoleWindow); +} +} + + +void rts::readConfig(const Zstring& filePath, XmlRealConfig& cfg, std::wstring& warningMsg) //throw FileError +{ + XmlDoc doc = loadXml(filePath); //throw FileError + + if (getXmlTypeNoThrow(doc) != RtsXmlType::REAL) //noexcept + throw FileError(replaceCpy(_("File %x does not contain a valid configuration."), L"%x", fmtPath(filePath))); + + int formatVer = 0; + /*bool success =*/ doc.root().getAttribute("XmlFormat", formatVer); + + XmlIn in(doc); + ::readConfig(in, cfg, formatVer); + + try + { + checkXmlMappingErrors(in, filePath); //throw FileError + + //(try to) migrate old configuration automatically + if (formatVer < XML_FORMAT_RTS_CFG) + try { rts::writeConfig(cfg, filePath); /*throw FileError*/ } + catch (FileError&) { assert(false); } //don't bother user! + } + catch (const FileError& e) { warningMsg = e.toString(); } +} + + +void rts::writeConfig(const XmlRealConfig& cfg, const Zstring& filePath) //throw FileError +{ + XmlDoc doc("FreeFileSync"); + doc.root().setAttribute("XmlType", "REAL"); + doc.root().setAttribute("XmlFormat", XML_FORMAT_RTS_CFG); + + XmlOut out(doc); + ::writeConfig(cfg, out); + + saveXml(doc, filePath); //throw FileError +} + + +void rts::readRealOrBatchConfig(const Zstring& filePath, XmlRealConfig& cfg, std::wstring& warningMsg) //throw FileError +{ + XmlDoc doc = loadXml(filePath); //throw FileError + //quick exit if file is not an FFS XML + + const RtsXmlType xmlType = ::getXmlTypeNoThrow(doc); + + //convert batch config to RealTimeSync config + if (xmlType == RtsXmlType::BATCH) + { + XmlIn in(doc); + + //read folder pairs + std::set uniqueFolders; + + for (XmlIn inPair = in["FolderPairs"]["Pair"]; inPair; inPair.next()) + { + Zstring folderPathPhraseLeft; + Zstring folderPathPhraseRight; + inPair["Left" ](folderPathPhraseLeft); + inPair["Right"](folderPathPhraseRight); + + uniqueFolders.insert(folderPathPhraseLeft); + uniqueFolders.insert(folderPathPhraseRight); + } + + //don't report failure as warning only: + checkXmlMappingErrors(in, filePath); //throw FileError + //--------------------------------------------------------------------------------------- + + eraseIf(uniqueFolders, [](const Zstring& str) { return trimCpy(str).empty(); }); + cfg.directories.assign(uniqueFolders.begin(), uniqueFolders.end()); + cfg.commandline = Zstr('"') + fff::getFreeFileSyncLauncherPath() + Zstr("\" \"") + filePath + Zstr('"'); + } + else + return readConfig(filePath, cfg, warningMsg); //throw FileError +} + + +wxLanguage rts::getProgramLanguage() //throw FileError +{ + const Zstring& filePath = fff::getConfigDirPathPf() + Zstr("GlobalSettings.xml"); + + XmlDoc doc; + try + { + doc = loadXml(filePath); //throw FileError + } + catch (FileError&) + { + if (!itemStillExists(filePath)) //throw FileError + return fff::getSystemLanguage(); + throw; + } + + if (getXmlTypeNoThrow(doc) != RtsXmlType::GLOBAL) //noexcept + throw FileError(replaceCpy(_("File %x does not contain a valid configuration."), L"%x", fmtPath(filePath))); + + XmlIn in(doc); + + wxLanguage lng = wxLANGUAGE_UNKNOWN; + in["General"]["Language"].attribute("Name", lng); + + checkXmlMappingErrors(in, filePath); //throw FileError + return lng; +} diff --git a/FreeFileSync/Source/RealTimeSync/config.h b/FreeFileSync/Source/RealTimeSync/config.h new file mode 100644 index 00000000..75d88aa5 --- /dev/null +++ b/FreeFileSync/Source/RealTimeSync/config.h @@ -0,0 +1,34 @@ +// ***************************************************************************** +// * 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 XML_PROC_H_0813748158321813490 +#define XML_PROC_H_0813748158321813490 + +#include +#include +#include + +namespace rts +{ +struct XmlRealConfig +{ + std::vector directories; + Zstring commandline; + bool hideConsoleWindow = false; + unsigned int delay = 10; +}; + +void readConfig(const Zstring& filePath, XmlRealConfig& config, std::wstring& warningMsg); //throw FileError +void writeConfig(const XmlRealConfig& config, const Zstring& filePath); //throw FileError + + +//reuse (some of) FreeFileSync's xml files +void readRealOrBatchConfig(const Zstring& filePath, XmlRealConfig& config, std::wstring& warningMsg); //throw FileError + +wxLanguage getProgramLanguage(); //throw FileError +} + +#endif //XML_PROC_H_0813748158321813490 diff --git a/FreeFileSync/Source/RealTimeSync/folder_selector2.cpp b/FreeFileSync/Source/RealTimeSync/folder_selector2.cpp index f32a6f6d..7900b5c5 100644 --- a/FreeFileSync/Source/RealTimeSync/folder_selector2.cpp +++ b/FreeFileSync/Source/RealTimeSync/folder_selector2.cpp @@ -13,7 +13,6 @@ #include "../base/resolve_path.h" #include - using namespace zen; using namespace rts; diff --git a/FreeFileSync/Source/RealTimeSync/gui_generated.cpp b/FreeFileSync/Source/RealTimeSync/gui_generated.cpp index bcd592ac..2bdb9a56 100644 --- a/FreeFileSync/Source/RealTimeSync/gui_generated.cpp +++ b/FreeFileSync/Source/RealTimeSync/gui_generated.cpp @@ -87,11 +87,32 @@ MainDlgGenerated::MainDlgGenerated( wxWindow* parent, wxWindowID id, const wxStr bSizer161->Add( bSizer16, 0, 0, 5 ); + wxBoxSizer* bSizer152; + bSizer152 = new wxBoxSizer( wxHORIZONTAL ); + m_staticText811 = new wxStaticText( this, wxID_ANY, _("To get started just import a \"ffs_batch\" file."), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText811->Wrap( -1 ); m_staticText811->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) ); - bSizer161->Add( m_staticText811, 0, wxALIGN_CENTER_HORIZONTAL|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); + bSizer152->Add( m_staticText811, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + + m_staticText10 = new wxStaticText( this, wxID_ANY, _("("), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText10->Wrap( -1 ); + m_staticText10->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) ); + + bSizer152->Add( m_staticText10, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 2 ); + + m_bitmapBatch = new wxStaticBitmap( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); + bSizer152->Add( m_bitmapBatch, 0, wxALIGN_CENTER_VERTICAL, 5 ); + + m_staticText11 = new wxStaticText( this, wxID_ANY, _(")"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText11->Wrap( -1 ); + m_staticText11->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) ); + + bSizer152->Add( m_staticText11, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 2 ); + + + bSizer161->Add( bSizer152, 0, wxALIGN_CENTER_HORIZONTAL|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); bSizerMain->Add( bSizer161, 0, wxALL|wxEXPAND, 5 ); @@ -223,7 +244,13 @@ MainDlgGenerated::MainDlgGenerated( wxWindow* parent, wxWindowID id, const wxStr bSizer13->Add( m_staticText6, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); - bSizer141->Add( bSizer13, 0, 0, 5 ); + bSizer13->Add( 0, 0, 1, wxEXPAND, 5 ); + + m_checkBoxHideConsole = new wxCheckBox( m_panelMain, wxID_ANY, _("&Hide console window"), wxDefaultPosition, wxDefaultSize, 0 ); + bSizer13->Add( m_checkBoxHideConsole, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); + + + bSizer141->Add( bSizer13, 0, wxEXPAND, 5 ); m_textCtrlCommand = new wxTextCtrl( m_panelMain, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 ); m_textCtrlCommand->SetToolTip( _("The command is triggered if:\n- files or subfolders change\n- new folders arrive (e.g. USB stick insert)") ); diff --git a/FreeFileSync/Source/RealTimeSync/gui_generated.h b/FreeFileSync/Source/RealTimeSync/gui_generated.h index 080fd473..c51491f8 100644 --- a/FreeFileSync/Source/RealTimeSync/gui_generated.h +++ b/FreeFileSync/Source/RealTimeSync/gui_generated.h @@ -24,14 +24,15 @@ namespace zen { class BitmapTextButton; } #include #include #include -#include #include +#include #include #include #include #include #include #include +#include #include #include "zen/i18n.h" @@ -56,6 +57,9 @@ protected: wxStaticText* m_staticText4; wxStaticText* m_staticText5; wxStaticText* m_staticText811; + wxStaticText* m_staticText10; + wxStaticBitmap* m_bitmapBatch; + wxStaticText* m_staticText11; wxStaticLine* m_staticline2; wxPanel* m_panelMain; wxStaticBitmap* m_bitmapFolders; @@ -74,6 +78,7 @@ protected: wxStaticLine* m_staticline211; wxStaticBitmap* m_bitmapCommand; wxStaticText* m_staticText6; + wxCheckBox* m_checkBoxHideConsole; wxTextCtrl* m_textCtrlCommand; wxStaticLine* m_staticline5; zen::BitmapTextButton* m_buttonStart; diff --git a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp index 37c04636..9eb1122f 100644 --- a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp +++ b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp @@ -14,7 +14,7 @@ #include #include #include -#include "xml_proc.h" +#include "config.h" #include "tray_menu.h" #include "app_icon.h" #include "../base/help_provider.h" @@ -78,14 +78,16 @@ MainDialog::MainDialog(const Zstring& cfgFileName) : setRelativeFontSize(*m_buttonStart, 1.5); m_txtCtrlDirectoryMain->SetMinSize(wxSize(fastFromDIP(300), -1)); - m_spinCtrlDelay->SetMinSize(wxSize(fastFromDIP(70), -1)); //Hack: set size (why does wxWindow::Size() not work?) + m_checkBoxHideConsole->Hide(); //only relevant on Windows m_bpButtonRemoveTopFolder->Hide(); m_panelMainFolder->Layout(); + m_bitmapBatch ->SetBitmap(getResourceImage(L"file_batch_sicon")); m_bitmapFolders->SetBitmap(fff::IconBuffer::genericDirIcon(fff::IconBuffer::SIZE_SMALL)); m_bitmapCommand->SetBitmap(shrinkImage(getResourceImage(L"command_line").ConvertToImage(), fastFromDIP(20))); + m_bpButtonAddFolder ->SetBitmapLabel(getResourceImage(L"item_add")); m_bpButtonRemoveTopFolder->SetBitmapLabel(getResourceImage(L"item_remove")); setBitmapTextLabel(*m_buttonStart, getResourceImage(L"startRts").ConvertToImage(), m_buttonStart->GetLabel(), fastFromDIP(5), fastFromDIP(8)); @@ -348,9 +350,9 @@ void MainDialog::setConfiguration(const XmlRealConfig& cfg) insertAddFolder(addFolderPaths, 0); - m_textCtrlCommand->SetValue(utfTo(cfg.commandline)); - - m_spinCtrlDelay->SetValue(static_cast(cfg.delay)); + m_textCtrlCommand ->SetValue(utfTo(cfg.commandline)); + m_checkBoxHideConsole->SetValue(cfg.hideConsoleWindow); + m_spinCtrlDelay ->SetValue(static_cast(cfg.delay)); } @@ -363,8 +365,9 @@ XmlRealConfig MainDialog::getConfiguration() for (const DirectoryPanel* dp : additionalFolderPanels_) output.directories.push_back(dp->getPath()); - output.commandline = utfTo(m_textCtrlCommand->GetValue()); - output.delay = m_spinCtrlDelay->GetValue(); + output.commandline = utfTo(m_textCtrlCommand->GetValue()); + output.hideConsoleWindow = m_checkBoxHideConsole->GetValue(); + output.delay = m_spinCtrlDelay->GetValue(); return output; } diff --git a/FreeFileSync/Source/RealTimeSync/tray_menu.cpp b/FreeFileSync/Source/RealTimeSync/tray_menu.cpp index 365f4779..8fba1101 100644 --- a/FreeFileSync/Source/RealTimeSync/tray_menu.cpp +++ b/FreeFileSync/Source/RealTimeSync/tray_menu.cpp @@ -274,7 +274,7 @@ rts::AbortReason rts::runFolderMonitor(const XmlRealConfig& config, const wxStri auto cmdLineExp = fff::expandMacros(cmdLine); try { - shellExecute(cmdLineExp, ExecutionType::SYNC); //throw FileError + shellExecute(cmdLineExp, ExecutionType::SYNC, config.hideConsoleWindow); //throw FileError } catch (const FileError& e) { diff --git a/FreeFileSync/Source/RealTimeSync/tray_menu.h b/FreeFileSync/Source/RealTimeSync/tray_menu.h index 79c63dc2..9c18fa08 100644 --- a/FreeFileSync/Source/RealTimeSync/tray_menu.h +++ b/FreeFileSync/Source/RealTimeSync/tray_menu.h @@ -8,7 +8,7 @@ #define TRAY_MENU_H_3967857420987534253245 #include -#include "xml_proc.h" +#include "config.h" namespace rts diff --git a/FreeFileSync/Source/RealTimeSync/xml_proc.cpp b/FreeFileSync/Source/RealTimeSync/xml_proc.cpp deleted file mode 100644 index 4cd8636f..00000000 --- a/FreeFileSync/Source/RealTimeSync/xml_proc.cpp +++ /dev/null @@ -1,180 +0,0 @@ -// ***************************************************************************** -// * This file is part of the FreeFileSync project. It is distributed under * -// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * -// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * -// ***************************************************************************** - -#include "xml_proc.h" -#include -#include -#include -#include "../base/ffs_paths.h" -#include "../base/localization.h" - -using namespace zen; -using namespace rts; - - -namespace zen -{ -template <> inline -bool readText(const std::string& input, wxLanguage& value) -{ - if (const wxLanguageInfo* lngInfo = wxLocale::FindLanguageInfo(utfTo(input))) - { - value = static_cast(lngInfo->Language); - return true; - } - return false; -} -} - - -namespace -{ -enum class RtsXmlType -{ - REAL, - BATCH, - GLOBAL, - OTHER -}; -RtsXmlType getXmlTypeNoThrow(const XmlDoc& doc) //throw() -{ - if (doc.root().getNameAs() == "FreeFileSync") - { - std::string type; - if (doc.root().getAttribute("XmlType", type)) - { - if (type == "REAL") - return RtsXmlType::REAL; - else if (type == "BATCH") - return RtsXmlType::BATCH; - else if (type == "GLOBAL") - return RtsXmlType::GLOBAL; - } - } - return RtsXmlType::OTHER; -} - - -void readConfig(const XmlIn& in, XmlRealConfig& config) -{ - in["Directories"](config.directories); - in["Delay" ](config.delay); - in["Commandline"](config.commandline); -} - -void writeConfig(const XmlRealConfig& config, XmlOut& out) -{ - out["Directories"](config.directories); - out["Delay" ](config.delay); - out["Commandline"](config.commandline); -} - - -template -void readConfig(const Zstring& filePath, RtsXmlType type, ConfigType& cfg, std::wstring& warningMsg) //throw FileError -{ - XmlDoc doc = loadXml(filePath); //throw FileError - - if (getXmlTypeNoThrow(doc) != type) //noexcept - throw FileError(replaceCpy(_("File %x does not contain a valid configuration."), L"%x", fmtPath(filePath))); - - XmlIn in(doc); - ::readConfig(in, cfg); - - try - { - checkXmlMappingErrors(in, filePath); //throw FileError - } - catch (const FileError& e) { warningMsg = e.toString(); } -} -} - - -void rts::readConfig(const Zstring& filePath, XmlRealConfig& config, std::wstring& warningMsg) //throw FileError -{ - ::readConfig(filePath, RtsXmlType::REAL, config, warningMsg); //throw FileError -} - - -void rts::writeConfig(const XmlRealConfig& config, const Zstring& filepath) //throw FileError -{ - XmlDoc doc("FreeFileSync"); - doc.root().setAttribute("XmlType", "REAL"); - - XmlOut out(doc); - ::writeConfig(config, out); - - saveXml(doc, filepath); //throw FileError -} - - -void rts::readRealOrBatchConfig(const Zstring& filePath, XmlRealConfig& config, std::wstring& warningMsg) //throw FileError -{ - XmlDoc doc = loadXml(filePath); //throw FileError - //quick exit if file is not an FFS XML - - const RtsXmlType xmlType = ::getXmlTypeNoThrow(doc); - - //convert batch config to RealTimeSync config - if (xmlType == RtsXmlType::BATCH) - { - XmlIn in(doc); - - //read folder pairs - std::set uniqueFolders; - - for (XmlIn inPair = in["FolderPairs"]["Pair"]; inPair; inPair.next()) - { - Zstring folderPathPhraseLeft; - Zstring folderPathPhraseRight; - inPair["Left" ](folderPathPhraseLeft); - inPair["Right"](folderPathPhraseRight); - - uniqueFolders.insert(folderPathPhraseLeft); - uniqueFolders.insert(folderPathPhraseRight); - } - - //don't consider failure a warning only: - checkXmlMappingErrors(in, filePath); //throw FileError - - //--------------------------------------------------------------------------------------- - - eraseIf(uniqueFolders, [](const Zstring& str) { return trimCpy(str).empty(); }); - config.directories.assign(uniqueFolders.begin(), uniqueFolders.end()); - config.commandline = Zstr('"') + fff::getFreeFileSyncLauncherPath() + Zstr("\" \"") + filePath + Zstr('"'); - } - else - return readConfig(filePath, config, warningMsg); //throw FileError -} - - -wxLanguage rts::getProgramLanguage() //throw FileError -{ - const Zstring& filePath = fff::getConfigDirPathPf() + Zstr("GlobalSettings.xml"); - - XmlDoc doc; - try - { - doc = loadXml(filePath); //throw FileError - } - catch (FileError&) - { - if (!itemStillExists(filePath)) //throw FileError - return fff::getSystemLanguage(); - throw; - } - - if (getXmlTypeNoThrow(doc) != RtsXmlType::GLOBAL) //noexcept - throw FileError(replaceCpy(_("File %x does not contain a valid configuration."), L"%x", fmtPath(filePath))); - - XmlIn in(doc); - - wxLanguage lng = wxLANGUAGE_UNKNOWN; - in["General"]["Language"].attribute("Name", lng); - - checkXmlMappingErrors(in, filePath); //throw FileError - return lng; -} diff --git a/FreeFileSync/Source/RealTimeSync/xml_proc.h b/FreeFileSync/Source/RealTimeSync/xml_proc.h deleted file mode 100644 index ebad3c7e..00000000 --- a/FreeFileSync/Source/RealTimeSync/xml_proc.h +++ /dev/null @@ -1,33 +0,0 @@ -// ***************************************************************************** -// * This file is part of the FreeFileSync project. It is distributed under * -// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * -// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * -// ***************************************************************************** - -#ifndef XML_PROC_H_0813748158321813490 -#define XML_PROC_H_0813748158321813490 - -#include -#include -#include - -namespace rts -{ -struct XmlRealConfig -{ - std::vector directories; - Zstring commandline; - unsigned int delay = 10; -}; - -void readConfig(const Zstring& filePath, XmlRealConfig& config, std::wstring& warningMsg); //throw FileError -void writeConfig(const XmlRealConfig& config, const Zstring& filepath); //throw FileError - - -//reuse (some of) FreeFileSync's xml files -void readRealOrBatchConfig(const Zstring& filePath, XmlRealConfig& config, std::wstring& warningMsg); //throw FileError - -wxLanguage getProgramLanguage(); //throw FileError -} - -#endif //XML_PROC_H_0813748158321813490 diff --git a/FreeFileSync/Source/afs/abstract.cpp b/FreeFileSync/Source/afs/abstract.cpp new file mode 100644 index 00000000..4d8874fa --- /dev/null +++ b/FreeFileSync/Source/afs/abstract.cpp @@ -0,0 +1,448 @@ +// ***************************************************************************** +// * 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 "abstract.h" +#include +#include +#include + +using namespace zen; +using namespace fff; +using AFS = AbstractFileSystem; + + +const Zchar* AFS::TEMP_FILE_ENDING = Zstr(".ffs_tmp"); + + +bool fff::isValidRelPath(const Zstring& relPath) +{ + return !contains(relPath, '\\') && + !startsWith(relPath, FILE_NAME_SEPARATOR) && !endsWith(relPath, FILE_NAME_SEPARATOR) && + !contains(relPath, Zstring() + FILE_NAME_SEPARATOR + FILE_NAME_SEPARATOR); +} + + +int AFS::compareDevice(const AbstractFileSystem& lhs, const AbstractFileSystem& rhs) +{ + //note: in worst case, order is guaranteed to be stable only during each program run + if (typeid(lhs).before(typeid(rhs))) + return -1; + if (typeid(rhs).before(typeid(lhs))) + return 1; + assert(typeid(lhs) == typeid(rhs)); + //caveat: typeid returns static type for pointers, dynamic type for references!!! + + return lhs.compareDeviceSameAfsType(rhs); +} + + +int AFS::comparePath(const AbstractPath& lhs, const AbstractPath& rhs) +{ + const int rv = compareDevice(lhs.afsDevice.ref(), rhs.afsDevice.ref()); + if (rv != 0) + return rv; + + return compareString(lhs.afsPath.value, rhs.afsPath.value); +} + + +std::optional AFS::getParentPath(const AbstractPath& ap) +{ + if (const std::optional parentAfsPath = getParentPath(ap.afsPath)) + return AbstractPath(ap.afsDevice, *parentAfsPath); + + return {}; +} + + +std::optional AFS::getParentPath(const AfsPath& afsPath) +{ + if (afsPath.value.empty()) + return {}; + + return AfsPath(beforeLast(afsPath.value, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE)); +} + + +namespace +{ +struct FlatTraverserCallback : public AFS::TraverserCallback +{ + FlatTraverserCallback(const std::function& onFile, + const std::function& onFolder, + const std::function& onSymlink) : + onFile_ (onFile), + onFolder_ (onFolder), + onSymlink_(onSymlink) {} + +private: + void onFile (const AFS::FileInfo& fi) override { if (onFile_) onFile_ (fi); } + std::shared_ptr onFolder (const AFS::FolderInfo& fi) override { if (onFolder_) onFolder_ (fi); return nullptr; } + HandleLink onSymlink(const AFS::SymlinkInfo& si) override { if (onSymlink_) onSymlink_(si); return TraverserCallback::LINK_SKIP; } + + HandleError reportDirError (const std::wstring& msg, size_t retryNumber) override { throw FileError(msg); } + HandleError reportItemError(const std::wstring& msg, size_t retryNumber, const Zstring& itemName) override { throw FileError(msg); } + + const std::function onFile_; + const std::function onFolder_; + const std::function onSymlink_; +}; +} + + +void AFS::traverseFolderFlat(const AfsPath& afsPath, //throw FileError + const std::function& onFile, + const std::function& onFolder, + const std::function& onSymlink) const +{ + auto ft = std::make_shared(onFile, onFolder, onSymlink); //throw FileError + traverseFolderRecursive({{ afsPath, ft }}, 1 /*parallelOps*/); //throw FileError +} + + +//target existing: undefined behavior! (fail/overwrite/auto-rename) +AFS::FileCopyResult AFS::copyFileAsStream(const AfsPath& afsPathSource, const StreamAttributes& attrSource, //throw FileError, ErrorFileLocked, X + const AbstractPath& apTarget, const IOCallback& notifyUnbufferedIO /*throw X*/) const +{ + int64_t totalUnbufferedIO = 0; + IOCallbackDivider cbd(notifyUnbufferedIO, totalUnbufferedIO); + + int64_t totalBytesRead = 0; + int64_t totalBytesWritten = 0; + auto notifyUnbufferedRead = [&](int64_t bytesDelta) { totalBytesRead += bytesDelta; cbd(bytesDelta); }; + auto notifyUnbufferedWrite = [&](int64_t bytesDelta) { totalBytesWritten += bytesDelta; cbd(bytesDelta); }; + //-------------------------------------------------------------------------------------------------------- + + auto streamIn = getInputStream(afsPathSource, notifyUnbufferedRead); //throw FileError, ErrorFileLocked + + StreamAttributes attrSourceNew = {}; + //try to get the most current attributes if possible (input file might have changed after comparison!) + if (std::optional attr = streamIn->getAttributesBuffered()) //throw FileError + attrSourceNew = *attr; //Native/MTP/Google Drive + else //use more stale ones: + attrSourceNew = attrSource; //SFTP/FTP + //TODO: evaluate: consequences of stale attributes + + //target existing: undefined behavior! (fail/overwrite/auto-rename) + auto streamOut = getOutputStream(apTarget, attrSourceNew.fileSize, attrSourceNew.modTime, notifyUnbufferedWrite); //throw FileError + + bufferedStreamCopy(*streamIn, *streamOut); //throw FileError, ErrorFileLocked, X + + const AFS::FinalizeResult finResult = streamOut->finalize(); //throw FileError, X + + //catch file I/O bugs + read/write conflicts: (note: different check than inside AbstractFileSystem::OutputStream::finalize() => checks notifyUnbufferedIO()!) + ZEN_ON_SCOPE_FAIL(try { removeFilePlain(apTarget); /*throw FileError*/ } + catch (FileError&) {}); //after finalize(): not guarded by ~AFS::OutputStream() anymore! + + if (totalBytesRead != makeSigned(attrSourceNew.fileSize)) + throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(getDisplayPath(afsPathSource))), + replaceCpy(replaceCpy(_("Unexpected size of data stream.\nExpected: %x bytes\nActual: %y bytes"), + L"%x", numberTo(attrSourceNew.fileSize)), + L"%y", numberTo(totalBytesRead)) + L" [notifyUnbufferedRead]"); + + if (totalBytesWritten != totalBytesRead) + throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getDisplayPath(apTarget))), + replaceCpy(replaceCpy(_("Unexpected size of data stream.\nExpected: %x bytes\nActual: %y bytes"), + L"%x", numberTo(totalBytesRead)), + L"%y", numberTo(totalBytesWritten)) + L" [notifyUnbufferedWrite]"); + + AFS::FileCopyResult cpResult; + cpResult.fileSize = attrSourceNew.fileSize; + cpResult.modTime = attrSourceNew.modTime; + cpResult.sourceFileId = attrSourceNew.fileId; + cpResult.targetFileId = finResult.fileId; + cpResult.errorModTime = finResult.errorModTime; + /* Failing to set modification time is not a serious problem from synchronization perspective (treated like external update) + => Support additional scenarios: + - GVFS failing to set modTime for FTP: https://freefilesync.org/forum/viewtopic.php?t=2372 + - GVFS failing to set modTime for MTP: https://freefilesync.org/forum/viewtopic.php?t=2803 + - MTP failing to set modTime in general: fail non-silently rather than silently during file creation + - FTP failing to set modTime for servers without MFMT-support */ + return cpResult; +} + + +//target existing: undefined behavior! (fail/overwrite/auto-rename) +AFS::FileCopyResult AFS::copyFileTransactional(const AbstractPath& apSource, const StreamAttributes& attrSource, //throw FileError, ErrorFileLocked, X + const AbstractPath& apTarget, + bool copyFilePermissions, + bool transactionalCopy, + const std::function& onDeleteTargetFile, + const IOCallback& notifyUnbufferedIO /*throw X*/) +{ + auto copyFilePlain = [&](const AbstractPath& apTargetTmp) + { + //caveat: typeid returns static type for pointers, dynamic type for references!!! + if (typeid(apSource.afsDevice.ref()) == typeid(apTargetTmp.afsDevice.ref())) + return apSource.afsDevice.ref().copyFileForSameAfsType(apSource.afsPath, attrSource, + apTargetTmp, copyFilePermissions, notifyUnbufferedIO); //throw FileError, ErrorFileLocked, X + //target existing: undefined behavior! (fail/overwrite/auto-rename) + + //fall back to stream-based file copy: + if (copyFilePermissions) + throw FileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(AFS::getDisplayPath(apTargetTmp))), + _("Operation not supported between different devices.")); + + return apSource.afsDevice.ref().copyFileAsStream(apSource.afsPath, attrSource, apTargetTmp, notifyUnbufferedIO); //throw FileError, ErrorFileLocked, X + //target existing: undefined behavior! (fail/overwrite/auto-rename) + }; + + if (transactionalCopy && !hasNativeTransactionalCopy(apTarget)) + { + std::optional parentPath = AFS::getParentPath(apTarget); + if (!parentPath) + throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(AFS::getDisplayPath(apTarget))), L"Path is device root."); + const Zstring fileName = AFS::getItemName(apTarget); + + //- generate (hopefully) unique file name to avoid clashing with some remnant ffs_tmp file + //- do not loop: avoid pathological cases, e.g. https://freefilesync.org/forum/viewtopic.php?t=1592 + const Zstring& shortGuid = printNumber(Zstr("%04x"), static_cast(getCrc16(generateGUID()))); + const Zstring& tmpExt = Zstr('.') + shortGuid + TEMP_FILE_ENDING; + + Zstring tmpName = beforeLast(fileName, Zstr('.'), IF_MISSING_RETURN_ALL); + + //don't make the temp name longer than the original when hitting file system name length limitations: "lpMaximumComponentLength is commonly 255 characters" + while (tmpName.size() > 200) //BUT don't trim short names! we want early failure on filename-related issues + tmpName = getUnicodeSubstring(tmpName, 0 /*uniPosFirst*/, unicodeLength(tmpName) / 2 /*uniPosLast*/); //consider UTF encoding when cutting in the middle! (e.g. for macOS) + + const AbstractPath apTargetTmp = AFS::appendRelPath(*parentPath, tmpName + tmpExt); + //------------------------------------------------------------------------------------------- + + const AFS::FileCopyResult result = copyFilePlain(apTargetTmp); //throw FileError, ErrorFileLocked + + //transactional behavior: ensure cleanup; not needed before copyFilePlain() which is already transactional + ZEN_ON_SCOPE_FAIL( try { AFS::removeFilePlain(apTargetTmp); } + catch (FileError&) {}); + + //have target file deleted (after read access on source and target has been confirmed) => allow for almost transactional overwrite + if (onDeleteTargetFile) + onDeleteTargetFile(); //throw X + + //perf: this call is REALLY expensive on unbuffered volumes! ~40% performance decrease on FAT USB stick! + moveAndRenameItem(apTargetTmp, apTarget); //throw FileError, (ErrorMoveUnsupported) + + /* + CAVEAT on FAT/FAT32: the sequence of deleting the target file and renaming "file.txt.ffs_tmp" to "file.txt" does + NOT PRESERVE the creation time of the .ffs_tmp file, but SILENTLY "reuses" whatever creation time the old "file.txt" had! + This "feature" is called "File System Tunneling": + https://devblogs.microsoft.com/oldnewthing/?p=34923 + http://support.microsoft.com/kb/172190/en-us + */ + return result; + } + else + { + /* + Note: non-transactional file copy solves at least four problems: + -> skydrive - doesn't allow for .ffs_tmp extension and returns ERROR_INVALID_PARAMETER + -> network renaming issues + -> allow for true delete before copy to handle low disk space problems + -> higher performance on unbuffered drives (e.g. USB-sticks) + */ + if (onDeleteTargetFile) + onDeleteTargetFile(); + + return copyFilePlain(apTarget); //throw FileError, ErrorFileLocked + } +} + + +void AFS::createFolderIfMissingRecursion(const AbstractPath& ap) //throw FileError +{ + const std::optional parentPath = getParentPath(ap); + if (!parentPath) //device root + return; + + try //generally we expect that path already exists (see: versioning, base folder, log file path) => check first + { + if (getItemType(ap) != ItemType::FILE) //throw FileError + return; + } + catch (FileError&) {} //not yet existing or access error? let's find out... + + createFolderIfMissingRecursion(*parentPath); //throw FileError + + try + { + //target existing: fail/ignore + createFolderPlain(ap); //throw FileError + } + catch (FileError&) + { + try + { + if (getItemType(ap) != ItemType::FILE) //throw FileError + return; //already existing => possible, if createFolderIfMissingRecursion() is run in parallel + } + catch (FileError&) {} //not yet existing or access error + + throw; + } +} + + +//default implementation: folder traversal +std::optional AFS::itemStillExists(const AfsPath& afsPath) const //throw FileError +{ + try + { + //fast check: 1. perf 2. expected by getFolderStatusNonBlocking() 3. traversing non-existing folder below MIGHT NOT FAIL (e.g. for SFTP on AWS) + return getItemType(afsPath); //throw FileError + } + catch (const FileError& e) //not existing or access error + { + const std::optional parentAfsPath = getParentPath(afsPath); + if (!parentAfsPath) //device root + throw; + //else: let's dig deeper... don't bother checking Win32 codes; e.g. not existing item may have the codes: + // ERROR_FILE_NOT_FOUND, ERROR_PATH_NOT_FOUND, ERROR_INVALID_NAME, ERROR_INVALID_DRIVE, + // ERROR_NOT_READY, ERROR_INVALID_PARAMETER, ERROR_BAD_PATHNAME, ERROR_BAD_NETPATH => not reliable + + const Zstring itemName = getItemName(afsPath); + assert(!itemName.empty()); + + const std::optional parentType = AFS::itemStillExists(*parentAfsPath); //throw FileError + if (parentType && *parentType != ItemType::FILE /*obscure, but possible (and not an error)*/) + try + { + traverseFolderFlat(*parentAfsPath, //throw FileError + [&](const FileInfo& fi) { if (fi.itemName == itemName) throw ItemType::FILE; }, + [&](const FolderInfo& fi) { if (fi.itemName == itemName) throw ItemType::FOLDER; }, + [&](const SymlinkInfo& si) { if (si.itemName == itemName) throw ItemType::SYMLINK; }); + } + catch (const ItemType&) //finding the item after getItemType() previously failed is exceptional + { + throw e; //yes, slicing + } + return {}; + } +} + + +//default implementation: folder traversal +void AFS::removeFolderIfExistsRecursion(const AfsPath& afsPath, //throw FileError + const std::function& onBeforeFileDeletion /*throw X*/, //optional + const std::function& onBeforeFolderDeletion) const //one call for each object! +{ + //deferred recursion => save stack space and allow deletion of extremely deep hierarchies! + std::function removeFolderRecursionImpl; + removeFolderRecursionImpl = [this, &onBeforeFileDeletion, &onBeforeFolderDeletion, &removeFolderRecursionImpl](const AfsPath& folderPath) //throw FileError + { + std::vector fileNames; + std::vector folderNames; + std::vector symlinkNames; + + traverseFolderFlat(folderPath, //throw FileError + [&](const FileInfo& fi) { fileNames.push_back(fi.itemName); }, + [&](const FolderInfo& fi) { folderNames.push_back(fi.itemName); }, + [&](const SymlinkInfo& si) { symlinkNames.push_back(si.itemName); }); + + for (const Zstring& fileName : fileNames) + { + const AfsPath filePath(nativeAppendPaths(folderPath.value, fileName)); + if (onBeforeFileDeletion) + onBeforeFileDeletion(getDisplayPath(filePath)); //throw X + + removeFilePlain(filePath); //throw FileError + } + + for (const Zstring& symlinkName : symlinkNames) + { + const AfsPath linkPath(nativeAppendPaths(folderPath.value, symlinkName)); + if (onBeforeFileDeletion) + onBeforeFileDeletion(getDisplayPath(linkPath)); //throw X + + removeSymlinkPlain(linkPath); //throw FileError + } + + for (const Zstring& folderName : folderNames) + removeFolderRecursionImpl(AfsPath(nativeAppendPaths(folderPath.value, folderName))); //throw FileError + + if (onBeforeFolderDeletion) + onBeforeFolderDeletion(getDisplayPath(folderPath)); //throw X + + removeFolderPlain(folderPath); //throw FileError + }; + //-------------------------------------------------------------------------------------------------------------- + warn_static("what about parallelOps?") + + //no error situation if directory is not existing! manual deletion relies on it! + if (std::optional type = itemStillExists(afsPath)) //throw FileError + { + if (*type == AFS::ItemType::SYMLINK) + { + if (onBeforeFileDeletion) + onBeforeFileDeletion(getDisplayPath(afsPath)); //throw X + + removeSymlinkPlain(afsPath); //throw FileError + } + else + removeFolderRecursionImpl(afsPath); //throw FileError + } + else //even if the folder did not exist anymore, significant I/O work was done => report + if (onBeforeFolderDeletion) onBeforeFolderDeletion(getDisplayPath(afsPath)); //throw X +} + + +void AFS::removeFileIfExists(const AbstractPath& ap) //throw FileError +{ + try + { + AFS::removeFilePlain(ap); //throw FileError + } + catch (const FileError&) + { + try + { + if (!AFS::itemStillExists(ap)) //throw FileError + return; + } + catch (const FileError& e2) { throw FileError(replaceCpy(_("Cannot delete file %x."), L"%x", fmtPath(getDisplayPath(ap))), replaceCpy(e2.toString(), L"\n\n", L"\n")); } + //more relevant than previous exception (which could be "item not found") + throw; + } +} + + +void AFS::removeSymlinkIfExists(const AbstractPath& ap) //throw FileError +{ + try + { + AFS::removeSymlinkPlain(ap); //throw FileError + } + catch (const FileError&) + { + try + { + if (!AFS::itemStillExists(ap)) //throw FileError + return; + } + catch (const FileError& e2) { throw FileError(replaceCpy(_("Cannot delete symbolic link %x."), L"%x", fmtPath(getDisplayPath(ap))), replaceCpy(e2.toString(), L"\n\n", L"\n")); } + //more relevant than previous exception (which could be "item not found") + throw; + } +} + + +void AFS::removeEmptyFolderIfExists(const AbstractPath& ap) //throw FileError +{ + try + { + AFS::removeFolderPlain(ap); //throw FileError + } + catch (const FileError&) + { + try + { + if (!AFS::itemStillExists(ap)) //throw FileError + return; + } + catch (const FileError& e2) { throw FileError(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtPath(getDisplayPath(ap))), replaceCpy(e2.toString(), L"\n\n", L"\n")); } + //more relevant than previous exception (which could be "item not found") + + throw; + } +} diff --git a/FreeFileSync/Source/afs/abstract.h b/FreeFileSync/Source/afs/abstract.h new file mode 100644 index 00000000..5a2172aa --- /dev/null +++ b/FreeFileSync/Source/afs/abstract.h @@ -0,0 +1,537 @@ +// ***************************************************************************** +// * 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 ABSTRACT_H_873450978453042524534234 +#define ABSTRACT_H_873450978453042524534234 + +#include +#include +#include +#include //InputStream/OutputStream support buffered stream concept +#include //NOT a wxWidgets dependency! + + +namespace fff +{ +bool isValidRelPath(const Zstring& relPath); + +struct AbstractFileSystem; + +//============================================================================================================== +using AfsDevice = zen::SharedRef; + +struct AfsPath //= path relative to the file system root folder (no leading/traling separator) +{ + AfsPath() {} + explicit AfsPath(const Zstring& p) : value(p) { assert(isValidRelPath(value)); } + Zstring value; +}; + +struct AbstractPath //THREAD-SAFETY: like an int! +{ + AbstractPath(const AfsDevice& afsIn, const AfsPath& afsPathIn) : afsDevice(afsIn), afsPath(afsPathIn) {} + + //template -> don't use forwarding constructor: it circumvents AfsPath's explicit constructor! + //AbstractPath(T1&& afsIn, T2&& afsPathIn) : afsDevice(std::forward(afsIn)), afsPath(std::forward(afsPathIn)) {} + + AfsDevice afsDevice; //"const AbstractFileSystem" => all accesses expected to be thread-safe!!! + AfsPath afsPath; //relative to device root +}; +//============================================================================================================== + +struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model thread-safe access! +{ + //=============== convenience ================= + static Zstring getItemName(const AbstractPath& ap) { assert(getParentPath(ap)); return getItemName(ap.afsPath); } + static Zstring getItemName(const AfsPath& afsPath) { using namespace zen; return afterLast(afsPath.value, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); } + + static bool isNullPath(const AbstractPath& ap) { return isNullDevice(ap.afsDevice) /*&& ap.afsPath.value.empty()*/; } + + static AbstractPath appendRelPath(const AbstractPath& ap, const Zstring& relPath); + + static std::optional getParentPath(const AbstractPath& ap); + static std::optional getParentPath(const AfsPath& afsPath); + //============================================= + + static int compareDevice(const AbstractFileSystem& lhs, const AbstractFileSystem& rhs); + + static int comparePath(const AbstractPath& lhs, const AbstractPath& rhs); + + static bool isNullDevice(const AfsDevice& afsDevice) { return afsDevice.ref().isNullFileSystem(); } + + static std::wstring getDisplayPath(const AbstractPath& ap) { return ap.afsDevice.ref().getDisplayPath(ap.afsPath); } + + static Zstring getInitPathPhrase(const AbstractPath& ap) { return ap.afsDevice.ref().getInitPathPhrase(ap.afsPath); } + + static std::optional getNativeItemPath(const AbstractPath& ap) { return ap.afsDevice.ref().getNativeItemPath(ap.afsPath); } + + //---------------------------------------------------------------------------------------------------------------- + static void authenticateAccess(const AfsDevice& afsDevice, bool allowUserInteraction) //throw FileError + { return afsDevice.ref().authenticateAccess(allowUserInteraction); } + + static int getAccessTimeout(const AbstractPath& ap) { return ap.afsDevice.ref().getAccessTimeout(); } //returns "0" if no timeout in force + + static bool supportPermissionCopy(const AbstractPath& apSource, const AbstractPath& apTarget); //throw FileError + + static bool hasNativeTransactionalCopy(const AbstractPath& ap) { return ap.afsDevice.ref().hasNativeTransactionalCopy(); } + //---------------------------------------------------------------------------------------------------------------- + + using FileId = zen::Zbase; //AfsDevice-dependent unique ID + + enum class ItemType : unsigned char + { + FILE, + FOLDER, + SYMLINK, + }; + //(hopefully) fast: does not distinguish between error/not existing + //root path? => do access test + static ItemType getItemType(const AbstractPath& ap) { return ap.afsDevice.ref().getItemType(ap.afsPath); } //throw FileError + + //assumes: - base path still exists + // - all child item path parts must correspond to folder traversal + // => we can conclude whether an item is *not* existing anymore by doing a *case-sensitive* name search => potentially SLOW! + // root path? => do access test + static std::optional itemStillExists(const AbstractPath& ap) { return ap.afsDevice.ref().itemStillExists(ap.afsPath); } //throw FileError + //---------------------------------------------------------------------------------------------------------------- + + //already existing: fail/ignore + //does NOT create parent directories recursively if not existing + static void createFolderPlain(const AbstractPath& ap) { ap.afsDevice.ref().createFolderPlain(ap.afsPath); } //throw FileError + + //already existing: ignore + //creates parent directories recursively if not existing + static void createFolderIfMissingRecursion(const AbstractPath& ap); //throw FileError + + static void removeFolderIfExistsRecursion(const AbstractPath& ap, //throw FileError + const std::function& onBeforeFileDeletion /*throw X*/, //optional + const std::function& onBeforeFolderDeletion) //one call for each object! + { return ap.afsDevice.ref().removeFolderIfExistsRecursion(ap.afsPath, onBeforeFileDeletion, onBeforeFolderDeletion); } + + static void removeFileIfExists (const AbstractPath& ap); //throw FileError; return "false" if file is not existing + static void removeSymlinkIfExists(const AbstractPath& ap); // + static void removeEmptyFolderIfExists(const AbstractPath& ap); //throw FileError + + static void removeFilePlain (const AbstractPath& ap) { ap.afsDevice.ref().removeFilePlain (ap.afsPath); } //throw FileError + static void removeSymlinkPlain(const AbstractPath& ap) { ap.afsDevice.ref().removeSymlinkPlain(ap.afsPath); } //throw FileError + static void removeFolderPlain (const AbstractPath& ap) { ap.afsDevice.ref().removeFolderPlain (ap.afsPath); } //throw FileError + //---------------------------------------------------------------------------------------------------------------- + //static void setModTime(const AbstractPath& ap, time_t modTime) { ap.afsDevice.ref().setModTime(ap.afsPath, modTime); } //throw FileError, follows symlinks + + static AbstractPath getSymlinkResolvedPath(const AbstractPath& ap) { return ap.afsDevice.ref().getSymlinkResolvedPath (ap.afsPath); } //throw FileError + static std::string getSymlinkBinaryContent(const AbstractPath& ap) { return ap.afsDevice.ref().getSymlinkBinaryContent(ap.afsPath); } //throw FileError + //---------------------------------------------------------------------------------------------------------------- + //noexcept; optional return value: + static zen::ImageHolder getFileIcon (const AbstractPath& ap, int pixelSize) { return ap.afsDevice.ref().getFileIcon (ap.afsPath, pixelSize); } + static zen::ImageHolder getThumbnailImage(const AbstractPath& ap, int pixelSize) { return ap.afsDevice.ref().getThumbnailImage(ap.afsPath, pixelSize); } + //---------------------------------------------------------------------------------------------------------------- + + struct StreamAttributes + { + time_t modTime; //number of seconds since Jan. 1st 1970 UTC + uint64_t fileSize; + FileId fileId; //optional! + }; + + //---------------------------------------------------------------------------------------------------------------- + struct InputStream + { + virtual ~InputStream() {} + virtual size_t read(void* buffer, size_t bytesToRead) = 0; //throw FileError, ErrorFileLocked, X; return "bytesToRead" bytes unless end of stream! + virtual size_t getBlockSize() const = 0; //non-zero block size is AFS contract! it's implementer's job to always give a reasonable buffer size! + + //only returns attributes if they are already buffered within stream handle and determination would be otherwise expensive (e.g. FTP/SFTP): + virtual std::optional getAttributesBuffered() = 0; //throw FileError + }; + + + struct FinalizeResult + { + FileId fileId; + std::optional errorModTime; + }; + + struct OutputStreamImpl + { + virtual ~OutputStreamImpl() {} + virtual void write(const void* buffer, size_t bytesToWrite) = 0; //throw FileError, X + virtual FinalizeResult finalize() = 0; //throw FileError, X + }; + + //TRANSACTIONAL output stream! => call finalize when done! + struct OutputStream + { + OutputStream(std::unique_ptr&& outStream, const AbstractPath& filePath, std::optional streamSize); + ~OutputStream(); + void write(const void* buffer, size_t bytesToWrite); //throw FileError, X + FinalizeResult finalize(); //throw FileError, X + + private: + std::unique_ptr outStream_; //bound! + const AbstractPath filePath_; + bool finalizeSucceeded_ = false; + const std::optional bytesExpected_; + uint64_t bytesWrittenTotal_ = 0; + }; + + //return value always bound: + static std::unique_ptr getInputStream(const AbstractPath& ap, const zen::IOCallback& notifyUnbufferedIO /*throw X*/) //throw FileError, ErrorFileLocked + { return ap.afsDevice.ref().getInputStream(ap.afsPath, notifyUnbufferedIO); } + + //target existing: undefined behavior! (fail/overwrite/auto-rename) + static std::unique_ptr getOutputStream(const AbstractPath& ap, //throw FileError + std::optional streamSize, + std::optional modTime, + const zen::IOCallback& notifyUnbufferedIO /*throw X*/) + { return std::make_unique(ap.afsDevice.ref().getOutputStream(ap.afsPath, streamSize, modTime, notifyUnbufferedIO), ap, streamSize); } + //---------------------------------------------------------------------------------------------------------------- + + struct SymlinkInfo + { + Zstring itemName; + time_t modTime; //number of seconds since Jan. 1st 1970 UTC + }; + + struct FileInfo + { + Zstring itemName; + uint64_t fileSize; //unit: bytes! + time_t modTime; //number of seconds since Jan. 1st 1970 UTC + FileId fileId; //optional: empty if not supported! + const SymlinkInfo* symlinkInfo; //only filled if file is a followed symlink + }; + + struct FolderInfo + { + Zstring itemName; + const SymlinkInfo* symlinkInfo; //only filled if folder is a followed symlink + }; + + struct TraverserCallback + { + virtual ~TraverserCallback() {} + + enum HandleLink + { + LINK_FOLLOW, //dereferences link, then calls "onFolder()" or "onFile()" + LINK_SKIP + }; + + enum HandleError + { + ON_ERROR_RETRY, + ON_ERROR_CONTINUE + }; + + virtual void onFile (const FileInfo& fi) = 0; // + virtual HandleLink onSymlink(const SymlinkInfo& si) = 0; //throw X + virtual std::shared_ptr onFolder (const FolderInfo& fi) = 0; // + //nullptr: ignore directory, non-nullptr: traverse into, using the (new) callback + + virtual HandleError reportDirError (const std::wstring& msg, size_t retryNumber) = 0; //failed directory traversal -> consider directory data at current level as incomplete! + virtual HandleError reportItemError(const std::wstring& msg, size_t retryNumber, const Zstring& itemName) = 0; //failed to get data for single file/dir/symlink only! + }; + + using TraverserWorkload = std::vector /*throw X*/>>; + + //- client needs to handle duplicate file reports! (FilePlusTraverser fallback, retrying to read directory contents, ...) + static void traverseFolderRecursive(const AfsDevice& afsDevice, const TraverserWorkload& workload /*throw X*/, size_t parallelOps) { afsDevice.ref().traverseFolderRecursive(workload, parallelOps); } + + static void traverseFolderFlat(const AbstractPath& ap, //throw FileError + const std::function& onFile, // + const std::function& onFolder, //optional + const std::function& onSymlink) // + { ap.afsDevice.ref().traverseFolderFlat(ap.afsPath, onFile, onFolder, onSymlink); } + //---------------------------------------------------------------------------------------------------------------- + + //target existing: undefined behavior! (fail/overwrite/auto-rename) + static void moveAndRenameItem(const AbstractPath& pathFrom, const AbstractPath& pathTo); //throw FileError, ErrorMoveUnsupported + + //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: + static const Zchar* TEMP_FILE_ENDING; //don't use Zstring as global constant: avoid static initialization order problem in global namespace! + + struct FileCopyResult + { + uint64_t fileSize = 0; + time_t modTime = 0; //number of seconds since Jan. 1st 1970 UTC + FileId sourceFileId; + FileId targetFileId; + std::optional errorModTime; //failure to set modification time + }; + + //symlink handling: follow + //target existing: undefined behavior! (fail/overwrite/auto-rename) + //returns current attributes at the time of copy + static FileCopyResult copyFileTransactional(const AbstractPath& apSource, const StreamAttributes& attrSource, //throw FileError, ErrorFileLocked, X + const AbstractPath& apTarget, + bool copyFilePermissions, + bool transactionalCopy, + //if target is existing user *must* implement deletion to avoid undefined behavior + //if transactionalCopy == true, full read access on source had been proven at this point, so it's safe to delete it. + const std::function& onDeleteTargetFile /*throw X*/, + //accummulated delta != file size! consider ADS, sparse, compressed files + const zen::IOCallback& notifyUnbufferedIO /*throw X*/); + + //already existing: fail/ignore + //symlink handling: follow link! + static void copyNewFolder(const AbstractPath& apSource, const AbstractPath& apTarget, bool copyFilePermissions); //throw FileError + + static void copySymlink (const AbstractPath& apSource, const AbstractPath& apTarget, bool copyFilePermissions); //throw FileError + + //---------------------------------------------------------------------------------------------------------------- + + static uint64_t getFreeDiskSpace(const AbstractPath& ap) { return ap.afsDevice.ref().getFreeDiskSpace(ap.afsPath); } //throw FileError, returns 0 if not available + + static bool supportsRecycleBin(const AbstractPath& ap) { return ap.afsDevice.ref().supportsRecycleBin(ap.afsPath); } //throw FileError + + struct RecycleSession + { + virtual ~RecycleSession() {} + //- multi-threaded access: internally synchronized! + virtual void recycleItemIfExists(const AbstractPath& itemPath, const Zstring& logicalRelPath) = 0; //throw FileError + + virtual void tryCleanup(const std::function& notifyDeletionStatus /*throw X*; displayPath may be empty*/) = 0; //throw FileError, X + }; + + //precondition: supportsRecycleBin() must return true! + static std::unique_ptr createRecyclerSession(const AbstractPath& ap) { return ap.afsDevice.ref().createRecyclerSession(ap.afsPath); } //throw FileError, return value must be bound! + + static void recycleItemIfExists(const AbstractPath& ap) { ap.afsDevice.ref().recycleItemIfExists(ap.afsPath); } //throw FileError + + //================================================================================================================ + + //no need to protect access: + virtual ~AbstractFileSystem() {} + + +protected: + //default implementation: folder traversal + virtual std::optional itemStillExists(const AfsPath& afsPath) const = 0; //throw FileError + + //default implementation: folder traversal + virtual void removeFolderIfExistsRecursion(const AfsPath& afsPath, //throw FileError + const std::function& onBeforeFileDeletion, //optional + const std::function& onBeforeFolderDeletion) const = 0; //one call for each object! + + void traverseFolderFlat(const AfsPath& afsPath, //throw FileError + const std::function& onFile, // + const std::function& onFolder, //optional + const std::function& onSymlink) const; // + + //target existing: undefined behavior! (fail/overwrite/auto-rename) + FileCopyResult copyFileAsStream(const AfsPath& afsPathSource, const StreamAttributes& attrSource, //throw FileError, ErrorFileLocked, X + const AbstractPath& apTarget, const zen::IOCallback& notifyUnbufferedIO /*throw X*/) const; + +private: + virtual std::optional getNativeItemPath(const AfsPath& afsPath) const { return {}; }; + + virtual Zstring getInitPathPhrase(const AfsPath& afsPath) const = 0; + + virtual std::wstring getDisplayPath(const AfsPath& afsPath) const = 0; + + virtual bool isNullFileSystem() const = 0; + + virtual int compareDeviceSameAfsType(const AbstractFileSystem& afsRhs) const = 0; + + //---------------------------------------------------------------------------------------------------------------- + virtual ItemType getItemType(const AfsPath& afsPath) const = 0; //throw FileError + //---------------------------------------------------------------------------------------------------------------- + + //already existing: fail/ignore + virtual void createFolderPlain(const AfsPath& afsPath) const = 0; //throw FileError + + //non-recursive folder deletion: + virtual void removeFilePlain (const AfsPath& afsPath) const = 0; //throw FileError + virtual void removeSymlinkPlain(const AfsPath& afsPath) const = 0; //throw FileError + virtual void removeFolderPlain (const AfsPath& afsPath) const = 0; //throw FileError + + //---------------------------------------------------------------------------------------------------------------- + //virtual void setModTime(const AfsPath& afsPath, time_t modTime) const = 0; //throw FileError, follows symlinks + + virtual AbstractPath getSymlinkResolvedPath(const AfsPath& afsPath) const = 0; //throw FileError + virtual std::string getSymlinkBinaryContent(const AfsPath& afsPath) const = 0; //throw FileError + //---------------------------------------------------------------------------------------------------------------- + virtual std::unique_ptr getInputStream(const AfsPath& afsPath, const zen::IOCallback& notifyUnbufferedIO /*throw X*/) const = 0; //throw FileError, ErrorFileLocked + + //target existing: undefined behavior! (fail/overwrite/auto-rename) + virtual std::unique_ptr getOutputStream(const AfsPath& afsPath, //throw FileError + std::optional streamSize, + std::optional modTime, + const zen::IOCallback& notifyUnbufferedIO /*throw X*/) const = 0; + //---------------------------------------------------------------------------------------------------------------- + virtual void traverseFolderRecursive(const TraverserWorkload& workload /*throw X*/, size_t parallelOps) const = 0; + //---------------------------------------------------------------------------------------------------------------- + virtual bool supportsPermissions(const AfsPath& afsPath) const = 0; //throw FileError + + //target existing: undefined behavior! (fail/overwrite/auto-rename) + virtual void moveAndRenameItemForSameAfsType(const AfsPath& pathFrom, const AbstractPath& pathTo) const = 0; //throw FileError, ErrorMoveUnsupported + + //symlink handling: follow link! + //target existing: undefined behavior! (fail/overwrite/auto-rename) + virtual FileCopyResult copyFileForSameAfsType(const AfsPath& afsPathSource, const StreamAttributes& attrSource, //throw FileError, ErrorFileLocked, X + const AbstractPath& apTarget, bool copyFilePermissions, + //accummulated delta != file size! consider ADS, sparse, compressed files + const zen::IOCallback& notifyUnbufferedIO /*throw X*/) const = 0; + + + //target existing: fail/ignore + //symlink handling: follow link! + virtual void copyNewFolderForSameAfsType(const AfsPath& afsPathSource, const AbstractPath& apTarget, bool copyFilePermissions) const = 0; //throw FileError + + virtual void copySymlinkForSameAfsType(const AfsPath& afsPathSource, const AbstractPath& apTarget, bool copyFilePermissions) const = 0; //throw FileError + + //---------------------------------------------------------------------------------------------------------------- + virtual zen::ImageHolder getFileIcon (const AfsPath& afsPath, int pixelSize) const = 0; //noexcept; optional return value + virtual zen::ImageHolder getThumbnailImage(const AfsPath& afsPath, int pixelSize) const = 0; // + + virtual void authenticateAccess(bool allowUserInteraction) const = 0; //throw FileError + + virtual int getAccessTimeout() const = 0; //returns "0" if no timeout in force + + virtual bool hasNativeTransactionalCopy() const = 0; + //---------------------------------------------------------------------------------------------------------------- + + virtual uint64_t getFreeDiskSpace(const AfsPath& afsPath) const = 0; //throw FileError, returns 0 if not available + virtual bool supportsRecycleBin(const AfsPath& afsPath) const = 0; //throw FileError + virtual std::unique_ptr createRecyclerSession(const AfsPath& afsPath) const = 0; //throw FileError, return value must be bound! + virtual void recycleItemIfExists(const AfsPath& afsPath) const = 0; //throw FileError +}; + + +inline bool operator< (const AfsDevice& lhs, const AfsDevice& rhs) { return AbstractFileSystem::compareDevice(lhs.ref(), rhs.ref()) < 0; } +inline bool operator==(const AfsDevice& lhs, const AfsDevice& rhs) { return AbstractFileSystem::compareDevice(lhs.ref(), rhs.ref()) == 0; } +inline bool operator!=(const AfsDevice& lhs, const AfsDevice& rhs) { return !(lhs == rhs); } + +inline bool operator< (const AbstractPath& lhs, const AbstractPath& rhs) { return AbstractFileSystem::comparePath(lhs, rhs) < 0; } +inline bool operator==(const AbstractPath& lhs, const AbstractPath& rhs) { return AbstractFileSystem::comparePath(lhs, rhs) == 0; } +inline bool operator!=(const AbstractPath& lhs, const AbstractPath& rhs) { return !(lhs == rhs); } + + + + + + + + +//------------------------------------ implementation ----------------------------------------- +inline +AbstractPath AbstractFileSystem::appendRelPath(const AbstractPath& ap, const Zstring& relPath) +{ + assert(isValidRelPath(relPath)); + return AbstractPath(ap.afsDevice, AfsPath(nativeAppendPaths(ap.afsPath.value, relPath))); +} + +//-------------------------------------------------------------------------- + +inline +AbstractFileSystem::OutputStream::OutputStream(std::unique_ptr&& outStream, const AbstractPath& filePath, std::optional streamSize) : + outStream_(std::move(outStream)), + filePath_(filePath), + bytesExpected_(streamSize) {} + + +inline +AbstractFileSystem::OutputStream::~OutputStream() +{ + using namespace zen; + + //we delete the file on errors: => file should not have existed prior to creating OutputStream instance!! + outStream_.reset(); //close file handle *before* remove! + + if (!finalizeSucceeded_) //transactional output stream! => clean up! + //even needed for Google Drive: e.g. user might cancel during OutputStreamImpl::finalize(), just after file was written transactionally + try { AbstractFileSystem::removeFilePlain(filePath_); /*throw FileError*/ } + catch (FileError&) {} +} + + +inline +void AbstractFileSystem::OutputStream::write(const void* data, size_t len) //throw FileError, X +{ + outStream_->write(data, len); //throw FileError, X + bytesWrittenTotal_ += len; +} + + +inline +AbstractFileSystem::FinalizeResult AbstractFileSystem::OutputStream::finalize() //throw FileError, X +{ + using namespace zen; + + //important check: catches corrupt SFTP download with libssh2! + if (bytesExpected_ && *bytesExpected_ != bytesWrittenTotal_) + throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getDisplayPath(filePath_))), //instead we should report the source file, but don't have it here... + replaceCpy(replaceCpy(_("Unexpected size of data stream.\nExpected: %x bytes\nActual: %y bytes"), + L"%x", numberTo(*bytesExpected_)), + L"%y", numberTo(bytesWrittenTotal_))); + + const FinalizeResult result = outStream_->finalize(); //throw FileError, X + finalizeSucceeded_ = true; + return result; +} + +//-------------------------------------------------------------------------- + +inline +bool AbstractFileSystem::supportPermissionCopy(const AbstractPath& apSource, const AbstractPath& apTarget) //throw FileError +{ + if (typeid(apSource.afsDevice.ref()) != typeid(apTarget.afsDevice.ref())) + return false; + + return apSource.afsDevice.ref().supportsPermissions(apSource.afsPath) && //throw FileError + apTarget.afsDevice.ref().supportsPermissions(apTarget.afsPath); +} + + +inline +void AbstractFileSystem::moveAndRenameItem(const AbstractPath& pathFrom, const AbstractPath& pathTo) //throw FileError, ErrorMoveUnsupported +{ + using namespace zen; + + if (typeid(pathFrom.afsDevice.ref()) == typeid(pathTo.afsDevice.ref())) + return pathFrom.afsDevice.ref().moveAndRenameItemForSameAfsType(pathFrom.afsPath, pathTo); //throw FileError, ErrorMoveUnsupported + + throw ErrorMoveUnsupported(replaceCpy(replaceCpy(_("Cannot move file %x to %y."), + L"%x", L"\n" + fmtPath(getDisplayPath(pathFrom))), + L"%y", L"\n" + fmtPath(getDisplayPath(pathTo))), _("Operation not supported between different devices.")); +} + + + +inline +void AbstractFileSystem::copyNewFolder(const AbstractPath& apSource, const AbstractPath& apTarget, bool copyFilePermissions) //throw FileError +{ + using namespace zen; + + if (typeid(apSource.afsDevice.ref()) == typeid(apTarget.afsDevice.ref())) + return apSource.afsDevice.ref().copyNewFolderForSameAfsType(apSource.afsPath, apTarget, copyFilePermissions); //throw FileError + + //fall back: + if (copyFilePermissions) + throw FileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(getDisplayPath(apTarget))), + _("Operation not supported between different devices.")); + + //already existing: fail/ignore + createFolderPlain(apTarget); //throw FileError +} + + +inline +void AbstractFileSystem::copySymlink(const AbstractPath& apSource, const AbstractPath& apTarget, bool copyFilePermissions) //throw FileError +{ + using namespace zen; + + if (typeid(apSource.afsDevice.ref()) == typeid(apTarget.afsDevice.ref())) + return apSource.afsDevice.ref().copySymlinkForSameAfsType(apSource.afsPath, apTarget, copyFilePermissions); //throw FileError + + throw FileError(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."), + L"%x", L"\n" + fmtPath(getDisplayPath(apSource))), + L"%y", L"\n" + fmtPath(getDisplayPath(apTarget))), _("Operation not supported between different devices.")); +} +} + +#endif //ABSTRACT_H_873450978453042524534234 diff --git a/FreeFileSync/Source/afs/abstract_impl.h b/FreeFileSync/Source/afs/abstract_impl.h new file mode 100644 index 00000000..7a175128 --- /dev/null +++ b/FreeFileSync/Source/afs/abstract_impl.h @@ -0,0 +1,274 @@ +// ***************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * +// ***************************************************************************** + +#ifndef IMPL_HELPER_H_873450978453042524534234 +#define IMPL_HELPER_H_873450978453042524534234 + +#include "abstract.h" +#include +#include + + +namespace fff +{ +inline +AfsPath sanitizeRootRelativePath(Zstring relPath) +{ + if constexpr (FILE_NAME_SEPARATOR != Zstr('/' )) replace(relPath, Zstr('/'), FILE_NAME_SEPARATOR); + if constexpr (FILE_NAME_SEPARATOR != Zstr('\\')) replace(relPath, Zstr('\\'), FILE_NAME_SEPARATOR); + trim(relPath, true, true, [](Zchar c) { return c == FILE_NAME_SEPARATOR; }); + return AfsPath(std::move(relPath)); +} + + +template inline //return ignored error message if available +std::wstring tryReportingDirError(Function cmd /*throw FileError*/, AbstractFileSystem::TraverserCallback& cb /*throw X*/) +{ + for (size_t retryNumber = 0;; ++retryNumber) + try + { + cmd(); //throw FileError + return std::wstring(); + } + catch (const zen::FileError& e) + { + assert(!e.toString().empty()); + switch (cb.reportDirError(e.toString(), retryNumber)) //throw X + { + case AbstractFileSystem::TraverserCallback::ON_ERROR_CONTINUE: + return e.toString(); + case AbstractFileSystem::TraverserCallback::ON_ERROR_RETRY: + break; //continue with loop + } + } +} + +template inline +bool tryReportingItemError(Command cmd, AbstractFileSystem::TraverserCallback& callback, const Zstring& itemName) //throw X, return "true" on success, "false" if error was ignored +{ + for (size_t retryNumber = 0;; ++retryNumber) + try + { + cmd(); //throw FileError + return true; + } + catch (const zen::FileError& e) + { + switch (callback.reportItemError(e.toString(), retryNumber, itemName)) //throw X + { + case AbstractFileSystem::TraverserCallback::ON_ERROR_RETRY: + break; + case AbstractFileSystem::TraverserCallback::ON_ERROR_CONTINUE: + return false; + } + } +} +//========================================================================================== + +/* +implement streaming API on top of libcurl's icky callback-based design + => support copying arbitrarily-large files: https://freefilesync.org/forum/viewtopic.php?t=4471 + => maximum performance through async processing (prefetching + output buffer!) + => cost per worker thread creation ~ 1/20 ms +*/ +class AsyncStreamBuffer +{ +public: + AsyncStreamBuffer(size_t bufferSize) : bufferSize_(bufferSize) { ringBuf_.reserve(bufferSize); } + + //context of output thread, blocking + void write(const void* buffer, size_t bytesToWrite) //throw + { + totalBytesWritten_ += bytesToWrite; //bytes already processed as far as raw FTP access is concerned + + auto it = static_cast(buffer); + const auto itEnd = it + bytesToWrite; + + for (std::unique_lock dummy(lockStream_); it != itEnd;) + { + //=> can't use InterruptibleThread's interruptibleWait() :( + // -> AsyncStreamBuffer is used for input and output streaming + // => both AsyncStreamBuffer::write()/read() would have to implement interruptibleWait() + // => one of these usually called from main thread + // => but interruptibleWait() cannot be called from main thread! + conditionBytesRead_.wait(dummy, [this] { return errorRead_ || ringBuf_.size() < bufferSize_; }); + + if (errorRead_) + std::rethrow_exception(errorRead_); //throw + + const size_t junkSize = std::min(static_cast(itEnd - it), bufferSize_ - ringBuf_.size()); + ringBuf_.insert_back(it, it + junkSize); + it += junkSize; + + conditionBytesWritten_.notify_all(); + } + } + + //context of output thread + void closeStream() + { + { + std::lock_guard dummy(lockStream_); + assert(!eof_); + eof_ = true; + } + conditionBytesWritten_.notify_all(); + } + + //context of input thread, blocking + //return "bytesToRead" bytes unless end of stream! + size_t read(void* buffer, size_t bytesToRead) //throw + { + if (bytesToRead == 0) //"read() with a count of 0 returns zero" => indistinguishable from end of file! => check! + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + zen::numberTo(__LINE__)); + + auto it = static_cast(buffer); + const auto itEnd = it + bytesToRead; + + for (std::unique_lock dummy(lockStream_); it != itEnd;) + { + conditionBytesWritten_.wait(dummy, [this] { return errorWrite_ || !ringBuf_.empty() || eof_; }); + + if (errorWrite_) + std::rethrow_exception(errorWrite_); //throw + + const size_t junkSize = std::min(static_cast(itEnd - it), ringBuf_.size()); + ringBuf_.extract_front(it, it + junkSize); + it += junkSize; + + conditionBytesRead_.notify_all(); + + if (eof_) //end of file + break; + } + + const size_t bytesRead = it - static_cast(buffer); + totalBytesRead_ += bytesRead; + return bytesRead; + } + + //context of input thread + void setReadError(const std::exception_ptr& error) + { + { + std::lock_guard dummy(lockStream_); + assert(!errorRead_); + if (!errorRead_) + errorRead_ = error; + } + conditionBytesRead_.notify_all(); + } + + //context of output thread + void setWriteError(const std::exception_ptr& error) + { + { + std::lock_guard dummy(lockStream_); + assert(!errorWrite_); + if (!errorWrite_) + errorWrite_ = error; + } + conditionBytesWritten_.notify_all(); + } + + //context of *output* thread + void checkReadErrors() //throw + { + std::lock_guard dummy(lockStream_); + if (errorRead_) + std::rethrow_exception(errorRead_); //throw + } + + // -> function not needed: when EOF is reached (without errors), reading is done => no further error can occur! + //void checkWriteErrors() //throw + //{ + // std::lock_guard dummy(lockStream_); + // if (errorWrite_) + // std::rethrow_exception(errorWrite_); //throw + //} + + uint64_t getTotalBytesWritten() const { return totalBytesWritten_; } + uint64_t getTotalBytesRead () const { return totalBytesRead_; } + +private: + AsyncStreamBuffer (const AsyncStreamBuffer&) = delete; + AsyncStreamBuffer& operator=(const AsyncStreamBuffer&) = delete; + + const size_t bufferSize_; + std::mutex lockStream_; + zen::RingBuffer ringBuf_; //prefetch/output buffer + bool eof_ = false; + std::exception_ptr errorWrite_; + std::exception_ptr errorRead_; + std::condition_variable conditionBytesWritten_; + std::condition_variable conditionBytesRead_; + + std::atomic totalBytesWritten_{ 0 }; //std:atomic is uninitialized by default! + std::atomic totalBytesRead_ { 0 }; // +}; + +//========================================================================================== + +//Google Drive/MTP happily create duplicate files/folders with the same names, without failing +//=> however, FFS's "check if already exists after failure" idiom *requires* failure +//=> serialize access (at path level) so that GoogleFileState access and file/folder creation act as a single operation +template +class PathAccessLocker +{ +public: + PathAccessLocker() {} + + class Lock + { + public: + Lock(const NativePath& nativePath) //throw SysError + { + { + const std::shared_ptr gpalh = getGlobalInstance(); //throw SysError + if (!gpalh) + throw zen::SysError(L"PathAccessLocker::Lock() function call not allowed during init/shutdown."); + m_ = gpalh->getOrCreateMutex(nativePath); + } + m_->lock(); + } + ~Lock() { m_->unlock(); } + + private: + Lock (const Lock&) = delete; + Lock& operator=(const Lock&) = delete; + + std::shared_ptr m_; + }; + +private: + PathAccessLocker (const PathAccessLocker&) = delete; + PathAccessLocker& operator=(const PathAccessLocker&) = delete; + + static std::shared_ptr getGlobalInstance(); + + std::shared_ptr getOrCreateMutex(const NativePath& nativePath) + { + std::shared_ptr m; + pathLocks_.access([&](std::map>& pathLocks) + { + //remove obsolete entries + zen::eraseIf(pathLocks, [](const auto& v) { return !v.second.lock(); }); + + //get or create mutex + std::weak_ptr& weakPtr = pathLocks[nativePath]; + m = weakPtr.lock(); + if (!m) + weakPtr = m = std::make_shared(); + }); + return m; + } + + zen::Protected>> pathLocks_; +}; + +} + +#endif //IMPL_HELPER_H_873450978453042524534234 diff --git a/FreeFileSync/Source/afs/concrete.cpp b/FreeFileSync/Source/afs/concrete.cpp new file mode 100644 index 00000000..b78f14da --- /dev/null +++ b/FreeFileSync/Source/afs/concrete.cpp @@ -0,0 +1,48 @@ +// ***************************************************************************** +// * 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 "concrete.h" +#include "native.h" +#include "ftp.h" +#include "sftp.h" +#include "gdrive.h" + +using namespace fff; + + +void fff::initAfs(const AfsConfig& cfg) +{ + googleDriveInit(appendSeparator(cfg.configDirPathPf) + Zstr("GoogleDrive"), + appendSeparator(cfg.resourceDirPathPf) + Zstr("Misc") + FILE_NAME_SEPARATOR + Zstr("cacert.pem")); +} + + +void fff::teardownAfs() +{ + googleDriveTeardown(); +} + + +AbstractPath fff::createAbstractPath(const Zstring& itemPathPhrase) //noexcept +{ + //greedy: try native evaluation first + if (acceptsItemPathPhraseNative(itemPathPhrase)) //noexcept + return createItemPathNative(itemPathPhrase); //noexcept + + //then the rest: + if (acceptsItemPathPhraseFtp(itemPathPhrase)) //noexcept + return createItemPathFtp(itemPathPhrase); //noexcept + + if (acceptsItemPathPhraseSftp(itemPathPhrase)) //noexcept + return createItemPathSftp(itemPathPhrase); //noexcept + + if (acceptsItemPathPhraseGdrive(itemPathPhrase)) //noexcept + return createItemPathGdrive(itemPathPhrase); //noexcept + + + //no idea? => native! + return createItemPathNative(itemPathPhrase); +} diff --git a/FreeFileSync/Source/afs/concrete.h b/FreeFileSync/Source/afs/concrete.h new file mode 100644 index 00000000..3ff507dd --- /dev/null +++ b/FreeFileSync/Source/afs/concrete.h @@ -0,0 +1,25 @@ +// ***************************************************************************** +// * 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 FS_CONCRETE_348787329573243 +#define FS_CONCRETE_348787329573243 + +#include "abstract.h" + +namespace fff +{ +struct AfsConfig +{ + Zstring resourceDirPathPf; //directory to read AFS-specific files + Zstring configDirPathPf; //directory to store AFS-specific files +}; +void initAfs(const AfsConfig& cfg); +void teardownAfs(); + +AbstractPath createAbstractPath(const Zstring& itemPathPhrase); //noexcept +} + +#endif //FS_CONCRETE_348787329573243 diff --git a/FreeFileSync/Source/afs/ftp.cpp b/FreeFileSync/Source/afs/ftp.cpp new file mode 100644 index 00000000..554093bf --- /dev/null +++ b/FreeFileSync/Source/afs/ftp.cpp @@ -0,0 +1,2246 @@ +// ***************************************************************************** +// * 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 "ftp.h" +#include +#include +#include +#include +#include "libcurl/curl_wrap.h" //DON'T include directly! +#include "init_curl_libssh2.h" +#include "ftp_common.h" +#include "abstract_impl.h" +#include "../base/resolve_path.h" + #include + +using namespace zen; +using namespace fff; +using AFS = AbstractFileSystem; + + +namespace +{ +Zstring concatenateFtpFolderPathPhrase(const FtpLoginInfo& login, const AfsPath& afsPath); //noexcept + +const std::chrono::seconds FTP_SESSION_MAX_IDLE_TIME (20); +const std::chrono::seconds FTP_SESSION_CLEANUP_INTERVAL(4); +const int FTP_STREAM_BUFFER_SIZE = 512 * 1024; //unit: [byte] +//FTP stream buffer should be at least as big as the biggest AFS block size (currently 256 KB for MTP), +//but there seems to be no reason for an upper limit + +const Zchar ftpPrefix[] = Zstr("ftp:"); + +enum class ServerEncoding +{ + utf8, + ansi +}; + +//use all configuration data that *defines* an SFTP session as key when buffering sessions! This is what user expects, e.g. when changing settings in FTP login dialog +struct FtpSessionId +{ + /*explicit*/ FtpSessionId(const FtpLoginInfo& login) : + server(login.server), + port(login.port), + username(login.username), + password(login.password), + useSsl(login.useSsl) {} + + Zstring server; + int port = 0; + Zstring username; + Zstring password; + bool useSsl = false; + //timeoutSec => irrelevant for session equality +}; + + +bool operator<(const FtpSessionId& lhs, const FtpSessionId& rhs) +{ + //exactly the type of case insensitive comparison we need for server names! + int rv = compareAsciiNoCase(lhs.server, rhs.server); //https://msdn.microsoft.com/en-us/library/windows/desktop/ms738519#IDNs + if (rv != 0) + return rv < 0; + + if (lhs.port != rhs.port) + return lhs.port < rhs.port; + + rv = compareString(lhs.username, rhs.username); //case sensitive! + if (rv != 0) + return rv < 0; + + rv = compareString(lhs.password, rhs.password); //case sensitive! + if (rv != 0) + return rv < 0; + + return lhs.useSsl < rhs.useSsl; +} + + +Zstring ansiToUtfEncoding(const std::string& str) //throw SysError +{ + gsize bytesWritten = 0; //not including the terminating null + + GError* error = nullptr; + ZEN_ON_SCOPE_EXIT(if (error) ::g_error_free(error);); + + //https://developer.gnome.org/glib/stable/glib-Character-Set-Conversion.html#g-convert + gchar* utfStr = ::g_convert(str.c_str(), //const gchar* str, + str.size(), //gssize len, + "UTF-8", //const gchar* to_codeset, + "LATIN1", //const gchar* from_codeset, + nullptr, //gsize* bytes_read, + &bytesWritten, //gsize* bytes_written, + &error); //GError** error + if (!utfStr) + { + if (!error) + throw SysError(L"g_convert: unknown error. (" + utfTo(str) + L")"); //user should never see this + + throw SysError(formatSystemError(L"g_convert", replaceCpy(_("Error Code %x"), L"%x", numberTo(error->code)), + utfTo(error->message)) + L" (" + utfTo(str) + L")"); + } + ZEN_ON_SCOPE_EXIT(::g_free(utfStr)); + + return { utfStr, bytesWritten }; + + +} + + +std::string utfToAnsiEncoding(const Zstring& str) //throw SysError +{ + gsize bytesWritten = 0; //not including the terminating null + + GError* error = nullptr; + ZEN_ON_SCOPE_EXIT(if (error) ::g_error_free(error);); + + gchar* ansiStr = ::g_convert(str.c_str(), //const gchar* str, + str.size(), //gssize len, + "LATIN1", //const gchar* to_codeset, + "UTF-8", //const gchar* from_codeset, + nullptr, //gsize* bytes_read, + &bytesWritten, //gsize* bytes_written, + &error); //GError** error + if (!ansiStr) + { + if (!error) + throw SysError(L"g_convert: unknown error. (" + utfTo(str) + L")"); //user should never see this + + throw SysError(formatSystemError(L"g_convert", replaceCpy(_("Error Code %x"), L"%x", numberTo(error->code)), + utfTo(error->message)) + L" (" + utfTo(str) + L")"); + } + ZEN_ON_SCOPE_EXIT(::g_free(ansiStr)); + + return { ansiStr, bytesWritten }; + +} + + +Zstring serverToUtfEncoding(const std::string& str, ServerEncoding enc) //throw SysError +{ + switch (enc) + { + case ServerEncoding::utf8: + return utfTo(str); + case ServerEncoding::ansi: + return ansiToUtfEncoding(str); //throw SysError + } + assert(false); + return {}; +} + + +std::string utfToServerEncoding(const Zstring& str, ServerEncoding enc) //throw SysError +{ + switch (enc) + { + case ServerEncoding::utf8: + return utfTo(str); + case ServerEncoding::ansi: + return utfToAnsiEncoding(str); //throw SysError + } + assert(false); + return {}; +} + + +std::wstring getCurlDisplayPath(const Zstring& serverName, const AfsPath& afsPath) +{ + Zstring displayPath = Zstring(ftpPrefix) + Zstr("//") + serverName; + const Zstring relPath = getServerRelPath(afsPath); + if (relPath != Zstr("/")) + displayPath += relPath; + return utfTo(displayPath); +} + + +std::vector splitFtpResponse(const std::string& buf) +{ + std::vector lines; + + std::string lineBuf; + auto flushLineBuf = [&] + { + if (!lineBuf.empty()) + { + lines.push_back(lineBuf); + lineBuf.clear(); + } + }; + for (const char c : buf) + if (c == '\r' || c == '\n' || c == '\0') + flushLineBuf(); + else + lineBuf += c; + + flushLineBuf(); + return lines; +} + + +class FtpLineParser +{ +public: + FtpLineParser(const std::string& line) : line_(line), it_(line_.begin()) {} + + template + std::string readRange(size_t count, Function acceptChar) //throw SysError + { + if (static_cast(count) > line_.end() - it_) + throw SysError(L"Unexpected end of line."); + + if (!std::all_of(it_, it_ + count, acceptChar)) + throw SysError(L"Expected char type not found."); + + std::string output(it_, it_ + count); + it_ += count; + return output; + } + + template //expects non-empty range! + std::string readRange(Function acceptChar) //throw SysError + { + auto itEnd = std::find_if(it_, line_.end(), std::not_fn(acceptChar)); + std::string output(it_, itEnd); + if (output.empty()) + throw SysError(L"Expected char range not found."); + it_ = itEnd; + return output; + } + + char peekNextChar() const { return it_ == line_.end() ? '\0' : *it_; } + +private: + const std::string line_; + std::string::const_iterator it_; +}; + +//---------------------------------------------------------------------------------------------------------------- + +std::wstring tryFormatFtpErrorCode(int ec) //https://en.wikipedia.org/wiki/List_of_FTP_server_return_codes +{ + if (ec == 400) return L"The command was not accepted but the error condition is temporary."; + if (ec == 421) return L"Service not available, closing control connection."; + if (ec == 425) return L"Cannot open data connection."; + if (ec == 426) return L"Connection closed; transfer aborted."; + if (ec == 430) return L"Invalid username or password."; + if (ec == 431) return L"Need some unavailable resource to process security."; + if (ec == 434) return L"Requested host unavailable."; + if (ec == 450) return L"Requested file action not taken."; + if (ec == 451) return L"Local error in processing."; + if (ec == 452) return L"Insufficient storage space in system. File unavailable, e.g. file busy."; + if (ec == 500) return L"Syntax error, command unrecognized or command line too long."; + if (ec == 501) return L"Syntax error in parameters or arguments."; + if (ec == 502) return L"Command not implemented."; + if (ec == 503) return L"Bad sequence of commands."; + if (ec == 504) return L"Command not implemented for that parameter."; + if (ec == 521) return L"Data connection cannot be opened with this PROT setting."; + if (ec == 522) return L"Server does not support the requested network protocol."; + if (ec == 530) return L"User not logged in."; + if (ec == 532) return L"Need account for storing files."; + if (ec == 533) return L"Command protection level denied for policy reasons."; + if (ec == 534) return L"Could not connect to server; issue regarding SSL."; + if (ec == 535) return L"Failed security check."; + if (ec == 536) return L"Requested PROT level not supported by mechanism."; + if (ec == 537) return L"Command protection level not supported by security mechanism."; + if (ec == 550) return L"File unavailable, e.g. file not found, no access."; + if (ec == 551) return L"Requested action aborted. Page type unknown."; + if (ec == 552) return L"Requested file action aborted. Exceeded storage allocation."; + if (ec == 553) return L"File name not allowed."; + return L""; +} + +//================================================================================================================ +//================================================================================================================ + +Global globalFtpSessionCount(createUniSessionCounter()); + + +class FtpSession +{ +public: + FtpSession(const FtpSessionId& sessionId) : //throw SysError + sessionId_(sessionId), + libsshCurlUnifiedInitCookie_(getLibsshCurlUnifiedInitCookie(globalFtpSessionCount)), //throw SysError + lastSuccessfulUseTime_(std::chrono::steady_clock::now()) {} + + ~FtpSession() + { + if (easyHandle_) + ::curl_easy_cleanup(easyHandle_); + } + + //const FtpLoginInfo& getSessionId() const { return sessionId_; } + + struct Option + { + template + Option(CURLoption o, T val) : option(o), value(static_cast(val)) { static_assert(sizeof(val) <= sizeof(value)); } + + template + Option(CURLoption o, T* val) : option(o), value(reinterpret_cast(val)) { static_assert(sizeof(val) <= sizeof(value)); } + + CURLoption option = CURLOPT_LASTENTRY; + uint64_t value = 0; + }; + + //returns server response (header data) + std::string perform(const AfsPath* afsPath /*optional, use last-used path if null*/, bool isDir, + const std::vector