summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xBugs.txt175
-rwxr-xr-xChangelog.txt22
-rwxr-xr-xFreeFileSync/Build/Resources/Icons.zipbin288396 -> 338833 bytes
-rwxr-xr-xFreeFileSync/Build/Resources/Languages/german.lng120
-rwxr-xr-xFreeFileSync/Build/Resources/cacert.pem35
-rw-r--r--FreeFileSync/Source/afs/abstract.h1
-rw-r--r--FreeFileSync/Source/afs/ftp.cpp429
-rw-r--r--FreeFileSync/Source/afs/gdrive.cpp129
-rw-r--r--FreeFileSync/Source/afs/libcurl/curl_wrap.h9
-rw-r--r--FreeFileSync/Source/afs/sftp.cpp109
-rw-r--r--FreeFileSync/Source/base/db_file.cpp22
-rw-r--r--FreeFileSync/Source/base/dir_lock.cpp48
-rw-r--r--FreeFileSync/Source/base/dir_lock.h10
-rw-r--r--FreeFileSync/Source/base/lock_holder.h11
-rw-r--r--FreeFileSync/Source/base/parallel_scan.cpp4
-rw-r--r--FreeFileSync/Source/base/resolve_path.cpp3
-rw-r--r--FreeFileSync/Source/base/synchronization.cpp2
-rw-r--r--FreeFileSync/Source/ui/gui_generated.cpp117
-rw-r--r--FreeFileSync/Source/ui/gui_generated.h41
-rw-r--r--FreeFileSync/Source/ui/main_dlg.cpp1
-rw-r--r--FreeFileSync/Source/ui/progress_indicator.cpp82
-rw-r--r--FreeFileSync/Source/ui/small_dlgs.cpp60
-rw-r--r--FreeFileSync/Source/ui/small_dlgs.h2
-rw-r--r--FreeFileSync/Source/version/version.h2
-rw-r--r--wx+/dc.h25
-rw-r--r--wx+/graph.cpp16
-rw-r--r--wx+/graph.h8
-rw-r--r--zen/http.cpp97
-rw-r--r--zen/http.h1
-rw-r--r--zen/json.h101
-rw-r--r--zen/shell_execute.h2
-rw-r--r--zen/zlib_wrap.cpp44
-rw-r--r--zen/zlib_wrap.h33
-rw-r--r--zenXml/zenxml/parser.h46
34 files changed, 1113 insertions, 694 deletions
diff --git a/Bugs.txt b/Bugs.txt
index 1cec865e..551614a0 100755
--- a/Bugs.txt
+++ b/Bugs.txt
@@ -2,22 +2,72 @@ When manually compiling FreeFileSync, you should also fix the following bugs in
----------------
-| libssh2 Bugs |
+| libcurl Bugs |
----------------
__________________________________________________________________________________________________________
-Amazons SFTP server returns legitimate package sizes of ~100kb!
-https://freefilesync.org/forum/viewtopic.php?t=5999
+/lib/setopt.c
+https://github.com/curl/curl/pull/4321
-/src/sftp.c:
+- if ((arg < CURLFTPMETHOD_DEFAULT) || (arg > CURLFTPMETHOD_SINGLECWD))
++ if ((arg < CURLFTPMETHOD_DEFAULT) || (arg >= CURLFTPMETHOD_LAST))
-- #define LIBSSH2_SFTP_PACKET_MAXLEN 80000
-+ #define LIBSSH2_SFTP_PACKET_MAXLEN 160000
__________________________________________________________________________________________________________
+https://github.com/curl/curl/pull/4331
+/include/curl/curl.h
+
+ CURLFTPMETHOD_SINGLECWD, /* one CWD to full dir, then work on file */
++ CURLFTPMETHOD_FULLPATH, //AKA "CURLFTPMETHOD_NOCWD_BUT_THIS_TIME_FOR_REAL"
+
+
+/lib/ftp.h
+
+ FTPFILE_SINGLECWD = 3 /* make one CWD, then SIZE / RETR / STOR on the
+ file */
++ FTPFILE_FULLPATH = 4 //AKA "FTPFILE_NOCWD_BUT_THIS_TIME_FOR_REAL"
+
+
+/lib/ftp.c
+
+- if ((data->set.ftp_filemethod == FTPFILE_NOCWD) &&
++ if ((data->set.ftp_filemethod == FTPFILE_NOCWD ||
++ data->set.ftp_filemethod == FTPFILE_FULLPATH) &&
+
+
++ if (data->set.ftp_filemethod == FTPFILE_FULLPATH)
++ {
++ //no CWDs happened => remember old working dir
++ //ftpc->prevmethod =
++ //ftpc->prevpath =
++ }
++ else
++ {
+ /* now store a copy of the directory we are in */
+ free(ftpc->prevpath);
+
+ [...]
+
+ else
+ {
+ ftpc->prevpath = NULL; /* no path */
+ free(path);
+ }
+ }
+
++ }
+
+
+ switch (data->set.ftp_filemethod)
+ {
+ case FTPFILE_NOCWD:
++ case FTPFILE_FULLPATH:
+
+
++ if (data->set.ftp_filemethod == FTPFILE_FULLPATH)
++ ftpc->cwddone = TRUE;
++ else
+ if (ftpc->prevpath)
-----------------
-| libcurl Bugs |
-----------------
__________________________________________________________________________________________________________
https://github.com/curl/curl/issues/1455
@@ -51,63 +101,53 @@ Replace with:
}
if (skipIp)
+
__________________________________________________________________________________________________________
+/lib/ftp.c
+https://github.com/curl/curl/pull/4332
-"wrong dir listing because libcurl remembers wrong CWD": https://github.com/curl/curl/issues/1782
+- else if (conn->bits.reuse && ftpc->entrypath)
++ else if (conn->bits.reuse && ftpc->entrypath &&
++ !(ftpc->dirdepth && ftpc->dirs[0][0] == '/')) //no need to go to entrypath when we have an absolute path
-=> "fixed" by adding only the "if (data->set.ftp_filemethod == FTPFILE_NOCWD)" below: https://github.com/curl/curl/issues/1811
-=> this is NOT enough! consider what happens for a reused connection that first used CURLFTPMETHOD_MULTICWD, now CURLFTPMETHOD_NOCWD:
-
- the code in ftp_state_cwd() will issue a CWD sequence that ends with "ftpc->cwdcount == 1"!!! See "if (++ftpc->cwdcount <= ftpc->dirdepth)"
- => this skips the previous "fix" in https://github.com/curl/curl/issues/1718 with
- if ((conn->data->set.ftp_filemethod == FTPFILE_NOCWD) && !ftpc->cwdcount)
+__________________________________________________________________________________________________________
+/lib/ftp.c
+https://github.com/curl/curl/pull/4348
-/lib/ftp.c:
- if (ftpc->prevpath)
- {
-+ if (data->set.ftp_filemethod == FTPFILE_NOCWD)
-+ {
-+ /*
-+ CURLFTPMETHOD_NOCWD
-+ if the connection is used for the first time, *no* CWD takes place
-+ if the connection is reused, ftp_state_cwd() issues a single "CWD ftpc->entrypath" before the operation
-+ in both cases ftp_done() sets ftpc->prevpath to "" after a successfull FTP operation
-+ ergo: "" corresponds to ftpc->entrypath, so we only ever need CWD if ftpc->prevpath != ""
-+ => avoid needless "CWD /" and reduce folder traversal time with CURLFTPMETHOD_NOCWD by 15-20%
-+ */
-+ if (strcmp(ftpc->prevpath, "") == 0)
-+ {
-+ infof(data, "Request has same path (\"%s\") as previous transfer\n", ftpc->prevpath);
-+ ftpc->cwddone = TRUE;
-+ }
-+ }
-+ else
-+ {
- /* prevpath is "raw" so we convert the input path before we compare the
- strings */
- size_t dlen;
- char* path;
- CURLcode result =
- Curl_urldecode(conn->data, data->state.path, 0, &path, &dlen, FALSE);
- if (result)
- {
- freedirs(ftpc);
- return result;
- }
-
- dlen -= ftpc->file?strlen(ftpc->file):0;
- if ((dlen == strlen(ftpc->prevpath)) &&
- !strncmp(path, ftpc->prevpath, dlen) &&
-- (ftpc->prevmethod == data->set.ftp_filemethod))
-+ true) //(ftpc->prevmethod == data->set.ftp_filemethod))
- {
- infof(data, "Request has same path as previous transfer\n");
- ftpc->cwddone = TRUE;
- }
- free(path);
-+ }
- }
+- size_t n = strlen(inpath);
+- /* Check if path does not end with /, as then we cut off the file part */
+- if (inpath[n - 1] != '/')
+- {
+- /* chop off the file part if format is dir/dir/file */
+- slashPos = strrchr(inpath, '/');
+- n = slashPos - inpath;
+- }
+
++ /* chop off the file part if format is dir/file
++ otherwise remove the trailing slash for dir/dir/
++ and full paths like %2f/ except for / */
++ size_t n = strrchr(inpath, '/') - inpath;
++ if(n == 0)
++ ++n;
+__________________________________________________________________________________________________________
+
+/lib/vtls/openssl.c
+https://github.com/curl/curl/issues/4329
+
+ case SSL_ERROR_ZERO_RETURN: /* no more data */
+- /* close_notify alert */
+- connclose(conn, "TLS close_notify");
+__________________________________________________________________________________________________________
+
+
+----------------
+| libssh2 Bugs |
+----------------
+__________________________________________________________________________________________________________
+move the following constants from src/sftp.h to include/libssh2_sftp.h:
+ #define MAX_SFTP_OUTGOING_SIZE 30000
+ #define MAX_SFTP_READ_SIZE 30000
__________________________________________________________________________________________________________
@@ -215,3 +255,16 @@ Backspace not working in filter dialog: http://www.freefilesync.org/forum/viewto
+// g_source_attach(source, NULL);
+// }
__________________________________________________________________________________________________________
+
+wxWidgets/GTK2 on some Linux systems incorrectly detects high DPI: https://freefilesync.org/forum/viewtopic.php?t=6114
+=> hack away high-DPI support for GTK2 (= pretend GTK2 has device independent pixels, which it clearly has not!)
+
+/include/wx/window.h:
+
+ #include "wx/gtk/window.h"
+- #ifdef __WXGTK3__
++ //#ifdef __WXGTK3__
+ #define wxHAVE_DPI_INDEPENDENT_PIXELS
+- #endif
++ //#endif
+__________________________________________________________________________________________________________
diff --git a/Changelog.txt b/Changelog.txt
index 74415e95..9d1713f4 100755
--- a/Changelog.txt
+++ b/Changelog.txt
@@ -1,3 +1,25 @@
+FreeFileSync 10.16 [2019-09-16]
+-------------------------------
+Redesigned progress indicator graphs
+Avoid needless HTTP delay prior to Google Drive upload
+Skip redundant CWDs during FTP metadata updates
+Fixed MLSD 501 syntax error on Serv-U FTP server
+Check FTP server status using home directory instead of root
+Avoid redundant TYPE changes during FTP directory listing
+Access FTP files by full path and avoid CWDs
+Support FTP home paths with non-ASCII chars
+Workaround libcurl bug failing to buffer FTP TLS authentication
+Skip redundant FTP SIZE check before downloading file
+Use ISO 8601 week of the year definition for %week% macro
+Show login prompt for disconnected NAS share
+Force icon resolution to 96 DPI in GTK2 build (Linux)
+Detect missing full disk access permission (macOS)
+Fixed accessibility issue due to graph color inconsistency
+Use short naming convention when deleting abandoned folder lock
+Detect endless folder lock recursion on buggy file systems
+Fixed Google Drive parsing error for invalid file time
+
+
FreeFileSync 10.15 [2019-08-15]
-------------------------------
Redesigned progress indicator stats
diff --git a/FreeFileSync/Build/Resources/Icons.zip b/FreeFileSync/Build/Resources/Icons.zip
index af35c0b3..3c78d303 100755
--- a/FreeFileSync/Build/Resources/Icons.zip
+++ b/FreeFileSync/Build/Resources/Icons.zip
Binary files differ
diff --git a/FreeFileSync/Build/Resources/Languages/german.lng b/FreeFileSync/Build/Resources/Languages/german.lng
index 45c61939..47ae6f4e 100755
--- a/FreeFileSync/Build/Resources/Languages/german.lng
+++ b/FreeFileSync/Build/Resources/Languages/german.lng
@@ -387,12 +387,12 @@ Tatsächlich: %y bytes
<source>Database file is corrupted:</source>
<target>Die Datenbankdatei ist beschädigt:</target>
-<source>The database files do not yet contain information about the last synchronization.</source>
-<target>Die Datenbankdateien beinhalten noch keine Informationen zur letzten Synchronisation.</target>
-
<source>Loading file %x...</source>
<target>Lade Datei %x...</target>
+<source>The database files do not yet contain information about the last synchronization.</source>
+<target>Die Datenbankdateien beinhalten noch keine Informationen zur letzten Synchronisation.</target>
+
<source>Saving file %x...</source>
<target>Speichere Datei %x...</target>
@@ -477,6 +477,27 @@ Tatsächlich: %y bytes
<source>Update attributes on right</source>
<target>Aktualisiere Attribute des rechten Elements</target>
+<source>Error parsing file %x, row %y, column %z.</source>
+<target>Fehler beim Auswerten der Datei %x, Zeile %y, Spalte %z.</target>
+
+<source>Services</source>
+<target>Dienste</target>
+
+<source>Show All</source>
+<target>Alle einblenden</target>
+
+<source>Hide Others</source>
+<target>Andere ausblenden</target>
+
+<source>Hide %x</source>
+<target>%x ausblenden</target>
+
+<source>Quit %x</source>
+<target>%x beenden</target>
+
+<source>Cannot set directory locks for the following folders:</source>
+<target>Die Verzeichnissperren können für die folgenden Ordner nicht gesetzt werden:</target>
+
<source>Errors:</source>
<target>Fehler:</target>
@@ -501,27 +522,6 @@ Tatsächlich: %y bytes
<source>Cleaning up log files:</source>
<target>Bereinige Protokolldateien:</target>
-<source>Error parsing file %x, row %y, column %z.</source>
-<target>Fehler beim Auswerten der Datei %x, Zeile %y, Spalte %z.</target>
-
-<source>Services</source>
-<target>Dienste</target>
-
-<source>Show All</source>
-<target>Alle einblenden</target>
-
-<source>Hide Others</source>
-<target>Andere ausblenden</target>
-
-<source>Hide %x</source>
-<target>%x ausblenden</target>
-
-<source>Quit %x</source>
-<target>%x beenden</target>
-
-<source>Cannot set directory locks for the following folders:</source>
-<target>Die Verzeichnissperren können für die folgenden Ordner nicht gesetzt werden:</target>
-
<source>
<pluralform>1 thread</pluralform>
<pluralform>%x threads</pluralform>
@@ -1441,6 +1441,21 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert.
<source>Highlight configurations that have not been run for more than the following number of days:</source>
<target>Konfigurationen hervorheben, die seit mehr als die folgende Anzahl an Tagen nicht mehr ausgeführt wurden:</target>
+<source>FreeFileSync requires access rights to avoid "Operation not permitted" errors when synchronizing your data (e.g. Mail, Messages, Calendars).</source>
+<target>FreeFileSync benötigt Zugriffsrechte, um "Operation nicht erlaubt" Fehler bei der Synchronisation deiner Daten (z.B. E-Mail, Nachrichten, Kalender) zu vermeiden.</target>
+
+<source>Locate the FreeFileSync app</source>
+<target>Die FreeFileSync App finden</target>
+
+<source>Open Security && Privacy</source>
+<target>Systemeinstellungen/Sicherheit öffnen</target>
+
+<source>Click the lock to allow changes.</source>
+<target>Das Schloss anklicken und Änderungen erlauben.</target>
+
+<source>Drag FreeFileSync into the panel.</source>
+<target>FreeFileSync in das Fenster ziehen.</target>
+
<source>Synchronization Settings</source>
<target>Synchronisationseinstellungen</target>
@@ -1465,6 +1480,9 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert.
<source>Highlight Configurations</source>
<target>Konfigurationen hervorheben</target>
+<source>Grant Full Disk Access</source>
+<target>Festplattenvollzugriff gewähren</target>
+
<source>Info</source>
<target>Info</target>
@@ -1525,33 +1543,6 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert.
<source>&Execute</source>
<target>&Ausführen</target>
-<source>
-<pluralform>1 directory</pluralform>
-<pluralform>%x directories</pluralform>
-</source>
-<target>
-<pluralform>1 Verzeichnis</pluralform>
-<pluralform>%x Verzeichnisse</pluralform>
-</target>
-
-<source>
-<pluralform>1 file</pluralform>
-<pluralform>%x files</pluralform>
-</source>
-<target>
-<pluralform>1 Datei</pluralform>
-<pluralform>%x Dateien</pluralform>
-</target>
-
-<source>
-<pluralform>Showing %y of 1 row</pluralform>
-<pluralform>Showing %y of %x rows</pluralform>
-</source>
-<target>
-<pluralform>Zeige %y von 1 Zeile</pluralform>
-<pluralform>Zeige %y von %x Zeilen</pluralform>
-</target>
-
<source>Set direction:</source>
<target>Setze Richtung:</target>
@@ -1690,6 +1681,33 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert.
<source>All files are in sync</source>
<target>Alle Dateien sind synchron</target>
+<source>
+<pluralform>1 directory</pluralform>
+<pluralform>%x directories</pluralform>
+</source>
+<target>
+<pluralform>1 Verzeichnis</pluralform>
+<pluralform>%x Verzeichnisse</pluralform>
+</target>
+
+<source>
+<pluralform>1 file</pluralform>
+<pluralform>%x files</pluralform>
+</source>
+<target>
+<pluralform>1 Datei</pluralform>
+<pluralform>%x Dateien</pluralform>
+</target>
+
+<source>
+<pluralform>Showing %y of 1 row</pluralform>
+<pluralform>Showing %y of %x rows</pluralform>
+</source>
+<target>
+<pluralform>Zeige %y von 1 Zeile</pluralform>
+<pluralform>Zeige %y von %x Zeilen</pluralform>
+</target>
+
<source>Cannot find %x</source>
<target>%x wurde nicht gefunden.</target>
diff --git a/FreeFileSync/Build/Resources/cacert.pem b/FreeFileSync/Build/Resources/cacert.pem
index 8e92f772..65be2181 100755
--- a/FreeFileSync/Build/Resources/cacert.pem
+++ b/FreeFileSync/Build/Resources/cacert.pem
@@ -1,7 +1,7 @@
##
## Bundle of CA Root Certificates
##
-## Certificate data from Mozilla as of: Wed May 15 03:12:09 2019 GMT
+## Certificate data from Mozilla as of: Wed Aug 28 03:12:10 2019 GMT
##
## This is a bundle of X.509 certificates of public Certificate Authorities
## (CA). These were automatically extracted from Mozilla's root certificates
@@ -14,7 +14,7 @@
## Just configure this file as the SSLCACertificateFile.
##
## Conversion done with mk-ca-bundle.pl version 1.27.
-## SHA256: 61eaa79ac46d923f2f74dfe401189424e96fa8736102b47ba2cdb4ea19af2cc8
+## SHA256: fffa309937c3be940649293f749b8207fabc6eb224e50e4bb3f2c5e44e0d6a6b
##
@@ -2613,37 +2613,6 @@ kbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+ZAAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3C
ekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su
-----END CERTIFICATE-----
-Certinomis - Root CA
-====================
------BEGIN CERTIFICATE-----
-MIIFkjCCA3qgAwIBAgIBATANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJGUjETMBEGA1UEChMK
-Q2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxHTAbBgNVBAMTFENlcnRpbm9taXMg
-LSBSb290IENBMB4XDTEzMTAyMTA5MTcxOFoXDTMzMTAyMTA5MTcxOFowWjELMAkGA1UEBhMCRlIx
-EzARBgNVBAoTCkNlcnRpbm9taXMxFzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMR0wGwYDVQQDExRD
-ZXJ0aW5vbWlzIC0gUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANTMCQos
-P5L2fxSeC5yaah1AMGT9qt8OHgZbn1CF6s2Nq0Nn3rD6foCWnoR4kkjW4znuzuRZWJflLieY6pOo
-d5tK8O90gC3rMB+12ceAnGInkYjwSond3IjmFPnVAy//ldu9n+ws+hQVWZUKxkd8aRi5pwP5ynap
-z8dvtF4F/u7BUrJ1Mofs7SlmO/NKFoL21prbcpjp3vDFTKWrteoB4owuZH9kb/2jJZOLyKIOSY00
-8B/sWEUuNKqEUL3nskoTuLAPrjhdsKkb5nPJWqHZZkCqqU2mNAKthH6yI8H7KsZn9DS2sJVqM09x
-RLWtwHkziOC/7aOgFLScCbAK42C++PhmiM1b8XcF4LVzbsF9Ri6OSyemzTUK/eVNfaoqoynHWmgE
-6OXWk6RiwsXm9E/G+Z8ajYJJGYrKWUM66A0ywfRMEwNvbqY/kXPLynNvEiCL7sCCeN5LLsJJwx3t
-FvYk9CcbXFcx3FXuqB5vbKziRcxXV4p1VxngtViZSTYxPDMBbRZKzbgqg4SGm/lg0h9tkQPTYKbV
-PZrdd5A9NaSfD171UkRpucC63M9933zZxKyGIjK8e2uR73r4F2iw4lNVYC2vPsKD2NkJK/DAZNuH
-i5HMkesE/Xa0lZrmFAYb1TQdvtj/dBxThZngWVJKYe2InmtJiUZ+IFrZ50rlau7SZRFDAgMBAAGj
-YzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTvkUz1pcMw6C8I
-6tNxIqSSaHh02TAfBgNVHSMEGDAWgBTvkUz1pcMw6C8I6tNxIqSSaHh02TANBgkqhkiG9w0BAQsF
-AAOCAgEAfj1U2iJdGlg+O1QnurrMyOMaauo++RLrVl89UM7g6kgmJs95Vn6RHJk/0KGRHCwPT5iV
-WVO90CLYiF2cN/z7ZMF4jIuaYAnq1fohX9B0ZedQxb8uuQsLrbWwF6YSjNRieOpWauwK0kDDPAUw
-Pk2Ut59KA9N9J0u2/kTO+hkzGm2kQtHdzMjI1xZSg081lLMSVX3l4kLr5JyTCcBMWwerx20RoFAX
-lCOotQqSD7J6wWAsOMwaplv/8gzjqh8c3LigkyfeY+N/IZ865Z764BNqdeuWXGKRlI5nU7aJ+BIJ
-y29SWwNyhlCVCNSNh4YVH5Uk2KRvms6knZtt0rJ2BobGVgjF6wnaNsIbW0G+YSrjcOa4pvi2WsS9
-Iff/ql+hbHY5ZtbqTFXhADObE5hjyW/QASAJN1LnDE8+zbz1X5YnpyACleAu6AdBBR8Vbtaw5Bng
-DwKTACdyxYvRVB9dSsNAl35VpnzBMwQUAR1JIGkLGZOdblgi90AMRgwjY/M50n92Uaf0yKHxDHYi
-I0ZSKS3io0EHVmmY0gUJvGnHWmHNj4FgFU2A3ZDifcRQ8ow7bkrHxuaAKzyBvBGAFhAn1/DNP3nM
-cyrDflOR1m749fPH0FFNjkulW+YZFzvWgQncItzujrnEj1PhZ7szuIgVRs/taTX/dQ1G885x4cVr
-hkIGuUE=
------END CERTIFICATE-----
-
OISTE WISeKey Global Root GB CA
===============================
-----BEGIN CERTIFICATE-----
diff --git a/FreeFileSync/Source/afs/abstract.h b/FreeFileSync/Source/afs/abstract.h
index 535d4114..85916fcd 100644
--- a/FreeFileSync/Source/afs/abstract.h
+++ b/FreeFileSync/Source/afs/abstract.h
@@ -468,6 +468,7 @@ AbstractFileSystem::FinalizeResult AbstractFileSystem::OutputStream::finalize()
replaceCpy(replaceCpy(_("Unexpected size of data stream.\nExpected: %x bytes\nActual: %y bytes"),
L"%x", numberTo<std::wstring>(*bytesExpected_)),
L"%y", numberTo<std::wstring>(bytesWrittenTotal_)));
+ warn_static("somehow indicate that this is about source, and not the file name presented")
const FinalizeResult result = outStream_->finalize(); //throw FileError, X
finalizeSucceeded_ = true;
diff --git a/FreeFileSync/Source/afs/ftp.cpp b/FreeFileSync/Source/afs/ftp.cpp
index 4901168d..af3605e4 100644
--- a/FreeFileSync/Source/afs/ftp.cpp
+++ b/FreeFileSync/Source/afs/ftp.cpp
@@ -25,6 +25,10 @@ namespace
{
Zstring concatenateFtpFolderPathPhrase(const FtpLoginInfo& login, const AfsPath& afsPath); //noexcept
+/*
+ Extensions to FTP: https://tools.ietf.org/html/rfc3659
+*/
+
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]
@@ -224,7 +228,7 @@ public:
template <class Function> //expects non-empty range!
std::string readRange(Function acceptChar) //throw SysError
{
- auto itEnd = std::find_if(it_, line_.end(), std::not_fn(acceptChar));
+ auto itEnd = std::find_if_not(it_, line_.end(), acceptChar);
std::string output(it_, itEnd);
if (output.empty())
throw SysError(L"Expected char range not found.");
@@ -241,37 +245,52 @@ private:
//----------------------------------------------------------------------------------------------------------------
-std::wstring tryFormatFtpErrorCode(int ec) //https://en.wikipedia.org/wiki/List_of_FTP_server_return_codes
+std::wstring formatFtpStatusCode(int sc)
{
- 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"";
+ const wchar_t* statusText = [&] //https://en.wikipedia.org/wiki/List_of_FTP_server_return_codes
+ {
+ switch (sc)
+ {
+ //*INDENT-OFF*
+ case 400: return L"The command was not accepted but the error condition is temporary.";
+ case 421: return L"Service not available, closing control connection.";
+ case 425: return L"Cannot open data connection.";
+ case 426: return L"Connection closed; transfer aborted.";
+ case 430: return L"Invalid username or password.";
+ case 431: return L"Need some unavailable resource to process security.";
+ case 434: return L"Requested host unavailable.";
+ case 450: return L"Requested file action not taken.";
+ case 451: return L"Local error in processing.";
+ case 452: return L"Insufficient storage space in system. File unavailable, e.g. file busy.";
+
+ case 500: return L"Syntax error, command unrecognized or command line too long.";
+ case 501: return L"Syntax error in parameters or arguments.";
+ case 502: return L"Command not implemented.";
+ case 503: return L"Bad sequence of commands.";
+ case 504: return L"Command not implemented for that parameter.";
+ case 521: return L"Data connection cannot be opened with this PROT setting.";
+ case 522: return L"Server does not support the requested network protocol.";
+ case 530: return L"User not logged in.";
+ case 532: return L"Need account for storing files.";
+ case 533: return L"Command protection level denied for policy reasons.";
+ case 534: return L"Could not connect to server; issue regarding SSL.";
+ case 535: return L"Failed security check.";
+ case 536: return L"Requested PROT level not supported by mechanism.";
+ case 537: return L"Command protection level not supported by security mechanism.";
+ case 550: return L"File unavailable, e.g. file not found, no access.";
+ case 551: return L"Requested action aborted. Page type unknown.";
+ case 552: return L"Requested file action aborted. Exceeded storage allocation.";
+ case 553: return L"File name not allowed.";
+
+ default: return L"";
+ //*INDENT-ON*
+ }
+ }();
+
+ if (strLength(statusText) == 0)
+ return trimCpy(replaceCpy<std::wstring>(L"FTP status %x.", L"%x", numberTo<std::wstring>(sc)));
+ else
+ return trimCpy(replaceCpy<std::wstring>(L"FTP status %x: ", L"%x", numberTo<std::wstring>(sc)) + statusText);
}
//================================================================================================================
@@ -309,7 +328,7 @@ public:
};
//returns server response (header data)
- std::string perform(const AfsPath* afsPath /*optional, use last-used path if null*/, bool isDir,
+ std::string perform(const AfsPath& afsPath, bool isDir, curl_ftpmethod pathMethod,
const std::vector<Option>& extraOptions, bool requiresUtf8, int timeoutSec) //throw SysError
{
if (requiresUtf8) //avoid endless recursion
@@ -319,7 +338,7 @@ public:
{
easyHandle_ = ::curl_easy_init();
if (!easyHandle_)
- throw SysError(formatSystemError(L"curl_easy_init", formatCurlErrorRaw(CURLE_OUT_OF_MEMORY), std::wstring()));
+ throw SysError(formatSystemError(L"curl_easy_init", formatCurlStatusCode(CURLE_OUT_OF_MEMORY), std::wstring()));
}
else
::curl_easy_reset(easyHandle_);
@@ -341,42 +360,29 @@ public:
options.emplace_back(CURLOPT_HEADERDATA, &headerData_);
options.emplace_back(CURLOPT_HEADERFUNCTION, onHeaderReceived);
- std::string curlPath; //lifetime: keep alive until after curl_easy_setopt() below
- if (std::any_of(extraOptions.begin(), extraOptions.end(), [](const Option& opt) { return opt.option == CURLOPT_FTP_FILEMETHOD && opt.value == CURLFTPMETHOD_NOCWD; }))
- {
- //CURLFTPMETHOD_NOCWD case => CURLOPT_URL will not be used for CWD but as argument, e.g., for MLSD
- //curl was fixed to expect encoded paths in this case, too: https://github.com/curl/curl/issues/1974
- AfsPath targetPath;
- bool targetPathisDir = true;
- if (afsPath)
- {
- targetPath = *afsPath;
- targetPathisDir = isDir;
- }
- curlPath = getCurlUrlPath(targetPath, targetPathisDir, timeoutSec); //throw SysError
- workingDirPath_ = AfsPath();
- }
- else
- {
- AfsPath currentPath;
- bool currentPathisDir = true;
- if (afsPath)
- {
- currentPath = *afsPath;
- currentPathisDir = isDir;
- }
- else //try to use libcurl's last-used working dir and avoid excess CWD round trips
- if (getActiveSocket()) //throw SysError
- currentPath = workingDirPath_;
- //what if our last curl_easy_perform() just deleted the working directory????
- //=> 1. libcurl recognizes last-used path and avoids the CWD accordingly 2. commands that depend on the working directory, e.g. PWD will fail on *some* servers
-
- curlPath = getCurlUrlPath(currentPath, currentPathisDir, timeoutSec); //throw SysError
- workingDirPath_ = currentPathisDir ? currentPath : AfsPath(beforeLast(currentPath.value, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE));
- //remember libcurl's working dir: path might not exist => make sure to clear if ::curl_easy_perform() fails!
- }
+ //lifetime: keep alive until after curl_easy_setopt() below
+ const std::string curlPath = getCurlUrlPath(afsPath, isDir, timeoutSec); //throw SysError
options.emplace_back(CURLOPT_URL, curlPath.c_str());
+ options.emplace_back(CURLOPT_FTP_FILEMETHOD, pathMethod);
+
+ assert(pathMethod != CURLFTPMETHOD_MULTICWD); //too slow!
+ assert(pathMethod != CURLFTPMETHOD_NOCWD); //too buggy!
+ /* "wrong dir listing because libcurl remembers wrong CWD": https://github.com/curl/curl/issues/1782
+
+ => "fixed" by adding only the "if (data->set.ftp_filemethod == FTPFILE_NOCWD)" below: https://github.com/curl/curl/issues/1811
+ => this is NOT enough! consider what happens for a reused connection that first used CURLFTPMETHOD_MULTICWD, now CURLFTPMETHOD_NOCWD:
+
+ the code in ftp_state_cwd() will issue a CWD sequence that ends with "ftpc->cwdcount == 1"!!! See "if (++ftpc->cwdcount <= ftpc->dirdepth)"
+ => this skips the previous "fix" in https://github.com/curl/curl/issues/1718 with
+ if ((conn->data->set.ftp_filemethod == FTPFILE_NOCWD) && !ftpc->cwdcount)
+ ------------------------------------------------------------
+ workaround => use absolute paths only!
+ ------------------------------------------------------------
+ CURLFTPMETHOD_NOCWD doesn't work as advertized: "CWD is sent despite CURLOPT_QUOTE/CURLOPT_NOBODY" https://github.com/curl/curl/issues/1443
+ */
+
+ warn_static("what if server uses ansii encoding")
const auto username = utfTo<std::string>(sessionId_.username);
const auto password = utfTo<std::string>(sessionId_.password);
if (!username.empty()) //else: libcurl handles anonymous login for us (including fake email as password)
@@ -385,6 +391,15 @@ public:
options.emplace_back(CURLOPT_PASSWORD, password.c_str());
}
+
+ warn_static("remove after test")
+ //const auto username2 = utfToAnsiEncoding(sessionId_.username);
+ //options.emplace_back(CURLOPT_USERNAME, username2.c_str());
+
+
+
+
+
if (sessionId_.port > 0)
options.emplace_back(CURLOPT_PORT, static_cast<long>(sessionId_.port));
@@ -402,13 +417,6 @@ public:
//CURLOPT_ACCEPTTIMEOUT_MS? => only relevant for "active" FTP connections
- if (!std::any_of(extraOptions.begin(), extraOptions.end(), [](const Option& opt) { return opt.option == CURLOPT_FTP_FILEMETHOD; }))
- options.emplace_back(CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_SINGLECWD);
- //let's save these needless round trips!! most servers should support "CWD /folder/subfolder"
- //=> 15% faster folder traversal time compared to CURLFTPMETHOD_MULTICWD!
- //CURLFTPMETHOD_NOCWD? Already set in the MLSD case; but use for legacy servers, too? supported?
-
-
//Use share interface? https://curl.haxx.se/libcurl/c/libcurl-share.html
//perf test, 4 and 8 parallel threads:
// CURL_LOCK_DATA_DNS => no measurable total time difference
@@ -491,7 +499,7 @@ public:
#endif
if (sessionId_.useTls) //https://tools.ietf.org/html/rfc4217
{
- options.emplace_back(CURLOPT_USE_SSL, CURLUSESSL_ALL); //require SSL for both control and data
+ options.emplace_back(CURLOPT_USE_SSL, CURLUSESSL_ALL); //require SSL for both control and data
options.emplace_back(CURLOPT_FTPSSLAUTH, CURLFTPAUTH_TLS); //try TLS first, then SSL (currently: CURLFTPAUTH_DEFAULT == CURLFTPAUTH_SSL)
}
@@ -507,14 +515,14 @@ public:
const CURLcode rc = ::curl_easy_setopt(easyHandle_, opt.option, opt.value);
if (rc != CURLE_OK)
throw SysError(formatSystemError(L"curl_easy_setopt " + numberTo<std::wstring>(opt.option),
- formatCurlErrorRaw(rc), utfTo<std::wstring>(::curl_easy_strerror(rc))));
+ formatCurlStatusCode(rc), utfTo<std::wstring>(::curl_easy_strerror(rc))));
}
//=======================================================================================================
const CURLcode rcPerf = ::curl_easy_perform(easyHandle_);
//WTF: curl_easy_perform() considers FTP response codes 4XX, 5XX as failure, but for HTTP response codes 4XX are considered success!! CONSISTENCY, people!!!
- long ftpStatus = 0; //optional
- /*const CURLcode rc =*/ ::curl_easy_getinfo(easyHandle_, CURLINFO_RESPONSE_CODE, &ftpStatus);
+ long ftpStatusCode = 0; //optional
+ /*const CURLcode rc =*/ ::curl_easy_getinfo(easyHandle_, CURLINFO_RESPONSE_CODE, &ftpStatusCode);
//note: CURLOPT_FAILONERROR(default:off) is only available for HTTP
//assert((rcPerf == CURLE_OK && 100 <= ftpStatus && ftpStatus < 400) || -> insufficient *FEAT can fail with 550, but still CURLE_OK because of *
@@ -522,10 +530,7 @@ public:
//=======================================================================================================
if (rcPerf != CURLE_OK)
- {
- workingDirPath_ = AfsPath(); //not sure what went wrong; no idea where libcurl's working dir currently is => libcurl might even have closed the old session!
- throw SysError(formatLastCurlError(L"curl_easy_perform", rcPerf, ftpStatus));
- }
+ throw SysError(formatLastCurlError(L"curl_easy_perform", rcPerf, ftpStatusCode));
lastSuccessfulUseTime_ = std::chrono::steady_clock::now();
return headerData_;
@@ -538,33 +543,83 @@ public:
ZEN_ON_SCOPE_EXIT(::curl_slist_free_all(quote));
quote = ::curl_slist_append(quote, ftpCmd.c_str());
- std::vector<FtpSession::Option> options =
+ return perform(AfsPath(), true /*isDir*/, CURLFTPMETHOD_FULLPATH, //really avoid needless CWDs unlike buggy(!) CURLFTPMETHOD_NOCWD
{
- FtpSession::Option(CURLOPT_NOBODY, 1L),
- FtpSession::Option(CURLOPT_QUOTE, quote),
- };
+ { CURLOPT_NOBODY, 1L },
+ { CURLOPT_QUOTE, quote },
+ }, requiresUtf8, timeoutSec); //throw SysError
+ }
- //observation: libcurl sends CWD *after* CURLOPT_QUOTE has run
- //perf: we neither need nor want libcurl to send CWD
- return perform(nullptr /*re-use last-used path*/, true /*isDir*/, options, requiresUtf8, timeoutSec); //throw SysError
+ void testConnection(int timeoutSec) //throw SysError
+ {
+ //FEAT: are there servers that don't support this command? fuck, yes: "550 FEAT: Operation not permitted" => buggy server not granting access, despite support!
+ if (supportsFeat(timeoutSec)) //throw SysError
+ runSingleFtpCommand("FEAT", false /*requiresUtf8*/, timeoutSec); //throw SysError
+ else
+ //PWD? will fail if last access deleted the working dir!
+ //"TYPE I"? might interfere with libcurls internal handling, but that's an improvement, right? right? :>
+ //=> but "HELP", and "NOOP" work, right?? https://en.wikipedia.org/wiki/List_of_FTP_commands
+ //Fuck my life: even "HELP" is not always implemented: https://freefilesync.org/forum/viewtopic.php?t=6002
+ runSingleFtpCommand("HELP", false /*requiresUtf8*/, timeoutSec); //throw SysError
+ //=> are there servers supporting neither FEAT nor HELP? only time will tell...
}
AfsPath getHomePath(int timeoutSec) //throw SysError
{
- perform(nullptr /*re-use last-used path*/, true /*isDir*/,
- { FtpSession::Option(CURLOPT_NOBODY, 1L) }, true /*requiresUtf8*/, timeoutSec); //throw SysError
- assert(easyHandle_);
+ if (!homePathCached_)
+ homePathCached_ = [&]
+ {
+ if (!easyHandle_)
+ testConnection(timeoutSec); //throw SysError
+ assert(easyHandle_);
+
+ const char* homePathCurl = nullptr; //not owned
+ /*CURLcode rc =*/ ::curl_easy_getinfo(easyHandle_, CURLINFO_FTP_ENTRY_PATH, &homePathCurl);
+
+ if (homePathCurl && isAsciiString(homePathCurl))
+ return sanitizeRootRelativePath(utfTo<Zstring>(homePathCurl));
- const char* homePath = nullptr; //not owned
- /*CURLcode rc =*/ ::curl_easy_getinfo(easyHandle_, CURLINFO_FTP_ENTRY_PATH, &homePath);
+ //home path with non-ASCII chars: libcurl issues PWD right after login *before* server was set up for UTF8
+ //=> CURLINFO_FTP_ENTRY_PATH could be in any encoding => useless!
+ // Test case: Windows 10 IIS FTP with non-Ascii entry path
+ //=> start new FTP session and parse PWD *after* UTF8 is enabled:
+ if (easyHandle_)
+ {
+ ::curl_easy_cleanup(easyHandle_);
+ easyHandle_ = nullptr;
+ }
- if (!homePath)
- return AfsPath();
- return sanitizeRootRelativePath(utfTo<Zstring>(homePath));
+ for (const std::string& line : splitFtpResponse(runSingleFtpCommand("PWD", true /*requiresUtf8*/, timeoutSec))) //throw SysError
+ if (startsWith(line, "257 "))
+ {
+ /* 257<space>[rubbish]"<directory-name>"<space><commentary> according to libcurl
+
+ "The directory name can contain any character; embedded double-quotes should be escaped by
+ double-quotes (the "quote-doubling" convention)." https://tools.ietf.org/html/rfc959 */
+ auto itBegin = std::find(line.begin(), line.end(), '"');
+ if (itBegin != line.end())
+ for (auto it = ++itBegin; it != line.end(); ++it)
+ if (*it == '"')
+ {
+ if (it + 1 != line.end() && it[1] == '"')
+ ++it; //skip double quote
+ else
+ {
+ const std::string homePathRaw = replaceCpy<std::string>({ itBegin, it }, "\"\"", '"');
+ const ServerEncoding enc = getServerEncoding(timeoutSec); //throw SysError
+ const Zstring homePathUtf = serverToUtfEncoding(homePathRaw, enc); //throw SysError
+ return sanitizeRootRelativePath(homePathUtf);
+ }
+ }
+ }
+ return AfsPath(); //error: home path could not be determined
+ }();
+ return *homePathCached_;
}
//------------------------------------------------------------------------------------------------------------
+ bool supportsFeat(int timeoutSec) { return getFeatureSupport(&Features::feat, timeoutSec); } //
bool supportsMlsd(int timeoutSec) { return getFeatureSupport(&Features::mlsd, timeoutSec); } //
bool supportsMfmt(int timeoutSec) { return getFeatureSupport(&Features::mfmt, timeoutSec); } //throw SysError
bool supportsClnt(int timeoutSec) { return getFeatureSupport(&Features::clnt, timeoutSec); } //
@@ -593,11 +648,9 @@ private:
FtpSession (const FtpSession&) = delete;
FtpSession& operator=(const FtpSession&) = delete;
- std::string getCurlUrlPath(const AfsPath& afsPath, bool isDir, int timeoutSec) //throw SysError
+ std::string getCurlUrlPath(const AfsPath& afsPath /*optional*/, bool isDir, int timeoutSec) //throw SysError
{
- //Some FTP servers distinguish between user-home- and root-relative paths! e.g. FreeNAS: https://freefilesync.org/forum/viewtopic.php?t=6129
- //=> use root-relative paths (= same as expected by CURLOPT_QUOTE)
- std::string curlRelPath = "/%2f"; //https://curl.haxx.se/docs/faq.html#How_do_I_list_the_root_dir_of_an
+ std::string curlRelPath; //libcurl expects encoded paths (except for '/' char!!!)
for (const std::string& comp : split(getServerRelPathInternal(afsPath, timeoutSec), '/', SplitType::SKIP_EMPTY)) //throw SysError
{
@@ -606,16 +659,21 @@ private:
throw SysError(replaceCpy<std::wstring>(L"curl_easy_escape: conversion failure (%x)", L"%x", utfTo<std::wstring>(comp)));
ZEN_ON_SCOPE_EXIT(::curl_free(compFmt));
+ if (!curlRelPath.empty())
+ curlRelPath += '/';
curlRelPath += compFmt;
- curlRelPath += '/';
}
- if (endsWith(curlRelPath, '/'))
- curlRelPath.pop_back();
- std::string path = utfTo<std::string>(Zstring(ftpPrefix) + Zstr("//") + sessionId_.server) + curlRelPath;
+ /* 1. FFS CURLFTPMETHOD_NOCWD is buggy (see comment FtpSession::perform()) => must use absolute, not home-relative paths!
+ 2. Support CURLFTPMETHOD_FULLPATH => must use absolute, not home-relative paths!
+ 3. Some FTP servers distinguish between user-home- and root-relative paths! e.g. FreeNAS: https://freefilesync.org/forum/viewtopic.php?t=6129
+ => use root-relative paths (= same as expected by CURLOPT_QUOTE) https://curl.haxx.se/docs/faq.html#How_do_I_list_the_root_dir_of_an
+ => use // because /%2f had bugs (but they should be fixed: https://github.com/curl/curl/pull/4348)
+ */
+ std::string path = utfTo<std::string>(Zstring(ftpPrefix) + Zstr("//") + sessionId_.server) + "//" + curlRelPath;
if (isDir && !endsWith(path, '/')) //curl-FTP needs directory paths to end with a slash
- path += "/";
+ path += '/';
return path;
}
@@ -630,7 +688,7 @@ private:
{
//[!] supportsUtf8() is buffered! => FTP session might not yet exist (or was closed by libcurl after a failure)
if (std::optional<curl_socket_t> currentSocket = getActiveSocket()) //throw SysError
- if (*currentSocket == utf8EnabledSocket_) //caveat: a non-utf8-enabled session might already exist, e.g. from a previous call to supportsMlsd()
+ if (*currentSocket == utf8EnabledSocket_) //caveat: a non-UTF8-enabled session might already exist, e.g. from a previous call to supportsMlsd()
return;
//some servers even require "CLNT" before accepting "OPTS UTF8 ON": https://social.msdn.microsoft.com/Forums/en-US/d602574f-8a69-4d69-b337-52b6081902cf/problem-with-ftpwebrequestopts-utf8-on-501-please-clnt-first
@@ -641,7 +699,6 @@ private:
//-> ignore if server does not know this legacy command (but report all *other* issues; else getActiveSocket() below won't return value and hide real error!)
runSingleFtpCommand("*OPTS UTF8 ON", false /*requiresUtf8*/, timeoutSec); //throw SysError
-
//make sure our unicode-enabled session is still there (== libcurl behaves as we expect)
std::optional<curl_socket_t> currentSocket = getActiveSocket(); //throw SysError
if (!currentSocket)
@@ -658,7 +715,7 @@ private:
curl_socket_t currentSocket = 0;
const CURLcode rc = ::curl_easy_getinfo(easyHandle_, CURLINFO_ACTIVESOCKET, &currentSocket);
if (rc != CURLE_OK)
- throw SysError(formatSystemError(L"curl_easy_getinfo: CURLINFO_ACTIVESOCKET", formatCurlErrorRaw(rc), utfTo<std::wstring>(::curl_easy_strerror(rc))));
+ throw SysError(formatSystemError(L"curl_easy_getinfo: CURLINFO_ACTIVESOCKET", formatCurlStatusCode(rc), utfTo<std::wstring>(::curl_easy_strerror(rc))));
if (currentSocket != CURL_SOCKET_BAD)
return currentSocket;
}
@@ -667,6 +724,7 @@ private:
struct Features
{
+ bool feat = false; //not always enabled (e.g. might be disabled because... who knows why)
bool mlsd = false;
bool mfmt = false;
bool clnt = false;
@@ -690,15 +748,10 @@ private:
if (!featureCache_)
{
//ignore errors if server does not support FEAT (do those exist?), but fail for all others
- const std::string featResponse = runSingleFtpCommand("*FEAT", false /*requiresUtf8*/, timeoutSec); //throw SysError
+ featureCache_ = parseFeatResponse(runSingleFtpCommand("*FEAT", false /*requiresUtf8*/, timeoutSec)); //throw SysError
//used by sessionEnableUtf8()! => requiresUtf8 = false!!!
- sf->access([&](FeatureList& feat)
- {
- auto& f = feat[sessionId_.server];
- f = parseFeatResponse(featResponse);
- featureCache_ = f;
- });
+ sf->access([&](FeatureList& feat) { feat[sessionId_.server] = featureCache_; });
}
}
return (*featureCache_).*status;
@@ -709,37 +762,41 @@ private:
Features output; //FEAT command: https://tools.ietf.org/html/rfc2389#page-4
const std::vector<std::string> lines = splitFtpResponse(featResponse);
- auto it = std::find_if(lines.begin(), lines.end(), [](const std::string& line) { return startsWith(line, "211-"); });
- if (it != lines.end()) ++it;
- for (; it != lines.end(); ++it)
+ auto it = std::find_if(lines.begin(), lines.end(), [](const std::string& line) { return startsWith(line, "211-") || startsWith(line, "211 "); });
+ if (it != lines.end())
{
- const std::string& line = *it;
- if ( equalAsciiNoCase(line, "211 End") ||
- startsWithAsciiNoCase(line, "211 End ")) //e.g. Serv-U: "211 End (for details use "HELP commmand" where command is the command of interest)"
- break;
-
- //https://tools.ietf.org/html/rfc3659#section-7.8
- //"a server-FTP process that supports MLST, and MLSD [...] MUST indicate that this support exists"
- //"there is no distinct FEAT output for MLSD. The presence of the MLST feature indicates that both MLST and MLSD are supported"
- if (equalAsciiNoCase (line, " MLST") ||
- startsWithAsciiNoCase(line, " MLST ")) //SP "MLST" [SP factlist] CRLF
- output.mlsd = true;
-
- //https://tools.ietf.org/html/draft-somers-ftp-mfxx-04#section-3.3
- //"Where a server-FTP process supports the MFMT command [...] it MUST include the response to the FEAT command"
- else if (equalAsciiNoCase(line, " MFMT")) //SP "MFMT" CRLF
- output.mfmt = true;
-
- else if (equalAsciiNoCase(line, " UTF8"))
- output.utf8 = true;
-
- else if (equalAsciiNoCase(line, " CLNT"))
- output.clnt = true;
+ output.feat = true;
+ ++it;
+ for (; it != lines.end(); ++it)
+ {
+ const std::string& line = *it;
+ if (equalAsciiNoCase(line, "211 End") ||
+ startsWithAsciiNoCase(line, "211 End ")) //Serv-U: "211 End (for details use "HELP commmand" where command is the command of interest)"
+ break; //Home Ftp Server: "211 End of extentions."
+
+ //https://tools.ietf.org/html/rfc3659#section-7.8
+ //"a server-FTP process that supports MLST, and MLSD [...] MUST indicate that this support exists"
+ //"there is no distinct FEAT output for MLSD. The presence of the MLST feature indicates that both MLST and MLSD are supported"
+ if (equalAsciiNoCase (line, " MLST") ||
+ startsWithAsciiNoCase(line, " MLST ")) //SP "MLST" [SP factlist] CRLF
+ output.mlsd = true;
+
+ //https://tools.ietf.org/html/draft-somers-ftp-mfxx-04#section-3.3
+ //"Where a server-FTP process supports the MFMT command [...] it MUST include the response to the FEAT command"
+ else if (equalAsciiNoCase(line, " MFMT")) //SP "MFMT" CRLF
+ output.mfmt = true;
+
+ else if (equalAsciiNoCase(line, " UTF8"))
+ output.utf8 = true;
+
+ else if (equalAsciiNoCase(line, " CLNT"))
+ output.clnt = true;
+ }
}
return output;
}
- std::wstring formatLastCurlError(const std::wstring& functionName, CURLcode ec, long ftpResponse) const
+ std::wstring formatLastCurlError(const std::wstring& functionName, CURLcode ec, long ftpStatusCode) const
{
std::wstring errorMsg;
@@ -753,11 +810,7 @@ private:
errorMsg += (errorMsg.empty() ? L"" : L"\n") + trimCpy(utfTo<std::wstring>(headerLines.back())); //that *should* be the servers error response
}
else //failed to get server response
- {
- const std::wstring descr = tryFormatFtpErrorCode(ftpResponse);
- if (!descr.empty())
- errorMsg += (errorMsg.empty() ? L"" : L"\n") + numberTo<std::wstring>(ftpResponse) + L": " + descr;
- }
+ errorMsg += (errorMsg.empty() ? L"" : L"\n") + formatFtpStatusCode(ftpStatusCode);
#if 0
//utfTo<std::wstring>(::curl_easy_strerror(ec)) is uninteresting
//use CURLINFO_OS_ERRNO ?? https://curl.haxx.se/libcurl/c/CURLINFO_OS_ERRNO.html
@@ -766,7 +819,7 @@ private:
if (nativeErrorCode != 0)
errorMsg += (errorMsg.empty() ? L"" : L"\n") + std::wstring(L"Native error code: ") + numberTo<std::wstring>(nativeErrorCode);
#endif
- return formatSystemError(functionName, formatCurlErrorRaw(ec), errorMsg);
+ return formatSystemError(functionName, formatCurlStatusCode(ec), errorMsg);
}
const FtpSessionId sessionId_;
@@ -774,11 +827,10 @@ private:
char curlErrorBuf_[CURL_ERROR_SIZE] = {};
std::string headerData_;
- AfsPath workingDirPath_;
-
curl_socket_t utf8EnabledSocket_ = 0;
std::optional<Features> featureCache_;
+ std::optional<AfsPath> homePathCached_;
std::shared_ptr<UniCounterCookie> libsshCurlUnifiedInitCookie_;
std::chrono::steady_clock::time_point lastSuccessfulUseTime_;
@@ -941,9 +993,10 @@ public:
{
std::vector<FtpSession::Option> options =
{
- FtpSession::Option(CURLOPT_WRITEDATA, &rawListing),
- FtpSession::Option(CURLOPT_WRITEFUNCTION, onBytesReceived),
+ { CURLOPT_WRITEDATA, &rawListing },
+ { CURLOPT_WRITEFUNCTION, onBytesReceived },
};
+ curl_ftpmethod pathMethod = CURLFTPMETHOD_SINGLECWD;
if (session.supportsMlsd(login.timeoutSec)) //throw SysError
{
@@ -968,12 +1021,12 @@ public:
}();
if (!pathHasWildcards)
- options.emplace_back(CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_NOCWD); //16% faster traversal compared to CURLFTPMETHOD_SINGLECWD (35% faster than CURLFTPMETHOD_MULTICWD)
+ pathMethod = CURLFTPMETHOD_FULLPATH; //16% faster traversal compared to CURLFTPMETHOD_SINGLECWD (35% faster than CURLFTPMETHOD_MULTICWD)
}
//else: use "LIST" + CURLFTPMETHOD_SINGLECWD
+ //caveat: let's better not use LIST parameters: https://cr.yp.to/ftp/list.html
- session.perform(&afsDirPath, true /*isDir*/, options, true /*requiresUtf8*/, login.timeoutSec); //throw SysError
-
+ session.perform(afsDirPath, true /*isDir*/, pathMethod, options, true /*requiresUtf8*/, login.timeoutSec); //throw SysError
const ServerEncoding encoding = session.getServerEncoding(login.timeoutSec); //throw SysError
if (session.supportsMlsd(login.timeoutSec)) //throw SysError
@@ -1048,16 +1101,15 @@ private:
if (tc == TimeComp())
throw SysError(L"Modification time could not be parsed. (" + utfTo<std::wstring>(modifyFact) + L")");
- time_t utcTime = utcToTimeT(tc); //returns -1 on error
- if (utcTime == -1)
+ item.modTime = utcToTimeT(tc); //returns -1 on error
+ if (item.modTime == -1)
{
if (tc.year == 1600 || //FTP on Windows phone: zero-initialized FILETIME equals "December 31, 1600" or "January 1, 1601"
tc.year == 1601) // => is this also relevant in this context of MLST UTC time??
- utcTime = 0;
+ item.modTime = 0;
else
throw SysError(L"Modification time could not be parsed. (" + utfTo<std::wstring>(modifyFact) + L")");
}
- item.modTime = utcTime;
}
if (equalAsciiNoCase(typeFact, "cdir"))
@@ -1196,13 +1248,13 @@ private:
//------------------------------------------------------------------------------------
//user
parser.readRange(std::not_fn(isWhiteSpace<char>)); //throw SysError
- parser.readRange(&isWhiteSpace<char>); //throw SysError
+ parser.readRange(&isWhiteSpace<char>); //throw SysError
//------------------------------------------------------------------------------------
//group
if (haveGroup)
{
parser.readRange(std::not_fn(isWhiteSpace<char>)); //throw SysError
- parser.readRange(&isWhiteSpace<char>); //throw SysError
+ parser.readRange(&isWhiteSpace<char>); //throw SysError
}
//------------------------------------------------------------------------------------
//file size (no separators)
@@ -1539,11 +1591,12 @@ void ftpFileDownload(const FtpLoginInfo& login, const AfsPath& afsFilePath, //th
{
accessFtpSession(login, [&](FtpSession& session) //throw SysError
{
- session.perform(&afsFilePath, false /*isDir*/, //throw SysError
+ session.perform(afsFilePath, false /*isDir*/, CURLFTPMETHOD_FULLPATH, //are there any servers that require CURLFTPMETHOD_SINGLECWD? let's find out
{
- FtpSession::Option(CURLOPT_WRITEDATA, &onBytesReceived),
- FtpSession::Option(CURLOPT_WRITEFUNCTION, onBytesReceivedWrapper),
- }, true /*requiresUtf8*/, login.timeoutSec);
+ { CURLOPT_WRITEDATA, &onBytesReceived },
+ { CURLOPT_WRITEFUNCTION, onBytesReceivedWrapper },
+ { CURLOPT_IGNORE_CONTENT_LENGTH, 1L }, //skip FTP "SIZE" command before download (=> download until actual EOF if file size changes)
+ }, true /*requiresUtf8*/, login.timeoutSec); //throw SysError
});
}
catch (const SysError& e)
@@ -1604,18 +1657,18 @@ void ftpFileUpload(const FtpLoginInfo& login, const AfsPath& afsFilePath, //thro
//optimize fail-safe copy with RNFR/RNTO as CURLOPT_POSTQUOTE? -> even slightly *slower* than RNFR/RNTO as additional curl_easy_perform()
*/
- session.perform(&afsFilePath, false /*isDir*/, //throw SysError
+ session.perform(afsFilePath, false /*isDir*/, CURLFTPMETHOD_FULLPATH, //are there any servers that require CURLFTPMETHOD_SINGLECWD? let's find out
{
- FtpSession::Option(CURLOPT_UPLOAD, 1L),
- //FtpSession::Option(CURLOPT_INFILESIZE_LARGE, static_cast<curl_off_t>(inputBuffer.size())),
- //=> CURLOPT_INFILESIZE_LARGE does not issue a specific FTP command, but is used by libcurl only!
+ { CURLOPT_UPLOAD, 1L },
+ { CURLOPT_READDATA, &getBytesToSend },
+ { CURLOPT_READFUNCTION, getBytesToSendWrapper },
- FtpSession::Option(CURLOPT_READDATA, &getBytesToSend),
- FtpSession::Option(CURLOPT_READFUNCTION, getBytesToSendWrapper),
+ //{ CURLOPT_INFILESIZE_LARGE, static_cast<curl_off_t>(inputBuffer.size()) },
+ //=> CURLOPT_INFILESIZE_LARGE does not issue a specific FTP command, but is used by libcurl only!
- //FtpSession::Option(CURLOPT_PREQUOTE, quote),
- //FtpSession::Option(CURLOPT_POSTQUOTE, quote),
- }, true /*requiresUtf8*/, login.timeoutSec);
+ //{ CURLOPT_PREQUOTE, quote },
+ //{ CURLOPT_POSTQUOTE, quote },
+ }, true /*requiresUtf8*/, login.timeoutSec); //throw SysError
});
}
catch (const SysError& e)
@@ -1841,16 +1894,16 @@ private:
//don't use MLST: broken for Pure-FTPd: https://freefilesync.org/forum/viewtopic.php?t=4287
const std::optional<AfsPath> parentAfsPath = getParentPath(afsPath);
- if (!parentAfsPath) //device root => quick access tests: just see if the server responds at all!
- {
- //don't use PWD: if last access deleted the working dir, PWD will fail on some servers, e.g. https://freefilesync.org/forum/viewtopic.php?t=4314
- //FEAT: are there servers that don't support this command? fuck, yes: "550 FEAT: Operation not permitted" => buggy server not granting access, despite support!
- //=> but "HELP", and "NOOP" work, right?? https://en.wikipedia.org/wiki/List_of_FTP_commands
- //Fuck my life: even "HELP" is not always implemented: https://freefilesync.org/forum/viewtopic.php?t=6002
- //Screw this, just traverse the root folder: (only a single round-trip for FTP)
- /*std::vector<FtpItem> items =*/ FtpDirectoryReader::execute(login_, afsPath); //throw FileError
- return ItemType::FOLDER;
- }
+ if (!parentAfsPath) //device root => do a quick access tests to see if the server responds at all!
+ try
+ {
+ accessFtpSession(login_, [&](FtpSession& session) //throw SysError
+ {
+ session.testConnection(login_.timeoutSec); //throw SysError
+ });
+ return ItemType::FOLDER;
+ }
+ catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(getCurlDisplayPath(login_.server, afsPath))), e.toString()); }
const Zstring itemName = getItemName(afsPath);
assert(!itemName.empty());
@@ -2073,11 +2126,11 @@ private:
quote = ::curl_slist_append(quote, ("RNFR " + session.getServerRelPathInternal(pathFrom, login_.timeoutSec)).c_str()); //throw SysError
quote = ::curl_slist_append(quote, ("RNTO " + session.getServerRelPathInternal(pathTo.afsPath, login_.timeoutSec)).c_str()); //
- session.perform(nullptr /*re-use last-used path*/, true /*isDir*/, //throw SysError
+ session.perform(AfsPath(), true /*isDir*/, CURLFTPMETHOD_FULLPATH, //really avoid needless CWDs unlike buggy(!) CURLFTPMETHOD_NOCWD
{
- FtpSession::Option(CURLOPT_NOBODY, 1L),
- FtpSession::Option(CURLOPT_QUOTE, quote),
- }, true /*requiresUtf8*/, login_.timeoutSec);
+ { CURLOPT_NOBODY, 1L },
+ { CURLOPT_QUOTE, quote },
+ }, true /*requiresUtf8*/, login_.timeoutSec); //throw SysError
});
}
catch (const SysError& e)
diff --git a/FreeFileSync/Source/afs/gdrive.cpp b/FreeFileSync/Source/afs/gdrive.cpp
index b55350bf..9b885036 100644
--- a/FreeFileSync/Source/afs/gdrive.cpp
+++ b/FreeFileSync/Source/afs/gdrive.cpp
@@ -138,61 +138,6 @@ std::wstring formatGoogleErrorRaw(const std::string& serverResponse)
return utfTo<std::wstring>(serverResponse);
}
-
-std::wstring tryFormatHttpErrorCode(int ec) //https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
-{
- if (ec == 300) return L"Multiple Choices.";
- if (ec == 301) return L"Moved Permanently.";
- if (ec == 302) return L"Moved temporarily.";
- if (ec == 303) return L"See Other";
- if (ec == 304) return L"Not Modified.";
- if (ec == 305) return L"Use Proxy.";
- if (ec == 306) return L"Switch Proxy.";
- if (ec == 307) return L"Temporary Redirect.";
- if (ec == 308) return L"Permanent Redirect.";
-
- if (ec == 400) return L"Bad Request.";
- if (ec == 401) return L"Unauthorized.";
- if (ec == 402) return L"Payment Required.";
- if (ec == 403) return L"Forbidden.";
- if (ec == 404) return L"Not Found.";
- if (ec == 405) return L"Method Not Allowed.";
- if (ec == 406) return L"Not Acceptable.";
- if (ec == 407) return L"Proxy Authentication Required.";
- if (ec == 408) return L"Request Timeout.";
- if (ec == 409) return L"Conflict.";
- if (ec == 410) return L"Gone.";
- if (ec == 411) return L"Length Required.";
- if (ec == 412) return L"Precondition Failed.";
- if (ec == 413) return L"Payload Too Large.";
- if (ec == 414) return L"URI Too Long.";
- if (ec == 415) return L"Unsupported Media Type.";
- if (ec == 416) return L"Range Not Satisfiable.";
- if (ec == 417) return L"Expectation Failed.";
- if (ec == 418) return L"I'm a teapot.";
- if (ec == 421) return L"Misdirected Request.";
- if (ec == 422) return L"Unprocessable Entity.";
- if (ec == 423) return L"Locked.";
- if (ec == 424) return L"Failed Dependency.";
- if (ec == 426) return L"Upgrade Required.";
- if (ec == 428) return L"Precondition Required.";
- if (ec == 429) return L"Too Many Requests.";
- if (ec == 431) return L"Request Header Fields Too Large.";
- if (ec == 451) return L"Unavailable For Legal Reasons.";
-
- if (ec == 500) return L"Internal Server Error.";
- if (ec == 501) return L"Not Implemented.";
- if (ec == 502) return L"Bad Gateway.";
- if (ec == 503) return L"Service Unavailable.";
- if (ec == 504) return L"Gateway Timeout.";
- if (ec == 505) return L"HTTP Version Not Supported.";
- if (ec == 506) return L"Variant Also Negotiates.";
- if (ec == 507) return L"Insufficient Storage.";
- if (ec == 508) return L"Loop Detected.";
- if (ec == 510) return L"Not Extended.";
- if (ec == 511) return L"Network Authentication Required.";
- return L"";
-}
//----------------------------------------------------------------------------------------------------------------
Global<UniSessionCounter> httpSessionCount(createUniSessionCounter());
@@ -239,7 +184,7 @@ public:
{
easyHandle_ = ::curl_easy_init();
if (!easyHandle_)
- throw SysError(formatSystemError(L"curl_easy_init", formatCurlErrorRaw(CURLE_OUT_OF_MEMORY), std::wstring()));
+ throw SysError(formatSystemError(L"curl_easy_init", formatCurlStatusCode(CURLE_OUT_OF_MEMORY), std::wstring()));
}
else
::curl_easy_reset(easyHandle_);
@@ -339,9 +284,13 @@ public:
//---------------------------------------------------
struct curl_slist* headers = nullptr; //"libcurl will not copy the entire list so you must keep it!"
ZEN_ON_SCOPE_EXIT(::curl_slist_free_all(headers));
+
for (const std::string& headerLine : extraHeaders)
headers = ::curl_slist_append(headers, headerLine.c_str());
+ //WTF!!! 1 sec delay when server doesn't support "Expect: 100-continue!! https://stackoverflow.com/questions/49670008/how-to-disable-expect-100-continue-in-libcurl
+ headers = ::curl_slist_append(headers, "Expect:"); //guess, what: www.googleapis.com doesn't support it! e.g. gdriveUploadFile()
+
if (headers)
options.emplace_back(CURLOPT_HTTPHEADER, headers);
//---------------------------------------------------
@@ -353,13 +302,14 @@ public:
const CURLcode rc = ::curl_easy_setopt(easyHandle_, opt.option, opt.value);
if (rc != CURLE_OK)
throw SysError(formatSystemError(L"curl_easy_setopt " + numberTo<std::wstring>(opt.option),
- formatCurlErrorRaw(rc), utfTo<std::wstring>(::curl_easy_strerror(rc))));
+ formatCurlStatusCode(rc), utfTo<std::wstring>(::curl_easy_strerror(rc))));
}
//=======================================================================================================
const CURLcode rcPerf = ::curl_easy_perform(easyHandle_);
//WTF: curl_easy_perform() considers FTP response codes 4XX, 5XX as failure, but for HTTP response codes 4XX are considered success!! CONSISTENCY, people!!!
//=> at least libcurl is aware: CURLOPT_FAILONERROR: "request failure on HTTP response >= 400"; default: "0, do not fail on error"
+ //https://curl.haxx.se/docs/faq.html#curl_doesn_t_return_error_for_HT
//=> Curiously Google also screws up in their REST API design and returns HTTP 4XX status for domain-level errors!
//=> let caller handle HTTP status to work around this mess!
@@ -390,16 +340,15 @@ private:
HttpSession (const HttpSession&) = delete;
HttpSession& operator=(const HttpSession&) = delete;
- std::wstring formatLastCurlError(const std::wstring& functionName, CURLcode ec, int httpStatusCode) const
+ std::wstring formatLastCurlError(const std::wstring& functionName, CURLcode ec, int httpStatusCode /*optional*/) const
{
std::wstring errorMsg;
if (curlErrorBuf_[0] != 0)
errorMsg = trimCpy(utfTo<std::wstring>(curlErrorBuf_));
- const std::wstring descr = tryFormatHttpErrorCode(httpStatusCode);
- if (!descr.empty())
- errorMsg += (errorMsg.empty() ? L"" : L"\n") + numberTo<std::wstring>(httpStatusCode) + L": " + descr;
+ if (httpStatusCode != 0) //optional
+ errorMsg += (errorMsg.empty() ? L"" : L"\n") + formatHttpStatusCode(httpStatusCode);
#if 0
//utfTo<std::wstring>(::curl_easy_strerror(ec)) is uninteresting
//use CURLINFO_OS_ERRNO ?? https://curl.haxx.se/libcurl/c/CURLINFO_OS_ERRNO.html
@@ -408,7 +357,7 @@ private:
if (nativeErrorCode != 0)
errorMsg += (errorMsg.empty() ? L"" : L"\n") + std::wstring(L"Native error code: ") + numberTo<std::wstring>(nativeErrorCode);
#endif
- return formatSystemError(functionName, formatCurlErrorRaw(ec), errorMsg);
+ return formatSystemError(functionName, formatCurlStatusCode(ec), errorMsg);
}
const HttpSessionId sessionId_;
@@ -1042,9 +991,19 @@ std::vector<GoogleFileItem> readFolderContent(const std::string& folderId, const
const uint64_t fileSize = size ? stringTo<uint64_t>(*size) : 0; //not available for folders
//RFC 3339 date-time: e.g. "2018-09-29T08:39:12.053Z"
- const time_t modTime = utcToTimeT(parseTime("%Y-%m-%dT%H:%M:%S", beforeLast(*modifiedTime, '.', IF_MISSING_RETURN_ALL))); //returns -1 on error
- if (modTime == -1 || !endsWith(*modifiedTime, 'Z')) //'Z' means "UTC" => it seems Google doesn't use the time-zone offset postfix
- throw SysError(L"Modification time could not be parsed. (" + utfTo<std::wstring>(*modifiedTime) + L")");
+ const TimeComp tc = parseTime("%Y-%m-%dT%H:%M:%S", beforeLast(*modifiedTime, '.', IF_MISSING_RETURN_ALL));
+ if (tc == TimeComp() || !endsWith(*modifiedTime, 'Z')) //'Z' means "UTC" => it seems Google doesn't use the time-zone offset postfix
+ throw SysError(L"Modification time could not be parsed. (" + utfTo<std::wstring>(*modifiedTime) + L")");
+
+ time_t modTime = utcToTimeT(tc); //returns -1 on error
+ if (modTime == -1)
+ {
+ if (tc.year == 1600 || //zero-initialized FILETIME equals "December 31, 1600" or "January 1, 1601"
+ tc.year == 1601) // => yes, possible even on Google Drive: https://freefilesync.org/forum/viewtopic.php?t=6602
+ modTime = 0;
+ else
+ throw SysError(L"Modification time could not be parsed. (" + utfTo<std::wstring>(*modifiedTime) + L")");
+ }
std::vector<std::string> parentIds;
for (const auto& parentVal : parents->arrayVal)
@@ -1141,9 +1100,19 @@ ChangesDelta getChangesDelta(const std::string& startPageToken, const std::strin
itemDetails.fileSize = size ? stringTo<uint64_t>(*size) : 0; //not available for folders
//RFC 3339 date-time: e.g. "2018-09-29T08:39:12.053Z"
- itemDetails.modTime = utcToTimeT(parseTime("%Y-%m-%dT%H:%M:%S", beforeLast(*modifiedTime, '.', IF_MISSING_RETURN_ALL))); //returns -1 on error
- if (itemDetails.modTime == -1 || !endsWith(*modifiedTime, 'Z')) //'Z' means "UTC" => it seems Google doesn't use the time-zone offset postfix
- throw SysError(L"Modification time could not be parsed. (" + utfTo<std::wstring>(*modifiedTime) + L")");
+ const TimeComp tc = parseTime("%Y-%m-%dT%H:%M:%S", beforeLast(*modifiedTime, '.', IF_MISSING_RETURN_ALL));
+ if (tc == TimeComp() || !endsWith(*modifiedTime, 'Z')) //'Z' means "UTC" => it seems Google doesn't use the time-zone offset postfix
+ throw SysError(L"Modification time could not be parsed. (" + utfTo<std::wstring>(*modifiedTime) + L")");
+
+ itemDetails.modTime = utcToTimeT(tc); //returns -1 on error
+ if (itemDetails.modTime == -1)
+ {
+ if (tc.year == 1600 || //zero-initialized FILETIME equals "December 31, 1600" or "January 1, 1601"
+ tc.year == 1601) // => yes, possible even on Google Drive: https://freefilesync.org/forum/viewtopic.php?t=6602
+ itemDetails.modTime = 0;
+ else
+ throw SysError(L"Modification time could not be parsed. (" + utfTo<std::wstring>(*modifiedTime) + L")");
+ }
for (const auto& parentVal : parents->arrayVal)
{
@@ -1485,9 +1454,8 @@ std::string /*itemId*/ gdriveUploadFile(const Zstring& fileName, const std::stri
{
//https://developers.google.com/drive/api/v3/folder#inserting_a_file_in_a_folder
//https://developers.google.com/drive/api/v3/resumable-upload
- try
- {
- //step 1: initiate resumable upload session
+
+ //step 1: initiate resumable upload session
std::string uploadUrlRelative;
{
std::string postBuf = "{\n";
@@ -1544,13 +1512,13 @@ std::string /*itemId*/ gdriveUploadFile(const Zstring& fileName, const std::stri
//step 2: upload file content
//not officially documented, but Google Drive supports compressed file upload when "Content-Encoding: gzip" is set! :)))
- InputStreamAsGzip gzipStream(readBlock); //throw ZlibInternalError
+ InputStreamAsGzip gzipStream(readBlock); //throw SysError
- auto readBlockAsGzip = [&](void* buffer, size_t bytesToRead) { return gzipStream.read(buffer, bytesToRead); }; //throw ZlibInternalError, X
+ auto readBlockAsGzip = [&](void* buffer, size_t bytesToRead) { return gzipStream.read(buffer, bytesToRead); }; //throw SysError, X
//returns "bytesToRead" bytes unless end of stream! => fits into "0 signals EOF: Posix read() semantics"
std::string response;
- googleHttpsRequest(uploadUrlRelative, { "Content-Encoding: gzip" }, {} /*extraOptions*/, //throw SysError, ZlibInternalError, X
+ googleHttpsRequest(uploadUrlRelative, { "Content-Encoding: gzip" }, {} /*extraOptions*/, //throw SysError, X
[&](const void* buffer, size_t bytesToWrite) { response.append(static_cast<const char*>(buffer), bytesToWrite); }, readBlockAsGzip);
JsonValue jresponse;
@@ -1562,11 +1530,6 @@ std::string /*itemId*/ gdriveUploadFile(const Zstring& fileName, const std::stri
throw SysError(formatGoogleErrorRaw(response));
return *itemId;
- }
- catch (ZlibInternalError&)
- {
- throw SysError(L"zlib internal error");
- }
}
@@ -2254,9 +2217,9 @@ private:
ByteArray zstreamOut;
try
{
- zstreamOut = compress(streamOut.ref(), 3 /*compression level: see db_file.cpp*/); //throw ZlibInternalError
+ zstreamOut = compress(streamOut.ref(), 3 /*compression level: see db_file.cpp*/); //throw SysError
}
- catch (ZlibInternalError&) { throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(dbFilePath)), L"zlib internal error"); }
+ catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(dbFilePath)), e.toString()); }
saveBinContainer(dbFilePath, zstreamOut, nullptr /*notifyUnbufferedIO*/); //throw FileError
}
@@ -2267,9 +2230,9 @@ private:
ByteArray rawStream;
try
{
- rawStream = decompress(zstream); //throw ZlibInternalError
+ rawStream = decompress(zstream); //throw SysError
}
- catch (ZlibInternalError&) { throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(dbFilePath)), L"Zlib internal error"); }
+ catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(dbFilePath)), e.toString()); }
MemoryStreamIn<ByteArray> streamIn(rawStream);
try
diff --git a/FreeFileSync/Source/afs/libcurl/curl_wrap.h b/FreeFileSync/Source/afs/libcurl/curl_wrap.h
index 7a5a4f45..670ad6f5 100644
--- a/FreeFileSync/Source/afs/libcurl/curl_wrap.h
+++ b/FreeFileSync/Source/afs/libcurl/curl_wrap.h
@@ -20,9 +20,9 @@ namespace zen
{
namespace
{
-std::wstring formatCurlErrorRaw(CURLcode ec)
+std::wstring formatCurlStatusCode(CURLcode sc)
{
- switch (ec)
+ switch (sc)
{
ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_OK);
ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_UNSUPPORTED_PROTOCOL);
@@ -118,9 +118,12 @@ std::wstring formatCurlErrorRaw(CURLcode ec)
ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SSL_INVALIDCERTSTATUS);
ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_HTTP2_STREAM);
ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_RECURSIVE_API_CALL);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_AUTH_ERROR);
ZEN_CHECK_CASE_FOR_CONSTANT(CURL_LAST);
}
- return L"Unknown Curl error: " + numberTo<std::wstring>(ec);
+ static_assert(CURL_LAST == CURLE_AUTH_ERROR + 1);
+
+ return replaceCpy<std::wstring>(L"Curl status %x.", L"%x", numberTo<std::wstring>(sc));
}
}
}
diff --git a/FreeFileSync/Source/afs/sftp.cpp b/FreeFileSync/Source/afs/sftp.cpp
index d1276d11..aa5de7eb 100644
--- a/FreeFileSync/Source/afs/sftp.cpp
+++ b/FreeFileSync/Source/afs/sftp.cpp
@@ -167,9 +167,9 @@ std::wstring getSftpDisplayPath(const Zstring& serverName, const AfsPath& afsPat
//===========================================================================================================================
-std::wstring formatSshErrorRaw(int ec)
+std::wstring formatSshStatusCode(int sc)
{
- switch (ec)
+ switch (sc)
{
ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_ERROR_NONE);
ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_ERROR_SOCKET_NONE);
@@ -218,77 +218,72 @@ std::wstring formatSshErrorRaw(int ec)
ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_ERROR_ENCRYPT);
ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_ERROR_BAD_SOCKET);
ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_ERROR_KNOWN_HOSTS);
+ ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_ERROR_CHANNEL_WINDOW_FULL);
+ ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_ERROR_KEYFILE_AUTH_FAILED);
}
- return L"Unknown SSH error: " + numberTo<std::wstring>(ec);
+ return replaceCpy<std::wstring>(L"SSH status %x.", L"%x", numberTo<std::wstring>(sc));
}
-std::wstring formatSftpErrorRaw(unsigned long ec)
+std::wstring formatSftpStatusCode(unsigned long sc)
{
- switch (ec)
- {
- ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_OK);
- ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_EOF);
- ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_NO_SUCH_FILE);
- ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_PERMISSION_DENIED);
- ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_FAILURE);
- ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_BAD_MESSAGE);
- ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_NO_CONNECTION);
- ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_CONNECTION_LOST);
- ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_OP_UNSUPPORTED);
- ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_INVALID_HANDLE);
- ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_NO_SUCH_PATH);
- ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_FILE_ALREADY_EXISTS);
- ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_WRITE_PROTECT);
- ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_NO_MEDIA);
- ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM);
- ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_QUOTA_EXCEEDED);
- ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_UNKNOWN_PRINCIPAL);
- ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_LOCK_CONFLICT);
- ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_DIR_NOT_EMPTY);
- ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_NOT_A_DIRECTORY);
- ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_INVALID_FILENAME);
- ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_LINK_LOOP);
+ switch (sc)
+ {
+ //*INDENT-OFF*
+ ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_OK);
+ ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_EOF);
+ ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_NO_SUCH_FILE);
+ ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_PERMISSION_DENIED);
+ ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_FAILURE);
+ ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_BAD_MESSAGE);
+ ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_NO_CONNECTION);
+ ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_CONNECTION_LOST);
+ ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_OP_UNSUPPORTED);
+ ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_INVALID_HANDLE);
+ ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_NO_SUCH_PATH);
+ ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_FILE_ALREADY_EXISTS);
+ ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_WRITE_PROTECT);
+ ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_NO_MEDIA);
+ ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM);
+ ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_QUOTA_EXCEEDED);
+ ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_UNKNOWN_PRINCIPAL);
+ ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_LOCK_CONFLICT);
+ ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_DIR_NOT_EMPTY);
+ ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_NOT_A_DIRECTORY);
+ ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_INVALID_FILENAME);
+ ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_FX_LINK_LOOP);
//SFTP error codes missing from libssh2: https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.1
- case 22:
- return L"SSH_FX_CANNOT_DELETE";
- case 23:
- return L"SSH_FX_INVALID_PARAMETER";
- case 24:
- return L"SSH_FX_FILE_IS_A_DIRECTORY";
- case 25:
- return L"SSH_FX_BYTE_RANGE_LOCK_CONFLICT";
- case 26:
- return L"SSH_FX_BYTE_RANGE_LOCK_REFUSED";
- case 27:
- return L"SSH_FX_DELETE_PENDING";
- case 28:
- return L"SSH_FX_FILE_CORRUPT";
- case 29:
- return L"SSH_FX_OWNER_INVALID";
- case 30:
- return L"SSH_FX_GROUP_INVALID";
- case 31:
- return L"SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK";
+ case 22: return L"SSH_FX_CANNOT_DELETE";
+ case 23: return L"SSH_FX_INVALID_PARAMETER";
+ case 24: return L"SSH_FX_FILE_IS_A_DIRECTORY";
+ case 25: return L"SSH_FX_BYTE_RANGE_LOCK_CONFLICT";
+ case 26: return L"SSH_FX_BYTE_RANGE_LOCK_REFUSED";
+ case 27: return L"SSH_FX_DELETE_PENDING";
+ case 28: return L"SSH_FX_FILE_CORRUPT";
+ case 29: return L"SSH_FX_OWNER_INVALID";
+ case 30: return L"SSH_FX_GROUP_INVALID";
+ case 31: return L"SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK";
+
+ default: return replaceCpy<std::wstring>(L"SFTP status %x.", L"%x", numberTo<std::wstring>(sc));
+ //*INDENT-ON*
}
- return L"Unknown SFTP error: " + numberTo<std::wstring>(ec);
}
std::wstring formatLastSshError(const std::wstring& functionName, LIBSSH2_SESSION* sshSession, LIBSSH2_SFTP* sftpChannel /*optional*/)
{
char* lastErrorMsg = nullptr; //owned by "sshSession"
- const int lastErrorCode = ::libssh2_session_last_error(sshSession, &lastErrorMsg, nullptr, false /*want_buf*/);
+ const int sshStatusCode = ::libssh2_session_last_error(sshSession, &lastErrorMsg, nullptr, false /*want_buf*/);
assert(lastErrorMsg);
std::wstring errorMsg;
if (lastErrorMsg)
errorMsg = trimCpy(utfTo<std::wstring>(lastErrorMsg));
- if (sftpChannel && lastErrorCode == LIBSSH2_ERROR_SFTP_PROTOCOL)
- errorMsg += (errorMsg.empty() ? L"" : L" - ") + formatSftpErrorRaw(::libssh2_sftp_last_error(sftpChannel));
+ if (sftpChannel && sshStatusCode == LIBSSH2_ERROR_SFTP_PROTOCOL)
+ errorMsg += (errorMsg.empty() ? L"" : L" - ") + formatSftpStatusCode(::libssh2_sftp_last_error(sftpChannel));
- return formatSystemError(functionName, formatSshErrorRaw(lastErrorCode), errorMsg);
+ return formatSystemError(functionName, formatSshStatusCode(sshStatusCode), errorMsg);
}
//===========================================================================================================================
@@ -325,14 +320,14 @@ public:
sshSession_ = ::libssh2_session_init();
if (!sshSession_) //does not set ssh last error; source: only memory allocation may fail
- throw SysError(formatSystemError(L"libssh2_session_init", formatSshErrorRaw(LIBSSH2_ERROR_ALLOC), std::wstring()));
+ throw SysError(formatSystemError(L"libssh2_session_init", formatSshStatusCode(LIBSSH2_ERROR_ALLOC), std::wstring()));
/*
=> libssh2 using zlib crashes for Bitvise Servers: https://freefilesync.org/forum/viewtopic.php?t=2825
=> Don't enable zlib compression: libssh2 also recommends this option disabled: http://comments.gmane.org/gmane.network.ssh.libssh2.devel/6203
const int rc = ::libssh2_session_flag(sshSession_, LIBSSH2_FLAG_COMPRESS, 1); //does not set ssh last error
if (rc != 0)
- throw SysError(formatSystemError(L"libssh2_session_flag", formatSshErrorRaw(rc), std::wstring()));
+ throw SysError(formatSystemError(L"libssh2_session_flag", formatSshStatusCode(rc), std::wstring()));
=> build libssh2 without LIBSSH2_HAVE_ZLIB
*/
@@ -570,7 +565,7 @@ public:
{
if (numeric::dist(std::chrono::steady_clock::now(), nbInfo.commandStartTime) > std::chrono::seconds(timeoutSec))
//consider SSH session corrupted! => isHealthy() will see pending command
- throw FatalSshError(formatSystemError(functionName, formatSshErrorRaw(LIBSSH2_ERROR_TIMEOUT),
+ throw FatalSshError(formatSystemError(functionName, formatSshStatusCode(LIBSSH2_ERROR_TIMEOUT),
_P("Operation timed out after 1 second.", "Operation timed out after %x seconds.", timeoutSec)));
return false;
}
@@ -1586,6 +1581,8 @@ public:
{
try
{
+ warn_static("should we use ~ instead???") //https://curl.haxx.se/docs/faq.html#How_to_SFTP_from_my_user_s_home
+
//we never ever change the SFTP working directory, right? ...right?
return getServerRealPath("."); //throw SysError
}
diff --git a/FreeFileSync/Source/base/db_file.cpp b/FreeFileSync/Source/base/db_file.cpp
index 578b53f8..d27313bc 100644
--- a/FreeFileSync/Source/base/db_file.cpp
+++ b/FreeFileSync/Source/base/db_file.cpp
@@ -187,11 +187,11 @@ public:
7 12.54 3633
8 12.51 9032
9 12.50 19698 (maximal compression) */
- return compress(stream, 3); //throw ZlibInternalError
+ return compress(stream, 3); //throw SysError
}
- catch (ZlibInternalError&)
+ catch (const SysError& e)
{
- throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(displayFilePathL + L"/" + displayFilePathR)), L"zlib internal error");
+ throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(displayFilePathL + L"/" + displayFilePathR)), e.toString());
}
};
@@ -294,11 +294,11 @@ public:
{
try
{
- return decompress(stream); //throw ZlibInternalError
+ return decompress(stream); //throw SysError
}
- catch (ZlibInternalError&)
+ catch (const SysError& e)
{
- throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(displayFilePathL + L"/" + displayFilePathR)), L"Zlib internal error");
+ throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(displayFilePathL + L"/" + displayFilePathR)), e.toString());
}
};
@@ -336,7 +336,7 @@ public:
const size_t size2ndPart = static_cast<size_t>(readNumber<uint64_t>(in2ndPart));
ByteArray tmpB;
- tmpB.resize(size1stPart + size2ndPart); //throw bad_alloc
+ tmpB.resize(size1stPart + size2ndPart); //throw std::bad_alloc
readArray(in1stPart, &*tmpB.begin(), size1stPart); //stream always non-empty
readArray(in2ndPart, &*tmpB.begin() + size1stPart, size2ndPart); //throw UnexpectedEndOfStreamError
@@ -359,7 +359,7 @@ public:
const size_t sizePart2 = static_cast<size_t>(readNumber<uint64_t>(streamInPart2));
ByteArray buf;
- buf.resize(sizePart1 + sizePart2); //throw bad_alloc
+ buf.resize(sizePart1 + sizePart2); //throw std::bad_alloc
if (sizePart1 > 0) readArray(streamInPart1, &*buf.begin(), sizePart1); //throw UnexpectedEndOfStreamError
if (sizePart2 > 0) readArray(streamInPart2, &*buf.begin() + sizePart1, sizePart2); //
@@ -911,7 +911,7 @@ void fff::saveLastSynchronousState(const BaseFolderPair& baseFolder, bool transa
}
else //some MTP devices don't even allow renaming files: https://freefilesync.org/forum/viewtopic.php?t=6531
{
- warn_static("caveat: throw X leaves db file as deleted!")
+ warn_static("caveat: throw X leaves db file as deleted!")
AFS::removeFileIfExists(dbPathL); //throw FileError
saveStreams(streamsL, dbPathL, notifySaveL); //throw FileError, X
@@ -937,14 +937,14 @@ void fff::saveLastSynchronousState(const BaseFolderPair& baseFolder, bool transa
if (transactionalCopy && !AFS::hasNativeTransactionalCopy(dbPathL))
{
- AbstractPath dbPathTmpL2 = AFS::appendRelPath(*AFS::getParentPath(dbPathL), AFS::getItemName(dbPathL) + Zstr('.') + shortGuid + AFS::TEMP_FILE_ENDING);
+ AbstractPath dbPathTmpL2 = AFS::appendRelPath(*AFS::getParentPath(dbPathL), AFS::getItemName(dbPathL) + Zstr('.') + shortGuid + AFS::TEMP_FILE_ENDING);
saveStreams(streamsL, dbPathTmpL2, notifySaveL); //throw FileError, X
dbPathTmpL = dbPathTmpL2;
}
if (transactionalCopy && !AFS::hasNativeTransactionalCopy(dbPathR))
{
- AbstractPath dbPathTmpR2 = AFS::appendRelPath(*AFS::getParentPath(dbPathR), AFS::getItemName(dbPathR) + Zstr('.') + shortGuid + AFS::TEMP_FILE_ENDING);
+ AbstractPath dbPathTmpR2 = AFS::appendRelPath(*AFS::getParentPath(dbPathR), AFS::getItemName(dbPathR) + Zstr('.') + shortGuid + AFS::TEMP_FILE_ENDING);
saveStreams(streamsR, dbPathTmpR2, notifySaveR); //throw FileError, X
dbPathTmpR = dbPathTmpR2;
}
diff --git a/FreeFileSync/Source/base/dir_lock.cpp b/FreeFileSync/Source/base/dir_lock.cpp
index 39245bae..43ffb4b2 100644
--- a/FreeFileSync/Source/base/dir_lock.cpp
+++ b/FreeFileSync/Source/base/dir_lock.cpp
@@ -31,8 +31,44 @@ const std::chrono::seconds DETECT_ABANDONED_INTERVAL(30); //assume abandoned loc
const char LOCK_FORMAT_DESCR[] = "FreeFileSync";
const int LOCK_FORMAT_VER = 2; //lock file format version
+const int ABANDONED_LOCK_LEVEL_MAX = 10;
+}
+
+
+Zstring fff::impl::getLockFilePathForAbandonedLock(const Zstring& lockFilePath) //throw FileError
+{
+ auto it = zen::findLast(lockFilePath.begin(), lockFilePath.end(), FILE_NAME_SEPARATOR);
+ if (it == lockFilePath.end())
+ it = lockFilePath.begin();
+ else
+ ++it;
+
+ const Zstring prefix (lockFilePath.begin(), it);
+ /**/ Zstring fileName( it, lockFilePath.end());
+ int level = 0;
+
+ //recursive abandoned locks!? (almost) impossible, except for file system bugs: https://freefilesync.org/forum/viewtopic.php?t=6568
+ if (startsWith(fileName, Zstr("Delete."))) //e.g. Delete.1.sync.ffs_lock
+ {
+ const Zstring tmp = afterFirst(fileName, Zstr('.'), IF_MISSING_RETURN_NONE);
+
+ const Zstring levelStr = beforeFirst(tmp, Zstr('.'), IF_MISSING_RETURN_NONE);
+ if (!levelStr.empty() && std::all_of(levelStr.begin(), levelStr.end(), [](Zchar c) { return zen::isDigit(c); }))
+ {
+ fileName = afterFirst(tmp, Zstr('.'), IF_MISSING_RETURN_NONE);
+ level = stringTo<int>(levelStr) + 1;
+
+ if (level >= ABANDONED_LOCK_LEVEL_MAX)
+ throw FileError(replaceCpy(_("Cannot delete file %x."), L"%x", fmtPath(lockFilePath)), L"Endless recursion.");
+ }
+ }
+
+ return prefix + Zstr("Delete.") + numberTo<Zstring>(level) + Zstr(".") + fileName; //preserve lock file extension!
+}
+namespace
+{
//worker thread
class LifeSigns
{
@@ -69,16 +105,6 @@ private:
};
-Zstring abandonedLockDeletionName(const Zstring& lockFilePath) //make sure to NOT change file ending!
-{
- const size_t pos = lockFilePath.rfind(FILE_NAME_SEPARATOR); //search from end
- return pos == Zstring::npos ? Zstr("Del.") + lockFilePath :
- Zstring(lockFilePath.c_str(), pos + 1) + //include path separator
- Zstr("Del.") +
- afterLast(lockFilePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL);
-}
-
-
using ProcessId = pid_t;
@@ -303,7 +329,7 @@ void waitOnDirLock(const Zstring& lockFilePath, const DirLockCallback& notifySta
if (lockOwnderDead || //no need to wait any longer...
lastCheckTime >= lastLifeSign + DETECT_ABANDONED_INTERVAL)
{
- DirLock guardDeletion(abandonedLockDeletionName(lockFilePath), notifyStatus, cbInterval); //throw FileError
+ DirLock guardDeletion(fff::impl::getLockFilePathForAbandonedLock(lockFilePath), notifyStatus, cbInterval); //throw FileError
//now that the lock is in place check existence again: meanwhile another process may have deleted and created a new lock!
std::string currentLockId;
diff --git a/FreeFileSync/Source/base/dir_lock.h b/FreeFileSync/Source/base/dir_lock.h
index 20795804..1895c0e7 100644
--- a/FreeFileSync/Source/base/dir_lock.h
+++ b/FreeFileSync/Source/base/dir_lock.h
@@ -31,13 +31,21 @@ using DirLockCallback = std::function<void(const std::wstring& msg)>; //throw X
class DirLock
{
public:
- DirLock(const Zstring& lockFilePath, const DirLockCallback& notifyStatus, std::chrono::milliseconds cbInterval); //throw FileError, callback only used during construction
+ DirLock(const Zstring& lockFilePath, //throw FileError
+ const DirLockCallback& notifyStatus, //callback only used during construction
+ std::chrono::milliseconds cbInterval); //
private:
class LockAdmin;
class SharedDirLock;
std::shared_ptr<SharedDirLock> sharedLock_;
};
+
+
+namespace impl //declare for unit tests:
+{
+Zstring getLockFilePathForAbandonedLock(const Zstring& lockFilePath); //throw FileError
+}
}
#endif //DIR_LOCK_H_81740832174954356
diff --git a/FreeFileSync/Source/base/lock_holder.h b/FreeFileSync/Source/base/lock_holder.h
index 7bc470ba..fb1679bb 100644
--- a/FreeFileSync/Source/base/lock_holder.h
+++ b/FreeFileSync/Source/base/lock_holder.h
@@ -24,17 +24,17 @@ public:
{
using namespace zen;
- std::map<Zstring, FileError> failedLocks;
+ std::vector<std::pair<Zstring, FileError>> failedLocks;
for (const Zstring& folderPath : folderPaths)
try
{
- //lock file creation is synchronous and may block noticeably for very slow devices (usb sticks, mapped cloud storages)
+ //lock file creation is synchronous and may block noticeably for very slow devices (USB sticks, mapped cloud storage)
lockHolder_.emplace_back(appendSeparator(folderPath) + Zstr("sync") + LOCK_FILE_ENDING,
[&](const std::wstring& msg) { pcb.reportStatus(msg); /*throw X*/ },
UI_UPDATE_INTERVAL / 2); //throw FileError
}
- catch (const FileError& e) { failedLocks.emplace(folderPath, e); }
+ catch (const FileError& e) { failedLocks.emplace_back(folderPath, e); }
if (!failedLocks.empty())
{
@@ -42,8 +42,9 @@ public:
for (const auto& [folderPath, error] : failedLocks)
{
- msg += L"\n\n" + fmtPath(folderPath);
- msg += L"\n" + replaceCpy(error.toString(), L"\n\n", L"\n");
+ msg += L"\n\n";
+ //msg += fmtPath(folderPath) + L"\n" -> seems redundant
+ msg += replaceCpy(error.toString(), L"\n\n", L"\n");
}
pcb.reportWarning(msg, warnDirectoryLockFailed); //throw X
diff --git a/FreeFileSync/Source/base/parallel_scan.cpp b/FreeFileSync/Source/base/parallel_scan.cpp
index 1853355a..df2839fb 100644
--- a/FreeFileSync/Source/base/parallel_scan.cpp
+++ b/FreeFileSync/Source/base/parallel_scan.cpp
@@ -17,6 +17,8 @@ using namespace fff;
namespace
{
+const int FOLDER_TRAVERSAL_LEVEL_MAX = 100;
+
/* PERF NOTE
---------------------------------------------
@@ -322,7 +324,7 @@ std::shared_ptr<AFS::TraverserCallback> DirCallback::onFolder(const AFS::FolderI
cfg_.acb.incItemsScanned(); //add 1 element to the progress indicator
//------------------------------------------------------------------------------------
- if (level_ > 100) //Win32 traverser: stack overflow approximately at level 1000
+ if (level_ > FOLDER_TRAVERSAL_LEVEL_MAX) //Win32 traverser: stack overflow approximately at level 1000
//check after FolderContainer::addSubFolder()
for (size_t retryNumber = 0;; ++retryNumber)
switch (reportItemError(replaceCpy(_("Cannot read directory %x."), L"%x", AFS::getDisplayPath(AFS::appendRelPath(cfg_.baseFolderPath, relPath))) +
diff --git a/FreeFileSync/Source/base/resolve_path.cpp b/FreeFileSync/Source/base/resolve_path.cpp
index 89000583..1799acb5 100644
--- a/FreeFileSync/Source/base/resolve_path.cpp
+++ b/FreeFileSync/Source/base/resolve_path.cpp
@@ -107,10 +107,11 @@ std::optional<Zstring> tryResolveMacro(const Zstring& macro) //macro without %-c
return true;
};
+ //https://en.cppreference.com/w/cpp/chrono/c/strftime
if (resolveTimePhrase(Zstr("weekday"), Zstr("%A"))) return timeStr;
if (resolveTimePhrase(Zstr("day" ), Zstr("%d"))) return timeStr;
if (resolveTimePhrase(Zstr("month" ), Zstr("%m"))) return timeStr;
- if (resolveTimePhrase(Zstr("week" ), Zstr("%U"))) return timeStr;
+ if (resolveTimePhrase(Zstr("week" ), Zstr("%V"))) return timeStr; //ISO 8601 week of the year
if (resolveTimePhrase(Zstr("year" ), Zstr("%Y"))) return timeStr;
if (resolveTimePhrase(Zstr("hour" ), Zstr("%H"))) return timeStr;
if (resolveTimePhrase(Zstr("min" ), Zstr("%M"))) return timeStr;
diff --git a/FreeFileSync/Source/base/synchronization.cpp b/FreeFileSync/Source/base/synchronization.cpp
index bd407296..33923cc6 100644
--- a/FreeFileSync/Source/base/synchronization.cpp
+++ b/FreeFileSync/Source/base/synchronization.cpp
@@ -2533,7 +2533,7 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime
L" " + AFS::getDisplayPath(baseFolder.getAbstractPath<RIGHT_SIDE>()));
//------------------------------------------------------------------------------------------
- //checking a second time: (a long time may have passed since folder comparison!)
+ //checking a second time: (a long time may have passed since syncing the previous folder pairs!)
if (baseFolderDrop< LEFT_SIDE>(baseFolder, callback) ||
baseFolderDrop<RIGHT_SIDE>(baseFolder, callback))
continue;
diff --git a/FreeFileSync/Source/ui/gui_generated.cpp b/FreeFileSync/Source/ui/gui_generated.cpp
index d8e104bc..4a7d822a 100644
--- a/FreeFileSync/Source/ui/gui_generated.cpp
+++ b/FreeFileSync/Source/ui/gui_generated.cpp
@@ -1832,7 +1832,7 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w
m_panelFilterSettingsTab->SetSizer( bSizer278 );
m_panelFilterSettingsTab->Layout();
bSizer278->Fit( m_panelFilterSettingsTab );
- m_notebook->AddPage( m_panelFilterSettingsTab, _("dummy"), true );
+ m_notebook->AddPage( m_panelFilterSettingsTab, _("dummy"), false );
m_panelSyncSettingsTab = new wxPanel( m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
m_panelSyncSettingsTab->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) );
@@ -2347,7 +2347,7 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w
m_panelSyncSettingsTab->SetSizer( bSizer276 );
m_panelSyncSettingsTab->Layout();
bSizer276->Fit( m_panelSyncSettingsTab );
- m_notebook->AddPage( m_panelSyncSettingsTab, _("dummy"), false );
+ m_notebook->AddPage( m_panelSyncSettingsTab, _("dummy"), true );
bSizer190->Add( m_notebook, 1, wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 5 );
@@ -5341,3 +5341,116 @@ CfgHighlightDlgGenerated::CfgHighlightDlgGenerated( wxWindow* parent, wxWindowID
CfgHighlightDlgGenerated::~CfgHighlightDlgGenerated()
{
}
+
+WarnAccessRightsMissingDlgGenerated::WarnAccessRightsMissingDlgGenerated( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style )
+{
+ this->SetSizeHints( wxSize( -1, -1 ), wxDefaultSize );
+ this->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) );
+
+ wxBoxSizer* bSizer330;
+ bSizer330 = new wxBoxSizer( wxHORIZONTAL );
+
+ m_bitmapGrantAccess = new wxStaticBitmap( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 );
+ bSizer330->Add( m_bitmapGrantAccess, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT, 10 );
+
+ wxBoxSizer* bSizer95;
+ bSizer95 = new wxBoxSizer( wxVERTICAL );
+
+ m_staticTextDescr = new wxStaticText( this, wxID_ANY, _("FreeFileSync requires access rights to avoid \"Operation not permitted\" errors when synchronizing your data (e.g. Mail, Messages, Calendars)."), wxDefaultPosition, wxSize( -1, -1 ), 0 );
+ m_staticTextDescr->Wrap( -1 );
+ bSizer95->Add( m_staticTextDescr, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 10 );
+
+ m_staticline20 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
+ bSizer95->Add( m_staticline20, 0, wxEXPAND, 5 );
+
+ m_panel39 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
+ m_panel39->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) );
+
+ wxBoxSizer* bSizer166;
+ bSizer166 = new wxBoxSizer( wxVERTICAL );
+
+ ffgSizer11 = new wxFlexGridSizer( 0, 2, 5, 5 );
+ ffgSizer11->SetFlexibleDirection( wxBOTH );
+ ffgSizer11->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
+
+ m_staticTextStep1 = new wxStaticText( m_panel39, wxID_ANY, _("1."), wxDefaultPosition, wxDefaultSize, 0 );
+ m_staticTextStep1->Wrap( -1 );
+ ffgSizer11->Add( m_staticTextStep1, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 );
+
+ m_buttonLocateBundle = new wxButton( m_panel39, wxID_ANY, _("Locate the FreeFileSync app"), wxDefaultPosition, wxDefaultSize, 0 );
+ ffgSizer11->Add( m_buttonLocateBundle, 0, wxALIGN_CENTER_VERTICAL|wxEXPAND, 5 );
+
+ m_staticTextStep2 = new wxStaticText( m_panel39, wxID_ANY, _("2."), wxDefaultPosition, wxDefaultSize, 0 );
+ m_staticTextStep2->Wrap( -1 );
+ ffgSizer11->Add( m_staticTextStep2, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 );
+
+ m_buttonOpenSecurity = new wxButton( m_panel39, wxID_ANY, _("Open Security && Privacy"), wxDefaultPosition, wxDefaultSize, 0 );
+ ffgSizer11->Add( m_buttonOpenSecurity, 0, wxALIGN_CENTER_VERTICAL|wxEXPAND, 5 );
+
+ m_staticTextStep3 = new wxStaticText( m_panel39, wxID_ANY, _("3."), wxDefaultPosition, wxDefaultSize, 0 );
+ m_staticTextStep3->Wrap( -1 );
+ ffgSizer11->Add( m_staticTextStep3, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 );
+
+ m_staticTextAllowChanges = new wxStaticText( m_panel39, wxID_ANY, _("Click the lock to allow changes."), wxDefaultPosition, wxDefaultSize, 0 );
+ m_staticTextAllowChanges->Wrap( -1 );
+ ffgSizer11->Add( m_staticTextAllowChanges, 0, wxALIGN_CENTER_VERTICAL, 5 );
+
+ m_staticTextStep4 = new wxStaticText( m_panel39, wxID_ANY, _("4."), wxDefaultPosition, wxDefaultSize, 0 );
+ m_staticTextStep4->Wrap( -1 );
+ ffgSizer11->Add( m_staticTextStep4, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 );
+
+ m_staticTextGrantAccess = new wxStaticText( m_panel39, wxID_ANY, _("Drag FreeFileSync into the panel."), wxDefaultPosition, wxDefaultSize, 0 );
+ m_staticTextGrantAccess->Wrap( -1 );
+ ffgSizer11->Add( m_staticTextGrantAccess, 0, wxALIGN_CENTER_VERTICAL, 5 );
+
+
+ bSizer166->Add( ffgSizer11, 0, wxALL|wxALIGN_CENTER_HORIZONTAL, 10 );
+
+
+ m_panel39->SetSizer( bSizer166 );
+ m_panel39->Layout();
+ bSizer166->Fit( m_panel39 );
+ bSizer95->Add( m_panel39, 1, wxEXPAND, 5 );
+
+ m_staticline36 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
+ bSizer95->Add( m_staticline36, 0, wxEXPAND, 5 );
+
+ wxBoxSizer* bSizer25;
+ bSizer25 = new wxBoxSizer( wxVERTICAL );
+
+ m_checkBoxDontShowAgain = new wxCheckBox( this, wxID_ANY, _("&Don't show this dialog again"), wxDefaultPosition, wxDefaultSize, 0 );
+ bSizer25->Add( m_checkBoxDontShowAgain, 0, wxALIGN_CENTER_HORIZONTAL|wxALL, 5 );
+
+ bSizerStdButtons = new wxBoxSizer( wxHORIZONTAL );
+
+ m_buttonClose = new wxButton( this, wxID_OK, _("Close"), wxDefaultPosition, wxSize( -1, -1 ), 0 );
+ m_buttonClose->SetDefault();
+ bSizerStdButtons->Add( m_buttonClose, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT|wxLEFT, 5 );
+
+
+ bSizer25->Add( bSizerStdButtons, 0, wxALIGN_RIGHT, 5 );
+
+
+ bSizer95->Add( bSizer25, 0, wxEXPAND, 5 );
+
+
+ bSizer330->Add( bSizer95, 1, wxEXPAND, 5 );
+
+
+ this->SetSizer( bSizer330 );
+ this->Layout();
+ bSizer330->Fit( this );
+
+ this->Centre( wxBOTH );
+
+ // Connect Events
+ this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( WarnAccessRightsMissingDlgGenerated::OnClose ) );
+ m_buttonLocateBundle->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( WarnAccessRightsMissingDlgGenerated::OnShowAppBundle ), NULL, this );
+ m_buttonOpenSecurity->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( WarnAccessRightsMissingDlgGenerated::OnOpenSecuritySettings ), NULL, this );
+ m_checkBoxDontShowAgain->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( WarnAccessRightsMissingDlgGenerated::OnCheckBoxClick ), NULL, this );
+ m_buttonClose->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( WarnAccessRightsMissingDlgGenerated::OnOK ), NULL, this );
+}
+
+WarnAccessRightsMissingDlgGenerated::~WarnAccessRightsMissingDlgGenerated()
+{
+}
diff --git a/FreeFileSync/Source/ui/gui_generated.h b/FreeFileSync/Source/ui/gui_generated.h
index 616cf0bb..41febc65 100644
--- a/FreeFileSync/Source/ui/gui_generated.h
+++ b/FreeFileSync/Source/ui/gui_generated.h
@@ -1295,4 +1295,45 @@ public:
};
+///////////////////////////////////////////////////////////////////////////////
+/// Class WarnAccessRightsMissingDlgGenerated
+///////////////////////////////////////////////////////////////////////////////
+class WarnAccessRightsMissingDlgGenerated : public wxDialog
+{
+private:
+
+protected:
+ wxStaticBitmap* m_bitmapGrantAccess;
+ wxStaticText* m_staticTextDescr;
+ wxStaticLine* m_staticline20;
+ wxPanel* m_panel39;
+ wxFlexGridSizer* ffgSizer11;
+ wxStaticText* m_staticTextStep1;
+ wxButton* m_buttonLocateBundle;
+ wxStaticText* m_staticTextStep2;
+ wxButton* m_buttonOpenSecurity;
+ wxStaticText* m_staticTextStep3;
+ wxStaticText* m_staticTextAllowChanges;
+ wxStaticText* m_staticTextStep4;
+ wxStaticText* m_staticTextGrantAccess;
+ wxStaticLine* m_staticline36;
+ wxCheckBox* m_checkBoxDontShowAgain;
+ wxBoxSizer* bSizerStdButtons;
+ wxButton* m_buttonClose;
+
+ // Virtual event handlers, overide them in your derived class
+ virtual void OnClose( wxCloseEvent& event ) { event.Skip(); }
+ virtual void OnShowAppBundle( wxCommandEvent& event ) { event.Skip(); }
+ virtual void OnOpenSecuritySettings( wxCommandEvent& event ) { event.Skip(); }
+ virtual void OnCheckBoxClick( wxCommandEvent& event ) { event.Skip(); }
+ virtual void OnOK( wxCommandEvent& event ) { event.Skip(); }
+
+
+public:
+
+ WarnAccessRightsMissingDlgGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("Grant Full Disk Access"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE );
+ ~WarnAccessRightsMissingDlgGenerated();
+
+};
+
#endif //__GUI_GENERATED_H__
diff --git a/FreeFileSync/Source/ui/main_dlg.cpp b/FreeFileSync/Source/ui/main_dlg.cpp
index d4f3aa5c..1721245a 100644
--- a/FreeFileSync/Source/ui/main_dlg.cpp
+++ b/FreeFileSync/Source/ui/main_dlg.cpp
@@ -736,6 +736,7 @@ MainDialog::MainDialog(const Zstring& globalConfigFilePath,
if (!selectedRows.empty())
m_gridCfgHistory->makeRowVisible(selectedRows.front());
+
m_buttonCompare->SetFocus();
//----------------------------------------------------------------------------------------------------------------------------------------------------------------
diff --git a/FreeFileSync/Source/ui/progress_indicator.cpp b/FreeFileSync/Source/ui/progress_indicator.cpp
index f3339193..394fb60e 100644
--- a/FreeFileSync/Source/ui/progress_indicator.cpp
+++ b/FreeFileSync/Source/ui/progress_indicator.cpp
@@ -193,7 +193,7 @@ CompareProgressDialog::Impl::Impl(wxFrame& parentWindow) :
m_panelProgressGraph->setAttributes(Graph2D::MainAttributes().setMinY(0).setMaxY(2).
setLabelX(Graph2D::LABEL_X_NONE).
setLabelY(Graph2D::LABEL_Y_NONE).
- setBackgroundColor(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)).
+ setBaseColors(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT), wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)).
setSelectionMode(Graph2D::SELECT_NONE));
m_panelProgressGraph->addCurve(curveDataBytes_, Graph2D::CurveAttributes().setLineWidth(1).fillPolygonArea(getColorBytes()).setColor(Graph2D::getBorderColor()));
@@ -494,32 +494,34 @@ private:
class CurveDataTotalBlock : public CurveData
{
public:
- void setValue (double x, double y) { x_ = x; y_ = y; }
- void setValueX(double x) { x_ = x; }
- double getValueX() const { return x_; }
+ void setValue(double x1, double x2, double y) { x1_ = x1; x2_ = x2; y_ = y; }
+ void setTimes(double x1, double x2) { x1_ = x1; x2_ = x2; }
+ double getTotalTime() const { return x2_; }
private:
- std::pair<double, double> getRangeX() const override { return { x_, x_ }; } //conceptually just a vertical line!
+ std::pair<double, double> getRangeX() const override { return { x1_, x2_ }; }
std::vector<CurvePoint> getPoints(double minX, double maxX, const wxSize& areaSizePx) const override
{
return
{
- { 0, y_ },
- { x_, y_ },
- { x_, 0 },
+ { x1_, 0 },
+ { x1_, y_ },
+ { x2_, y_ },
+ { x2_, 0 },
};
}
- double x_ = 0; //time elapsed in seconds
- double y_ = 0; //items/bytes processed
+ double x1_ = 0; //elapsed time [s]
+ double x2_ = 0; //total time [s] (estimated)
+ double y_ = 0; //items/bytes total
};
class CurveDataProcessedBlock : public CurveData
{
public:
- void setValue(double x1, double x2, double y) { x1_ = x1; x2_ = x2; y_ = y; }
+ void setValue(double x1, double x2, double y1, double y2) { x1_ = x1; x2_ = x2; y1_ = y1; y2_ = y2; }
private:
std::pair<double, double> getRangeX() const override { return { x1_, x2_ }; }
@@ -528,17 +530,17 @@ private:
{
return
{
- { 0, y_ },
- { x1_, y_ },
- { x1_, 0 },
- { x1_, y_ },
- { x2_, y_ },
+ { x1_, 0 },
+ { x1_, y2_ },
+ { x1_, y1_ },
+ { x2_, y1_ },
};
}
- double x1_ = 0; //time elapsed in seconds
- double x2_ = 0; //total time (estimated)
- double y_ = 0; //items/bytes processed
+ double x1_ = 0; //elapsed time [s]
+ double x2_ = 0; //total time [s] (estimated)
+ double y1_ = 0; //items/bytes processed
+ double y2_ = 0; //items/bytes total
};
@@ -823,26 +825,26 @@ SyncProgressDialogImpl<TopLevelDialog>::SyncProgressDialogImpl(long style, //wxF
const int xLabelHeight = this->GetCharHeight() + fastFromDIP(2) /*margin*/; //use same height for both graphs to make sure they stretch evenly
const int yLabelWidth = fastFromDIP(70);
pnl_.m_panelGraphBytes->setAttributes(Graph2D::MainAttributes().
- setLabelX(Graph2D::LABEL_X_TOP, xLabelHeight, std::make_shared<LabelFormatterTimeElapsed>(true)).
- setLabelY(Graph2D::LABEL_Y_RIGHT, yLabelWidth, std::make_shared<LabelFormatterBytes>()).
- setBackgroundColor(wxColor(208, 208, 208)). //light grey
+ setLabelX(Graph2D::LABEL_X_TOP, xLabelHeight, std::make_shared<LabelFormatterTimeElapsed>(true)).
+ setLabelY(Graph2D::LABEL_Y_RIGHT, yLabelWidth, std::make_shared<LabelFormatterBytes>()).
+ setBaseColors(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT), wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)).
setSelectionMode(Graph2D::SELECT_NONE));
pnl_.m_panelGraphItems->setAttributes(Graph2D::MainAttributes().
setLabelX(Graph2D::LABEL_X_BOTTOM, xLabelHeight, std::make_shared<LabelFormatterTimeElapsed>(true)).
setLabelY(Graph2D::LABEL_Y_RIGHT, yLabelWidth, std::make_shared<LabelFormatterItemCount>()).
- setBackgroundColor(wxColor(208, 208, 208)). //light grey
+ setBaseColors(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT), wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)).
setSelectionMode(Graph2D::SELECT_NONE));
pnl_.m_panelGraphBytes->setCurve(curveDataBytesTotal_, Graph2D::CurveAttributes().setLineWidth(1).fillCurveArea(*wxWHITE).setColor(wxColor(192, 192, 192))); //medium grey
pnl_.m_panelGraphItems->setCurve(curveDataItemsTotal_, Graph2D::CurveAttributes().setLineWidth(1).fillCurveArea(*wxWHITE).setColor(wxColor(192, 192, 192))); //medium grey
- pnl_.m_panelGraphBytes->addCurve(curveDataBytesCurrent_, Graph2D::CurveAttributes().setLineWidth(1).fillCurveArea(getColorBytesBackground()).setColor(getColorBytesBackgroundRim()));
- pnl_.m_panelGraphItems->addCurve(curveDataItemsCurrent_, Graph2D::CurveAttributes().setLineWidth(1).fillCurveArea(getColorItemsBackground()).setColor(getColorItemsBackgroundRim()));
-
pnl_.m_panelGraphBytes->addCurve(curveDataBytes_, Graph2D::CurveAttributes().setLineWidth(2).fillCurveArea(getColorBytes()).setColor(getColorBytesRim()));
pnl_.m_panelGraphItems->addCurve(curveDataItems_, Graph2D::CurveAttributes().setLineWidth(2).fillCurveArea(getColorItems()).setColor(getColorItemsRim()));
+ pnl_.m_panelGraphBytes->addCurve(curveDataBytesCurrent_, Graph2D::CurveAttributes().setLineWidth(2).fillCurveArea(getColorBytesBackground()).setColor(getColorBytesBackgroundRim()));
+ pnl_.m_panelGraphItems->addCurve(curveDataItemsCurrent_, Graph2D::CurveAttributes().setLineWidth(2).fillCurveArea(getColorItemsBackground()).setColor(getColorItemsBackgroundRim()));
+
//graph legend:
auto generateSquareBitmap = [&](const wxColor& fillCol, const wxColor& borderCol)
{
@@ -940,10 +942,10 @@ void SyncProgressDialogImpl<TopLevelDialog>::initNewPhase()
updateStaticGui(); //evaluates "syncStat_->currentPhase()"
//reset graphs (e.g. after binary comparison)
- curveDataBytesTotal_ ->setValue(0, 0);
- curveDataItemsTotal_ ->setValue(0, 0);
- curveDataBytesCurrent_->setValue(0, 0, 0);
- curveDataItemsCurrent_->setValue(0, 0, 0);
+ curveDataBytesTotal_ ->setTimes(0, 0);
+ curveDataItemsTotal_ ->setTimes(0, 0);
+ curveDataBytesCurrent_->setValue(0, 0, 0, 0);
+ curveDataItemsCurrent_->setValue(0, 0, 0, 0);
curveDataBytes_ ->clear();
curveDataItems_ ->clear();
@@ -1057,15 +1059,15 @@ void SyncProgressDialogImpl<TopLevelDialog>::updateProgressGui(bool allowYield)
if (trayIcon_.get()) trayIcon_->setProgress(fractionTotal);
if (taskbar_ .get()) taskbar_ ->setProgress(fractionTotal);
- const double timeTotalSecTentative = bytesCurrent == bytesTotal ? timeElapsedDouble : std::max(curveDataBytesTotal_->getValueX(), timeElapsedDouble);
+ const double timeTotalSecTentative = bytesCurrent == bytesTotal ? timeElapsedDouble : std::max(curveDataBytesTotal_->getTotalTime(), timeElapsedDouble);
//constant line graph
- curveDataBytesCurrent_->setValue(timeElapsedDouble, timeTotalSecTentative, bytesCurrent);
- curveDataItemsCurrent_->setValue(timeElapsedDouble, timeTotalSecTentative, itemsCurrent);
+ curveDataBytesCurrent_->setValue(timeElapsedDouble, timeTotalSecTentative, bytesCurrent, bytesTotal);
+ curveDataItemsCurrent_->setValue(timeElapsedDouble, timeTotalSecTentative, itemsCurrent, itemsTotal);
//tentatively update total time, may be improved on below:
- curveDataBytesTotal_->setValue(timeTotalSecTentative, bytesTotal);
- curveDataItemsTotal_->setValue(timeTotalSecTentative, itemsTotal);
+ curveDataBytesTotal_->setValue(timeElapsedDouble, timeTotalSecTentative, bytesTotal);
+ curveDataItemsTotal_->setValue(timeElapsedDouble, timeTotalSecTentative, itemsTotal);
}
//even though notifyProgressChange() already set the latest data, let's add another sample to have all curves consider "timeNowMs"
@@ -1129,13 +1131,13 @@ void SyncProgressDialogImpl<TopLevelDialog>::updateProgressGui(bool allowYield)
//update estimated total time marker with precision of "10% remaining time" only to avoid needless jumping around:
const double timeRemainingSec = remTimeSec ? *remTimeSec : 0;
const double timeTotalSec = timeElapsedDouble + timeRemainingSec;
- if (numeric::dist(curveDataBytesTotal_->getValueX(), timeTotalSec) > 0.1 * timeRemainingSec)
+ if (numeric::dist(curveDataBytesTotal_->getTotalTime(), timeTotalSec) > 0.1 * timeRemainingSec)
{
- curveDataBytesTotal_->setValueX(timeTotalSec);
- curveDataItemsTotal_->setValueX(timeTotalSec);
+ curveDataBytesTotal_->setTimes(timeElapsedDouble, timeTotalSec);
+ curveDataItemsTotal_->setTimes(timeElapsedDouble, timeTotalSec);
//don't forget to update these, too:
- curveDataBytesCurrent_->setValue(timeElapsedDouble, timeTotalSec, bytesCurrent);
- curveDataItemsCurrent_->setValue(timeElapsedDouble, timeTotalSec, itemsCurrent);
+ curveDataBytesCurrent_->setValue(timeElapsedDouble, timeTotalSec, bytesCurrent, bytesTotal);
+ curveDataItemsCurrent_->setValue(timeElapsedDouble, timeTotalSec, itemsCurrent, itemsTotal);
}
}
}
diff --git a/FreeFileSync/Source/ui/small_dlgs.cpp b/FreeFileSync/Source/ui/small_dlgs.cpp
index 97942754..44a4d7c2 100644
--- a/FreeFileSync/Source/ui/small_dlgs.cpp
+++ b/FreeFileSync/Source/ui/small_dlgs.cpp
@@ -45,11 +45,12 @@
-
using namespace zen;
using namespace fff;
+namespace
+{
class AboutDlg : public AboutDlgGenerated
{
public:
@@ -167,16 +168,18 @@ void AboutDlg::onLocalKeyEvent(wxKeyEvent& event) //process key events without e
{
event.Skip();
}
-
+}
void fff::showAboutDialog(wxWindow* parent)
{
- AboutDlg aboutDlg(parent);
- aboutDlg.ShowModal();
+ AboutDlg dlg(parent);
+ dlg.ShowModal();
}
//########################################################################################
+namespace
+{
class CloudSetupDlg : public CloudSetupDlgGenerated
{
public:
@@ -661,16 +664,18 @@ void CloudSetupDlg::OnOkay(wxCommandEvent& event)
EndModal(ReturnSmallDlg::BUTTON_OKAY);
}
-
+}
ReturnSmallDlg::ButtonPressed fff::showCloudSetupDialog(wxWindow* parent, Zstring& folderPathPhrase, size_t& parallelOps, const std::wstring* parallelOpsDisabledReason)
{
- CloudSetupDlg setupDlg(parent, folderPathPhrase, parallelOps, parallelOpsDisabledReason);
- return static_cast<ReturnSmallDlg::ButtonPressed>(setupDlg.ShowModal());
+ CloudSetupDlg dlg(parent, folderPathPhrase, parallelOps, parallelOpsDisabledReason);
+ return static_cast<ReturnSmallDlg::ButtonPressed>(dlg.ShowModal());
}
//########################################################################################
+namespace
+{
class CopyToDialog : public CopyToDlgGenerated
{
public:
@@ -788,7 +793,7 @@ void CopyToDialog::OnOK(wxCommandEvent& event)
EndModal(ReturnSmallDlg::BUTTON_OKAY);
}
-
+}
ReturnSmallDlg::ButtonPressed fff::showCopyToDialog(wxWindow* parent,
std::span<const FileSystemObject* const> rowsOnLeft,
@@ -811,6 +816,8 @@ ReturnSmallDlg::ButtonPressed fff::showCopyToDialog(wxWindow* parent,
//########################################################################################
+namespace
+{
class DeleteDialog : public DeleteDlgGenerated
{
public:
@@ -921,19 +928,21 @@ void DeleteDialog::OnOK(wxCommandEvent& event)
EndModal(ReturnSmallDlg::BUTTON_OKAY);
}
-
+}
ReturnSmallDlg::ButtonPressed fff::showDeleteDialog(wxWindow* parent,
std::span<const FileSystemObject* const> rowsOnLeft,
std::span<const FileSystemObject* const> rowsOnRight,
bool& useRecycleBin)
{
- DeleteDialog confirmDeletion(parent, rowsOnLeft, rowsOnRight, useRecycleBin);
- return static_cast<ReturnSmallDlg::ButtonPressed>(confirmDeletion.ShowModal());
+ DeleteDialog dlg(parent, rowsOnLeft, rowsOnRight, useRecycleBin);
+ return static_cast<ReturnSmallDlg::ButtonPressed>(dlg.ShowModal());
}
//########################################################################################
+namespace
+{
class SyncConfirmationDlg : public SyncConfirmationDlgGenerated
{
public:
@@ -1020,7 +1029,7 @@ void SyncConfirmationDlg::OnStartSync(wxCommandEvent& event)
dontShowAgainOut_ = m_checkBoxDontShowAgain->GetValue();
EndModal(ReturnSmallDlg::BUTTON_OKAY);
}
-
+}
ReturnSmallDlg::ButtonPressed fff::showSyncConfirmationDlg(wxWindow* parent,
bool syncSelection,
@@ -1038,6 +1047,8 @@ ReturnSmallDlg::ButtonPressed fff::showSyncConfirmationDlg(wxWindow* parent,
//########################################################################################
+namespace
+{
class OptionsDlg : public OptionsDlgGenerated
{
public:
@@ -1347,7 +1358,7 @@ void OptionsDlg::OnShowLogFolder(wxHyperlinkEvent& event)
}
catch (const FileError& e) { showNotificationDialog(this, DialogInfoType::error, PopupDialogCfg().setDetailInstructions(e.toString())); }
}
-
+}
ReturnSmallDlg::ButtonPressed fff::showOptionsDlg(wxWindow* parent, XmlGlobalSettings& globalCfg)
{
@@ -1357,6 +1368,8 @@ ReturnSmallDlg::ButtonPressed fff::showOptionsDlg(wxWindow* parent, XmlGlobalSet
//########################################################################################
+namespace
+{
class SelectTimespanDlg : public SelectTimespanDlgGenerated
{
public:
@@ -1446,16 +1459,18 @@ void SelectTimespanDlg::OnOkay(wxCommandEvent& event)
EndModal(ReturnSmallDlg::BUTTON_OKAY);
}
-
+}
ReturnSmallDlg::ButtonPressed fff::showSelectTimespanDlg(wxWindow* parent, time_t& timeFrom, time_t& timeTo)
{
- SelectTimespanDlg timeSpanDlg(parent, timeFrom, timeTo);
- return static_cast<ReturnSmallDlg::ButtonPressed>(timeSpanDlg.ShowModal());
+ SelectTimespanDlg dlg(parent, timeFrom, timeTo);
+ return static_cast<ReturnSmallDlg::ButtonPressed>(dlg.ShowModal());
}
//########################################################################################
+namespace
+{
class CfgHighlightDlg : public CfgHighlightDlgGenerated
{
public:
@@ -1500,16 +1515,18 @@ void CfgHighlightDlg::OnOkay(wxCommandEvent& event)
cfgHistSyncOverdueDaysOut_ = m_spinCtrlOverdueDays->GetValue();
EndModal(ReturnSmallDlg::BUTTON_OKAY);
}
-
+}
ReturnSmallDlg::ButtonPressed fff::showCfgHighlightDlg(wxWindow* parent, int& cfgHistSyncOverdueDays)
{
- CfgHighlightDlg cfgHighDlg(parent, cfgHistSyncOverdueDays);
- return static_cast<ReturnSmallDlg::ButtonPressed>(cfgHighDlg.ShowModal());
+ CfgHighlightDlg dlg(parent, cfgHistSyncOverdueDays);
+ return static_cast<ReturnSmallDlg::ButtonPressed>(dlg.ShowModal());
}
//########################################################################################
+namespace
+{
class ActivationDlg : public ActivationDlgGenerated
{
public:
@@ -1580,7 +1597,7 @@ void ActivationDlg::OnActivateOffline(wxCommandEvent& event)
manualActivationKeyOut_ = m_textCtrlOfflineActivationKey->GetValue();
EndModal(static_cast<int>(ReturnActivationDlg::ACTIVATE_OFFLINE));
}
-
+}
ReturnActivationDlg fff::showActivationDialog(wxWindow* parent, const std::wstring& lastErrorMsg, const std::wstring& manualActivationUrl, std::wstring& manualActivationKey)
{
@@ -1670,3 +1687,6 @@ DownloadProgressWindow::~DownloadProgressWindow() { pimpl_->Destroy(); }
void DownloadProgressWindow::notifyNewFile(const Zstring& filePath) { pimpl_->notifyNewFile(filePath); }
void DownloadProgressWindow::notifyProgress(int64_t delta) { pimpl_->notifyProgress(delta); }
void DownloadProgressWindow::requestUiRefresh() { pimpl_->requestUiRefresh(); } //throw CancelPressed
+
+//########################################################################################
+
diff --git a/FreeFileSync/Source/ui/small_dlgs.h b/FreeFileSync/Source/ui/small_dlgs.h
index 3607b593..f3636a0c 100644
--- a/FreeFileSync/Source/ui/small_dlgs.h
+++ b/FreeFileSync/Source/ui/small_dlgs.h
@@ -80,6 +80,8 @@ private:
class Impl;
Impl* const pimpl_;
};
+
+
}
#endif //SMALL_DLGS_H_8321790875018750245
diff --git a/FreeFileSync/Source/version/version.h b/FreeFileSync/Source/version/version.h
index ad914ec1..aee4ad4d 100644
--- a/FreeFileSync/Source/version/version.h
+++ b/FreeFileSync/Source/version/version.h
@@ -3,7 +3,7 @@
namespace fff
{
-const char ffsVersion[] = "10.15"; //internal linkage!
+const char ffsVersion[] = "10.16"; //internal linkage!
const char FFS_VERSION_SEPARATOR = '.';
}
diff --git a/wx+/dc.h b/wx+/dc.h
index bcf62a14..894fb473 100644
--- a/wx+/dc.h
+++ b/wx+/dc.h
@@ -12,24 +12,25 @@
#include <zen/basic_math.h>
#include <wx/dcbuffer.h> //for macro: wxALWAYS_NATIVE_DOUBLE_BUFFER
#include <wx/dcscreen.h>
+ #include <gtk/gtk.h>
namespace zen
{
/*
- 1. wxDCClipper does *not* stack: another fix for yet another poor wxWidgets implementation
+ 1. wxDCClipper does *not* stack: another fix for yet another poor wxWidgets implementation
- class RecursiveDcClipper
- {
- RecursiveDcClipper(wxDC& dc, const wxRect& r)
- };
+ class RecursiveDcClipper
+ {
+ RecursiveDcClipper(wxDC& dc, const wxRect& r)
+ };
- 2. wxAutoBufferedPaintDC skips one pixel on left side when RTL layout is active: a fix for a poor wxWidgets implementation
+ 2. wxAutoBufferedPaintDC skips one pixel on left side when RTL layout is active: a fix for a poor wxWidgets implementation
- class BufferedPaintDC
- {
- BufferedPaintDC(wxWindow& wnd, std::unique_ptr<wxBitmap>& buffer)
- };
+ class BufferedPaintDC
+ {
+ BufferedPaintDC(wxWindow& wnd, std::unique_ptr<wxBitmap>& buffer)
+ };
*/
@@ -50,10 +51,12 @@ Standard DPI:
inline
int fastFromDIP(int d) //like wxWindow::FromDIP (but tied to primary monitor and buffered)
{
-
#ifdef wxHAVE_DPI_INDEPENDENT_PIXELS //pulled from wx/window.h: https://github.com/wxWidgets/wxWidgets/blob/master/include/wx/window.h#L2029
return d; //e.g. macOS, GTK3
#else //https://github.com/wxWidgets/wxWidgets/blob/master/src/common/wincmn.cpp#L2865
+ static_assert(GTK_MAJOR_VERSION == 2);
+ //GTK2 doesn't properly support high DPI: https://freefilesync.org/forum/viewtopic.php?t=6114
+ //=> requires general fix at wxWidgets-level
assert(wxTheApp); //only call after wxWidgets was initalized!
static const int dpiY = wxScreenDC().GetPPI().y; //perf: buffering for calls to ::GetDeviceCaps() needed!?
const int defaultDpi = 96;
diff --git a/wx+/graph.cpp b/wx+/graph.cpp
index 2440f77d..aac2cc10 100644
--- a/wx+/graph.cpp
+++ b/wx+/graph.cpp
@@ -151,7 +151,7 @@ void drawXLabel(wxDC& dc, double xMin, double xMax, int blockCount, const Conver
return;
wxDCPenChanger dummy(dc, wxColor(192, 192, 192)); //light grey => not accessible! but no big deal...
- wxDCTextColourChanger dummy2(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); //use user setting for labels
+ wxDCTextColourChanger dummy2(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
const double valRangePerBlock = (xMax - xMin) / blockCount;
@@ -179,7 +179,7 @@ void drawYLabel(wxDC& dc, double yMin, double yMax, int blockCount, const Conver
return;
wxDCPenChanger dummy(dc, wxColor(192, 192, 192)); //light grey => not accessible! but no big deal...
- wxDCTextColourChanger dummy2(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); //use user setting for labels
+ wxDCTextColourChanger dummy2(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
const double valRangePerBlock = (yMax - yMin) / blockCount;
@@ -200,7 +200,7 @@ void drawYLabel(wxDC& dc, double yMin, double yMax, int blockCount, const Conver
}
-void drawCornerText(wxDC& dc, const wxRect& graphArea, const wxString& txt, Graph2D::PosCorner pos, const wxColor& backgroundColor)
+void drawCornerText(wxDC& dc, const wxRect& graphArea, const wxString& txt, Graph2D::PosCorner pos, const wxColor& colorText, const wxColor& colorBack)
{
if (txt.empty()) return;
@@ -225,13 +225,13 @@ void drawCornerText(wxDC& dc, const wxRect& graphArea, const wxString& txt, Grap
drawPos.y += graphArea.height - boxExtent.GetHeight();
break;
}
-
{
//add text shadow to improve readability:
- wxDCTextColourChanger dummy(dc, backgroundColor);
+ wxDCTextColourChanger dummy(dc, colorBack);
dc.DrawText(txt, drawPos + border + wxSize(fastFromDIP(1), fastFromDIP(1)));
}
- wxDCTextColourChanger dummy(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
+
+ wxDCTextColourChanger dummy(dc, colorText);
dc.DrawText(txt, drawPos + border);
}
@@ -593,7 +593,7 @@ void Graph2D::render(wxDC& dc) const
{
//paint graph background (excluding label area)
wxDCPenChanger dummy (dc, getBorderColor());
- wxDCBrushChanger dummy2(dc, attr_.backgroundColor);
+ wxDCBrushChanger dummy2(dc, attr_.colorBack);
//accessibility: consider system text and background colors; small drawback: color of graphs is NOT connected to the background! => responsibility of client to use correct colors
dc.DrawRectangle(graphArea);
@@ -849,7 +849,7 @@ void Graph2D::render(wxDC& dc) const
//5. draw corner texts
for (const auto& [cornerPos, text] : attr_.cornerTexts)
- drawCornerText(dc, graphArea, text, cornerPos, attr_.backgroundColor);
+ drawCornerText(dc, graphArea, text, cornerPos, attr_.colorText, attr_.colorBack);
}
}
}
diff --git a/wx+/graph.h b/wx+/graph.h
index f1ae5d5a..f1f0c76c 100644
--- a/wx+/graph.h
+++ b/wx+/graph.h
@@ -264,7 +264,8 @@ public:
MainAttributes& setCornerText(const wxString& txt, PosCorner pos) { cornerTexts[pos] = txt; return *this; }
- MainAttributes& setBackgroundColor(const wxColor& col) { backgroundColor = col; return *this; }
+ //accessibility: always set both colors
+ MainAttributes& setBaseColors(const wxColor& text, const wxColor& back) { colorText = text; colorBack = back; return *this; }
MainAttributes& setSelectionMode(SelMode mode) { mouseSelMode = mode; return *this; }
@@ -287,10 +288,13 @@ public:
std::map<PosCorner, wxString> cornerTexts;
- wxColor backgroundColor = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
+ wxColor colorText = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
+ wxColor colorBack = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
+
SelMode mouseSelMode = SELECT_RECTANGLE;
};
+
void setAttributes(const MainAttributes& newAttr) { attr_ = newAttr; Refresh(); }
MainAttributes getAttributes() const { return attr_; }
diff --git a/zen/http.cpp b/zen/http.cpp
index d4c30741..93651d0b 100644
--- a/zen/http.cpp
+++ b/zen/http.cpp
@@ -46,7 +46,7 @@ public:
else //HTTP default port: 80, see %WINDIR%\system32\drivers\etc\services
socket_ = std::make_unique<Socket>(server, Zstr("http")); //throw SysError
- //we don't support "chunked and gzip transfer encoding" => HTTP 1.0
+ //we don't support "chunked and gzip transfer encoding" => HTTP 1.0
std::map<std::string, std::string, LessAsciiNoCase> headers;
headers["Host" ] = utfTo<std::string>(server); //only required for HTTP/1.1 but a few servers expect it even for HTTP/1.0
headers["User-Agent"] = utfTo<std::string>(userAgent);
@@ -235,8 +235,8 @@ std::unique_ptr<HttpInputStream::Impl> sendHttpRequestImpl(const Zstring& url,
auto response = std::make_unique<HttpInputStream::Impl>(urlRed, postParams, false /*disableGetCache*/, userAgent, caCertFilePath, notifyUnbufferedIO); //throw SysError
//https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_Redirection
- const int statusCode = response->getStatusCode();
- if (statusCode / 100 == 3) //e.g. 301, 302, 303, 307... we're not too greedy since we check location, too!
+ const int httpStatusCode = response->getStatusCode();
+ if (httpStatusCode / 100 == 3) //e.g. 301, 302, 303, 307... we're not too greedy since we check location, too!
{
const std::string* value = response->getHeader("Location");
if (!value || value->empty())
@@ -246,9 +246,8 @@ std::unique_ptr<HttpInputStream::Impl> sendHttpRequestImpl(const Zstring& url,
}
else
{
- if (statusCode != 200) //HTTP_STATUS_OK
- throw SysError(replaceCpy<std::wstring>(L"HTTP status code %x.", L"%x", numberTo<std::wstring>(statusCode)));
- //e.g. 404 - HTTP_STATUS_NOT_FOUND
+ if (httpStatusCode != 200) //HTTP_STATUS_OK(200)
+ throw SysError(formatHttpStatusCode(httpStatusCode)); //e.g. HTTP_STATUS_NOT_FOUND(404)
return response;
}
@@ -271,7 +270,7 @@ std::string urlencode(const std::string& str)
out += c;
else
{
- const auto [high, low] = hexify(c);
+ const auto [high, low] = hexify(c);
out += '%';
out += high;
out += low;
@@ -327,7 +326,7 @@ std::vector<std::pair<std::string, std::string>> zen::xWwwFormUrlDecode(const st
HttpInputStream zen::sendHttpPost(const Zstring& url, const std::vector<std::pair<std::string, std::string>>& postParams,
- const Zstring& userAgent, const Zstring* caCertFilePath, const IOCallback& notifyUnbufferedIO) //throw SysError
+ const Zstring& userAgent, const Zstring* caCertFilePath, const IOCallback& notifyUnbufferedIO) //throw SysError
{
return sendHttpRequestImpl(url, &postParams, userAgent, caCertFilePath, notifyUnbufferedIO); //throw SysError
}
@@ -357,3 +356,85 @@ bool zen::internetIsAlive() //noexcept
}
catch (SysError&) { return false; }
}
+
+
+std::wstring zen::formatHttpStatusCode(int sc)
+{
+ const wchar_t* statusText = [&] //https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
+ {
+ switch (sc)
+ {
+ //*INDENT-OFF*
+ case 300: return L"Multiple choices.";
+ case 301: return L"Moved permanently.";
+ case 302: return L"Moved temporarily.";
+ case 303: return L"See other";
+ case 304: return L"Not modified.";
+ case 305: return L"Use proxy.";
+ case 306: return L"Switch proxy.";
+ case 307: return L"Temporary redirect.";
+ case 308: return L"Permanent redirect.";
+
+ case 400: return L"Bad request.";
+ case 401: return L"Unauthorized.";
+ case 402: return L"Payment required.";
+ case 403: return L"Forbidden.";
+ case 404: return L"Not found.";
+ case 405: return L"Method not allowed.";
+ case 406: return L"Not acceptable.";
+ case 407: return L"Proxy authentication required.";
+ case 408: return L"Request timeout.";
+ case 409: return L"Conflict.";
+ case 410: return L"Gone.";
+ case 411: return L"Length required.";
+ case 412: return L"Precondition failed.";
+ case 413: return L"Payload too large.";
+ case 414: return L"URI too long.";
+ case 415: return L"Unsupported media type.";
+ case 416: return L"Range not satisfiable.";
+ case 417: return L"Expectation failed.";
+ case 418: return L"I'm a teapot.";
+ case 421: return L"Misdirected request.";
+ case 422: return L"Unprocessable entity.";
+ case 423: return L"Locked.";
+ case 424: return L"Failed dependency.";
+ case 425: return L"Too early.";
+ case 426: return L"Upgrade required.";
+ case 428: return L"Precondition required.";
+ case 429: return L"Too many requests.";
+ case 431: return L"Request header fields too large.";
+ case 451: return L"Unavailable for legal reasons.";
+
+ case 500: return L"Internal server error.";
+ case 501: return L"Not implemented.";
+ case 502: return L"Bad gateway.";
+ case 503: return L"Service unavailable.";
+ case 504: return L"Gateway timeout.";
+ case 505: return L"HTTP version not supported.";
+ case 506: return L"Variant also negotiates.";
+ case 507: return L"Insufficient storage.";
+ case 508: return L"Loop detected.";
+ case 510: return L"Not extended.";
+ case 511: return L"Network authentication required.";
+
+ //Cloudflare errors regarding origin server:
+ case 520: return L"Unknown error (Cloudflare)";
+ case 521: return L"Web server is down (Cloudflare)";
+ case 522: return L"Connection timed out (Cloudflare)";
+ case 523: return L"Origin is unreachable (Cloudflare)";
+ case 524: return L"A timeout occurred (Cloudflare)";
+ case 525: return L"SSL handshake failed (Cloudflare)";
+ case 526: return L"Invalid SSL certificate (Cloudflare)";
+ case 527: return L"Railgun error (Cloudflare)";
+ case 530: return L"Origin DNS error (Cloudflare)";
+
+ default: return L"";
+ //*INDENT-ON*
+ }
+ }();
+
+ if (strLength(statusText) == 0)
+ return trimCpy(replaceCpy<std::wstring>(L"HTTP status %x.", L"%x", numberTo<std::wstring>(sc)));
+ else
+ return trimCpy(replaceCpy<std::wstring>(L"HTTP status %x: ", L"%x", numberTo<std::wstring>(sc)) + statusText);
+} \ No newline at end of file
diff --git a/zen/http.h b/zen/http.h
index 2fafa574..42b0e279 100644
--- a/zen/http.h
+++ b/zen/http.h
@@ -47,6 +47,7 @@ HttpInputStream sendHttpPost(const Zstring& url,
const Zstring* caCertFilePath /*optional: enable certificate validation*/,
const IOCallback& notifyUnbufferedIO /*throw X*/);
bool internetIsAlive(); //noexcept
+std::wstring formatHttpStatusCode(int httpStatusCode);
std::string xWwwFormUrlEncode(const std::vector<std::pair<std::string, std::string>>& paramPairs);
std::vector<std::pair<std::string, std::string>> xWwwFormUrlDecode(const std::string& str);
diff --git a/zen/json.h b/zen/json.h
index 9b85ccb9..15157cf7 100644
--- a/zen/json.h
+++ b/zen/json.h
@@ -12,7 +12,8 @@
namespace zen
{
-//https://tools.ietf.org/html/rfc8259
+//Spec: https://tools.ietf.org/html/rfc8259
+//Test: http://seriot.ch/parsing_json.php
struct JsonValue
{
enum class Type
@@ -92,26 +93,31 @@ std::string jsonEscape(const std::string& str)
{
std::string output;
for (const char c : str)
- {
- if (c == '"') output += "\\\""; //escaping mandatory
- else if (c == '\\') output += "\\\\"; //
-
- else if (c == '\b') output += "\\b"; //
- else if (c == '\f') output += "\\f"; //
- else if (c == '\n') output += "\\n"; //prefer compact escaping
- else if (c == '\r') output += "\\r"; //
- else if (c == '\t') output += "\\t"; //
-
- else if (static_cast<unsigned char>(c) < 32)
+ switch (c)
{
- const auto [high, low] = hexify(c);
- output += "\\u00";
- output += high;
- output += low;
+ //*INDENT-OFF*
+ case '"': output += "\\\""; break; //escaping mandatory
+ case '\\': output += "\\\\"; break; //
+
+ case '\b': output += "\\b"; break; //
+ case '\f': output += "\\f"; break; //
+ case '\n': output += "\\n"; break; //prefer compact escaping
+ case '\r': output += "\\r"; break; //
+ case '\t': output += "\\t"; break; //
+
+ default:
+ if (static_cast<unsigned char>(c) < 32)
+ {
+ const auto [high, low] = hexify(c);
+ output += "\\u00";
+ output += high;
+ output += low;
+ }
+ else
+ output += c;
+ break;
+ //*INDENT-ON*
}
- else
- output += c;
- }
return output;
}
@@ -151,31 +157,36 @@ std::string jsonUnescape(const std::string& str)
}
const char c2 = *it;
- if (c2 == '"' ||
- c2 == '\\' ||
- c2 == '/')
- writeOut(c2);
- else if (c2 == 'b') writeOut('\b');
- else if (c2 == 'f') writeOut('\f');
- else if (c2 == 'n') writeOut('\n');
- else if (c2 == 'r') writeOut('\r');
- else if (c2 == 't') writeOut('\t');
-
- else if (c2 == 'u' &&
- str.end() - it >= 5 &&
- isHexDigit(it[1]) &&
- isHexDigit(it[2]) &&
- isHexDigit(it[3]) &&
- isHexDigit(it[4]))
- {
- utf16Buf += static_cast<impl::Char16>(static_cast<unsigned char>(unhexify(it[1], it[2])) * 256 +
- static_cast<unsigned char>(unhexify(it[3], it[4])));
- it += 4;
- }
- else //unknown escape sequence!
+ switch (c2)
{
- writeOut(c);
- writeOut(c2);
+ //*INDENT-OFF*
+ case '"':
+ case '\\':
+ case '/': writeOut(c2); break;
+ case 'b': writeOut('\b'); break;
+ case 'f': writeOut('\f'); break;
+ case 'n': writeOut('\n'); break;
+ case 'r': writeOut('\r'); break;
+ case 't': writeOut('\t'); break;
+ default:
+ if (c2 == 'u' &&
+ str.end() - it >= 5 &&
+ isHexDigit(it[1]) &&
+ isHexDigit(it[2]) &&
+ isHexDigit(it[3]) &&
+ isHexDigit(it[4]))
+ {
+ utf16Buf += static_cast<impl::Char16>(static_cast<unsigned char>(unhexify(it[1], it[2])) * 256 +
+ static_cast<unsigned char>(unhexify(it[3], it[4])));
+ it += 4;
+ }
+ else //unknown escape sequence!
+ {
+ writeOut(c);
+ writeOut(c2);
+ }
+ break;
+ //*INDENT-ON*
}
}
else
@@ -322,7 +333,7 @@ public:
Token getNextToken() //throw JsonParsingError
{
//skip whitespace
- pos_ = std::find_if(pos_, stream_.end(), std::not_fn(isJsonWhiteSpace));
+ pos_ = std::find_if_not(pos_, stream_.end(), isJsonWhiteSpace);
if (pos_ == stream_.end())
return Token::Type::eof;
@@ -368,7 +379,7 @@ public:
}
//expect a number:
- const auto itNumEnd = std::find_if(pos_, stream_.end(), std::not_fn(isJsonNumDigit));
+ const auto itNumEnd = std::find_if_not(pos_, stream_.end(), isJsonNumDigit);
if (itNumEnd == pos_)
throw JsonParsingError(posRow(), posCol());
diff --git a/zen/shell_execute.h b/zen/shell_execute.h
index 4875a039..56322236 100644
--- a/zen/shell_execute.h
+++ b/zen/shell_execute.h
@@ -72,7 +72,7 @@ void shellExecute(const Zstring& command, ExecutionType type, bool hideConsole)
inline
void openWithDefaultApplication(const Zstring& itemPath) //throw FileError
{
- shellExecute("xdg-open \"" + itemPath + '"', ExecutionType::ASYNC, false/*hideConsole*/); //
+ shellExecute("xdg-open \"" + itemPath + '"', ExecutionType::ASYNC, false /*hideConsole*/); //throw FileError
}
}
diff --git a/zen/zlib_wrap.cpp b/zen/zlib_wrap.cpp
index ff5799c3..8979efa6 100644
--- a/zen/zlib_wrap.cpp
+++ b/zen/zlib_wrap.cpp
@@ -9,17 +9,39 @@
//Linux/macOS: use zlib system header for both wxWidgets and libcurl (zlib is required for HTTP)
// => don't compile wxWidgets with: --with-zlib=builtin
#include <zlib.h> //https://www.zlib.net/manual.html
+#include <zen/scope_guard.h>
using namespace zen;
+namespace
+{
+std::wstring formatZlibStatusCode(int sc)
+{
+ switch (sc)
+ {
+ ZEN_CHECK_CASE_FOR_CONSTANT(Z_OK);
+ ZEN_CHECK_CASE_FOR_CONSTANT(Z_STREAM_END);
+ ZEN_CHECK_CASE_FOR_CONSTANT(Z_NEED_DICT);
+ ZEN_CHECK_CASE_FOR_CONSTANT(Z_ERRNO);
+ ZEN_CHECK_CASE_FOR_CONSTANT(Z_STREAM_ERROR);
+ ZEN_CHECK_CASE_FOR_CONSTANT(Z_DATA_ERROR);
+ ZEN_CHECK_CASE_FOR_CONSTANT(Z_MEM_ERROR);
+ ZEN_CHECK_CASE_FOR_CONSTANT(Z_BUF_ERROR);
+ ZEN_CHECK_CASE_FOR_CONSTANT(Z_VERSION_ERROR);
+ }
+ return replaceCpy<std::wstring>(L"zlib status %x.", L"%x", numberTo<std::wstring>(sc));
+}
+}
+
+
size_t zen::impl::zlib_compressBound(size_t len)
{
return ::compressBound(static_cast<uLong>(len)); //upper limit for buffer size, larger than input size!!!
}
-size_t zen::impl::zlib_compress(const void* src, size_t srcLen, void* trg, size_t trgLen, int level) //throw ZlibInternalError
+size_t zen::impl::zlib_compress(const void* src, size_t srcLen, void* trg, size_t trgLen, int level) //throw SysError
{
uLongf bufferSize = static_cast<uLong>(trgLen);
const int rv = ::compress2(static_cast<Bytef*>(trg), //Bytef* dest,
@@ -31,12 +53,13 @@ size_t zen::impl::zlib_compress(const void* src, size_t srcLen, void* trg, size_
// Z_MEM_ERROR: not enough memory
// Z_BUF_ERROR: not enough room in the output buffer
if (rv != Z_OK || bufferSize > trgLen)
- throw ZlibInternalError();
+ throw SysError(formatSystemError(L"compress2", formatZlibStatusCode(rv), L"zlib error"));
+
return bufferSize;
}
-size_t zen::impl::zlib_decompress(const void* src, size_t srcLen, void* trg, size_t trgLen) //throw ZlibInternalError
+size_t zen::impl::zlib_decompress(const void* src, size_t srcLen, void* trg, size_t trgLen) //throw SysError
{
uLongf bufferSize = static_cast<uLong>(trgLen);
const int rv = ::uncompress(static_cast<Bytef*>(trg), //Bytef* dest,
@@ -48,7 +71,8 @@ size_t zen::impl::zlib_decompress(const void* src, size_t srcLen, void* trg, siz
// Z_BUF_ERROR: not enough room in the output buffer
// Z_DATA_ERROR: input data was corrupted or incomplete
if (rv != Z_OK || bufferSize > trgLen)
- throw ZlibInternalError();
+ throw SysError(formatSystemError(L"uncompress", formatZlibStatusCode(rv), L"zlib error"));
+
return bufferSize;
}
@@ -56,7 +80,7 @@ size_t zen::impl::zlib_decompress(const void* src, size_t srcLen, void* trg, siz
class InputStreamAsGzip::Impl
{
public:
- Impl(const std::function<size_t(void* buffer, size_t bytesToRead)>& readBlock /*throw X*/) : //throw ZlibInternalError; returning 0 signals EOF: Posix read() semantics
+ Impl(const std::function<size_t(void* buffer, size_t bytesToRead)>& readBlock /*throw X*/) : //throw SysError; returning 0 signals EOF: Posix read() semantics
readBlock_(readBlock)
{
const int windowBits = MAX_WBITS + 16; //"add 16 to windowBits to write a simple gzip header"
@@ -72,7 +96,7 @@ public:
memLevel, //int memLevel
Z_DEFAULT_STRATEGY); //int strategy
if (rv != Z_OK)
- throw ZlibInternalError();
+ throw SysError(formatSystemError(L"deflateInit2", formatZlibStatusCode(rv), L"zlib error"));
}
~Impl()
@@ -81,7 +105,7 @@ public:
assert(rv == Z_OK);
}
- size_t read(void* buffer, size_t bytesToRead) //throw ZlibInternalError, X; return "bytesToRead" bytes unless end of stream!
+ size_t read(void* buffer, size_t bytesToRead) //throw SysError, X; return "bytesToRead" bytes unless end of stream!
{
if (bytesToRead == 0) //"read() with a count of 0 returns zero" => indistinguishable from end of file! => check!
throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
@@ -107,7 +131,7 @@ public:
if (rv == Z_STREAM_END)
return bytesToRead - gzipStream_.avail_out;
if (rv != Z_OK)
- throw ZlibInternalError();
+ throw SysError(formatSystemError(L"deflate", formatZlibStatusCode(rv), L"zlib error"));
if (gzipStream_.avail_out == 0)
return bytesToRead;
@@ -122,6 +146,6 @@ private:
};
-zen::InputStreamAsGzip::InputStreamAsGzip(const std::function<size_t(void* buffer, size_t bytesToRead)>& readBlock /*throw X*/) : pimpl_(std::make_unique<Impl>(readBlock)) {} //throw ZlibInternalError
+zen::InputStreamAsGzip::InputStreamAsGzip(const std::function<size_t(void* buffer, size_t bytesToRead)>& readBlock /*throw X*/) : pimpl_(std::make_unique<Impl>(readBlock)) {} //throw SysError
zen::InputStreamAsGzip::~InputStreamAsGzip() {}
-size_t zen::InputStreamAsGzip::read(void* buffer, size_t bytesToRead) { return pimpl_->read(buffer, bytesToRead); } //throw ZlibInternalError, X
+size_t zen::InputStreamAsGzip::read(void* buffer, size_t bytesToRead) { return pimpl_->read(buffer, bytesToRead); } //throw SysError, X
diff --git a/zen/zlib_wrap.h b/zen/zlib_wrap.h
index c8647baf..fbe26193 100644
--- a/zen/zlib_wrap.h
+++ b/zen/zlib_wrap.h
@@ -8,31 +8,30 @@
#define ZLIB_WRAP_H_428597064566
#include "serialize.h"
+#include "sys_error.h"
namespace zen
{
-class ZlibInternalError {};
-
// compression level must be between 0 and 9:
// 0: no compression
// 9: best compression
template <class BinContainer> //as specified in serialize.h
-BinContainer compress(const BinContainer& stream, int level); //throw ZlibInternalError
+BinContainer compress(const BinContainer& stream, int level); //throw SysError
//caveat: output stream is physically larger than input! => strip additional reserved space if needed: "BinContainer(output.begin(), output.end())"
template <class BinContainer>
-BinContainer decompress(const BinContainer& stream); //throw ZlibInternalError
+BinContainer decompress(const BinContainer& stream); //throw SysError
class InputStreamAsGzip //convert input stream into gzip on the fly
{
public:
- InputStreamAsGzip( //throw ZlibInternalError
+ InputStreamAsGzip( //throw SysError
const std::function<size_t(void* buffer, size_t bytesToRead)>& readBlock /*throw X*/); //returning 0 signals EOF: Posix read() semantics
~InputStreamAsGzip();
- size_t read(void* buffer, size_t bytesToRead); //throw ZlibInternalError, X; return "bytesToRead" bytes unless end of stream!
+ size_t read(void* buffer, size_t bytesToRead); //throw SysError, X; return "bytesToRead" bytes unless end of stream!
private:
class Impl;
@@ -49,13 +48,13 @@ private:
namespace impl
{
size_t zlib_compressBound(size_t len);
-size_t zlib_compress (const void* src, size_t srcLen, void* trg, size_t trgLen, int level); //throw ZlibInternalError
-size_t zlib_decompress(const void* src, size_t srcLen, void* trg, size_t trgLen); //throw ZlibInternalError
+size_t zlib_compress (const void* src, size_t srcLen, void* trg, size_t trgLen, int level); //throw SysError
+size_t zlib_decompress(const void* src, size_t srcLen, void* trg, size_t trgLen); //throw SysError
}
template <class BinContainer>
-BinContainer compress(const BinContainer& stream, int level) //throw ZlibInternalError
+BinContainer compress(const BinContainer& stream, int level) //throw SysError
{
BinContainer contOut;
if (!stream.empty()) //don't dereference iterator into empty container!
@@ -73,7 +72,7 @@ BinContainer compress(const BinContainer& stream, int level) //throw ZlibInterna
stream.size(),
&*contOut.begin() + contOut.size() - bufferEstimate,
bufferEstimate,
- level); //throw ZlibInternalError
+ level); //throw SysError
if (bytesWritten < bufferEstimate)
contOut.resize(contOut.size() - (bufferEstimate - bytesWritten)); //caveat: unsigned arithmetics
//caveat: physical memory consumption still *unchanged*!
@@ -83,7 +82,7 @@ BinContainer compress(const BinContainer& stream, int level) //throw ZlibInterna
template <class BinContainer>
-BinContainer decompress(const BinContainer& stream) //throw ZlibInternalError
+BinContainer decompress(const BinContainer& stream) //throw SysError
{
BinContainer contOut;
if (!stream.empty()) //don't dereference iterator into empty container!
@@ -91,30 +90,30 @@ BinContainer decompress(const BinContainer& stream) //throw ZlibInternalError
//retrieve size of uncompressed data
uint64_t uncompressedSize = 0; //use portable number type!
if (stream.size() < sizeof(uncompressedSize))
- throw ZlibInternalError();
+ throw SysError(L"zlib error: stream size < 8");
std::memcpy(&uncompressedSize, &*stream.begin(), sizeof(uncompressedSize));
//attention: contOut MUST NOT be empty! Else it will pass a nullptr to zlib_decompress() => Z_STREAM_ERROR although "uncompressedSize == 0"!!!
//secondary bug: don't dereference iterator into empty container!
if (uncompressedSize == 0) //cannot be 0: compress() directly maps empty -> empty container skipping zlib!
- throw ZlibInternalError();
+ throw SysError(L"zlib error: uncompressed size == 0");
try
{
contOut.resize(static_cast<size_t>(uncompressedSize)); //throw std::bad_alloc
}
- catch (std::bad_alloc&) //most likely due to data corruption!
+ catch (const std::bad_alloc& e) //most likely due to data corruption!
{
- throw ZlibInternalError();
+ throw SysError(L"zlib error: " + _("Out of memory.") + L" " + utfTo<std::wstring>(e.what()));
}
const size_t bytesWritten = impl::zlib_decompress(&*stream.begin() + sizeof(uncompressedSize),
stream.size() - sizeof(uncompressedSize),
&*contOut.begin(),
- static_cast<size_t>(uncompressedSize)); //throw ZlibInternalError
+ static_cast<size_t>(uncompressedSize)); //throw SysError
if (bytesWritten != static_cast<size_t>(uncompressedSize))
- throw ZlibInternalError();
+ throw SysError(L"zlib error: bytes written != uncompressed size");
}
return contOut;
}
diff --git a/zenXml/zenxml/parser.h b/zenXml/zenxml/parser.h
index f7604ebf..dbf5a8c4 100644
--- a/zenXml/zenxml/parser.h
+++ b/zenXml/zenxml/parser.h
@@ -79,31 +79,31 @@ std::string normalize(const std::string& str, Predicate pred) //pred: unary func
{
std::string output;
for (const char c : str)
- {
- if (c == '&') //
- output += "&amp;";
- else if (c == '<') //normalization mandatory: https://www.w3.org/TR/xml/#syntax
- output += "&lt;";
- else if (c == '>') //
- output += "&gt;";
- else if (pred(c))
+ switch (c)
{
- if (c == '\'')
- output += "&apos;";
- else if (c == '"')
- output += "&quot;";
- else
- {
- output += "&#x";
- const auto [high, low] = hexify(c);
- output += high;
- output += low;
- output += ';';
- }
+ //*INDENT-OFF*
+ case '&': output += "&amp;"; break; //
+ case '<': output += "&lt;"; break; //normalization mandatory: https://www.w3.org/TR/xml/#syntax
+ case '>': output += "&gt;"; break; //
+ default:
+ if (pred(c))
+ {
+ if (c == '\'') output += "&apos;";
+ else if (c == '"') output += "&quot;";
+ else
+ {
+ output += "&#x";
+ const auto [high, low] = hexify(c);
+ output += high;
+ output += low;
+ output += ';';
+ }
+ }
+ else
+ output += c;
+ break;
+ //*INDENT-ON*
}
- else
- output += c;
- }
return output;
}
bgstack15