diff options
101 files changed, 1629 insertions, 1091 deletions
diff --git a/FreeFileSync/Build/Changelog.txt b/FreeFileSync/Build/Changelog.txt index 510a754e..e2967c96 100644 --- a/FreeFileSync/Build/Changelog.txt +++ b/FreeFileSync/Build/Changelog.txt @@ -1,5 +1,20 @@ -FreeFileSync 8.0 ----------------- +FreeFileSync 8.1 [2016-04-21] +----------------------------- +Follow shell links during drag and drop on main dialog (Windows) +Significantly improved main grid rendering performance +Log info about non-default global settings +Establish new network connections only when needed (Windows) +Show only a single login dialog per network share +Show login dialogs for the same network address one after another +Fixed endless recursion for paths containing certain unicode characters (OS X) +Support using portable version without direct installation +Fixed access denied error when verifying read-only target file (Windows) +New global option for sound cue after comparison +Updated help file + + +FreeFileSync 8.0 [2016-03-15] +----------------------------- Fine-tuned buffer sizes for 70% improved SFTP stream I/O speed Support incomplete read/write operations while maximizing buffer saturation Automatically check consistency of FreeFileSync installation @@ -892,7 +907,7 @@ Updated translation files FreeFileSync 5.2 [2012-04-01] ----------------------------- -Fixed runtime error "Error comparing strings! (LCMapString)" (Windows 2000, XP only) +Fixed runtime error "Error comparing strings! (LCMapString)" (Windows 2000, XP) FreeFileSync 5.1 [2012-03-31] diff --git a/FreeFileSync/Build/Help/FreeFileSync.hhc b/FreeFileSync/Build/Help/FreeFileSync.hhc index 79864cd9..194d8951 100644 --- a/FreeFileSync/Build/Help/FreeFileSync.hhc +++ b/FreeFileSync/Build/Help/FreeFileSync.hhc @@ -52,6 +52,10 @@ <param name="Local" value="html\schedule-a-batch-job.html"> </OBJECT> <LI> <OBJECT type="text/sitemap"> + <param name="Name" value="Synchronization Settings"> + <param name="Local" value="html\synchronization-settings.html"> + </OBJECT> + <LI> <OBJECT type="text/sitemap"> <param name="Name" value="Synchronize with SFTP"> <param name="Local" value="html\synchronize-with-sftp.html"> </OBJECT> diff --git a/FreeFileSync/Build/Help/FreeFileSync.hhp b/FreeFileSync/Build/Help/FreeFileSync.hhp index a165eb37..92006f89 100644 --- a/FreeFileSync/Build/Help/FreeFileSync.hhp +++ b/FreeFileSync/Build/Help/FreeFileSync.hhp @@ -19,6 +19,7 @@ html\external-applications.html html\freefilesync.html html\macros.html html\schedule-a-batch-job.html +html\synchronization-settings.html html\synchronize-with-sftp.html html\tips-and-tricks.html html\variable-drive-letters.html diff --git a/FreeFileSync/Build/Help/html/Versioning.html b/FreeFileSync/Build/Help/html/Versioning.html index 11e6f82b..e3ca9bbd 100644 --- a/FreeFileSync/Build/Help/html/Versioning.html +++ b/FreeFileSync/Build/Help/html/Versioning.html @@ -26,8 +26,9 @@ without any decoration and will replace already existing older versions. </p> - <br> - + <img src="../images/versioning.png" alt="Versioning"><br> + + <h2>2. Keep all versions of old files</h2> <p> diff --git a/FreeFileSync/Build/Help/html/expert-settings.html b/FreeFileSync/Build/Help/html/expert-settings.html index d430be4e..e6f34531 100644 --- a/FreeFileSync/Build/Help/html/expert-settings.html +++ b/FreeFileSync/Build/Help/html/expert-settings.html @@ -31,7 +31,7 @@ <<b>LockDirectoriesDuringSync</b> Enabled="true"/><br> <<b>VerifyCopiedFiles</b> Enabled="false"/><br> <<b>LastSyncsLogSizeMax</b> Bytes="100000"/><br> - <<b>NotificationSound</b> SyncComplete="harp.wav"/> + <<b>NotificationSound</b> CompareFinished="ding.wav" SyncFinished="harp.wav"/> </div> </div></div></div> <br> @@ -89,7 +89,7 @@ <p> <b>NotificationSound:</b><br> - Select a sound file from the FreeFileSync installation directory to be played after synchronization. Set an empty name if no sound should be played. + Select sound files from the FreeFileSync installation directory to be played after comparison or synchronization. Set empty names if no sound should be played. </p> </body> </html>
\ No newline at end of file diff --git a/FreeFileSync/Build/Help/html/synchronization-settings.html b/FreeFileSync/Build/Help/html/synchronization-settings.html new file mode 100644 index 00000000..ff3c79e1 --- /dev/null +++ b/FreeFileSync/Build/Help/html/synchronization-settings.html @@ -0,0 +1,41 @@ +<!doctype html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <link rel="stylesheet" type="text/css" href="base.css" > + <title>Synchronization Settings</title> +</head> + +<body> + <h1>Synchronization Settings</h1> + + <p> + <img src="../images/synchronization-settings.png" alt="Synchronization settings dialog"><br> + + </p> + + <h2>Detect Moved Files</h2> + + <p> + FreeFileSync is able to detect moved files on one side and can quickly apply the same move on the target side during synchronization instead of a slow copy and delete. To make this work FreeFileSync requires database files (sync.ffs_db) to compare the current file system state against the time of the last synchronization. + </p> + <p> + The <i>Two-Way</i> variant already creates database files, therefore detection of moved files is always active.<br> + The <i>Mirror</i> variant however does not need a database file to find synchronization directions, so detection of moved files + is not available by default. If you don't mind the creation of the database files you can enable this feature by + selecting the <b>Detect moved files</b> checkbox. + </p> + + <div class="box-outer"><div class="bluebox"><div class="box-inner"> + <b>Note</b> + <ul style="margin: 0"> + <li>Detection of moved files is not available when synchronizing a folder pair for the first time. Only beginning with the second sync + the database files are available to determine moved files. + <li>Detection is not supported by all file systems. Most notably, certain file moves on the FAT file system cannot be detected. + Also virtualized file systems, e.g. a mounted WebDAV drive, might not support move detection. In these cases FreeFileSync will automatically fall back to copy and delete. + </ul> + </div></div></div> + <br> + +</body> +</html>
\ No newline at end of file diff --git a/FreeFileSync/Build/Help/images/synchronization-settings.png b/FreeFileSync/Build/Help/images/synchronization-settings.png Binary files differnew file mode 100644 index 00000000..cd778caf --- /dev/null +++ b/FreeFileSync/Build/Help/images/synchronization-settings.png diff --git a/FreeFileSync/Build/Help/images/versioning.png b/FreeFileSync/Build/Help/images/versioning.png Binary files differnew file mode 100644 index 00000000..1bc4643c --- /dev/null +++ b/FreeFileSync/Build/Help/images/versioning.png diff --git a/FreeFileSync/Build/Languages/arabic.lng b/FreeFileSync/Build/Languages/arabic.lng index 8f522c4c..d8614bfb 100644 --- a/FreeFileSync/Build/Languages/arabic.lng +++ b/FreeFileSync/Build/Languages/arabic.lng @@ -337,7 +337,7 @@ Actual: %y bytes <source>Searching for folder %x...</source> <target>البحث عن المجلد %x...</target> -<source>Time out while searching for folder %x.</source> +<source>Timeout while searching for folder %x.</source> <target>انقضت المهلة أثناء البحث عن مجلد %x.</target> <source>Cannot get process information.</source> diff --git a/FreeFileSync/Build/Languages/bulgarian.lng b/FreeFileSync/Build/Languages/bulgarian.lng index 96716838..bf36cdb9 100644 --- a/FreeFileSync/Build/Languages/bulgarian.lng +++ b/FreeFileSync/Build/Languages/bulgarian.lng @@ -333,7 +333,7 @@ Actual: %y bytes <source>Searching for folder %x...</source> <target>Търсене на папка %x...</target> -<source>Time out while searching for folder %x.</source> +<source>Timeout while searching for folder %x.</source> <target>Изтекло време за търсене на папка %x.</target> <source>Cannot get process information.</source> diff --git a/FreeFileSync/Build/Languages/chinese_simple.lng b/FreeFileSync/Build/Languages/chinese_simple.lng index 21f03125..0a8049b9 100644 --- a/FreeFileSync/Build/Languages/chinese_simple.lng +++ b/FreeFileSync/Build/Languages/chinese_simple.lng @@ -332,7 +332,7 @@ Actual: %y bytes <source>Searching for folder %x...</source> <target>正在搜索文件夹 %x...</target> -<source>Time out while searching for folder %x.</source> +<source>Timeout while searching for folder %x.</source> <target>搜索文件夹 %x 超时.</target> <source>Cannot get process information.</source> diff --git a/FreeFileSync/Build/Languages/chinese_traditional.lng b/FreeFileSync/Build/Languages/chinese_traditional.lng index f383f381..5fc90821 100644 --- a/FreeFileSync/Build/Languages/chinese_traditional.lng +++ b/FreeFileSync/Build/Languages/chinese_traditional.lng @@ -332,7 +332,7 @@ Actual: %y bytes <source>Searching for folder %x...</source> <target>正在搜尋資料夾 %x…</target> -<source>Time out while searching for folder %x.</source> +<source>Timeout while searching for folder %x.</source> <target>搜尋資料夾 %x 時超時。</target> <source>Cannot get process information.</source> diff --git a/FreeFileSync/Build/Languages/croatian.lng b/FreeFileSync/Build/Languages/croatian.lng index 92cb8282..3f8f4318 100644 --- a/FreeFileSync/Build/Languages/croatian.lng +++ b/FreeFileSync/Build/Languages/croatian.lng @@ -334,7 +334,7 @@ Stvarno: %y bajta <source>Searching for folder %x...</source> <target>Tražim mapu %x...</target> -<source>Time out while searching for folder %x.</source> +<source>Timeout while searching for folder %x.</source> <target>Isteklo vrijeme traženja mape %x.</target> <source>Cannot get process information.</source> diff --git a/FreeFileSync/Build/Languages/czech.lng b/FreeFileSync/Build/Languages/czech.lng index ff525fef..3fca8e3a 100644 --- a/FreeFileSync/Build/Languages/czech.lng +++ b/FreeFileSync/Build/Languages/czech.lng @@ -334,7 +334,7 @@ Aktuálně: %y b <source>Searching for folder %x...</source> <target>Otevírání složky %x...</target> -<source>Time out while searching for folder %x.</source> +<source>Timeout while searching for folder %x.</source> <target>Vypršel časový limit pro nalezení adresáře %x.</target> <source>Cannot get process information.</source> diff --git a/FreeFileSync/Build/Languages/danish.lng b/FreeFileSync/Build/Languages/danish.lng index 1808257c..de7625b4 100644 --- a/FreeFileSync/Build/Languages/danish.lng +++ b/FreeFileSync/Build/Languages/danish.lng @@ -333,7 +333,7 @@ Aktuel: %y byte <source>Searching for folder %x...</source> <target>Søger efter mappen %x...</target> -<source>Time out while searching for folder %x.</source> +<source>Timeout while searching for folder %x.</source> <target>Timeout ved søgning efter mappen %x.</target> <source>Cannot get process information.</source> diff --git a/FreeFileSync/Build/Languages/dutch.lng b/FreeFileSync/Build/Languages/dutch.lng index d4f4412c..8f76028d 100644 --- a/FreeFileSync/Build/Languages/dutch.lng +++ b/FreeFileSync/Build/Languages/dutch.lng @@ -333,7 +333,7 @@ Werkelijk: %y bytes <source>Searching for folder %x...</source> <target>Zoek naar map %x...</target> -<source>Time out while searching for folder %x.</source> +<source>Timeout while searching for folder %x.</source> <target>Time-out tijdens het zoeken naar de map %x.</target> <source>Cannot get process information.</source> diff --git a/FreeFileSync/Build/Languages/english_uk.lng b/FreeFileSync/Build/Languages/english_uk.lng index 025252cd..4f5a67f4 100644 --- a/FreeFileSync/Build/Languages/english_uk.lng +++ b/FreeFileSync/Build/Languages/english_uk.lng @@ -333,7 +333,7 @@ Actual: %y bytes <source>Searching for folder %x...</source> <target>Searching for folder %x...</target> -<source>Time out while searching for folder %x.</source> +<source>Timeout while searching for folder %x.</source> <target>Time out while searching for folder %x.</target> <source>Cannot get process information.</source> diff --git a/FreeFileSync/Build/Languages/finnish.lng b/FreeFileSync/Build/Languages/finnish.lng index 3dcd8cea..b78a0b4d 100644 --- a/FreeFileSync/Build/Languages/finnish.lng +++ b/FreeFileSync/Build/Languages/finnish.lng @@ -333,7 +333,7 @@ Todellinen: %y tavua <source>Searching for folder %x...</source> <target>Etsitään hakemistoa %x...</target> -<source>Time out while searching for folder %x.</source> +<source>Timeout while searching for folder %x.</source> <target>Aikaviive hakiessa hakemistoa %x.</target> <source>Cannot get process information.</source> diff --git a/FreeFileSync/Build/Languages/french.lng b/FreeFileSync/Build/Languages/french.lng index 075b04f2..b3fd3e5c 100644 --- a/FreeFileSync/Build/Languages/french.lng +++ b/FreeFileSync/Build/Languages/french.lng @@ -333,7 +333,7 @@ Trouvé : %y octets <source>Searching for folder %x...</source> <target>Recherche du dossier %x...</target> -<source>Time out while searching for folder %x.</source> +<source>Timeout while searching for folder %x.</source> <target>Timeout lors de la recherche du dossier %x.</target> <source>Cannot get process information.</source> diff --git a/FreeFileSync/Build/Languages/german.lng b/FreeFileSync/Build/Languages/german.lng index de0e6ca3..5c5ec50c 100644 --- a/FreeFileSync/Build/Languages/german.lng +++ b/FreeFileSync/Build/Languages/german.lng @@ -7,12 +7,6 @@ <plural_definition>n == 1 ? 0 : 1</plural_definition> </header> -<source>Installation files are corrupt. Please reinstall FreeFileSync.</source> -<target>Die Installationsdateien sind beschädigt. Bitte installieren Sie FreeFileSync neu.</target> - -<source>Consistency check failed for %x.</source> -<target>Die Konsistenzprüfung für %x ist fehlgeschlagen.</target> - <source>Both sides have changed since last synchronization.</source> <target>Beide Seiten wurden seit der letzten Synchronisation verändert.</target> @@ -157,6 +151,39 @@ <source>Generating file list...</source> <target>Erzeuge Dateiliste...</target> +<source>Fail-safe file copy</source> +<target>Dateien ausfallsicher kopieren</target> + +<source>Enabled</source> +<target>Aktiviert</target> + +<source>Disabled</source> +<target>Deaktiviert</target> + +<source>Copy locked files</source> +<target>Gesperrte Dateien kopieren</target> + +<source>Copy file access permissions</source> +<target>Dateizugriffsberechtigungen kopieren</target> + +<source>File time tolerance</source> +<target>Dateizeittoleranz</target> + +<source>Folder access timeout</source> +<target>Zeitlimit für Ordnerzugriff</target> + +<source>Run with background priority</source> +<target>Mit Hintergrundpriorität ausführen</target> + +<source>Lock directories during sync</source> +<target>Verzeichnisse während Synchronisation sperren</target> + +<source>Verify copied files</source> +<target>Kopierte Dateien verifizieren</target> + +<source>Using non-default global settings:</source> +<target>Verwende nicht dem Standard entsprechende globale Einstellungen:</target> + <source>Starting comparison</source> <target>Starte Vergleich</target> @@ -297,8 +324,8 @@ Tatsächlich: %y bytes <source>Error Code %x:</source> <target>Fehlercode %x:</target> -<source>Failed to connect to SFTP server %x.</source> -<target>Die Verbindung zum SFTP-Server %x ist fehlgeschlagen.</target> +<source>Unable to connect to %x.</source> +<target>Es kann keine Verbindung zu %x aufgebaut werden.</target> <source> <pluralform>1 byte</pluralform> @@ -339,7 +366,7 @@ Tatsächlich: %y bytes <source>Searching for folder %x...</source> <target>Suche Ordner %x...</target> -<source>Time out while searching for folder %x.</source> +<source>Timeout while searching for folder %x.</source> <target>Das Zeitlimit für die Suche nach Ordner %x wurde überschritten.</target> <source>Cannot get process information.</source> @@ -587,6 +614,9 @@ Die Befehlszeile wird ausgelöst, wenn: <source>Updating attributes of %x</source> <target>Aktualisiere Attribute von %x</target> +<source>Cannot write file attributes of %x.</source> +<target>Die Dateiattribute von %x können nicht geschrieben werden.</target> + <source>%x and %y have different content.</source> <target>%x und %y haben unterschiedlichen Inhalt.</target> @@ -612,10 +642,10 @@ Die Befehlszeile wird ausgelöst, wenn: <target>Die folgenden Elemente haben ungelöste Konflikte und werden nicht synchronisiert werden:</target> <source>The following folders are significantly different. Make sure you have selected the correct folders for synchronization.</source> -<target>Die folgenden Ordner unterscheiden sich erheblich. Stellen Sie sicher, dass die richtigen Ordner für die Synchronisation ausgewählt sind.</target> +<target>Die folgenden Ordner unterscheiden sich erheblich. Überprüfen Sie, ob die richtigen Ordner für die Synchronisation ausgewählt wurden.</target> <source>Not enough free disk space available in:</source> -<target>Nicht genügend freier Speicher verfügbar unter:</target> +<target>Nicht genügend freier Speicher verfügbar für:</target> <source>Required:</source> <target>Benötigt:</target> @@ -884,6 +914,9 @@ Die Befehlszeile wird ausgelöst, wenn: <source>Folder pair:</source> <target>Ordnerpaar:</target> +<source>Main settings:</source> +<target>Haupteinstellungen:</target> + <source>Use local settings:</source> <target>Verwende lokale Einstellungen:</target> @@ -944,6 +977,9 @@ Die Befehlszeile wird ausgelöst, wenn: <source>C&lear</source> <target>&Löschen</target> +<source>Detect synchronization directions with the help of database files</source> +<target>Ermittle die Synchronisationsrichtungen mit Hilfe von Datenbankdateien</target> + <source>Detect moved files</source> <target>Verschobene Dateien erkennen</target> @@ -958,24 +994,21 @@ Die Befehlszeile wird ausgelöst, wenn: - Erkennung bei erster Synchronisation nicht verfügbar. </target> -<source>Detect synchronization directions with the help of database files</source> -<target>Ermittle die Synchronisationsrichtungen mit Hilfe von Datenbankdateien</target> - <source>Delete files:</source> <target>Dateien löschen:</target> -<source>&Permanent</source> -<target>&Permanent</target> - -<source>Delete or overwrite files permanently</source> -<target>Dateien endgültig löschen oder überschreiben</target> - <source>&Recycle bin</source> <target>Papier&korb</target> <source>Back up deleted and overwritten files in the recycle bin</source> <target>Gelöschte und überschriebene Dateien im Papierkorb sichern</target> +<source>&Permanent</source> +<target>&Permanent</target> + +<source>Delete or overwrite files permanently</source> +<target>Dateien endgültig löschen oder überschreiben</target> + <source>&Versioning</source> <target>&Versionierung</target> @@ -988,15 +1021,15 @@ Die Befehlszeile wird ausgelöst, wenn: <source>Handle errors:</source> <target>Fehlerbehandlung:</target> -<source>Hide all error and warning messages</source> -<target>Alle Fehler- und Warnmeldungen unterdrücken</target> - <source>&Pop-up</source> <target>&Nachfragen</target> <source>Show pop-up on errors or warnings</source> <target>Ein Auswahlfenster bei Fehlern oder Warnungen anzeigen</target> +<source>Hide all error and warning messages</source> +<target>Alle Fehler- und Warnmeldungen unterdrücken</target> + <source>On completion:</source> <target>Nach Abschluss:</target> @@ -1102,9 +1135,6 @@ Die Befehlszeile wird ausgelöst, wenn: <source>The following settings are used for all synchronization jobs.</source> <target>Die folgenden Einstellungen werden für alle Synchronisationsaufgaben verwendet.</target> -<source>Fail-safe file copy</source> -<target>Dateien ausfallsicher kopieren</target> - <source> Copy to a temporary file (*.ffs_tmp) before overwriting target. This guarantees a consistent state even in case of a serious error. @@ -1117,18 +1147,12 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. <source>(recommended)</source> <target>(Empfohlen)</target> -<source>Copy locked files</source> -<target>Gesperrte Dateien kopieren</target> - <source>Copy shared or locked files using the Volume Shadow Copy Service.</source> <target>Kopiere gesperrte Dateien mit Hilfe des Volumenschattenkopie-Dienstes.</target> <source>(requires administrator rights)</source> <target>(Benötigt Administratorrechte)</target> -<source>Copy file access permissions</source> -<target>Dateizugriffsberechtigungen kopieren</target> - <source>Transfer file and folder permissions.</source> <target>Übertrage Datei- und Ordnerberechtigungen.</target> @@ -1618,21 +1642,24 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. <source>FreeFileSync is up to date.</source> <target>FreeFileSync ist auf dem neuesten Stand.</target> -<source>Unable to connect to www.freefilesync.org.</source> -<target>Es kann keine Verbindung zu www.freefilesync.org aufgebaut werden.</target> - <source>Cannot find current FreeFileSync version number online. A newer version is likely available. Check manually now?</source> <target>Die aktuelle FreeFileSync Versionsnummer wurde online nicht gefunden. Wahrscheinlich ist eine neuere Version verfügbar. Jetzt manuell prüfen?</target> <source>&Check</source> <target>&Prüfen</target> +<source>Consistency check failed for %x.</source> +<target>Die Konsistenzprüfung für %x ist fehlgeschlagen.</target> + <source>Cannot find system function %x.</source> <target>Die Systemfunktion %x wurde nicht gefunden.</target> <source>Unable to register to receive system messages.</source> <target>Die Registrierung zum Empfang von Systemmeldungen ist fehlgeschlagen.</target> +<source>Installation files are corrupt. Please reinstall FreeFileSync.</source> +<target>Die Installationsdateien sind beschädigt. Bitte installieren Sie FreeFileSync neu.</target> + <source>Unable to register device notifications for %x.</source> <target>Die Registrierung für Gerätemeldungen ist für %x fehlgeschlagen.</target> @@ -1642,9 +1669,6 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. <source>The file is locked by another process:</source> <target>Die Datei wird von einem anderen Prozess gesperrt:</target> -<source>Cannot write file attributes of %x.</source> -<target>Die Dateiattribute von %x können nicht geschrieben werden.</target> - <source>Cannot read security context of %x.</source> <target>Der Sicherheitskontext von %x kann nicht gelesen werden.</target> diff --git a/FreeFileSync/Build/Languages/greek.lng b/FreeFileSync/Build/Languages/greek.lng index 11dd984b..a7a14841 100644 --- a/FreeFileSync/Build/Languages/greek.lng +++ b/FreeFileSync/Build/Languages/greek.lng @@ -333,7 +333,7 @@ Actual: %y bytes <source>Searching for folder %x...</source> <target>Αναζήτηση του υποκαταλόγου %x...</target> -<source>Time out while searching for folder %x.</source> +<source>Timeout while searching for folder %x.</source> <target>Λήξη χρονικού ορίου αναζήτησης του υποκαταλόγου %x.</target> <source>Cannot get process information.</source> diff --git a/FreeFileSync/Build/Languages/hebrew.lng b/FreeFileSync/Build/Languages/hebrew.lng index 945710d1..9c3e3ca7 100644 --- a/FreeFileSync/Build/Languages/hebrew.lng +++ b/FreeFileSync/Build/Languages/hebrew.lng @@ -333,7 +333,7 @@ Actual: %y bytes <source>Searching for folder %x...</source> <target>מחפש את תיקייה %x...</target> -<source>Time out while searching for folder %x.</source> +<source>Timeout while searching for folder %x.</source> <target>פקע הזמן עבור חיפוש של תיקייה %x.</target> <source>Cannot get process information.</source> diff --git a/FreeFileSync/Build/Languages/hindi.lng b/FreeFileSync/Build/Languages/hindi.lng index 058b6b19..b546be2c 100644 --- a/FreeFileSync/Build/Languages/hindi.lng +++ b/FreeFileSync/Build/Languages/hindi.lng @@ -333,7 +333,7 @@ Actual: %y bytes <source>Searching for folder %x...</source> <target>फ़ोल्डर %x की खोज हो रही है...</target> -<source>Time out while searching for folder %x.</source> +<source>Timeout while searching for folder %x.</source> <target>फ़ोल्डर %x की खोज करते हुए समय बाह्य हआ।</target> <source>Cannot get process information.</source> diff --git a/FreeFileSync/Build/Languages/hungarian.lng b/FreeFileSync/Build/Languages/hungarian.lng index e4e9794d..59a5f5b7 100644 --- a/FreeFileSync/Build/Languages/hungarian.lng +++ b/FreeFileSync/Build/Languages/hungarian.lng @@ -333,7 +333,7 @@ Jelenlegi: %y bájt <source>Searching for folder %x...</source> <target>%x könyvtár keresése...</target> -<source>Time out while searching for folder %x.</source> +<source>Timeout while searching for folder %x.</source> <target>Időtúllépés a %x könyvtár keresése folyamán.</target> <source>Cannot get process information.</source> diff --git a/FreeFileSync/Build/Languages/italian.lng b/FreeFileSync/Build/Languages/italian.lng index 77638f94..bea508f7 100644 --- a/FreeFileSync/Build/Languages/italian.lng +++ b/FreeFileSync/Build/Languages/italian.lng @@ -333,7 +333,7 @@ Attuale: %y byte <source>Searching for folder %x...</source> <target>Ricerca della cartella %x...</target> -<source>Time out while searching for folder %x.</source> +<source>Timeout while searching for folder %x.</source> <target>Pausa durante la ricerca per la cartella %x.</target> <source>Cannot get process information.</source> diff --git a/FreeFileSync/Build/Languages/japanese.lng b/FreeFileSync/Build/Languages/japanese.lng index 3c3f6842..b75ca319 100644 --- a/FreeFileSync/Build/Languages/japanese.lng +++ b/FreeFileSync/Build/Languages/japanese.lng @@ -332,7 +332,7 @@ Actual: %y bytes <source>Searching for folder %x...</source> <target>フォルダ %x を検索中...</target> -<source>Time out while searching for folder %x.</source> +<source>Timeout while searching for folder %x.</source> <target>フォルダ %x 検索中にタイムアウト.</target> <source>Cannot get process information.</source> diff --git a/FreeFileSync/Build/Languages/korean.lng b/FreeFileSync/Build/Languages/korean.lng index dc39bfdb..06dbe5f9 100644 --- a/FreeFileSync/Build/Languages/korean.lng +++ b/FreeFileSync/Build/Languages/korean.lng @@ -332,7 +332,7 @@ Actual: %y bytes <source>Searching for folder %x...</source> <target>폴더 %x 검색 중...</target> -<source>Time out while searching for folder %x.</source> +<source>Timeout while searching for folder %x.</source> <target>폴더 %x을(를) 검색하는 동안 타임아웃 됨.</target> <source>Cannot get process information.</source> diff --git a/FreeFileSync/Build/Languages/lithuanian.lng b/FreeFileSync/Build/Languages/lithuanian.lng index 72eb4cd2..6b88f722 100644 --- a/FreeFileSync/Build/Languages/lithuanian.lng +++ b/FreeFileSync/Build/Languages/lithuanian.lng @@ -334,7 +334,7 @@ Esamas: %y baitai <source>Searching for folder %x...</source> <target>Ieškoma aplanko %x...</target> -<source>Time out while searching for folder %x.</source> +<source>Timeout while searching for folder %x.</source> <target>Laikas baigėsi ieškant %x aplanko.</target> <source>Cannot get process information.</source> diff --git a/FreeFileSync/Build/Languages/outdated/norwegian.lng b/FreeFileSync/Build/Languages/outdated/norwegian.lng index 879580ba..f0558e6d 100644 --- a/FreeFileSync/Build/Languages/outdated/norwegian.lng +++ b/FreeFileSync/Build/Languages/outdated/norwegian.lng @@ -317,7 +317,7 @@ Actual: %y bytes <source>Searching for folder %x...</source> <target>Søker etter mappen %x...</target> -<source>Time out while searching for folder %x.</source> +<source>Timeout while searching for folder %x.</source> <target></target> <source>Cannot get process information.</source> diff --git a/FreeFileSync/Build/Languages/outdated/scottish_gaelic.lng b/FreeFileSync/Build/Languages/outdated/scottish_gaelic.lng index 928b14f8..cd52c07c 100644 --- a/FreeFileSync/Build/Languages/outdated/scottish_gaelic.lng +++ b/FreeFileSync/Build/Languages/outdated/scottish_gaelic.lng @@ -212,7 +212,7 @@ <source>Saving file %x...</source> <target></target> -<source>Time out while searching for folder %x.</source> +<source>Timeout while searching for folder %x.</source> <target></target> <source>Failed to connect to SFTP server %x.</source> diff --git a/FreeFileSync/Build/Languages/polish.lng b/FreeFileSync/Build/Languages/polish.lng index 8a808194..841cc6d5 100644 --- a/FreeFileSync/Build/Languages/polish.lng +++ b/FreeFileSync/Build/Languages/polish.lng @@ -334,7 +334,7 @@ Przesłany: %y bajtów <source>Searching for folder %x...</source> <target>Wyszukiwanie katalogu %x...</target> -<source>Time out while searching for folder %x.</source> +<source>Timeout while searching for folder %x.</source> <target>Przekroczono czas oczekiwania podczas szukania katalogu %x.</target> <source>Cannot get process information.</source> diff --git a/FreeFileSync/Build/Languages/portuguese.lng b/FreeFileSync/Build/Languages/portuguese.lng index 73f7f010..a374f87d 100644 --- a/FreeFileSync/Build/Languages/portuguese.lng +++ b/FreeFileSync/Build/Languages/portuguese.lng @@ -333,7 +333,7 @@ Actual: %y bytes <source>Searching for folder %x...</source> <target>À procura da pasta %x...</target> -<source>Time out while searching for folder %x.</source> +<source>Timeout while searching for folder %x.</source> <target>Tempo limite na procura da pasta %x.</target> <source>Cannot get process information.</source> diff --git a/FreeFileSync/Build/Languages/portuguese_br.lng b/FreeFileSync/Build/Languages/portuguese_br.lng index 82faeea6..485f32e3 100644 --- a/FreeFileSync/Build/Languages/portuguese_br.lng +++ b/FreeFileSync/Build/Languages/portuguese_br.lng @@ -333,7 +333,7 @@ Atual: %y bytes <source>Searching for folder %x...</source> <target>Buscando pela pasta %x...</target> -<source>Time out while searching for folder %x.</source> +<source>Timeout while searching for folder %x.</source> <target>Tempo esgotado na procura pela pasta %x.</target> <source>Cannot get process information.</source> diff --git a/FreeFileSync/Build/Languages/romanian.lng b/FreeFileSync/Build/Languages/romanian.lng index 5dc84db8..68448fa7 100644 --- a/FreeFileSync/Build/Languages/romanian.lng +++ b/FreeFileSync/Build/Languages/romanian.lng @@ -334,7 +334,7 @@ Actuală: %y baiți <source>Searching for folder %x...</source> <target>Caut dosarul %x...</target> -<source>Time out while searching for folder %x.</source> +<source>Timeout while searching for folder %x.</source> <target>Timp expirat la căutarea dosarului %x.</target> <source>Cannot get process information.</source> diff --git a/FreeFileSync/Build/Languages/russian.lng b/FreeFileSync/Build/Languages/russian.lng index 74fd26f7..a988dc6f 100644 --- a/FreeFileSync/Build/Languages/russian.lng +++ b/FreeFileSync/Build/Languages/russian.lng @@ -343,7 +343,7 @@ Actual: %y bytes <source>Searching for folder %x...</source> <target>Поиск папки %x...</target> -<source>Time out while searching for folder %x.</source> +<source>Timeout while searching for folder %x.</source> <target>Тайм-аут при поиске папки %x.</target> <source>Cannot get process information.</source> diff --git a/FreeFileSync/Build/Languages/serbian.lng b/FreeFileSync/Build/Languages/serbian.lng index 55f61c4b..a741dd76 100644 --- a/FreeFileSync/Build/Languages/serbian.lng +++ b/FreeFileSync/Build/Languages/serbian.lng @@ -334,7 +334,7 @@ Actual: %y bytes <source>Searching for folder %x...</source> <target>Тражим фолдер %x...</target> -<source>Time out while searching for folder %x.</source> +<source>Timeout while searching for folder %x.</source> <target>Дошло је до тајмаута при претрази фолдера %x.</target> <source>Cannot get process information.</source> diff --git a/FreeFileSync/Build/Languages/slovak.lng b/FreeFileSync/Build/Languages/slovak.lng index c7d78a3e..74dd1f68 100644 --- a/FreeFileSync/Build/Languages/slovak.lng +++ b/FreeFileSync/Build/Languages/slovak.lng @@ -334,7 +334,7 @@ Aktuálne: %y b <source>Searching for folder %x...</source> <target>Vyhľadávanie priečinka %x...</target> -<source>Time out while searching for folder %x.</source> +<source>Timeout while searching for folder %x.</source> <target>Vypršal časový limit pre nájdenie priečinka %x.</target> <source>Cannot get process information.</source> diff --git a/FreeFileSync/Build/Languages/slovenian.lng b/FreeFileSync/Build/Languages/slovenian.lng index 9fda61ff..1b6d116d 100644 --- a/FreeFileSync/Build/Languages/slovenian.lng +++ b/FreeFileSync/Build/Languages/slovenian.lng @@ -335,7 +335,7 @@ Dejansko: %y bajtov <source>Searching for folder %x...</source> <target>Iskanje mape %x...</target> -<source>Time out while searching for folder %x.</source> +<source>Timeout while searching for folder %x.</source> <target>Čas je potekel med iskanjem datoteke %x.</target> <source>Cannot get process information.</source> diff --git a/FreeFileSync/Build/Languages/spanish.lng b/FreeFileSync/Build/Languages/spanish.lng index fccdf5ad..f51aac89 100644 --- a/FreeFileSync/Build/Languages/spanish.lng +++ b/FreeFileSync/Build/Languages/spanish.lng @@ -333,7 +333,7 @@ Reales: %y bytes <source>Searching for folder %x...</source> <target>Buscando carpeta %x…</target> -<source>Time out while searching for folder %x.</source> +<source>Timeout while searching for folder %x.</source> <target>Tiempo agotado buscando la carpeta %x.</target> <source>Cannot get process information.</source> diff --git a/FreeFileSync/Build/Languages/swedish.lng b/FreeFileSync/Build/Languages/swedish.lng index 3a8e4f91..12c9b91f 100644 --- a/FreeFileSync/Build/Languages/swedish.lng +++ b/FreeFileSync/Build/Languages/swedish.lng @@ -333,7 +333,7 @@ Aktuell: %y byte <source>Searching for folder %x...</source> <target>Söker efter mappen %x...</target> -<source>Time out while searching for folder %x.</source> +<source>Timeout while searching for folder %x.</source> <target>Timeout vid sökning efter mappen %x.</target> <source>Cannot get process information.</source> diff --git a/FreeFileSync/Build/Languages/turkish.lng b/FreeFileSync/Build/Languages/turkish.lng index 0419a7f7..0d0b620a 100644 --- a/FreeFileSync/Build/Languages/turkish.lng +++ b/FreeFileSync/Build/Languages/turkish.lng @@ -333,7 +333,7 @@ Gerçekleşen: %y bayt <source>Searching for folder %x...</source> <target>%x klasörü aranıyor...</target> -<source>Time out while searching for folder %x.</source> +<source>Timeout while searching for folder %x.</source> <target>%x klasöründeki arama işlemi zaman aşımına uğradı.</target> <source>Cannot get process information.</source> diff --git a/FreeFileSync/Build/Languages/ukrainian.lng b/FreeFileSync/Build/Languages/ukrainian.lng index f821aa56..72f21600 100644 --- a/FreeFileSync/Build/Languages/ukrainian.lng +++ b/FreeFileSync/Build/Languages/ukrainian.lng @@ -334,7 +334,7 @@ Actual: %y bytes <source>Searching for folder %x...</source> <target>Пошук папки %x...</target> -<source>Time out while searching for folder %x.</source> +<source>Timeout while searching for folder %x.</source> <target>Вичерпався час пошуку папки %x.</target> <source>Cannot get process information.</source> diff --git a/FreeFileSync/Source/Makefile b/FreeFileSync/Source/Makefile index e7d21650..45f6f91a 100644 --- a/FreeFileSync/Source/Makefile +++ b/FreeFileSync/Source/Makefile @@ -83,6 +83,7 @@ CPP_LIST+=../../wx+/grid.cpp CPP_LIST+=../../wx+/image_tools.cpp CPP_LIST+=../../wx+/graph.cpp CPP_LIST+=../../wx+/tooltip.cpp +CPP_LIST+=../../wx+/http.cpp CPP_LIST+=../../wx+/image_resources.cpp CPP_LIST+=../../wx+/popup_dlg.cpp CPP_LIST+=../../wx+/popup_dlg_generated.cpp @@ -113,9 +114,10 @@ install: mkdir -p $(APPSHAREDIR) cp -R ../Build/Languages/ \ ../Build/Help/ \ - ../Build/Sync_Complete.wav \ + ../Build/ding.wav \ + ../Build/gong.wav \ + ../Build/harp.wav \ ../Build/Resources.zip \ - ../Build/styles.gtk_rc \ $(APPSHAREDIR) mkdir -p $(DOCSHAREDIR) diff --git a/FreeFileSync/Source/RealtimeSync/monitor.cpp b/FreeFileSync/Source/RealtimeSync/monitor.cpp index cce7ddf5..188c6c1a 100644 --- a/FreeFileSync/Source/RealtimeSync/monitor.cpp +++ b/FreeFileSync/Source/RealtimeSync/monitor.cpp @@ -95,7 +95,7 @@ WaitResult waitForChanges(const std::vector<Zstring>& folderPathPhrases, //throw const std::int64_t TICKS_DIR_CHECK_INTERVAL = CHECK_FOLDER_INTERVAL * ticksPerSec(); //0 on error TickVal lastCheck = getTicks(); //0 on error - while (true) + for (;;) { const bool checkDirExistNow = [&]() -> bool //checking once per sec should suffice { @@ -166,7 +166,11 @@ void waitForMissingDirs(const std::vector<Zstring>& folderPathPhrases, //throw F { #ifdef ZEN_WIN //1. login to network share, if necessary -> we probably do NOT want multiple concurrent runs: GUI!? - loginNetworkShare(folderPathFmt, false); //login networks shares, no PW prompt -> is this really RTS's job? + try{ + connectNetworkShare(folderPathFmt, false /*allowUserInteraction*/); //throw FileError + //is this really RTS's job? + } + catch (FileError&) {} #endif //2. check dir existence return zen::dirExists(folderPathFmt); @@ -230,12 +234,12 @@ void rts::monitorDirectories(const std::vector<Zstring>& folderPathPhrases, unsi //schedule initial execution (*after* all directories have arrived, which could take some time which we don't want to include) time_t nextExecDate = std::time(nullptr) + delay; - while (true) //loop over command invocations + for (;;) //loop over command invocations { DirWatcher::Entry lastChangeDetected; try { - while (true) //loop over detected changes + for (;;) //loop over detected changes { //wait for changes (and for all directories to become available) WaitResult res = waitForChanges(folderPathPhrases, [&](bool readyForSync) //throw FileError, ExecCommandNowException @@ -271,7 +275,7 @@ void rts::monitorDirectories(const std::vector<Zstring>& folderPathPhrases, unsi } }; - while (true) + for (;;) try { execMonitoring(); //throw FileError diff --git a/FreeFileSync/Source/application.cpp b/FreeFileSync/Source/application.cpp index 71a59bfb..7c1a802e 100644 --- a/FreeFileSync/Source/application.cpp +++ b/FreeFileSync/Source/application.cpp @@ -80,7 +80,7 @@ std::vector<Zstring> getCommandlineArgs(const wxApp& app) { std::vector<Zstring> args; #ifdef ZEN_WIN - //"Parsing C++ Command-Line Arguments": https://msdn.microsoft.com/en-us/library/17w5ykft + //"Parsing C++ Command-Line Arguments": https://msdn.microsoft.com/en-us/library/17w5ykft //we do the job ourselves! both wxWidgets and ::CommandLineToArgvW() parse "C:\" "D:\" as single line C:\" D:\" //-> "solution": we just don't support protected quotation mark! Zstring cmdLine = ::GetCommandLine(); //only way to get a unicode commandline @@ -615,7 +615,7 @@ void runBatchMode(const Zstring& globalConfigFile, const XmlBatchConfig& batchCf //class handling status updates and error messages BatchStatusHandler statusHandler(!batchCfg.runMinimized, //throw BatchAbortProcess extractJobName(referenceFile), - globalCfg.soundFileSyncComplete, + globalCfg.soundFileSyncFinished, timeStamp, batchCfg.logFolderPathPhrase, batchCfg.logfilesCountLimit, @@ -628,7 +628,9 @@ void runBatchMode(const Zstring& globalConfigFile, const XmlBatchConfig& batchCf batchCfg.mainCfg.onCompletion, globalCfg.gui.onCompletionHistory); - const std::vector<FolderPairCfg> cmpConfig = extractCompareCfg(batchCfg.mainCfg, globalCfg.fileTimeTolerance); + logNonDefaultSettings(globalCfg, statusHandler); //inform about (important) non-default global settings + + const std::vector<FolderPairCfg> cmpConfig = extractCompareCfg(batchCfg.mainCfg); bool allowPwPrompt = false; switch (batchCfg.handleError) @@ -646,6 +648,7 @@ void runBatchMode(const Zstring& globalConfigFile, const XmlBatchConfig& batchCf //COMPARE DIRECTORIES FolderComparison cmpResult = compare(globalCfg.optDialogs, + globalCfg.fileTimeTolerance, allowPwPrompt, //allowUserInteraction globalCfg.runWithBackgroundPriority, globalCfg.folderAccessTimeout, @@ -664,7 +667,7 @@ void runBatchMode(const Zstring& globalConfigFile, const XmlBatchConfig& batchCf globalCfg.verifyFileCopy, globalCfg.copyLockedFiles, globalCfg.copyFilePermissions, - globalCfg.failsafeFileCopy, + globalCfg.failSafeFileCopy, globalCfg.runWithBackgroundPriority, globalCfg.folderAccessTimeout, syncProcessCfg, diff --git a/FreeFileSync/Source/comparison.cpp b/FreeFileSync/Source/comparison.cpp index 99e9b8d6..c96fb05a 100644 --- a/FreeFileSync/Source/comparison.cpp +++ b/FreeFileSync/Source/comparison.cpp @@ -18,7 +18,7 @@ using namespace zen; -std::vector<FolderPairCfg> zen::extractCompareCfg(const MainConfiguration& mainCfg, int fileTimeTolerance) +std::vector<FolderPairCfg> zen::extractCompareCfg(const MainConfiguration& mainCfg) { //merge first and additional pairs std::vector<FolderPairEnh> allPairs = { mainCfg.firstPair }; @@ -31,7 +31,6 @@ std::vector<FolderPairCfg> zen::extractCompareCfg(const MainConfiguration& mainC return FolderPairCfg(enhPair.folderPathPhraseLeft_, enhPair.folderPathPhraseRight_, enhPair.altCmpConfig.get() ? enhPair.altCmpConfig->compareVar : mainCfg.cmpConfig.compareVar, enhPair.altCmpConfig.get() ? enhPair.altCmpConfig->handleSymlinks : mainCfg.cmpConfig.handleSymlinks, - fileTimeTolerance, enhPair.altCmpConfig.get() ? enhPair.altCmpConfig->ignoreTimeShiftMinutes : mainCfg.cmpConfig.ignoreTimeShiftMinutes, normalizeFilters(mainCfg.globalFilter, enhPair.localFilter), @@ -164,7 +163,7 @@ void checkFolderDependency(const std::vector<ResolvedFolderPair>& folderPairs, b class ComparisonBuffer { public: - ComparisonBuffer(const std::set<DirectoryKey>& keysToRead, ProcessCallback& callback); + ComparisonBuffer(const std::set<DirectoryKey>& keysToRead, int fileTimeTolerance, ProcessCallback& callback); //create comparison result table and fill category except for files existing on both sides: undefinedFiles and undefinedSymlinks are appended! std::shared_ptr<BaseFolderPair> compareByTimeSize(const ResolvedFolderPair& fp, const FolderPairCfg& fpConfig) const; @@ -181,11 +180,13 @@ private: std::vector<SymlinkPair*>& undefinedSymlinks) const; std::map<DirectoryKey, DirectoryValue> directoryBuffer; //contains only *existing* directories + const int fileTimeTolerance_; ProcessCallback& callback_; }; -ComparisonBuffer::ComparisonBuffer(const std::set<DirectoryKey>& keysToRead, ProcessCallback& callback) : callback_(callback) +ComparisonBuffer::ComparisonBuffer(const std::set<DirectoryKey>& keysToRead, int fileTimeTolerance, ProcessCallback& callback) : + fileTimeTolerance_(fileTimeTolerance), callback_(callback) { class CbImpl : public FillBufferCallback { @@ -328,7 +329,7 @@ std::shared_ptr<BaseFolderPair> ComparisonBuffer::compareByTimeSize(const Resolv for (FilePair* file : uncategorizedFiles) { switch (compareFileTime(file->getLastWriteTime<LEFT_SIDE>(), - file->getLastWriteTime<RIGHT_SIDE>(), fpConfig.fileTimeTolerance, fpConfig.ignoreTimeShiftMinutes)) + file->getLastWriteTime<RIGHT_SIDE>(), fileTimeTolerance_, fpConfig.ignoreTimeShiftMinutes)) { case TimeResult::EQUAL: //Caveat: @@ -806,7 +807,7 @@ std::shared_ptr<BaseFolderPair> ComparisonBuffer::performComparison(const Resolv bufValueRight != nullptr, fpCfg.filter.nameFilter->copyFilterAddingExclusion(excludefilterFailedRead), fpCfg.compareVar, - fpCfg.fileTimeTolerance, + fileTimeTolerance_, fpCfg.ignoreTimeShiftMinutes); //PERF_START; @@ -831,7 +832,42 @@ std::shared_ptr<BaseFolderPair> ComparisonBuffer::performComparison(const Resolv } +void zen::logNonDefaultSettings(const xmlAccess::XmlGlobalSettings& activeSettings, ProcessCallback& callback) +{ + const xmlAccess::XmlGlobalSettings defaultSettings; + std::wstring changedSettingsMsg; + + if (activeSettings.failSafeFileCopy != defaultSettings.failSafeFileCopy) + changedSettingsMsg += L"\n " + _("Fail-safe file copy") + L" - " + (activeSettings.failSafeFileCopy ? _("Enabled") : _("Disabled")); + + if (activeSettings.copyLockedFiles != defaultSettings.copyLockedFiles) + changedSettingsMsg += L"\n " + _("Copy locked files") + L" - " + (activeSettings.copyLockedFiles ? _("Enabled") : _("Disabled")); + + if (activeSettings.copyFilePermissions != defaultSettings.copyFilePermissions) + changedSettingsMsg += L"\n " + _("Copy file access permissions") + L" - " + (activeSettings.copyFilePermissions ? _("Enabled") : _("Disabled")); + + if (activeSettings.fileTimeTolerance != defaultSettings.fileTimeTolerance) + changedSettingsMsg += L"\n " + _("File time tolerance") + L" - " + numberTo<std::wstring>(activeSettings.fileTimeTolerance); + + if (activeSettings.folderAccessTimeout != defaultSettings.folderAccessTimeout) + changedSettingsMsg += L"\n " + _("Folder access timeout") + L" - " + numberTo<std::wstring>(activeSettings.folderAccessTimeout); + + if (activeSettings.runWithBackgroundPriority != defaultSettings.runWithBackgroundPriority) + changedSettingsMsg += L"\n " + _("Run with background priority") + L" - " + (activeSettings.runWithBackgroundPriority ? _("Enabled") : _("Disabled")); + + if (activeSettings.createLockFile != defaultSettings.createLockFile) + changedSettingsMsg += L"\n " + _("Lock directories during sync") + L" - " + (activeSettings.createLockFile ? _("Enabled") : _("Disabled")); + + if (activeSettings.verifyFileCopy != defaultSettings.verifyFileCopy) + changedSettingsMsg += L"\n " + _("Verify copied files") + L" - " + (activeSettings.verifyFileCopy ? _("Enabled") : _("Disabled")); + + if (!changedSettingsMsg.empty()) + callback.reportInfo(_("Using non-default global settings:") + changedSettingsMsg); +} + + FolderComparison zen::compare(xmlAccess::OptionalDialogs& warnings, + int fileTimeTolerance, bool allowUserInteraction, bool runWithBackgroundPriority, int folderAccessTimeout, @@ -840,6 +876,15 @@ FolderComparison zen::compare(xmlAccess::OptionalDialogs& warnings, const std::vector<FolderPairCfg>& cfgList, ProcessCallback& callback) { + //PERF_START; + + //indicator at the very beginning of the log to make sense of "total time" + //init process: keep at beginning so that all gui elements are initialized properly + callback.initNewPhase(-1, 0, ProcessCallback::PHASE_SCANNING); //may throw; it's not known how many files will be scanned => -1 objects + //callback.reportInfo(_("Starting comparison")); -> still useful? + + //------------------------------------------------------------------------------- + //specify process and resource handling priorities std::unique_ptr<ScheduleForBackgroundProcessing> backgroundPrio; if (runWithBackgroundPriority) @@ -863,13 +908,6 @@ FolderComparison zen::compare(xmlAccess::OptionalDialogs& warnings, callback.reportInfo(e.toString()); //may throw! } - //PERF_START; - - callback.reportInfo(_("Starting comparison")); //indicator at the very beginning of the log to make sense of "total time" - - //init process: keep at beginning so that all gui elements are initialized properly - callback.initNewPhase(-1, 0, ProcessCallback::PHASE_SCANNING); //may throw; it's not known how many files will be scanned => -1 objects - //-------------------some basic checks:------------------------------------------ const ResolvedBaseFolders& resInfo = initializeBaseFolders(cfgList, folderAccessTimeout, allowUserInteraction, callback); @@ -921,7 +959,7 @@ FolderComparison zen::compare(xmlAccess::OptionalDialogs& warnings, { //------------ traverse/read folders ----------------------------------------------------- //PERF_START; - ComparisonBuffer cmpBuff(dirsToRead, callback); + ComparisonBuffer cmpBuff(dirsToRead, fileTimeTolerance, callback); //PERF_STOP; //process binary comparison as one junk diff --git a/FreeFileSync/Source/comparison.h b/FreeFileSync/Source/comparison.h index 41617d4f..ea258696 100644 --- a/FreeFileSync/Source/comparison.h +++ b/FreeFileSync/Source/comparison.h @@ -22,7 +22,6 @@ struct FolderPairCfg const Zstring& folderPathPhraseRight, CompareVariant cmpVar, SymLinkHandling handleSymlinksIn, - int fileTimeToleranceIn, const std::vector<unsigned int>& ignoreTimeShiftMinutesIn, const NormalizedFilter& filterIn, const DirectionConfig& directCfg) : @@ -30,7 +29,6 @@ struct FolderPairCfg folderPathPhraseRight_(folderPathPhraseRight), compareVar(cmpVar), handleSymlinks(handleSymlinksIn), - fileTimeTolerance(fileTimeToleranceIn), ignoreTimeShiftMinutes(ignoreTimeShiftMinutesIn), filter(filterIn), directionCfg(directCfg) {} @@ -40,7 +38,6 @@ struct FolderPairCfg CompareVariant compareVar; SymLinkHandling handleSymlinks; - int fileTimeTolerance; std::vector<unsigned int> ignoreTimeShiftMinutes; NormalizedFilter filter; @@ -48,10 +45,14 @@ struct FolderPairCfg DirectionConfig directionCfg; }; -std::vector<FolderPairCfg> extractCompareCfg(const MainConfiguration& mainCfg, int fileTimeTolerance); //fill FolderPairCfg and resolve folder pairs +std::vector<FolderPairCfg> extractCompareCfg(const MainConfiguration& mainCfg); //fill FolderPairCfg and resolve folder pairs + +//inform about (important) non-default global settings related to comparison and synchronization +void logNonDefaultSettings(const xmlAccess::XmlGlobalSettings& currentSettings, ProcessCallback& callback); //FFS core routine: FolderComparison compare(xmlAccess::OptionalDialogs& warnings, + int fileTimeTolerance, bool allowUserInteraction, bool runWithBackgroundPriority, int folderAccessTimeout, diff --git a/FreeFileSync/Source/fs/abstract.cpp b/FreeFileSync/Source/fs/abstract.cpp index b8c5bec3..0f7c9ae6 100644 --- a/FreeFileSync/Source/fs/abstract.cpp +++ b/FreeFileSync/Source/fs/abstract.cpp @@ -93,7 +93,7 @@ AFS::FileAttribAfterCopy AFS::copyFileTransactional(const AbstractPath& apSource CAVEAT on FAT/FAT32: the sequence of deleting the target file and renaming "file.txt.ffs_tmp" to "file.txt" does NOT PRESERVE the creation time of the .ffs_tmp file, but SILENTLY "reuses" whatever creation time the old "file.txt" had! This "feature" is called "File System Tunneling": - http://blogs.msdn.com/b/oldnewthing/archive/2005/07/15/439261.aspx + https://blogs.msdn.microsoft.com/oldnewthing/20050715-14/?p=34923 http://support.microsoft.com/kb/172190/en-us */ return attr; diff --git a/FreeFileSync/Source/fs/abstract.h b/FreeFileSync/Source/fs/abstract.h index e424506c..17ece0b8 100644 --- a/FreeFileSync/Source/fs/abstract.h +++ b/FreeFileSync/Source/fs/abstract.h @@ -135,7 +135,7 @@ struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model t ~OutputStream(); size_t getBlockSize() const { return outStream_->getBlockSize(); } //non-zero block size is AFS contract! size_t tryWrite(const void* data, size_t len); //throw FileError; may return short! CONTRACT: bytesToWrite > 0 - FileId finalize (const std::function<void()>& onUpdateStatus); //throw FileError + FileId finalize(const std::function<void()>& onUpdateStatus); //throw FileError private: std::unique_ptr<OutputStreamImpl> outStream_; //bound! diff --git a/FreeFileSync/Source/fs/native.cpp b/FreeFileSync/Source/fs/native.cpp index de0b3d27..572881f5 100644 --- a/FreeFileSync/Source/fs/native.cpp +++ b/FreeFileSync/Source/fs/native.cpp @@ -23,6 +23,8 @@ #include <fcntl.h> //fallocate, fcntl #endif +using namespace zen; + namespace { @@ -453,13 +455,13 @@ private: void connectNetworkFolder(const Zstring& itemPathImpl, bool allowUserInteraction) const override //throw FileError { - warn_static("clean-up/remove/re-think the getAsyncConnectFolder() function") + warn_static("clean-up/remove/re-think connectNetworkFolder()") #ifdef ZEN_WIN initComForThread(); //throw FileError //login to network share, if necessary - loginNetworkShare(itemPathImpl, allowUserInteraction); + connectNetworkShare(itemPathImpl, allowUserInteraction); //throw FileError #endif } @@ -571,8 +573,8 @@ bool RecycleSessionNative::recycleItem(const AbstractPath& itemPath, const Zstri if (pos == Zstring::npos) return false; - const size_t pos2 = itemPathImpl.find(L'\\', pos + 1); - return endsWith(StringRef<const Zchar>(itemPathImpl.begin(), pos2 == Zstring::npos ? itemPathImpl.end() : itemPathImpl.begin() + pos2), AFS::TEMP_FILE_ENDING); + auto itEnd = std::find(itemPathImpl.begin() + pos + 1, itemPathImpl.end(), L'\\'); + return endsWith(StringRef<const Zchar>(itemPathImpl.begin(), itEnd), AFS::TEMP_FILE_ENDING); }(); //do not create RecycleBin.ffs_tmp directories recursively if recycling a particular item fails forever! diff --git a/FreeFileSync/Source/fs/native_traverser_impl.h b/FreeFileSync/Source/fs/native_traverser_impl.h index 3c3efe2f..330df263 100644 --- a/FreeFileSync/Source/fs/native_traverser_impl.h +++ b/FreeFileSync/Source/fs/native_traverser_impl.h @@ -25,8 +25,8 @@ AFS::FileId convertToAbstractFileId(const zen::FileId& fid) if (fid == zen::FileId()) return AFS::FileId(); - AFS::FileId out(reinterpret_cast<const char*>(&fid.first), sizeof(fid.first)); - out.append(reinterpret_cast<const char*>(&fid.second), sizeof(fid.second)); + AFS::FileId out(reinterpret_cast<const char*>(&fid.volumeId), sizeof(fid.volumeId)); + out.append(reinterpret_cast<const char*>(&fid.fileIndex), sizeof(fid.fileIndex)); return out; } diff --git a/FreeFileSync/Source/lib/db_file.cpp b/FreeFileSync/Source/lib/db_file.cpp index 0bcc3e40..47d963a3 100644 --- a/FreeFileSync/Source/lib/db_file.cpp +++ b/FreeFileSync/Source/lib/db_file.cpp @@ -406,9 +406,9 @@ private: warn_static("remove after migration! 2015-05-02") if (streamVersion_ == 1) { - auto devId = static_cast<DeviceId >(readNumber<std::uint64_t>(input)); // + auto devId = static_cast<VolumeId >(readNumber<std::uint64_t>(input)); // auto fileIdx = static_cast<FileIndex>(readNumber<std::uint64_t>(input)); //silence "loss of precision" compiler warnings - if (devId != 0 || fileIdx != 0) + if (devId != 0 && fileIdx != 0) { fileId.append(reinterpret_cast<const char*>(&devId), sizeof(devId)); fileId.append(reinterpret_cast<const char*>(&fileIdx), sizeof(fileIdx)); diff --git a/FreeFileSync/Source/lib/dir_exist_async.h b/FreeFileSync/Source/lib/dir_exist_async.h index 56a14cdc..3bb01f32 100644 --- a/FreeFileSync/Source/lib/dir_exist_async.h +++ b/FreeFileSync/Source/lib/dir_exist_async.h @@ -74,7 +74,7 @@ FolderStatus getFolderStatusNonBlocking(const std::set<AbstractPath, AFS::LessAb catch (const FileError& e) { output.failedChecks.emplace(fi.first, e); } } else - output.failedChecks.emplace(fi.first, FileError(replaceCpy(_("Time out while searching for folder %x."), L"%x", displayPathFmt))); + output.failedChecks.emplace(fi.first, FileError(replaceCpy(_("Timeout while searching for folder %x."), L"%x", displayPathFmt))); } return output; diff --git a/FreeFileSync/Source/lib/dir_lock.cpp b/FreeFileSync/Source/lib/dir_lock.cpp index fc98f308..4cc9b9f1 100644 --- a/FreeFileSync/Source/lib/dir_lock.cpp +++ b/FreeFileSync/Source/lib/dir_lock.cpp @@ -515,9 +515,8 @@ bool tryLock(const Zstring& lockFilePath) //throw FileError FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode, nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, CREATE_NEW, //_In_ DWORD dwCreationDisposition, - FILE_ATTRIBUTE_TEMPORARY | // - FILE_ATTRIBUTE_NORMAL | - FILE_FLAG_BACKUP_SEMANTICS, //_In_ DWORD dwFlagsAndAttributes, + FILE_ATTRIBUTE_TEMPORARY | //https://blogs.msdn.microsoft.com/larryosterman/2004/04/19/its-only-temporary/ + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, //_In_ DWORD dwFlagsAndAttributes, nullptr); //_In_opt_ HANDLE hTemplateFile if (fileHandle == INVALID_HANDLE_VALUE) { diff --git a/FreeFileSync/Source/lib/ffs_paths.cpp b/FreeFileSync/Source/lib/ffs_paths.cpp index 31694214..ee843156 100644 --- a/FreeFileSync/Source/lib/ffs_paths.cpp +++ b/FreeFileSync/Source/lib/ffs_paths.cpp @@ -141,7 +141,11 @@ Zstring zen::getFreeFileSyncLauncherPath() if (CFStringRef path = ::CFURLCopyFileSystemPath(absUrl, kCFURLPOSIXPathStyle)) { ZEN_ON_SCOPE_EXIT(::CFRelease(path)); - return appendSeparator(osx::cfStringToZstring(path)) + "Contents/MacOS/FreeFileSync"; + try + { + return appendSeparator(osx::cfStringToZstring(path)) + "Contents/MacOS/FreeFileSync"; //throw SysError + } + catch (SysError&) {} } } return Zstr("./FreeFileSync"); //fallback: at least give some hint... diff --git a/FreeFileSync/Source/lib/localization.cpp b/FreeFileSync/Source/lib/localization.cpp index 2f93c7ae..098f42d7 100644 --- a/FreeFileSync/Source/lib/localization.cpp +++ b/FreeFileSync/Source/lib/localization.cpp @@ -25,7 +25,7 @@ #elif defined ZEN_MAC #include <zen/osx_string.h> - #include <CoreServices/CoreServices.h> + // #include <CoreServices/CoreServices.h> #endif using namespace zen; @@ -134,14 +134,21 @@ struct LessTranslation return ::wcscasecmp(lhs.languageName.c_str(), rhs.languageName.c_str()) < 0; //ignores case; locale-dependent! #elif defined ZEN_MAC - CFStringRef langL = osx::createCFString(utfCvrtTo<std::string>(lhs.languageName).c_str()); - ZEN_ON_SCOPE_EXIT(::CFRelease(langL)); + try + { + CFStringRef langL = osx::createCFString(utfCvrtTo<std::string>(lhs.languageName).c_str()); //throw SysError + ZEN_ON_SCOPE_EXIT(::CFRelease(langL)); - CFStringRef langR = osx::createCFString(utfCvrtTo<std::string>(rhs.languageName).c_str()); - ZEN_ON_SCOPE_EXIT(::CFRelease(langR)); + CFStringRef langR = osx::createCFString(utfCvrtTo<std::string>(rhs.languageName).c_str()); //throw SysError + ZEN_ON_SCOPE_EXIT(::CFRelease(langR)); - //correctly position "czech" unlike wcscasecmp(): - return::CFStringCompare(langL, langR, kCFCompareLocalized | kCFCompareCaseInsensitive) == kCFCompareLessThan; //no-fail + //correctly position "czech" unlike wcscasecmp(): + return::CFStringCompare(langL, langR, kCFCompareLocalized | kCFCompareCaseInsensitive) == kCFCompareLessThan; //no-fail + } + catch (const SysError& e) + { + throw std::runtime_error(utfCvrtTo<std::string>(e.toString()) + " " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); + } #endif } }; diff --git a/FreeFileSync/Source/lib/process_xml.cpp b/FreeFileSync/Source/lib/process_xml.cpp index 5bd93e67..04b55ea5 100644 --- a/FreeFileSync/Source/lib/process_xml.cpp +++ b/FreeFileSync/Source/lib/process_xml.cpp @@ -981,7 +981,7 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& config) else inGeneral["Language"].attribute("Name", config.programLanguage); - inGeneral["FailSafeFileCopy" ].attribute("Enabled", config.failsafeFileCopy); + inGeneral["FailSafeFileCopy" ].attribute("Enabled", config.failSafeFileCopy); inGeneral["CopyLockedFiles" ].attribute("Enabled", config.copyLockedFiles); inGeneral["CopyFilePermissions" ].attribute("Enabled", config.copyFilePermissions); inGeneral["AutomaticRetry" ].attribute("Count" , config.automaticRetryCount); @@ -992,7 +992,8 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& config) inGeneral["LockDirectoriesDuringSync"].attribute("Enabled", config.createLockFile); inGeneral["VerifyCopiedFiles" ].attribute("Enabled", config.verifyFileCopy); inGeneral["LastSyncsLogSizeMax" ].attribute("Bytes" , config.lastSyncsLogFileSizeMax); - inGeneral["NotificationSound" ].attribute("SyncComplete", config.soundFileSyncComplete); + inGeneral["NotificationSound" ].attribute("CompareFinished", config.soundFileCompareFinished); + inGeneral["NotificationSound" ].attribute("SyncFinished" , config.soundFileSyncFinished); XmlIn inOpt = inGeneral["OptionalDialogs"]; inOpt["WarnUnresolvedConflicts" ].attribute("Enabled", config.optDialogs.warningUnresolvedConflicts); @@ -1368,7 +1369,7 @@ void writeConfig(const XmlGlobalSettings& config, XmlOut& out) outGeneral["Language"].attribute("Name", config.programLanguage); - outGeneral["FailSafeFileCopy" ].attribute("Enabled", config.failsafeFileCopy); + outGeneral["FailSafeFileCopy" ].attribute("Enabled", config.failSafeFileCopy); outGeneral["CopyLockedFiles" ].attribute("Enabled", config.copyLockedFiles); outGeneral["CopyFilePermissions" ].attribute("Enabled", config.copyFilePermissions); outGeneral["AutomaticRetry" ].attribute("Count" , config.automaticRetryCount); @@ -1379,7 +1380,8 @@ void writeConfig(const XmlGlobalSettings& config, XmlOut& out) outGeneral["LockDirectoriesDuringSync"].attribute("Enabled", config.createLockFile); outGeneral["VerifyCopiedFiles" ].attribute("Enabled", config.verifyFileCopy); outGeneral["LastSyncsLogSizeMax" ].attribute("Bytes" , config.lastSyncsLogFileSizeMax); - outGeneral["NotificationSound" ].attribute("SyncComplete", config.soundFileSyncComplete); + outGeneral["NotificationSound" ].attribute("CompareFinished", config.soundFileCompareFinished); + outGeneral["NotificationSound" ].attribute("SyncFinished" , config.soundFileSyncFinished); XmlOut outOpt = outGeneral["OptionalDialogs"]; outOpt["WarnUnresolvedConflicts" ].attribute("Enabled", config.optDialogs.warningUnresolvedConflicts); diff --git a/FreeFileSync/Source/lib/process_xml.h b/FreeFileSync/Source/lib/process_xml.h index 8e72f71c..6e06e795 100644 --- a/FreeFileSync/Source/lib/process_xml.h +++ b/FreeFileSync/Source/lib/process_xml.h @@ -140,7 +140,7 @@ struct XmlGlobalSettings //--------------------------------------------------------------------- //Shared (GUI/BATCH) settings wxLanguage programLanguage = zen::getSystemLanguage(); - bool failsafeFileCopy = true; + bool failSafeFileCopy = true; bool copyLockedFiles = false; //safer default: avoid copies of partially written files bool copyFilePermissions = false; size_t automaticRetryCount = 0; @@ -152,7 +152,8 @@ struct XmlGlobalSettings bool createLockFile = true; bool verifyFileCopy = false; size_t lastSyncsLogFileSizeMax = 100000; //maximum size for LastSyncs.log: use a human-readable number - Zstring soundFileSyncComplete = Zstr("gong.wav"); + Zstring soundFileCompareFinished; + Zstring soundFileSyncFinished= Zstr("gong.wav"); OptionalDialogs optDialogs; diff --git a/FreeFileSync/Source/lib/resolve_path.cpp b/FreeFileSync/Source/lib/resolve_path.cpp index 371d3ee3..f51435c0 100644 --- a/FreeFileSync/Source/lib/resolve_path.cpp +++ b/FreeFileSync/Source/lib/resolve_path.cpp @@ -511,7 +511,7 @@ void getDirectoryAliasesRecursive(const Zstring& dirpath, std::set<Zstring, Less addEnvVar(L"WinDir"); // C:\Windows addEnvVar(L"Temp"); // C:\Windows\Temp - //add CSIDL values: http://msdn.microsoft.com/en-us/library/bb762494(v=vs.85).aspx + //add CSIDL values: https://msdn.microsoft.com/en-us/library/bb762494 const auto& csidlMap = CsidlConstants::get(); envToDir.insert(csidlMap.begin(), csidlMap.end()); @@ -601,25 +601,158 @@ Zstring zen::getResolvedFilePath(const Zstring& pathPhrase) //noexcept #ifdef ZEN_WIN -void zen::loginNetworkShare(const Zstring& dirpathOrig, bool allowUserInteraction) //throw() - user interaction: show OS password prompt +namespace +{ +//1. let's not keep state whether connections are established or not +//2. show only a single login dialog per network share +//3. show login dialogs for the same network address but different share one after another: usually passing the first login will auto-login the rest! +class NetworkConnector +{ +public: + static NetworkConnector& getInstance() + { + static NetworkConnector inst; + return inst; //meyers singleton: avoid static initialization order problem in global namespace! + } + + struct Credentials + { + Zstring localName; //optional + Zstring remoteName; + bool allowUserInteraction = false; + }; + + void establishConnection(const Credentials& cred) //throw FileError + { + std::unique_lock<std::mutex> dummy(lockConnections); + + auto it = activeLogins.find(cred); + if (it != activeLogins.end()) + { + auto fut = it->second; + dummy.unlock(); //wait *outside* the lock! + return fut.get(); //throw FileError + } + else + { + std::promise<void> connectShare; + activeLogins.emplace(cred, connectShare.get_future()); + + //get or create mutex associated with the network address: + const Zstring networkName = beforeLast(cred.remoteName, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); + NetworkLock& netLock = networkLocks[networkName]; + ++netLock.shareCount; + std::mutex& netMutex = netLock.m; + dummy.unlock(); //do work *outside* the lock! + + ZEN_ON_SCOPE_EXIT + ( + dummy.lock(); + activeLogins.erase(cred); + if (--netLock.shareCount == 0) + networkLocks.erase(networkName); + dummy.unlock(); + ); + + try + { + std::lock_guard<std::mutex> dummy2(netMutex); //show one login dialog per network address at a time; ideally we should only serialize connection + //attempts that show the login dialog: fulfilled for deviceless connections in this context, but not necessarily for mapped network shares + connect(cred); //throw FileError + connectShare.set_value(); + } + catch (FileError&) + { + connectShare.set_exception(std::current_exception()); + throw; + } + } + } + +private: + NetworkConnector() {} + NetworkConnector (const NetworkConnector&) = delete; + NetworkConnector& operator=(const NetworkConnector&) = delete; + + static void connect(const Credentials& cred); //throw FileError + + std::mutex lockConnections; + std::map<Credentials, std::shared_future<void>> activeLogins; + + struct NetworkLock + { + std::mutex m; + int shareCount = 0; + }; + std::map<Zstring, NetworkLock, LessFilePath> networkLocks; +}; + + +bool operator<(const NetworkConnector::Credentials& lhs, const NetworkConnector::Credentials& rhs) +{ + int rv = cmpFilePath(lhs.localName.c_str(), lhs.localName.size(), + rhs.localName.c_str(), rhs.localName.size()); + if (rv != 0) return rv < 0; + + rv = cmpFilePath(lhs.remoteName.c_str(), lhs.remoteName.size(), + rhs.remoteName.c_str(), rhs.remoteName.size()); + if (rv != 0) return rv < 0; + + return static_cast<int>(lhs.allowUserInteraction) < static_cast<int>(rhs.allowUserInteraction); +} + + +//blocks heavily if network is not reachable!!! +void NetworkConnector::connect(const Credentials& cred) //throw FileError +{ + NETRESOURCE trgRes = {}; + trgRes.dwType = RESOURCETYPE_DISK; + assert(!cred.remoteName.empty()); + trgRes.lpRemoteName = const_cast<LPWSTR>(cred.remoteName.c_str()); // + if (!cred.localName.empty()) + trgRes.lpLocalName = const_cast<LPWSTR>(cred.localName.c_str()); //lpNetResource is marked "__in", seems WNetAddConnection2 is not const correct! + + const DWORD rv = ::WNetAddConnection2(&trgRes, //__in LPNETRESOURCE lpNetResource, + nullptr, //__in LPCTSTR lpPassword, + nullptr, //__in LPCTSTR lpUsername, + cred.allowUserInteraction ? CONNECT_INTERACTIVE : 0); //__in DWORD dwFlags + if (rv != NO_ERROR) + throw FileError(replaceCpy(_("Unable to connect to %x."), L"%x", fmtPath(trgRes.lpRemoteName)), formatSystemError(L"WNetAddConnection2", rv)); + // 53 ERROR_BAD_NETPATH + // 67 ERROR_BAD_NET_NAME + // 86 ERROR_INVALID_PASSWORD + //1219 ERROR_SESSION_CREDENTIAL_CONFLICT Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed. Disconnect all previous connections to the server or shared resource and try again. + //1326 ERROR_LOGON_FAILURE Logon failure: unknown user name or bad password. + //1236 ERROR_CONNECTION_ABORTED +} +} + + +void zen::connectNetworkShare(const Zstring& dirpathOrig, bool allowUserInteraction) //throw FileError { /* - ATTENTION: it is not safe to retrieve UNC path via ::WNetGetConnection() for every type of network share: - - network type |::WNetGetConnection rv | lpRemoteName | existing UNC path - -----------------------------|-------------------------|---------------------------------|---------------- - inactive local network share | ERROR_CONNECTION_UNAVAIL| \\192.168.1.27\new2 | YES - WebDrive | NO_ERROR | \\Webdrive-ZenJu\GNU | NO - Box.net (WebDav) | NO_ERROR | \\www.box.net\DavWWWRoot\dav | YES - NetDrive | ERROR_NOT_CONNECTED | <empty> | NO + ---------------------- + |Mapped Network Share| + ---------------------- + test if unconnected: WNetGetConnection + ERROR_CONNECTION_UNAVAIL (can't use GetFileAttributes: returns ERROR_PATH_NOT_FOUND) + Password-proteced? + Windows remembers credentials? + + ----------------------- + |Deviceless Connection| + ----------------------- + test if unconnected: GetFileAttributes + ERROR_LOGON_FAILURE => looks like GetFileAttributes() internally tries to establish the connection! + Password-proteced? yes, otherwise GetFileAttributes would have succeeded + Windows remembers credentials? no, otherwise GetFileAttributes would have succeeded ____________________________________________________________________________________________________________ Windows Login Prompt Naming Conventions: - network share: \\<server>\<share> e.g. \\WIN-XP\folder or \\192.168.1.50\folder - user account: <Domain>\<user> e.g. WIN-XP\Zenju or 192.168.1.50\Zenju + network share: \\<server>\<share> e.g. \\WIN7-VM\folder or \\192.168.1.50\folder + user account: <Domain>\<user> e.g. WIN7-VM\Zenju or 192.168.1.50\Zenju + Note: the remote login user must be different from the active login user of the network share (at least for Win7-hosted share)! Windows Command Line: - - list *all* active network connections, including deviceless ones which are hidden in Explorer: + - list all active network connections, including deviceless ones which are hidden in Explorer: net use - delete active connection: net use /delete \\server\share @@ -630,51 +763,20 @@ void zen::loginNetworkShare(const Zstring& dirpathOrig, bool allowUserInteractio Problems: I. WNetAddConnection2() allows (at least certain) invalid credentials (e.g. username: a/password: a) and establishes an *unusable* connection II. WNetAddConnection2() refuses to overwrite an existing (unusable) connection created in I), but shows prompt repeatedly - III. WNetAddConnection2() won't bring up the prompt if *wrong* credentials had been entered just recently, even with CONNECT_INTERACTIVE specified! => 2-step proccess - */ + III. WNetAddConnection2() won't bring up the prompt if *wrong* credentials had been entered just recently, even with CONNECT_INTERACTIVE specified! + ____________________________________________________________________________________________________________ - auto connect = [&](NETRESOURCE& trgRes) //blocks heavily if network is not reachable!!! - { - //1. first try to connect without user interaction - blocks! - DWORD rv = ::WNetAddConnection2(&trgRes, //__in LPNETRESOURCE lpNetResource, - nullptr, //__in LPCTSTR lpPassword, - nullptr, //__in LPCTSTR lpUsername, - 0); //__in DWORD dwFlags - //53L ERROR_BAD_NETPATH The network path was not found. - //67L ERROR_BAD_NET_NAME - //86L ERROR_INVALID_PASSWORD - //1219L ERROR_SESSION_CREDENTIAL_CONFLICT Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed. Disconnect all previous connections to the server or shared resource and try again. - //1326L ERROR_LOGON_FAILURE Logon failure: unknown user name or bad password. - //1236L ERROR_CONNECTION_ABORTED - if (somethingExists(trgRes.lpRemoteName)) //blocks! - return; //success: connection usable! -> don't care about "rv" - - if (rv == ERROR_BAD_NETPATH || //like ERROR_PATH_NOT_FOUND - rv == ERROR_BAD_NET_NAME|| //like ERROR_FILE_NOT_FOUND - rv == ERROR_CONNECTION_ABORTED) //failed to connect to a network that existed not too long ago; will later return ERROR_BAD_NETPATH - return; //no need to show a prompt for an unreachable network device - - //2. if first attempt failed, we need to *force* prompt by using CONNECT_PROMPT - if (allowUserInteraction) - { - //avoid problem II.) - DWORD rv2= ::WNetCancelConnection2(trgRes.lpRemoteName, //_In_ LPCTSTR lpName, - 0, //_In_ DWORD dwFlags, - true); //_In_ BOOL fForce - //2250L ERROR_NOT_CONNECTED - - //enforce login prompt - DWORD rv3 = ::WNetAddConnection2(&trgRes, // __in LPNETRESOURCE lpNetResource, - nullptr, // __in LPCTSTR lpPassword, - nullptr, // __in LPCTSTR lpUsername, - CONNECT_INTERACTIVE | CONNECT_PROMPT); //__in DWORD dwFlags - (void)rv2; - (void)rv3; - } - }; + ATTENTION: ::WNetGetConnection() does not return a valid UNC path for every type of network share: + network type |::WNetGetConnection rv | lpRemoteName | existing UNC path + ------------------------------|-------------------------|---------------------------------|---------------- + inactive mapped network share | ERROR_CONNECTION_UNAVAIL| \\192.168.1.27\new2 | YES + WebDrive | NO_ERROR | \\Webdrive-ZenJu\GNU | NO + Box.net (WebDav) | NO_ERROR | \\www.box.net\DavWWWRoot\dav | YES + NetDrive | ERROR_NOT_CONNECTED | <empty> | NO + */ - Zstring dirpath = removeLongPathPrefix(dirpathOrig); + Zstring dirpath = removeLongPathPrefix(dirpathOrig); //\\?\C:\path is not a UNC path: https://msdn.microsoft.com/en-us/library/windows/desktop/bb773712 trim(dirpath, true, false); //1. locally mapped network share @@ -684,26 +786,22 @@ void zen::loginNetworkShare(const Zstring& dirpathOrig, bool allowUserInteractio { DWORD bufferSize = 10000; std::vector<wchar_t> remoteNameBuffer(bufferSize); - - //map local -> remote - - //note: following function call does NOT block! + //note: THE following call does NOT block! DWORD rv = ::WNetGetConnection(driveLetter.c_str(), //__in LPCTSTR lpLocalName in the form "<driveletter>:" &remoteNameBuffer[0], //__out LPTSTR lpRemoteName, &bufferSize); //__inout LPDWORD lpnLength if (rv == ERROR_CONNECTION_UNAVAIL) //remoteNameBuffer will be filled nevertheless! { - //ERROR_CONNECTION_UNAVAIL: network mapping is existing, but not connected - + //1201 ERROR_CONNECTION_UNAVAIL: "device is not currently connected but it is a remembered connection." + //2250 ERROR_NOT_CONNECTED: "This network connection does not exist" Zstring networkShare = &remoteNameBuffer[0]; if (!networkShare.empty()) { - NETRESOURCE trgRes = {}; - trgRes.dwType = RESOURCETYPE_DISK; - trgRes.lpLocalName = const_cast<LPWSTR>(driveLetter.c_str()); //lpNetResource is marked "__in", seems WNetAddConnection2 is not const correct! - trgRes.lpRemoteName = const_cast<LPWSTR>(networkShare.c_str()); // - - connect(trgRes); //blocks! + NetworkConnector::Credentials cred = {}; + cred.localName = driveLetter; + cred.remoteName = networkShare; + cred.allowUserInteraction = allowUserInteraction; + NetworkConnector::getInstance().establishConnection(cred); //throw FileError } } } @@ -722,14 +820,15 @@ void zen::loginNetworkShare(const Zstring& dirpathOrig, bool allowUserInteractio if (!networkShare.empty()) { - //::WNetGetResourceInformation seems to fail with ERROR_BAD_NET_NAME even for existing unconnected network shares! - // => unconditionally try to connect to network share, seems we cannot reliably detect connection status otherwise - - NETRESOURCE trgRes = {}; - trgRes.dwType = RESOURCETYPE_DISK; - trgRes.lpRemoteName = const_cast<LPWSTR>(networkShare.c_str()); //trgRes is "__in" - - connect(trgRes); //blocks! + //test if network share is connected: WNetGetResourceInformation() seems to fail with ERROR_BAD_NET_NAME even for existing unconnected network shares! => alternative: + const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(networkShare).c_str()); + if (attr == INVALID_FILE_ATTRIBUTES && ::GetLastError() == ERROR_LOGON_FAILURE) + { + NetworkConnector::Credentials cred = {}; + cred.remoteName = networkShare; + cred.allowUserInteraction = allowUserInteraction; + NetworkConnector::getInstance().establishConnection(cred); //throw FileError + } } } } diff --git a/FreeFileSync/Source/lib/resolve_path.h b/FreeFileSync/Source/lib/resolve_path.h index 34d50360..d6ab29ad 100644 --- a/FreeFileSync/Source/lib/resolve_path.h +++ b/FreeFileSync/Source/lib/resolve_path.h @@ -31,7 +31,7 @@ std::vector<Zstring> getDirectoryAliases(const Zstring& folderPathPhrase); //may #ifdef ZEN_WIN //*blocks* if network is not reachable or when showing login prompt dialog! - void loginNetworkShare(const Zstring& dirpath, bool allowUserInteraction); //noexcept; user interaction: show OS password prompt + void connectNetworkShare(const Zstring& dirpath, bool allowUserInteraction /*show OS password prompt*/); //throw FileError #endif } diff --git a/FreeFileSync/Source/lib/status_handler.h b/FreeFileSync/Source/lib/status_handler.h index 92956fb2..b2048329 100644 --- a/FreeFileSync/Source/lib/status_handler.h +++ b/FreeFileSync/Source/lib/status_handler.h @@ -84,7 +84,7 @@ protected: if (!abortRequested) statusText_ = text; requestUiRefresh(); /*throw X */ } - void reportInfo (const std::wstring& text) override { assert(!text.empty()); if (!abortRequested) statusText_ = text; requestUiRefresh(); /*throw X */ } //log text in derived class + void reportInfo(const std::wstring& text) override { assert(!text.empty()); if (!abortRequested) statusText_ = text; requestUiRefresh(); /*throw X */ } //log text in derived class //implement AbortCallback void requestAbortion() override diff --git a/FreeFileSync/Source/synchronization.cpp b/FreeFileSync/Source/synchronization.cpp index 1d1eb1d1..093cc600 100644 --- a/FreeFileSync/Source/synchronization.cpp +++ b/FreeFileSync/Source/synchronization.cpp @@ -618,7 +618,7 @@ public: SynchronizeFolderPair(ProcessCallback& procCallback, bool verifyCopiedFiles, bool copyFilePermissions, - bool transactionalFileCopy, + bool failSafeFileCopy, #ifdef ZEN_WIN shadow::ShadowCopy* shadowCopyHandler, #endif @@ -632,16 +632,7 @@ public: delHandlingRight_(delHandlingRight), verifyCopiedFiles_(verifyCopiedFiles), copyFilePermissions_(copyFilePermissions), - transactionalFileCopy_(transactionalFileCopy), - txtCreatingFile (_("Creating file %x" )), - txtCreatingLink (_("Creating symbolic link %x" )), - txtCreatingFolder (_("Creating folder %x" )), - txtOverwritingFile (_("Updating file %x" )), - txtOverwritingLink (_("Updating symbolic link %x")), - txtVerifying (_("Verifying file %x" )), - txtWritingAttributes(_("Updating attributes of %x" )), - txtMovingFile (_("Moving file %x to %y")) - {} + failSafeFileCopy_(failSafeFileCopy) {} void startSync(BaseFolderPair& baseFolder) { @@ -707,17 +698,17 @@ private: const bool verifyCopiedFiles_; const bool copyFilePermissions_; - const bool transactionalFileCopy_; + const bool failSafeFileCopy_; //preload status texts - const std::wstring txtCreatingFile; - const std::wstring txtCreatingLink; - const std::wstring txtCreatingFolder; - const std::wstring txtOverwritingFile; - const std::wstring txtOverwritingLink; - const std::wstring txtVerifying; - const std::wstring txtWritingAttributes; - const std::wstring txtMovingFile; + const std::wstring txtCreatingFile {_("Creating file %x" )}; + const std::wstring txtCreatingLink {_("Creating symbolic link %x")}; + const std::wstring txtCreatingFolder {_("Creating folder %x" )}; + const std::wstring txtOverwritingFile {_("Updating file %x" )}; + const std::wstring txtOverwritingLink {_("Updating symbolic link %x")}; + const std::wstring txtVerifying {_("Verifying file %x" )}; + const std::wstring txtWritingAttributes{_("Updating attributes of %x")}; + const std::wstring txtMovingFile {_("Moving file %x to %y" )}; }; //--------------------------------------------------------------------------------------------------------------- @@ -1282,7 +1273,7 @@ void SynchronizeFolderPair::synchronizeFileInt(FilePair& file, SyncOperation syn //if fail-safe file copy is active, then the next operation will be a simple "rename" //=> don't risk reportStatus() throwing GuiAbortProcess() leaving the target deleted rather than updated! - if (!transactionalFileCopy_) + if (!failSafeFileCopy_) reportStatus(txtOverwritingFile, AFS::getDisplayPath(targetPathResolvedOld)); //restore status text copy file }; @@ -1614,19 +1605,54 @@ void verifyFiles(const AbstractPath& sourcePath, const AbstractPath& targetPath, if (Opt<Zstring> nativeTargetPath = AFS::getNativeItemPath(targetPath)) { #ifdef ZEN_WIN - const HANDLE fileHandle = ::CreateFile(applyLongPathPrefix(*nativeTargetPath).c_str(), //_In_ LPCTSTR lpFileName, - GENERIC_WRITE | //_In_ DWORD dwDesiredAccess, - GENERIC_READ, //=> request read-access, too, just like "copy /v" command - FILE_SHARE_READ | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode, - nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, - OPEN_EXISTING, //_In_ DWORD dwCreationDisposition, - FILE_ATTRIBUTE_NORMAL, //_In_ DWORD dwFlagsAndAttributes, - nullptr); //_In_opt_ HANDLE hTemplateFile - if (fileHandle == INVALID_HANDLE_VALUE) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot open file %x."), L"%x", fmtPath(*nativeTargetPath)), L"CreateFile"); - ZEN_ON_SCOPE_EXIT(::CloseHandle(fileHandle)); - - if (!::FlushFileBuffers(fileHandle)) + //temporarily reset read-only flag if required + DWORD attribs = INVALID_FILE_ATTRIBUTES; + ZEN_ON_SCOPE_EXIT( + if (attribs != INVALID_FILE_ATTRIBUTES) + ::SetFileAttributes(applyLongPathPrefix(*nativeTargetPath).c_str(), attribs); + ); + + auto openFile = [&] + { + return ::CreateFile(applyLongPathPrefix(*nativeTargetPath).c_str(), //_In_ LPCTSTR lpFileName, + GENERIC_WRITE | //_In_ DWORD dwDesiredAccess, + GENERIC_READ, //=> request read-access, too, just like "copy /v" command + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode, + nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, + OPEN_EXISTING, //_In_ DWORD dwCreationDisposition, + FILE_ATTRIBUTE_NORMAL, //_In_ DWORD dwFlagsAndAttributes, + nullptr); //_In_opt_ HANDLE hTemplateFile + }; + + HANDLE hFile = openFile(); + if (hFile == INVALID_HANDLE_VALUE) + { + DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls! + + //CreateFile/FILE_SHARE_WRITE fails for read-only file => temporarily remove attribute + if (ec == ERROR_ACCESS_DENIED) //fails if file is read-only (or for "other" reasons) + { + const DWORD tmpAttr = ::GetFileAttributes(applyLongPathPrefix(*nativeTargetPath).c_str()); + if (tmpAttr == INVALID_FILE_ATTRIBUTES) + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(*nativeTargetPath)), L"GetFileAttributes"); + + if (tmpAttr & FILE_ATTRIBUTE_READONLY) + { + if (!::SetFileAttributes(applyLongPathPrefix(*nativeTargetPath).c_str(), FILE_ATTRIBUTE_NORMAL)) + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtPath(*nativeTargetPath)), L"SetFileAttributes"); + + attribs = tmpAttr; //reapplied on scope exit + hFile = openFile(); //without read-only this should work + ec = ::GetLastError(); + } + } + //begin of "regular" error reporting + if (hFile == INVALID_HANDLE_VALUE) + throw FileError(replaceCpy(_("Cannot open file %x."), L"%x", fmtPath(*nativeTargetPath)), formatSystemError(L"CreateFile", ec)); + } + ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile)); + + if (!::FlushFileBuffers(hFile)) THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(*nativeTargetPath)), L"FlushFileBuffers"); #elif defined ZEN_LINUX || defined ZEN_MAC @@ -1663,7 +1689,7 @@ AFS::FileAttribAfterCopy SynchronizeFolderPair::copyFileWithCallback(const Abstr { AFS::FileAttribAfterCopy newAttr = AFS::copyFileTransactional(sourcePathTmp, targetPath, //throw FileError, ErrorFileLocked copyFilePermissions_, - transactionalFileCopy_, + failSafeFileCopy_, onDeleteTargetFile, onNotifyCopyStatus); @@ -1806,36 +1832,13 @@ void zen::synchronize(const TimeComp& timeStamp, bool verifyCopiedFiles, bool copyLockedFiles, bool copyFilePermissions, - bool transactionalFileCopy, + bool failSafeFileCopy, bool runWithBackgroundPriority, int folderAccessTimeout, const std::vector<FolderPairSyncCfg>& syncConfig, FolderComparison& folderCmp, ProcessCallback& callback) { - //specify process and resource handling priorities - std::unique_ptr<ScheduleForBackgroundProcessing> backgroundPrio; - if (runWithBackgroundPriority) - try - { - backgroundPrio = std::make_unique<ScheduleForBackgroundProcessing>(); //throw FileError - } - catch (const FileError& e) //not an error in this context - { - callback.reportInfo(e.toString()); //may throw! - } - - //prevent operating system going into sleep state - std::unique_ptr<PreventStandby> noStandby; - try - { - noStandby = std::make_unique<PreventStandby>(); //throw FileError - } - catch (const FileError& e) //not an error in this context - { - callback.reportInfo(e.toString()); //may throw! - } - //PERF_START; if (syncConfig.size() != folderCmp.size()) @@ -1861,10 +1864,35 @@ void zen::synchronize(const TimeComp& timeStamp, ProcessCallback::PHASE_SYNCHRONIZING); } - std::vector<FolderPairJobType> jobType(folderCmp.size(), FolderPairJobType::PROCESS); //folder pairs may be skipped after fatal errors were found + //------------------------------------------------------------------------------- + + //specify process and resource handling priorities + std::unique_ptr<ScheduleForBackgroundProcessing> backgroundPrio; + if (runWithBackgroundPriority) + try + { + backgroundPrio = std::make_unique<ScheduleForBackgroundProcessing>(); //throw FileError + } + catch (const FileError& e) //not an error in this context + { + callback.reportInfo(e.toString()); //may throw! + } + + //prevent operating system going into sleep state + std::unique_ptr<PreventStandby> noStandby; + try + { + noStandby = std::make_unique<PreventStandby>(); //throw FileError + } + catch (const FileError& e) //not an error in this context + { + callback.reportInfo(e.toString()); //may throw! + } //-------------------execute basic checks all at once before starting sync-------------------------------------- + std::vector<FolderPairJobType> jobType(folderCmp.size(), FolderPairJobType::PROCESS); //folder pairs may be skipped after fatal errors were found + std::vector<SyncStatistics::ConflictInfo> unresolvedConflicts; //aggregate information @@ -2225,7 +2253,7 @@ void zen::synchronize(const TimeComp& timeStamp, callback); - SynchronizeFolderPair syncFP(callback, verifyCopiedFiles, copyPermissionsFp, transactionalFileCopy, + SynchronizeFolderPair syncFP(callback, verifyCopiedFiles, copyPermissionsFp, failSafeFileCopy, #ifdef ZEN_WIN shadowCopyHandler.get(), #endif diff --git a/FreeFileSync/Source/synchronization.h b/FreeFileSync/Source/synchronization.h index 795c6370..d066e65d 100644 --- a/FreeFileSync/Source/synchronization.h +++ b/FreeFileSync/Source/synchronization.h @@ -90,7 +90,7 @@ void synchronize(const TimeComp& timeStamp, bool verifyCopiedFiles, bool copyLockedFiles, bool copyFilePermissions, - bool transactionalFileCopy, + bool failSafeFileCopy, bool runWithBackgroundPriority, int folderAccessTimeout, const std::vector<FolderPairSyncCfg>& syncConfig, //CONTRACT: syncConfig and folderCmp correspond row-wise! diff --git a/FreeFileSync/Source/ui/custom_grid.cpp b/FreeFileSync/Source/ui/custom_grid.cpp index 9e352d58..342d0a6a 100644 --- a/FreeFileSync/Source/ui/custom_grid.cpp +++ b/FreeFileSync/Source/ui/custom_grid.cpp @@ -517,7 +517,6 @@ private: break; } - if (fileIcon.IsOk()) { wxRect rectIcon = rectTmp; @@ -526,10 +525,10 @@ private: auto drawIcon = [&](const wxBitmap& icon) { if (isActive) - drawBitmapRtlNoMirror(dc, icon, rectIcon, wxALIGN_CENTER, this->buffer); //without "this->" GCC 4.7.2 compiler crash on Debian + drawBitmapRtlNoMirror(dc, icon, rectIcon, wxALIGN_CENTER); else drawBitmapRtlNoMirror(dc, wxBitmap(icon.ConvertToImage().ConvertToGreyscale(1.0 / 3, 1.0 / 3, 1.0 / 3)), //treat all channels equally! - rectIcon, wxALIGN_CENTER, this->buffer); + rectIcon, wxALIGN_CENTER); }; drawIcon(fileIcon); @@ -542,22 +541,24 @@ private: rectTmp.width -= iconSize; } - std::unique_ptr<wxDCTextColourChanger> dummy3; - if (getRowDisplayType(row) != DisplayType::NORMAL) - dummy3 = std::make_unique<wxDCTextColourChanger>(dc, *wxBLACK); //accessibility: always set both foreground AND background colors! + wxDCTextColourChanger dummy(dc); + if (!isActive) + dummy.Set(wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT)); + else if (getRowDisplayType(row) != DisplayType::NORMAL) + dummy.Set(*wxBLACK); //accessibility: always set both foreground AND background colors! //draw text if (static_cast<ColumnTypeRim>(colType) == ColumnTypeRim::SIZE && refGrid().GetLayoutDirection() != wxLayout_RightToLeft) { //have file size right-justified (but don't change for RTL languages) rectTmp.width -= GAP_SIZE; - drawCellText(dc, rectTmp, getValue(row, colType), isActive, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL); + drawCellText(dc, rectTmp, getValue(row, colType), wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL); } else { rectTmp.x += GAP_SIZE; rectTmp.width -= GAP_SIZE; - drawCellText(dc, rectTmp, getValue(row, colType), isActive, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); + drawCellText(dc, rectTmp, getValue(row, colType), wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); } } @@ -618,8 +619,7 @@ private: if (colType == static_cast<ColumnType>(sortInfo->type_) && (side == LEFT_SIDE) == sortInfo->onLeft_) { const wxBitmap& marker = getResourceImage(sortInfo->ascending_ ? L"sortAscending" : L"sortDescending"); - wxPoint markerBegin = rectInside.GetTopLeft() + wxPoint((rectInside.width - marker.GetWidth()) / 2, 0); - dc.DrawBitmap(marker, markerBegin, true); //respect 2-pixel gap + drawBitmapRtlNoMirror(dc, marker, rectInside, wxALIGN_CENTER_HORIZONTAL); } } } @@ -715,7 +715,7 @@ private: std::vector<char> failedLoads; //effectively a vector<bool> of size "number of rows" - Opt<wxBitmap> buffer; //avoid costs of recreating this temporal variable + Opt<wxBitmap> renderBuf; //avoid costs of recreating this temporal variable }; @@ -937,9 +937,9 @@ private: const bool drawMouseHover = static_cast<HoverAreaCenter>(rowHover) == HoverAreaCenter::CHECK_BOX; if (fsObj->isActive()) - drawBitmapRtlMirror(dc, getResourceImage(drawMouseHover ? L"checkbox_true_hover" : L"checkbox_true"), rect, wxALIGN_CENTER, buffer); + drawBitmapRtlMirror(dc, getResourceImage(drawMouseHover ? L"checkbox_true_hover" : L"checkbox_true"), rect, wxALIGN_CENTER, renderBuf); else //default - drawBitmapRtlMirror(dc, getResourceImage(drawMouseHover ? L"checkbox_false_hover" : L"checkbox_false"), rect, wxALIGN_CENTER, buffer); + drawBitmapRtlMirror(dc, getResourceImage(drawMouseHover ? L"checkbox_false_hover" : L"checkbox_false"), rect, wxALIGN_CENTER, renderBuf); } break; @@ -957,14 +957,14 @@ private: //wxWidgets screws up again and has wxALIGN_RIGHT off by one pixel! -> use wxALIGN_LEFT instead const wxRect rectNotch(rectTmp.x + rectTmp.width - notch.GetWidth(), rectTmp.y, notch.GetWidth(), rectTmp.height); - drawBitmapRtlMirror(dc, notch, rectNotch, wxALIGN_LEFT, buffer); + drawBitmapRtlMirror(dc, notch, rectNotch, wxALIGN_LEFT, renderBuf); rectTmp.width -= notch.GetWidth(); } if (!highlightSyncAction_) - drawBitmapRtlMirror(dc, getCmpResultImage(fsObj->getCategory()), rectTmp, wxALIGN_CENTER, buffer); + drawBitmapRtlMirror(dc, getCmpResultImage(fsObj->getCategory()), rectTmp, wxALIGN_CENTER, renderBuf); else if (fsObj->getCategory() != FILE_EQUAL) //don't show = in both middle columns - drawBitmapRtlMirror(dc, greyScale(getCmpResultImage(fsObj->getCategory())), rectTmp, wxALIGN_CENTER, buffer); + drawBitmapRtlMirror(dc, greyScale(getCmpResultImage(fsObj->getCategory())), rectTmp, wxALIGN_CENTER, renderBuf); } break; @@ -979,19 +979,19 @@ private: switch (rowHoverCenter) { case HoverAreaCenter::DIR_LEFT: - drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SyncDirection::LEFT)), rect, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, buffer); + drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SyncDirection::LEFT)), rect, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, renderBuf); break; case HoverAreaCenter::DIR_NONE: - drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SyncDirection::NONE)), rect, wxALIGN_CENTER, buffer); + drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SyncDirection::NONE)), rect, wxALIGN_CENTER, renderBuf); break; case HoverAreaCenter::DIR_RIGHT: - drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SyncDirection::RIGHT)), rect, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL, buffer); + drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SyncDirection::RIGHT)), rect, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL, renderBuf); break; case HoverAreaCenter::CHECK_BOX: if (highlightSyncAction_) - drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->getSyncOperation()), rect, wxALIGN_CENTER, buffer); + drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->getSyncOperation()), rect, wxALIGN_CENTER, renderBuf); else if (fsObj->getSyncOperation() != SO_EQUAL) //don't show = in both middle columns - drawBitmapRtlMirror(dc, greyScale(getSyncOpImage(fsObj->getSyncOperation())), rect, wxALIGN_CENTER, buffer); + drawBitmapRtlMirror(dc, greyScale(getSyncOpImage(fsObj->getSyncOperation())), rect, wxALIGN_CENTER, renderBuf); break; } } @@ -1059,7 +1059,7 @@ private: drawColumnLabelBackground(dc, rectInside, highlighted); const wxBitmap& cmpIcon = getResourceImage(L"compare_small"); - drawBitmapRtlNoMirror(dc, highlightSyncAction_ ? greyScale(cmpIcon) : cmpIcon, rectInside, wxALIGN_CENTER, buffer); + drawBitmapRtlNoMirror(dc, highlightSyncAction_ ? greyScale(cmpIcon) : cmpIcon, rectInside, wxALIGN_CENTER); } break; @@ -1069,7 +1069,7 @@ private: drawColumnLabelBackground(dc, rectInside, highlighted); const wxBitmap& syncIcon = getResourceImage(L"sync_small"); - drawBitmapRtlNoMirror(dc, highlightSyncAction_ ? syncIcon : greyScale(syncIcon), rectInside, wxALIGN_CENTER, buffer); + drawBitmapRtlNoMirror(dc, highlightSyncAction_ ? syncIcon : greyScale(syncIcon), rectInside, wxALIGN_CENTER); } break; } @@ -1234,7 +1234,7 @@ private: bool highlightSyncAction_ = false; bool selectionInProgress = false; - Opt<wxBitmap> buffer; //avoid costs of recreating this temporal variable + Opt<wxBitmap> renderBuf; //avoid costs of recreating this temporal variable Tooltip toolTip; wxImage notch; }; diff --git a/FreeFileSync/Source/ui/folder_selector.cpp b/FreeFileSync/Source/ui/folder_selector.cpp index f9123cf7..63a5f4f0 100644 --- a/FreeFileSync/Source/ui/folder_selector.cpp +++ b/FreeFileSync/Source/ui/folder_selector.cpp @@ -60,10 +60,12 @@ bool acceptShellItemPaths(const std::vector<Zstring>& shellItemPaths) //accept files or folders from: //- file system paths //- MTP paths - - if (shellItemPaths.empty()) return false; - return acceptsItemPathPhraseNative(shellItemPaths[0]) || // - acceptsItemPathPhraseMtp (shellItemPaths[0]); //noexcept + return std::any_of(shellItemPaths.begin(), shellItemPaths.end(), + [&](const Zstring& shellItemPath) + { + return acceptsItemPathPhraseNative(shellItemPath) || // + acceptsItemPathPhraseMtp (shellItemPath); //noexcept + }); } diff --git a/FreeFileSync/Source/ui/gui_generated.cpp b/FreeFileSync/Source/ui/gui_generated.cpp index ebd9d706..c217a408 100644 --- a/FreeFileSync/Source/ui/gui_generated.cpp +++ b/FreeFileSync/Source/ui/gui_generated.cpp @@ -42,6 +42,7 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const m_menuFile->AppendSeparator(); + wxMenuItem* m_menuItem4; m_menuItem4 = new wxMenuItem( m_menuFile, wxID_EXIT, wxString( _("E&xit") ) , wxEmptyString, wxITEM_NORMAL ); m_menuFile->Append( m_menuItem4 ); @@ -1078,18 +1079,22 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w wxBoxSizer* bSizer275; bSizer275 = new wxBoxSizer( wxVERTICAL ); - bSizerLocalCompSettings = new wxBoxSizer( wxVERTICAL ); + bSizerHeaderCompSettings = new wxBoxSizer( wxVERTICAL ); + + m_staticTextMainCompSettings = new wxStaticText( m_panelCompSettingsHolder, wxID_ANY, _("Main settings:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticTextMainCompSettings->Wrap( -1 ); + bSizerHeaderCompSettings->Add( m_staticTextMainCompSettings, 0, wxALL, 10 ); m_checkBoxUseLocalCmpOptions = new wxCheckBox( m_panelCompSettingsHolder, wxID_ANY, _("Use local settings:"), wxDefaultPosition, wxDefaultSize, 0 ); m_checkBoxUseLocalCmpOptions->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); - bSizerLocalCompSettings->Add( m_checkBoxUseLocalCmpOptions, 0, wxALL|wxEXPAND, 10 ); + bSizerHeaderCompSettings->Add( m_checkBoxUseLocalCmpOptions, 0, wxALL|wxEXPAND, 10 ); - m_staticline59 = new wxStaticLine( m_panelCompSettingsHolder, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); - bSizerLocalCompSettings->Add( m_staticline59, 0, wxEXPAND, 5 ); + m_staticlineCompHeader = new wxStaticLine( m_panelCompSettingsHolder, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + bSizerHeaderCompSettings->Add( m_staticlineCompHeader, 0, wxEXPAND, 5 ); - bSizer275->Add( bSizerLocalCompSettings, 0, wxEXPAND, 5 ); + bSizer275->Add( bSizerHeaderCompSettings, 0, wxEXPAND, 5 ); m_panelComparisonSettings = new wxPanel( m_panelCompSettingsHolder, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); m_panelComparisonSettings->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); @@ -1248,17 +1253,21 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w wxBoxSizer* bSizer278; bSizer278 = new wxBoxSizer( wxVERTICAL ); - bSizerLocalFilterSettings = new wxBoxSizer( wxVERTICAL ); + bSizerHeaderFilterSettings = new wxBoxSizer( wxVERTICAL ); + + m_staticTextMainFilterSettings = new wxStaticText( m_panelFilterSettingsHolder, wxID_ANY, _("Main settings:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticTextMainFilterSettings->Wrap( -1 ); + bSizerHeaderFilterSettings->Add( m_staticTextMainFilterSettings, 0, wxALL, 10 ); - m_staticText144 = new wxStaticText( m_panelFilterSettingsHolder, wxID_ANY, _("Local settings:"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticText144->Wrap( -1 ); - bSizerLocalFilterSettings->Add( m_staticText144, 0, wxALL, 10 ); + m_staticTextLocalFilterSettings = new wxStaticText( m_panelFilterSettingsHolder, wxID_ANY, _("Local settings:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticTextLocalFilterSettings->Wrap( -1 ); + bSizerHeaderFilterSettings->Add( m_staticTextLocalFilterSettings, 0, wxALL, 10 ); - m_staticline61 = new wxStaticLine( m_panelFilterSettingsHolder, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); - bSizerLocalFilterSettings->Add( m_staticline61, 0, wxEXPAND, 5 ); + m_staticlineFilterHeader = new wxStaticLine( m_panelFilterSettingsHolder, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + bSizerHeaderFilterSettings->Add( m_staticlineFilterHeader, 0, wxEXPAND, 5 ); - bSizer278->Add( bSizerLocalFilterSettings, 0, wxEXPAND, 5 ); + bSizer278->Add( bSizerHeaderFilterSettings, 0, wxEXPAND, 5 ); m_panelFilterSettings = new wxPanel( m_panelFilterSettingsHolder, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); m_panelFilterSettings->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); @@ -1472,16 +1481,20 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w wxBoxSizer* bSizer276; bSizer276 = new wxBoxSizer( wxVERTICAL ); - bSizerLocalSyncSettings = new wxBoxSizer( wxVERTICAL ); + bSizerHeaderSyncSettings = new wxBoxSizer( wxVERTICAL ); + + m_staticTextMainSyncSettings = new wxStaticText( m_panelSyncSettingsHolder, wxID_ANY, _("Main settings:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticTextMainSyncSettings->Wrap( -1 ); + bSizerHeaderSyncSettings->Add( m_staticTextMainSyncSettings, 0, wxALL, 10 ); m_checkBoxUseLocalSyncOptions = new wxCheckBox( m_panelSyncSettingsHolder, wxID_ANY, _("Use local settings:"), wxDefaultPosition, wxDefaultSize, 0 ); - bSizerLocalSyncSettings->Add( m_checkBoxUseLocalSyncOptions, 0, wxALL|wxEXPAND, 10 ); + bSizerHeaderSyncSettings->Add( m_checkBoxUseLocalSyncOptions, 0, wxALL|wxEXPAND, 10 ); - m_staticline60 = new wxStaticLine( m_panelSyncSettingsHolder, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); - bSizerLocalSyncSettings->Add( m_staticline60, 0, wxEXPAND, 5 ); + m_staticlineSyncHeader = new wxStaticLine( m_panelSyncSettingsHolder, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + bSizerHeaderSyncSettings->Add( m_staticlineSyncHeader, 0, wxEXPAND, 5 ); - bSizer276->Add( bSizerLocalSyncSettings, 0, wxEXPAND, 5 ); + bSizer276->Add( bSizerHeaderSyncSettings, 0, wxEXPAND, 5 ); m_panelSyncSettings = new wxPanel( m_panelSyncSettingsHolder, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); m_panelSyncSettings->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); @@ -1523,13 +1536,7 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer236->Add( m_toggleBtnCustom, 0, wxEXPAND, 5 ); - bSizer235->Add( bSizer236, 0, wxRIGHT|wxLEFT, 5 ); - - m_checkBoxDetectMove = new wxCheckBox( m_panelSyncSettings, wxID_ANY, _("Detect moved files"), wxDefaultPosition, wxDefaultSize, 0 ); - m_checkBoxDetectMove->SetValue(true); - m_checkBoxDetectMove->SetToolTip( _("- Not supported by all file systems\n- Requires and creates database files\n- Detection not available for first sync") ); - - bSizer235->Add( m_checkBoxDetectMove, 0, wxEXPAND|wxALL, 5 ); + bSizer235->Add( bSizer236, 0, wxBOTTOM|wxRIGHT|wxLEFT, 5 ); bSizer237->Add( bSizer235, 0, wxALL, 5 ); @@ -1540,11 +1547,8 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w wxBoxSizer* bSizer238; bSizer238 = new wxBoxSizer( wxVERTICAL ); - m_textCtrlSyncVarDescription = new wxTextCtrl( m_panelSyncSettings, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_READONLY|wxNO_BORDER ); - bSizer238->Add( m_textCtrlSyncVarDescription, 1, wxEXPAND|wxLEFT, 5 ); - m_staticline43 = new wxStaticLine( m_panelSyncSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); - bSizer238->Add( m_staticline43, 0, wxEXPAND, 5 ); + bSizer238->Add( 0, 0, 1, wxEXPAND, 5 ); bSizerSyncConfig = new wxBoxSizer( wxHORIZONTAL ); @@ -1653,7 +1657,34 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer238->Add( bSizerSyncConfig, 0, wxALL, 10 ); - bSizer237->Add( bSizer238, 1, wxEXPAND, 5 ); + bSizer238->Add( 0, 0, 1, wxEXPAND, 5 ); + + m_staticline431 = new wxStaticLine( m_panelSyncSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + bSizer238->Add( m_staticline431, 0, wxEXPAND, 5 ); + + wxBoxSizer* bSizer201; + bSizer201 = new wxBoxSizer( wxHORIZONTAL ); + + m_checkBoxDetectMove = new wxCheckBox( m_panelSyncSettings, wxID_ANY, _("Detect moved files"), wxDefaultPosition, wxDefaultSize, 0 ); + m_checkBoxDetectMove->SetValue(true); + m_checkBoxDetectMove->SetToolTip( _("- Not supported by all file systems\n- Requires and creates database files\n- Detection not available for first sync") ); + + bSizer201->Add( m_checkBoxDetectMove, 0, wxALL|wxEXPAND, 5 ); + + m_hyperlink242 = new wxHyperlinkCtrl( m_panelSyncSettings, wxID_ANY, _("More information"), wxEmptyString, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + bSizer201->Add( m_hyperlink242, 0, wxTOP|wxBOTTOM|wxRIGHT, 5 ); + + + bSizer238->Add( bSizer201, 0, wxALL, 5 ); + + + bSizer237->Add( bSizer238, 0, wxEXPAND, 5 ); + + m_staticline531 = new wxStaticLine( m_panelSyncSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL ); + bSizer237->Add( m_staticline531, 0, wxEXPAND, 5 ); + + m_textCtrlSyncVarDescription = new wxTextCtrl( m_panelSyncSettings, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_READONLY|wxNO_BORDER ); + bSizer237->Add( m_textCtrlSyncVarDescription, 1, wxLEFT|wxEXPAND, 5 ); bSizer232->Add( bSizer237, 0, wxEXPAND, 5 ); @@ -1661,38 +1692,42 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w m_staticline54 = new wxStaticLine( m_panelSyncSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); bSizer232->Add( m_staticline54, 0, wxEXPAND, 5 ); - wxBoxSizer* bSizer184; - bSizer184 = new wxBoxSizer( wxVERTICAL ); + bSizerDelHandling = new wxBoxSizer( wxHORIZONTAL ); - wxBoxSizer* bSizer180; - bSizer180 = new wxBoxSizer( wxHORIZONTAL ); + wxBoxSizer* bSizer202; + bSizer202 = new wxBoxSizer( wxVERTICAL ); m_staticText87 = new wxStaticText( m_panelSyncSettings, wxID_ANY, _("Delete files:"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText87->Wrap( -1 ); - bSizer180->Add( m_staticText87, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxRIGHT, 5 ); + bSizer202->Add( m_staticText87, 0, wxALL, 5 ); - m_radioBtnPermanent = new wxRadioButton( m_panelSyncSettings, wxID_ANY, _("&Permanent"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP ); - m_radioBtnPermanent->SetToolTip( _("Delete or overwrite files permanently") ); + m_bpButtonDeletionType = new wxBitmapButton( m_panelSyncSettings, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 46,46 ), wxBU_AUTODRAW ); + bSizer202->Add( m_bpButtonDeletionType, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); - bSizer180->Add( m_radioBtnPermanent, 0, wxTOP|wxBOTTOM|wxLEFT, 5 ); - m_radioBtnRecycler = new wxRadioButton( m_panelSyncSettings, wxID_ANY, _("&Recycle bin"), wxDefaultPosition, wxDefaultSize, 0 ); + bSizerDelHandling->Add( bSizer202, 0, 0, 5 ); + + wxBoxSizer* bSizer2011; + bSizer2011 = new wxBoxSizer( wxVERTICAL ); + + m_radioBtnRecycler = new wxRadioButton( m_panelSyncSettings, wxID_ANY, _("&Recycle bin"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP ); + m_radioBtnRecycler->SetValue( true ); m_radioBtnRecycler->SetToolTip( _("Back up deleted and overwritten files in the recycle bin") ); - bSizer180->Add( m_radioBtnRecycler, 0, wxTOP|wxBOTTOM|wxLEFT, 5 ); + bSizer2011->Add( m_radioBtnRecycler, 0, wxEXPAND|wxALL, 5 ); - m_radioBtnVersioning = new wxRadioButton( m_panelSyncSettings, wxID_ANY, _("&Versioning"), wxDefaultPosition, wxDefaultSize, 0 ); - m_radioBtnVersioning->SetToolTip( _("Move files to a user-defined folder") ); + m_radioBtnPermanent = new wxRadioButton( m_panelSyncSettings, wxID_ANY, _("&Permanent"), wxDefaultPosition, wxDefaultSize, 0 ); + m_radioBtnPermanent->SetToolTip( _("Delete or overwrite files permanently") ); - bSizer180->Add( m_radioBtnVersioning, 0, wxALL, 5 ); + bSizer2011->Add( m_radioBtnPermanent, 0, wxBOTTOM|wxRIGHT|wxLEFT|wxEXPAND, 5 ); + m_radioBtnVersioning = new wxRadioButton( m_panelSyncSettings, wxID_ANY, _("&Versioning"), wxDefaultPosition, wxDefaultSize, 0 ); + m_radioBtnVersioning->SetToolTip( _("Move files to a user-defined folder") ); - bSizer184->Add( bSizer180, 0, 0, 5 ); + bSizer2011->Add( m_radioBtnVersioning, 0, wxBOTTOM|wxRIGHT|wxLEFT|wxEXPAND, 5 ); - bSizerVersioning = new wxBoxSizer( wxHORIZONTAL ); - m_bpButtonDeletionType = new wxBitmapButton( m_panelSyncSettings, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 46,46 ), wxBU_AUTODRAW ); - bSizerVersioning->Add( m_bpButtonDeletionType, 0, wxALIGN_CENTER_VERTICAL, 5 ); + bSizerDelHandling->Add( bSizer2011, 0, wxALIGN_CENTER_VERTICAL, 5 ); m_panelVersioning = new wxPanel( m_panelSyncSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); m_panelVersioning->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); @@ -1717,13 +1752,25 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer156->Add( m_bpButtonSelectAltFolder, 0, wxEXPAND, 5 ); - bSizer191->Add( bSizer156, 0, wxEXPAND|wxBOTTOM, 5 ); + bSizer191->Add( bSizer156, 0, wxEXPAND|wxALL, 5 ); - bSizer192 = new wxBoxSizer( wxHORIZONTAL ); + wxBoxSizer* bSizer198; + bSizer198 = new wxBoxSizer( wxHORIZONTAL ); m_staticText93 = new wxStaticText( m_panelVersioning, wxID_ANY, _("Naming convention:"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText93->Wrap( -1 ); - bSizer192->Add( m_staticText93, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + bSizer198->Add( m_staticText93, 0, wxRIGHT|wxALIGN_CENTER_VERTICAL, 5 ); + + + bSizer198->Add( 0, 0, 1, wxEXPAND, 5 ); + + m_hyperlink17 = new wxHyperlinkCtrl( m_panelVersioning, wxID_ANY, _("Show examples"), wxEmptyString, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + bSizer198->Add( m_hyperlink17, 0, 0, 5 ); + + + bSizer191->Add( bSizer198, 0, wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); + + bSizer192 = new wxBoxSizer( wxHORIZONTAL ); wxArrayString m_choiceVersioningStyleChoices; m_choiceVersioningStyle = new wxChoice( m_panelVersioning, wxID_ANY, wxDefaultPosition, wxDefaultSize, m_choiceVersioningStyleChoices, 0 ); @@ -1750,62 +1797,45 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer192->Add( m_staticTextNamingCvtPart3, 0, wxALIGN_CENTER_VERTICAL, 5 ); - bSizer192->Add( 0, 0, 1, wxEXPAND, 5 ); - - m_hyperlink17 = new wxHyperlinkCtrl( m_panelVersioning, wxID_ANY, _("Show examples"), wxEmptyString, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); - bSizer192->Add( m_hyperlink17, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 ); - - - bSizer191->Add( bSizer192, 0, wxEXPAND, 5 ); + bSizer191->Add( bSizer192, 0, wxBOTTOM|wxRIGHT|wxLEFT, 5 ); m_panelVersioning->SetSizer( bSizer191 ); m_panelVersioning->Layout(); bSizer191->Fit( m_panelVersioning ); - bSizerVersioning->Add( m_panelVersioning, 1, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 ); - - - bSizer184->Add( bSizerVersioning, 0, wxTOP|wxEXPAND, 5 ); + bSizerDelHandling->Add( m_panelVersioning, 1, 0, 5 ); - bSizer232->Add( bSizer184, 0, wxALL|wxEXPAND, 10 ); - - bSizerMiscConfig = new wxBoxSizer( wxVERTICAL ); + bSizer232->Add( bSizerDelHandling, 0, wxALL|wxEXPAND, 5 ); m_staticline582 = new wxStaticLine( m_panelSyncSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); - bSizerMiscConfig->Add( m_staticline582, 0, wxEXPAND, 5 ); + bSizer232->Add( m_staticline582, 0, wxEXPAND, 5 ); - wxBoxSizer* bSizer1732; - bSizer1732 = new wxBoxSizer( wxHORIZONTAL ); - - wxBoxSizer* bSizer174; - bSizer174 = new wxBoxSizer( wxVERTICAL ); + bSizerMiscConfig = new wxBoxSizer( wxHORIZONTAL ); m_staticText88 = new wxStaticText( m_panelSyncSettings, wxID_ANY, _("Handle errors:"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText88->Wrap( -1 ); - bSizer174->Add( m_staticText88, 0, wxBOTTOM, 5 ); + bSizerMiscConfig->Add( m_staticText88, 0, wxTOP|wxBOTTOM|wxLEFT, 10 ); wxBoxSizer* bSizer175; - bSizer175 = new wxBoxSizer( wxHORIZONTAL ); - - m_radioBtnIgnoreErrors = new wxRadioButton( m_panelSyncSettings, wxID_ANY, _("&Ignore"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP ); - m_radioBtnIgnoreErrors->SetToolTip( _("Hide all error and warning messages") ); - - bSizer175->Add( m_radioBtnIgnoreErrors, 0, wxTOP|wxBOTTOM|wxLEFT, 5 ); + bSizer175 = new wxBoxSizer( wxVERTICAL ); - m_radioBtnPopupOnErrors = new wxRadioButton( m_panelSyncSettings, wxID_ANY, _("&Pop-up"), wxDefaultPosition, wxDefaultSize, 0 ); + m_radioBtnPopupOnErrors = new wxRadioButton( m_panelSyncSettings, wxID_ANY, _("&Pop-up"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP ); + m_radioBtnPopupOnErrors->SetValue( true ); m_radioBtnPopupOnErrors->SetToolTip( _("Show pop-up on errors or warnings") ); - bSizer175->Add( m_radioBtnPopupOnErrors, 0, wxALL, 5 ); + bSizer175->Add( m_radioBtnPopupOnErrors, 0, wxEXPAND|wxALL, 5 ); + m_radioBtnIgnoreErrors = new wxRadioButton( m_panelSyncSettings, wxID_ANY, _("&Ignore"), wxDefaultPosition, wxDefaultSize, 0 ); + m_radioBtnIgnoreErrors->SetToolTip( _("Hide all error and warning messages") ); - bSizer174->Add( bSizer175, 0, 0, 5 ); + bSizer175->Add( m_radioBtnIgnoreErrors, 0, wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); - bSizer1732->Add( bSizer174, 0, wxALL, 10 ); + bSizerMiscConfig->Add( bSizer175, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); m_staticline57 = new wxStaticLine( m_panelSyncSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL ); - bSizer1732->Add( m_staticline57, 0, wxEXPAND, 5 ); + bSizerMiscConfig->Add( m_staticline57, 0, wxEXPAND, 5 ); bSizerOnCompletion = new wxBoxSizer( wxVERTICAL ); @@ -1817,10 +1847,7 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizerOnCompletion->Add( m_comboBoxOnCompletion, 0, wxEXPAND, 5 ); - bSizer1732->Add( bSizerOnCompletion, 1, wxALL, 10 ); - - - bSizerMiscConfig->Add( bSizer1732, 1, wxEXPAND, 5 ); + bSizerMiscConfig->Add( bSizerOnCompletion, 1, wxALL, 10 ); bSizer232->Add( bSizerMiscConfig, 1, wxEXPAND, 5 ); @@ -1894,21 +1921,22 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w m_toggleBtnUpdate->Connect( wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnSyncUpdate ), NULL, this ); m_toggleBtnCustom->Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( ConfigDlgGenerated::OnSyncCustomDouble ), NULL, this ); m_toggleBtnCustom->Connect( wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnSyncCustom ), NULL, this ); - m_checkBoxDetectMove->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnToggleDetectMovedFiles ), NULL, this ); m_bpButtonLeftOnly->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnExLeftSideOnly ), NULL, this ); m_bpButtonLeftNewer->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnLeftNewer ), NULL, this ); m_bpButtonDifferent->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnDifferent ), NULL, this ); m_bpButtonConflict->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnConflict ), NULL, this ); m_bpButtonRightNewer->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnRightNewer ), NULL, this ); m_bpButtonRightOnly->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnExRightSideOnly ), NULL, this ); - m_radioBtnPermanent->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( ConfigDlgGenerated::OnDeletionPermanent ), NULL, this ); + m_checkBoxDetectMove->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnToggleDetectMovedFiles ), NULL, this ); + m_hyperlink242->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( ConfigDlgGenerated::OnHelpDetectMovedFiles ), NULL, this ); + m_bpButtonDeletionType->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnToggleDeletionType ), NULL, this ); m_radioBtnRecycler->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( ConfigDlgGenerated::OnDeletionRecycler ), NULL, this ); + m_radioBtnPermanent->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( ConfigDlgGenerated::OnDeletionPermanent ), NULL, this ); m_radioBtnVersioning->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( ConfigDlgGenerated::OnDeletionVersioning ), NULL, this ); - m_bpButtonDeletionType->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnToggleDeletionType ), NULL, this ); - m_choiceVersioningStyle->Connect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( ConfigDlgGenerated::OnChangeSyncOption ), NULL, this ); m_hyperlink17->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( ConfigDlgGenerated::OnHelpVersioning ), NULL, this ); - m_radioBtnIgnoreErrors->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( ConfigDlgGenerated::OnErrorIgnore ), NULL, this ); + m_choiceVersioningStyle->Connect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( ConfigDlgGenerated::OnChangeSyncOption ), NULL, this ); m_radioBtnPopupOnErrors->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( ConfigDlgGenerated::OnErrorPopup ), NULL, this ); + m_radioBtnIgnoreErrors->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( ConfigDlgGenerated::OnErrorIgnore ), NULL, this ); m_buttonOkay->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnOkay ), NULL, this ); m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnCancel ), NULL, this ); } @@ -3006,36 +3034,31 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS wxBoxSizer* bSizer180; bSizer180 = new wxBoxSizer( wxHORIZONTAL ); - wxBoxSizer* bSizer171; - bSizer171 = new wxBoxSizer( wxVERTICAL ); - m_staticText82 = new wxStaticText( m_panel35, wxID_ANY, _("Handle errors:"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText82->Wrap( -1 ); - bSizer171->Add( m_staticText82, 0, wxALL, 5 ); + bSizer180->Add( m_staticText82, 0, wxTOP|wxBOTTOM|wxLEFT, 10 ); wxBoxSizer* bSizer169; - bSizer169 = new wxBoxSizer( wxHORIZONTAL ); - - m_radioBtnIgnoreErrors = new wxRadioButton( m_panel35, wxID_ANY, _("&Ignore"), wxDefaultPosition, wxDefaultSize, 0 ); - m_radioBtnIgnoreErrors->SetToolTip( _("Hide all error and warning messages") ); - - bSizer169->Add( m_radioBtnIgnoreErrors, 0, wxTOP|wxBOTTOM|wxLEFT|wxALIGN_CENTER_VERTICAL, 5 ); + bSizer169 = new wxBoxSizer( wxVERTICAL ); m_radioBtnPopupOnErrors = new wxRadioButton( m_panel35, wxID_ANY, _("&Pop-up"), wxDefaultPosition, wxDefaultSize, 0 ); + m_radioBtnPopupOnErrors->SetValue( true ); m_radioBtnPopupOnErrors->SetToolTip( _("Show pop-up on errors or warnings") ); - bSizer169->Add( m_radioBtnPopupOnErrors, 0, wxTOP|wxBOTTOM|wxLEFT|wxALIGN_CENTER_VERTICAL, 5 ); + bSizer169->Add( m_radioBtnPopupOnErrors, 0, wxEXPAND|wxALL, 5 ); - m_radioBtnStopOnError = new wxRadioButton( m_panel35, wxID_ANY, _("&Stop"), wxDefaultPosition, wxDefaultSize, 0 ); - m_radioBtnStopOnError->SetToolTip( _("Stop synchronization at first error") ); + m_radioBtnIgnoreErrors = new wxRadioButton( m_panel35, wxID_ANY, _("&Ignore"), wxDefaultPosition, wxDefaultSize, 0 ); + m_radioBtnIgnoreErrors->SetToolTip( _("Hide all error and warning messages") ); - bSizer169->Add( m_radioBtnStopOnError, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); + bSizer169->Add( m_radioBtnIgnoreErrors, 0, wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); + m_radioBtnStopOnError = new wxRadioButton( m_panel35, wxID_ANY, _("&Stop"), wxDefaultPosition, wxDefaultSize, 0 ); + m_radioBtnStopOnError->SetToolTip( _("Stop synchronization at first error") ); - bSizer171->Add( bSizer169, 0, wxBOTTOM|wxRIGHT|wxLEFT, 5 ); + bSizer169->Add( m_radioBtnStopOnError, 0, wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); - bSizer180->Add( bSizer171, 0, wxALL, 5 ); + bSizer180->Add( bSizer169, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); m_staticline26 = new wxStaticLine( m_panel35, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL ); bSizer180->Add( m_staticline26, 0, wxEXPAND, 5 ); @@ -3043,21 +3066,15 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS wxBoxSizer* bSizer170; bSizer170 = new wxBoxSizer( wxVERTICAL ); - m_checkBoxRunMinimized = new wxCheckBox( m_panel35, wxID_ANY, _("Run minimized"), wxDefaultPosition, wxDefaultSize, 0 ); - bSizer170->Add( m_checkBoxRunMinimized, 0, wxEXPAND|wxALL, 5 ); - - wxBoxSizer* bSizer179; - bSizer179 = new wxBoxSizer( wxHORIZONTAL ); - m_staticText81 = new wxStaticText( m_panel35, wxID_ANY, _("On completion:"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText81->Wrap( -1 ); - bSizer179->Add( m_staticText81, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + bSizer170->Add( m_staticText81, 0, wxALL, 5 ); m_comboBoxOnCompletion = new OnCompletionBox( m_panel35, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, NULL, 0 ); - bSizer179->Add( m_comboBoxOnCompletion, 1, wxALIGN_CENTER_VERTICAL, 5 ); - + bSizer170->Add( m_comboBoxOnCompletion, 0, wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); - bSizer170->Add( bSizer179, 0, wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); + m_checkBoxRunMinimized = new wxCheckBox( m_panel35, wxID_ANY, _("Run minimized"), wxDefaultPosition, wxDefaultSize, 0 ); + bSizer170->Add( m_checkBoxRunMinimized, 0, wxEXPAND|wxALL, 5 ); bSizer180->Add( bSizer170, 1, wxALL, 5 ); @@ -3147,8 +3164,8 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS // Connect Events this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( BatchDlgGenerated::OnClose ) ); - m_radioBtnIgnoreErrors->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( BatchDlgGenerated::OnErrorIgnore ), NULL, this ); m_radioBtnPopupOnErrors->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( BatchDlgGenerated::OnErrorPopup ), NULL, this ); + m_radioBtnIgnoreErrors->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( BatchDlgGenerated::OnErrorIgnore ), NULL, this ); m_radioBtnStopOnError->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( BatchDlgGenerated::OnErrorStop ), NULL, this ); m_checkBoxGenerateLogfile->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( BatchDlgGenerated::OnToggleGenerateLogfile ), NULL, this ); m_checkBoxLogfilesLimit->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( BatchDlgGenerated::OnToggleLogfilesLimit ), NULL, this ); diff --git a/FreeFileSync/Source/ui/gui_generated.h b/FreeFileSync/Source/ui/gui_generated.h index 105a989f..842b7a10 100644 --- a/FreeFileSync/Source/ui/gui_generated.h +++ b/FreeFileSync/Source/ui/gui_generated.h @@ -74,7 +74,6 @@ protected: wxMenuItem* m_menuItemSave; wxMenuItem* m_menuItemSaveAs; wxMenuItem* m_menuItemSaveAsBatch; - wxMenuItem* m_menuItem4; wxMenu* m_menu4; wxMenuItem* m_menuItemCompare; wxMenuItem* m_menuItemCompSettings; @@ -271,9 +270,10 @@ protected: wxListBox* m_listBoxFolderPair; wxNotebook* m_notebook; wxPanel* m_panelCompSettingsHolder; - wxBoxSizer* bSizerLocalCompSettings; + wxBoxSizer* bSizerHeaderCompSettings; + wxStaticText* m_staticTextMainCompSettings; wxCheckBox* m_checkBoxUseLocalCmpOptions; - wxStaticLine* m_staticline59; + wxStaticLine* m_staticlineCompHeader; wxPanel* m_panelComparisonSettings; wxStaticText* m_staticText91; wxStaticBitmap* m_bitmapByTimeSize; @@ -298,9 +298,10 @@ protected: wxStaticLine* m_staticline441; wxStaticLine* m_staticline331; wxPanel* m_panelFilterSettingsHolder; - wxBoxSizer* bSizerLocalFilterSettings; - wxStaticText* m_staticText144; - wxStaticLine* m_staticline61; + wxBoxSizer* bSizerHeaderFilterSettings; + wxStaticText* m_staticTextMainFilterSettings; + wxStaticText* m_staticTextLocalFilterSettings; + wxStaticLine* m_staticlineFilterHeader; wxPanel* m_panelFilterSettings; wxStaticBitmap* m_bitmapInclude; wxStaticText* m_staticText78; @@ -329,19 +330,17 @@ protected: wxStaticLine* m_staticline46; wxButton* m_buttonClear; wxPanel* m_panelSyncSettingsHolder; - wxBoxSizer* bSizerLocalSyncSettings; + wxBoxSizer* bSizerHeaderSyncSettings; + wxStaticText* m_staticTextMainSyncSettings; wxCheckBox* m_checkBoxUseLocalSyncOptions; - wxStaticLine* m_staticline60; + wxStaticLine* m_staticlineSyncHeader; wxPanel* m_panelSyncSettings; wxStaticText* m_staticText86; wxToggleButton* m_toggleBtnTwoWay; wxToggleButton* m_toggleBtnMirror; wxToggleButton* m_toggleBtnUpdate; wxToggleButton* m_toggleBtnCustom; - wxCheckBox* m_checkBoxDetectMove; wxStaticLine* m_staticline53; - wxTextCtrl* m_textCtrlSyncVarDescription; - wxStaticLine* m_staticline43; wxBoxSizer* bSizerSyncConfig; wxStaticText* m_staticText119; wxStaticText* m_staticText120; @@ -359,28 +358,33 @@ protected: wxBitmapButton* m_bpButtonRightNewer; wxBitmapButton* m_bpButtonRightOnly; wxStaticBitmap* m_bitmapDatabase; + wxStaticLine* m_staticline431; + wxCheckBox* m_checkBoxDetectMove; + wxHyperlinkCtrl* m_hyperlink242; + wxStaticLine* m_staticline531; + wxTextCtrl* m_textCtrlSyncVarDescription; wxStaticLine* m_staticline54; + wxBoxSizer* bSizerDelHandling; wxStaticText* m_staticText87; - wxRadioButton* m_radioBtnPermanent; + wxBitmapButton* m_bpButtonDeletionType; wxRadioButton* m_radioBtnRecycler; + wxRadioButton* m_radioBtnPermanent; wxRadioButton* m_radioBtnVersioning; - wxBoxSizer* bSizerVersioning; - wxBitmapButton* m_bpButtonDeletionType; wxPanel* m_panelVersioning; FolderHistoryBox* m_versioningFolderPath; wxButton* m_buttonSelectVersioningFolder; - wxBoxSizer* bSizer192; wxStaticText* m_staticText93; + wxHyperlinkCtrl* m_hyperlink17; + wxBoxSizer* bSizer192; wxChoice* m_choiceVersioningStyle; wxStaticText* m_staticTextNamingCvtPart1; wxStaticText* m_staticTextNamingCvtPart2Bold; wxStaticText* m_staticTextNamingCvtPart3; - wxHyperlinkCtrl* m_hyperlink17; - wxBoxSizer* bSizerMiscConfig; wxStaticLine* m_staticline582; + wxBoxSizer* bSizerMiscConfig; wxStaticText* m_staticText88; - wxRadioButton* m_radioBtnIgnoreErrors; wxRadioButton* m_radioBtnPopupOnErrors; + wxRadioButton* m_radioBtnIgnoreErrors; wxStaticLine* m_staticline57; wxBoxSizer* bSizerOnCompletion; wxStaticText* m_staticText89; @@ -416,21 +420,22 @@ protected: virtual void OnSyncUpdate( wxCommandEvent& event ) { event.Skip(); } virtual void OnSyncCustomDouble( wxMouseEvent& event ) { event.Skip(); } virtual void OnSyncCustom( wxCommandEvent& event ) { event.Skip(); } - virtual void OnToggleDetectMovedFiles( wxCommandEvent& event ) { event.Skip(); } virtual void OnExLeftSideOnly( wxCommandEvent& event ) { event.Skip(); } virtual void OnLeftNewer( wxCommandEvent& event ) { event.Skip(); } virtual void OnDifferent( wxCommandEvent& event ) { event.Skip(); } virtual void OnConflict( wxCommandEvent& event ) { event.Skip(); } virtual void OnRightNewer( wxCommandEvent& event ) { event.Skip(); } virtual void OnExRightSideOnly( wxCommandEvent& event ) { event.Skip(); } - virtual void OnDeletionPermanent( wxCommandEvent& event ) { event.Skip(); } + virtual void OnToggleDetectMovedFiles( wxCommandEvent& event ) { event.Skip(); } + virtual void OnHelpDetectMovedFiles( wxHyperlinkEvent& event ) { event.Skip(); } + virtual void OnToggleDeletionType( wxCommandEvent& event ) { event.Skip(); } virtual void OnDeletionRecycler( wxCommandEvent& event ) { event.Skip(); } + virtual void OnDeletionPermanent( wxCommandEvent& event ) { event.Skip(); } virtual void OnDeletionVersioning( wxCommandEvent& event ) { event.Skip(); } - virtual void OnToggleDeletionType( wxCommandEvent& event ) { event.Skip(); } - virtual void OnChangeSyncOption( wxCommandEvent& event ) { event.Skip(); } virtual void OnHelpVersioning( wxHyperlinkEvent& event ) { event.Skip(); } - virtual void OnErrorIgnore( wxCommandEvent& event ) { event.Skip(); } + virtual void OnChangeSyncOption( wxCommandEvent& event ) { event.Skip(); } virtual void OnErrorPopup( wxCommandEvent& event ) { event.Skip(); } + virtual void OnErrorIgnore( wxCommandEvent& event ) { event.Skip(); } virtual void OnOkay( wxCommandEvent& event ) { event.Skip(); } virtual void OnCancel( wxCommandEvent& event ) { event.Skip(); } @@ -721,13 +726,13 @@ protected: wxStaticLine* m_staticline18; wxPanel* m_panel35; wxStaticText* m_staticText82; - wxRadioButton* m_radioBtnIgnoreErrors; wxRadioButton* m_radioBtnPopupOnErrors; + wxRadioButton* m_radioBtnIgnoreErrors; wxRadioButton* m_radioBtnStopOnError; wxStaticLine* m_staticline26; - wxCheckBox* m_checkBoxRunMinimized; wxStaticText* m_staticText81; OnCompletionBox* m_comboBoxOnCompletion; + wxCheckBox* m_checkBoxRunMinimized; wxStaticLine* m_staticline25; wxCheckBox* m_checkBoxGenerateLogfile; wxPanel* m_panelLogfile; @@ -742,8 +747,8 @@ protected: // Virtual event handlers, overide them in your derived class virtual void OnClose( wxCloseEvent& event ) { event.Skip(); } - virtual void OnErrorIgnore( wxCommandEvent& event ) { event.Skip(); } virtual void OnErrorPopup( wxCommandEvent& event ) { event.Skip(); } + virtual void OnErrorIgnore( wxCommandEvent& event ) { event.Skip(); } virtual void OnErrorStop( wxCommandEvent& event ) { event.Skip(); } virtual void OnToggleGenerateLogfile( wxCommandEvent& event ) { event.Skip(); } virtual void OnToggleLogfilesLimit( wxCommandEvent& event ) { event.Skip(); } diff --git a/FreeFileSync/Source/ui/gui_status_handler.cpp b/FreeFileSync/Source/ui/gui_status_handler.cpp index 9335560b..82f0b707 100644 --- a/FreeFileSync/Source/ui/gui_status_handler.cpp +++ b/FreeFileSync/Source/ui/gui_status_handler.cpp @@ -146,32 +146,52 @@ void StatusHandlerTemporaryPanel::initNewPhase(int objectsTotal, std::int64_t da } +void StatusHandlerTemporaryPanel::reportInfo(const std::wstring& text) +{ + StatusHandler::reportInfo(text); + errorLog.logMsg(text, TYPE_INFO); +} + + ProcessCallback::Response StatusHandlerTemporaryPanel::reportError(const std::wstring& errorMessage, size_t retryNumber) { //no need to implement auto-retry here: 1. user is watching 2. comparison is fast //=> similar behavior like "ignoreErrors" which is also not used for the comparison phase in GUI mode - if (ignoreErrors) - return ProcessCallback::IGNORE_ERROR; - - forceUiRefresh(); + //always, except for "retry": + auto guardWriteLog = zen::makeGuard<ScopeGuardRunMode::ON_EXIT>([&] { errorLog.logMsg(errorMessage, TYPE_ERROR); }); - bool ignoreNextErrors = false; - switch (showConfirmationDialog3(&mainDlg, DialogInfoType::ERROR2, PopupDialogCfg3(). - setDetailInstructions(errorMessage). - setCheckBox(ignoreNextErrors, _("&Ignore subsequent errors"), ConfirmationButton3::DONT_DO_IT), - _("&Ignore"), _("&Retry"))) + switch (handleError_) { - case ConfirmationButton3::DO_IT: //ignore - ignoreErrors = ignoreNextErrors; - return ProcessCallback::IGNORE_ERROR; + case ON_GUIERROR_POPUP: + { + forceUiRefresh(); + + bool ignoreNextErrors = false; + switch (showConfirmationDialog3(&mainDlg, DialogInfoType::ERROR2, PopupDialogCfg3(). + setDetailInstructions(errorMessage). + setCheckBox(ignoreNextErrors, _("&Ignore subsequent errors"), ConfirmationButton3::DONT_DO_IT), + _("&Ignore"), _("&Retry"))) + { + case ConfirmationButton3::DO_IT: //ignore + if (ignoreNextErrors) //falsify only + handleError_ = ON_GUIERROR_IGNORE; + return ProcessCallback::IGNORE_ERROR; - case ConfirmationButton3::DONT_DO_IT: //retry - return ProcessCallback::RETRY; + case ConfirmationButton3::DONT_DO_IT: //retry + guardWriteLog.dismiss(); + errorLog.logMsg(errorMessage + L"\n-> " + _("Retrying operation..."), TYPE_INFO); //explain why there are duplicate "doing operation X" info messages in the log! + return ProcessCallback::RETRY; - case ConfirmationButton3::CANCEL: - abortProcessNow(); - break; + case ConfirmationButton3::CANCEL: + abortProcessNow(); + break; + } + } + break; + + case ON_GUIERROR_IGNORE: + return ProcessCallback::IGNORE_ERROR; } assert(false); @@ -181,6 +201,8 @@ ProcessCallback::Response StatusHandlerTemporaryPanel::reportError(const std::ws void StatusHandlerTemporaryPanel::reportFatalError(const std::wstring& errorMessage) { + errorLog.logMsg(errorMessage, TYPE_FATAL_ERROR); + forceUiRefresh(); showNotificationDialog(&mainDlg, DialogInfoType::ERROR2, PopupDialogCfg().setTitle(_("Serious Error")).setDetailInstructions(errorMessage)); } @@ -188,24 +210,35 @@ void StatusHandlerTemporaryPanel::reportFatalError(const std::wstring& errorMess void StatusHandlerTemporaryPanel::reportWarning(const std::wstring& warningMessage, bool& warningActive) { - if (!warningActive || ignoreErrors) //if errors are ignored, then warnings should also - return; + errorLog.logMsg(warningMessage, TYPE_WARNING); - forceUiRefresh(); + if (!warningActive) //if errors are ignored, then warnings should also + return; - //show pop-up and ask user how to handle warning - bool dontWarnAgain = false; - switch (showConfirmationDialog(&mainDlg, DialogInfoType::WARNING, - PopupDialogCfg().setDetailInstructions(warningMessage). - setCheckBox(dontWarnAgain, _("&Don't show this warning again")), - _("&Ignore"))) + switch (handleError_) { - case ConfirmationButton::DO_IT: - warningActive = !dontWarnAgain; - break; - case ConfirmationButton::CANCEL: - abortProcessNow(); - break; + case ON_GUIERROR_POPUP: + { + forceUiRefresh(); + + bool dontWarnAgain = false; + switch (showConfirmationDialog(&mainDlg, DialogInfoType::WARNING, + PopupDialogCfg().setDetailInstructions(warningMessage). + setCheckBox(dontWarnAgain, _("&Don't show this warning again")), + _("&Ignore"))) + { + case ConfirmationButton::DO_IT: + warningActive = !dontWarnAgain; + break; + case ConfirmationButton::CANCEL: + abortProcessNow(); + break; + } + } + break; + + case ON_GUIERROR_IGNORE: + break; //if errors are ignored, then warnings should also } } @@ -216,16 +249,16 @@ void StatusHandlerTemporaryPanel::forceUiRefresh() } -void StatusHandlerTemporaryPanel::OnAbortCompare(wxCommandEvent& event) +void StatusHandlerTemporaryPanel::abortProcessNow() { - requestAbortion(); + requestAbortion(); //just make sure... + throw GuiAbortProcess(); } -void StatusHandlerTemporaryPanel::abortProcessNow() +void StatusHandlerTemporaryPanel::OnAbortCompare(wxCommandEvent& event) { - requestAbortion(); //just make sure... - throw GuiAbortProcess(); + requestAbortion(); } //######################################################################################################## @@ -278,25 +311,25 @@ StatusHandlerFloatingDialog::~StatusHandlerFloatingDialog() } //------------ end of sync: begin of cleanup -------------------------------------- - const int totalErrors = errorLog.getItemCount(TYPE_ERROR | TYPE_FATAL_ERROR); //evaluate before finalizing log - const int totalWarnings = errorLog.getItemCount(TYPE_WARNING); + const int totalErrors = errorLog_.getItemCount(TYPE_ERROR | TYPE_FATAL_ERROR); //evaluate before finalizing log + const int totalWarnings = errorLog_.getItemCount(TYPE_WARNING); //finalize error log std::wstring finalStatus; if (abortIsRequested()) { finalStatus = _("Synchronization stopped"); - errorLog.logMsg(finalStatus, TYPE_ERROR); + errorLog_.logMsg(finalStatus, TYPE_ERROR); } else if (totalErrors > 0) { finalStatus = _("Synchronization completed with errors"); - errorLog.logMsg(finalStatus, TYPE_ERROR); + errorLog_.logMsg(finalStatus, TYPE_ERROR); } else if (totalWarnings > 0) { finalStatus = _("Synchronization completed with warnings"); - errorLog.logMsg(finalStatus, TYPE_WARNING); //give status code same warning priority as display category! + errorLog_.logMsg(finalStatus, TYPE_WARNING); //give status code same warning priority as display category! } else { @@ -305,7 +338,7 @@ StatusHandlerFloatingDialog::~StatusHandlerFloatingDialog() finalStatus = _("Nothing to synchronize"); //even if "ignored conflicts" occurred! else finalStatus = _("Synchronization completed successfully"); - errorLog.logMsg(finalStatus, TYPE_INFO); + errorLog_.logMsg(finalStatus, TYPE_INFO); } const SummaryInfo summary = @@ -319,7 +352,7 @@ StatusHandlerFloatingDialog::~StatusHandlerFloatingDialog() //----------------- write results into LastSyncs.log------------------------ try { - saveToLastSyncsLog(summary, errorLog, lastSyncsLogFileSizeMax_, OnUpdateLogfileStatusNoThrow(*this, utfCvrtTo<std::wstring>(getLastSyncsLogfilePath()))); //throw FileError + saveToLastSyncsLog(summary, errorLog_, lastSyncsLogFileSizeMax_, OnUpdateLogfileStatusNoThrow(*this, utfCvrtTo<std::wstring>(getLastSyncsLogfilePath()))); //throw FileError } catch (FileError&) { assert(false); } @@ -329,13 +362,13 @@ StatusHandlerFloatingDialog::~StatusHandlerFloatingDialog() if (showFinalResults) { if (abortIsRequested()) - progressDlg->processHasFinished(SyncProgressDialog::RESULT_ABORTED, errorLog); //enable okay and close events + progressDlg->processHasFinished(SyncProgressDialog::RESULT_ABORTED, errorLog_); //enable okay and close events else if (totalErrors > 0) - progressDlg->processHasFinished(SyncProgressDialog::RESULT_FINISHED_WITH_ERROR, errorLog); + progressDlg->processHasFinished(SyncProgressDialog::RESULT_FINISHED_WITH_ERROR, errorLog_); else if (totalWarnings > 0) - progressDlg->processHasFinished(SyncProgressDialog::RESULT_FINISHED_WITH_WARNINGS, errorLog); + progressDlg->processHasFinished(SyncProgressDialog::RESULT_FINISHED_WITH_WARNINGS, errorLog_); else - progressDlg->processHasFinished(SyncProgressDialog::RESULT_FINISHED_WITH_SUCCESS, errorLog); + progressDlg->processHasFinished(SyncProgressDialog::RESULT_FINISHED_WITH_SUCCESS, errorLog_); } else progressDlg->closeWindowDirectly(); @@ -375,7 +408,7 @@ void StatusHandlerFloatingDialog::updateProcessedData(int objectsDelta, std::int void StatusHandlerFloatingDialog::reportInfo(const std::wstring& text) { StatusHandler::reportInfo(text); - errorLog.logMsg(text, TYPE_INFO); + errorLog_.logMsg(text, TYPE_INFO); } @@ -384,8 +417,8 @@ ProcessCallback::Response StatusHandlerFloatingDialog::reportError(const std::ws //auto-retry if (retryNumber < automaticRetryCount_) { - errorLog.logMsg(errorMessage + L"\n-> " + - _P("Automatic retry in 1 second...", "Automatic retry in %x seconds...", automaticRetryDelay_), TYPE_INFO); + errorLog_.logMsg(errorMessage + L"\n-> " + + _P("Automatic retry in 1 second...", "Automatic retry in %x seconds...", automaticRetryDelay_), TYPE_INFO); //delay const int iterations = static_cast<int>(1000 * automaticRetryDelay_ / UI_UPDATE_INTERVAL); //always round down: don't allow for negative remaining time below for (int i = 0; i < iterations; ++i) @@ -399,7 +432,7 @@ ProcessCallback::Response StatusHandlerFloatingDialog::reportError(const std::ws //always, except for "retry": - auto guardWriteLog = zen::makeGuard<ScopeGuardRunMode::ON_EXIT>([&] { errorLog.logMsg(errorMessage, TYPE_ERROR); }); + auto guardWriteLog = zen::makeGuard<ScopeGuardRunMode::ON_EXIT>([&] { errorLog_.logMsg(errorMessage, TYPE_ERROR); }); switch (handleError_) { @@ -422,7 +455,7 @@ ProcessCallback::Response StatusHandlerFloatingDialog::reportError(const std::ws case ConfirmationButton3::DONT_DO_IT: //retry guardWriteLog.dismiss(); - errorLog.logMsg(errorMessage + L"\n-> " + _("Retrying operation..."), TYPE_INFO); //explain why there are duplicate "doing operation X" info messages in the log! + errorLog_.logMsg(errorMessage + L"\n-> " + _("Retrying operation..."), TYPE_INFO); //explain why there are duplicate "doing operation X" info messages in the log! return ProcessCallback::RETRY; case ConfirmationButton3::CANCEL: @@ -443,7 +476,7 @@ ProcessCallback::Response StatusHandlerFloatingDialog::reportError(const std::ws void StatusHandlerFloatingDialog::reportFatalError(const std::wstring& errorMessage) { - errorLog.logMsg(errorMessage, TYPE_FATAL_ERROR); + errorLog_.logMsg(errorMessage, TYPE_FATAL_ERROR); switch (handleError_) { @@ -479,7 +512,7 @@ void StatusHandlerFloatingDialog::reportFatalError(const std::wstring& errorMess void StatusHandlerFloatingDialog::reportWarning(const std::wstring& warningMessage, bool& warningActive) { - errorLog.logMsg(warningMessage, TYPE_WARNING); + errorLog_.logMsg(warningMessage, TYPE_WARNING); if (!warningActive) return; diff --git a/FreeFileSync/Source/ui/gui_status_handler.h b/FreeFileSync/Source/ui/gui_status_handler.h index d8931cbf..eeed35d2 100644 --- a/FreeFileSync/Source/ui/gui_status_handler.h +++ b/FreeFileSync/Source/ui/gui_status_handler.h @@ -28,20 +28,24 @@ public: ~StatusHandlerTemporaryPanel(); void initNewPhase(int objectsTotal, std::int64_t dataTotal, Phase phaseID) override; - void forceUiRefresh() override; + void reportInfo (const std::wstring& text) override; Response reportError (const std::wstring& text, size_t retryNumber) override; void reportFatalError(const std::wstring& errorMessage) override; void reportWarning (const std::wstring& warningMessage, bool& warningActive) override; + void forceUiRefresh() override; void abortProcessNow() override final; //throw GuiAbortProcess + zen::ErrorLog getErrorLog() const { return errorLog; } + private: void OnKeyPressed(wxKeyEvent& event); void OnAbortCompare(wxCommandEvent& event); //handle abort button click MainDialog& mainDlg; - bool ignoreErrors = false; + xmlAccess::OnGuiError handleError_ = xmlAccess::ON_GUIERROR_POPUP; + zen::ErrorLog errorLog; }; @@ -62,13 +66,13 @@ public: void initNewPhase (int objectsTotal, std::int64_t dataTotal, Phase phaseID) override; void updateProcessedData(int objectsDelta, std::int64_t dataDelta ) override; - void reportInfo (const std::wstring& text ) override; - void forceUiRefresh () override; + void reportInfo (const std::wstring& text ) override; Response reportError (const std::wstring& text, size_t retryNumber ) override; void reportFatalError(const std::wstring& errorMessage ) override; void reportWarning (const std::wstring& warningMessage, bool& warningActive) override; + void forceUiRefresh() override; void abortProcessNow() override final; //throw GuiAbortProcess private: @@ -77,7 +81,7 @@ private: SyncProgressDialog* progressDlg; //managed to have shorter lifetime than this handler! const size_t lastSyncsLogFileSizeMax_; xmlAccess::OnGuiError handleError_; - zen::ErrorLog errorLog; + zen::ErrorLog errorLog_; const size_t automaticRetryCount_; const size_t automaticRetryDelay_; const std::wstring jobName_; diff --git a/FreeFileSync/Source/ui/main_dlg.cpp b/FreeFileSync/Source/ui/main_dlg.cpp index 8e1713e2..5d3ac556 100644 --- a/FreeFileSync/Source/ui/main_dlg.cpp +++ b/FreeFileSync/Source/ui/main_dlg.cpp @@ -610,8 +610,6 @@ MainDialog::MainDialog(const Zstring& globalConfigFile, gridDataView = std::make_shared<GridView>(); treeDataView = std::make_shared<TreeView>(); - cleanedUp = false; - #ifdef ZEN_WIN new PanelMoveWindow(*this); //allow moving main dialog by clicking (nearly) anywhere... //ownership passed to "this" #endif @@ -954,8 +952,7 @@ void MainDialog::setGlobalCfgOnInit(const xmlAccess::XmlGlobalSettings& globalSe m_checkBoxMatchCase->SetValue(globalCfg.gui.mainDlg.textSearchRespectCase); //wxAuiManager erroneously loads panel captions, we don't want that - typedef std::vector<std::pair<wxString, wxString>> CaptionNameMapping; - CaptionNameMapping captionNameMap; + std::vector<std::pair<wxString, wxString>>captionNameMap; const wxAuiPaneInfoArray& paneArray = auiMgr.GetAllPanes(); for (size_t i = 0; i < paneArray.size(); ++i) captionNameMap.emplace_back(paneArray[i].caption, paneArray[i].name); @@ -1309,6 +1306,7 @@ AbstractPath getExistingParentFolder(const FileSystemObject& fsObj) } } + void MainDialog::openExternalApplication(const wxString& commandline, const std::vector<FileSystemObject*>& selection, bool leftSide) { if (commandline.empty()) @@ -3636,13 +3634,14 @@ void MainDialog::OnCompare(wxCommandEvent& event) //handle status display and error messages StatusHandlerTemporaryPanel statusHandler(*this); - const std::vector<zen::FolderPairCfg> cmpConfig = extractCompareCfg(getConfig().mainCfg, globalCfg.fileTimeTolerance); + const std::vector<zen::FolderPairCfg> cmpConfig = extractCompareCfg(getConfig().mainCfg); //GUI mode: place directory locks on directories isolated(!) during both comparison and synchronization std::unique_ptr<LockHolder> dirLocks; //COMPARE DIRECTORIES folderCmp = compare(globalCfg.optDialogs, + globalCfg.fileTimeTolerance, true, //allowUserInteraction globalCfg.runWithBackgroundPriority, globalCfg.folderAccessTimeout, @@ -3667,10 +3666,13 @@ void MainDialog::OnCompare(wxCommandEvent& event) m_gridNavi->clearSelection(ALLOW_GRID_EVENT); - //play (optional) sound notification after sync has completed (GUI and batch mode) - //const Zstring soundFile = zen::getResourceDir() + Zstr("Compare_Complete.wav"); - //if (fileExists(soundFile)) - // wxSound::Play(toWx(soundFile), wxSOUND_ASYNC); + //play (optional) sound notification + if (!globalCfg.soundFileCompareFinished.empty()) + { + const Zstring soundFile = getResourceDir() + globalCfg.soundFileCompareFinished; + if (fileExists(soundFile)) + wxSound::Play(utfCvrtTo<wxString>(soundFile), wxSOUND_ASYNC); //warning: this may fail and show a wxWidgets error message! => must not play when running FFS as batch! + } //add to folder history after successful comparison only folderHistoryLeft ->addItem(toZ(m_folderPathLeft ->GetValue())); @@ -3826,10 +3828,13 @@ void MainDialog::OnStartSync(wxCommandEvent& event) globalCfg.automaticRetryCount, globalCfg.automaticRetryDelay, xmlAccess::extractJobName(activeCfgFilename), - globalCfg.soundFileSyncComplete, + globalCfg.soundFileSyncFinished, guiCfg.mainCfg.onCompletion, globalCfg.gui.onCompletionHistory); + //inform about (important) non-default global settings + logNonDefaultSettings(globalCfg, statusHandler); //let's report here rather than before comparison (user might have changed global settings in the meantime!) + //wxBusyCursor dummy; -> redundant: progress already shown in progress dialog! //GUI mode: place directory locks on directories isolated(!) during both comparison and synchronization @@ -3861,7 +3866,7 @@ void MainDialog::OnStartSync(wxCommandEvent& event) globalCfg.verifyFileCopy, globalCfg.copyLockedFiles, globalCfg.copyFilePermissions, - globalCfg.failsafeFileCopy, + globalCfg.failSafeFileCopy, globalCfg.runWithBackgroundPriority, globalCfg.folderAccessTimeout, syncProcessCfg, @@ -4780,7 +4785,7 @@ void MainDialog::OnMenuCheckVersionAutomatically(wxCommandEvent& event) { flashStatusInformation(_("Searching for program updates...")); //synchronous update check is sufficient here: - evalPeriodicUpdateCheck(this, globalCfg.gui.lastUpdateCheck, globalCfg.gui.lastOnlineVersion, retrieveOnlineVersion().get()); + periodicUpdateCheckEval(this, globalCfg.gui.lastUpdateCheck, globalCfg.gui.lastOnlineVersion, periodicUpdateCheckRunAsync(periodicUpdateCheckPrepare().get()).get()); } } @@ -4795,10 +4800,12 @@ void MainDialog::OnRegularUpdateCheck(wxIdleEvent& event) { flashStatusInformation(_("Searching for program updates...")); - guiQueue.processAsync([] { return retrieveOnlineVersion(); }, - [this] (std::shared_ptr<UpdateCheckResult>&& result) + std::shared_ptr<UpdateCheckResultPrep> resultPrep = periodicUpdateCheckPrepare(); //run on main thread: + + guiQueue.processAsync([resultPrep] { return periodicUpdateCheckRunAsync(resultPrep.get()); }, //run on worker thread: (long-running part of the check) + [this] (std::shared_ptr<UpdateCheckResultAsync>&& resultAsync) { - evalPeriodicUpdateCheck(this, globalCfg.gui.lastUpdateCheck, globalCfg.gui.lastOnlineVersion, result.get()); + periodicUpdateCheckEval(this, globalCfg.gui.lastUpdateCheck, globalCfg.gui.lastOnlineVersion, resultAsync.get()); //run on main thread: }); } } diff --git a/FreeFileSync/Source/ui/main_dlg.h b/FreeFileSync/Source/ui/main_dlg.h index 25106545..0288d19f 100644 --- a/FreeFileSync/Source/ui/main_dlg.h +++ b/FreeFileSync/Source/ui/main_dlg.h @@ -11,6 +11,7 @@ #include <list> #include <stack> #include <memory> +//#include <zen/error_log.h> #include <wx+/async_task.h> #include <wx+/file_drop.h> #include <wx/aui/aui.h> @@ -312,8 +313,6 @@ private: //compare status panel (hidden on start, shown when comparing) std::unique_ptr<CompareProgressDialog> compareStatus; //always bound - bool cleanedUp; - //toggle to display configuration preview instead of comparison result: //for read access use: m_bpButtonViewTypeSyncAction->isActive() //when changing value use: diff --git a/FreeFileSync/Source/ui/progress_indicator.cpp b/FreeFileSync/Source/ui/progress_indicator.cpp index bfa7b506..5dce1c34 100644 --- a/FreeFileSync/Source/ui/progress_indicator.cpp +++ b/FreeFileSync/Source/ui/progress_indicator.cpp @@ -27,6 +27,7 @@ #include <wx+/image_resources.h> #include <zen/file_access.h> #include <zen/thread.h> +#include <wx+/rtl.h> #include "gui_generated.h" #include "../lib/ffs_paths.h" #include "../lib/perf_check.h" @@ -589,7 +590,7 @@ public: switch (static_cast<ColumnTypeMsg>(colType)) { case COL_TYPE_MSG_TIME: - drawCellText(dc, rectTmp, getValue(row, colType), true, wxALIGN_CENTER); + drawCellText(dc, rectTmp, getValue(row, colType), wxALIGN_CENTER); break; case COL_TYPE_MSG_CATEGORY: @@ -597,14 +598,14 @@ public: switch (entry->type) { case TYPE_INFO: - dc.DrawLabel(wxString(), getResourceImage(L"msg_info_small"), rectTmp, wxALIGN_CENTER); + drawBitmapRtlNoMirror(dc, getResourceImage(L"msg_info_small"), rectTmp, wxALIGN_CENTER); break; case TYPE_WARNING: - dc.DrawLabel(wxString(), getResourceImage(L"msg_warning_small"), rectTmp, wxALIGN_CENTER); + drawBitmapRtlNoMirror(dc, getResourceImage(L"msg_warning_small"), rectTmp, wxALIGN_CENTER); break; case TYPE_ERROR: case TYPE_FATAL_ERROR: - dc.DrawLabel(wxString(), getResourceImage(L"msg_error_small"), rectTmp, wxALIGN_CENTER); + drawBitmapRtlNoMirror(dc, getResourceImage(L"msg_error_small"), rectTmp, wxALIGN_CENTER); break; } break; @@ -612,7 +613,7 @@ public: case COL_TYPE_MSG_TEXT: rectTmp.x += COLUMN_GAP_LEFT; rectTmp.width -= COLUMN_GAP_LEFT; - drawCellText(dc, rectTmp, getValue(row, colType), true); + drawCellText(dc, rectTmp, getValue(row, colType)); break; } } diff --git a/FreeFileSync/Source/ui/small_dlgs.cpp b/FreeFileSync/Source/ui/small_dlgs.cpp index d7b4cd37..09439eea 100644 --- a/FreeFileSync/Source/ui/small_dlgs.cpp +++ b/FreeFileSync/Source/ui/small_dlgs.cpp @@ -118,7 +118,7 @@ AboutDlg::AboutDlg(wxWindow* parent) : AboutDlgGenerated(parent) const int BORDER_SIZE = 5; wxBitmap headerBmp(GetClientSize().GetWidth(), versionImage.GetHeight() + 2 * BORDER_SIZE, 24); //attention: *must* pass 24 bits, auto-determination fails on Windows high-contrast colors schemes!!! - //problem only manifests when calling wxDC::DrawBitmap + //problem only shows when calling wxDC::DrawBitmap { wxMemoryDC dc(headerBmp); dc.SetBackground(*wxWHITE_BRUSH); @@ -674,7 +674,7 @@ OptionsDlg::OptionsDlg(wxWindow* parent, xmlAccess::XmlGlobalSettings& globalSet m_bpButtonRemoveRow->SetBitmapLabel(getResourceImage(L"item_remove")); setBitmapTextLabel(*m_buttonResetDialogs, getResourceImage(L"reset_dialogs").ConvertToImage(), m_buttonResetDialogs->GetLabel()); - m_checkBoxFailSafe ->SetValue(globalSettings.failsafeFileCopy); + m_checkBoxFailSafe ->SetValue(globalSettings.failSafeFileCopy); m_checkBoxCopyLocked ->SetValue(globalSettings.copyLockedFiles); m_checkBoxCopyPermissions->SetValue(globalSettings.copyFilePermissions); @@ -744,7 +744,7 @@ void OptionsDlg::updateGui() void OptionsDlg::OnOkay(wxCommandEvent& event) { //write settings only when okay-button is pressed (except hidden dialog reset)! - globalSettingsOut.failsafeFileCopy = m_checkBoxFailSafe->GetValue(); + globalSettingsOut.failSafeFileCopy = m_checkBoxFailSafe->GetValue(); globalSettingsOut.copyLockedFiles = m_checkBoxCopyLocked->GetValue(); globalSettingsOut.copyFilePermissions = m_checkBoxCopyPermissions->GetValue(); @@ -776,7 +776,7 @@ void OptionsDlg::OnDefault(wxCommandEvent& event) { const xmlAccess::XmlGlobalSettings defaultCfg; - m_checkBoxFailSafe ->SetValue(defaultCfg.failsafeFileCopy); + m_checkBoxFailSafe ->SetValue(defaultCfg.failSafeFileCopy); m_checkBoxCopyLocked ->SetValue(defaultCfg.copyLockedFiles); m_checkBoxCopyPermissions->SetValue(defaultCfg.copyFilePermissions); diff --git a/FreeFileSync/Source/ui/sync_cfg.cpp b/FreeFileSync/Source/ui/sync_cfg.cpp index 74789c83..2b3fa496 100644 --- a/FreeFileSync/Source/ui/sync_cfg.cpp +++ b/FreeFileSync/Source/ui/sync_cfg.cpp @@ -128,7 +128,8 @@ private: void OnToggleDeletionType(wxCommandEvent& event) override { toggleDeletionPolicy(handleDeletion); updateSyncGui(); } - void OnHelpVersioning(wxHyperlinkEvent& event) override { displayHelpEntry(L"versioning", this); } + void OnHelpDetectMovedFiles(wxHyperlinkEvent& event) override { displayHelpEntry(L"synchronization-settings" , this); } + void OnHelpVersioning (wxHyperlinkEvent& event) override { displayHelpEntry(L"versioning", this); } std::shared_ptr<const SyncConfig> getSyncConfig() const; void setSyncConfig(std::shared_ptr<const SyncConfig> syncCfg); @@ -320,7 +321,7 @@ ConfigDialog::ConfigDialog(wxWindow* parent, add(VER_STYLE_ADD_TIMESTAMP, _("Time stamp"), _("Append a time stamp to each file name")); //use spacer to keep dialog height stable, no matter if versioning options are visible - bSizerVersioning->Add(0, m_panelVersioning->GetSize().GetHeight()); + bSizerDelHandling->Add(0, m_panelVersioning->GetSize().GetHeight()); //----------------------------------------------------- @@ -844,13 +845,13 @@ void toggleDeletionPolicy(DeletionPolicy& deletionPolicy) switch (deletionPolicy) { case DELETE_PERMANENTLY: - deletionPolicy = DELETE_TO_RECYCLER; + deletionPolicy = DELETE_TO_VERSIONING; break; case DELETE_TO_RECYCLER: - deletionPolicy = DELETE_TO_VERSIONING; + deletionPolicy = DELETE_PERMANENTLY; break; case DELETE_TO_VERSIONING: - deletionPolicy = DELETE_PERMANENTLY; + deletionPolicy = DELETE_TO_RECYCLER; break; } } @@ -1064,7 +1065,7 @@ void ConfigDialog::updateMiscGui() void ConfigDialog::selectFolderPairConfig(int newPairIndexToShow) { assert(selectedPairIndexToShow == EMPTY_PAIR_INDEX_SELECTED); - assert(newPairIndexToShow == -1 || makeUnsigned(newPairIndexToShow) < folderPairConfig_.size()); + assert(newPairIndexToShow == -1 || makeUnsigned(newPairIndexToShow) < folderPairConfig_.size()); numeric::clamp(newPairIndexToShow, -1, static_cast<int>(folderPairConfig_.size()) - 1); selectedPairIndexToShow = newPairIndexToShow; @@ -1073,13 +1074,19 @@ void ConfigDialog::selectFolderPairConfig(int newPairIndexToShow) //show/hide controls that are only relevant for main/local config const bool mainConfigSelected = newPairIndexToShow < 0; //comparison panel: - bSizerLocalCompSettings->Show(!mainConfigSelected); + m_staticTextMainCompSettings->Show( mainConfigSelected && !folderPairConfig_.empty()); + m_checkBoxUseLocalCmpOptions->Show(!mainConfigSelected && !folderPairConfig_.empty()); + m_staticlineCompHeader->Show(!folderPairConfig_.empty()); m_panelCompSettingsHolder->Layout(); //fix comp panel glitch on Win 7 125% font size //filter panel - bSizerLocalFilterSettings->Show(!mainConfigSelected); + m_staticTextMainFilterSettings ->Show( mainConfigSelected && !folderPairConfig_.empty()); + m_staticTextLocalFilterSettings->Show(!mainConfigSelected && !folderPairConfig_.empty()); + m_staticlineFilterHeader->Show(!folderPairConfig_.empty()); m_panelFilterSettingsHolder->Layout(); //sync panel: - bSizerLocalSyncSettings->Show(!mainConfigSelected); + m_staticTextMainSyncSettings ->Show( mainConfigSelected && !folderPairConfig_.empty()); + m_checkBoxUseLocalSyncOptions->Show(!mainConfigSelected && !folderPairConfig_.empty()); + m_staticlineSyncHeader->Show(!folderPairConfig_.empty()); m_panelSyncSettingsHolder->Layout(); //misc bSizerMiscConfig->Show(mainConfigSelected); diff --git a/FreeFileSync/Source/ui/tree_view.cpp b/FreeFileSync/Source/ui/tree_view.cpp index 590a00c9..a1420ff1 100644 --- a/FreeFileSync/Source/ui/tree_view.cpp +++ b/FreeFileSync/Source/ui/tree_view.cpp @@ -869,8 +869,7 @@ private: if (colType == static_cast<ColumnType>(sortInfo.first)) { const wxBitmap& marker = getResourceImage(sortInfo.second ? L"sortAscending" : L"sortDescending"); - wxPoint markerBegin = rectInside.GetTopLeft() + wxPoint((rectInside.width - marker.GetWidth()) / 2, 0); - dc.DrawBitmap(marker, markerBegin, true); //respect 2-pixel gap + drawBitmapRtlNoMirror(dc, marker, rectInside, wxALIGN_CENTER_HORIZONTAL); } } } @@ -950,7 +949,7 @@ private: } wxDCTextColourChanger dummy3(dc, *wxBLACK); //accessibility: always set both foreground AND background colors! - dc.DrawLabel(numberTo<wxString>(node->percent_) + L"%", areaPerc, wxALIGN_CENTER); + drawCellText(dc, areaPerc, numberTo<std::wstring>(node->percent_) + L"%", wxALIGN_CENTER); rectTmp.x += WIDTH_PERCENTAGE_BAR + 2 * GAP_SIZE; rectTmp.width -= WIDTH_PERCENTAGE_BAR + 2 * GAP_SIZE; @@ -967,7 +966,7 @@ private: //clearArea(dc, rectStat, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); clearArea(dc, rectStat, *wxWHITE); //accessibility: always set both foreground AND background colors! - drawBitmapRtlMirror(dc, bmp, rectStat, wxALIGN_CENTER, buffer); + drawBitmapRtlMirror(dc, bmp, rectStat, wxALIGN_CENTER, renderBuf); }; const bool drawMouseHover = static_cast<HoverAreaNavi>(rowHover) == HoverAreaNavi::NODE; @@ -1003,13 +1002,19 @@ private: if (!isActive) nodeIcon = wxBitmap(nodeIcon.ConvertToImage().ConvertToGreyscale(1.0 / 3, 1.0 / 3, 1.0 / 3)); //treat all channels equally! - drawBitmapRtlNoMirror(dc, nodeIcon, rectTmp, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, buffer); + drawBitmapRtlNoMirror(dc, nodeIcon, rectTmp, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); rectTmp.x += widthNodeIcon + GAP_SIZE; rectTmp.width -= widthNodeIcon + GAP_SIZE; if (rectTmp.width > 0) - drawCellText(dc, rectTmp, getValue(row, colType), isActive, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); + { + wxDCTextColourChanger dummy(dc); + if (!isActive) + dummy.Set(wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT)); + + drawCellText(dc, rectTmp, getValue(row, colType), wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); + } } } } @@ -1032,7 +1037,7 @@ private: rectTmp.width -= 2 * GAP_SIZE; } - drawCellText(dc, rectTmp, getValue(row, colType), true, alignment); + drawCellText(dc, rectTmp, getValue(row, colType), alignment); } } @@ -1265,7 +1270,7 @@ private: const wxBitmap dirIcon = IconBuffer::genericDirIcon (IconBuffer::SIZE_SMALL); const wxBitmap rootBmp; - Opt<wxBitmap> buffer; //avoid costs of recreating this temporal variable + Opt<wxBitmap> renderBuf; //avoid costs of recreating this temporal variable const int widthNodeIcon; const int widthLevelStep; const int widthNodeStatus; diff --git a/FreeFileSync/Source/ui/version_check.cpp b/FreeFileSync/Source/ui/version_check.cpp index de76a998..056e57ac 100644 --- a/FreeFileSync/Source/ui/version_check.cpp +++ b/FreeFileSync/Source/ui/version_check.cpp @@ -12,57 +12,47 @@ #include <zen/build_info.h> #include <zen/basic_math.h> #include <zen/file_error.h> +#include <zen/thread.h> //std::thread::id #include <wx+/popup_dlg.h> +#include <wx+/http.h> #include "version_id.h" #include "../lib/ffs_paths.h" #ifdef ZEN_WIN - #include <zen/win.h> //tame wininet.h include #include <zen/win_ver.h> - #include <zen/com_tools.h> - #include <wininet.h> +// #include <zen/com_tools.h> #elif defined ZEN_MAC #include <CoreServices/CoreServices.h> //Gestalt() #endif -#if defined ZEN_LINUX || defined ZEN_MAC - #include <zen/thread.h> //std::thread::id - #include <wx/protocol/http.h> - #include <wx/app.h> -#endif - - using namespace zen; namespace { -#ifndef ZEN_WIN - #ifndef NDEBUG - const std::thread::id mainThreadId = std::this_thread::get_id(); - #endif +#ifndef NDEBUG + const std::thread::id mainThreadId = std::this_thread::get_id(); #endif std::wstring getIso639Language() { - //respect thread-safety for WinInetAccess => don't use wxWidgets in the Windows build here!!! + assert(std::this_thread::get_id() == mainThreadId); //this function is not thread-safe, consider wxWidgets usage #ifdef ZEN_WIN //use a more reliable function than wxWidgets: const int bufSize = 10; wchar_t buf[bufSize] = {}; - int rv = ::GetLocaleInfo(LOCALE_USER_DEFAULT, //_In_ LCID Locale, - LOCALE_SISO639LANGNAME,//_In_ LCTYPE LCType, - buf, //_Out_opt_ LPTSTR lpLCData, - bufSize); //_In_ int cchData + int rv = ::GetLocaleInfo(LOCALE_USER_DEFAULT, //_In_ LCID Locale, + LOCALE_SISO639LANGNAME, //_In_ LCTYPE LCType, + buf, //_Out_opt_ LPTSTR lpLCData, + bufSize); //_In_ int cchData if (0 < rv && rv < bufSize) return buf; //MSDN: "This can be a 3-letter code for languages that don't have a 2-letter code"! assert(false); return std::wstring(); -#else - assert(std::this_thread::get_id() == mainThreadId); +#else const std::wstring localeName(wxLocale::GetLanguageCanonicalName(wxLocale::GetSystemLanguage())); if (localeName.empty()) return std::wstring(); @@ -75,22 +65,21 @@ std::wstring getIso639Language() std::wstring getIso3166Country() { - //respect thread-safety for WinInetAccess => don't use wxWidgets in the Windows build here!!! + assert(std::this_thread::get_id() == mainThreadId); //this function is not thread-safe, consider wxWidgets usage #ifdef ZEN_WIN //use a more reliable function than wxWidgets: const int bufSize = 10; wchar_t buf[bufSize] = {}; - int rv = ::GetLocaleInfo(LOCALE_USER_DEFAULT, //_In_ LCID Locale, - LOCALE_SISO3166CTRYNAME,//_In_ LCTYPE LCType, - buf, //_Out_opt_ LPTSTR lpLCData, - bufSize); //_In_ int cchData + int rv = ::GetLocaleInfo(LOCALE_USER_DEFAULT, //_In_ LCID Locale, + LOCALE_SISO3166CTRYNAME, //_In_ LCTYPE LCType, + buf, //_Out_opt_ LPTSTR lpLCData, + bufSize); //_In_ int cchData if (0 < rv && rv < bufSize) return buf; //MSDN: "This can also return a number, such as "029" for Caribbean."! assert(false); return std::wstring(); -#else - assert(std::this_thread::get_id() == mainThreadId); +#else const std::wstring localeName(wxLocale::GetLanguageCanonicalName(wxLocale::GetSystemLanguage())); if (localeName.empty()) return std::wstring(); @@ -100,24 +89,23 @@ std::wstring getIso3166Country() } -std::string geHttpPostParameters() //must be in application/x-www-form-urlencoded format!!! -{ - //1. coordinate with get_latest_version_number.php - //2. respect thread-safety for WinInetAccess => don't use wxWidgets in the Windows build here!!! - std::string params = "ffs_version=" + utfCvrtTo<std::string>(zen::ffsVersion); +//coordinate with get_latest_version_number.php +std::vector<std::pair<std::string, std::string>> geHttpPostParameters() +{ + assert(std::this_thread::get_id() == mainThreadId); //this function is not thread-safe, e.g. consider wxWidgets usage in isPortableVersion() + std::vector<std::pair<std::string, std::string>> params; - params += "&ffs_type="; - params += isPortableVersion() ? "Portable" : "Local"; + params.emplace_back("ffs_version", utfCvrtTo<std::string>(zen::ffsVersion)); + params.emplace_back("ffs_type", isPortableVersion() ? "Portable" : "Local"); #ifdef ZEN_WIN - params += "&os_name=Windows"; + params.emplace_back("os_name", "Windows"); const auto osvMajor = getOsVersion().major; const auto osvMinor = getOsVersion().minor; #elif defined ZEN_LINUX - params += "&os_name=Linux"; - assert(std::this_thread::get_id() == mainThreadId); + params.emplace_back("os_name", "Linux"); const wxLinuxDistributionInfo distribInfo = wxGetLinuxDistributionInfo(); assert(contains(distribInfo.Release, L'.')); @@ -129,271 +117,33 @@ std::string geHttpPostParameters() //must be in application/x-www-form-urlencode const int osvMinor = stringTo<int>(digits[1]); #elif defined ZEN_MAC - params += "&os_name=Mac"; + params.emplace_back("os_name", "Mac"); + SInt32 osvMajor = 0; SInt32 osvMinor = 0; ::Gestalt(gestaltSystemVersionMajor, &osvMajor); ::Gestalt(gestaltSystemVersionMinor, &osvMinor); #endif - params += "&os_version=" + numberTo<std::string>(osvMajor) + "." + numberTo<std::string>(osvMinor); + params.emplace_back("os_version", numberTo<std::string>(osvMajor) + "." + numberTo<std::string>(osvMinor)); - params += #ifdef ZEN_WIN - running64BitWindows() ? "&os_arch=64" : "&os_arch=32"; + params.emplace_back("os_arch", running64BitWindows() ? "64" : "32"); #elif defined ZEN_LINUX || defined ZEN_MAC #ifdef ZEN_BUILD_32BIT - "&os_arch=32"; + params.emplace_back("os_arch", "32"); #elif defined ZEN_BUILD_64BIT - "&os_arch=64"; + params.emplace_back("os_arch", "64"); #endif #endif const std::string isoLang = utfCvrtTo<std::string>(getIso639Language()); const std::string isoCountry = utfCvrtTo<std::string>(getIso3166Country()); - params += "&language=" + (!isoLang .empty() ? isoLang : "zz"); - params += "&country=" + (!isoCountry.empty() ? isoCountry : "ZZ"); - - return params; -} - - -std::string sendHttpRequestImpl(const std::wstring& url, //throw FileError - const std::string* postParams, //issue POST if bound, GET otherwise - int level = 0) -{ - assert(!startsWith(url, L"https:")); //not supported by wxHTTP! - std::wstring urlFmt = url; - if (startsWith(urlFmt, L"http://")) - urlFmt = afterFirst(urlFmt, L"://", IF_MISSING_RETURN_NONE); - const std::wstring server = beforeFirst(urlFmt, L'/', IF_MISSING_RETURN_ALL); - const std::wstring page = L'/' + afterFirst(urlFmt, L'/', IF_MISSING_RETURN_NONE); -#ifdef ZEN_WIN - //WinInet: 1. uses IE proxy settings! :) 2. follows HTTP redirects by default 3. swallows HTTPS if needed - HINTERNET hInternet = ::InternetOpen(L"FFS-Update-Check", //_In_ LPCTSTR lpszAgent, - INTERNET_OPEN_TYPE_PRECONFIG, //_In_ DWORD dwAccessType, - nullptr, //_In_ LPCTSTR lpszProxyName, - nullptr, //_In_ LPCTSTR lpszProxyBypass, - 0); //_In_ DWORD dwFlags - if (!hInternet) - THROW_LAST_FILE_ERROR(_("Internet access failed."), L"InternetOpen"); - ZEN_ON_SCOPE_EXIT(::InternetCloseHandle(hInternet)); - - HINTERNET hSession = ::InternetConnect(hInternet, //_In_ HINTERNET hInternet, - server.c_str(), //_In_ LPCTSTR lpszServerName, - INTERNET_DEFAULT_HTTP_PORT, //_In_ INTERNET_PORT nServerPort, - nullptr, //_In_ LPCTSTR lpszUsername, - nullptr, //_In_ LPCTSTR lpszPassword, - INTERNET_SERVICE_HTTP, //_In_ DWORD dwService, - 0, //_In_ DWORD dwFlags, - 0); //_In_ DWORD_PTR dwContext - if (!hSession) - THROW_LAST_FILE_ERROR(_("Internet access failed."), L"InternetConnect"); - ZEN_ON_SCOPE_EXIT(::InternetCloseHandle(hSession)); - - const wchar_t* acceptTypes[] = { L"*/*", nullptr }; - DWORD requestFlags = INTERNET_FLAG_KEEP_CONNECTION | - INTERNET_FLAG_NO_UI | - INTERNET_FLAG_RELOAD | // - INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS; //relevant for GET only - if (postParams) - requestFlags |= INTERNET_FLAG_NO_AUTO_REDIRECT; //POST would be re-issued as GET during auto-redirect => handle ourselves! - - HINTERNET hRequest = ::HttpOpenRequest(hSession, //_In_ HINTERNET hConnect, - postParams ? L"POST" : L"GET", //_In_ LPCTSTR lpszVerb, - page.c_str(), //_In_ LPCTSTR lpszObjectName, - nullptr, //_In_ LPCTSTR lpszVersion, - nullptr, //_In_ LPCTSTR lpszReferer, - acceptTypes, //_In_ LPCTSTR *lplpszAcceptTypes, - requestFlags, //_In_ DWORD dwFlags, - 0); //_In_ DWORD_PTR dwContext - if (!hRequest) - THROW_LAST_FILE_ERROR(_("Internet access failed."), L"HttpOpenRequest"); - ZEN_ON_SCOPE_EXIT(::InternetCloseHandle(hRequest)); - - const std::wstring headers = postParams ? L"Content-type: application/x-www-form-urlencoded" : L""; - std::string postParamsTmp = postParams ? *postParams : ""; - char* postParamBuf = postParamsTmp.empty() ? nullptr : &*postParamsTmp.begin(); - if (!::HttpSendRequest(hRequest, //_In_ HINTERNET hRequest, - headers.c_str(), //_In_ LPCTSTR lpszHeaders, - headers.size(), //_In_ DWORD dwHeadersLength, - postParamBuf, //_In_ LPVOID lpOptional, - postParamsTmp.size())) //_In_ DWORD dwOptionalLength - THROW_LAST_FILE_ERROR(_("Internet access failed."), L"HttpSendRequest"); - - DWORD sc = 0; - { - DWORD bufLen = sizeof(sc); - if (!::HttpQueryInfo(hRequest, //_In_ HINTERNET hRequest, - HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, //_In_ DWORD dwInfoLevel, - &sc, //_Inout_ LPVOID lpvBuffer, - &bufLen, //_Inout_ LPDWORD lpdwBufferLength, - nullptr)) //_Inout_ LPDWORD lpdwIndex - THROW_LAST_FILE_ERROR(_("Internet access failed."), L"HttpQueryInfo: HTTP_QUERY_STATUS_CODE"); - } - - //http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_Redirection - if (sc / 100 == 3) //e.g. 301, 302, 303, 307... we're not too greedy since we check location, too! - { - if (level < 5) //"A user agent should not automatically redirect a request more than five times, since such redirections usually indicate an infinite loop." - { - DWORD bufLen = 10000; - std::wstring location(bufLen, L'\0'); - if (!::HttpQueryInfo(hRequest, HTTP_QUERY_LOCATION, &*location.begin(), &bufLen, nullptr)) - THROW_LAST_FILE_ERROR(_("Internet access failed."), L"HttpQueryInfo: HTTP_QUERY_LOCATION"); - if (bufLen >= location.size()) //HttpQueryInfo expected to write terminating zero - throw FileError(_("Internet access failed."), L"HttpQueryInfo: HTTP_QUERY_LOCATION, buffer overflow"); - location.resize(bufLen); - - if (!location.empty()) - return sendHttpRequestImpl(location, postParams, level + 1); - } - throw FileError(_("Internet access failed."), L"Unresolvable redirect."); - } - - if (sc != HTTP_STATUS_OK) //200 - throw FileError(_("Internet access failed."), replaceCpy<std::wstring>(L"HTTP status code %x.", L"%x", numberTo<std::wstring>(sc))); - //e.g. 404 - HTTP_STATUS_NOT_FOUND - - std::string buffer; - const DWORD blockSize = 64 * 1024; - //internet says "HttpQueryInfo() + HTTP_QUERY_CONTENT_LENGTH" not supported by all http servers... - for (;;) - { - buffer.resize(buffer.size() + blockSize); - - DWORD bytesRead = 0; - if (!::InternetReadFile(hRequest, //_In_ HINTERNET hFile, - &*(buffer.begin() + buffer.size() - blockSize), //_Out_ LPVOID lpBuffer, - blockSize, //_In_ DWORD dwNumberOfBytesToRead, - &bytesRead)) //_Out_ LPDWORD lpdwNumberOfBytesRead - THROW_LAST_FILE_ERROR(_("Internet access failed."), L"InternetReadFile"); - - if (bytesRead < blockSize) - buffer.resize(buffer.size() - (blockSize - bytesRead)); //caveat: unsigned arithmetics - - if (bytesRead == 0) - return buffer; - } - -#else - assert(std::this_thread::get_id() == mainThreadId); - assert(wxApp::IsMainLoopRunning()); - - wxHTTP webAccess; - webAccess.SetHeader(L"User-Agent", L"FFS-Update-Check"); - webAccess.SetTimeout(10 /*[s]*/); //default: 10 minutes: WTF are these wxWidgets people thinking??? - - if (!webAccess.Connect(server)) //will *not* fail for non-reachable url here! - throw FileError(_("Internet access failed."), L"wxHTTP::Connect"); - - if (postParams) - if (!webAccess.SetPostText(L"application/x-www-form-urlencoded", utfCvrtTo<wxString>(*postParams))) - throw FileError(_("Internet access failed."), L"wxHTTP::SetPostText"); - - std::unique_ptr<wxInputStream> httpStream(webAccess.GetInputStream(page)); //must be deleted BEFORE webAccess is closed - const int sc = webAccess.GetResponse(); - - //http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_Redirection - if (sc / 100 == 3) //e.g. 301, 302, 303, 307... we're not too greedy since we check location, too! - { - if (level < 5) //"A user agent should not automatically redirect a request more than five times, since such redirections usually indicate an infinite loop." - { - const std::wstring newUrl(webAccess.GetHeader(L"Location")); - if (!newUrl.empty()) - return sendHttpRequestImpl(newUrl, postParams, level + 1); - } - throw FileError(_("Internet access failed."), L"Unresolvable redirect."); - } + params.emplace_back("language", !isoLang .empty() ? isoLang : "zz"); + params.emplace_back("country" , !isoCountry.empty() ? isoCountry : "ZZ"); - if (sc != 200) //HTTP_STATUS_OK - throw FileError(_("Internet access failed."), replaceCpy<std::wstring>(L"HTTP status code %x.", L"%x", numberTo<std::wstring>(sc))); - - if (!httpStream || webAccess.GetError() != wxPROTO_NOERR) - throw FileError(_("Internet access failed."), L"wxHTTP::GetError"); - - std::string buffer; - int newValue = 0; - while ((newValue = httpStream->GetC()) != wxEOF) - buffer.push_back(static_cast<char>(newValue)); - return buffer; -#endif -} - - -bool internetIsAlive() //noexcept -{ -#ifdef ZEN_WIN - //::InternetAttemptConnect(0) -> not working as expected: succeeds even when there is no internet connection! - - HINTERNET hInternet = ::InternetOpen(L"FreeFileSync", //_In_ LPCTSTR lpszAgent, - INTERNET_OPEN_TYPE_PRECONFIG, //_In_ DWORD dwAccessType, - nullptr, //_In_ LPCTSTR lpszProxyName, - nullptr, //_In_ LPCTSTR lpszProxyBypass, - 0); //_In_ DWORD dwFlags - if (!hInternet) - return false; - ZEN_ON_SCOPE_EXIT(::InternetCloseHandle(hInternet)); - - //InternetOpenUrl is shortcut for HTTP:GET with InternetConnect + HttpOpenRequest + HttpSendRequest: - HINTERNET hRequest = ::InternetOpenUrl(hInternet, //_In_ HINTERNET hInternet, - L"http://www.google.com/", //_In_ LPCTSTR lpszUrl, - nullptr, //_In_ LPCTSTR lpszHeaders, - 0, //_In_ DWORD dwHeadersLength, - INTERNET_FLAG_KEEP_CONNECTION | - INTERNET_FLAG_NO_UI | - INTERNET_FLAG_RELOAD | - INTERNET_FLAG_NO_AUTO_REDIRECT, //_In_ DWORD dwFlags, - 0); //_In_ DWORD_PTR dwContext - //fails with ERROR_INTERNET_NAME_NOT_RESOLVED if server not found => the server-relative part is checked by HTTP_QUERY_STATUS_CODE!!! - if (!hRequest) - return false; - ZEN_ON_SCOPE_EXIT(::InternetCloseHandle(hRequest)); - - DWORD sc = 0; - { - DWORD bufLen = sizeof(sc); - if (!::HttpQueryInfo(hRequest, //_In_ HINTERNET hRequest, - HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, //_In_ DWORD dwInfoLevel, - &sc, //_Inout_ LPVOID lpvBuffer, - &bufLen, //_Inout_ LPDWORD lpdwBufferLength, - nullptr)) //_Inout_ LPDWORD lpdwIndex - return false; - } - -#else - const wxString server = L"www.google.com"; - const wxString page = L"/"; - - wxHTTP webAccess; - webAccess.SetTimeout(10 /*[s]*/); //default: 10 minutes: WTF are these wxWidgets people thinking??? - - if (!webAccess.Connect(server)) //will *not* fail for non-reachable url here! - return false; - - std::unique_ptr<wxInputStream> httpStream(webAccess.GetInputStream(page)); //call before checking wxHTTP::GetResponse() - const int sc = webAccess.GetResponse(); -#endif - //attention: http://www.google.com/ might redirect to "https" => don't follow, just return "true"!!! - return sc / 100 == 2 || //e.g. 200 - sc / 100 == 3; //e.g. 301, 302, 303, 307... when in doubt, consider internet alive! -} - - -#if 0 //not needed yet: -Wunused-function on clang -inline -std::string sendHttpGet(const std::wstring& url) //throw FileError -{ - return sendHttpRequestImpl(url, nullptr); //throw FileError -} -#endif - - -inline -std::string sendHttpPost(const std::wstring& url, const std::string& postParams) //throw FileError -{ - return sendHttpRequestImpl(url, &postParams); //throw FileError + return params; } @@ -405,14 +155,14 @@ enum GetVerResult }; //access is thread-safe on Windows (WinInet), but not on Linux/OS X (wxWidgets) -GetVerResult getOnlineVersion(std::wstring& version) +GetVerResult getOnlineVersion(const std::vector<std::pair<std::string, std::string>>& postParams, std::wstring& version) { try { //harmonize with wxHTTP: get_latest_version_number.php must be accessible without https!!! - const std::string buffer = sendHttpPost(L"http://www.freefilesync.org/get_latest_version_number.php", geHttpPostParameters()); //throw FileError + const std::string buffer = sendHttpPost(L"http://www.freefilesync.org/get_latest_version_number.php", L"FFS-Update-Check", postParams); //throw FileError version = utfCvrtTo<std::wstring>(buffer); - trim(version); //Windows: remove trailing blank and newline + trim(version); return version.empty() ? GET_VER_PAGE_NOT_FOUND : GET_VER_SUCCESS; //empty version possible?? } catch (const FileError&) @@ -424,11 +174,10 @@ GetVerResult getOnlineVersion(std::wstring& version) std::vector<size_t> parseVersion(const std::wstring& version) { - std::vector<std::wstring> digits = split(version, FFS_VERSION_SEPARATOR); - - std::vector<size_t> output; - std::transform(digits.begin(), digits.end(), std::back_inserter(output), [&](const std::wstring& d) { return stringTo<size_t>(d); }); - return output; + std::vector<size_t> output; + for (const std::wstring& digit : split(version, FFS_VERSION_SEPARATOR)) + output.push_back(stringTo<size_t>(digit)); + return output; } } @@ -461,7 +210,7 @@ void zen::disableUpdateCheck(time_t& lastUpdateCheck) void zen::checkForUpdateNow(wxWindow* parent, std::wstring& lastOnlineVersion) { std::wstring onlineVersion; - switch (getOnlineVersion(onlineVersion)) + switch (getOnlineVersion(geHttpPostParameters(), onlineVersion)) { case GET_VER_SUCCESS: lastOnlineVersion = onlineVersion; @@ -488,7 +237,7 @@ void zen::checkForUpdateNow(wxWindow* parent, std::wstring& lastOnlineVersion) case GET_VER_NO_CONNECTION: showNotificationDialog(parent, DialogInfoType::ERROR2, PopupDialogCfg(). setTitle(_("Check for Program Updates")). - setMainInstructions(_("Unable to connect to www.freefilesync.org."))); + setMainInstructions(replaceCpy(_("Unable to connect to %x."), L"%x", L"www.freefilesync.org."))); break; case GET_VER_PAGE_NOT_FOUND: @@ -520,7 +269,23 @@ bool zen::shouldRunPeriodicUpdateCheck(time_t lastUpdateCheck) } -struct zen::UpdateCheckResult +struct zen::UpdateCheckResultPrep +{ + const std::vector<std::pair<std::string, std::string>> postParameters { geHttpPostParameters() }; +}; + +//run on main thread: +std::shared_ptr<UpdateCheckResultPrep> zen::periodicUpdateCheckPrepare() +{ +#ifdef ZEN_WIN + return std::make_shared<UpdateCheckResultPrep>(); +#else + return nullptr; +#endif +} + + +struct zen::UpdateCheckResultAsync { #ifdef ZEN_WIN GetVerResult versionStatus = GET_VER_PAGE_NOT_FOUND; @@ -528,33 +293,28 @@ struct zen::UpdateCheckResult #endif }; - -std::shared_ptr<UpdateCheckResult> zen::retrieveOnlineVersion() +//run on worker thread: +std::shared_ptr<UpdateCheckResultAsync> zen::periodicUpdateCheckRunAsync(const UpdateCheckResultPrep* resultPrep) { #ifdef ZEN_WIN - try - { - ComInitializer ci; //throw SysError - - auto result = std::make_shared<UpdateCheckResult>(); - result->versionStatus = getOnlineVersion(result->onlineVersion); //access is thread-safe on Windows only! + auto result = std::make_shared<UpdateCheckResultAsync>(); + result->versionStatus = getOnlineVersion(resultPrep->postParameters, result->onlineVersion); //access is thread-safe on Windows only! return result; - } - catch (SysError&) { assert(false); return nullptr; } #else return nullptr; #endif } -void zen::evalPeriodicUpdateCheck(wxWindow* parent, time_t& lastUpdateCheck, std::wstring& lastOnlineVersion, const UpdateCheckResult* result) +//run on main thread: +void zen::periodicUpdateCheckEval(wxWindow* parent, time_t& lastUpdateCheck, std::wstring& lastOnlineVersion, const UpdateCheckResultAsync* resultAsync) { #ifdef ZEN_WIN - const GetVerResult versionStatus = result->versionStatus; - const std::wstring onlineVersion = result->onlineVersion; + const GetVerResult versionStatus = resultAsync->versionStatus; + const std::wstring onlineVersion = resultAsync->onlineVersion; #else std::wstring onlineVersion; - const GetVerResult versionStatus = getOnlineVersion(onlineVersion); + const GetVerResult versionStatus = getOnlineVersion(geHttpPostParameters(), onlineVersion); #endif switch (versionStatus) diff --git a/FreeFileSync/Source/ui/version_check.h b/FreeFileSync/Source/ui/version_check.h index 06bb79f6..6982fe7f 100644 --- a/FreeFileSync/Source/ui/version_check.h +++ b/FreeFileSync/Source/ui/version_check.h @@ -14,19 +14,25 @@ namespace zen { -bool updateCheckActive(time_t lastUpdateCheck); +bool updateCheckActive (time_t lastUpdateCheck); void disableUpdateCheck(time_t& lastUpdateCheck); bool haveNewerVersionOnline(const std::wstring& onlineVersion); //periodic update check: bool shouldRunPeriodicUpdateCheck(time_t lastUpdateCheck); -//long-runing part of the check: thread-safe => run asynchronously -struct UpdateCheckResult; -std::shared_ptr<UpdateCheckResult> retrieveOnlineVersion(); -//eval on main thread: -void evalPeriodicUpdateCheck(wxWindow* parent, time_t& lastUpdateCheck, std::wstring& lastOnlineVersion, const UpdateCheckResult* result); +struct UpdateCheckResultPrep; +struct UpdateCheckResultAsync; + +//run on main thread: +std::shared_ptr<UpdateCheckResultPrep> periodicUpdateCheckPrepare(); +//run on worker thread: (long-running part of the check) +std::shared_ptr<UpdateCheckResultAsync> periodicUpdateCheckRunAsync(const UpdateCheckResultPrep* resultPrep); +//run on main thread: +void periodicUpdateCheckEval(wxWindow* parent, time_t& lastUpdateCheck, std::wstring& lastOnlineVersion, const UpdateCheckResultAsync* resultAsync); + +//---------------------------------------------------------------------------- //call from main thread: void checkForUpdateNow(wxWindow* parent, std::wstring& lastOnlineVersion); diff --git a/FreeFileSync/Source/version/version.h b/FreeFileSync/Source/version/version.h index 59a94db2..2a835304 100644 --- a/FreeFileSync/Source/version/version.h +++ b/FreeFileSync/Source/version/version.h @@ -3,8 +3,8 @@ namespace zen { - const wchar_t ffsVersion[] = L"8.0"; //internal linkage! - const wchar_t FFS_VERSION_SEPARATOR = L'.'; +const wchar_t ffsVersion[] = L"8.1"; //internal linkage! +const wchar_t FFS_VERSION_SEPARATOR = L'.'; } #endif diff --git a/wx+/file_drop.h b/wx+/file_drop.h index f8943788..cfa0ea6c 100644 --- a/wx+/file_drop.h +++ b/wx+/file_drop.h @@ -50,7 +50,7 @@ void MyDlg::OnFilesDropped(FileDropEvent& event); namespace impl { inline -wxEventType createNewEventType() +wxEventType getFileDropEventType() { //inline functions have external linkage by default => this static is also extern, i.e. program wide unique! but defined in a header... ;) static wxEventType dummy = wxNewEventType(); @@ -59,7 +59,7 @@ wxEventType createNewEventType() } //define new event type -const wxEventType EVENT_DROP_FILE = impl::createNewEventType(); +const wxEventType EVENT_DROP_FILE = impl::getFileDropEventType(); class FileDropEvent : public wxCommandEvent { @@ -101,7 +101,7 @@ public: ~DragDropCleanupWindow() { impl::unregisterDragDrop(dropHwnd); } private: - HWND dropHwnd; + const HWND dropHwnd; }; } diff --git a/wx+/grid.cpp b/wx+/grid.cpp index f80c9c73..9d896dc2 100644 --- a/wx+/grid.cpp +++ b/wx+/grid.cpp @@ -88,7 +88,7 @@ void GridData::renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType c rectTmp.x += COLUMN_GAP_LEFT; rectTmp.width -= COLUMN_GAP_LEFT; - drawCellText(dc, rectTmp, getValue(row, colType), true); + drawCellText(dc, rectTmp, getValue(row, colType)); } @@ -122,65 +122,71 @@ void GridData::drawCellBackground(wxDC& dc, const wxRect& rect, bool enabled, bo } -namespace -{ -const wchar_t ELLIPSIS = L'\u2026'; //... - -template <class Function> inline -std::wstring getTruncatedText(const std::wstring& text, Function textFits) +void GridData::drawCellText(wxDC& dc, const wxRect& rect, const std::wstring& text, int alignment) { - if (textFits(text)) - return text; - - //unlike Windows 7 Explorer, we truncate UTF-16 correctly: e.g. CJK-Ideogramm encodes to TWO wchar_t: utfCvrtTo<std::wstring>("\xf0\xa4\xbd\x9c"); - size_t low = 0; //number of unicode chars! - size_t high = unicodeLength(text); // - - for (;;) - { - const size_t middle = (low + high) / 2; + /* + performance notes (Windows): + - wxDC::GetTextExtent() is by far the most expensive call (20x more expensive than wxDC::DrawText()) + - wxDC::DrawLabel() is inefficiently implemented; internally calls: wxDC::GetMultiLineTextExtent(), wxDC::GetTextExtent(), wxDC::DrawText() + - wxDC::GetMultiLineTextExtent() calls wxDC::GetTextExtent() + - wxDC::DrawText also calls wxDC::GetTextExtent()!! + => wxDC::DrawLabel() boils down to 3(!) calls to wxDC::GetTextExtent()!!! + - wxDC::DrawLabel results in GetTextExtent() call even for empty strings!!! + => skip the wxDC::DrawLabel() cruft and directly call wxDC::DrawText! + */ - std::wstring candidate(strBegin(text), findUnicodePos(text, middle)); - candidate += ELLIPSIS; + //truncate large texts and add ellipsis + assert(!contains(text, L"\n")); + const wchar_t ELLIPSIS = L'\u2026'; //"..." + + std::wstring textTrunc = text; + wxSize extentTrunc = dc.GetTextExtent(textTrunc); + if (extentTrunc.GetWidth() > rect.width) + { + //unlike Windows 7 Explorer, we truncate UTF-16 correctly: e.g. CJK-Ideogramm encodes to TWO wchar_t: utfCvrtTo<std::wstring>("\xf0\xa4\xbd\x9c"); + size_t low = 0; //number of unicode chars! + size_t high = unicodeLength(text); // + if (high > 1) + for (;;) + { + const size_t middle = (low + high) / 2; //=> never 0 when "high - low > 1" + if (high - low <= 1) + { + if (low == 0) + { + textTrunc = ELLIPSIS; + extentTrunc = dc.GetTextExtent(ELLIPSIS); + } + break; + } - if (high - low <= 1) - return candidate; + const std::wstring& candidate = std::wstring(strBegin(text), findUnicodePos(text, middle)) + ELLIPSIS; + const wxSize extentCand = dc.GetTextExtent(candidate); //perf: most expensive call of this routine! - if (textFits(candidate)) - low = middle; - else - high = middle; + if (extentCand.GetWidth() <= rect.width) + { + low = middle; + textTrunc = candidate; + extentTrunc = extentCand; + } + else + high = middle; + } } -} - - -void drawTextLabelFitting(wxDC& dc, const std::wstring& text, const wxRect& rect, int alignment) -{ - RecursiveDcClipper clip(dc, rect); //wxDC::DrawLabel doesn't care about width, WTF? - - /* - performance notes: - wxDC::DrawLabel() is implemented in terms of both wxDC::GetMultiLineTextExtent() and wxDC::DrawText() - wxDC::GetMultiLineTextExtent() is implemented in terms of wxDC::GetTextExtent() - - average total times: - Windows Linux - single wxDC::DrawText() 7s 50s - wxDC::DrawLabel() + 10s 90s - repeated GetTextExtent() - */ - //truncate large texts and add ellipsis - auto textFits = [&](const std::wstring& phrase) { return dc.GetTextExtent(phrase).GetWidth() <= rect.GetWidth(); }; - dc.DrawLabel(getTruncatedText(text, textFits), rect, alignment); -} -} + wxPoint pt = rect.GetTopLeft(); + if (alignment & wxALIGN_RIGHT) //note: wxALIGN_LEFT == 0! + pt.x += rect.width - extentTrunc.GetWidth(); + else if (alignment & wxALIGN_CENTER_HORIZONTAL) + pt.x += (rect.width - extentTrunc.GetWidth()) / 2; + if (alignment & wxALIGN_BOTTOM) //note: wxALIGN_TOP == 0! + pt.y += rect.height - extentTrunc.GetHeight(); + else if (alignment & wxALIGN_CENTER_VERTICAL) + pt.y += (rect.height - extentTrunc.GetHeight()) / 2; -void GridData::drawCellText(wxDC& dc, const wxRect& rect, const std::wstring& text, bool enabled, int alignment) -{ - wxDCTextColourChanger dummy(dc, enabled ? dc.GetTextForeground() : wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT)); - drawTextLabelFitting(dc, text, rect, alignment); + RecursiveDcClipper clip(dc, rect); + dc.DrawText(textTrunc, pt); } @@ -226,7 +232,7 @@ void GridData::drawColumnLabelBackground(wxDC& dc, const wxRect& rect, bool high void GridData::drawColumnLabelText(wxDC& dc, const wxRect& rect, const std::wstring& text) { wxDCTextColourChanger dummy(dc, getColorLabelText()); //accessibility: always set both foreground AND background colors! - drawTextLabelFitting(dc, text, rect, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); + drawCellText(dc, rect, text, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); } //---------------------------------------------------------------------------------------------------------------- @@ -524,10 +530,8 @@ private: //label text wxRect textRect = rect; textRect.Deflate(1); - { - RecursiveDcClipper clip(dc, textRect); //wxDC::DrawLabel doesn't care about with, WTF? - dc.DrawLabel(formatRow(row), textRect, wxALIGN_CENTRE); - } + + GridData::drawCellText(dc, textRect, formatRow(row), wxALIGN_CENTRE); //border lines { @@ -1668,7 +1672,7 @@ void Grid::scrollDelta(int deltaX, int deltaY) scrollPosX = std::max(0, scrollPosX); //wxScrollHelper::Scroll() will exit prematurely if input happens to be "-1"! scrollPosY = std::max(0, scrollPosY); // - Scroll(scrollPosX, scrollPosY); + Scroll(scrollPosX, scrollPosY); //internally calls wxWindows::Update()! updateWindowSizes(); //may show horizontal scroll bar } @@ -2014,7 +2018,7 @@ void Grid::makeRowVisible(size_t row) if (clientPosY < 0) { const int scrollPosY = labelRect.y / pixelsPerUnitY; - Scroll(scrollPosX, scrollPosY); + Scroll(scrollPosX, scrollPosY); //internally calls wxWindows::Update()! updateWindowSizes(); //may show horizontal scroll bar } else if (clientPosY + labelRect.height > rowLabelWin_->GetClientSize().GetHeight()) @@ -2076,7 +2080,7 @@ void Grid::scrollTo(size_t row) if (scrollPosYOld != scrollPosYNew) //support polling { - Scroll(scrollPosXOld, scrollPosYNew); + Scroll(scrollPosXOld, scrollPosYNew); //internally calls wxWindows::Update()! updateWindowSizes(); //may show horizontal scroll bar Refresh(); } @@ -102,7 +102,7 @@ public: virtual size_t getRowCount() const = 0; - //cell area + //cell area: virtual std::wstring getValue(size_t row, ColumnType colType) const = 0; virtual void renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected); //default implementation virtual void renderCell (wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected, HoverArea rowHover); @@ -110,17 +110,17 @@ public: virtual std::wstring getToolTip (size_t row, ColumnType colType) const { return std::wstring(); } virtual HoverArea getRowMouseHover(size_t row, ColumnType colType, int cellRelativePosX, int cellWidth) { return HoverArea::NONE; } - //label area + //label area: virtual std::wstring getColumnLabel(ColumnType colType) const = 0; virtual void renderColumnLabel(Grid& grid, wxDC& dc, const wxRect& rect, ColumnType colType, bool highlighted); //default implementation virtual std::wstring getToolTip(ColumnType colType) const { return std::wstring(); } static const int COLUMN_GAP_LEFT; //for left-aligned text -protected: //optional helper routines + //optional helper routines: + static void drawCellText (wxDC& dc, const wxRect& rect, const std::wstring& text, int alignment = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); static wxRect drawCellBorder (wxDC& dc, const wxRect& rect); //returns inner rectangle static void drawCellBackground(wxDC& dc, const wxRect& rect, bool enabled, bool selected, const wxColor& backgroundColor); - static void drawCellText (wxDC& dc, const wxRect& rect, const std::wstring& text, bool enabled, int alignment = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); static wxRect drawColumnLabelBorder (wxDC& dc, const wxRect& rect); //returns inner rectangle static void drawColumnLabelBackground(wxDC& dc, const wxRect& rect, bool highlighted); diff --git a/wx+/http.cpp b/wx+/http.cpp new file mode 100644 index 00000000..f73587b3 --- /dev/null +++ b/wx+/http.cpp @@ -0,0 +1,302 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#include "http.h" +#ifdef ZEN_WIN + #include <zen/win.h> //tame wininet.h include + #include <wininet.h> +#endif + +#if defined ZEN_LINUX || defined ZEN_MAC + #include <zen/thread.h> //std::thread::id + #include <wx/protocol/http.h> +#endif + +using namespace zen; + + +namespace +{ +#ifdef ZEN_WIN + #if defined NDEBUG && defined __WXWINDOWS__ + #error don not use wxWidgets for this component! + #endif +#else + #ifndef NDEBUG + const std::thread::id mainThreadId = std::this_thread::get_id(); + #endif +#endif + + +std::string sendHttpRequestImpl(const std::wstring& url, //throw FileError + const std::wstring& userAgent, + const std::string* postParams, //issue POST if bound, GET otherwise + int level = 0) +{ + assert(!startsWith(makeUpperCopy(url), L"HTTPS:")); //not supported by wxHTTP! + const std::wstring urlFmt = startsWith(makeUpperCopy(url), L"HTTP://") ? afterFirst(url, L"://", IF_MISSING_RETURN_NONE) : url; + const std::wstring server = beforeFirst(urlFmt, L'/', IF_MISSING_RETURN_ALL); + const std::wstring page = L'/' + afterFirst(urlFmt, L'/', IF_MISSING_RETURN_NONE); + +#ifdef ZEN_WIN + //WinInet: 1. uses IE proxy settings! :) 2. follows HTTP redirects by default 3. swallows HTTPS if needed + HINTERNET hInternet = ::InternetOpen(userAgent.c_str(), //_In_ LPCTSTR lpszAgent, + INTERNET_OPEN_TYPE_PRECONFIG, //_In_ DWORD dwAccessType, + nullptr, //_In_ LPCTSTR lpszProxyName, + nullptr, //_In_ LPCTSTR lpszProxyBypass, + 0); //_In_ DWORD dwFlags + if (!hInternet) + THROW_LAST_FILE_ERROR(_("Internet access failed."), L"InternetOpen"); + ZEN_ON_SCOPE_EXIT(::InternetCloseHandle(hInternet)); + + HINTERNET hSession = ::InternetConnect(hInternet, //_In_ HINTERNET hInternet, + server.c_str(), //_In_ LPCTSTR lpszServerName, + INTERNET_DEFAULT_HTTP_PORT, //_In_ INTERNET_PORT nServerPort, + nullptr, //_In_ LPCTSTR lpszUsername, + nullptr, //_In_ LPCTSTR lpszPassword, + INTERNET_SERVICE_HTTP, //_In_ DWORD dwService, + 0, //_In_ DWORD dwFlags, + 0); //_In_ DWORD_PTR dwContext + if (!hSession) + THROW_LAST_FILE_ERROR(_("Internet access failed."), L"InternetConnect"); + ZEN_ON_SCOPE_EXIT(::InternetCloseHandle(hSession)); + + const wchar_t* acceptTypes[] = { L"*/*", nullptr }; + DWORD requestFlags = INTERNET_FLAG_KEEP_CONNECTION | + INTERNET_FLAG_NO_UI | + INTERNET_FLAG_RELOAD | // + INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS; //relevant for GET only + if (postParams) + requestFlags |= INTERNET_FLAG_NO_AUTO_REDIRECT; //POST would be re-issued as GET during auto-redirect => handle ourselves! + + HINTERNET hRequest = ::HttpOpenRequest(hSession, //_In_ HINTERNET hConnect, + postParams ? L"POST" : L"GET", //_In_ LPCTSTR lpszVerb, + page.c_str(), //_In_ LPCTSTR lpszObjectName, + nullptr, //_In_ LPCTSTR lpszVersion, + nullptr, //_In_ LPCTSTR lpszReferer, + acceptTypes, //_In_ LPCTSTR *lplpszAcceptTypes, + requestFlags, //_In_ DWORD dwFlags, + 0); //_In_ DWORD_PTR dwContext + if (!hRequest) + THROW_LAST_FILE_ERROR(_("Internet access failed."), L"HttpOpenRequest"); + ZEN_ON_SCOPE_EXIT(::InternetCloseHandle(hRequest)); + + const std::wstring headers = postParams ? L"Content-type: application/x-www-form-urlencoded" : L""; + std::string postParamsTmp = postParams ? *postParams : ""; + char* postParamBuf = postParamsTmp.empty() ? nullptr : &*postParamsTmp.begin(); + if (!::HttpSendRequest(hRequest, //_In_ HINTERNET hRequest, + headers.c_str(), //_In_ LPCTSTR lpszHeaders, + static_cast<DWORD>(headers.size()), //_In_ DWORD dwHeadersLength, + postParamBuf, //_In_ LPVOID lpOptional, + static_cast<DWORD>(postParamsTmp.size()))) //_In_ DWORD dwOptionalLength + THROW_LAST_FILE_ERROR(_("Internet access failed."), L"HttpSendRequest"); + + DWORD sc = 0; + { + DWORD bufLen = sizeof(sc); + if (!::HttpQueryInfo(hRequest, //_In_ HINTERNET hRequest, + HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, //_In_ DWORD dwInfoLevel, + &sc, //_Inout_ LPVOID lpvBuffer, + &bufLen, //_Inout_ LPDWORD lpdwBufferLength, + nullptr)) //_Inout_ LPDWORD lpdwIndex + THROW_LAST_FILE_ERROR(_("Internet access failed."), L"HttpQueryInfo: HTTP_QUERY_STATUS_CODE"); + } + + //http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_Redirection + if (sc / 100 == 3) //e.g. 301, 302, 303, 307... we're not too greedy since we check location, too! + { + if (level < 5) //"A user agent should not automatically redirect a request more than five times, since such redirections usually indicate an infinite loop." + { + DWORD bufLen = 10000; + std::wstring location(bufLen, L'\0'); + if (!::HttpQueryInfo(hRequest, HTTP_QUERY_LOCATION, &*location.begin(), &bufLen, nullptr)) + THROW_LAST_FILE_ERROR(_("Internet access failed."), L"HttpQueryInfo: HTTP_QUERY_LOCATION"); + if (bufLen >= location.size()) //HttpQueryInfo expected to write terminating zero + throw FileError(_("Internet access failed."), L"HttpQueryInfo: HTTP_QUERY_LOCATION, buffer overflow"); + location.resize(bufLen); + + if (!location.empty()) + return sendHttpRequestImpl(location, userAgent, postParams, level + 1); + } + throw FileError(_("Internet access failed."), L"Unresolvable redirect."); + } + + if (sc != HTTP_STATUS_OK) //200 + throw FileError(_("Internet access failed."), replaceCpy<std::wstring>(L"HTTP status code %x.", L"%x", numberTo<std::wstring>(sc))); + //e.g. 404 - HTTP_STATUS_NOT_FOUND + + std::string buffer; + const DWORD blockSize = 64 * 1024; + //internet says "HttpQueryInfo() + HTTP_QUERY_CONTENT_LENGTH" not supported by all http servers... + for (;;) + { + buffer.resize(buffer.size() + blockSize); + + DWORD bytesRead = 0; + if (!::InternetReadFile(hRequest, //_In_ HINTERNET hFile, + &*(buffer.begin() + buffer.size() - blockSize), //_Out_ LPVOID lpBuffer, + blockSize, //_In_ DWORD dwNumberOfBytesToRead, + &bytesRead)) //_Out_ LPDWORD lpdwNumberOfBytesRead + THROW_LAST_FILE_ERROR(_("Internet access failed."), L"InternetReadFile"); + + if (bytesRead > blockSize) //better safe than sorry + throw FileError(_("Internet access failed."), L"InternetReadFile: buffer overflow."); + + if (bytesRead < blockSize) + buffer.resize(buffer.size() - (blockSize - bytesRead)); //caveat: unsigned arithmetics + + if (bytesRead == 0) + return buffer; + } + +#else + assert(std::this_thread::get_id() == mainThreadId); + assert(wxApp::IsMainLoopRunning()); + + wxHTTP webAccess; + webAccess.SetHeader(L"User-Agent", userAgent); + webAccess.SetTimeout(10 /*[s]*/); //default: 10 minutes: WTF are these wxWidgets people thinking??? + + if (!webAccess.Connect(server)) //will *not* fail for non-reachable url here! + throw FileError(_("Internet access failed."), L"wxHTTP::Connect"); + + if (postParams) + if (!webAccess.SetPostText(L"application/x-www-form-urlencoded", utfCvrtTo<wxString>(*postParams))) + throw FileError(_("Internet access failed."), L"wxHTTP::SetPostText"); + + std::unique_ptr<wxInputStream> httpStream(webAccess.GetInputStream(page)); //must be deleted BEFORE webAccess is closed + const int sc = webAccess.GetResponse(); + + //http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_Redirection + if (sc / 100 == 3) //e.g. 301, 302, 303, 307... we're not too greedy since we check location, too! + { + if (level < 5) //"A user agent should not automatically redirect a request more than five times, since such redirections usually indicate an infinite loop." + { + const std::wstring newUrl(webAccess.GetHeader(L"Location")); + if (!newUrl.empty()) + return sendHttpRequestImpl(newUrl, userAgent, postParams, level + 1); + } + throw FileError(_("Internet access failed."), L"Unresolvable redirect."); + } + + if (sc != 200) //HTTP_STATUS_OK + throw FileError(_("Internet access failed."), replaceCpy<std::wstring>(L"HTTP status code %x.", L"%x", numberTo<std::wstring>(sc))); + + if (!httpStream || webAccess.GetError() != wxPROTO_NOERR) + throw FileError(_("Internet access failed."), L"wxHTTP::GetError"); + + std::string buffer; + int newValue = 0; + while ((newValue = httpStream->GetC()) != wxEOF) + buffer.push_back(static_cast<char>(newValue)); + return buffer; +#endif +} + + +//encode into "application/x-www-form-urlencoded" +std::string urlencode(const std::string& str) +{ + std::string out; + for (const char c : str) //follow PHP spec: https://github.com/php/php-src/blob/master/ext/standard/url.c#L500 + if (c == ' ') + out += '+'; + else if (('0' <= c && c <= '9') || + ('A' <= c && c <= 'Z') || + ('a' <= c && c <= 'z') || + c == '-' || c == '.' || c == '_') //note: "~" is encoded by PHP! + out += c; + else + { + const char hexDigits[]= "0123456789ABCDEF"; + out += '%'; + out += hexDigits[static_cast<unsigned char>(c) / 16]; + out += hexDigits[static_cast<unsigned char>(c) % 16]; + } + return out; +} +} + + +std::string zen::sendHttpPost(const std::wstring& url, const std::wstring& userAgent, const std::vector<std::pair<std::string, std::string>>& postParams) //throw FileError +{ + //convert post parameters into "application/x-www-form-urlencoded" + std::string flatParams; + for (const auto& pair : postParams) + flatParams += urlencode(pair.first) + '=' + urlencode(pair.second) + '&'; + //encode both key and value: https://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1 + if (!flatParams.empty()) + flatParams.pop_back(); + + return sendHttpRequestImpl(url, userAgent, &flatParams); //throw FileError +} + + +std::string zen::sendHttpGet(const std::wstring& url, const std::wstring& userAgent) //throw FileError +{ + return sendHttpRequestImpl(url, userAgent, nullptr); //throw FileError +} + + +bool zen::internetIsAlive() //noexcept +{ +#ifdef ZEN_WIN + //::InternetAttemptConnect(0) -> not working as expected: succeeds even when there is no internet connection! + + HINTERNET hInternet = ::InternetOpen(L"FreeFileSync", //_In_ LPCTSTR lpszAgent, + INTERNET_OPEN_TYPE_PRECONFIG, //_In_ DWORD dwAccessType, + nullptr, //_In_ LPCTSTR lpszProxyName, + nullptr, //_In_ LPCTSTR lpszProxyBypass, + 0); //_In_ DWORD dwFlags + if (!hInternet) + return false; + ZEN_ON_SCOPE_EXIT(::InternetCloseHandle(hInternet)); + + //InternetOpenUrl is shortcut for HTTP:GET with InternetConnect + HttpOpenRequest + HttpSendRequest: + HINTERNET hRequest = ::InternetOpenUrl(hInternet, //_In_ HINTERNET hInternet, + L"http://www.google.com/", //_In_ LPCTSTR lpszUrl, + nullptr, //_In_ LPCTSTR lpszHeaders, + 0, //_In_ DWORD dwHeadersLength, + INTERNET_FLAG_KEEP_CONNECTION | + INTERNET_FLAG_NO_UI | + INTERNET_FLAG_RELOAD | + INTERNET_FLAG_NO_AUTO_REDIRECT, //_In_ DWORD dwFlags, + 0); //_In_ DWORD_PTR dwContext + //fails with ERROR_INTERNET_NAME_NOT_RESOLVED if server not found => the server-relative part is checked by HTTP_QUERY_STATUS_CODE!!! + if (!hRequest) + return false; + ZEN_ON_SCOPE_EXIT(::InternetCloseHandle(hRequest)); + + DWORD sc = 0; + { + DWORD bufLen = sizeof(sc); + if (!::HttpQueryInfo(hRequest, //_In_ HINTERNET hRequest, + HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, //_In_ DWORD dwInfoLevel, + &sc, //_Inout_ LPVOID lpvBuffer, + &bufLen, //_Inout_ LPDWORD lpdwBufferLength, + nullptr)) //_Inout_ LPDWORD lpdwIndex + return false; + } + +#else + assert(std::this_thread::get_id() == mainThreadId); + + const wxString server = L"www.google.com"; + const wxString page = L"/"; + + wxHTTP webAccess; + webAccess.SetTimeout(10 /*[s]*/); //default: 10 minutes: WTF are these wxWidgets people thinking??? + + if (!webAccess.Connect(server)) //will *not* fail for non-reachable url here! + return false; + + std::unique_ptr<wxInputStream> httpStream(webAccess.GetInputStream(page)); //call before checking wxHTTP::GetResponse() + const int sc = webAccess.GetResponse(); +#endif + //attention: http://www.google.com/ might redirect to "https" => don't follow, just return "true"!!! + return sc / 100 == 2 || //e.g. 200 + sc / 100 == 3; //e.g. 301, 302, 303, 307... when in doubt, consider internet alive! +} diff --git a/wx+/http.h b/wx+/http.h new file mode 100644 index 00000000..90375f42 --- /dev/null +++ b/wx+/http.h @@ -0,0 +1,25 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#ifndef HTTP_h_879083425703425702 +#define HTTP_h_879083425703425702 + +#include <zen/file_error.h> + +namespace zen +{ +/* + TREAD-SAFETY + ------------ + Windows: WinInet-based => may be called from worker thread + Linux: wxWidgets-based => don't call from worker thread +*/ +std::string sendHttpPost(const std::wstring& url, const std::wstring& userAgent, const std::vector<std::pair<std::string, std::string>>& postParams); //throw FileError +std::string sendHttpGet (const std::wstring& url, const std::wstring& userAgent); //throw FileError +bool internetIsAlive(); //noexcept +} + +#endif //HTTP_h_879083425703425702 diff --git a/wx+/image_resources.cpp b/wx+/image_resources.cpp index 7c6de43f..d6c6ab68 100644 --- a/wx+/image_resources.cpp +++ b/wx+/image_resources.cpp @@ -83,7 +83,7 @@ void GlobalResources::init(const Zstring& filepath) wxZipInputStream streamIn(input, wxConvUTF8); //do NOT rely on wxConvLocal! On failure shows unhelpful popup "Cannot convert from the charset 'Unknown encoding (-1)'!" - while (true) + for (;;) { std::unique_ptr<wxZipEntry> entry(streamIn.GetNextEntry()); //take ownership! if (!entry) @@ -7,34 +7,17 @@ #ifndef RTL_H_0183487180058718273432148 #define RTL_H_0183487180058718273432148 -//#include <memory> #include <zen/optional.h> #include <wx/dcmemory.h> -#include <wx/dcmirror.h> #include <wx/image.h> -#include <wx/icon.h> #include <wx/app.h> namespace zen { //functions supporting right-to-left GUI layout - -void drawBitmapRtlMirror(wxDC& dc, - const wxBitmap& image, - const wxRect& rect, - int alignment, - Opt<wxBitmap>& buffer); //mirror image if layout is RTL + fix some strange wxDC::Blit bug on RTL - -void drawBitmapRtlNoMirror(wxDC& dc, //wxDC::DrawLabel does already NOT mirror by default (but does a crappy job at it, surprise) - const wxBitmap& image, - const wxRect& rect, - int alignment, - Opt<wxBitmap>& buffer); - -void drawIconRtlNoMirror(wxDC& dc, //wxDC::DrawIcon DOES mirror by default - const wxIcon& icon, - const wxPoint& pt, - Opt<wxBitmap>& buffer); +void drawBitmapRtlMirror (wxDC& dc, const wxBitmap& image, const wxRect& rect, int alignment, Opt<wxBitmap>& buffer); +void drawBitmapRtlNoMirror(wxDC& dc, const wxBitmap& image, const wxRect& rect, int alignment); +//wxDC::DrawIcon DOES mirror by default -> implement RTL support when needed wxBitmap mirrorIfRtl(const wxBitmap& bmp); @@ -48,10 +31,31 @@ wxBitmap mirrorIfRtl(const wxBitmap& bmp); //---------------------- implementation ------------------------ -namespace +namespace impl { -template <class DrawImageFun> -void drawRtlImpl(wxDC& dc, const wxRect& rect, Opt<wxBitmap>& buffer, bool doMirror, DrawImageFun draw) +//don't use wxDC::DrawLabel: it results in expensive GetTextExtent() call even when passing an empty string!!! +//also avoid wxDC::DrawLabel 1-off alignment bugs +inline +void drawBitmapAligned(wxDC& dc, const wxBitmap& image, const wxRect& rect, int alignment) +{ + wxPoint pt = rect.GetTopLeft(); + if (alignment & wxALIGN_RIGHT) //note: wxALIGN_LEFT == 0! + pt.x += rect.width - image.GetWidth(); + else if (alignment & wxALIGN_CENTER_HORIZONTAL) + pt.x += (rect.width - image.GetWidth()) / 2; + + if (alignment & wxALIGN_BOTTOM) //note: wxALIGN_TOP == 0! + pt.y += rect.height - image.GetHeight(); + else if (alignment & wxALIGN_CENTER_VERTICAL) + pt.y += (rect.height - image.GetHeight()) / 2; + + dc.DrawBitmap(image, pt); +} +} + + +inline +void drawBitmapRtlMirror(wxDC& dc, const wxBitmap& image, const wxRect& rect, int alignment, Opt<wxBitmap>& buffer) { if (dc.GetLayoutDirection() == wxLayout_RightToLeft) { @@ -61,50 +65,20 @@ void drawRtlImpl(wxDC& dc, const wxRect& rect, Opt<wxBitmap>& buffer, bool doMir wxMemoryDC memDc(*buffer); memDc.Blit(wxPoint(0, 0), rect.GetSize(), &dc, rect.GetTopLeft()); //blit in: background is mirrored due to memDc, dc having different layout direction! - if (!doMirror) - { - *buffer = wxBitmap(buffer->ConvertToImage().Mirror()); - memDc.SelectObject(*buffer); - } - - draw(memDc, wxRect(0, 0, rect.width, rect.height)); - //note: we cannot simply use memDc.SetLayoutDirection(wxLayout_RightToLeft) due to some strange 1 pixel bug! so it's a quadruple mirror! :> - - if (!doMirror) - { - *buffer = wxBitmap(buffer->ConvertToImage().Mirror()); - memDc.SelectObject(*buffer); - } + impl::drawBitmapAligned(memDc, image, wxRect(0, 0, rect.width, rect.height), alignment); + //note: we cannot simply use memDc.SetLayoutDirection(wxLayout_RightToLeft) due to some strange 1 pixel bug! dc.Blit(rect.GetTopLeft(), rect.GetSize(), &memDc, wxPoint(0, 0)); //blit out: mirror once again } else - draw(dc, rect); -} -} - - -inline -void drawBitmapRtlMirror(wxDC& dc, const wxBitmap& image, const wxRect& rect, int alignment, Opt<wxBitmap>& buffer) -{ - return drawRtlImpl(dc, rect, buffer, true, [&](wxDC& dc2, const wxRect& rect2) { dc2.DrawLabel(wxString(), image, rect2, alignment); }); + impl::drawBitmapAligned(dc, image, rect, alignment); } -inline -void drawBitmapRtlNoMirror(wxDC& dc, const wxBitmap& image, const wxRect& rect, int alignment, Opt<wxBitmap>& buffer) -{ - if (dc.GetLayoutDirection() == wxLayout_RightToLeft) - if ((alignment & wxALIGN_CENTER_HORIZONTAL) == 0) //we still *do* want to mirror alignment! - alignment ^= wxALIGN_RIGHT; - static_assert(wxALIGN_LEFT == 0, "doh"); - return drawRtlImpl(dc, rect, buffer, false, [&](wxDC& dc2, const wxRect& rect2) { dc2.DrawLabel(wxString(), image, rect2, alignment); }); -} inline -void drawIconRtlNoMirror(wxDC& dc, const wxIcon& icon, const wxPoint& pt, Opt<wxBitmap>& buffer) +void drawBitmapRtlNoMirror(wxDC& dc, const wxBitmap& image, const wxRect& rect, int alignment) { - wxRect rect(pt.x, pt.y, icon.GetWidth(), icon.GetHeight()); - return drawRtlImpl(dc, rect, buffer, false, [&](wxDC& dc2, const wxRect& rect2) { dc2.DrawIcon(icon, rect2.GetTopLeft()); }); + return impl::drawBitmapAligned(dc, image, rect, alignment); //wxDC::DrawBitmap does NOT mirror by default } diff --git a/zen/build_info.h b/zen/build_info.h index 09e1c5a7..c430a49a 100644 --- a/zen/build_info.h +++ b/zen/build_info.h @@ -12,7 +12,7 @@ namespace zen //determine build info: defines ZEN_BUILD_32BIT or ZEN_BUILD_64BIT #ifdef ZEN_WIN - #ifdef _WIN64 + #ifdef _WIN64 //_WIN32 is defined by the compiler for both 32 and 64 bit builds, unlike _WIN64 #define ZEN_BUILD_64BIT #else #define ZEN_BUILD_32BIT diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp index 702a8e32..bb78939f 100644 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -564,9 +564,12 @@ DirWatcher::DirWatcher(const Zstring& dirPath) : baseDirPath(dirPath), pimpl_(std::make_unique<Pimpl>()) { - CFStringRef dirpathCf = osx::createCFString(baseDirPath.c_str()); //returns nullptr on error - if (!dirpathCf) - throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath)), L"Function call failed: createCFString"); //no error code documented! + CFStringRef dirpathCf = nullptr; + try + { + dirpathCf = osx::createCFString(baseDirPath.c_str()); //throw SysError + } + catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath)), e.toString()); } ZEN_ON_SCOPE_EXIT(::CFRelease(dirpathCf)); CFArrayRef dirpathCfArray = ::CFArrayCreate(nullptr, //CFAllocatorRef allocator, diff --git a/zen/file_access.cpp b/zen/file_access.cpp index ea13d381..78da0220 100644 --- a/zen/file_access.cpp +++ b/zen/file_access.cpp @@ -167,6 +167,31 @@ bool isFatDrive(const Zstring& filePath) //noexcept return &buffer[0] == Zstring(L"FAT") || &buffer[0] == Zstring(L"FAT32"); } + + +#ifdef ZEN_WIN_VISTA_AND_LATER +bool isFatDrive(HANDLE hFile) //noexcept +{ + const DWORD bufferSize = MAX_PATH + 1; //"The length of the file system name buffer, in TCHARs. The maximum buffer size is MAX_PATH + 1" + std::vector<wchar_t> buffer(bufferSize); + + if (!::GetVolumeInformationByHandleW(hFile, //_In_ HANDLE hFile, + nullptr, //_Out_writes_opt_(nVolumeNameSize) LPWSTR lpVolumeNameBuffer, + 0, //_In_ DWORD nVolumeNameSize, + nullptr, //_Out_opt_ LPDWORD lpVolumeSerialNumber, + nullptr, //_Out_opt_ LPDWORD lpMaximumComponentLength, + nullptr, //_Out_opt_ LPDWORD lpFileSystemFlags, + &buffer[0], //_Out_writes_opt_(nFileSystemNameSize) LPWSTR lpFileSystemNameBuffer, + bufferSize)) //_In_ DWORD nFileSystemNameSize + { + assert(false); + return false; + } + + return &buffer[0] == Zstring(L"FAT") || + &buffer[0] == Zstring(L"FAT32"); +} +#endif #endif } @@ -247,6 +272,43 @@ std::uint64_t zen::getFreeDiskSpace(const Zstring& path) //throw FileError, retu } +VolumeId zen::getVolumeId(const Zstring& itemPath) //throw FileError +{ +#ifdef ZEN_WIN + //this works for: + //- root paths "C:\", "D:\" + //- network shares: \\share\dirname + //- indirection: subst S: %USERPROFILE% + // -> GetVolumePathName() + GetVolumeInformation() OTOH incorrectly resolves "S:\Desktop\somedir" to "S:\Desktop\" - nice try... + const HANDLE hItem = ::CreateFile(zen::applyLongPathPrefix(itemPath).c_str(), //_In_ LPCTSTR lpFileName, + 0, //_In_ DWORD dwDesiredAccess, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode, + nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, + OPEN_EXISTING, //_In_ DWORD dwCreationDisposition, + // FILE_FLAG_OPEN_REPARSE_POINT -> no, we follow symlinks! + FILE_FLAG_BACKUP_SEMANTICS, //_In_ DWORD dwFlagsAndAttributes, + /*needed to open a directory*/ + nullptr); //_In_opt_ HANDLE hTemplateFile + if (hItem == INVALID_HANDLE_VALUE) + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"CreateFile"); + ZEN_ON_SCOPE_EXIT(::CloseHandle(hItem)); + + BY_HANDLE_FILE_INFORMATION fileInfo = {}; + if (!::GetFileInformationByHandle(hItem, &fileInfo)) + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"GetFileInformationByHandle"); + + return fileInfo.dwVolumeSerialNumber; + +#elif defined ZEN_LINUX || defined ZEN_MAC + struct ::stat fileInfo = {}; + if (::stat(itemPath.c_str(), &fileInfo) != 0) + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"stat"); + + return fileInfo.st_dev; +#endif +} + + bool zen::removeFile(const Zstring& filePath) //throw FileError { #ifdef ZEN_WIN @@ -398,7 +460,7 @@ void renameFile_sub(const Zstring& pathSource, const Zstring& pathTarget) //thro if (!::MoveFileEx(pathSourceFmt.c_str(), //__in LPCTSTR lpExistingFileName, pathTargetFmt.c_str(), //__in_opt LPCTSTR lpNewFileName, - 0)) //__in DWORD dwFlags + 0)) //__in DWORD dwFlags { DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls! @@ -412,7 +474,7 @@ void renameFile_sub(const Zstring& pathSource, const Zstring& pathTarget) //thro //try again... if (::MoveFileEx(pathSourceFmt.c_str(), //__in LPCTSTR lpExistingFileName, pathTargetFmt.c_str(), //__in_opt LPCTSTR lpNewFileName, - 0)) //__in DWORD dwFlags + 0)) //__in DWORD dwFlags { //(try to) restore file attributes ::SetFileAttributes(pathTargetFmt.c_str(), oldAttr); //don't handle error @@ -623,7 +685,7 @@ void setFileTimeByHandle(HANDLE hFile, //throw FileError nullptr, //__in_opt const FILETIME *lpLastAccessTime, &lastWriteTime)) //__in_opt const FILETIME *lpLastWriteTime { - DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls! + const DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls! auto toLargeInteger = [](const FILETIME& ft) -> LARGE_INTEGER { @@ -667,8 +729,7 @@ void setFileTimeByHandle(HANDLE hFile, //throw FileError setFileInfo(basicInfo2); //throw FileError } catch (FileError&) {} - - ec = ERROR_SUCCESS; + return; } } #endif @@ -676,7 +737,11 @@ void setFileTimeByHandle(HANDLE hFile, //throw FileError std::wstring errorMsg = replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(filePath)); //add more meaningful message: FAT accepts only a subset of the NTFS date range - if (ec == ERROR_INVALID_PARAMETER && isFatDrive(filePath)) +#ifdef ZEN_WIN_VISTA_AND_LATER + if (ec == ERROR_INVALID_PARAMETER && isFatDrive(hFile)) +#else + if (ec == ERROR_INVALID_PARAMETER && isFatDrive(filePath)) +#endif { //let's not fail due to an invalid creation time on FAT: http://www.freefilesync.org/forum/viewtopic.php?t=2278 if (creationTime) //retry (single-level recursion at most!) @@ -718,8 +783,7 @@ void setFileTimeByHandle(HANDLE hFile, //throw FileError } } - if (ec != ERROR_SUCCESS) - throw FileError(errorMsg, formatSystemError(L"SetFileTime", ec)); + throw FileError(errorMsg, formatSystemError(L"SetFileTime", ec)); } } @@ -1398,7 +1462,7 @@ void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath, //- automatically copies symbolic links if encountered: unfortunately it doesn't copy symlinks over network shares but silently creates empty folders instead (on XP)! //- it isn't able to copy most junctions because of missing permissions (although target path can be retrieved alternatively!) if (!::CreateDirectory(applyLongPathPrefixCreateDir(targetPath).c_str(), //__in LPCTSTR lpPathName, - nullptr)) //__in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes + nullptr)) //__in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes { DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls! @@ -1473,7 +1537,7 @@ void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath, if (::GetFileInformationByHandle(hDirSrc, &dirInfo)) { ::SetFileAttributes(applyLongPathPrefix(targetPath).c_str(), dirInfo.dwFileAttributes); - //copy "read-only and system attributes": http://blogs.msdn.com/b/oldnewthing/archive/2003/09/30/55100.aspx + //copy "read-only and system attributes": https://blogs.msdn.microsoft.com/oldnewthing/20030930-00/?p=42353/ const bool isEncrypted = (dirInfo.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) != 0; const bool isCompressed = (dirInfo.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0; @@ -1544,7 +1608,7 @@ void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool return ret != INVALID_FILE_ATTRIBUTES && (ret & FILE_ATTRIBUTE_DIRECTORY); }(); - typedef BOOLEAN (WINAPI* CreateSymbolicLinkFunc)(LPCTSTR lpSymlinkFileName, LPCTSTR lpTargetFileName, DWORD dwFlags); + using CreateSymbolicLinkFunc = BOOLEAN (WINAPI*)(LPCTSTR lpSymlinkFileName, LPCTSTR lpTargetFileName, DWORD dwFlags); const SysDllFun<CreateSymbolicLinkFunc> createSymbolicLink(L"kernel32.dll", "CreateSymbolicLinkW"); if (!createSymbolicLink) @@ -1663,7 +1727,10 @@ bool canCopyAsSparse(DWORD fileAttrSource, Function getTargetFsFlags) //throw () DWORD targetFsFlags = 0; if (!getTargetFsFlags(targetFsFlags)) - return false; + { + assert(false); + return false; + } assert(targetFsFlags != 0); return (targetFsFlags & FILE_SUPPORTS_SPARSE_FILES) != 0; @@ -2069,9 +2136,8 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize, 0, //_In_ DWORD nOutBufferSize, &bytesReturned, //_Out_opt_ LPDWORD lpBytesReturned nullptr)) //_Inout_opt_ LPOVERLAPPED lpOverlapped - {} //may legitimately fail with ERROR_INVALID_FUNCTION if - - // - if target folder is encrypted + {} //may legitimately fail with ERROR_INVALID_FUNCTION if: + // - target folder is encrypted // - target volume does not support compressed attribute //############################################################################# } diff --git a/zen/file_access.h b/zen/file_access.h index 026027e7..9c1b37ef 100644 --- a/zen/file_access.h +++ b/zen/file_access.h @@ -15,6 +15,8 @@ namespace zen { +//note: certain functions require COM initialization! (vista_file_op.h) + bool fileExists (const Zstring& filePath); //noexcept; check whether file or file-symlink exists bool dirExists (const Zstring& dirPath ); //noexcept; check whether directory or dir-symlink exists bool symlinkExists (const Zstring& linkPath); //noexcept; check whether a symbolic link exists @@ -31,6 +33,7 @@ void setFileTime(const Zstring& filePath, std::int64_t modificationTime, ProcSym //symlink handling: always evaluate target std::uint64_t getFilesize(const Zstring& filePath); //throw FileError std::uint64_t getFreeDiskSpace(const Zstring& path); //throw FileError, returns 0 if not available +VolumeId getVolumeId(const Zstring& itemPath); //throw FileError bool removeFile(const Zstring& filePath); //throw FileError; return "false" if file is not existing diff --git a/zen/file_error.h b/zen/file_error.h index f41a878a..61a2e89c 100644 --- a/zen/file_error.h +++ b/zen/file_error.h @@ -39,6 +39,7 @@ DEFINE_NEW_FILE_ERROR(ErrorDifferentVolume); //CAVEAT: thread-local Win32 error code is easily overwritten => evaluate *before* making any (indirect) system calls: //-> MinGW + Win XP: "throw" statement allocates memory to hold the exception object => error code is cleared //-> VC 2015, Debug: std::wstring allocator internally calls ::FlsGetValue() => error code is cleared +// https://connect.microsoft.com/VisualStudio/feedback/details/1775690/calling-operator-new-may-set-lasterror-to-0 #ifdef _MSC_VER #define THROW_LAST_FILE_ERROR(msg, functionName) \ do \ diff --git a/zen/file_id_def.h b/zen/file_id_def.h index 24e45795..2880121c 100644 --- a/zen/file_id_def.h +++ b/zen/file_id_def.h @@ -20,12 +20,28 @@ namespace zen { #ifdef ZEN_WIN -typedef DWORD DeviceId; -typedef ULONGLONG FileIndex; +using VolumeId = DWORD; +using FileIndex = ULONGLONG; -typedef std::pair<DeviceId, FileIndex> FileId; //optional! (however, always set on Linux, and *generally* available on Windows) +#elif defined ZEN_LINUX || defined ZEN_MAC +namespace impl { typedef struct ::stat StatDummy; } //sigh... + +using VolumeId = decltype(impl::StatDummy::st_dev); +using FileIndex = decltype(impl::StatDummy::st_ino); +#endif + + +struct FileId //always available on Linux, and *generally* available on Windows) +{ + FileId() {} + FileId(VolumeId volId, FileIndex fIdx) : volumeId(volId), fileIndex(fIdx) {} + VolumeId volumeId = 0; + FileIndex fileIndex = 0; +}; +inline bool operator==(const FileId& lhs, const FileId& rhs) { return lhs.volumeId == rhs.volumeId && lhs.fileIndex == rhs.fileIndex; } +#ifdef ZEN_WIN inline FileId extractFileId(const BY_HANDLE_FILE_INFORMATION& fileInfo) { @@ -44,19 +60,11 @@ FileId extractFileId(DWORD volumeSerialNumber, ULONGLONG fileIndex) FileId(volumeSerialNumber, fileIndex) : FileId(); } -static_assert(sizeof(FileId().first ) == sizeof(BY_HANDLE_FILE_INFORMATION().dwVolumeSerialNumber), ""); -static_assert(sizeof(FileId().second) == sizeof(BY_HANDLE_FILE_INFORMATION().nFileIndexHigh) + sizeof(BY_HANDLE_FILE_INFORMATION().nFileIndexLow), ""); -static_assert(sizeof(FileId().second) == sizeof(ULARGE_INTEGER), ""); - +static_assert(sizeof(FileId().volumeId ) == sizeof(BY_HANDLE_FILE_INFORMATION().dwVolumeSerialNumber), ""); +static_assert(sizeof(FileId().fileIndex) == sizeof(BY_HANDLE_FILE_INFORMATION().nFileIndexHigh) + sizeof(BY_HANDLE_FILE_INFORMATION().nFileIndexLow), ""); +static_assert(sizeof(FileId().fileIndex) == sizeof(ULARGE_INTEGER), ""); #elif defined ZEN_LINUX || defined ZEN_MAC -namespace impl { typedef struct ::stat StatDummy; } //sigh... - -typedef decltype(impl::StatDummy::st_dev) DeviceId; -typedef decltype(impl::StatDummy::st_ino) FileIndex; - -typedef std::pair<DeviceId, FileIndex> FileId; - inline FileId extractFileId(const struct ::stat& fileInfo) { diff --git a/zen/file_io.cpp b/zen/file_io.cpp index b4351ee8..3891abe6 100644 --- a/zen/file_io.cpp +++ b/zen/file_io.cpp @@ -114,7 +114,7 @@ FileInput::FileInput(const Zstring& filepath) : //throw FileError, ErrorFileLock fileHandle = createHandle(FILE_SHARE_READ | FILE_SHARE_DELETE); if (fileHandle == INVALID_HANDLE_VALUE) { - //=> support reading files which are open for write (e.g. Firefox db files): follow CopyFileEx() by addding FILE_SHARE_WRITE only for second try: + //=> support reading files which are open for write (e.g. Firefox .db, .sqlite files): follow CopyFileEx() by addding FILE_SHARE_WRITE only for second try: if (::GetLastError() == ERROR_SHARING_VIOLATION) fileHandle = createHandle(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE); @@ -185,7 +185,7 @@ size_t FileInput::tryRead(void* buffer, size_t bytesToRead) //throw FileError; m throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); #ifdef ZEN_WIN - //posix ::read() semantics: test for end of file: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365690%28v=vs.85%29.aspx + //posix ::read() semantics: test for end of file: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365690 DWORD bytesRead = 0; if (!::ReadFile(fileHandle, //__in HANDLE hFile, buffer, //__out LPVOID lpBuffer, @@ -233,7 +233,7 @@ FileOutput::FileOutput(const Zstring& filepath, AccessFlag access) : //throw Fil { return ::CreateFile(applyLongPathPrefix(filepath).c_str(), //_In_ LPCTSTR lpFileName, GENERIC_READ | GENERIC_WRITE, //_In_ DWORD dwDesiredAccess, - /* http://msdn.microsoft.com/en-us/library/aa363858(v=vs.85).aspx + /* https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858#files quote: When an application creates a file across a network, it is better to use GENERIC_READ | GENERIC_WRITE for dwDesiredAccess than to use GENERIC_WRITE alone. The resulting code is faster, because the redirector can use the cache manager and send fewer SMBs with more data. @@ -253,7 +253,7 @@ FileOutput::FileOutput(const Zstring& filepath, AccessFlag access) : //throw Fil { DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls! - //CREATE_ALWAYS fails with ERROR_ACCESS_DENIED if the existing file is hidden or "system" http://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx + //CREATE_ALWAYS fails with ERROR_ACCESS_DENIED if the existing file is hidden or "system": https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858#files if (ec == ERROR_ACCESS_DENIED && dwCreationDisposition == CREATE_ALWAYS) { const DWORD attrib = ::GetFileAttributes(applyLongPathPrefix(filepath).c_str()); @@ -355,7 +355,7 @@ size_t FileOutput::tryWrite(const void* buffer, size_t bytesToWrite) //throw Fil throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); #ifdef ZEN_WIN - DWORD bytesWritten = 0; //this parameter is NOT optional: http://blogs.msdn.com/b/oldnewthing/archive/2013/04/04/10407417.aspx + DWORD bytesWritten = 0; //this parameter is NOT optional: https://blogs.msdn.microsoft.com/oldnewthing/20130404-00/?p=4753/ if (!::WriteFile(fileHandle, //__in HANDLE hFile, buffer, //__out LPVOID lpBuffer, static_cast<DWORD>(bytesToWrite), //__in DWORD nNumberOfBytesToWrite, diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp index aa39e508..89eb6e48 100644 --- a/zen/file_traverser.cpp +++ b/zen/file_traverser.cpp @@ -68,13 +68,13 @@ void zen::traverseFolder(const Zstring& dirPath, //skip "." and ".." const wchar_t* const itemNameRaw = findData.cFileName; - if (itemNameRaw[0] == 0) - throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtPath(dirPath)), L"FindNextFile: Data corruption; item with empty name."); - if (itemNameRaw[0] == L'.' && (itemNameRaw[1] == 0 || (itemNameRaw[1] == L'.' && itemNameRaw[2] == 0))) continue; + if (itemNameRaw[0] == 0) + throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtPath(dirPath)), L"FindNextFile: Data corruption; item with empty name."); + const Zstring& itemPath = appendSeparator(dirPath) + itemNameRaw; if (zen::isSymlink(findData)) //check first! @@ -121,9 +121,6 @@ void zen::traverseFolder(const Zstring& dirPath, //don't return "." and ".." const char* itemNameRaw = dirEntry->d_name; - if (itemNameRaw[0] == 0) - throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtPath(dirPath)), L"readdir_r: Data corruption; item with empty name."); - if (itemNameRaw[0] == '.' && (itemNameRaw[1] == 0 || (itemNameRaw[1] == '.' && itemNameRaw[2] == 0))) continue; @@ -134,15 +131,18 @@ void zen::traverseFolder(const Zstring& dirPath, { itemName = osx::convertToPrecomposedUtf(itemNameRaw); //throw SysError } - catch (const SysError& e) //failure is not an item-level error since wo don't have the proper decomposed name!!! + catch (const SysError& e) //failure is not an item-level error since we don't have the proper decomposed name!!! { throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtPath(dirPath)), L"Failed to generate precomposed file name: " + fmtPath(itemNameRaw) + L"\n" + e.toString()); //too obscure to warrant translation } - const Zstring& itemPath = appendSeparator(dirPath) + itemName; #else - const Zstring& itemPath = appendSeparator(dirPath) + itemNameRaw; + const Zstring& itemName = itemNameRaw; #endif + if (itemName.empty()) //checks result of osx::convertToPrecomposedUtf, too! + throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtPath(dirPath)), L"readdir_r: Data corruption; item with empty name."); + + const Zstring& itemPath = appendSeparator(dirPath) + itemName; struct ::stat statData = {}; try diff --git a/zen/format_unit.cpp b/zen/format_unit.cpp index 3dfe805b..a880552e 100644 --- a/zen/format_unit.cpp +++ b/zen/format_unit.cpp @@ -302,7 +302,7 @@ std::wstring zen::utcToLocalTimeString(std::int64_t utcTime) #endif static const bool useNewLocalTimeCalculation = zen::vistaOrLater(); - //http://msdn.microsoft.com/en-us/library/ms724277(VS.85).aspx + //https://msdn.microsoft.com/en-us/library/ms724277 if (useNewLocalTimeCalculation) //DST conversion like in Windows 7: NTFS stays fixed, but FAT jumps by one hour { SYSTEMTIME systemTimeUtc = {}; @@ -22,7 +22,7 @@ namespace zen { inline -std::string generateGUID() //creates a 16 byte GUID +std::string generateGUID() //creates a 16-byte GUID { boost::uuids::uuid nativeRep = boost::uuids::random_generator()(); //generator is only thread-safe like an int, so we keep it local until we need to optimize perf diff --git a/zen/scope_guard.h b/zen/scope_guard.h index 67eb3053..f8c32127 100644 --- a/zen/scope_guard.h +++ b/zen/scope_guard.h @@ -22,7 +22,7 @@ inline int getUncaughtExceptionCount() { return std::uncaught_exceptions(); } #ifdef ZEN_LINUX static_assert(__GNUC__ < 5 || (__GNUC__ == 5 && (__GNUC_MINOR__ < 3 || (__GNUC_MINOR__ == 3 && __GNUC_PATCHLEVEL__ <= 1))), "check std::uncaught_exceptions support"); #else - static_assert(__clang_major__ < 7 || (__clang_major__ == 7 && __clang_minor__ <= 0), "check std::uncaught_exceptions support"); + static_assert(__clang_major__ < 7 || (__clang_major__ == 7 && __clang_minor__ <= 3), "check std::uncaught_exceptions support"); #endif namespace __cxxabiv1 @@ -61,6 +61,47 @@ enum class ScopeGuardRunMode template <ScopeGuardRunMode runMode, typename F> +struct ScopeGuardDestructor; + +//specialize scope guard destructor code and get rid of those pesky MSVC "4127 conditional expression is constant" +template <typename F> +struct ScopeGuardDestructor<ScopeGuardRunMode::ON_EXIT, F> +{ + static void run(F& fun, int exeptionCountOld) + { + (void)exeptionCountOld; //silence unused parameter warning + try { fun(); } + catch (...) { assert(false); } //consistency: don't expect exceptions for ON_EXIT even if "!failed"! + } +}; + + +template <typename F> +struct ScopeGuardDestructor<ScopeGuardRunMode::ON_SUCCESS, F> +{ + static void run(F& fun, int exeptionCountOld) + { + const bool failed = getUncaughtExceptionCount() > exeptionCountOld; + if (!failed) + fun(); //throw X + } +}; + + +template <typename F> +struct ScopeGuardDestructor<ScopeGuardRunMode::ON_FAIL, F> +{ + static void run(F& fun, int exeptionCountOld) + { + const bool failed = getUncaughtExceptionCount() > exeptionCountOld; + if (failed) + try { fun(); } + catch (...) { assert(false); } + } +}; + + +template <ScopeGuardRunMode runMode, typename F> class ScopeGuard { public: @@ -74,26 +115,7 @@ public: ~ScopeGuard() noexcept(runMode != ScopeGuardRunMode::ON_SUCCESS) { if (!dismissed) - { -#ifdef _MSC_VER -#pragma warning(suppress: 4127) //"conditional expression is constant" -#endif - if (runMode != ScopeGuardRunMode::ON_EXIT) - { - const bool failed = getUncaughtExceptionCount() > exeptionCount; - if ((runMode == ScopeGuardRunMode::ON_FAIL) != failed) - return; - } - -#ifdef _MSC_VER -#pragma warning(suppress: 4127) //"conditional expression is constant" -#endif - if (runMode == ScopeGuardRunMode::ON_SUCCESS) - fun_(); //throw X - else - try { fun_(); } - catch (...) { assert(false); } //consistency: don't expect exceptions for ON_EXIT even if "!failed"! - } + ScopeGuardDestructor<runMode, F>::run(fun_, exeptionCount); } void dismiss() { dismissed = true; } diff --git a/zen/shell_execute.h b/zen/shell_execute.h index e6dcf7f5..060ba84d 100644 --- a/zen/shell_execute.h +++ b/zen/shell_execute.h @@ -82,14 +82,14 @@ void shellExecute(const Zstring& command, ExecutionType type) //throw FileError trim(commandTmp, true, false); //CommandLineToArgvW() does not like leading spaces std::vector<Zstring> argv; - { - int argc = 0; - LPWSTR* tmp = ::CommandLineToArgvW(commandTmp.c_str(), &argc); - if (!tmp) - THROW_LAST_FILE_ERROR(_("Incorrect command line:") + L"\n" + commandTmp.c_str(), L"CommandLineToArgvW"); + { + int argc = 0; + LPWSTR* tmp = ::CommandLineToArgvW(commandTmp.c_str(), &argc); + if (!tmp) + THROW_LAST_FILE_ERROR(_("Incorrect command line:") + L"\n" + commandTmp.c_str(), L"CommandLineToArgvW"); ZEN_ON_SCOPE_EXIT(::LocalFree(tmp)); std::copy(tmp, tmp + argc, std::back_inserter(argv)); - } + } Zstring filepath; Zstring arguments; diff --git a/zen/stl_tools.h b/zen/stl_tools.h index b78dd5dd..058609e6 100644 --- a/zen/stl_tools.h +++ b/zen/stl_tools.h @@ -62,6 +62,7 @@ bool equal(InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, InputIterator2 last2); size_t hashBytes(const unsigned char* ptr, size_t len); +size_t hashBytesAppend(size_t hashVal, const unsigned char* ptr, size_t len); //support for custom string classes in std::unordered_set/map @@ -216,20 +217,30 @@ size_t hashBytes(const unsigned char* ptr, size_t len) //http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function #ifdef ZEN_BUILD_32BIT const size_t basis = 2166136261U; - const size_t prime = 16777619U; #elif defined ZEN_BUILD_64BIT const size_t basis = 14695981039346656037ULL; +#endif + return hashBytesAppend(basis, ptr, len); +} + + +inline +size_t hashBytesAppend(size_t hashVal, const unsigned char* ptr, size_t len) +{ +#ifdef ZEN_BUILD_32BIT + const size_t prime = 16777619U; +#elif defined ZEN_BUILD_64BIT const size_t prime = 1099511628211ULL; #endif - size_t val = basis; for (size_t i = 0; i < len; ++i) { - val ^= static_cast<size_t>(ptr[i]); - val *= prime; + hashVal ^= static_cast<size_t>(ptr[i]); + hashVal *= prime; } - return val; + return hashVal; } + } #endif //STL_TOOLS_H_84567184321434 diff --git a/zen/string_tools.h b/zen/string_tools.h index 92ca1654..ed6a1a54 100644 --- a/zen/string_tools.h +++ b/zen/string_tools.h @@ -77,7 +77,7 @@ template <class T, class S> T copyStringTo(S&& str); template <> inline bool isWhiteSpace(char ch) { - assert(ch != 0); //std C++ does not consider 0 as white space + assert(ch != 0); //std C++ does not consider 0 as white space //caveat 1: std::isspace() takes an int, but expects an unsigned char //caveat 2: some parts of UTF-8 chars are erroneously seen as whitespace, e.g. the a0 from "\xec\x8b\xa0" (MSVC) return static_cast<unsigned char>(ch) < 128 && @@ -85,10 +85,10 @@ bool isWhiteSpace(char ch) } template <> inline -bool isWhiteSpace(wchar_t ch) +bool isWhiteSpace(wchar_t ch) { - assert(ch != 0); //std C++ does not consider 0 as white space - return std::iswspace(ch) != 0; + assert(ch != 0); //std C++ does not consider 0 as white space + return std::iswspace(ch) != 0; } diff --git a/zen/symlink_target.h b/zen/symlink_target.h index f50d1806..c4e166e8 100644 --- a/zen/symlink_target.h +++ b/zen/symlink_target.h @@ -135,7 +135,7 @@ Zstring getSymlinkRawTargetString_impl(const Zstring& linkPath) //throw FileErro throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(linkPath)), L"Not a symbolic link or junction."); //absolute symlinks and junctions use NT namespace naming convention while relative ones do not: - //http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx#NT_Namespaces + //https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247#NT_Namespaces return ntPathToWin32Path(output); #elif defined ZEN_LINUX || defined ZEN_MAC @@ -158,7 +158,7 @@ Zstring getResolvedSymlinkPath_impl(const Zstring& linkPath) //throw FileError using namespace zen; #ifdef ZEN_WIN //GetFinalPathNameByHandle() is not available before Vista! - typedef DWORD (WINAPI* GetFinalPathNameByHandleWFunc)(HANDLE hFile, LPTSTR lpszFilePath, DWORD cchFilePath, DWORD dwFlags); + using GetFinalPathNameByHandleWFunc = DWORD (WINAPI*)(HANDLE hFile, LPTSTR lpszFilePath, DWORD cchFilePath, DWORD dwFlags); const SysDllFun<GetFinalPathNameByHandleWFunc> getFinalPathNameByHandle(L"kernel32.dll", "GetFinalPathNameByHandleW"); if (!getFinalPathNameByHandle) throw FileError(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtPath(linkPath)), replaceCpy(_("Cannot find system function %x."), L"%x", L"\"GetFinalPathNameByHandleW\"")); @@ -184,15 +184,12 @@ Zstring getResolvedSymlinkPath_impl(const Zstring& linkPath) //throw FileError &targetPath[0], //__out LPTSTR lpszFilePath, bufferSize, //__in DWORD cchFilePath, 0); //__in DWORD dwFlags - if (charsWritten == 0 || charsWritten >= bufferSize) - { - const std::wstring errorMsg = replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtPath(linkPath)); - if (charsWritten == 0) - THROW_LAST_FILE_ERROR(errorMsg, L"GetFinalPathNameByHandle"); - throw FileError(errorMsg); - } + if (charsWritten == 0) + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtPath(linkPath)), L"GetFinalPathNameByHandle"); + if (charsWritten >= bufferSize) + throw FileError(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtPath(linkPath)), L"GetFinalPathNameByHandle: buffer overflow."); - return Zstring(&targetPath[0], charsWritten); + return removeLongPathPrefix(Zstring(&targetPath[0], charsWritten)); //MSDN: GetFinalPathNameByHandle() always prepends "\\?\" #elif defined ZEN_LINUX || defined ZEN_MAC char* targetPath = ::realpath(linkPath.c_str(), nullptr); @@ -238,7 +238,7 @@ void utf8ToCodePoint(CharIterator first, CharIterator last, Function writeOutput template <class CharString> inline size_t unicodeLength(const CharString& str, char) //utf8 { - typedef typename GetCharType<CharString>::Type CharType; + using CharType = typename GetCharType<CharString>::Type; const CharType* strFirst = strBegin(str); const CharType* const strLast = strFirst + strLength(str); @@ -258,7 +258,7 @@ size_t unicodeLength(const CharString& str, char) //utf8 template <class WideString> inline size_t unicodeLengthWide(const WideString& str, Int2Type<2>) //windows: utf16-wchar_t { - typedef typename GetCharType<WideString>::Type CharType; + using CharType = typename GetCharType<WideString>::Type; const CharType* strFirst = strBegin(str); const CharType* const strLast = strFirst + strLength(str); @@ -302,7 +302,7 @@ namespace implementation template <class CharString> inline size_t findUnicodePos(const CharString& str, size_t unicodePos, char) //utf8-char { - typedef typename GetCharType<CharString>::Type CharType; + using CharType = typename GetCharType<CharString>::Type; const CharType* strFirst = strBegin(str); const size_t strLen = strLength(str); @@ -326,7 +326,7 @@ size_t findUnicodePos(const CharString& str, size_t unicodePos, char) //utf8-cha template <class WideString> inline size_t findUnicodePosWide(const WideString& str, size_t unicodePos, Int2Type<2>) //windows: utf16-wchar_t { - typedef typename GetCharType<WideString>::Type CharType; + using CharType = typename GetCharType<WideString>::Type; const CharType* strFirst = strBegin(str); const size_t strLen = strLength(str); |