diff options
95 files changed, 2768 insertions, 1863 deletions
diff --git a/FreeFileSync/Build/Changelog.txt b/FreeFileSync/Build/Changelog.txt index cf12e790..2831c26d 100644 --- a/FreeFileSync/Build/Changelog.txt +++ b/FreeFileSync/Build/Changelog.txt @@ -1,3 +1,20 @@ +FreeFileSync 8.7 [2016-12-06] +----------------------------- +New auto-updater feature for FreeFileSync Donation Edition +Download zip archive of portable FreeFileSync Donation Edition +New command line options to define parameters for silent installation +Support offline activation for portable Donation Edition +Use automatic keyboard-interactive SFTP authentication as fallback +Check for available SFTP authentication methods before login +Support cloud sync of portable edition installation files +Access donation transaction details from about dialog +Use width from flexible grid column when showing/hiding extra columns +Show item short names in middle column tooltip +Enhanced file category descriptions with modification times +Don't warn about missing recycle bin when only moving or updating attributes +Fixed crash when switching to main dialog during batch sync + + FreeFileSync 8.6 [2016-10-25] ----------------------------- Added SFTP support for OS X diff --git a/FreeFileSync/Build/Help/html/exclude-items.html b/FreeFileSync/Build/Help/html/exclude-items.html index bc60d79a..6e3cf0d9 100644 --- a/FreeFileSync/Build/Help/html/exclude-items.html +++ b/FreeFileSync/Build/Help/html/exclude-items.html @@ -72,7 +72,7 @@ <h2>Example: <span style="font-weight:normal">Exclude a sub folder except for certain files</span></h2> <p> - Set up two folder pairs with the same source and target paths but with distinct local filters:<br> + Set up <b>two folder pairs</b> with the same source and target paths but with distinct local filters:<br> Folder pair 1; local <em>exclude</em> filter: <span class="file-path">\SubFolder\</span><br> Folder pair 2; local <em>include</em> filter: <span class="file-path">\SubFolder\*.txt</span> </p> diff --git a/FreeFileSync/Build/Languages/arabic.lng b/FreeFileSync/Build/Languages/arabic.lng index 1d1bb221..1f735dae 100644 --- a/FreeFileSync/Build/Languages/arabic.lng +++ b/FreeFileSync/Build/Languages/arabic.lng @@ -115,9 +115,6 @@ <source>Path to an alternate GlobalSettings.xml file.</source> <target>تحديد مسار مختلف لملف GlobalSettings.xml.</target> -<source>Any number of FreeFileSync .ffs_gui and/or .ffs_batch configuration files.</source> -<target>أي عدد من ملفات خيارات FreeFileSync أو\و _gui and/or .ffs_batch.</target> - <source>Any number of alternative directory pairs for at most one config file.</source> <target>أي عدد من أزواج المسارات البديلة من أجل ملف خيارات واحد.</target> @@ -278,8 +275,8 @@ Actual: %y bytes </source> <target> حجم غير متوقع لتدفق البيانات. -المتوقع: %x bytes -الفعلي: %y bytes +المتوقع: %x bytes +الفعلي: %y bytes </target> <source>Cannot copy symbolic link %x to %y.</source> @@ -344,22 +341,22 @@ Actual: %y bytes <pluralform>%x bytes</pluralform> </source> <target> -<pluralform>0 byte</pluralform> -<pluralform>1 byte</pluralform> -<pluralform>2 bytes</pluralform> -<pluralform>%x bytes</pluralform> -<pluralform>%x bytes</pluralform> -<pluralform>%x bytes</pluralform> +<pluralform>0 byte</pluralform> +<pluralform>1 byte</pluralform> +<pluralform>2 bytes</pluralform> +<pluralform>%x bytes</pluralform> +<pluralform>%x bytes</pluralform> +<pluralform>%x bytes</pluralform> </target> <source>%x MB</source> -<target>%x MB</target> +<target>%x MB</target> <source>%x KB</source> -<target>%x KB</target> +<target>%x KB</target> <source>%x GB</source> -<target>%x GB</target> +<target>%x GB</target> <source>Cannot load file %x.</source> <target>لا يمكن فتح الملف %x.</target> @@ -900,9 +897,6 @@ The command is triggered if: <source>Find:</source> <target>بحث:</target> -<source>Match case</source> -<target>مطابقة حالة الأحرف (Match case)</target> - <source>New</source> <target>جديد</target> @@ -1212,7 +1206,7 @@ This guarantees a consistent state even in case of a serious error. <target>الا&فتراضي</target> <source>Source code written in C++ using:</source> -<target>الرماز المصدري مكتوب بلغة C++ باستخدام:</target> +<target>الرماز المصدري مكتوب بلغة C++ باستخدام:</target> <source>If you like FreeFileSync:</source> <target>إذا أعجبك FreeFileSync:</target> @@ -1590,9 +1584,6 @@ This guarantees a consistent state even in case of a serious error. <source>Parent folder path</source> <target>مسار المجلد الحاوي</target> -<source>Temporary local copy for SFTP and MTP storage</source> -<target>نسخة محلية مؤقتة لتخزين SFTP and MTP</target> - <source>Parameters for opposite side</source> <target>معلمات الجانب المعاكس</target> @@ -1756,10 +1747,10 @@ This guarantees a consistent state even in case of a serious error. <target>نوع العنصر %x غير مدعوم:</target> <source>%x TB</source> -<target>%x TB</target> +<target>%x TB</target> <source>%x PB</source> -<target>%x PB</target> +<target>%x PB</target> <source> <pluralform>1 min</pluralform> diff --git a/FreeFileSync/Build/Languages/english_uk.lng b/FreeFileSync/Build/Languages/english_uk.lng index 6508fcb0..648076bf 100644 --- a/FreeFileSync/Build/Languages/english_uk.lng +++ b/FreeFileSync/Build/Languages/english_uk.lng @@ -1,5 +1,5 @@ <header> - <language>English (UK)</language> + <language>English (UK)</language> <translator>Robert Readman</translator> <locale>English (U.K.)</locale> <image>flag_england.png</image> diff --git a/FreeFileSync/Build/Languages/german.lng b/FreeFileSync/Build/Languages/german.lng index d6cc1e25..a3cff38c 100644 --- a/FreeFileSync/Build/Languages/german.lng +++ b/FreeFileSync/Build/Languages/german.lng @@ -7,6 +7,12 @@ <plural_definition>n == 1 ? 0 : 1</plural_definition> </header> +<source>Installation was registered on a different operating system.</source> +<target>Die Installation wurde auf einem anderen Betriebssystem registriert.</target> + +<source>The server does not support authentication via %x.</source> +<target>Der Server unterstützt keine Authentifizierung über %x.</target> + <source>Both sides have changed since last synchronization.</source> <target>Beide Seiten wurden seit der letzten Synchronisation verändert.</target> @@ -130,14 +136,14 @@ <source>Date:</source> <target>Datum:</target> -<source>Files %x have the same date but a different size.</source> -<target>Die Dateien %x haben dasselbe Datum, aber unterschiedliche Größen.</target> +<source>Files have the same date but a different size.</source> +<target>Die Dateien haben dasselbe Datum, aber unterschiedliche Größen.</target> <source>Size:</source> <target>Größe:</target> -<source>Content comparison was skipped for excluded files %x.</source> -<target>Der Vergleich nach Dateiinhalt wurde für die ausgeschlossenen Dateien %x übersprungen.</target> +<source>Content comparison was skipped for excluded files.</source> +<target>Der Vergleich nach Dateiinhalt wurde für die ausgeschlossenen Dateien übersprungen.</target> <source>Items differ in attributes only</source> <target>Die Elemente unterscheiden sich nur in Attributen</target> @@ -279,8 +285,8 @@ Tatsächlich: %y bytes <source>Cannot open directory %x.</source> <target>Das Verzeichnis %x kann nicht geöffnet werden.</target> -<source>Cannot enumerate directory %x.</source> -<target>Das Verzeichnis %x kann nicht gelistet werden.</target> +<source>Cannot read directory %x.</source> +<target>Das Verzeichnis %x kann nicht gelesen werden.</target> <source>Cannot read file attributes of %x.</source> <target>Die Dateiattribute von %x können nicht gelesen werden.</target> @@ -381,7 +387,7 @@ Tatsächlich: %y bytes <source>Database file %x does not yet exist.</source> <target>Die Datenbankdatei %x existiert noch nicht.</target> -<source>Database file is corrupt:</source> +<source>Database file is corrupted:</source> <target>Die Datenbankdatei ist beschädigt:</target> <source>Database files do not share a common session.</source> @@ -863,11 +869,8 @@ Die Befehlszeile wird ausgelöst, wenn: <source>&Tools</source> <target>E&xtras</target> -<source>&Check for new version</source> -<target>&Auf neue Version prüfen</target> - -<source>&Check now</source> -<target>&Jetzt prüfen</target> +<source>&Check for updates now</source> +<target>&Jetzt auf Aktualisierung prüfen</target> <source>Check &automatically once a week</source> <target>&Automatisch wöchentlich prüfen</target> @@ -1240,6 +1243,9 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. <source>Source code written in C++ using:</source> <target>Der Quellcode wurde in C++ geschrieben mit:</target> +<source>Donation details</source> +<target>Spendendetails</target> + <source>If you like FreeFileSync:</source> <target>Wenn Sie FreeFileSync mögen:</target> @@ -1249,7 +1255,7 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. <source>Feedback and suggestions are welcome</source> <target>Feedback und Vorschläge sind willkommen</target> -<source>Homepage</source> +<source>Home page</source> <target>Homepage</target> <source>Email</source> @@ -1261,6 +1267,27 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. <source>Many thanks for localization:</source> <target>Vielen Dank für die Lokalisation:</target> +<source>Activate the FreeFileSync Donation Edition by one of the following methods:</source> +<target>Aktivieren Sie die FreeFileSync Spendenversion durch eine der folgenden Methoden:</target> + +<source>1. Activate via internet now:</source> +<target>1. Jetzt über das Internet aktivieren:</target> + +<source>Activate online</source> +<target>Online aktivieren</target> + +<source>2. Retrieve an offline activation key from the following URL:</source> +<target>2. Einen Offline-Aktivierungsschlüssel von folgender URL abrufen:</target> + +<source>&Copy to clipboard</source> +<target>In &Zwischenablage kopieren</target> + +<source>Enter activation key:</source> +<target>Aktivierungsschlüssel eingeben:</target> + +<source>Activate offline</source> +<target>Offline aktivieren</target> + <source>SSH File Transfer Protocol</source> <target>SSH File Transfer Protocol</target> @@ -1279,6 +1306,9 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. <source>Select Time Span</source> <target>Zeitspanne auswählen</target> +<source>FreeFileSync Donation Edition</source> +<target>FreeFileSync Spendenversion</target> + <source>&Options</source> <target>&Optionen</target> @@ -1300,8 +1330,8 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. <source>Overview</source> <target>Übersicht</target> -<source>&Download</source> -<target>Zum &Download</target> +<source>&Show details</source> +<target>&Zeige Details</target> <source>A new version of FreeFileSync is available:</source> <target>Eine neue Version von FreeFileSync ist verfügbar:</target> @@ -1543,6 +1573,12 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. <source>Loading...</source> <target>Lade...</target> +<source>Installation files are corrupted. Please reinstall FreeFileSync.</source> +<target>Die Installationsdateien sind beschädigt. Bitte installieren Sie FreeFileSync neu.</target> + +<source>Thank you, %x, for your donation and support!</source> +<target>Danke %x für die Spende und Unterstützung!</target> + <source>Password:</source> <target>Passwort:</target> @@ -1606,6 +1642,9 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. <source>&Show</source> <target>&Zeigen</target> +<source>Downloading update...</source> +<target>Lade Aktualisierung...</target> + <source>Identify equal files by comparing modification time and size.</source> <target>Erkenne gleiche Dateien anhand der Änderungszeit und Größe.</target> @@ -1699,12 +1738,33 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. <source>Percentage</source> <target>Prozent</target> +<source>Failed to retrieve update information.</source> +<target>Aktualisierungsinformationen konnten nicht abgerufen werden.</target> + +<source>Automatic updates:</source> +<target>Automatische Aktualisierung:</target> + +<source>Requires FreeFileSync Donation Edition</source> +<target>Benötigt FreeFileSync Spendenversion</target> + <source>Check for Program Updates</source> <target>Suche nach neuer Programmversion</target> +<source>Auto-update now or download manually from the FreeFileSync home page?</source> +<target>Jetzt automatisch aktualisieren oder manuell von der FreeFileSync Homepage herunterladen?</target> + +<source>&Auto-update</source> +<target>&Automatisch</target> + +<source>&Home page</source> +<target>&Homepage</target> + <source>Download now?</source> <target>Jetzt herunterladen?</target> +<source>&Download</source> +<target>Zum &Download</target> + <source>FreeFileSync is up to date.</source> <target>FreeFileSync ist auf dem neuesten Stand.</target> @@ -1717,14 +1777,17 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. <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>Failed to activate FreeFileSync Donation Edition.</source> +<target>Die Aktivierung der FreeFileSync Spendenversion ist fehlgeschlagen.</target> + +<source>Incorrect activation key.</source> +<target>Falscher Aktivierungsschlüssel.</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>Cannot find system function %x.</source> +<target>Die Systemfunktion %x wurde nicht gefunden.</target> <source>Unable to register device notifications for %x.</source> <target>Die Registrierung für Gerätemeldungen ist für %x fehlgeschlagen.</target> @@ -1882,15 +1945,6 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. <source>Edit with FreeFileSync</source> <target>Mit FreeFileSync editieren</target> -<source>Thank you, %x, for your donation and support!</source> -<target>Danke %x für die Spende und Unterstützung!</target> - -<source>This FreeFileSync installer for donors has reached its daily installation limit.</source> -<target>Das FreeFileSync Installationsprogramm für Spender hat sein tägliches Installationslimit erreicht.</target> - -<source>Download the regular version from the FreeFileSync homepage now?</source> -<target>Jetzt die normale Version von der FreeFileSync Homepage herunterladen?</target> - <source>The portable version cannot install into the selected folder.</source> <target>Die portable Version kann nicht in den gewählten Ordner installiert werden.</target> diff --git a/FreeFileSync/Build/Languages/hebrew.lng b/FreeFileSync/Build/Languages/hebrew.lng index 3fdd3083..48fc065d 100644 --- a/FreeFileSync/Build/Languages/hebrew.lng +++ b/FreeFileSync/Build/Languages/hebrew.lng @@ -1184,7 +1184,7 @@ This guarantees a consistent state even in case of a serious error. <target>&ברירת מחדל</target> <source>Source code written in C++ using:</source> -<target>קוד מקור נכתב ב- C++ באמצעות:</target> +<target>קוד מקור נכתב ב- C++ באמצעות:</target> <source>If you like FreeFileSync:</source> <target>במידה ו-FreeFileSync מוצאת חן בעינכם:</target> diff --git a/FreeFileSync/Build/Languages/portuguese_br.lng b/FreeFileSync/Build/Languages/portuguese_br.lng index b155f3a9..8a420222 100644 --- a/FreeFileSync/Build/Languages/portuguese_br.lng +++ b/FreeFileSync/Build/Languages/portuguese_br.lng @@ -1,5 +1,5 @@ <header> - <language>Português (BR)</language> + <language>Português (BR)</language> <translator>Edison Aranha</translator> <locale>pt_BR</locale> <image>flag_brazil.png</image> diff --git a/FreeFileSync/Build/Languages/russian.lng b/FreeFileSync/Build/Languages/russian.lng index 620d79bd..3933e8bb 100644 --- a/FreeFileSync/Build/Languages/russian.lng +++ b/FreeFileSync/Build/Languages/russian.lng @@ -796,7 +796,7 @@ The command is triggered if: <target>Выберите папку SFTP</target> <source>Select alternative folder type</source> -<target>Выберете альтернативный тип папки</target> +<target>Выберите альтернативный тип папки</target> <source>&New</source> <target>&Новая</target> @@ -895,7 +895,7 @@ The command is triggered if: <target>Тип просмотра:</target> <source>Select view:</source> -<target>Выберете отображение:</target> +<target>Выберите отображение:</target> <source>Statistics:</source> <target>Статистика:</target> @@ -1062,10 +1062,10 @@ The command is triggered if: <target>Папка на сервере:</target> <source>Select a directory on the server:</source> -<target>Выберете папку на сервере:</target> +<target>Выберите папку на сервере:</target> <source>Select Folder</source> -<target>Выберете папку</target> +<target>Выберите папку</target> <source>Start synchronization now?</source> <target>Начать синхронизацию сейчас?</target> @@ -1098,7 +1098,7 @@ The command is triggered if: <target>Свернуть в область уведомлений</target> <source>Bytes copied:</source> -<target>Данных скопированно:</target> +<target>Данных скопировано:</target> <source>Close</source> <target>Закрыть</target> diff --git a/FreeFileSync/Build/Languages/spanish.lng b/FreeFileSync/Build/Languages/spanish.lng index 62240f19..aafbacec 100644 --- a/FreeFileSync/Build/Languages/spanish.lng +++ b/FreeFileSync/Build/Languages/spanish.lng @@ -1,6 +1,6 @@ <header> <language>Español</language> - <translator>I.R.Maturana (irmlab.com)</translator> + <translator>I.R.Maturana (irmlab.com)</translator> <locale>es_ES</locale> <image>flag_spain.png</image> <plural_count>2</plural_count> diff --git a/FreeFileSync/Build/Resources.zip b/FreeFileSync/Build/Resources.zip Binary files differindex 9acc3a10..85b1967d 100644 --- a/FreeFileSync/Build/Resources.zip +++ b/FreeFileSync/Build/Resources.zip diff --git a/FreeFileSync/Source/RealTimeSync/application.cpp b/FreeFileSync/Source/RealTimeSync/application.cpp index 735e0b8b..cc127249 100644 --- a/FreeFileSync/Source/RealTimeSync/application.cpp +++ b/FreeFileSync/Source/RealTimeSync/application.cpp @@ -65,7 +65,7 @@ bool Application::OnInit() wxToolTip::SetMaxWidth(-1); //disable tooltip wrapping -> Windows only #elif defined ZEN_LINUX - ::gtk_rc_parse((zen::getResourceDir() + "styles.gtk_rc").c_str()); //remove inner border from bitmap buttons + ::gtk_rc_parse((zen::getResourceDirPf() + "styles.gtk_rc").c_str()); //remove inner border from bitmap buttons #endif //Windows User Experience Interaction Guidelines: tool tips should have 5s timeout, info tips no timeout => compromise: @@ -74,7 +74,18 @@ bool Application::OnInit() SetAppName(L"RealTimeSync"); - initResourceImages(getResourceDir() + Zstr("Resources.zip")); + initResourceImages(getResourceDirPf() + Zstr("Resources.zip")); + + try + { + setLanguage(xmlAccess::getProgramLanguage()); //throw FileError + } + catch (const FileError& e) + { + warn_static("Bug? (exit on frame delete)") + showNotificationDialog(nullptr, DialogInfoType::ERROR2, PopupDialogCfg().setDetailInstructions(e.toString())); + //continue! + } Connect(wxEVT_QUERY_END_SESSION, wxEventHandler(Application::onQueryEndSession), nullptr, this); Connect(wxEVT_END_SESSION, wxEventHandler(Application::onQueryEndSession), nullptr, this); @@ -85,7 +96,6 @@ bool Application::OnInit() Connect(EVENT_ENTER_EVENT_LOOP, wxEventHandler(Application::onEnterEventLoop), nullptr, this); wxCommandEvent scrollEvent(EVENT_ENTER_EVENT_LOOP); AddPendingEvent(scrollEvent); - return true; //true: continue processing; false: exit immediately. } @@ -103,17 +113,6 @@ void Application::onEnterEventLoop(wxEvent& event) { Disconnect(EVENT_ENTER_EVENT_LOOP, wxEventHandler(Application::onEnterEventLoop), nullptr, this); - try - { - wxLanguage lngId = xmlAccess::getProgramLanguage(); - setLanguage(lngId); //throw FileError - } - catch (const FileError& e) - { - showNotificationDialog(nullptr, DialogInfoType::ERROR2, PopupDialogCfg().setDetailInstructions(e.toString())); - //continue! - } - //try to set config/batch- filepath set by %1 parameter std::vector<Zstring> commandArgs; for (int i = 1; i < argc; ++i) diff --git a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp index af22b7a0..4bb61add 100644 --- a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp +++ b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp @@ -73,7 +73,7 @@ void MainDialog::create(const Zstring& cfgFile) MainDialog::MainDialog(wxDialog* dlg, const Zstring& cfgFileName) : MainDlgGenerated(dlg), - lastRunConfigPath(zen::getConfigDir() + Zstr("LastRun.ffs_real")) + lastRunConfigPath_(zen::getConfigDirPathPf() + Zstr("LastRun.ffs_real")) { #ifdef ZEN_WIN new MouseMoveWindow(*this); //ownership passed to "this" @@ -100,9 +100,9 @@ MainDialog::MainDialog(wxDialog* dlg, const Zstring& cfgFileName) //--------------------------- load config values ------------------------------------ xmlAccess::XmlRealConfig newConfig; - const Zstring currentConfigFile = cfgFileName.empty() ? lastRunConfigPath : cfgFileName; + const Zstring currentConfigFile = cfgFileName.empty() ? lastRunConfigPath_ : cfgFileName; bool loadCfgSuccess = false; - if (!cfgFileName.empty() || fileExists(lastRunConfigPath)) + if (!cfgFileName.empty() || fileExists(lastRunConfigPath_)) try { std::wstring warningMsg; @@ -161,7 +161,7 @@ MainDialog::~MainDialog() try //write config to XML { - writeConfig(currentCfg, lastRunConfigPath); //throw FileError + writeConfig(currentCfg, lastRunConfigPath_); //throw FileError } catch (const FileError& e) { @@ -172,7 +172,7 @@ MainDialog::~MainDialog() void MainDialog::onQueryEndSession() { - try { writeConfig(getConfiguration(), lastRunConfigPath); } //throw FileError + try { writeConfig(getConfiguration(), lastRunConfigPath_); } //throw FileError catch (const FileError&) {} //we try our best do to something useful in this extreme situation - no reason to notify or even log errors here! } @@ -228,7 +228,7 @@ void MainDialog::OnStart(wxCommandEvent& event) xmlAccess::XmlRealConfig currentCfg = getConfiguration(); - switch (rts::startDirectoryMonitor(currentCfg, xmlAccess::extractJobName(utfCvrtTo<Zstring>(currentConfigFileName)))) + switch (rts::startDirectoryMonitor(currentCfg, xmlAccess::extractJobName(utfCvrtTo<Zstring>(currentConfigFileName_)))) { case rts::EXIT_APP: Close(); @@ -249,7 +249,7 @@ void MainDialog::OnStart(wxCommandEvent& event) void MainDialog::OnConfigSave(wxCommandEvent& event) { - Zstring defaultFileName = currentConfigFileName.empty() ? Zstr("Realtime.ffs_real") : currentConfigFileName; + Zstring defaultFileName = currentConfigFileName_.empty() ? Zstr("Realtime.ffs_real") : currentConfigFileName_; //attention: currentConfigFileName may be an imported *.ffs_batch file! We don't want to overwrite it with a GUI config! if (pathEndsWith(defaultFileName, Zstr(".ffs_batch"))) defaultFileName = beforeLast(defaultFileName, Zstr("."), IF_MISSING_RETURN_NONE) + Zstr(".ffs_real"); @@ -306,15 +306,15 @@ void MainDialog::loadConfig(const Zstring& filepath) void MainDialog::setLastUsedConfig(const Zstring& filepath) { //set title - if (filepath == lastRunConfigPath) + if (filepath == lastRunConfigPath_) { SetTitle(L"RealTimeSync - " + _("Automated Synchronization")); - currentConfigFileName.clear(); + currentConfigFileName_.clear(); } else { SetTitle(utfCvrtTo<wxString>(filepath)); - currentConfigFileName = filepath; + currentConfigFileName_ = filepath; } } @@ -323,7 +323,7 @@ void MainDialog::OnConfigLoad(wxCommandEvent& event) { wxFileDialog filePicker(this, wxString(), - utfCvrtTo<wxString>(beforeLast(currentConfigFileName, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE)), //default dir + utfCvrtTo<wxString>(beforeLast(currentConfigFileName_, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE)), //default dir wxString(), wxString(L"RealTimeSync (*.ffs_real; *.ffs_batch)|*.ffs_real;*.ffs_batch") + L"|" +_("All files") + L" (*.*)|*", wxFD_OPEN); @@ -490,7 +490,7 @@ void MainDialog::removeAddFolder(size_t pos) //the deferred deletion it is expected to do (and which is implemented correctly on Windows and Linux) //http://bb10.com/python-wxpython-devel/2012-09/msg00004.html //=> since we're in a mouse button callback of a sub-component of "pairToDelete" we need to delay deletion ourselves: - guiQueue.processAsync([] {}, [pairToDelete] { pairToDelete->Destroy(); }); + guiQueue_.processAsync([] {}, [pairToDelete] { pairToDelete->Destroy(); }); //set size of scrolled window const size_t additionalRows = std::min(dirpathsExtra.size(), MAX_ADD_FOLDERS); //up to MAX_ADD_FOLDERS additional folders shall be shown diff --git a/FreeFileSync/Source/RealTimeSync/main_dlg.h b/FreeFileSync/Source/RealTimeSync/main_dlg.h index f540bd5d..cee7574c 100644 --- a/FreeFileSync/Source/RealTimeSync/main_dlg.h +++ b/FreeFileSync/Source/RealTimeSync/main_dlg.h @@ -62,10 +62,10 @@ private: std::vector<DirectoryPanel*> dirpathsExtra; //additional pairs to the standard pair - const Zstring lastRunConfigPath; - Zstring currentConfigFileName; + const Zstring lastRunConfigPath_; + Zstring currentConfigFileName_; - zen::AsyncGuiQueue guiQueue; //schedule and run long-running tasks asynchronously, but process results on GUI queue + zen::AsyncGuiQueue guiQueue_; //schedule and run long-running tasks asynchronously, but process results on GUI queue }; #endif //MAIN_DLG_H_2384790842252445 diff --git a/FreeFileSync/Source/RealTimeSync/monitor.cpp b/FreeFileSync/Source/RealTimeSync/monitor.cpp index c07c137c..a7902eb6 100644 --- a/FreeFileSync/Source/RealTimeSync/monitor.cpp +++ b/FreeFileSync/Source/RealTimeSync/monitor.cpp @@ -11,6 +11,7 @@ #include <zen/dir_watcher.h> #include <zen/thread.h> //#include <zen/tick_count.h> +#include <zen/basic_math.h> #include <wx/utils.h> #include "../lib/resolve_path.h" //#include "../library/db_file.h" //SYNC_DB_FILE_ENDING -> complete file too much of a dependency; file ending too little to decouple into single header @@ -22,7 +23,7 @@ using namespace zen; namespace { -const int CHECK_FOLDER_INTERVAL = 1; //unit: [s] +const int FOLDER_EXISTENCE_CHECK_INTERVAL_SEC = 1; //unit: [s] std::vector<Zstring> getFormattedDirs(const std::vector<Zstring>& folderPathPhrases) //throw FileError @@ -78,7 +79,7 @@ WaitResult waitForChanges(const std::vector<Zstring>& folderPathPhrases, //throw //a non-existent network path may block, so check existence asynchronously! auto ftDirExists = runAsync([=] { return zen::dirExists(folderPathFmt); }); //we need to check dirExists(), not somethingExists(): it's not clear if DirWatcher detects a type clash (file instead of directory!) - while (ftDirExists.wait_for(std::chrono::milliseconds(rts::UI_UPDATE_INTERVAL / 2)) != std::future_status::ready) + while (ftDirExists.wait_for(std::chrono::milliseconds(rts::UI_UPDATE_INTERVAL_MS / 2)) != std::future_status::ready) onRefreshGui(false /*readyForSync*/); //may throw! if (!ftDirExists.get()) return WaitResult(folderPathFmt); @@ -93,15 +94,16 @@ WaitResult waitForChanges(const std::vector<Zstring>& folderPathPhrases, //throw } } - auto lastCheck = std::chrono::steady_clock::now(); + auto lastCheckTime = std::chrono::steady_clock::now(); for (;;) { const bool checkDirExistNow = [&]() -> bool //checking once per sec should suffice { const auto now = std::chrono::steady_clock::now(); - if (now >= lastCheck + std::chrono::seconds(CHECK_FOLDER_INTERVAL)) + + if (numeric::dist(now, lastCheckTime) > std::chrono::seconds(FOLDER_EXISTENCE_CHECK_INTERVAL_SEC)) //handle potential chrono wrap-around! { - lastCheck = now; + lastCheckTime = now; return true; } return false; @@ -145,7 +147,7 @@ WaitResult waitForChanges(const std::vector<Zstring>& folderPathPhrases, //throw } } - std::this_thread::sleep_for(std::chrono::milliseconds(rts::UI_UPDATE_INTERVAL / 2)); + std::this_thread::sleep_for(std::chrono::milliseconds(rts::UI_UPDATE_INTERVAL_MS / 2)); onRefreshGui(true /*readyForSync*/); //throw ?: may start sync at this presumably idle time } } @@ -174,16 +176,16 @@ void waitForMissingDirs(const std::vector<Zstring>& folderPathPhrases, //throw F //2. check dir existence return zen::dirExists(folderPathFmt); }); - while (ftDirExisting.wait_for(std::chrono::milliseconds(rts::UI_UPDATE_INTERVAL / 2)) != std::future_status::ready) + while (ftDirExisting.wait_for(std::chrono::milliseconds(rts::UI_UPDATE_INTERVAL_MS / 2)) != std::future_status::ready) onRefreshGui(folderPathFmt); //may throw! if (!ftDirExisting.get()) { allExisting = false; //wait some time... - const int refreshInterval = rts::UI_UPDATE_INTERVAL / 2; - static_assert(CHECK_FOLDER_INTERVAL * 1000 % refreshInterval == 0, ""); - for (int i = 0; i < CHECK_FOLDER_INTERVAL * 1000 / refreshInterval; ++i) + const int refreshInterval = rts::UI_UPDATE_INTERVAL_MS / 2; + static_assert(FOLDER_EXISTENCE_CHECK_INTERVAL_SEC * 1000 % refreshInterval == 0, ""); + for (int i = 0; i < FOLDER_EXISTENCE_CHECK_INTERVAL_SEC * 1000 / refreshInterval; ++i) { onRefreshGui(folderPathFmt); //may throw! std::this_thread::sleep_for(std::chrono::milliseconds(refreshInterval)); diff --git a/FreeFileSync/Source/RealTimeSync/monitor.h b/FreeFileSync/Source/RealTimeSync/monitor.h index 4d3538fd..8fb7ae3f 100644 --- a/FreeFileSync/Source/RealTimeSync/monitor.h +++ b/FreeFileSync/Source/RealTimeSync/monitor.h @@ -13,7 +13,7 @@ namespace rts { -const int UI_UPDATE_INTERVAL = 100; //unit: [ms]; perform ui updates not more often than necessary, 100 seems to be a good value with only a minimal performance loss +const int UI_UPDATE_INTERVAL_MS = 100; //unit: [ms]; perform ui updates not more often than necessary, 100 seems to be a good value with only a minimal performance loss struct MonitorCallback diff --git a/FreeFileSync/Source/RealTimeSync/tray_menu.cpp b/FreeFileSync/Source/RealTimeSync/tray_menu.cpp index c421ff72..83da8f86 100644 --- a/FreeFileSync/Source/RealTimeSync/tray_menu.cpp +++ b/FreeFileSync/Source/RealTimeSync/tray_menu.cpp @@ -5,8 +5,8 @@ // ***************************************************************************** #include "tray_menu.h" +#include <chrono> #include <zen/thread.h> -#include <zen/tick_count.h> #include <wx/taskbar.h> #include <wx/icon.h> //Linux needs this #include <wx/app.h> @@ -25,15 +25,16 @@ using namespace zen; namespace { -const int RETRY_AFTER_ERROR_INTERVAL = 15; //unit: [s] +const int RETRY_AFTER_ERROR_INTERVAL_SEC = 15; //unit: [s] + +std::chrono::steady_clock::time_point lastExec; -const std::int64_t TICKS_UPDATE_INTERVAL = rts::UI_UPDATE_INTERVAL* ticksPerSec() / 1000; -TickVal lastExec = getTicks(); bool updateUiIsAllowed() { - const TickVal now = getTicks(); //0 on error - if (dist(lastExec, now) >= TICKS_UPDATE_INTERVAL) //perform ui updates not more often than necessary + const auto now = std::chrono::steady_clock::now(); + + if (numeric::dist(now, lastExec) > std::chrono::milliseconds(rts::UI_UPDATE_INTERVAL_MS)) //handle potential chrono wrap-around! { lastExec = now; return true; @@ -307,8 +308,8 @@ rts::AbortReason rts::startDirectoryMonitor(const xmlAccess::XmlRealConfig& conf trayIcon.clearShowErrorRequested(); //wait for some time, then return to retry - static_assert(RETRY_AFTER_ERROR_INTERVAL * 1000 % UI_UPDATE_INTERVAL == 0, ""); - for (int i = 0; i < RETRY_AFTER_ERROR_INTERVAL * 1000 / UI_UPDATE_INTERVAL; ++i) + static_assert(RETRY_AFTER_ERROR_INTERVAL_SEC * 1000 % UI_UPDATE_INTERVAL_MS == 0, ""); + for (int i = 0; i < RETRY_AFTER_ERROR_INTERVAL_SEC * 1000 / UI_UPDATE_INTERVAL_MS; ++i) { trayIcon.doUiRefreshNow(); //throw AbortMonitoring @@ -321,7 +322,7 @@ rts::AbortReason rts::startDirectoryMonitor(const xmlAccess::XmlRealConfig& conf case ConfirmationButton::CANCEL: throw AbortMonitoring(SHOW_GUI); } - std::this_thread::sleep_for(std::chrono::milliseconds(UI_UPDATE_INTERVAL)); + std::this_thread::sleep_for(std::chrono::milliseconds(UI_UPDATE_INTERVAL_MS)); } } diff --git a/FreeFileSync/Source/algorithm.cpp b/FreeFileSync/Source/algorithm.cpp index 5dba0989..1def34af 100644 --- a/FreeFileSync/Source/algorithm.cpp +++ b/FreeFileSync/Source/algorithm.cpp @@ -329,14 +329,14 @@ public: private: DetectMovedFiles(BaseFolderPair& baseFolder, const InSyncFolder& dbFolder) : - cmpVar (baseFolder.getCompVariant()), - fileTimeTolerance(baseFolder.getFileTimeTolerance()), - ignoreTimeShiftMinutes(baseFolder.getIgnoredTimeShift()) + cmpVar_ (baseFolder.getCompVariant()), + fileTimeTolerance_(baseFolder.getFileTimeTolerance()), + ignoreTimeShiftMinutes_(baseFolder.getIgnoredTimeShift()) { recurse(baseFolder, &dbFolder); - if ((!exLeftOnlyById .empty() || !exLeftOnlyByPath .empty()) && - (!exRightOnlyById.empty() || !exRightOnlyByPath.empty())) + if ((!exLeftOnlyById_ .empty() || !exLeftOnlyByPath_ .empty()) && + (!exRightOnlyById_.empty() || !exRightOnlyByPath_.empty())) detectMovePairs(dbFolder); } @@ -360,10 +360,10 @@ private: if (cat == FILE_LEFT_SIDE_ONLY) { if (const InSyncFile* dbFile = getDbFileEntry()) - exLeftOnlyByPath.emplace(dbFile, &file); + exLeftOnlyByPath_.emplace(dbFile, &file); else if (!file.getFileId<LEFT_SIDE>().empty()) { - auto rv = exLeftOnlyById.emplace(file.getFileId<LEFT_SIDE>(), &file); + auto rv = exLeftOnlyById_.emplace(file.getFileId<LEFT_SIDE>(), &file); if (!rv.second) //duplicate file ID! NTFS hard link/symlink? rv.first->second = nullptr; } @@ -371,10 +371,10 @@ private: else if (cat == FILE_RIGHT_SIDE_ONLY) { if (const InSyncFile* dbFile = getDbFileEntry()) - exRightOnlyByPath.emplace(dbFile, &file); + exRightOnlyByPath_.emplace(dbFile, &file); else if (!file.getFileId<RIGHT_SIDE>().empty()) { - auto rv = exRightOnlyById.emplace(file.getFileId<RIGHT_SIDE>(), &file); + auto rv = exRightOnlyById_.emplace(file.getFileId<RIGHT_SIDE>(), &file); if (!rv.second) //duplicate file ID! NTFS hard link/symlink? rv.first->second = nullptr; } @@ -443,10 +443,10 @@ private: void findAndSetMovePair(const InSyncFile& dbFile) const { - if (stillInSync(dbFile, cmpVar, fileTimeTolerance, ignoreTimeShiftMinutes)) - if (FilePair* fileLeftOnly = getAssocFilePair<LEFT_SIDE>(dbFile, exLeftOnlyById, exLeftOnlyByPath)) + if (stillInSync(dbFile, cmpVar_, fileTimeTolerance_, ignoreTimeShiftMinutes_)) + if (FilePair* fileLeftOnly = getAssocFilePair<LEFT_SIDE>(dbFile, exLeftOnlyById_, exLeftOnlyByPath_)) if (sameSizeAndDate<LEFT_SIDE>(*fileLeftOnly, dbFile)) - if (FilePair* fileRightOnly = getAssocFilePair<RIGHT_SIDE>(dbFile, exRightOnlyById, exRightOnlyByPath)) + if (FilePair* fileRightOnly = getAssocFilePair<RIGHT_SIDE>(dbFile, exRightOnlyById_, exRightOnlyByPath_)) if (sameSizeAndDate<RIGHT_SIDE>(*fileRightOnly, dbFile)) if (fileLeftOnly ->getMoveRef() == nullptr && //don't let a row participate in two move pairs! fileRightOnly->getMoveRef() == nullptr) // @@ -456,16 +456,16 @@ private: } } - const CompareVariant cmpVar; - const int fileTimeTolerance; - const std::vector<unsigned int> ignoreTimeShiftMinutes; + const CompareVariant cmpVar_; + const int fileTimeTolerance_; + const std::vector<unsigned int> ignoreTimeShiftMinutes_; - std::unordered_map<AFS::FileId, FilePair*, StringHash> exLeftOnlyById; //FilePair* == nullptr for duplicate ids! => consider aliasing through symlinks! - std::unordered_map<AFS::FileId, FilePair*, StringHash> exRightOnlyById; //=> avoid ambiguity for mixtures of files/symlinks on one side and allow 1-1 mapping only! + std::unordered_map<AFS::FileId, FilePair*, StringHash> exLeftOnlyById_; //FilePair* == nullptr for duplicate ids! => consider aliasing through symlinks! + std::unordered_map<AFS::FileId, FilePair*, StringHash> exRightOnlyById_; //=> avoid ambiguity for mixtures of files/symlinks on one side and allow 1-1 mapping only! //MSVC: std::unordered_map: about twice as fast as std::map for 1 million items! - std::unordered_map<const InSyncFile*, FilePair*> exLeftOnlyByPath; //MSVC: only 4% faster than std::map for 1 million items! - std::unordered_map<const InSyncFile*, FilePair*> exRightOnlyByPath; + std::unordered_map<const InSyncFile*, FilePair*> exLeftOnlyByPath_; //MSVC: only 4% faster than std::map for 1 million items! + std::unordered_map<const InSyncFile*, FilePair*> exRightOnlyByPath_; /* detect renamed files: @@ -502,12 +502,9 @@ public: private: RedetermineTwoWay(BaseFolderPair& baseFolder, const InSyncFolder& dbFolder) : - txtBothSidesChanged(_("Both sides have changed since last synchronization.")), - txtNoSideChanged(_("Cannot determine sync-direction:") + L" \n" + _("No change since last synchronization.")), - txtDbNotInSync(_("Cannot determine sync-direction:") + L" \n" + _("The database entry is not in sync considering current settings.")), - cmpVar (baseFolder.getCompVariant()), - fileTimeTolerance (baseFolder.getFileTimeTolerance()), - ignoreTimeShiftMinutes(baseFolder.getIgnoredTimeShift()) + cmpVar_ (baseFolder.getCompVariant()), + fileTimeTolerance_ (baseFolder.getFileTimeTolerance()), + ignoreTimeShiftMinutes_(baseFolder.getIgnoredTimeShift()) { //-> considering filter not relevant: //if narrowing filter: all ok; if widening filter (if file ex on both sides -> conflict, fine; if file ex. on one side: copy to other side: fine) @@ -548,13 +545,13 @@ private: } //evaluation - const bool changeOnLeft = !matchesDbEntry< LEFT_SIDE>(file, dbEntry, ignoreTimeShiftMinutes); - const bool changeOnRight = !matchesDbEntry<RIGHT_SIDE>(file, dbEntry, ignoreTimeShiftMinutes); + const bool changeOnLeft = !matchesDbEntry< LEFT_SIDE>(file, dbEntry, ignoreTimeShiftMinutes_); + const bool changeOnRight = !matchesDbEntry<RIGHT_SIDE>(file, dbEntry, ignoreTimeShiftMinutes_); if (changeOnLeft != changeOnRight) { //if database entry not in sync according to current settings! -> set direction based on sync status only! - if (dbEntry && !stillInSync(dbEntry->second, cmpVar, fileTimeTolerance, ignoreTimeShiftMinutes)) + if (dbEntry && !stillInSync(dbEntry->second, cmpVar_, fileTimeTolerance_, ignoreTimeShiftMinutes_)) file.setSyncDirConflict(txtDbNotInSync); else file.setSyncDir(changeOnLeft ? SyncDirection::RIGHT : SyncDirection::LEFT); @@ -584,13 +581,13 @@ private: } //evaluation - const bool changeOnLeft = !matchesDbEntry< LEFT_SIDE>(symlink, dbEntry, ignoreTimeShiftMinutes); - const bool changeOnRight = !matchesDbEntry<RIGHT_SIDE>(symlink, dbEntry, ignoreTimeShiftMinutes); + const bool changeOnLeft = !matchesDbEntry< LEFT_SIDE>(symlink, dbEntry, ignoreTimeShiftMinutes_); + const bool changeOnRight = !matchesDbEntry<RIGHT_SIDE>(symlink, dbEntry, ignoreTimeShiftMinutes_); if (changeOnLeft != changeOnRight) { //if database entry not in sync according to current settings! -> set direction based on sync status only! - if (dbEntry && !stillInSync(dbEntry->second, cmpVar, fileTimeTolerance, ignoreTimeShiftMinutes)) + if (dbEntry && !stillInSync(dbEntry->second, cmpVar_, fileTimeTolerance_, ignoreTimeShiftMinutes_)) symlink.setSyncDirConflict(txtDbNotInSync); else symlink.setSyncDir(changeOnLeft ? SyncDirection::RIGHT : SyncDirection::LEFT); @@ -650,13 +647,13 @@ private: recurse(folder, dbEntry ? &dbEntry->second : nullptr); } - const std::wstring txtBothSidesChanged; - const std::wstring txtNoSideChanged; - const std::wstring txtDbNotInSync; + const std::wstring txtBothSidesChanged = _("Both sides have changed since last synchronization."); + const std::wstring txtNoSideChanged = _("Cannot determine sync-direction:") + L" \n" + _("No change since last synchronization."); + const std::wstring txtDbNotInSync = _("Cannot determine sync-direction:") + L" \n" + _("The database entry is not in sync considering current settings."); - const CompareVariant cmpVar; - const int fileTimeTolerance; - const std::vector<unsigned int> ignoreTimeShiftMinutes; + const CompareVariant cmpVar_; + const int fileTimeTolerance_; + const std::vector<unsigned int> ignoreTimeShiftMinutes_; }; } @@ -1572,7 +1569,7 @@ void TempFileBuffer::createTempFiles(const std::set<FileDetails>& workLoad, Proc tempPathTmp += Zstr("FFS-"); const std::string guid = generateGUID(); //no need for full-blown (pseudo-)random numbers for this one-time invocation - const uint32_t crc32 = getCrc32(guid.begin(), guid.end()); + const uint32_t crc32 = getCrc32(guid); tempPathTmp += printNumber<Zstring>(Zstr("%08x"), static_cast<unsigned int>(crc32)); makeDirectoryRecursively(tempPathTmp); //throw FileError @@ -1593,7 +1590,7 @@ void TempFileBuffer::createTempFiles(const std::set<FileDetails>& workLoad, Proc writeNumber (cookie, details.descr.isFollowedSymlink); writeContainer(cookie, AFS::getInitPathPhrase(details.path)); - const uint16_t crc16 = getCrc16(cookie.ref().begin(), cookie.ref().end()); + const uint16_t crc16 = getCrc16(cookie.ref()); const Zstring detailsHash = printNumber<Zstring>(Zstr("%04x"), static_cast<unsigned int>(crc16)); const Zstring fileName = AFS::getFileShortName(details.path); diff --git a/FreeFileSync/Source/application.cpp b/FreeFileSync/Source/application.cpp index 1a86d915..15aa04a0 100644 --- a/FreeFileSync/Source/application.cpp +++ b/FreeFileSync/Source/application.cpp @@ -18,17 +18,15 @@ #include "synchronization.h" #include "ui/batch_status_handler.h" #include "ui/main_dlg.h" -#include "ui/switch_to_gui.h" #include "lib/help_provider.h" #include "lib/process_xml.h" #include "lib/error_log.h" #include "lib/resolve_path.h" #ifdef ZEN_WIN - #include <zen/win_ver.h> - //#include <zen/dll.h> - #include "lib/app_user_mode_id.h" #include <zen/service_notification.h> + #include "lib/app_user_mode_id.h" + #include "ui/version_check.h" #elif defined ZEN_LINUX #include <gtk/gtk.h> @@ -67,21 +65,21 @@ std::vector<Zstring> getCommandlineArgs(const wxApp& app) while (endsWith(cmdLine, L' ')) //may end with space cmdLine.pop_back(); - auto iterStart = cmdLine.end(); //end() means: no token + auto itStart = cmdLine.end(); //end() means: no token for (auto it = cmdLine.begin(); it != cmdLine.end(); ++it) if (*it == L' ') //space commits token { - if (iterStart != cmdLine.end()) + if (itStart != cmdLine.end()) { - args.emplace_back(iterStart, it); - iterStart = cmdLine.end(); //expect consecutive blanks! + args.emplace_back(itStart, it); + itStart = cmdLine.end(); //expect consecutive blanks! } } else { //start new token - if (iterStart == cmdLine.end()) - iterStart = it; + if (itStart == cmdLine.end()) + itStart = it; if (*it == L'\"') { @@ -90,8 +88,8 @@ std::vector<Zstring> getCommandlineArgs(const wxApp& app) break; } } - if (iterStart != cmdLine.end()) - args.emplace_back(iterStart, cmdLine.end()); + if (itStart != cmdLine.end()) + args.emplace_back(itStart, cmdLine.end()); if (!args.empty()) args.erase(args.begin()); //remove first argument which is exe path by convention: https://blogs.msdn.microsoft.com/oldnewthing/20060515-07/?p=31203 @@ -131,7 +129,7 @@ bool Application::OnInit() #elif defined ZEN_LINUX ::gtk_init(nullptr, nullptr); - //::gtk_rc_parse((getResourceDir() + "styles.gtk_rc").c_str()); //remove inner border from bitmap buttons + //::gtk_rc_parse((getResourceDirPf() + "styles.gtk_rc").c_str()); //remove inner border from bitmap buttons #endif //Windows User Experience Interaction Guidelines: tool tips should have 5s timeout, info tips no timeout => compromise: @@ -140,17 +138,27 @@ bool Application::OnInit() SetAppName(L"FreeFileSync"); //if not set, the default is the executable's name! - initResourceImages(getResourceDir() + Zstr("Resources.zip")); + initResourceImages(getResourceDirPf() + Zstr("Resources.zip")); + + try + { + //tentatively set program language to OS default until GlobalSettings.xml is read later + setLanguage(xmlAccess::XmlGlobalSettings().programLanguage); //throw FileError + } + catch (const FileError&) { assert(false); } Connect(wxEVT_QUERY_END_SESSION, wxEventHandler(Application::onQueryEndSession), nullptr, this); Connect(wxEVT_END_SESSION, wxEventHandler(Application::onQueryEndSession), nullptr, this); +#ifdef ZEN_WIN + runSanityChecks(); +#endif + //do not call wxApp::OnInit() to avoid using wxWidgets command line parser //Note: app start is deferred: batch mode requires the wxApp eventhandler to be established for UI update events. This is not the case at the time of OnInit()! Connect(EVENT_ENTER_EVENT_LOOP, wxEventHandler(Application::onEnterEventLoop), nullptr, this); AddPendingEvent(wxCommandEvent(EVENT_ENTER_EVENT_LOOP)); - return true; //true: continue processing; false: exit immediately. } @@ -251,13 +259,6 @@ void Application::launch(const std::vector<Zstring>& commandArgs) wxTheApp->SetExitOnFrameDelete(false); //prevent popup-windows from becoming temporary top windows leading to program exit after closure ZEN_ON_SCOPE_EXIT(if (!mainWindowWasSet()) wxTheApp->ExitMainLoop();); //quit application, if no main window was set (batch silent mode) - try - { - //tentatively set program language to OS default until GlobalSettings.xml is read later - setLanguage(xmlAccess::XmlGlobalSettings().programLanguage); //throw FileError - } - catch (const FileError&) { assert(false); } - auto notifyFatalError = [&](const std::wstring& msg, const std::wstring& title) { auto titleTmp = copyStringTo<std::wstring>(wxTheApp->GetAppDisplayName()) + L" - " + title; @@ -595,10 +596,8 @@ void runBatchMode(const Zstring& globalConfigFile, const XmlBatchConfig& batchCf { const TimeComp timeStamp = localTime(); - const SwitchToGui switchBatchToGui(globalConfigFile, globalCfg, referenceFile, batchCfg); //prepare potential operational switch - //class handling status updates and error messages - BatchStatusHandler statusHandler(!batchCfg.runMinimized, //throw BatchAbortProcess + BatchStatusHandler statusHandler(!batchCfg.runMinimized, //throw BatchAbortProcess, BatchRequestSwitchToMainDialog extractJobName(referenceFile), globalCfg.soundFileSyncFinished, timeStamp, @@ -608,7 +607,6 @@ void runBatchMode(const Zstring& globalConfigFile, const XmlBatchConfig& batchCf batchCfg.handleError, globalCfg.automaticRetryCount, globalCfg.automaticRetryDelay, - switchBatchToGui, returnCode, batchCfg.mainCfg.onCompletion, globalCfg.gui.onCompletionHistory); @@ -640,7 +638,7 @@ void runBatchMode(const Zstring& globalConfigFile, const XmlBatchConfig& batchCf globalCfg.createLockFile, dirLocks, cmpConfig, - statusHandler); + statusHandler); //throw ? //START SYNCHRONIZATION const std::vector<FolderPairSyncCfg> syncProcessCfg = extractSyncCfg(batchCfg.mainCfg); @@ -657,9 +655,14 @@ void runBatchMode(const Zstring& globalConfigFile, const XmlBatchConfig& batchCf globalCfg.folderAccessTimeout, syncProcessCfg, cmpResult, - statusHandler); + statusHandler); //throw ? } catch (BatchAbortProcess&) {} //exit used by statusHandler + catch (BatchRequestSwitchToMainDialog&) + { + //open new toplevel window *after* progress dialog is gone => run on main event loop + return MainDialog::create(globalConfigFile, &globalCfg, xmlAccess::convertBatchToGui(batchCfg), { referenceFile }, true /*startComparison*/); + } try //save global settings to XML: e.g. ignored warnings { diff --git a/FreeFileSync/Source/comparison.cpp b/FreeFileSync/Source/comparison.cpp index 4b251e8b..36318542 100644 --- a/FreeFileSync/Source/comparison.cpp +++ b/FreeFileSync/Source/comparison.cpp @@ -45,11 +45,6 @@ namespace { struct ResolvedFolderPair { - ResolvedFolderPair(const AbstractPath& left, - const AbstractPath& right) : - folderPathLeft (left), - folderPathRight(right) {} - AbstractPath folderPathLeft; AbstractPath folderPathRight; }; @@ -83,7 +78,7 @@ ResolvedBaseFolders initializeBaseFolders(const std::vector<FolderPairCfg>& cfgL uniqueBaseFolders.insert(folderPathLeft); uniqueBaseFolders.insert(folderPathRight); - output.resolvedPairs.emplace_back(folderPathLeft, folderPathRight); + output.resolvedPairs.push_back({ folderPathLeft, folderPathRight }); } const FolderStatus status = getFolderStatusNonBlocking(uniqueBaseFolders, folderAccessTimeout, allowUserInteraction, callback); //re-check *all* directories on each try! @@ -128,7 +123,7 @@ void checkForIncompleteInput(const std::vector<ResolvedFolderPair>& folderPairs, else if (!AFS::isNullPath(fp.folderPathLeft)) haveFullPair = true; - if (havePartialPair == haveFullPair) //error if: all empty or exist both full and partial pairs -> support single-dir scenario + if (havePartialPair == haveFullPair) //error if: all empty or exist both full and partial pairs -> support single-folder comparison scenario callback.reportWarning(_("A folder input field is empty.") + L" \n\n" + _("The corresponding folder will be considered as empty."), warningInputFieldEmpty); } @@ -179,7 +174,7 @@ private: std::vector<FilePair*>& undefinedFiles, std::vector<SymlinkPair*>& undefinedSymlinks) const; - std::map<DirectoryKey, DirectoryValue> directoryBuffer; //contains only *existing* directories + std::map<DirectoryKey, DirectoryValue> directoryBuffer_; //contains only *existing* directories const int fileTimeTolerance_; ProcessCallback& callback_; }; @@ -195,8 +190,8 @@ ComparisonBuffer::ComparisonBuffer(const std::set<DirectoryKey>& keysToRead, int void reportStatus(const std::wstring& statusMsg, int itemsTotal) override { - callback_.updateProcessedData(itemsTotal - itemsReported, 0); //processed bytes are reported in subfunctions! - itemsReported = itemsTotal; + callback_.updateProcessedData(itemsTotal - itemsReported_, 0); //processed bytes are reported in subfunctions! + itemsReported_ = itemsTotal; callback_.reportStatus(statusMsg); //may throw //callback_.requestUiRefresh(); //already called by reportStatus() @@ -219,13 +214,13 @@ ComparisonBuffer::ComparisonBuffer(const std::set<DirectoryKey>& keysToRead, int private: ProcessCallback& callback_; - int itemsReported = 0; + int itemsReported_ = 0; } cb(callback); fillBuffer(keysToRead, //in - directoryBuffer, //out + directoryBuffer_, //out cb, - UI_UPDATE_INTERVAL / 2); //every ~50 ms + UI_UPDATE_INTERVAL_MS / 2); //every ~50 ms } @@ -233,38 +228,39 @@ ComparisonBuffer::ComparisonBuffer(const std::set<DirectoryKey>& keysToRead, int //const wchar_t arrowLeft [] = L"\u2190"; //const wchar_t arrowRight[] = L"\u2192"; unicode arrows -> too small -const wchar_t arrowLeft [] = L"<--"; -const wchar_t arrowRight[] = L"-->"; +const wchar_t arrowLeft [] = L"<-"; +const wchar_t arrowRight[] = L"->"; +//NOTE: conflict texts are NOT expected to contain additional path info (already implicit through associated item!) +// => only add path info if information is relevant, e.g. conflict is specific to left/right side only -//check for very old dates or dates in the future -std::wstring getConflictInvalidDate(const std::wstring& displayPath, std::int64_t utcTime) +template <SelectedSide side, class FileOrLinkPair> inline +std::wstring getConflictInvalidDate(const FileOrLinkPair& file) { - return replaceCpy(_("File %x has an invalid date."), L"%x", fmtPath(displayPath)) + L"\n" + - _("Date:") + L" " + utcToLocalTimeString(utcTime); + return replaceCpy(_("File %x has an invalid date."), L"%x", fmtPath(AFS::getDisplayPath(file.template getAbstractPath<side>()))) + L"\n" + + _("Date:") + L" " + utcToLocalTimeString(file.template getLastWriteTime<side>()); } -//check for changed files with same modification date std::wstring getConflictSameDateDiffSize(const FilePair& file) { - return replaceCpy(_("Files %x have the same date but a different size."), L"%x", fmtPath(file.getPairRelativePath())) + L"\n" + - L" " + arrowLeft + L" " + _("Date:") + L" " + utcToLocalTimeString(file.getLastWriteTime< LEFT_SIDE>()) + L" " + _("Size:") + L" " + toGuiString(file.getFileSize<LEFT_SIDE>()) + L"\n" + - L" " + arrowRight + L" " + _("Date:") + L" " + utcToLocalTimeString(file.getLastWriteTime<RIGHT_SIDE>()) + L" " + _("Size:") + L" " + toGuiString(file.getFileSize<RIGHT_SIDE>()); + return _("Files have the same date but a different size.") + L"\n" + + arrowLeft + L" " + _("Date:") + L" " + utcToLocalTimeString(file.getLastWriteTime< LEFT_SIDE>()) + L" " + _("Size:") + L" " + toGuiString(file.getFileSize<LEFT_SIDE>()) + L"\n" + + arrowRight + L" " + _("Date:") + L" " + utcToLocalTimeString(file.getLastWriteTime<RIGHT_SIDE>()) + L" " + _("Size:") + L" " + toGuiString(file.getFileSize<RIGHT_SIDE>()); } std::wstring getConflictSkippedBinaryComparison(const FilePair& file) { - return replaceCpy(_("Content comparison was skipped for excluded files %x."), L"%x", fmtPath(file.getPairRelativePath())); + return _("Content comparison was skipped for excluded files."); } std::wstring getDescrDiffMetaShortnameCase(const FileSystemObject& fsObj) { return _("Items differ in attributes only") + L"\n" + - L" " + arrowLeft + L" " + fmtPath(fsObj.getItemName< LEFT_SIDE>()) + L"\n" + - L" " + arrowRight + L" " + fmtPath(fsObj.getItemName<RIGHT_SIDE>()); + arrowLeft + L" " + fmtPath(fsObj.getItemName< LEFT_SIDE>()) + L"\n" + + arrowRight + L" " + fmtPath(fsObj.getItemName<RIGHT_SIDE>()); } @@ -272,8 +268,8 @@ template <class FileOrLinkPair> std::wstring getDescrDiffMetaDate(const FileOrLinkPair& file) { return _("Items differ in attributes only") + L"\n" + - L" " + arrowLeft + L" " + _("Date:") + L" " + utcToLocalTimeString(file.template getLastWriteTime< LEFT_SIDE>()) + L"\n" + - L" " + arrowRight + L" " + _("Date:") + L" " + utcToLocalTimeString(file.template getLastWriteTime<RIGHT_SIDE>()); + arrowLeft + L" " + _("Date:") + L" " + utcToLocalTimeString(file.template getLastWriteTime< LEFT_SIDE>()) + L"\n" + + arrowRight + L" " + _("Date:") + L" " + utcToLocalTimeString(file.template getLastWriteTime<RIGHT_SIDE>()); } //----------------------------------------------------------------------------- @@ -304,11 +300,11 @@ void categorizeSymlinkByTime(SymlinkPair& symlink) break; case TimeResult::LEFT_INVALID: - symlink.setCategoryConflict(getConflictInvalidDate(AFS::getDisplayPath(symlink.getAbstractPath<LEFT_SIDE>()), symlink.getLastWriteTime<LEFT_SIDE>())); + symlink.setCategoryConflict(getConflictInvalidDate<LEFT_SIDE>(symlink)); break; case TimeResult::RIGHT_INVALID: - symlink.setCategoryConflict(getConflictInvalidDate(AFS::getDisplayPath(symlink.getAbstractPath<RIGHT_SIDE>()), symlink.getLastWriteTime<RIGHT_SIDE>())); + symlink.setCategoryConflict(getConflictInvalidDate<RIGHT_SIDE>(symlink)); break; } } @@ -356,11 +352,11 @@ std::shared_ptr<BaseFolderPair> ComparisonBuffer::compareByTimeSize(const Resolv break; case TimeResult::LEFT_INVALID: - file->setCategoryConflict(getConflictInvalidDate(AFS::getDisplayPath(file->getAbstractPath<LEFT_SIDE>()), file->getLastWriteTime<LEFT_SIDE>())); + file->setCategoryConflict(getConflictInvalidDate<LEFT_SIDE>(*file)); break; case TimeResult::RIGHT_INVALID: - file->setCategoryConflict(getConflictInvalidDate(AFS::getDisplayPath(file->getAbstractPath<RIGHT_SIDE>()), file->getLastWriteTime<RIGHT_SIDE>())); + file->setCategoryConflict(getConflictInvalidDate<RIGHT_SIDE>(*file)); break; } } @@ -734,7 +730,7 @@ void MergeSides::mergeTwoSides(const FolderContainer& lhs, const FolderContainer //----------------------------------------------------------------------------------------------- -//mark excluded directories (see fillBuffer()) + remove superfluous excluded subdirectories +//uncheck excluded directories (see fillBuffer()) + remove superfluous excluded subdirectories void stripExcludedDirectories(HierarchyObject& hierObj, const HardFilter& filterProc) { for (FolderPair& folder : hierObj.refSubFolders()) @@ -771,8 +767,8 @@ std::shared_ptr<BaseFolderPair> ComparisonBuffer::performComparison(const Resolv auto getDirValue = [&](const AbstractPath& folderPath) -> const DirectoryValue* { - auto it = directoryBuffer.find(DirectoryKey(folderPath, fpCfg.filter.nameFilter, fpCfg.handleSymlinks)); - return it != directoryBuffer.end() ? &it->second : nullptr; + auto it = directoryBuffer_.find(DirectoryKey(folderPath, fpCfg.filter.nameFilter, fpCfg.handleSymlinks)); + return it != directoryBuffer_.end() ? &it->second : nullptr; }; const DirectoryValue* bufValueLeft = getDirValue(fp.folderPathLeft); @@ -904,26 +900,23 @@ FolderComparison zen::compare(xmlAccess::OptionalDialogs& warnings, callback.reportInfo(e.toString()); //may throw! } - //-------------------some basic checks:------------------------------------------ - const ResolvedBaseFolders& resInfo = initializeBaseFolders(cfgList, folderAccessTimeout, allowUserInteraction, callback); - //directory existence only checked *once* to avoid race conditions! if (resInfo.resolvedPairs.size() != cfgList.size()) throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); + auto basefolderExisting = [&](const AbstractPath& folderPath) { return resInfo.existingBaseFolders.find(folderPath) != resInfo.existingBaseFolders.end(); }; + + //-----------execute basic checks all at once before starting comparison---------- + checkForIncompleteInput(resInfo.resolvedPairs, warnings.warningInputFieldEmpty, callback); checkFolderDependency (resInfo.resolvedPairs, warnings.warningDependentFolders, callback); - //list of directories that are *expected* to be existent (and need to be scanned)! - //-------------------end of basic checks------------------------------------------ - auto basefolderExisting = [&](const AbstractPath& folderPath) { return resInfo.existingBaseFolders.find(folderPath) != resInfo.existingBaseFolders.end(); }; - - std::vector<std::pair<ResolvedFolderPair, FolderPairCfg>> totalWorkLoad; + std::vector<std::pair<ResolvedFolderPair, FolderPairCfg>> workLoad; for (size_t i = 0; i < cfgList.size(); ++i) - totalWorkLoad.emplace_back(resInfo.resolvedPairs[i], cfgList[i]); + workLoad.emplace_back(resInfo.resolvedPairs[i], cfgList[i]); //lock (existing) directories before comparison if (createDirLocks) @@ -941,9 +934,9 @@ FolderComparison zen::compare(xmlAccess::OptionalDialogs& warnings, //------------------- fill directory buffer --------------------------------------------------- std::set<DirectoryKey> dirsToRead; - for (const auto& w : totalWorkLoad) + for (const auto& w : workLoad) { - if (basefolderExisting(w.first.folderPathLeft)) //only traverse *currently existing* directories: at this point user is aware that non-ex + empty string are seen as empty folder! + if (basefolderExisting(w.first.folderPathLeft)) //only traverse *currently existing* folders: at this point user is aware that non-ex + empty string are seen as empty folder! dirsToRead.emplace(w.first.folderPathLeft, w.second.filter.nameFilter, w.second.handleSymlinks); if (basefolderExisting(w.first.folderPathRight)) dirsToRead.emplace(w.first.folderPathRight, w.second.filter.nameFilter, w.second.handleSymlinks); @@ -960,7 +953,7 @@ FolderComparison zen::compare(xmlAccess::OptionalDialogs& warnings, //process binary comparison as one junk std::vector<std::pair<ResolvedFolderPair, FolderPairCfg>> workLoadByContent; - for (const auto& w : totalWorkLoad) + for (const auto& w : workLoad) switch (w.second.compareVar) { case CompareVariant::TIME_SIZE: @@ -973,7 +966,7 @@ FolderComparison zen::compare(xmlAccess::OptionalDialogs& warnings, std::list<std::shared_ptr<BaseFolderPair>> outputByContent = cmpBuff.compareByContent(workLoadByContent); //write output in expected order - for (const auto& w : totalWorkLoad) + for (const auto& w : workLoad) switch (w.second.compareVar) { case CompareVariant::TIME_SIZE: @@ -996,14 +989,14 @@ FolderComparison zen::compare(xmlAccess::OptionalDialogs& warnings, //--------- set initial sync-direction -------------------------------------------------- - for (auto j = begin(output); j != end(output); ++j) + for (auto it = begin(output); it != end(output); ++it) { - const FolderPairCfg& fpCfg = cfgList[j - output.begin()]; + const FolderPairCfg& fpCfg = cfgList[it - output.begin()]; callback.reportStatus(_("Calculating sync directions...")); callback.forceUiRefresh(); - zen::redetermineSyncDirection(fpCfg.directionCfg, *j, + zen::redetermineSyncDirection(fpCfg.directionCfg, *it, [&](const std::wstring& warning) { callback.reportWarning(warning, warnings.warningDatabaseError); }, [&](std::int64_t bytesDelta) { callback.requestUiRefresh(); });//throw X } diff --git a/FreeFileSync/Source/file_hierarchy.cpp b/FreeFileSync/Source/file_hierarchy.cpp index 98b72662..0d517525 100644 --- a/FreeFileSync/Source/file_hierarchy.cpp +++ b/FreeFileSync/Source/file_hierarchy.cpp @@ -144,6 +144,9 @@ bool hasDirectChild(const HierarchyObject& hierObj, Predicate p) std::any_of(hierObj.refSubLinks ().begin(), hierObj.refSubLinks ().end(), p) || std::any_of(hierObj.refSubFolders().begin(), hierObj.refSubFolders().end(), p); } + +const wchar_t arrowLeft [] = L"<-"; +const wchar_t arrowRight[] = L"->"; } @@ -316,12 +319,44 @@ std::wstring zen::getCategoryDescription(CompareFilesResult cmpRes) std::wstring zen::getCategoryDescription(const FileSystemObject& fsObj) { + const std::wstring footer = L"\n[" + utfCvrtTo<std::wstring>(fsObj. getPairItemName()) + L"]"; + const CompareFilesResult cmpRes = fsObj.getCategory(); - if (cmpRes == FILE_CONFLICT || - cmpRes == FILE_DIFFERENT_METADATA) - return fsObj.getCatExtraDescription(); + switch (cmpRes) + { + case FILE_LEFT_SIDE_ONLY: + case FILE_RIGHT_SIDE_ONLY: + case FILE_DIFFERENT_CONTENT: + case FILE_EQUAL: + return getCategoryDescription(cmpRes) + footer; //use generic description + + case FILE_LEFT_NEWER: + case FILE_RIGHT_NEWER: + { + std::wstring descr = getCategoryDescription(cmpRes); - return getCategoryDescription(cmpRes); + visitFSObject(fsObj, [](const FolderPair& folder) {}, + [&](const FilePair& file) + { + descr = descr + L"\n" + + arrowLeft + L" " + zen::utcToLocalTimeString(file.getLastWriteTime< LEFT_SIDE>()) + L"\n" + + arrowRight + L" " + zen::utcToLocalTimeString(file.getLastWriteTime<RIGHT_SIDE>()); + }, + [&](const SymlinkPair& symlink) + { + descr = descr + L"\n" + + arrowLeft + L" " + zen::utcToLocalTimeString(symlink.getLastWriteTime< LEFT_SIDE>()) + L"\n" + + arrowRight + L" " + zen::utcToLocalTimeString(symlink.getLastWriteTime<RIGHT_SIDE>()); + }); + return descr + footer; + } + + case FILE_DIFFERENT_METADATA: + case FILE_CONFLICT: + return fsObj.getCatExtraDescription() + footer; + } + assert(false); + return std::wstring(); } @@ -365,6 +400,8 @@ std::wstring zen::getSyncOpDescription(SyncOperation op) std::wstring zen::getSyncOpDescription(const FileSystemObject& fsObj) { + const std::wstring footer = L"\n[" + utfCvrtTo<std::wstring>(fsObj. getPairItemName()) + L"]"; + const SyncOperation op = fsObj.getSyncOperation(); switch (op) { @@ -376,7 +413,7 @@ std::wstring zen::getSyncOpDescription(const FileSystemObject& fsObj) case SO_OVERWRITE_RIGHT: case SO_DO_NOTHING: case SO_EQUAL: - return getSyncOpDescription(op); //use generic description + return getSyncOpDescription(op) + footer; //use generic description case SO_COPY_METADATA_TO_LEFT: case SO_COPY_METADATA_TO_RIGHT: @@ -389,11 +426,10 @@ std::wstring zen::getSyncOpDescription(const FileSystemObject& fsObj) if (shortNameOld != shortNameNew) //detected change in case return getSyncOpDescription(op) + L"\n" + - fmtPath(shortNameOld) + L" ->\n" + //show short name only - fmtPath(shortNameNew); + fmtPath(shortNameOld) + L" " + arrowRight + L"\n" + //show short name only + fmtPath(shortNameNew) /*+ footer -> redundant */; } - //fallback: - return getSyncOpDescription(op); + return getSyncOpDescription(op) + footer; //fallback case SO_MOVE_LEFT_SOURCE: case SO_MOVE_LEFT_TARGET: @@ -413,21 +449,21 @@ std::wstring zen::getSyncOpDescription(const FileSystemObject& fsObj) const Zstring relSource = getRelName(*sourceFile, onLeft); const Zstring relTarget = getRelName(*targetFile, !onLeft); + //attention: ::SetWindowText() doesn't handle tab characters correctly in combination with certain file names, so don't use them return getSyncOpDescription(op) + L"\n" + (equalFilePath(beforeLast(relSource, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE), beforeLast(relTarget, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE)) ? //detected pure "rename" - fmtPath(afterLast(relSource, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL)) + L" ->\n" + //show short name only + fmtPath(afterLast(relSource, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL)) + L" " + arrowRight + L"\n" + //show short name only fmtPath(afterLast(relTarget, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL)) : //"move" or "move + rename" - fmtPath(relSource) + L" ->\n" + - fmtPath(relTarget)); - //attention: ::SetWindowText() doesn't handle tab characters correctly in combination with certain file names, so don't use them + fmtPath(relSource) + L" " + arrowRight + L"\n" + + fmtPath(relTarget)) /*+ footer -> redundant */; } break; case SO_UNRESOLVED_CONFLICT: - return fsObj.getSyncOpConflict(); + return fsObj.getSyncOpConflict() + footer; } assert(false); diff --git a/FreeFileSync/Source/fs/abstract.h b/FreeFileSync/Source/fs/abstract.h index 302c99db..86688543 100644 --- a/FreeFileSync/Source/fs/abstract.h +++ b/FreeFileSync/Source/fs/abstract.h @@ -44,18 +44,22 @@ struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model t static bool isNullPath(const AbstractPath& ap) { return ap.afs->isNullPath(ap.itemPathImpl); } - static AbstractPath appendRelPath(const AbstractPath& ap, const Zstring& relPath) + static AbstractPath appendRelPath(const AbstractPath& ap, const Zstring& relPath); + + struct PathComplement { -#ifdef ZEN_WIN - assert(!contains(relPath, L"/")); //relPath is expected to use FILE_NAME_SEPARATOR! -#endif - assert(relPath.empty() || (!startsWith(relPath, FILE_NAME_SEPARATOR) && !endsWith(relPath, FILE_NAME_SEPARATOR))); - return AbstractPath(ap.afs, ap.afs->appendRelPathToItemPathImpl(ap.itemPathImpl, relPath)); - } + Zstring relPathL; //- relative path is filled only if the corresponding abstract path is a sub folder of the other (=> both relative paths are empty if abstract paths match) + Zstring relPathR; //- without FILE_NAME_SEPARATOR prefix + }; + static Opt<PathComplement> getPathComplement(const AbstractPath& lhs, const AbstractPath& rhs); static Zstring getFileShortName(const AbstractPath& ap) { return ap.afs->getFileShortName(ap.itemPathImpl); } - static bool havePathDependency(const AbstractPath& lhs, const AbstractPath& rhs); + static bool havePathDependency(const AbstractPath& lhs, const AbstractPath& rhs) + { + warn_static("remove after migration") + return typeid(*lhs.afs) != typeid(*rhs.afs) ? false : lhs.afs->havePathDependencySameAfsType(lhs.itemPathImpl, rhs); + } static Opt<Zstring> getNativeItemPath(const AbstractPath& ap) { return ap.afs->isNativeFileSystem() ? Opt<Zstring>(ap.itemPathImpl) : NoValue(); } @@ -140,9 +144,9 @@ struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model t private: std::unique_ptr<OutputStreamImpl> outStream_; //bound! const AbstractPath filePath_; - bool finalizeSucceeded = false; - Opt<std::uint64_t> bytesExpected; - std::uint64_t bytesWrittenTotal = 0; + bool finalizeSucceeded_ = false; + Opt<std::uint64_t> bytesExpected_; + std::uint64_t bytesWrittenTotal_ = 0; }; //return value always bound: @@ -278,13 +282,15 @@ private: virtual Zstring appendRelPathToItemPathImpl(const Zstring& itemPathImpl, const Zstring& relPath) const = 0; + virtual Opt<PathComplement> getPathComplementSameAfsType(const Zstring& itemPathImplLhs, const AbstractPath& apRhs) const = 0; + + virtual bool lessItemPathSameAfsType(const Zstring& itemPathImplLhs, const AbstractPath& apRhs) const = 0; + //used during folder creation if parent folder is missing virtual Opt<Zstring> getParentFolderPathImpl(const Zstring& itemPathImpl) const = 0; virtual Zstring getFileShortName(const Zstring& itemPathImpl) const = 0; - virtual bool lessItemPathSameAfsType(const Zstring& itemPathImplLhs, const AbstractPath& apRhs) const = 0; - virtual bool havePathDependencySameAfsType(const Zstring& itemPathImplLhs, const AbstractPath& apRhs) const = 0; //---------------------------------------------------------------------------------------------------------------- @@ -414,12 +420,39 @@ bool AbstractFileSystem::equalAbstractPath(const AbstractPath& lhs, const Abstra return !LessAbstractPath()(lhs, rhs) && !LessAbstractPath()(rhs, lhs); } +namespace impl +{ +inline +bool isValidRelPath(const Zstring& relPath) +{ +#ifdef ZEN_WIN + const bool check1 = !contains(relPath, L'/'); //relPath is expected to use FILE_NAME_SEPARATOR! +#elif defined ZEN_LINUX || defined ZEN_MAC + const bool check1 = !contains(relPath, '\\'); +#endif + const bool check2 = relPath.empty() || (!startsWith(relPath, FILE_NAME_SEPARATOR) && !endsWith(relPath, FILE_NAME_SEPARATOR)); + return check1 && check2; +} +} inline -bool AbstractFileSystem::havePathDependency(const AbstractPath& lhs, const AbstractPath& rhs) +AbstractPath AbstractFileSystem::appendRelPath(const AbstractPath& ap, const Zstring& relPath) { - return typeid(*lhs.afs) != typeid(*rhs.afs) ? false : lhs.afs->havePathDependencySameAfsType(lhs.itemPathImpl, rhs); -}; + assert(impl::isValidRelPath(relPath)); + return AbstractPath(ap.afs, ap.afs->appendRelPathToItemPathImpl(ap.itemPathImpl, relPath)); +} + + +inline +auto AbstractFileSystem::getPathComplement(const AbstractPath& lhs, const AbstractPath& rhs) -> Opt<PathComplement> +{ + if (typeid(*lhs.afs) != typeid(*rhs.afs)) + return NoValue(); + Opt<PathComplement> result = lhs.afs->getPathComplementSameAfsType(lhs.itemPathImpl, rhs); + assert(!result || (impl::isValidRelPath(result->relPathL)&& impl::isValidRelPath(result->relPathR))); + assert(!result || result->relPathL.empty() || result->relPathR.empty()); + return result; +} inline @@ -452,7 +485,7 @@ AbstractFileSystem::OutputStream::OutputStream(std::unique_ptr<OutputStreamImpl> outStream_(std::move(outStream)), filePath_(filePath) { if (streamSize) - bytesExpected = *streamSize; + bytesExpected_ = *streamSize; } @@ -463,7 +496,7 @@ AbstractFileSystem::OutputStream::~OutputStream() outStream_.reset(); //close file handle *before* remove! - if (!finalizeSucceeded) //transactional output stream! => clean up! + if (!finalizeSucceeded_) //transactional output stream! => clean up! try { AbstractFileSystem::removeFile(filePath_); /*throw FileError*/ } catch (FileError& e) { (void)e; assert(false); } } @@ -479,7 +512,7 @@ size_t AbstractFileSystem::OutputStream::tryWrite(const void* data, size_t len) if (bytesWritten > len) throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); - bytesWrittenTotal += bytesWritten; + bytesWrittenTotal_ += bytesWritten; return bytesWritten; } @@ -488,14 +521,14 @@ inline AbstractFileSystem::FileId AbstractFileSystem::OutputStream::finalize(const std::function<void()>& onUpdateStatus) //throw FileError { //important check: catches corrupt sftp download with libssh2! - if (bytesExpected && *bytesExpected != bytesWrittenTotal) + if (bytesExpected_ && *bytesExpected_ != bytesWrittenTotal_) throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getDisplayPath(filePath_))), replaceCpy(replaceCpy(_("Unexpected size of data stream.\nExpected: %x bytes\nActual: %y bytes"), - L"%x", numberTo<std::wstring>(*bytesExpected)), - L"%y", numberTo<std::wstring>(bytesWrittenTotal))); + L"%x", numberTo<std::wstring>(*bytesExpected_)), + L"%y", numberTo<std::wstring>(bytesWrittenTotal_))); FileId fileId = outStream_->finalize(onUpdateStatus); //throw FileError - finalizeSucceeded = true; + finalizeSucceeded_ = true; return fileId; } diff --git a/FreeFileSync/Source/fs/native.cpp b/FreeFileSync/Source/fs/native.cpp index 994d3388..9c341769 100644 --- a/FreeFileSync/Source/fs/native.cpp +++ b/FreeFileSync/Source/fs/native.cpp @@ -264,6 +264,24 @@ private: Zstring appendRelPathToItemPathImpl(const Zstring& itemPathImpl, const Zstring& relPath) const override { return appendPaths(itemPathImpl, relPath, FILE_NAME_SEPARATOR); } + Opt<PathComplement> getPathComplementSameAfsType(const Zstring& itemPathImplLhs, const AbstractPath& apRhs) const override + { + const Zstring& lhs = appendSeparator(itemPathImplLhs); + const Zstring& rhs = appendSeparator(getItemPathImpl(apRhs)); + + const size_t lenMin = std::min(lhs.length(), rhs.length()); + + if (cmpFilePath(lhs.c_str(), lenMin, + rhs.c_str(), lenMin) != 0) + return NoValue(); + + return PathComplement({ Zstring(lhs.begin() + lenMin, lhs.end()), + Zstring(rhs.begin() + lenMin, rhs.end()) + }); + } + + bool lessItemPathSameAfsType(const Zstring& itemPathImplLhs, const AbstractPath& apRhs) const override { return LessFilePath()(itemPathImplLhs, getItemPathImpl(apRhs)); } + //used during folder creation if parent folder is missing Opt<Zstring> getParentFolderPathImpl(const Zstring& itemPathImpl) const override { @@ -292,8 +310,6 @@ private: Zstring getFileShortName(const Zstring& itemPathImpl) const override { return afterLast(itemPathImpl, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); } - bool lessItemPathSameAfsType(const Zstring& itemPathImplLhs, const AbstractPath& apRhs) const override { return LessFilePath()(itemPathImplLhs, getItemPathImpl(apRhs)); } - bool havePathDependencySameAfsType(const Zstring& itemPathImplLhs, const AbstractPath& apRhs) const override { const Zstring& lhs = appendSeparator(itemPathImplLhs); diff --git a/FreeFileSync/Source/fs/native_traverser_impl.h b/FreeFileSync/Source/fs/native_traverser_impl.h index dedc5c61..35891c8a 100644 --- a/FreeFileSync/Source/fs/native_traverser_impl.h +++ b/FreeFileSync/Source/fs/native_traverser_impl.h @@ -77,7 +77,7 @@ private: { struct ::dirent* dirEntry = nullptr; if (::readdir_r(dirObj, reinterpret_cast< ::dirent*>(&buffer[0]), &dirEntry) != 0) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtPath(dirPath)), L"readdir_r"); + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), L"readdir_r"); //don't retry but restart dir traversal on error! http://blogs.msdn.com/b/oldnewthing/archive/2014/06/12/10533529.aspx if (!dirEntry) //no more items @@ -86,7 +86,7 @@ private: //don't return "." and ".." const char* itemName = dirEntry->d_name; //evaluate dirEntry *before* going into recursion => we use a single "buffer"! - if (itemName[0] == 0) throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtPath(dirPath)), L"readdir_r: Data corruption; item is missing a name."); + if (itemName[0] == 0) throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), L"readdir_r: Data corruption; item is missing a name."); if (itemName[0] == '.' && (itemName[1] == 0 || (itemName[1] == '.' && itemName[2] == 0))) continue; diff --git a/FreeFileSync/Source/lib/binary.cpp b/FreeFileSync/Source/lib/binary.cpp index 38fc0fda..0d9b9549 100644 --- a/FreeFileSync/Source/lib/binary.cpp +++ b/FreeFileSync/Source/lib/binary.cpp @@ -39,22 +39,22 @@ const size_t BLOCK_SIZE_MAX = 16 * 1024 * 1024; struct StreamReader { StreamReader(const AbstractPath& filePath, const std::function<void(std::int64_t bytesDelta)>& notifyProgress, size_t& unevenBytes) : - stream(AFS::getInputStream(filePath)), //throw FileError, (ErrorFileLocked) - defaultBlockSize(stream->getBlockSize()), - dynamicBlockSize(defaultBlockSize), + stream_(AFS::getInputStream(filePath)), //throw FileError, (ErrorFileLocked) + defaultBlockSize_(stream_->getBlockSize()), + dynamicBlockSize_(defaultBlockSize_), notifyProgress_(notifyProgress), unevenBytes_(unevenBytes) {} void appendChunk(std::vector<char>& buffer) //throw FileError { - assert(!eof); - if (eof) return; + assert(!eof_); + if (eof_) return; const auto startTime = std::chrono::steady_clock::now(); - buffer.resize(buffer.size() + dynamicBlockSize); - const size_t bytesRead = stream->tryRead(&*(buffer.end() - dynamicBlockSize), dynamicBlockSize); //throw FileError; may return short, only 0 means EOF! => CONTRACT: bytesToRead > 0 - buffer.resize(buffer.size() - dynamicBlockSize + bytesRead); //caveat: unsigned arithmetics + buffer.resize(buffer.size() + dynamicBlockSize_); + const size_t bytesRead = stream_->tryRead(&*(buffer.end() - dynamicBlockSize_), dynamicBlockSize_); //throw FileError; may return short, only 0 means EOF! => CONTRACT: bytesToRead > 0 + buffer.resize(buffer.size() - dynamicBlockSize_ + bytesRead); //caveat: unsigned arithmetics const auto stopTime = std::chrono::steady_clock::now(); @@ -68,7 +68,7 @@ struct StreamReader if (bytesRead == 0) { - eof = true; + eof_ = true; return; } @@ -76,31 +76,31 @@ struct StreamReader const auto loopTimeMs = std::chrono::duration_cast<std::chrono::milliseconds>(stopTime - startTime).count(); if (loopTimeMs >= 100) - lastDelayViolation = stopTime; + lastDelayViolation_ = stopTime; //avoid "flipping back": e.g. DVD-ROMs read 32MB at once, so first read may be > 500 ms, but second one will be 0ms! - if (stopTime >= lastDelayViolation + std::chrono::seconds(2)) + if (stopTime >= lastDelayViolation_ + std::chrono::seconds(2)) { - lastDelayViolation = stopTime; - proposedBlockSize = dynamicBlockSize * 2; + lastDelayViolation_ = stopTime; + proposedBlockSize = dynamicBlockSize_ * 2; } if (loopTimeMs > 500) - proposedBlockSize = dynamicBlockSize / 2; + proposedBlockSize = dynamicBlockSize_ / 2; - if (defaultBlockSize <= proposedBlockSize && proposedBlockSize <= BLOCK_SIZE_MAX) - dynamicBlockSize = proposedBlockSize; + if (defaultBlockSize_ <= proposedBlockSize && proposedBlockSize <= BLOCK_SIZE_MAX) + dynamicBlockSize_ = proposedBlockSize; } - bool isEof() const { return eof; } + bool isEof() const { return eof_; } private: - const std::unique_ptr<AFS::InputStream> stream; - const size_t defaultBlockSize; - size_t dynamicBlockSize; + const std::unique_ptr<AFS::InputStream> stream_; + const size_t defaultBlockSize_; + size_t dynamicBlockSize_; const std::function<void(std::int64_t bytesDelta)> notifyProgress_; size_t& unevenBytes_; - std::chrono::steady_clock::time_point lastDelayViolation = std::chrono::steady_clock::now(); - bool eof = false; + std::chrono::steady_clock::time_point lastDelayViolation_ = std::chrono::steady_clock::now(); + bool eof_ = false; }; } diff --git a/FreeFileSync/Source/lib/db_file.cpp b/FreeFileSync/Source/lib/db_file.cpp index 7fb7977e..ea679635 100644 --- a/FreeFileSync/Source/lib/db_file.cpp +++ b/FreeFileSync/Source/lib/db_file.cpp @@ -141,11 +141,11 @@ DbStreams loadStreams(const AbstractPath& dbPath, const std::function<void(std:: } catch (UnexpectedEndOfStreamError&) { - throw FileError(_("Database file is corrupt:") + L"\n" + fmtPath(AFS::getDisplayPath(dbPath)), L"Unexpected end of stream."); + throw FileError(_("Database file is corrupted:") + L"\n" + fmtPath(AFS::getDisplayPath(dbPath)), L"Unexpected end of stream."); } catch (const std::bad_alloc& e) //still required? { - throw FileError(_("Database file is corrupt:") + L"\n" + fmtPath(AFS::getDisplayPath(dbPath)), + throw FileError(_("Database file is corrupted:") + L"\n" + fmtPath(AFS::getDisplayPath(dbPath)), _("Out of memory.") + L" " + utfCvrtTo<std::wstring>(e.what())); } } @@ -305,7 +305,7 @@ public: const int streamVersionR = readNumber<std::int32_t>(inR); // if (streamVersionL != streamVersionR) - throw FileError(_("Database file is corrupt:") + L"\n" + fmtPath(displayFilePathL) + L"\n" + fmtPath(displayFilePathR), L"different stream formats"); + throw FileError(_("Database file is corrupted:") + L"\n" + fmtPath(displayFilePathL) + L"\n" + fmtPath(displayFilePathR), L"different stream formats"); if (streamVersionL != DB_FORMAT_STREAM) throw FileError(replaceCpy(_("Database file %x is incompatible."), L"%x", fmtPath(displayFilePathL)), L"unknown stream format"); @@ -314,7 +314,7 @@ public: const bool has1stPartR = readNumber<std::int8_t>(inR) != 0; // if (has1stPartL == has1stPartR) - throw FileError(_("Database file is corrupt:") + L"\n" + fmtPath(displayFilePathL) + L"\n" + fmtPath(displayFilePathR), L"second part missing"); + throw FileError(_("Database file is corrupted:") + L"\n" + fmtPath(displayFilePathL) + L"\n" + fmtPath(displayFilePathR), L"second part missing"); MemStreamIn& in1stPart = has1stPartL ? inL : inR; MemStreamIn& in2ndPart = has1stPartL ? inR : inL; @@ -340,11 +340,11 @@ public: } catch (const UnexpectedEndOfStreamError&) { - throw FileError(_("Database file is corrupt:") + L"\n" + fmtPath(displayFilePathL) + L"\n" + fmtPath(displayFilePathR), L"Unexpected end of stream."); + throw FileError(_("Database file is corrupted:") + L"\n" + fmtPath(displayFilePathL) + L"\n" + fmtPath(displayFilePathR), L"Unexpected end of stream."); } catch (const std::bad_alloc& e) { - throw FileError(_("Database file is corrupt:") + L"\n" + fmtPath(displayFilePathL) + L"\n" + fmtPath(displayFilePathR), + throw FileError(_("Database file is corrupted:") + L"\n" + fmtPath(displayFilePathL) + L"\n" + fmtPath(displayFilePathR), _("Out of memory.") + L" " + utfCvrtTo<std::wstring>(e.what())); } } diff --git a/FreeFileSync/Source/lib/dir_exist_async.h b/FreeFileSync/Source/lib/dir_exist_async.h index d3eaa9c1..d790404e 100644 --- a/FreeFileSync/Source/lib/dir_exist_async.h +++ b/FreeFileSync/Source/lib/dir_exist_async.h @@ -10,6 +10,7 @@ #include <list> #include <zen/thread.h> #include <zen/file_error.h> +#include <zen/basic_math.h> #include "../fs/abstract.h" #include "../process_callback.h" @@ -49,7 +50,7 @@ FolderStatus getFolderStatusNonBlocking(const std::set<AbstractPath, AFS::LessAb })); //don't wait (almost) endlessly like Win32 would on non-existing network shares: - std::chrono::steady_clock::time_point stopTime = std::chrono::steady_clock::now() + std::chrono::seconds(folderAccessTimeout); + const auto startTime = std::chrono::steady_clock::now(); for (auto& fi : futureInfo) { @@ -57,8 +58,8 @@ FolderStatus getFolderStatusNonBlocking(const std::set<AbstractPath, AFS::LessAb procCallback.reportStatus(replaceCpy(_("Searching for folder %x..."), L"%x", displayPathFmt)); //may throw! - while (std::chrono::steady_clock::now() < stopTime && - fi.second.wait_for(std::chrono::milliseconds(UI_UPDATE_INTERVAL / 2)) != std::future_status::ready) + while (numeric::dist(std::chrono::steady_clock::now(), startTime) < std::chrono::seconds(folderAccessTimeout) && //handle potential chrono wrap-around! + fi.second.wait_for(std::chrono::milliseconds(UI_UPDATE_INTERVAL_MS / 2)) != std::future_status::ready) procCallback.requestUiRefresh(); //may throw! if (isReady(fi.second)) diff --git a/FreeFileSync/Source/lib/dir_lock.cpp b/FreeFileSync/Source/lib/dir_lock.cpp index 6a8960ca..e2184781 100644 --- a/FreeFileSync/Source/lib/dir_lock.cpp +++ b/FreeFileSync/Source/lib/dir_lock.cpp @@ -364,12 +364,12 @@ std::string retrieveLockId(const Zstring& lockFilePath) //throw FileError } -enum ProcessStatus +enum class ProcessStatus { - PROC_STATUS_NOT_RUNNING, - PROC_STATUS_RUNNING, - PROC_STATUS_ITS_US, - PROC_STATUS_CANT_TELL + NOT_RUNNING, + RUNNING, + ITS_US, + CANT_TELL, }; ProcessStatus getProcessStatus(const LockInformation& lockInfo) //throw FileError @@ -378,15 +378,15 @@ ProcessStatus getProcessStatus(const LockInformation& lockInfo) //throw FileErro if (lockInfo.computerName != localInfo.computerName || lockInfo.userId != localInfo.userId) //another user may run a session right now! - return PROC_STATUS_CANT_TELL; //lock owned by different computer in this network + return ProcessStatus::CANT_TELL; //lock owned by different computer in this network if (lockInfo.sessionId == localInfo.sessionId && lockInfo.processId == localInfo.processId) //obscure, but possible: deletion failed or a lock file is "stolen" and put back while the program is running - return PROC_STATUS_ITS_US; + return ProcessStatus::ITS_US; if (Opt<SessionId> sessionId = getSessionId(lockInfo.processId)) //throw FileError - return *sessionId == lockInfo.sessionId ? PROC_STATUS_RUNNING : PROC_STATUS_NOT_RUNNING; - return PROC_STATUS_NOT_RUNNING; + return *sessionId == lockInfo.sessionId ? ProcessStatus::RUNNING : ProcessStatus::NOT_RUNNING; + return ProcessStatus::NOT_RUNNING; } @@ -412,12 +412,12 @@ void waitOnDirLock(const Zstring& lockFilePath, DirLockCallback* callback) //thr originalLockId = lockInfo.lockId; switch (getProcessStatus(lockInfo)) //throw FileError { - case PROC_STATUS_ITS_US: //since we've already passed LockAdmin, the lock file seems abandoned ("stolen"?) although it's from this process - case PROC_STATUS_NOT_RUNNING: + case ProcessStatus::ITS_US: //since we've already passed LockAdmin, the lock file seems abandoned ("stolen"?) although it's from this process + case ProcessStatus::NOT_RUNNING: lockOwnderDead = true; break; - case PROC_STATUS_RUNNING: - case PROC_STATUS_CANT_TELL: + case ProcessStatus::RUNNING: + case ProcessStatus::CANT_TELL: break; } } diff --git a/FreeFileSync/Source/lib/error_log.h b/FreeFileSync/Source/lib/error_log.h index bef4a75d..3241803e 100644 --- a/FreeFileSync/Source/lib/error_log.h +++ b/FreeFileSync/Source/lib/error_log.h @@ -34,7 +34,7 @@ void logFatalError(const std::string& msg) //throw() const std::string logEntry = "[" + formatTime<std::string>(FORMAT_DATE) + " "+ formatTime<std::string>(FORMAT_TIME) + "] " + msg; try { - saveBinContainer(getConfigDir() + Zstr("LastError.log"), logEntry, nullptr); //throw FileError + saveBinContainer(getConfigDirPathPf() + Zstr("LastError.log"), logEntry, nullptr); //throw FileError } catch (const FileError&) {} } diff --git a/FreeFileSync/Source/lib/ffs_paths.cpp b/FreeFileSync/Source/lib/ffs_paths.cpp index 9df9ce5e..f57409d8 100644 --- a/FreeFileSync/Source/lib/ffs_paths.cpp +++ b/FreeFileSync/Source/lib/ffs_paths.cpp @@ -30,22 +30,22 @@ Zstring getExecutablePathPf() //directory containing executable WITH path separa return appendSeparator(beforeLast(utfCvrtTo<Zstring>(wxStandardPaths::Get().GetExecutablePath()), FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE)); } #endif +} + #ifdef ZEN_WIN -inline -Zstring getInstallFolderPathPf() //root install directory WITH path separator at end +Zstring zen::getInstallDirPath() //root install directory WITHOUT path separator at end { - return appendSeparator(beforeLast(beforeLast(getExecutablePathPf(), FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE), FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE)); + return beforeLast(beforeLast(getExecutablePathPf(), FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE), FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE); } #endif -} bool zen::isPortableVersion() { #ifdef ZEN_WIN - return !fileExists(getInstallFolderPathPf() + L"uninstall.exe") && //created by NSIS - !dirExists (getInstallFolderPathPf() + L"Uninstall"); //created by Inno Setup + return !fileExists(appendSeparator(getInstallDirPath()) + L"uninstall.exe") && //created by NSIS + !dirExists (appendSeparator(getInstallDirPath()) + L"Uninstall"); //created by Inno Setup #elif defined ZEN_LINUX return !endsWith(getExecutablePathPf(), "/bin/"); //this check is a bit lame... @@ -56,18 +56,7 @@ bool zen::isPortableVersion() } -bool zen::manualProgramUpdateRequired() -{ -#if defined ZEN_WIN || defined ZEN_MAC - return true; -#elif defined ZEN_LINUX - return true; - //return isPortableVersion(); //locally installed version is updated by Launchpad -#endif -} - - -Zstring zen::getResourceDir() +Zstring zen::getResourceDirPf() { //make independent from wxWidgets global variable "appname"; support being called by RealTimeSync auto appName = wxTheApp->GetAppName(); @@ -75,7 +64,7 @@ Zstring zen::getResourceDir() ZEN_ON_SCOPE_EXIT(wxTheApp->SetAppName(appName)); #ifdef ZEN_WIN - return getInstallFolderPathPf(); + return appendSeparator(getInstallDirPath()); #elif defined ZEN_LINUX if (isPortableVersion()) return getExecutablePathPf(); @@ -87,7 +76,7 @@ Zstring zen::getResourceDir() } -Zstring zen::getConfigDir() +Zstring zen::getConfigDirPathPf() { //make independent from wxWidgets global variable "appname"; support being called by RealTimeSync auto appName = wxTheApp->GetAppName(); @@ -96,7 +85,7 @@ Zstring zen::getConfigDir() #ifdef ZEN_WIN if (isPortableVersion()) - return getInstallFolderPathPf(); + return appendSeparator(getInstallDirPath()); #elif defined ZEN_LINUX if (isPortableVersion()) return getExecutablePathPf(); @@ -119,7 +108,7 @@ Zstring zen::getConfigDir() Zstring zen::getFreeFileSyncLauncherPath() { #ifdef ZEN_WIN - return getInstallFolderPathPf() + Zstr("FreeFileSync.exe"); + return appendSeparator(getInstallDirPath()) + Zstr("FreeFileSync.exe"); #elif defined ZEN_LINUX return getExecutablePathPf() + Zstr("FreeFileSync"); diff --git a/FreeFileSync/Source/lib/ffs_paths.h b/FreeFileSync/Source/lib/ffs_paths.h index 497681c1..2fe32220 100644 --- a/FreeFileSync/Source/lib/ffs_paths.h +++ b/FreeFileSync/Source/lib/ffs_paths.h @@ -14,14 +14,17 @@ namespace zen //------------------------------------------------------------------------------ //global program directories //------------------------------------------------------------------------------ -Zstring getResourceDir(); //resource directory WITH path separator at end -Zstring getConfigDir (); //config directory WITH path separator at end +Zstring getResourceDirPf(); //resource directory WITH path separator at end +Zstring getConfigDirPathPf (); //config directory WITH path separator at end //------------------------------------------------------------------------------ bool isPortableVersion(); +#ifdef ZEN_WIN + Zstring getInstallDirPath(); //root install directory WITHOUT path separator at end +#endif + Zstring getFreeFileSyncLauncherPath(); //full path to application launcher C:\...\FreeFileSync.exe -bool manualProgramUpdateRequired(); } #endif //FFS_PATHS_H_842759083425342534253 diff --git a/FreeFileSync/Source/lib/generate_logfile.h b/FreeFileSync/Source/lib/generate_logfile.h index 9b463426..ff85d2f7 100644 --- a/FreeFileSync/Source/lib/generate_logfile.h +++ b/FreeFileSync/Source/lib/generate_logfile.h @@ -12,6 +12,7 @@ #include <zen/format_unit.h> #include "ffs_paths.h" #include "../fs/abstract.h" +#include "../file_hierarchy.h" namespace zen @@ -152,7 +153,7 @@ void streamToLogFile(const SummaryInfo& summary, //throw FileError inline -Zstring getLastSyncsLogfilePath() { return getConfigDir() + Zstr("LastSyncs.log"); } +Zstring getLastSyncsLogfilePath() { return getConfigDirPathPf() + Zstr("LastSyncs.log"); } inline diff --git a/FreeFileSync/Source/lib/hard_filter.cpp b/FreeFileSync/Source/lib/hard_filter.cpp index 36ad6871..6bef6e11 100644 --- a/FreeFileSync/Source/lib/hard_filter.cpp +++ b/FreeFileSync/Source/lib/hard_filter.cpp @@ -110,11 +110,13 @@ const Char* cStringFind(const Char* str, Char ch) //= strchr(), wcschr() } +/* struct FullMatch { static bool matchesMaskEnd (const Zchar* path) { return *path == 0; } static bool matchesMaskStar(const Zchar* path) { return true; } }; +*/ struct ParentFolderMatch //strict match of parent folder path! { @@ -250,8 +252,6 @@ std::vector<Zstring> zen::splitByDelimiter(const Zstring& filterString) NameFilter::NameFilter(const Zstring& includePhrase, const Zstring& excludePhrase) { - //no need for regular expressions: In tests wxRegex was slower than wxString::Matches() by a factor of 10 - //setup include/exclude filters for files and directories for (const Zstring& entry : splitByDelimiter(includePhrase)) addFilterEntry(entry, includeMasksFileFolder, includeMasksFolder); for (const Zstring& entry : splitByDelimiter(excludePhrase)) addFilterEntry(entry, excludeMasksFileFolder, excludeMasksFolder); @@ -274,6 +274,7 @@ void NameFilter::addExclusion(const Zstring& excludePhrase) bool NameFilter::passFileFilter(const Zstring& relFilePath) const { + assert(!startsWith(relFilePath, FILE_NAME_SEPARATOR)); #if defined ZEN_WIN || defined ZEN_MAC //Windows does NOT distinguish between upper/lower-case const Zstring& pathFmt = makeUpperCopy(relFilePath); #elif defined ZEN_LINUX //Linux DOES distinguish between upper/lower-case @@ -291,6 +292,7 @@ bool NameFilter::passFileFilter(const Zstring& relFilePath) const bool NameFilter::passDirFilter(const Zstring& relDirPath, bool* childItemMightMatch) const { + assert(!startsWith(relDirPath, FILE_NAME_SEPARATOR)); assert(!childItemMightMatch || *childItemMightMatch == true); //check correct usage #if defined ZEN_WIN || defined ZEN_MAC //Windows does NOT distinguish between upper/lower-case @@ -306,8 +308,8 @@ bool NameFilter::passDirFilter(const Zstring& relDirPath, bool* childItemMightMa *childItemMightMatch = false; //perf: no need to traverse deeper; subfolders/subfiles would be excluded by filter anyway! /* Attention: the design choice that "childItemMightMatch" is optional implies that the filter must provide correct results no matter if this - value is considered by the client! In particular, if *childItemMightMatch == false, then any filter evaluations for child items must - also return "false"! + value is considered by the client! + In particular, if *childItemMightMatch == false, then any filter evaluations for child items must also return "false"! This is not a problem for folder traversal which stops at the first *childItemMightMatch == false anyway, but other code continues recursing further, e.g. the database update code in db_file.cpp recurses unconditionally without filter check! It's possible to construct edge cases with incorrect behavior if "childItemMightMatch" were not optional: diff --git a/FreeFileSync/Source/lib/help_provider.h b/FreeFileSync/Source/lib/help_provider.h index a42995be..67e1c34e 100644 --- a/FreeFileSync/Source/lib/help_provider.h +++ b/FreeFileSync/Source/lib/help_provider.h @@ -15,22 +15,20 @@ inline void uninitializeHelp() {} } #else +#include <zen/globals.h> +#include <wx+/http.h> +#include "ffs_paths.h" + #ifdef ZEN_WIN - #include <zen/zstring.h> #include <wx/msw/helpchm.h> - #elif defined ZEN_LINUX || defined ZEN_MAC #include <wx/html/helpctrl.h> #endif -#include "ffs_paths.h" - namespace zen { -void displayHelpEntry(wxWindow* parent); void displayHelpEntry(const wxString& topic, wxWindow* parent); - void uninitializeHelp(); //clean up gracefully during app shutdown: leaving this up to static destruction crashes on Win 8.1! @@ -41,68 +39,29 @@ void uninitializeHelp(); //clean up gracefully during app shutdown: leaving this //######################## implementation ######################## namespace impl { -//finish wxWidgets' job: #ifdef ZEN_WIN -class FfsHelpController +class FfsHelpController //finish wxWidgets' job: { public: - static FfsHelpController& instance() - { - static FfsHelpController inst; //external linkage, despite inline definition! - return inst; - } + FfsHelpController () { chmHlp.Initialize(utfCvrtTo<wxString>(zen::getResourceDirPf()) + L"FreeFileSync.chm"); } + ~FfsHelpController() { chmHlp.Quit(); } //don't keep help window open while app is shut down! => crash on Win 8.1! - void openSection(const wxString& section, wxWindow* parent) + void displaySection(const wxString& section) { - //don't put in constructor: not needed if only uninitialize() is ever called! - if (!chmHlp) - { - chmHlp = std::make_unique<wxCHMHelpController>(); - chmHlp->Initialize(utfCvrtTo<wxString>(zen::getResourceDir()) + L"FreeFileSync.chm"); - } - - if (section.empty()) - chmHlp->DisplayContents(); - else - chmHlp->DisplaySection(replaceCpy(section, L'/', utfCvrtTo<wxString>(FILE_NAME_SEPARATOR))); - } - - void uninitialize() //avoid static init/teardown order fiasco - { - if (chmHlp) - { - chmHlp->Quit(); //don't let help windows open while app is shut down! => crash on Win 8.1! - chmHlp.reset(); - } + chmHlp.DisplaySection(replaceCpy(section, L'/', FILE_NAME_SEPARATOR)); } private: - FfsHelpController() {} - ~FfsHelpController() { assert(!chmHlp); } - - std::unique_ptr<wxCHMHelpController> chmHlp; + wxCHMHelpController chmHlp; }; -#elif defined ZEN_LINUX || defined ZEN_MAC -struct FfsHelpController -{ - static FfsHelpController& instance() - { - static FfsHelpController inst; - return inst; - } - void openSection(const wxString& section, wxWindow* parent) - { - wxHtmlModalHelp dlg(parent, utfCvrtTo<wxString>(zen::getResourceDir()) + L"Help/FreeFileSync.hhp", section, - wxHF_DEFAULT_STYLE | wxHF_DIALOG | wxHF_MODAL | wxHF_MERGE_BOOKS); - (void)dlg; - //-> solves modal help craziness on OSX! - //-> Suse Linux: avoids program hang on exit if user closed help parent dialog before the help dialog itself was closed (why is this even possible???) - // avoids ESC key not being recognized by help dialog (but by parent dialog instead) - } - void uninitialize() {} -}; +inline +Global<FfsHelpController>& refGlobalHelpController() +{ + static Global<FfsHelpController> inst(std::make_unique<FfsHelpController>()); //external linkage even in header! + return inst; +} #endif } @@ -110,21 +69,35 @@ struct FfsHelpController inline void displayHelpEntry(const wxString& topic, wxWindow* parent) { - impl::FfsHelpController::instance().openSection(L"html/" + topic + L".html", parent); -} + if (internetIsAlive()) //noexcept + wxLaunchDefaultBrowser(L"http://www.freefilesync.org/manual.php?topic=" + topic); + else + -> what if FFS is blocked, but the web browser would have internet access?? + { + const wxString section = L"html/" + topic + L".html"; +#ifdef ZEN_WIN + if (std::shared_ptr<impl::FfsHelpController> h = impl::refGlobalHelpController().get()) + h->displaySection(section); +#elif defined ZEN_LINUX || defined ZEN_MAC + wxHtmlModalHelp dlg(parent, utfCvrtTo<wxString>(zen::getResourceDirPf()) + L"Help/FreeFileSync.hhp", section, + wxHF_DEFAULT_STYLE | wxHF_DIALOG | wxHF_MODAL | wxHF_MERGE_BOOKS); + (void)dlg; + //-> solves modal help craziness on OSX! + //-> Suse Linux: avoids program hang on exit if user closed help parent dialog before the help dialog itself was closed (why is this even possible???) + // avoids ESC key not being recognized by help dialog (but by parent dialog instead) -inline -void displayHelpEntry(wxWindow* parent) -{ - impl::FfsHelpController::instance().openSection(wxString(), parent); +#endif + } } + inline void uninitializeHelp() { - impl::FfsHelpController::instance().uninitialize(); - +#ifdef ZEN_WIN + impl::refGlobalHelpController().set(nullptr); +#endif } } #endif diff --git a/FreeFileSync/Source/lib/icon_buffer.cpp b/FreeFileSync/Source/lib/icon_buffer.cpp index 91f6f68f..10965397 100644 --- a/FreeFileSync/Source/lib/icon_buffer.cpp +++ b/FreeFileSync/Source/lib/icon_buffer.cpp @@ -29,10 +29,6 @@ const size_t BUFFER_SIZE_MAX = 800; //maximum number of icons to hold in buffer: const std::thread::id mainThreadId = std::this_thread::get_id(); #endif -#ifdef ZEN_WIN - const bool wereVistaOrLater = vistaOrLater(); -#endif - //destroys raw icon! Call from GUI thread only! wxBitmap extractWxBitmap(ImageHolder&& ih) @@ -333,14 +329,14 @@ public: IconBuffer::IconSize st) : workload_(workload), buffer_(buffer), - iconSizeType(st) {} + iconSizeType_(st) {} void operator()() const; //thread entry private: std::shared_ptr<WorkLoad> workload_; //main/worker thread may access different shared_ptr instances safely (even though they have the same target!) std::shared_ptr<Buffer> buffer_; //http://www.boost.org/doc/libs/1_43_0/libs/smart_ptr/shared_ptr.htm?sess=8153b05b34d890e02d48730db1ff7ddc#ThreadSafety - const IconBuffer::IconSize iconSizeType; + const IconBuffer::IconSize iconSizeType_; }; @@ -370,13 +366,11 @@ void WorkerThread::operator()() const //thread entry { #ifdef ZEN_WIN setCurrentThreadName("Icon Buffer Worker"); - try { - //1. Initialize COM here due to the icon_loader.h dependency only, but NOT due to native.h, mtp.h's internal COM usage => this is not our responsibility! + //1. Initialize COM here only due to the icon_loader.h dependency, but NOT due to native.h, mtp.h's internal COM usage => this is not our responsibility! ComInitializer ci; //throw SysError #endif - for (;;) { interruptionPoint(); //throw ThreadInterruption @@ -385,7 +379,7 @@ void WorkerThread::operator()() const //thread entry const AbstractPath itemPath = workload_->extractNextFile(); //throw ThreadInterruption if (!buffer_->hasIcon(itemPath)) //perf: workload may contain duplicate entries? - buffer_->insert(itemPath, getDisplayIcon(itemPath, iconSizeType)); + buffer_->insert(itemPath, getDisplayIcon(itemPath, iconSizeType_)); } #ifdef ZEN_WIN @@ -404,7 +398,7 @@ struct IconBuffer::Impl InterruptibleThread worker; //------------------------- - std::map<Zstring, wxBitmap, LessFilePath> extensionIcons; + std::map<Zstring, wxBitmap, LessFilePath> extensionIcons; //no item count limit!? Test case C:\ ~ 3800 unique file extensions }; @@ -434,11 +428,11 @@ int IconBuffer::getSize(IconSize sz) return 24; #endif case IconBuffer::SIZE_MEDIUM: -#ifdef ZEN_WIN - if (!wereVistaOrLater) return 32; //48x48 doesn't look sharp on XP -#endif +#ifdef ZEN_WIN_PRE_VISTA + return 32; //48x48 doesn't look sharp on XP +#else //Win (Vista) or Linux/Mac return 48; - +#endif case IconBuffer::SIZE_LARGE: return 128; } @@ -467,7 +461,6 @@ Opt<wxBitmap> IconBuffer::retrieveFileIcon(const AbstractPath& filePath) if (hasStandardIconExtension(fileName)) return this->getIconByExtension(fileName); //buffered!!! #endif - if (Opt<wxBitmap> ico = pimpl->buffer->retrieve(filePath)) return ico; @@ -489,17 +482,17 @@ void IconBuffer::setWorkload(const std::vector<AbstractPath>& load) wxBitmap IconBuffer::getIconByExtension(const Zstring& filePath) { - const Zstring& extension = getFileExtension(filePath); + const Zstring& ext = getFileExtension(filePath); assert(std::this_thread::get_id() == mainThreadId); - auto it = pimpl->extensionIcons.find(extension); + auto it = pimpl->extensionIcons.find(ext); if (it == pimpl->extensionIcons.end()) { - const Zstring& templateName(extension.empty() ? Zstr("file") : Zstr("file.") + extension); + const Zstring& templateName(ext.empty() ? Zstr("file") : Zstr("file.") + ext); //don't pass actual file name to getIconByTemplatePath(), e.g. "AUTHORS" has own mime type on Linux!!! //=> we want to buffer by extension only to minimize buffer-misses! - it = pimpl->extensionIcons.emplace(extension, extractWxBitmap(getIconByTemplatePath(templateName, IconBuffer::getSize(iconSizeType)))).first; + it = pimpl->extensionIcons.emplace(ext, extractWxBitmap(getIconByTemplatePath(templateName, IconBuffer::getSize(iconSizeType)))).first; } //need buffer size limit??? return it->second; @@ -527,7 +520,9 @@ wxBitmap IconBuffer::linkOverlayIcon(IconSize sz) if (pixelSize >= 128) return L"link_128"; if (pixelSize >= 48) return L"link_48"; +#ifdef ZEN_WIN_PRE_VISTA if (pixelSize >= 32) return L"link_32"; +#endif if (pixelSize >= 24) return L"link_24"; return L"link_16"; }()); @@ -540,8 +535,8 @@ bool zen::hasLinkExtension(const Zstring& filepath) return hasWindowsLinkExtension(filepath); #elif defined ZEN_LINUX - const Zstring& extension = getFileExtension(filepath); - return extension == "desktop"; + const Zstring& ext = getFileExtension(filepath); + return ext == "desktop"; #elif defined ZEN_MAC return false; //alias files already get their arrow icon via "NSWorkspace::iconForFile" diff --git a/FreeFileSync/Source/lib/icon_holder.h b/FreeFileSync/Source/lib/icon_holder.h index 63a93436..9046a18a 100644 --- a/FreeFileSync/Source/lib/icon_holder.h +++ b/FreeFileSync/Source/lib/icon_holder.h @@ -18,33 +18,33 @@ struct ImageHolder //prepare conversion to wxImage as much as possible while sta ImageHolder() {} ImageHolder(int w, int h, bool withAlpha) : //init with allocated memory - width(w), height(h), - rgb(static_cast<unsigned char*>(::malloc(width * height * 3))), - alpha(withAlpha ? static_cast<unsigned char*>(::malloc(width * height)) : nullptr) {} + width_(w), height_(h), + rgb_(static_cast<unsigned char*>(::malloc(w * h * 3))), + alpha_(withAlpha ? static_cast<unsigned char*>(::malloc(w * h)) : nullptr) {} ImageHolder (ImageHolder&& tmp) = default; // ImageHolder& operator=(ImageHolder&& tmp) = default; //move semantics only! ImageHolder (const ImageHolder&) = delete; // ImageHolder& operator=(const ImageHolder&) = delete; // - explicit operator bool() const { return rgb.get() != nullptr; } + explicit operator bool() const { return rgb_.get() != nullptr; } - int getWidth () const { return width; } - int getHeight() const { return height; } + int getWidth () const { return width_; } + int getHeight() const { return height_; } - unsigned char* getRgb () { return rgb .get(); } - unsigned char* getAlpha() { return alpha.get(); } + unsigned char* getRgb () { return rgb_ .get(); } + unsigned char* getAlpha() { return alpha_.get(); } - unsigned char* releaseRgb () { return rgb .release(); } - unsigned char* releaseAlpha() { return alpha.release(); } + unsigned char* releaseRgb () { return rgb_ .release(); } + unsigned char* releaseAlpha() { return alpha_.release(); } struct CLibFree { void operator()(unsigned char* p) const { ::free(p); } }; //use malloc/free to allow direct move into wxImage! private: - int width = 0; - int height = 0; - std::unique_ptr<unsigned char, CLibFree> rgb; //optional - std::unique_ptr<unsigned char, CLibFree> alpha; // + int width_ = 0; + int height_ = 0; + std::unique_ptr<unsigned char, CLibFree> rgb_; //optional + std::unique_ptr<unsigned char, CLibFree> alpha_; // }; } diff --git a/FreeFileSync/Source/lib/icon_loader.cpp b/FreeFileSync/Source/lib/icon_loader.cpp index 088fb082..1a1c0610 100644 --- a/FreeFileSync/Source/lib/icon_loader.cpp +++ b/FreeFileSync/Source/lib/icon_loader.cpp @@ -90,10 +90,11 @@ ImageHolder copyToImageHolder(const GdkPixbuf* pixbuf) IconSizeType getThumbSizeType(int pixelSize) { //coordinate with IconBuffer::getSize()! - if (pixelSize >= 256) return ICON_SIZE_256; if (pixelSize >= 128) return ICON_SIZE_128; if (pixelSize >= 48) return ICON_SIZE_48; +#ifdef ZEN_WIN_PRE_VISTA if (pixelSize >= 32) return ICON_SIZE_32; +#endif return ICON_SIZE_16; } diff --git a/FreeFileSync/Source/lib/localization.cpp b/FreeFileSync/Source/lib/localization.cpp index 2e715c59..e691e5c9 100644 --- a/FreeFileSync/Source/lib/localization.cpp +++ b/FreeFileSync/Source/lib/localization.cpp @@ -158,10 +158,12 @@ std::vector<TranslationInfo> loadTranslations() { std::vector<TranslationInfo> locMapping; { + const wchar_t ltrMark = L'\u200E'; //UTF-8: E2 80 8E + //default entry: TranslationInfo newEntry; newEntry.languageID = wxLANGUAGE_ENGLISH_US; - newEntry.languageName = L"English (US)"; + newEntry.languageName = std::wstring(L"English (US)") + ltrMark; //handle weak ")" for bidi-algorithm newEntry.translatorName = L"Zenju"; newEntry.languageFlag = L"flag_usa.png"; newEntry.langFilePath = Zstr(""); @@ -171,7 +173,7 @@ std::vector<TranslationInfo> loadTranslations() //search language files available std::vector<Zstring> lngFilePaths; - traverseFolder(zen::getResourceDir() + Zstr("Languages"), [&](const zen::FileInfo& fi) //FileInfo is ambiguous on OS X + traverseFolder(zen::getResourceDirPf() + Zstr("Languages"), [&](const zen::FileInfo& fi) //FileInfo is ambiguous on OS X { if (pathEndsWith(fi.fullPath, Zstr(".lng"))) lngFilePaths.push_back(fi.fullPath); @@ -191,7 +193,7 @@ std::vector<TranslationInfo> loadTranslations() assert(!lngHeader.localeName .empty()); assert(!lngHeader.flagFile .empty()); /* - Some ISO codes are used by multiple wxLanguage ids which can lead to incorrect mapping!!! + Some ISO codes are used by multiple wxLanguage IDs which can lead to incorrect mapping!!! => Identify by description, e.g. "Chinese (Traditional)". The following ids are affected: wxLANGUAGE_CHINESE_TRADITIONAL wxLANGUAGE_ENGLISH_UK diff --git a/FreeFileSync/Source/lib/parallel_scan.cpp b/FreeFileSync/Source/lib/parallel_scan.cpp index 1057c530..0c34b534 100644 --- a/FreeFileSync/Source/lib/parallel_scan.cpp +++ b/FreeFileSync/Source/lib/parallel_scan.cpp @@ -5,11 +5,13 @@ // ***************************************************************************** #include "parallel_scan.h" +#include <chrono> #include <zen/file_error.h> +#include <zen/basic_math.h> #include <zen/thread.h> #include <zen/scope_guard.h> #include <zen/fixed_list.h> -#include <zen/tick_count.h> +//#include <zen/tick_count.h> #include "db_file.h" #include "lock_holder.h" @@ -157,52 +159,53 @@ using BasicWString = Zbase<wchar_t>; //thread-safe string class for UI texts class AsyncCallback //actor pattern { public: - AsyncCallback(size_t reportingIntervalMs) : reportingIntervalTicks(reportingIntervalMs * ticksPerSec() / 1000) {} + AsyncCallback(size_t reportingIntervalMs) : reportingIntervalMs_(reportingIntervalMs) {} //blocking call: context of worker thread FillBufferCallback::HandleError reportError(const std::wstring& msg, size_t retryNumber) //throw ThreadInterruption { - std::unique_lock<std::mutex> dummy(lockErrorInfo); - interruptibleWait(conditionCanReportError, dummy, [this] { return !errorInfo && !errorResponse; }); //throw ThreadInterruption + std::unique_lock<std::mutex> dummy(lockErrorInfo_); + interruptibleWait(conditionCanReportError_, dummy, [this] { return !errorInfo_ && !errorResponse_; }); //throw ThreadInterruption - errorInfo = std::make_unique<std::pair<BasicWString, size_t>>(copyStringTo<BasicWString>(msg), retryNumber); + errorInfo_ = std::make_unique<std::pair<BasicWString, size_t>>(copyStringTo<BasicWString>(msg), retryNumber); - interruptibleWait(conditionGotResponse, dummy, [this] { return static_cast<bool>(errorResponse); }); //throw ThreadInterruption + interruptibleWait(conditionGotResponse_, dummy, [this] { return static_cast<bool>(errorResponse_); }); //throw ThreadInterruption - FillBufferCallback::HandleError rv = *errorResponse; + FillBufferCallback::HandleError rv = *errorResponse_; - errorInfo .reset(); - errorResponse.reset(); + errorInfo_ .reset(); + errorResponse_.reset(); dummy.unlock(); //optimization for condition_variable::notify_all() - conditionCanReportError.notify_all(); //instead of notify_one(); workaround bug: https://svn.boost.org/trac/boost/ticket/7796 + conditionCanReportError_.notify_all(); //instead of notify_one(); workaround bug: https://svn.boost.org/trac/boost/ticket/7796 return rv; } void processErrors(FillBufferCallback& callback) //context of main thread, call repreatedly { - std::unique_lock<std::mutex> dummy(lockErrorInfo); - if (errorInfo.get() && !errorResponse.get()) + std::unique_lock<std::mutex> dummy(lockErrorInfo_); + if (errorInfo_.get() && !errorResponse_.get()) { - FillBufferCallback::HandleError rv = callback.reportError(copyStringTo<std::wstring>(errorInfo->first), errorInfo->second); //throw! - errorResponse = std::make_unique<FillBufferCallback::HandleError>(rv); + FillBufferCallback::HandleError rv = callback.reportError(copyStringTo<std::wstring>(errorInfo_->first), errorInfo_->second); //throw! + errorResponse_ = std::make_unique<FillBufferCallback::HandleError>(rv); dummy.unlock(); //optimization for condition_variable::notify_all() - conditionGotResponse.notify_all(); //instead of notify_one(); workaround bug: https://svn.boost.org/trac/boost/ticket/7796 + conditionGotResponse_.notify_all(); //instead of notify_one(); workaround bug: https://svn.boost.org/trac/boost/ticket/7796 } } - void incrementNotifyingThreadId() { ++notifyingThreadID; } //context of main thread + void incrementNotifyingThreadId() { ++notifyingThreadID_; } //context of main thread //perf optimization: comparison phase is 7% faster by avoiding needless std::wstring contstruction for reportCurrentFile() - bool mayReportCurrentFile(int threadID, TickVal& lastReportTime) const + bool mayReportCurrentFile(int threadID, std::chrono::steady_clock::time_point& lastReportTime) const { - if (threadID != notifyingThreadID) //only one thread at a time may report status + if (threadID != notifyingThreadID_) //only one thread at a time may report status return false; - const TickVal now = getTicks(); //0 on error - if (dist(lastReportTime, now) >= reportingIntervalTicks) //perform ui updates not more often than necessary + const auto now = std::chrono::steady_clock::now(); //0 on error + + if (numeric::dist(now, lastReportTime) > std::chrono::milliseconds(reportingIntervalMs_)) //perform ui updates not more often than necessary + handle potential chrono wrap-around! { lastReportTime = now; //keep "lastReportTime" at worker thread level to avoid locking! return true; @@ -212,24 +215,24 @@ public: void reportCurrentFile(const std::wstring& filepath) //context of worker thread { - std::lock_guard<std::mutex> dummy(lockCurrentStatus); - currentFile = copyStringTo<BasicWString>(filepath); + std::lock_guard<std::mutex> dummy(lockCurrentStatus_); + currentFile_ = copyStringTo<BasicWString>(filepath); } std::wstring getCurrentStatus() //context of main thread, call repreatedly { std::wstring filepath; { - std::lock_guard<std::mutex> dummy(lockCurrentStatus); - filepath = copyStringTo<std::wstring>(currentFile); + std::lock_guard<std::mutex> dummy(lockCurrentStatus_); + filepath = copyStringTo<std::wstring>(currentFile_); } if (filepath.empty()) return std::wstring(); - std::wstring statusText = copyStringTo<std::wstring>(textScanning); + std::wstring statusText = copyStringTo<std::wstring>(textScanning_); - const long activeCount = activeWorker; + const long activeCount = activeWorker_; if (activeCount >= 2) statusText += L" [" + replaceCpy(_P("1 thread", "%x threads", activeCount), L"%x", numberTo<std::wstring>(activeCount)) + L"]"; @@ -238,33 +241,33 @@ public: return statusText; } - void incItemsScanned() { ++itemsScanned; } //perf: irrelevant! scanning is almost entirely file I/O bound, not CPU bound! => no prob having multiple threads poking at the same variable! - long getItemsScanned() const { return itemsScanned; } + void incItemsScanned() { ++itemsScanned_; } //perf: irrelevant! scanning is almost entirely file I/O bound, not CPU bound! => no prob having multiple threads poking at the same variable! + long getItemsScanned() const { return itemsScanned_; } - void incActiveWorker() { ++activeWorker; } - void decActiveWorker() { --activeWorker; } - long getActiveWorker() const { return activeWorker; } + void incActiveWorker() { ++activeWorker_; } + void decActiveWorker() { --activeWorker_; } + long getActiveWorker() const { return activeWorker_; } private: //---- error handling ---- - std::mutex lockErrorInfo; - std::condition_variable conditionCanReportError; - std::condition_variable conditionGotResponse; - std::unique_ptr<std::pair<BasicWString, size_t>> errorInfo; //error message + retry number - std::unique_ptr<FillBufferCallback::HandleError> errorResponse; + std::mutex lockErrorInfo_; + std::condition_variable conditionCanReportError_; + std::condition_variable conditionGotResponse_; + std::unique_ptr<std::pair<BasicWString, size_t>> errorInfo_; //error message + retry number + std::unique_ptr<FillBufferCallback::HandleError> errorResponse_; //---- status updates ---- - std::atomic<int> notifyingThreadID { 0 }; //CAVEAT: do NOT use boost::thread::id: https://svn.boost.org/trac/boost/ticket/5754 + std::atomic<int> notifyingThreadID_ { 0 }; //CAVEAT: do NOT use boost::thread::id: https://svn.boost.org/trac/boost/ticket/5754 - std::mutex lockCurrentStatus; //use a different lock for current file: continue traversing while some thread may process an error - BasicWString currentFile; - const std::int64_t reportingIntervalTicks; + std::mutex lockCurrentStatus_; //use a different lock for current file: continue traversing while some thread may process an error + BasicWString currentFile_; + const std::int64_t reportingIntervalMs_; - const BasicWString textScanning { copyStringTo<BasicWString>(_("Scanning:")) }; //this one is (currently) not shared and could be made a std::wstring, but we stay consistent and use thread-safe variables in this class only! + const BasicWString textScanning_ { copyStringTo<BasicWString>(_("Scanning:")) }; //this one is (currently) not shared and could be made a std::wstring, but we stay consistent and use thread-safe variables in this class only! //---- status updates II (lock free) ---- - std::atomic<int> itemsScanned{ 0 }; //std:atomic is uninitialized by default! - std::atomic<int> activeWorker{ 0 }; // + std::atomic<int> itemsScanned_{ 0 }; //std:atomic is uninitialized by default! + std::atomic<int> activeWorker_{ 0 }; // }; //------------------------------------------------------------------------------------------------- @@ -296,7 +299,7 @@ public: AsyncCallback& acb_; const int threadID_; - TickVal lastReportTime; + std::chrono::steady_clock::time_point lastReportTime_; }; @@ -339,7 +342,7 @@ void DirCallback::onFile(const FileInfo& fi) //throw ThreadInterruption const Zstring fileRelPath = parentRelPathPf_ + fi.itemName; //update status information no matter whether item is excluded or not! - if (cfg.acb_.mayReportCurrentFile(cfg.threadID_, cfg.lastReportTime)) + if (cfg.acb_.mayReportCurrentFile(cfg.threadID_, cfg.lastReportTime_)) cfg.acb_.reportCurrentFile(AFS::getDisplayPath(AFS::appendRelPath(cfg.baseFolderPath_, fileRelPath))); //------------------------------------------------------------------------------------ @@ -371,7 +374,7 @@ std::unique_ptr<AFS::TraverserCallback> DirCallback::onDir(const DirInfo& di) // const Zstring& folderRelPath = parentRelPathPf_ + di.itemName; //update status information no matter whether item is excluded or not! - if (cfg.acb_.mayReportCurrentFile(cfg.threadID_, cfg.lastReportTime)) + if (cfg.acb_.mayReportCurrentFile(cfg.threadID_, cfg.lastReportTime_)) cfg.acb_.reportCurrentFile(AFS::getDisplayPath(AFS::appendRelPath(cfg.baseFolderPath_, folderRelPath))); //------------------------------------------------------------------------------------ @@ -390,11 +393,11 @@ std::unique_ptr<AFS::TraverserCallback> DirCallback::onDir(const DirInfo& di) // if (level_ > 100) //Win32 traverser: stack overflow approximately at level 1000 if (!tryReportingItemError([&] //check after FolderContainer::addSubFolder() { - throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", AFS::getDisplayPath(AFS::appendRelPath(cfg.baseFolderPath_, folderRelPath))), L"Endless recursion."); + throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", AFS::getDisplayPath(AFS::appendRelPath(cfg.baseFolderPath_, folderRelPath))), L"Endless recursion."); }, *this, di.itemName)) return nullptr; - return std::make_unique<DirCallback>(cfg, folderRelPath + FILE_NAME_SEPARATOR, subFolder, level_ + 1); //releaseDirTraverser() is guaranteed to be called in any case + return std::make_unique<DirCallback>(cfg, folderRelPath + FILE_NAME_SEPARATOR, subFolder, level_ + 1); } @@ -405,7 +408,7 @@ DirCallback::HandleLink DirCallback::onSymlink(const SymlinkInfo& si) //throw Th const Zstring& linkRelPath = parentRelPathPf_ + si.itemName; //update status information no matter whether item is excluded or not! - if (cfg.acb_.mayReportCurrentFile(cfg.threadID_, cfg.lastReportTime)) + if (cfg.acb_.mayReportCurrentFile(cfg.threadID_, cfg.lastReportTime_)) cfg.acb_.reportCurrentFile(AFS::getDisplayPath(AFS::appendRelPath(cfg.baseFolderPath_, linkRelPath))); switch (cfg.handleSymlinks_) @@ -500,7 +503,7 @@ public: acb_->incActiveWorker(); ZEN_ON_SCOPE_EXIT(acb_->decActiveWorker()); - if (acb_->mayReportCurrentFile(travCfg.threadID_, travCfg.lastReportTime)) + if (acb_->mayReportCurrentFile(travCfg.threadID_, travCfg.lastReportTime_)) acb_->reportCurrentFile(AFS::getDisplayPath(travCfg.baseFolderPath_)); //just in case first directory access is blocking DirCallback cb(travCfg, Zstring(), outputContainer, 0); diff --git a/FreeFileSync/Source/lib/process_xml.cpp b/FreeFileSync/Source/lib/process_xml.cpp index ba681397..1e31cabf 100644 --- a/FreeFileSync/Source/lib/process_xml.cpp +++ b/FreeFileSync/Source/lib/process_xml.cpp @@ -96,7 +96,7 @@ XmlGlobalSettings::XmlGlobalSettings() Zstring xmlAccess::getGlobalConfigFile() { - return zen::getConfigDir() + Zstr("GlobalSettings.xml"); + return zen::getConfigDirPathPf() + Zstr("GlobalSettings.xml"); } @@ -492,15 +492,15 @@ void writeText(const ColumnTypeNavi& value, std::string& output) { switch (value) { - case ColumnTypeNavi::BYTES: - output = "Bytes"; - break; - case ColumnTypeNavi::DIRECTORY: + case ColumnTypeNavi::FOLDER_NAME: output = "Tree"; break; case ColumnTypeNavi::ITEM_COUNT: output = "Count"; break; + case ColumnTypeNavi::BYTES: + output = "Bytes"; + break; } } @@ -508,12 +508,12 @@ template <> inline bool readText(const std::string& input, ColumnTypeNavi& value) { const std::string tmp = trimCpy(input); - if (tmp == "Bytes") - value = ColumnTypeNavi::BYTES; - else if (tmp == "Tree") - value = ColumnTypeNavi::DIRECTORY; + if (tmp == "Tree") + value = ColumnTypeNavi::FOLDER_NAME; else if (tmp == "Count") value = ColumnTypeNavi::ITEM_COUNT; + else if (tmp == "Bytes") + value = ColumnTypeNavi::BYTES; else return false; return true; @@ -1204,7 +1204,6 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& config, int formatVer) //last update check inGui["LastOnlineCheck" ](config.gui.lastUpdateCheck); inGui["LastOnlineVersion"](config.gui.lastOnlineVersion); - inGui["LastOnlineChanges"](config.gui.lastOnlineChangeLog); //batch specific global settings //XmlIn inBatch = in["Batch"]; @@ -1599,7 +1598,6 @@ void writeConfig(const XmlGlobalSettings& config, XmlOut& out) //last update check outGui["LastOnlineCheck" ](config.gui.lastUpdateCheck); outGui["LastOnlineVersion"](config.gui.lastOnlineVersion); - outGui["LastOnlineChanges"](config.gui.lastOnlineChangeLog); //batch specific global settings //XmlOut outBatch = out["Batch"]; diff --git a/FreeFileSync/Source/lib/process_xml.h b/FreeFileSync/Source/lib/process_xml.h index f29ed729..16659cc5 100644 --- a/FreeFileSync/Source/lib/process_xml.h +++ b/FreeFileSync/Source/lib/process_xml.h @@ -251,8 +251,7 @@ struct XmlGlobalSettings }; time_t lastUpdateCheck = 0; //number of seconds since 00:00 hours, Jan 1, 1970 UTC - std::wstring lastOnlineVersion; - std::wstring lastOnlineChangeLog; + std::string lastOnlineVersion; } gui; }; diff --git a/FreeFileSync/Source/lib/resolve_path.cpp b/FreeFileSync/Source/lib/resolve_path.cpp index b081e1e9..a67460bd 100644 --- a/FreeFileSync/Source/lib/resolve_path.cpp +++ b/FreeFileSync/Source/lib/resolve_path.cpp @@ -722,7 +722,7 @@ void NetworkConnector::connect(const Credentials& cred) //throw FileError 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! + 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, @@ -736,6 +736,7 @@ void NetworkConnector::connect(const Credentials& cred) //throw FileError //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 + //1244 ERROR_NOT_AUTHENTICATED } } diff --git a/FreeFileSync/Source/lib/status_handler.cpp b/FreeFileSync/Source/lib/status_handler.cpp index 1d940cc9..55765fc3 100644 --- a/FreeFileSync/Source/lib/status_handler.cpp +++ b/FreeFileSync/Source/lib/status_handler.cpp @@ -5,21 +5,22 @@ // ***************************************************************************** #include "status_handler.h" -#include <zen/tick_count.h> +#include <chrono> +#include <zen/basic_math.h> using namespace zen; namespace { -const std::int64_t TICKS_UPDATE_INTERVAL = UI_UPDATE_INTERVAL* ticksPerSec() / 1000; -TickVal lastExec = getTicks(); +std::chrono::steady_clock::time_point lastExec; }; bool zen::updateUiIsAllowed() { - const TickVal now = getTicks(); //0 on error - if (dist(lastExec, now) >= TICKS_UPDATE_INTERVAL) //perform ui updates not more often than necessary + const auto now = std::chrono::steady_clock::now(); + + if (numeric::dist(now, lastExec) > std::chrono::milliseconds(UI_UPDATE_INTERVAL_MS)) //handle potential chrono wrap-around! { lastExec = now; return true; diff --git a/FreeFileSync/Source/process_callback.h b/FreeFileSync/Source/process_callback.h index e7291196..51dd956f 100644 --- a/FreeFileSync/Source/process_callback.h +++ b/FreeFileSync/Source/process_callback.h @@ -12,7 +12,7 @@ //interface for comparison and synchronization process status updates (used by GUI or Batch mode) -const int UI_UPDATE_INTERVAL = 100; //unit: [ms]; perform ui updates not more often than necessary, +const int UI_UPDATE_INTERVAL_MS = 100; //unit: [ms]; perform ui updates not more often than necessary, //100 seems to be a good value with only a minimal performance loss; also used by Win 7 copy progress bar //this one is required by async directory existence check! diff --git a/FreeFileSync/Source/synchronization.cpp b/FreeFileSync/Source/synchronization.cpp index 3b58d4ba..4f36ded1 100644 --- a/FreeFileSync/Source/synchronization.cpp +++ b/FreeFileSync/Source/synchronization.cpp @@ -91,14 +91,17 @@ void SyncStatistics::processFile(const FilePair& file) case SO_DELETE_LEFT: ++deleteLeft_; + physicalDeleteLeft_ = true; break; case SO_DELETE_RIGHT: ++deleteRight_; + physicalDeleteRight_ = true; break; case SO_MOVE_LEFT_TARGET: ++updateLeft_; + //physicalDeleteLeft_ ? -> usually, no; except when falling back to "copy + delete" break; case SO_MOVE_RIGHT_TARGET: @@ -112,15 +115,17 @@ void SyncStatistics::processFile(const FilePair& file) case SO_OVERWRITE_LEFT: ++updateLeft_; bytesToProcess_ += static_cast<std::int64_t>(file.getFileSize<RIGHT_SIDE>()); + physicalDeleteLeft_ = true; break; case SO_OVERWRITE_RIGHT: ++updateRight_; bytesToProcess_ += static_cast<std::int64_t>(file.getFileSize<LEFT_SIDE>()); + physicalDeleteRight_ = true; break; case SO_UNRESOLVED_CONFLICT: - conflictMsgs_.emplace_back(file.getPairRelativePath(), file.getSyncOpConflict()); + conflictMsgs_.push_back({ file.getPairRelativePath(), file.getSyncOpConflict() }); break; case SO_COPY_METADATA_TO_LEFT: @@ -153,24 +158,28 @@ void SyncStatistics::processLink(const SymlinkPair& link) case SO_DELETE_LEFT: ++deleteLeft_; + physicalDeleteLeft_ = true; break; case SO_DELETE_RIGHT: ++deleteRight_; + physicalDeleteRight_ = true; break; case SO_OVERWRITE_LEFT: case SO_COPY_METADATA_TO_LEFT: ++updateLeft_; + physicalDeleteLeft_ = true; break; case SO_OVERWRITE_RIGHT: case SO_COPY_METADATA_TO_RIGHT: ++updateRight_; + physicalDeleteRight_ = true; break; case SO_UNRESOLVED_CONFLICT: - conflictMsgs_.emplace_back(link.getPairRelativePath(), link.getSyncOpConflict()); + conflictMsgs_.push_back({ link.getPairRelativePath(), link.getSyncOpConflict() }); break; case SO_MOVE_LEFT_SOURCE: @@ -198,16 +207,18 @@ void SyncStatistics::processFolder(const FolderPair& folder) ++createRight_; break; - case SO_DELETE_LEFT: //if deletion variant == user-defined directory existing on other volume, this results in a full copy + delete operation! + case SO_DELETE_LEFT: //if deletion variant == versioning with user-defined directory existing on other volume, this results in a full copy + delete operation! ++deleteLeft_; //however we cannot (reliably) anticipate this situation, fortunately statistics can be adapted during sync! + physicalDeleteLeft_ = true; break; case SO_DELETE_RIGHT: ++deleteRight_; + physicalDeleteRight_ = true; break; case SO_UNRESOLVED_CONFLICT: - conflictMsgs_.emplace_back(folder.getPairRelativePath(), folder.getSyncOpConflict()); + conflictMsgs_.push_back({ folder.getPairRelativePath(), folder.getSyncOpConflict() }); break; case SO_OVERWRITE_LEFT: @@ -262,6 +273,36 @@ std::vector<zen::FolderPairSyncCfg> zen::extractSyncCfg(const MainConfiguration& namespace { +inline +Opt<SelectedSide> getTargetDirection(SyncOperation syncOp) +{ + switch (syncOp) + { + case SO_CREATE_NEW_LEFT: + case SO_DELETE_LEFT: + case SO_OVERWRITE_LEFT: + case SO_COPY_METADATA_TO_LEFT: + case SO_MOVE_LEFT_SOURCE: + case SO_MOVE_LEFT_TARGET: + return LEFT_SIDE; + + case SO_CREATE_NEW_RIGHT: + case SO_DELETE_RIGHT: + case SO_OVERWRITE_RIGHT: + case SO_COPY_METADATA_TO_RIGHT: + case SO_MOVE_RIGHT_SOURCE: + case SO_MOVE_RIGHT_TARGET: + return RIGHT_SIDE; + + case SO_DO_NOTHING: + case SO_EQUAL: + case SO_UNRESOLVED_CONFLICT: + break; //nothing to do + } + return NoValue(); +} + + //test if user accidentally selected the wrong folders to sync bool significantDifferenceDetected(const SyncStatistics& folderPairStat) { @@ -542,12 +583,10 @@ public: { MinimumDiskSpaceNeeded inst; inst.recurse(baseFolder); - return std::make_pair(inst.spaceNeededLeft, inst.spaceNeededRight); + return std::make_pair(inst.spaceNeededLeft_, inst.spaceNeededRight_); } private: - MinimumDiskSpaceNeeded() {} - void recurse(const HierarchyObject& hierObj) { //don't process directories @@ -557,33 +596,33 @@ private: switch (file.getSyncOperation()) //evaluate comparison result and sync direction { case SO_CREATE_NEW_LEFT: - spaceNeededLeft += static_cast<std::int64_t>(file.getFileSize<RIGHT_SIDE>()); + spaceNeededLeft_ += static_cast<std::int64_t>(file.getFileSize<RIGHT_SIDE>()); break; case SO_CREATE_NEW_RIGHT: - spaceNeededRight += static_cast<std::int64_t>(file.getFileSize<LEFT_SIDE>()); + spaceNeededRight_ += static_cast<std::int64_t>(file.getFileSize<LEFT_SIDE>()); break; case SO_DELETE_LEFT: //if (freeSpaceDelLeft_) - spaceNeededLeft -= static_cast<std::int64_t>(file.getFileSize<LEFT_SIDE>()); + spaceNeededLeft_ -= static_cast<std::int64_t>(file.getFileSize<LEFT_SIDE>()); break; case SO_DELETE_RIGHT: //if (freeSpaceDelRight_) - spaceNeededRight -= static_cast<std::int64_t>(file.getFileSize<RIGHT_SIDE>()); + spaceNeededRight_ -= static_cast<std::int64_t>(file.getFileSize<RIGHT_SIDE>()); break; case SO_OVERWRITE_LEFT: //if (freeSpaceDelLeft_) - spaceNeededLeft -= static_cast<std::int64_t>(file.getFileSize<LEFT_SIDE>()); - spaceNeededLeft += static_cast<std::int64_t>(file.getFileSize<RIGHT_SIDE>()); + spaceNeededLeft_ -= static_cast<std::int64_t>(file.getFileSize<LEFT_SIDE>()); + spaceNeededLeft_ += static_cast<std::int64_t>(file.getFileSize<RIGHT_SIDE>()); break; case SO_OVERWRITE_RIGHT: //if (freeSpaceDelRight_) - spaceNeededRight -= static_cast<std::int64_t>(file.getFileSize<RIGHT_SIDE>()); - spaceNeededRight += static_cast<std::int64_t>(file.getFileSize<LEFT_SIDE>()); + spaceNeededRight_ -= static_cast<std::int64_t>(file.getFileSize<RIGHT_SIDE>()); + spaceNeededRight_ += static_cast<std::int64_t>(file.getFileSize<LEFT_SIDE>()); break; case SO_DO_NOTHING: @@ -606,8 +645,8 @@ private: recurse(folder); } - std::int64_t spaceNeededLeft = 0; - std::int64_t spaceNeededRight = 0; + std::int64_t spaceNeededLeft_ = 0; + std::int64_t spaceNeededRight_ = 0; }; //---------------------------------------------------------------------------------------- @@ -642,16 +681,16 @@ public: } private: - enum PassId + enum PassNo { PASS_ONE, //delete files PASS_TWO, //create, modify - PASS_NEVER //skip + PASS_NEVER //skip item }; - static PassId getPass(const FilePair& file); - static PassId getPass(const SymlinkPair& link); - static PassId getPass(const FolderPair& folder); + static PassNo getPass(const FilePair& file); + static PassNo getPass(const SymlinkPair& link); + static PassNo getPass(const FolderPair& folder); template <SelectedSide side> void prepare2StepMove(FilePair& sourceObj, FilePair& targetObj); //throw FileError @@ -660,7 +699,7 @@ private: void manageFileMove(FilePair& sourceObj, FilePair& targetObj); //throw FileError void runZeroPass(HierarchyObject& hierObj); - template <PassId pass> + template <PassNo pass> void runPass(HierarchyObject& hierObj); //throw X void synchronizeFile(FilePair& file); @@ -964,7 +1003,7 @@ void SynchronizeFolderPair::runZeroPass(HierarchyObject& hierObj) // - support change in type: overwrite file by directory, symlink by file, ect. inline -SynchronizeFolderPair::PassId SynchronizeFolderPair::getPass(const FilePair& file) +SynchronizeFolderPair::PassNo SynchronizeFolderPair::getPass(const FilePair& file) { switch (file.getSyncOperation()) //evaluate comparison result and sync direction { @@ -1002,7 +1041,7 @@ SynchronizeFolderPair::PassId SynchronizeFolderPair::getPass(const FilePair& fil inline -SynchronizeFolderPair::PassId SynchronizeFolderPair::getPass(const SymlinkPair& link) +SynchronizeFolderPair::PassNo SynchronizeFolderPair::getPass(const SymlinkPair& link) { switch (link.getSyncOperation()) //evaluate comparison result and sync direction { @@ -1034,7 +1073,7 @@ SynchronizeFolderPair::PassId SynchronizeFolderPair::getPass(const SymlinkPair& inline -SynchronizeFolderPair::PassId SynchronizeFolderPair::getPass(const FolderPair& folder) +SynchronizeFolderPair::PassNo SynchronizeFolderPair::getPass(const FolderPair& folder) { switch (folder.getSyncOperation()) //evaluate comparison result and sync direction { @@ -1065,7 +1104,7 @@ SynchronizeFolderPair::PassId SynchronizeFolderPair::getPass(const FolderPair& f } -template <SynchronizeFolderPair::PassId pass> +template <SynchronizeFolderPair::PassNo pass> void SynchronizeFolderPair::runPass(HierarchyObject& hierObj) //throw X { //synchronize files: @@ -1091,36 +1130,6 @@ void SynchronizeFolderPair::runPass(HierarchyObject& hierObj) //throw X //--------------------------------------------------------------------------------------------------------------- inline -Opt<SelectedSide> getTargetDirection(SyncOperation syncOp) -{ - switch (syncOp) - { - case SO_CREATE_NEW_LEFT: - case SO_DELETE_LEFT: - case SO_OVERWRITE_LEFT: - case SO_COPY_METADATA_TO_LEFT: - case SO_MOVE_LEFT_SOURCE: - case SO_MOVE_LEFT_TARGET: - return LEFT_SIDE; - - case SO_CREATE_NEW_RIGHT: - case SO_DELETE_RIGHT: - case SO_OVERWRITE_RIGHT: - case SO_COPY_METADATA_TO_RIGHT: - case SO_MOVE_RIGHT_SOURCE: - case SO_MOVE_RIGHT_TARGET: - return RIGHT_SIDE; - - case SO_DO_NOTHING: - case SO_EQUAL: - case SO_UNRESOLVED_CONFLICT: - break; //nothing to do - } - return NoValue(); -} - - -inline void SynchronizeFolderPair::synchronizeFile(FilePair& file) { const SyncOperation syncOp = file.getSyncOperation(); @@ -1855,13 +1864,14 @@ void zen::synchronize(const TimeComp& timeStamp, { int itemsTotal = 0; int64_t bytesTotal = 0; - for (auto j = begin(folderCmp); j != end(folderCmp); ++j) + std::for_each(begin(folderCmp), end(folderCmp), + [&](const BaseFolderPair& baseFolder) { - SyncStatistics fpStats(*j); + SyncStatistics fpStats(baseFolder); itemsTotal += getCUD(fpStats); bytesTotal += fpStats.getBytesToProcess(); folderPairStats.push_back(fpStats); - } + }); //inform about the total amount of data that will be processed from now on //keep at beginning so that all gui elements are initialized properly @@ -1903,15 +1913,16 @@ void zen::synchronize(const TimeComp& timeStamp, //aggregate information std::map<AbstractPath, ReadWriteCount, AFS::LessAbstractPath> dirReadWriteCount; //count read/write accesses - for (auto j = begin(folderCmp); j != end(folderCmp); ++j) + std::for_each(begin(folderCmp), end(folderCmp), + [&](const BaseFolderPair& baseFolder) { //create all entries first! otherwise counting accesses would be too complex during later inserts! - if (!AFS::isNullPath(j->getAbstractPath<LEFT_SIDE>())) //empty folder is always dependent => exclude! - dirReadWriteCount[j->getAbstractPath<LEFT_SIDE>()]; - if (!AFS::isNullPath(j->getAbstractPath<RIGHT_SIDE>())) - dirReadWriteCount[j->getAbstractPath<RIGHT_SIDE>()]; - } + if (!AFS::isNullPath(baseFolder.getAbstractPath<LEFT_SIDE>())) //empty folder is always dependent => exclude! + dirReadWriteCount[baseFolder.getAbstractPath<LEFT_SIDE>()]; + if (!AFS::isNullPath(baseFolder.getAbstractPath<RIGHT_SIDE>())) + dirReadWriteCount[baseFolder.getAbstractPath<RIGHT_SIDE>()]; + }); auto incReadCount = [&](const AbstractPath& baseFolderPath) { @@ -1936,9 +1947,10 @@ void zen::synchronize(const TimeComp& timeStamp, std::map<AbstractPath, bool, AFS::LessAbstractPath> recyclerSupported; //expensive to determine on Win XP => buffer + check recycle bin existence only once per base folder! //start checking folder pairs - for (auto j = begin(folderCmp); j != end(folderCmp); ++j) + for (auto itBase = begin(folderCmp); itBase != end(folderCmp); ++itBase) { - const size_t folderIndex = j - begin(folderCmp); + BaseFolderPair& baseFolder = *itBase; + const size_t folderIndex = itBase - begin(folderCmp); const FolderPairSyncCfg& folderPairCfg = syncConfig [folderIndex]; const SyncStatistics& folderPairStat = folderPairStats[folderIndex]; @@ -1946,13 +1958,21 @@ void zen::synchronize(const TimeComp& timeStamp, append(unresolvedConflicts, folderPairStat.getConflicts()); //exclude a few pathological cases (including empty left, right folders) - if (AFS::equalAbstractPath(j->getAbstractPath< LEFT_SIDE>(), - j->getAbstractPath<RIGHT_SIDE>())) + if (AFS::equalAbstractPath(baseFolder.getAbstractPath< LEFT_SIDE>(), + baseFolder.getAbstractPath<RIGHT_SIDE>())) { jobType[folderIndex] = FolderPairJobType::SKIP; continue; } + //skip folder pair if there is nothing to do (except for two-way mode and move-detection, where DB files need to be updated) + //-> skip creating (not yet existing) base directories in particular if there's no need + if (getCUD(folderPairStat) == 0) + { + jobType[folderIndex] = FolderPairJobType::ALREADY_IN_SYNC; + continue; + } + const bool writeLeft = folderPairStat.createCount<LEFT_SIDE>() + folderPairStat.updateCount<LEFT_SIDE>() + folderPairStat.deleteCount<LEFT_SIDE>() > 0; @@ -1961,36 +1981,29 @@ void zen::synchronize(const TimeComp& timeStamp, folderPairStat.updateCount<RIGHT_SIDE>() + folderPairStat.deleteCount<RIGHT_SIDE>() > 0; - //skip folder pair if there is nothing to do (except for two-way mode and move-detection, where DB files need to be updated) - //-> skip creating (not yet existing) base directories in particular if there's no need - if (!writeLeft && !writeRight) - { - jobType[folderIndex] = FolderPairJobType::ALREADY_IN_SYNC; - continue; - } - //aggregate information of folders used by multiple pairs in read/write access - if (!AFS::havePathDependency(j->getAbstractPath<LEFT_SIDE>(), j->getAbstractPath<RIGHT_SIDE>())) + if (!AFS::havePathDependency(baseFolder.getAbstractPath<LEFT_SIDE >(), + baseFolder.getAbstractPath<RIGHT_SIDE>())) { if (writeLeft) - incWriteCount(j->getAbstractPath<LEFT_SIDE>()); + incWriteCount(baseFolder.getAbstractPath<LEFT_SIDE>()); else if (writeRight) - incReadCount (j->getAbstractPath<LEFT_SIDE>()); + incReadCount (baseFolder.getAbstractPath<LEFT_SIDE>()); if (writeRight) - incWriteCount(j->getAbstractPath<RIGHT_SIDE>()); + incWriteCount(baseFolder.getAbstractPath<RIGHT_SIDE>()); else if (writeLeft) - incReadCount (j->getAbstractPath<RIGHT_SIDE>()); + incReadCount (baseFolder.getAbstractPath<RIGHT_SIDE>()); } else //if folder pair contains two dependent folders, a warning was already issued after comparison; in this context treat as one write access at most { if (writeLeft || writeRight) - incWriteCount(j->getAbstractPath<LEFT_SIDE>()); + incWriteCount(baseFolder.getAbstractPath<LEFT_SIDE>()); } //check for empty target folder paths: this only makes sense if empty field is source (and no DB files need to be created) - if ((AFS::isNullPath(j->getAbstractPath< LEFT_SIDE>()) && (writeLeft || folderPairCfg.saveSyncDB_)) || - (AFS::isNullPath(j->getAbstractPath<RIGHT_SIDE>()) && (writeRight || folderPairCfg.saveSyncDB_))) + if ((AFS::isNullPath(baseFolder.getAbstractPath< LEFT_SIDE>()) && (writeLeft || folderPairCfg.saveSyncDB_)) || + (AFS::isNullPath(baseFolder.getAbstractPath<RIGHT_SIDE>()) && (writeRight || folderPairCfg.saveSyncDB_))) { callback.reportFatalError(_("Target folder input field must not be empty.")); jobType[folderIndex] = FolderPairJobType::SKIP; @@ -2000,38 +2013,37 @@ void zen::synchronize(const TimeComp& timeStamp, //check for network drops after comparison // - convenience: exit sync right here instead of showing tons of errors during file copy // - early failure! there's no point in evaluating subsequent warnings - if (baseFolderDrop< LEFT_SIDE>(*j, folderAccessTimeout, callback) || - baseFolderDrop<RIGHT_SIDE>(*j, folderAccessTimeout, callback)) + if (baseFolderDrop< LEFT_SIDE>(baseFolder, folderAccessTimeout, callback) || + baseFolderDrop<RIGHT_SIDE>(baseFolder, folderAccessTimeout, callback)) { jobType[folderIndex] = FolderPairJobType::SKIP; continue; } //allow propagation of deletions only from *null-* or *existing* source folder: - auto sourceFolderMissing = [&](const AbstractPath& baseFolder, bool wasExisting) //we need to evaluate existence status from time of comparison! + auto sourceFolderMissing = [&](const AbstractPath& baseFolderPath, bool wasExisting) //we need to evaluate existence status from time of comparison! { - if (!AFS::isNullPath(baseFolder)) + if (!AFS::isNullPath(baseFolderPath)) //PERMANENT network drop: avoid data loss when source directory is not found AND user chose to ignore errors (else we wouldn't arrive here) if (folderPairStat.deleteCount() > 0) //check deletions only... (respect filtered items!) //folderPairStat.conflictCount() == 0 && -> there COULD be conflicts for <Two way> variant if directory existence check fails, but loading sync.ffs_db succeeds //https://sourceforge.net/tracker/?func=detail&atid=1093080&aid=3531351&group_id=234430 -> fixed, but still better not consider conflicts! if (!wasExisting) //avoid race-condition: we need to evaluate existence status from time of comparison! { - callback.reportFatalError(replaceCpy(_("Source folder %x not found."), L"%x", fmtPath(AFS::getDisplayPath(baseFolder)))); + callback.reportFatalError(replaceCpy(_("Source folder %x not found."), L"%x", fmtPath(AFS::getDisplayPath(baseFolderPath)))); return true; } return false; }; - if (sourceFolderMissing(j->getAbstractPath< LEFT_SIDE>(), j->isExisting< LEFT_SIDE>()) || - sourceFolderMissing(j->getAbstractPath<RIGHT_SIDE>(), j->isExisting<RIGHT_SIDE>())) + if (sourceFolderMissing(baseFolder.getAbstractPath< LEFT_SIDE>(), baseFolder.isExisting< LEFT_SIDE>()) || + sourceFolderMissing(baseFolder.getAbstractPath<RIGHT_SIDE>(), baseFolder.isExisting<RIGHT_SIDE>())) { jobType[folderIndex] = FolderPairJobType::SKIP; continue; } //check if user-defined directory for deletion was specified - if (folderPairCfg.handleDeletion == DeletionPolicy::VERSIONING && - folderPairStat.updateCount() + folderPairStat.deleteCount() > 0) + if (folderPairCfg.handleDeletion == DeletionPolicy::VERSIONING) { if (trimCpy(folderPairCfg.versioningFolderPhrase).empty()) { @@ -2043,11 +2055,11 @@ void zen::synchronize(const TimeComp& timeStamp, } //check if more than 50% of total number of files/dirs are to be created/overwritten/deleted - if (!AFS::isNullPath(j->getAbstractPath< LEFT_SIDE>()) && - !AFS::isNullPath(j->getAbstractPath<RIGHT_SIDE>())) + if (!AFS::isNullPath(baseFolder.getAbstractPath< LEFT_SIDE>()) && + !AFS::isNullPath(baseFolder.getAbstractPath<RIGHT_SIDE>())) if (significantDifferenceDetected(folderPairStat)) - significantDiffPairs.emplace_back(j->getAbstractPath< LEFT_SIDE>(), - j->getAbstractPath<RIGHT_SIDE>()); + significantDiffPairs.emplace_back(baseFolder.getAbstractPath< LEFT_SIDE>(), + baseFolder.getAbstractPath<RIGHT_SIDE>()); //check for sufficient free diskspace auto checkSpace = [&](const AbstractPath& baseFolderPath, std::int64_t minSpaceNeeded) @@ -2063,9 +2075,9 @@ void zen::synchronize(const TimeComp& timeStamp, } catch (FileError&) {} //for warning only => no need for tryReportingError() }; - const std::pair<std::int64_t, std::int64_t> spaceNeeded = MinimumDiskSpaceNeeded::calculate(*j); - checkSpace(j->getAbstractPath< LEFT_SIDE>(), spaceNeeded.first); - checkSpace(j->getAbstractPath<RIGHT_SIDE>(), spaceNeeded.second); + const std::pair<std::int64_t, std::int64_t> spaceNeeded = MinimumDiskSpaceNeeded::calculate(baseFolder); + checkSpace(baseFolder.getAbstractPath< LEFT_SIDE>(), spaceNeeded.first); + checkSpace(baseFolder.getAbstractPath<RIGHT_SIDE>(), spaceNeeded.second); //windows: check if recycle bin really exists; if not, Windows will silently delete, which is wrong auto checkRecycler = [&](const AbstractPath& baseFolderPath) @@ -2088,13 +2100,11 @@ void zen::synchronize(const TimeComp& timeStamp, if (folderPairCfg.handleDeletion == DeletionPolicy::RECYCLER) { - if (folderPairStat.updateCount<LEFT_SIDE>() + - folderPairStat.deleteCount<LEFT_SIDE>() > 0) - checkRecycler(j->getAbstractPath<LEFT_SIDE>()); + if (folderPairStat.expectPhysicalDeletion<LEFT_SIDE>()) + checkRecycler(baseFolder.getAbstractPath<LEFT_SIDE>()); - if (folderPairStat.updateCount<RIGHT_SIDE>() + - folderPairStat.deleteCount<RIGHT_SIDE>() > 0) - checkRecycler(j->getAbstractPath<RIGHT_SIDE>()); + if (folderPairStat.expectPhysicalDeletion<RIGHT_SIDE>()) + checkRecycler(baseFolder.getAbstractPath<RIGHT_SIDE>()); } } @@ -2104,7 +2114,7 @@ void zen::synchronize(const TimeComp& timeStamp, std::wstring msg = _("The following items have unresolved conflicts and will not be synchronized:"); for (const SyncStatistics::ConflictInfo& item : unresolvedConflicts) //show *all* conflicts in warning message - msg += L"\n\n" + fmtPath(item.first) + L": " + item.second; + msg += L"\n\n" + fmtPath(item.relPath) + L": " + item.msg; callback.reportWarning(msg, warnings.warningUnresolvedConflicts); } @@ -2176,9 +2186,10 @@ void zen::synchronize(const TimeComp& timeStamp, try { //loop through all directory pairs - for (auto j = begin(folderCmp); j != end(folderCmp); ++j) + for (auto itBase = begin(folderCmp); itBase != end(folderCmp); ++itBase) { - const size_t folderIndex = j - begin(folderCmp); + BaseFolderPair& baseFolder = *itBase; + const size_t folderIndex = itBase - begin(folderCmp); const FolderPairSyncCfg& folderPairCfg = syncConfig [folderIndex]; const SyncStatistics& folderPairStat = folderPairStats[folderIndex]; @@ -2187,19 +2198,19 @@ void zen::synchronize(const TimeComp& timeStamp, //------------------------------------------------------------------------------------------ callback.reportInfo(_("Synchronizing folder pair:") + L" [" + getVariantName(folderPairCfg.syncVariant_) + L"]\n" + - L" " + AFS::getDisplayPath(j->getAbstractPath< LEFT_SIDE>()) + L"\n" + - L" " + AFS::getDisplayPath(j->getAbstractPath<RIGHT_SIDE>())); + L" " + AFS::getDisplayPath(baseFolder.getAbstractPath< LEFT_SIDE>()) + L"\n" + + L" " + AFS::getDisplayPath(baseFolder.getAbstractPath<RIGHT_SIDE>())); //------------------------------------------------------------------------------------------ //checking a second time: (a long time may have passed since the intro checks!) - if (baseFolderDrop< LEFT_SIDE>(*j, folderAccessTimeout, callback) || - baseFolderDrop<RIGHT_SIDE>(*j, folderAccessTimeout, callback)) + if (baseFolderDrop< LEFT_SIDE>(baseFolder, folderAccessTimeout, callback) || + baseFolderDrop<RIGHT_SIDE>(baseFolder, folderAccessTimeout, callback)) continue; //create base folders if not yet existing if (folderPairStat.createCount() > 0 || folderPairCfg.saveSyncDB_) //else: temporary network drop leading to deletions already caught by "sourceFolderMissing" check! - if (!createBaseFolder< LEFT_SIDE>(*j, callback) || //+ detect temporary network drop!! - !createBaseFolder<RIGHT_SIDE>(*j, callback)) // + if (!createBaseFolder< LEFT_SIDE>(baseFolder, callback) || //+ detect temporary network drop!! + !createBaseFolder<RIGHT_SIDE>(baseFolder, callback)) // continue; //------------------------------------------------------------------------------------------ @@ -2211,7 +2222,7 @@ void zen::synchronize(const TimeComp& timeStamp, try { if (folderPairCfg.saveSyncDB_) - zen::saveLastSynchronousState(*j, nullptr); + zen::saveLastSynchronousState(baseFolder, nullptr); } //throw FileError catch (FileError&) {} ); @@ -2219,15 +2230,16 @@ void zen::synchronize(const TimeComp& timeStamp, if (jobType[folderIndex] == FolderPairJobType::PROCESS) { //guarantee removal of invalid entries (where element is empty on both sides) - ZEN_ON_SCOPE_EXIT(BaseFolderPair::removeEmpty(*j)); + ZEN_ON_SCOPE_EXIT(BaseFolderPair::removeEmpty(baseFolder)); bool copyPermissionsFp = false; tryReportingError([&] { copyPermissionsFp = copyFilePermissions && //copy permissions only if asked for and supported by *both* sides! - !AFS::isNullPath(j->getAbstractPath< LEFT_SIDE>()) && //scenario: directory selected on one side only - !AFS::isNullPath(j->getAbstractPath<RIGHT_SIDE>()) && // - AFS::supportPermissionCopy(j->getAbstractPath<LEFT_SIDE>(), j->getAbstractPath<RIGHT_SIDE>()); //throw FileError + !AFS::isNullPath(baseFolder.getAbstractPath< LEFT_SIDE>()) && //scenario: directory selected on one side only + !AFS::isNullPath(baseFolder.getAbstractPath<RIGHT_SIDE>()) && // + AFS::supportPermissionCopy(baseFolder.getAbstractPath<LEFT_SIDE>(), + baseFolder.getAbstractPath<RIGHT_SIDE>()); //throw FileError }, callback); //throw X? @@ -2244,15 +2256,15 @@ void zen::synchronize(const TimeComp& timeStamp, }; - DeletionHandling delHandlerL(j->getAbstractPath<LEFT_SIDE>(), - getEffectiveDeletionPolicy(j->getAbstractPath<LEFT_SIDE>()), + DeletionHandling delHandlerL(baseFolder.getAbstractPath<LEFT_SIDE>(), + getEffectiveDeletionPolicy(baseFolder.getAbstractPath<LEFT_SIDE>()), folderPairCfg.versioningFolderPhrase, folderPairCfg.versioningStyle_, timeStamp, callback); - DeletionHandling delHandlerR(j->getAbstractPath<RIGHT_SIDE>(), - getEffectiveDeletionPolicy(j->getAbstractPath<RIGHT_SIDE>()), + DeletionHandling delHandlerR(baseFolder.getAbstractPath<RIGHT_SIDE>(), + getEffectiveDeletionPolicy(baseFolder.getAbstractPath<RIGHT_SIDE>()), folderPairCfg.versioningFolderPhrase, folderPairCfg.versioningStyle_, timeStamp, @@ -2264,7 +2276,7 @@ void zen::synchronize(const TimeComp& timeStamp, shadowCopyHandler.get(), #endif delHandlerL, delHandlerR); - syncFP.startSync(*j); + syncFP.startSync(baseFolder); //(try to gracefully) cleanup temporary Recycle bin folders and versioning -> will be done in ~DeletionHandling anyway... tryReportingError([&] { delHandlerL.tryCleanup(true /*allowUserCallback*/); /*throw FileError*/}, callback); //throw X? @@ -2282,7 +2294,7 @@ void zen::synchronize(const TimeComp& timeStamp, tryReportingError([&] { std::int64_t bytesWritten = 0; - zen::saveLastSynchronousState(*j, [&](std::int64_t bytesDelta) //throw FileError + zen::saveLastSynchronousState(baseFolder, [&](std::int64_t bytesDelta) //throw FileError { bytesWritten += bytesDelta; callback.reportStatus(dbUpdateMsg + L" (" + filesizeToShortString(bytesWritten) + L")"); //throw X diff --git a/FreeFileSync/Source/synchronization.h b/FreeFileSync/Source/synchronization.h index db9b3308..3992cda0 100644 --- a/FreeFileSync/Source/synchronization.h +++ b/FreeFileSync/Source/synchronization.h @@ -35,12 +35,19 @@ public: int deleteCount() const { return SelectParam<side>::ref(deleteLeft_, deleteRight_); } int deleteCount() const { return deleteLeft_ + deleteRight_; } + template <SelectedSide side> + bool expectPhysicalDeletion() const { return SelectParam<side>::ref(physicalDeleteLeft_, physicalDeleteRight_); } + int conflictCount() const { return static_cast<int>(conflictMsgs_.size()); } std::int64_t getBytesToProcess() const { return bytesToProcess_; } size_t rowCount () const { return rowsTotal_; } - using ConflictInfo = std::pair<Zstring, std::wstring>; //pair(filePath/conflict message) + struct ConflictInfo + { + Zstring relPath; + std::wstring msg; + }; const std::vector<ConflictInfo>& getConflicts() const { return conflictMsgs_; } private: @@ -56,6 +63,8 @@ private: int updateRight_ = 0; int deleteLeft_ = 0; int deleteRight_ = 0; + bool physicalDeleteLeft_ = false; //at least 1 item will be deleted; considers most "update" cases which also delete items + bool physicalDeleteRight_ = false; // std::vector<ConflictInfo> conflictMsgs_; //conflict texts to display as a warning message std::int64_t bytesToProcess_ = 0; size_t rowsTotal_ = 0; diff --git a/FreeFileSync/Source/ui/batch_status_handler.cpp b/FreeFileSync/Source/ui/batch_status_handler.cpp index d9438dd4..76fe7b10 100644 --- a/FreeFileSync/Source/ui/batch_status_handler.cpp +++ b/FreeFileSync/Source/ui/batch_status_handler.cpp @@ -6,6 +6,7 @@ #include "batch_status_handler.h" #include <zen/shell_execute.h> +#include <zen/thread.h> #include <wx+/popup_dlg.h> #include <wx/app.h> #include "on_completion_box.h" @@ -143,25 +144,22 @@ BatchStatusHandler::BatchStatusHandler(bool showProgress, const xmlAccess::OnError handleError, size_t automaticRetryCount, size_t automaticRetryDelay, - const SwitchToGui& switchBatchToGui, //functionality to change from batch mode to GUI mode FfsReturnCode& returnCode, const Zstring& onCompletion, std::vector<Zstring>& onCompletionHistory) : - switchBatchToGui_(switchBatchToGui), - showFinalResults(showProgress), //=> exit immediately or wait when finished + showFinalResults_(showProgress), //=> exit immediately or wait when finished logfilesCountLimit_(logfilesCountLimit), lastSyncsLogFileSizeMax_(lastSyncsLogFileSizeMax), handleError_(handleError), returnCode_(returnCode), automaticRetryCount_(automaticRetryCount), automaticRetryDelay_(automaticRetryDelay), - progressDlg(createProgressDialog(*this, [this] { this->onProgressDialogTerminate(); }, *this, nullptr, showProgress, jobName, soundFileSyncComplete, onCompletion, onCompletionHistory)), - jobName_(jobName), - timeStamp_(timeStamp), - startTime_(std::time(nullptr)), - logFolderPathPhrase_(logFolderPathPhrase) + progressDlg_(createProgressDialog(*this, [this] { this->onProgressDialogTerminate(); }, *this, nullptr, showProgress, jobName, soundFileSyncComplete, onCompletion, onCompletionHistory)), + jobName_(jobName), + timeStamp_(timeStamp), + logFolderPathPhrase_(logFolderPathPhrase) { - //ATTENTION: "progressDlg" is an unmanaged resource!!! However, at this point we already consider construction complete! => + //ATTENTION: "progressDlg_" is an unmanaged resource!!! However, at this point we already consider construction complete! => //ZEN_ON_SCOPE_FAIL( cleanup(); ); //destructor call would lead to member double clean-up!!! //... @@ -176,28 +174,21 @@ BatchStatusHandler::~BatchStatusHandler() //------------ "on completion" command conceptually is part of the sync, not cleanup -------------------------------------- //decide whether to stay on status screen or exit immediately... - if (switchToGuiRequested) //-> avoid recursive yield() calls, thous switch not before ending batch mode + if (progressDlg_) { - try - { - switchBatchToGui_.execute(); //open FreeFileSync GUI - } - catch (...) { assert(false); } - showFinalResults = false; - } - else if (progressDlg) - { - if (progressDlg->getWindowIfVisible()) - showFinalResults = true; + if (switchToGuiRequested_) //-> avoid recursive yield() calls, thous switch not before ending batch mode + showFinalResults_ = false; + else if (progressDlg_->getWindowIfVisible()) + showFinalResults_ = true; //execute "on completion" command (even in case of ignored errors) if (!abortIsRequested()) //if aborted (manually), we don't execute the command { - const Zstring finalCommand = progressDlg->getExecWhenFinishedCommand(); //final value (after possible user modification) + const Zstring finalCommand = progressDlg_->getExecWhenFinishedCommand(); //final value (after possible user modification) if (!finalCommand.empty()) { if (isCloseProgressDlgCommand(finalCommand)) - showFinalResults = false; //take precedence over current visibility status + showFinalResults_ = false; //take precedence over current visibility status else try { @@ -211,8 +202,8 @@ BatchStatusHandler::~BatchStatusHandler() } //------------ 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 status; //additionally indicate errors in log file name @@ -221,21 +212,21 @@ BatchStatusHandler::~BatchStatusHandler() { raiseReturnCode(returnCode_, FFS_RC_ABORTED); finalStatusMsg = _("Synchronization stopped"); - errorLog.logMsg(finalStatusMsg, TYPE_ERROR); + errorLog_.logMsg(finalStatusMsg, TYPE_ERROR); status = _("Stopped"); } else if (totalErrors > 0) { raiseReturnCode(returnCode_, FFS_RC_FINISHED_WITH_ERRORS); finalStatusMsg = _("Synchronization completed with errors"); - errorLog.logMsg(finalStatusMsg, TYPE_ERROR); + errorLog_.logMsg(finalStatusMsg, TYPE_ERROR); status = _("Error"); } else if (totalWarnings > 0) { raiseReturnCode(returnCode_, FFS_RC_FINISHED_WITH_WARNINGS); finalStatusMsg = _("Synchronization completed with warnings"); - errorLog.logMsg(finalStatusMsg, TYPE_WARNING); + errorLog_.logMsg(finalStatusMsg, TYPE_WARNING); status = _("Warning"); } else @@ -245,7 +236,7 @@ BatchStatusHandler::~BatchStatusHandler() finalStatusMsg = _("Nothing to synchronize"); //even if "ignored conflicts" occurred! else finalStatusMsg = _("Synchronization completed successfully"); - errorLog.logMsg(finalStatusMsg, TYPE_INFO); + errorLog_.logMsg(finalStatusMsg, TYPE_INFO); } const SummaryInfo summary = @@ -263,7 +254,7 @@ BatchStatusHandler::~BatchStatusHandler() { auto requestUiRefreshNoThrow = [&] { try { requestUiRefresh(); /*throw X*/ } catch (...) {} }; - const AbstractPath logFolderPath = createAbstractPath(trimCpy(logFolderPathPhrase_).empty() ? getConfigDir() + Zstr("Logs") : logFolderPathPhrase_); //noexcept + const AbstractPath logFolderPath = createAbstractPath(trimCpy(logFolderPathPhrase_).empty() ? getConfigDirPathPf() + Zstr("Logs") : logFolderPathPhrase_); //noexcept try { tryReportingError([&] //errors logged here do not impact final status calculation above! => not a problem! @@ -272,7 +263,7 @@ BatchStatusHandler::~BatchStatusHandler() AFS::OutputStream& logFileStream = *rv.first; const AbstractPath logFilePath = rv.second; - streamToLogFile(summary, errorLog, logFileStream, OnUpdateLogfileStatusNoThrow(*this, AFS::getDisplayPath(logFilePath))); //throw FileError + streamToLogFile(summary, errorLog_, logFileStream, OnUpdateLogfileStatusNoThrow(*this, AFS::getDisplayPath(logFilePath))); //throw FileError logFileStream.finalize(requestUiRefreshNoThrow); //throw FileError }, *this); //throw X? } @@ -296,37 +287,37 @@ BatchStatusHandler::~BatchStatusHandler() //----------------- 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); } - if (progressDlg) + if (progressDlg_) { - if (showFinalResults) //warning: wxWindow::Show() is called within processHasFinished()! + if (showFinalResults_) //warning: wxWindow::Show() is called within processHasFinished()! { //notify about (logical) application main window => program won't quit, but stay on this dialog - //setMainWindow(progressDlg->getAsWindow()); -> not required anymore since we block waiting until dialog is closed below + //setMainWindow(progressDlg_->getAsWindow()); -> not required anymore since we block waiting until dialog is closed below - //notify to progressDlg that current process has ended + //notify to progressDlg_ that current process has ended 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(); //progressDlg is main window => program will quit directly + progressDlg_->closeWindowDirectly(); //progressDlg_ is main window => program will quit directly //wait until progress dialog notified shutdown via onProgressDialogTerminate() //-> required since it has our "this" pointer captured in lambda "notifyWindowTerminate"! //-> nicely manages dialog lifetime - while (progressDlg) + while (progressDlg_) { wxTheApp->Yield(); //*first* refresh GUI (removing flicker) before sleeping! - std::this_thread::sleep_for(std::chrono::milliseconds(UI_UPDATE_INTERVAL)); + std::this_thread::sleep_for(std::chrono::milliseconds(UI_UPDATE_INTERVAL_MS)); } } } @@ -335,8 +326,8 @@ BatchStatusHandler::~BatchStatusHandler() void BatchStatusHandler::initNewPhase(int itemsTotal, std::int64_t bytesTotal, ProcessCallback::Phase phaseID) { StatusHandler::initNewPhase(itemsTotal, bytesTotal, phaseID); - if (progressDlg) - progressDlg->initNewPhase(); //call after "StatusHandler::initNewPhase" + if (progressDlg_) + progressDlg_->initNewPhase(); //call after "StatusHandler::initNewPhase" forceUiRefresh(); //throw ?; OS X needs a full yield to update GUI and get rid of "dummy" texts } @@ -346,8 +337,8 @@ void BatchStatusHandler::updateProcessedData(int itemsDelta, std::int64_t bytesD { StatusHandler::updateProcessedData(itemsDelta, bytesDelta); - if (progressDlg) - progressDlg->notifyProgressChange(); //noexcept + if (progressDlg_) + progressDlg_->notifyProgressChange(); //noexcept //note: this method should NOT throw in order to properly allow undoing setting of statistics! } @@ -355,13 +346,13 @@ void BatchStatusHandler::updateProcessedData(int itemsDelta, std::int64_t bytesD void BatchStatusHandler::reportInfo(const std::wstring& text) { StatusHandler::reportInfo(text); - errorLog.logMsg(text, TYPE_INFO); + errorLog_.logMsg(text, TYPE_INFO); } void BatchStatusHandler::reportWarning(const std::wstring& warningMessage, bool& warningActive) { - errorLog.logMsg(warningMessage, TYPE_WARNING); + errorLog_.logMsg(warningMessage, TYPE_WARNING); if (!warningActive) return; @@ -370,12 +361,12 @@ void BatchStatusHandler::reportWarning(const std::wstring& warningMessage, bool& { case xmlAccess::ON_ERROR_POPUP: { - if (!progressDlg) abortProcessNow(); - PauseTimers dummy(*progressDlg); + if (!progressDlg_) abortProcessNow(); + PauseTimers dummy(*progressDlg_); forceUiRefresh(); bool dontWarnAgain = false; - switch (showConfirmationDialog3(progressDlg->getWindowIfVisible(), DialogInfoType::WARNING, PopupDialogCfg3(). + switch (showConfirmationDialog3(progressDlg_->getWindowIfVisible(), DialogInfoType::WARNING, PopupDialogCfg3(). setDetailInstructions(warningMessage + L"\n\n" + _("You can switch to FreeFileSync's main window to resolve this issue.")). setCheckBox(dontWarnAgain, _("&Don't show this warning again"), ConfirmationButton3::DONT_DO_IT), _("&Ignore"), _("&Switch"))) @@ -385,10 +376,10 @@ void BatchStatusHandler::reportWarning(const std::wstring& warningMessage, bool& break; case ConfirmationButton3::DONT_DO_IT: //switch - errorLog.logMsg(_("Switching to FreeFileSync's main window"), TYPE_INFO); - switchToGuiRequested = true; - abortProcessNow(); - break; + errorLog_.logMsg(_("Switching to FreeFileSync's main window"), TYPE_INFO); + switchToGuiRequested_ = true; //treat as a special kind of cancel + requestAbortion(); // + throw BatchRequestSwitchToMainDialog(); case ConfirmationButton3::CANCEL: abortProcessNow(); @@ -412,33 +403,33 @@ ProcessCallback::Response BatchStatusHandler::reportError(const std::wstring& er //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 + const int iterations = static_cast<int>(1000 * automaticRetryDelay_ / UI_UPDATE_INTERVAL_MS); //always round down: don't allow for negative remaining time below for (int i = 0; i < iterations; ++i) { reportStatus(_("Error") + L": " + _P("Automatic retry in 1 second...", "Automatic retry in %x seconds...", - (1000 * automaticRetryDelay_ - i * UI_UPDATE_INTERVAL + 999) / 1000)); //integer round up - std::this_thread::sleep_for(std::chrono::milliseconds(UI_UPDATE_INTERVAL)); + (1000 * automaticRetryDelay_ - i * UI_UPDATE_INTERVAL_MS + 999) / 1000)); //integer round up + std::this_thread::sleep_for(std::chrono::milliseconds(UI_UPDATE_INTERVAL_MS)); } return ProcessCallback::RETRY; } //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_) { case xmlAccess::ON_ERROR_POPUP: { - if (!progressDlg) abortProcessNow(); - PauseTimers dummy(*progressDlg); + if (!progressDlg_) abortProcessNow(); + PauseTimers dummy(*progressDlg_); forceUiRefresh(); bool ignoreNextErrors = false; - switch (showConfirmationDialog3(progressDlg->getWindowIfVisible(), DialogInfoType::ERROR2, PopupDialogCfg3(). + switch (showConfirmationDialog3(progressDlg_->getWindowIfVisible(), DialogInfoType::ERROR2, PopupDialogCfg3(). setDetailInstructions(errorMessage). setCheckBox(ignoreNextErrors, _("&Ignore subsequent errors"), ConfirmationButton3::DONT_DO_IT), _("&Ignore"), _("&Retry"))) @@ -450,7 +441,7 @@ ProcessCallback::Response BatchStatusHandler::reportError(const std::wstring& er case ConfirmationButton3::DONT_DO_IT: //retry guardWriteLog.dismiss(); - errorLog.logMsg(errorMessage + L"\n-> " + _("Retrying operation..."), TYPE_INFO); + errorLog_.logMsg(errorMessage + L"\n-> " + _("Retrying operation..."), TYPE_INFO); return ProcessCallback::RETRY; case ConfirmationButton3::CANCEL: @@ -475,18 +466,18 @@ ProcessCallback::Response BatchStatusHandler::reportError(const std::wstring& er void BatchStatusHandler::reportFatalError(const std::wstring& errorMessage) { - errorLog.logMsg(errorMessage, TYPE_FATAL_ERROR); + errorLog_.logMsg(errorMessage, TYPE_FATAL_ERROR); switch (handleError_) { case xmlAccess::ON_ERROR_POPUP: { - if (!progressDlg) abortProcessNow(); - PauseTimers dummy(*progressDlg); + if (!progressDlg_) abortProcessNow(); + PauseTimers dummy(*progressDlg_); forceUiRefresh(); bool ignoreNextErrors = false; - switch (showConfirmationDialog(progressDlg->getWindowIfVisible(), DialogInfoType::ERROR2, + switch (showConfirmationDialog(progressDlg_->getWindowIfVisible(), DialogInfoType::ERROR2, PopupDialogCfg().setTitle(_("Serious Error")). setDetailInstructions(errorMessage). setCheckBox(ignoreNextErrors, _("&Ignore subsequent errors")), @@ -515,20 +506,20 @@ void BatchStatusHandler::reportFatalError(const std::wstring& errorMessage) void BatchStatusHandler::forceUiRefresh() { - if (progressDlg) - progressDlg->updateGui(); + if (progressDlg_) + progressDlg_->updateGui(); } void BatchStatusHandler::abortProcessNow() { requestAbortion(); //just make sure... - throw BatchAbortProcess(); //abort can be triggered by progressDlg + throw BatchAbortProcess(); //abort can be triggered by progressDlg_ } void BatchStatusHandler::onProgressDialogTerminate() { - //it's responsibility of "progressDlg" to call requestAbortion() when closing dialog - progressDlg = nullptr; + //it's responsibility of "progressDlg_" to call requestAbortion() when closing dialog + progressDlg_ = nullptr; } diff --git a/FreeFileSync/Source/ui/batch_status_handler.h b/FreeFileSync/Source/ui/batch_status_handler.h index 774f230d..27ace6ee 100644 --- a/FreeFileSync/Source/ui/batch_status_handler.h +++ b/FreeFileSync/Source/ui/batch_status_handler.h @@ -10,18 +10,17 @@ #include <zen/error_log.h> #include <zen/time.h> #include "progress_indicator.h" -#include "switch_to_gui.h" #include "../lib/status_handler.h" #include "../lib/process_xml.h" #include "../lib/return_codes.h" -//Exception class used to abort the "compare" and "sync" process +//Exception classes used to abort the "compare" and "sync" process class BatchAbortProcess {}; - +class BatchRequestSwitchToMainDialog {}; //BatchStatusHandler(SyncProgressDialog) will internally process Window messages! disable GUI controls to avoid unexpected callbacks! -class BatchStatusHandler : public zen::StatusHandler //throw BatchAbortProcess +class BatchStatusHandler : public zen::StatusHandler //throw BatchAbortProcess, BatchRequestSwitchToMainDialog { public: BatchStatusHandler(bool showProgress, //defines: -start minimized and -quit immediately when finished @@ -34,7 +33,6 @@ public: const xmlAccess::OnError handleError, size_t automaticRetryCount, size_t automaticRetryDelay, - const zen::SwitchToGui& switchBatchToGui, //functionality to change from batch mode to GUI mode zen::FfsReturnCode& returnCode, const Zstring& onCompletion, std::vector<Zstring>& onCompletionHistory); @@ -54,23 +52,22 @@ public: private: void onProgressDialogTerminate(); - const zen::SwitchToGui& switchBatchToGui_; //functionality to change from batch mode to GUI mode - bool showFinalResults; - bool switchToGuiRequested = false; + bool showFinalResults_; + bool switchToGuiRequested_ = false; const int logfilesCountLimit_; const size_t lastSyncsLogFileSizeMax_; xmlAccess::OnError handleError_; - zen::ErrorLog errorLog; //list of non-resolved errors and warnings + zen::ErrorLog errorLog_; //list of non-resolved errors and warnings zen::FfsReturnCode& returnCode_; const size_t automaticRetryCount_; const size_t automaticRetryDelay_; - SyncProgressDialog* progressDlg; //managed to have shorter lifetime than this handler! + SyncProgressDialog* progressDlg_; //managed to have shorter lifetime than this handler! const std::wstring jobName_; const zen::TimeComp timeStamp_; - const time_t startTime_; //don't use wxStopWatch: may overflow after a few days due to ::QueryPerformanceCounter() + const time_t startTime_ = std::time(nullptr); //don't use wxStopWatch: may overflow after a few days due to ::QueryPerformanceCounter() const Zstring logFolderPathPhrase_; }; diff --git a/FreeFileSync/Source/ui/column_attr.h b/FreeFileSync/Source/ui/column_attr.h index d48640bc..2f8daf5f 100644 --- a/FreeFileSync/Source/ui/column_attr.h +++ b/FreeFileSync/Source/ui/column_attr.h @@ -16,7 +16,7 @@ enum class ColumnTypeRim ITEM_PATH, SIZE, DATE, - EXTENSION + EXTENSION, }; struct ColumnAttributeRim @@ -33,12 +33,12 @@ struct ColumnAttributeRim inline std::vector<ColumnAttributeRim> getDefaultColumnAttributesLeft() { - return + return //harmonize with main_dlg.cpp::onGridLabelContextRim() => expects stretched ITEM_PATH and non-stretched other columns! { { ColumnTypeRim::ITEM_PATH, -80, 1, true }, //stretch to full width and substract sum of fixed-size widths! + { ColumnTypeRim::EXTENSION, 60, 0, false }, { ColumnTypeRim::DATE, 112, 0, false }, { ColumnTypeRim::SIZE, 80, 0, true }, - { ColumnTypeRim::EXTENSION, 60, 0, false }, }; } @@ -48,9 +48,9 @@ std::vector<ColumnAttributeRim> getDefaultColumnAttributesRight() return { { ColumnTypeRim::ITEM_PATH, -80, 1, true }, + { ColumnTypeRim::EXTENSION, 60, 0, false }, { ColumnTypeRim::DATE, 112, 0, false }, { ColumnTypeRim::SIZE, 80, 0, true }, - { ColumnTypeRim::EXTENSION, 60, 0, false }, }; } @@ -58,7 +58,7 @@ enum class ItemPathFormat { FULL_PATH, RELATIVE_PATH, - ITEM_NAME + ITEM_NAME, }; const ItemPathFormat defaultItemPathFormatLeftGrid = ItemPathFormat::RELATIVE_PATH; @@ -77,9 +77,9 @@ enum class ColumnTypeCenter enum class ColumnTypeNavi { + FOLDER_NAME, + ITEM_COUNT, BYTES, - DIRECTORY, - ITEM_COUNT }; struct ColumnAttributeNavi @@ -87,7 +87,7 @@ struct ColumnAttributeNavi ColumnAttributeNavi() {} ColumnAttributeNavi(ColumnTypeNavi type, int offset, int stretch, bool visible) : type_(type), offset_(offset), stretch_(stretch), visible_(visible) {} - ColumnTypeNavi type_ = ColumnTypeNavi::DIRECTORY; + ColumnTypeNavi type_ = ColumnTypeNavi::FOLDER_NAME; int offset_ = 0; int stretch_ = 0;; bool visible_ = false; @@ -97,17 +97,17 @@ struct ColumnAttributeNavi inline std::vector<ColumnAttributeNavi> getDefaultColumnAttributesNavi() { - return + return //harmonize with tree_view.cpp::onGridLabelContext() => expects stretched FOLDER_NAME and non-stretched other columns! { - { ColumnTypeNavi::DIRECTORY, -120, 1, true }, //stretch to full width and substract sum of fixed size widths - { ColumnTypeNavi::ITEM_COUNT, 60, 0, true }, - { ColumnTypeNavi::BYTES, 60, 0, true }, //GTK needs a few pixels more width + { ColumnTypeNavi::FOLDER_NAME, -120, 1, true }, //stretch to full width and substract sum of fixed size widths + { ColumnTypeNavi::ITEM_COUNT, 60, 0, true }, + { ColumnTypeNavi::BYTES, 60, 0, true }, //GTK needs a few pixels more width }; } -const bool naviGridShowPercentageDefault = true; -const ColumnTypeNavi naviGridLastSortColumnDefault = ColumnTypeNavi::BYTES; //remember sort on navigation panel -const bool naviGridLastSortAscendingDefault = false; // +const bool naviGridShowPercentageDefault = true; +const ColumnTypeNavi naviGridLastSortColumnDefault = ColumnTypeNavi::BYTES; //remember sort on navigation panel +const bool naviGridLastSortAscendingDefault = false; // } #endif //COLUMN_ATTR_H_189467891346732143213 diff --git a/FreeFileSync/Source/ui/custom_grid.cpp b/FreeFileSync/Source/ui/custom_grid.cpp index 3d068b6b..b47e6de9 100644 --- a/FreeFileSync/Source/ui/custom_grid.cpp +++ b/FreeFileSync/Source/ui/custom_grid.cpp @@ -196,6 +196,29 @@ public: void setItemPathForm(ItemPathFormat fmt) { itemPathFormat = fmt; } + void getUnbufferedIconsForPreload(std::vector<std::pair<ptrdiff_t, AbstractPath>>& newLoad) //return (priority, filepath) list + { + if (iconMgr_) + { + const auto& rowsOnScreen = getVisibleRows(refGrid()); + const ptrdiff_t visibleRowCount = rowsOnScreen.second - rowsOnScreen.first; + + //preload icons not yet on screen: + const int preloadSize = 2 * std::max<ptrdiff_t>(20, visibleRowCount); //:= sum of lines above and below of visible range to preload + //=> use full visible height to handle "next page" command and a minimum of 20 for excessive mouse wheel scrolls + + for (ptrdiff_t i = 0; i < preloadSize; ++i) + { + const ptrdiff_t currentRow = rowsOnScreen.first - (preloadSize + 1) / 2 + getAlternatingPos(i, visibleRowCount + preloadSize); //for odd preloadSize start one row earlier + + const IconInfo ii = getIconInfo(currentRow); + if (ii.type == IconInfo::ICON_PATH) + if (!iconMgr_->refIconBuffer().readyForRetrieval(ii.fsObj->template getAbstractPath<side>())) + newLoad.emplace_back(i, ii.fsObj->template getAbstractPath<side>()); //insert least-important items on outer rim first + } + } + } + void updateNewAndGetUnbufferedIcons(std::vector<AbstractPath>& newLoad) //loads all not yet drawn icons { if (iconMgr_) @@ -229,29 +252,6 @@ public: } } - void getUnbufferedIconsForPreload(std::vector<std::pair<ptrdiff_t, AbstractPath>>& newLoad) //return (priority, filepath) list - { - if (iconMgr_) - { - const auto& rowsOnScreen = getVisibleRows(refGrid()); - const ptrdiff_t visibleRowCount = rowsOnScreen.second - rowsOnScreen.first; - - //preload icons not yet on screen: - const int preloadSize = 2 * std::max<ptrdiff_t>(20, visibleRowCount); //:= sum of lines above and below of visible range to preload - //=> use full visible height to handle "next page" command and a minimum of 20 for excessive mouse wheel scrolls - - for (ptrdiff_t i = 0; i < preloadSize; ++i) - { - const ptrdiff_t currentRow = rowsOnScreen.first - (preloadSize + 1) / 2 + getAlternatingPos(i, visibleRowCount + preloadSize); //for odd preloadSize start one row earlier - - const IconInfo ii = getIconInfo(currentRow); - if (ii.type == IconInfo::ICON_PATH) - if (!iconMgr_->refIconBuffer().readyForRetrieval(ii.fsObj->template getAbstractPath<side>())) - newLoad.emplace_back(i, ii.fsObj->template getAbstractPath<side>()); //insert least-important items on outer rim first - } - } - } - private: bool isFailedLoad(size_t row) const { return row < failedLoads.size() ? failedLoads[row] != 0 : false; } @@ -886,7 +886,7 @@ public: } selectionInProgress = false; - //update highlight and tooltip: on OS X no mouse movement event is generated after a mouse button click (unlike on Windows) + //update highlight_ and tooltip: on OS X no mouse movement event is generated after a mouse button click (unlike on Windows) wxPoint clientPos = refGrid().getMainWin().ScreenToClient(wxGetMousePosition()); onMouseMovement(clientPos); } @@ -1635,14 +1635,14 @@ class IconUpdater : private wxEvtHandler //update file icons periodically: use S public: IconUpdater(GridDataLeft& provLeft, GridDataRight& provRight, IconBuffer& iconBuffer) : provLeft_(provLeft), provRight_(provRight), iconBuffer_(iconBuffer) { - timer.Connect(wxEVT_TIMER, wxEventHandler(IconUpdater::loadIconsAsynchronously), nullptr, this); + timer_.Connect(wxEVT_TIMER, wxEventHandler(IconUpdater::loadIconsAsynchronously), nullptr, this); } - void start() { if (!timer.IsRunning()) timer.Start(100); } //timer interval in [ms] + void start() { if (!timer_.IsRunning()) timer_.Start(100); } //timer interval in [ms] //don't check too often! give worker thread some time to fetch data private: - void stop() { if (timer.IsRunning()) timer.Stop(); } + void stop() { if (timer_.IsRunning()) timer_.Stop(); } void loadIconsAsynchronously(wxEvent& event) //loads all (not yet) drawn icons { @@ -1671,7 +1671,7 @@ private: GridDataLeft& provLeft_; GridDataRight& provRight_; IconBuffer& iconBuffer_; - wxTimer timer; + wxTimer timer_; }; diff --git a/FreeFileSync/Source/ui/folder_selector.cpp b/FreeFileSync/Source/ui/folder_selector.cpp index 52af6bd8..1e14634d 100644 --- a/FreeFileSync/Source/ui/folder_selector.cpp +++ b/FreeFileSync/Source/ui/folder_selector.cpp @@ -199,8 +199,8 @@ void FolderSelector::onFilesDropped(FileDropEvent& event) setPath(fmtShellPath(itemPaths[0])); //drop two folder paths at once: - if (siblingSelector && itemPaths.size() >= 2) - siblingSelector->setPath(fmtShellPath(itemPaths[1])); + if (siblingSelector_ && itemPaths.size() >= 2) + siblingSelector_->setPath(fmtShellPath(itemPaths[1])); //notify action invoked by user wxCommandEvent dummy(EVENT_ON_FOLDER_SELECTED); diff --git a/FreeFileSync/Source/ui/folder_selector.h b/FreeFileSync/Source/ui/folder_selector.h index f521747f..c504efa0 100644 --- a/FreeFileSync/Source/ui/folder_selector.h +++ b/FreeFileSync/Source/ui/folder_selector.h @@ -41,7 +41,7 @@ public: ~FolderSelector(); - void setSiblingSelector(FolderSelector* selector) { siblingSelector = selector; } + void setSiblingSelector(FolderSelector* selector) { siblingSelector_ = selector; } Zstring getPath() const; void setPath(const Zstring& folderPathPhrase); @@ -61,7 +61,7 @@ private: wxButton& selectAltFolderButton_; FolderHistoryBox& folderComboBox_; wxStaticText* staticText_ = nullptr; //optional - FolderSelector* siblingSelector = nullptr; + FolderSelector* siblingSelector_ = nullptr; }; } diff --git a/FreeFileSync/Source/ui/gui_generated.cpp b/FreeFileSync/Source/ui/gui_generated.cpp index 4ca60bbe..2683f6a4 100644 --- a/FreeFileSync/Source/ui/gui_generated.cpp +++ b/FreeFileSync/Source/ui/gui_generated.cpp @@ -80,9 +80,8 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const m_menuTools->AppendSeparator(); - wxMenuItem* m_menuItem15; - m_menuItem15 = new wxMenuItem( m_menuTools, wxID_FIND, wxString( _("&Find...") ) + wxT('\t') + wxT("Ctrl+F"), wxEmptyString, wxITEM_NORMAL ); - m_menuTools->Append( m_menuItem15 ); + m_menuItemFind = new wxMenuItem( m_menuTools, wxID_FIND, wxString( _("&Find...") ) + wxT('\t') + wxT("Ctrl+F"), wxEmptyString, wxITEM_NORMAL ); + m_menuTools->Append( m_menuItemFind ); wxMenuItem* m_menuItem51; m_menuItem51 = new wxMenuItem( m_menuTools, wxID_ANY, wxString( _("&Reset layout") ) , wxEmptyString, wxITEM_NORMAL ); @@ -98,16 +97,14 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const m_menuItemHelp = new wxMenuItem( m_menuHelp, wxID_HELP, wxString( _("&View help") ) + wxT('\t') + wxT("F1"), wxEmptyString, wxITEM_NORMAL ); m_menuHelp->Append( m_menuItemHelp ); - m_menuCheckVersion = new wxMenu(); - wxMenuItem* m_menuCheckVersionItem = new wxMenuItem( m_menuHelp, wxID_ANY, _("&Check for new version"), wxEmptyString, wxITEM_NORMAL, m_menuCheckVersion ); - m_menuItemCheckVersionNow = new wxMenuItem( m_menuCheckVersion, wxID_ANY, wxString( _("&Check now") ) , wxEmptyString, wxITEM_NORMAL ); - m_menuCheckVersion->Append( m_menuItemCheckVersionNow ); + m_menuHelp->AppendSeparator(); - m_menuItemCheckVersionAuto = new wxMenuItem( m_menuCheckVersion, wxID_ANY, wxString( _("Check &automatically once a week") ) , wxEmptyString, wxITEM_CHECK ); - m_menuCheckVersion->Append( m_menuItemCheckVersionAuto ); - m_menuItemCheckVersionAuto->Check( true ); + m_menuItemCheckVersionNow = new wxMenuItem( m_menuHelp, wxID_ANY, wxString( _("&Check for updates now") ) , wxEmptyString, wxITEM_NORMAL ); + m_menuHelp->Append( m_menuItemCheckVersionNow ); - m_menuHelp->Append( m_menuCheckVersionItem ); + m_menuItemCheckVersionAuto = new wxMenuItem( m_menuHelp, wxID_ANY, wxString( _("Check &automatically once a week") ) , wxEmptyString, wxITEM_CHECK ); + m_menuHelp->Append( m_menuItemCheckVersionAuto ); + m_menuItemCheckVersionAuto->Check( true ); m_menuHelp->AppendSeparator(); @@ -973,7 +970,7 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const this->Connect( m_menuItemSyncSettings->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnSyncSettings ) ); this->Connect( m_menuItemSynchronize->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnStartSync ) ); this->Connect( m_menuItemOptions->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuOptions ) ); - this->Connect( m_menuItem15->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuFindItem ) ); + this->Connect( m_menuItemFind->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuFindItem ) ); this->Connect( m_menuItem51->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuResetLayout ) ); this->Connect( m_menuItem5->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuExportFileList ) ); this->Connect( m_menuItemHelp->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnShowHelp ) ); @@ -1246,7 +1243,7 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w m_panelCompSettingsHolder->SetSizer( bSizer275 ); m_panelCompSettingsHolder->Layout(); bSizer275->Fit( m_panelCompSettingsHolder ); - m_notebook->AddPage( m_panelCompSettingsHolder, _("dummy"), false ); + m_notebook->AddPage( m_panelCompSettingsHolder, _("dummy"), true ); m_panelFilterSettingsHolder = new wxPanel( m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); m_panelFilterSettingsHolder->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); @@ -1464,7 +1461,7 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w m_staticline46 = new wxStaticLine( m_panelFilterSettingsHolder, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL ); bSizer280->Add( m_staticline46, 0, wxEXPAND, 5 ); - m_buttonClear = new wxButton( m_panelFilterSettingsHolder, wxID_DEFAULT, _("C&lear"), wxDefaultPosition, wxSize( -1,-1 ), 0 ); + m_buttonClear = new wxButton( m_panelFilterSettingsHolder, wxID_ANY, _("C&lear"), wxDefaultPosition, wxSize( -1,-1 ), 0 ); bSizer280->Add( m_buttonClear, 0, wxALL|wxALIGN_CENTER_VERTICAL, 10 ); @@ -1761,22 +1758,22 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w m_staticText93->Wrap( -1 ); bSizer198->Add( m_staticText93, 0, wxRIGHT|wxALIGN_CENTER_VERTICAL, 5 ); + wxArrayString m_choiceVersioningStyleChoices; + m_choiceVersioningStyle = new wxChoice( m_panelVersioning, wxID_ANY, wxDefaultPosition, wxDefaultSize, m_choiceVersioningStyleChoices, 0 ); + m_choiceVersioningStyle->SetSelection( 0 ); + bSizer198->Add( m_choiceVersioningStyle, 0, 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 ); + bSizer198->Add( m_hyperlink17, 0, wxALIGN_CENTER_VERTICAL, 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 ); - m_choiceVersioningStyle->SetSelection( 0 ); - bSizer192->Add( m_choiceVersioningStyle, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); - m_staticTextNamingCvtPart1 = new wxStaticText( m_panelVersioning, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticTextNamingCvtPart1->Wrap( -1 ); m_staticTextNamingCvtPart1->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) ); @@ -1862,7 +1859,7 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w m_panelSyncSettingsHolder->SetSizer( bSizer276 ); m_panelSyncSettingsHolder->Layout(); bSizer276->Fit( m_panelSyncSettingsHolder ); - m_notebook->AddPage( m_panelSyncSettingsHolder, _("dummy"), true ); + m_notebook->AddPage( m_panelSyncSettingsHolder, _("dummy"), false ); bSizer190->Add( m_notebook, 1, wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 5 ); @@ -1933,8 +1930,8 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w 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_hyperlink17->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( ConfigDlgGenerated::OnHelpVersioning ), 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_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 ); @@ -3823,7 +3820,7 @@ OptionsDlgGenerated::~OptionsDlgGenerated() { } -TooltipDialogGenerated::TooltipDialogGenerated( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style ) +TooltipDlgGenerated::TooltipDlgGenerated( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style ) { this->SetSizeHints( wxDefaultSize, wxDefaultSize ); @@ -3843,7 +3840,7 @@ TooltipDialogGenerated::TooltipDialogGenerated( wxWindow* parent, wxWindowID id, bSizer158->Fit( this ); } -TooltipDialogGenerated::~TooltipDialogGenerated() +TooltipDlgGenerated::~TooltipDlgGenerated() { } @@ -4017,6 +4014,57 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS bSizer181->Add( bSizer187, 0, wxALL|wxEXPAND, 5 ); + m_panelThankYou = new wxPanel( m_panel41, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); + m_panelThankYou->SetBackgroundColour( wxColour( 153, 170, 187 ) ); + + wxBoxSizer* bSizer1831; + bSizer1831 = new wxBoxSizer( wxVERTICAL ); + + m_panel391 = new wxPanel( m_panelThankYou, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); + m_panel391->SetBackgroundColour( wxColour( 224, 247, 201 ) ); + + wxBoxSizer* bSizer1841; + bSizer1841 = new wxBoxSizer( wxHORIZONTAL ); + + + bSizer1841->Add( 0, 0, 1, wxEXPAND, 5 ); + + m_bitmapThanks = new wxStaticBitmap( m_panel391, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); + bSizer1841->Add( m_bitmapThanks, 0, wxALIGN_CENTER_VERTICAL, 5 ); + + wxBoxSizer* bSizer1781; + bSizer1781 = new wxBoxSizer( wxVERTICAL ); + + m_staticTextThanks = new wxStaticText( m_panel391, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTRE ); + m_staticTextThanks->Wrap( -1 ); + m_staticTextThanks->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), 70, 90, 90, false, wxEmptyString ) ); + m_staticTextThanks->SetForegroundColour( wxColour( 0, 0, 0 ) ); + + bSizer1781->Add( m_staticTextThanks, 0, wxALL|wxALIGN_CENTER_HORIZONTAL, 5 ); + + m_buttonShowDonationDetails = new wxButton( m_panel391, wxID_ANY, _("Donation details"), wxDefaultPosition, wxDefaultSize, 0 ); + m_buttonShowDonationDetails->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), 70, 90, 90, false, wxEmptyString ) ); + + bSizer1781->Add( m_buttonShowDonationDetails, 0, wxBOTTOM|wxRIGHT|wxLEFT|wxALIGN_CENTER_HORIZONTAL, 5 ); + + + bSizer1841->Add( bSizer1781, 0, wxALIGN_CENTER_VERTICAL, 5 ); + + + bSizer1841->Add( 0, 0, 1, wxEXPAND, 5 ); + + + m_panel391->SetSizer( bSizer1841 ); + m_panel391->Layout(); + bSizer1841->Fit( m_panel391 ); + bSizer1831->Add( m_panel391, 0, wxEXPAND|wxALL, 5 ); + + + m_panelThankYou->SetSizer( bSizer1831 ); + m_panelThankYou->Layout(); + bSizer1831->Fit( m_panelThankYou ); + bSizer181->Add( m_panelThankYou, 0, wxEXPAND|wxRIGHT|wxLEFT, 5 ); + m_panelDonate = new wxPanel( m_panel41, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); m_panelDonate->SetBackgroundColour( wxColour( 153, 170, 187 ) ); @@ -4038,12 +4086,12 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS wxBoxSizer* bSizer178; bSizer178 = new wxBoxSizer( wxVERTICAL ); - m_staticText83 = new wxStaticText( m_panel39, wxID_ANY, _("If you like FreeFileSync:"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticText83->Wrap( -1 ); - m_staticText83->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), 70, 90, 92, false, wxEmptyString ) ); - m_staticText83->SetForegroundColour( wxColour( 0, 0, 0 ) ); + m_staticTextDonate = new wxStaticText( m_panel39, wxID_ANY, _("If you like FreeFileSync:"), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTRE ); + m_staticTextDonate->Wrap( -1 ); + m_staticTextDonate->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), 70, 90, 90, false, wxEmptyString ) ); + m_staticTextDonate->SetForegroundColour( wxColour( 0, 0, 0 ) ); - bSizer178->Add( m_staticText83, 0, wxALL|wxALIGN_CENTER_HORIZONTAL, 5 ); + bSizer178->Add( m_staticTextDonate, 0, wxALL|wxALIGN_CENTER_HORIZONTAL, 5 ); m_buttonDonate = new wxButton( m_panel39, wxID_ANY, _("Donate with PayPal"), wxDefaultPosition, wxDefaultSize, 0 ); m_buttonDonate->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), 70, 90, 90, false, wxEmptyString ) ); @@ -4083,7 +4131,7 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS bSizer166->Add( 0, 0, 1, wxEXPAND, 5 ); m_bitmapHomepage = new wxStaticBitmap( m_panel41, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), 0 ); - m_bitmapHomepage->SetToolTip( _("Homepage") ); + m_bitmapHomepage->SetToolTip( _("Home page") ); bSizer166->Add( m_bitmapHomepage, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 ); @@ -4211,6 +4259,7 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS // Connect Events this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( AboutDlgGenerated::OnClose ) ); + m_buttonShowDonationDetails->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AboutDlgGenerated::OnShowDonationDetails ), NULL, this ); m_buttonDonate->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AboutDlgGenerated::OnDonate ), NULL, this ); m_buttonClose->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AboutDlgGenerated::OnOK ), NULL, this ); } @@ -4218,3 +4267,244 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS AboutDlgGenerated::~AboutDlgGenerated() { } + +DownloadProgressDlgGenerated::DownloadProgressDlgGenerated( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style ) +{ + this->SetSizeHints( wxDefaultSize, wxDefaultSize ); + + wxBoxSizer* bSizer24; + bSizer24 = new wxBoxSizer( wxVERTICAL ); + + wxBoxSizer* bSizer72; + bSizer72 = new wxBoxSizer( wxHORIZONTAL ); + + m_bitmapDownloading = new wxStaticBitmap( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); + bSizer72->Add( m_bitmapDownloading, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT, 10 ); + + m_staticTextHeader = new wxStaticText( this, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0|wxNO_BORDER ); + m_staticTextHeader->Wrap( 460 ); + bSizer72->Add( m_staticTextHeader, 0, wxALIGN_CENTER_VERTICAL|wxALL, 10 ); + + + bSizer72->Add( 20, 0, 0, 0, 5 ); + + + bSizer24->Add( bSizer72, 0, 0, 5 ); + + wxBoxSizer* bSizer212; + bSizer212 = new wxBoxSizer( wxVERTICAL ); + + m_gaugeProgress = new wxGauge( this, wxID_ANY, 100, wxDefaultPosition, wxDefaultSize, wxGA_HORIZONTAL ); + m_gaugeProgress->SetValue( 0 ); + bSizer212->Add( m_gaugeProgress, 0, wxEXPAND|wxRIGHT|wxLEFT, 5 ); + + m_staticTextDetails = new wxStaticText( this, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticTextDetails->Wrap( -1 ); + bSizer212->Add( m_staticTextDetails, 0, wxALIGN_CENTER_HORIZONTAL|wxALL, 5 ); + + + bSizer24->Add( bSizer212, 0, wxBOTTOM|wxRIGHT|wxLEFT, 5 ); + + m_staticline9 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + bSizer24->Add( m_staticline9, 0, wxEXPAND, 5 ); + + bSizerStdButtons = new wxBoxSizer( wxHORIZONTAL ); + + m_buttonCancel = new wxButton( this, wxID_CANCEL, _("Cancel"), wxDefaultPosition, wxSize( -1,-1 ), 0 ); + m_buttonCancel->SetDefault(); + bSizerStdButtons->Add( m_buttonCancel, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); + + + bSizer24->Add( bSizerStdButtons, 0, wxALIGN_RIGHT, 5 ); + + + this->SetSizer( bSizer24 ); + this->Layout(); + bSizer24->Fit( this ); + + // Connect Events + m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DownloadProgressDlgGenerated::OnCancel ), NULL, this ); +} + +DownloadProgressDlgGenerated::~DownloadProgressDlgGenerated() +{ +} + +ActivationDlgGenerated::ActivationDlgGenerated( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style ) +{ + this->SetSizeHints( wxSize( -1,-1 ), wxDefaultSize ); + this->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) ); + + wxBoxSizer* bSizer54; + bSizer54 = new wxBoxSizer( wxVERTICAL ); + + m_panel35 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); + m_panel35->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + + wxBoxSizer* bSizer172; + bSizer172 = new wxBoxSizer( wxVERTICAL ); + + wxBoxSizer* bSizer165; + bSizer165 = new wxBoxSizer( wxHORIZONTAL ); + + m_bitmapActivation = new wxStaticBitmap( m_panel35, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), 0 ); + bSizer165->Add( m_bitmapActivation, 0, wxALL, 10 ); + + + bSizer165->Add( 0, 120, 0, 0, 5 ); + + wxBoxSizer* bSizer16; + bSizer16 = new wxBoxSizer( wxVERTICAL ); + + + bSizer16->Add( 0, 10, 0, 0, 5 ); + + m_textCtrlLastError = new wxTextCtrl( m_panel35, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_READONLY|wxNO_BORDER ); + bSizer16->Add( m_textCtrlLastError, 1, wxEXPAND, 5 ); + + + bSizer16->Add( 0, 5, 0, 0, 5 ); + + + bSizer165->Add( bSizer16, 1, wxEXPAND, 5 ); + + + bSizer172->Add( bSizer165, 1, wxEXPAND, 5 ); + + m_staticTextMain = new wxStaticText( m_panel35, wxID_ANY, _("Activate the FreeFileSync Donation Edition by one of the following methods:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticTextMain->Wrap( -1 ); + bSizer172->Add( m_staticTextMain, 0, wxBOTTOM|wxRIGHT|wxLEFT, 10 ); + + + m_panel35->SetSizer( bSizer172 ); + m_panel35->Layout(); + bSizer172->Fit( m_panel35 ); + bSizer54->Add( m_panel35, 1, wxEXPAND, 5 ); + + m_staticline181 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + bSizer54->Add( m_staticline181, 0, wxEXPAND|wxBOTTOM, 5 ); + + m_staticline18111 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + bSizer54->Add( m_staticline18111, 0, wxEXPAND|wxTOP, 5 ); + + m_panel3511 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); + m_panel3511->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + + wxBoxSizer* bSizer263; + bSizer263 = new wxBoxSizer( wxVERTICAL ); + + wxBoxSizer* bSizer234; + bSizer234 = new wxBoxSizer( wxHORIZONTAL ); + + m_staticText136 = new wxStaticText( m_panel3511, wxID_ANY, _("1. Activate via internet now:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText136->Wrap( -1 ); + bSizer234->Add( m_staticText136, 1, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT, 5 ); + + m_buttonActivateOnline = new wxButton( m_panel3511, wxID_ANY, _("Activate online"), wxDefaultPosition, wxSize( -1,-1 ), 0 ); + m_buttonActivateOnline->SetDefault(); + m_buttonActivateOnline->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), 70, 90, 92, false, wxEmptyString ) ); + + bSizer234->Add( m_buttonActivateOnline, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); + + + bSizer263->Add( bSizer234, 0, wxEXPAND|wxALL, 5 ); + + + m_panel3511->SetSizer( bSizer263 ); + m_panel3511->Layout(); + bSizer263->Fit( m_panel3511 ); + bSizer54->Add( m_panel3511, 0, wxEXPAND, 5 ); + + m_staticline181111 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + bSizer54->Add( m_staticline181111, 0, wxEXPAND|wxBOTTOM, 5 ); + + m_staticline181112 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + bSizer54->Add( m_staticline181112, 0, wxEXPAND|wxTOP, 5 ); + + m_panel351 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); + m_panel351->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + + wxBoxSizer* bSizer266; + bSizer266 = new wxBoxSizer( wxVERTICAL ); + + wxBoxSizer* bSizer237; + bSizer237 = new wxBoxSizer( wxVERTICAL ); + + wxBoxSizer* bSizer236; + bSizer236 = new wxBoxSizer( wxHORIZONTAL ); + + m_staticText1361 = new wxStaticText( m_panel351, wxID_ANY, _("2. Retrieve an offline activation key from the following URL:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText1361->Wrap( -1 ); + bSizer236->Add( m_staticText1361, 1, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT, 5 ); + + m_buttonCopyUrl = new wxButton( m_panel351, wxID_ANY, _("&Copy to clipboard"), wxDefaultPosition, wxSize( -1,-1 ), 0 ); + m_buttonCopyUrl->SetDefault(); + m_buttonCopyUrl->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), 70, 90, 90, false, wxEmptyString ) ); + + bSizer236->Add( m_buttonCopyUrl, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); + + + bSizer237->Add( bSizer236, 0, wxEXPAND, 5 ); + + m_textCtrlManualActivationUrl = new wxTextCtrl( m_panel351, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( 220,-1 ), wxTE_MULTILINE|wxTE_READONLY|wxWANTS_CHARS ); + m_textCtrlManualActivationUrl->SetMaxLength( 0 ); + bSizer237->Add( m_textCtrlManualActivationUrl, 0, wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); + + wxBoxSizer* bSizer235; + bSizer235 = new wxBoxSizer( wxHORIZONTAL ); + + m_staticText13611 = new wxStaticText( m_panel351, wxID_ANY, _("Enter activation key:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText13611->Wrap( -1 ); + bSizer235->Add( m_staticText13611, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT, 5 ); + + m_textCtrlOfflineActivationKey = new wxTextCtrl( m_panel351, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( 220,-1 ), wxTE_PROCESS_ENTER|wxWANTS_CHARS ); + m_textCtrlOfflineActivationKey->SetMaxLength( 0 ); + bSizer235->Add( m_textCtrlOfflineActivationKey, 1, wxTOP|wxBOTTOM|wxLEFT|wxALIGN_CENTER_VERTICAL, 5 ); + + m_buttonActivateOffline = new wxButton( m_panel351, wxID_ANY, _("Activate offline"), wxDefaultPosition, wxSize( -1,-1 ), 0 ); + m_buttonActivateOffline->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), 70, 90, 92, false, wxEmptyString ) ); + + bSizer235->Add( m_buttonActivateOffline, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); + + + bSizer237->Add( bSizer235, 0, wxEXPAND, 5 ); + + + bSizer266->Add( bSizer237, 0, wxALL|wxEXPAND, 5 ); + + + m_panel351->SetSizer( bSizer266 ); + m_panel351->Layout(); + bSizer266->Fit( m_panel351 ); + bSizer54->Add( m_panel351, 0, wxEXPAND, 5 ); + + m_staticline13 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + bSizer54->Add( m_staticline13, 0, wxEXPAND, 5 ); + + bSizerStdButtons = new wxBoxSizer( wxHORIZONTAL ); + + m_buttonCancel = new wxButton( this, wxID_CANCEL, _("Cancel"), wxDefaultPosition, wxSize( -1,-1 ), 0 ); + bSizerStdButtons->Add( m_buttonCancel, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxRIGHT, 5 ); + + + bSizer54->Add( bSizerStdButtons, 0, wxALIGN_RIGHT, 5 ); + + + this->SetSizer( bSizer54 ); + this->Layout(); + bSizer54->Fit( this ); + + this->Centre( wxBOTH ); + + // Connect Events + this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( ActivationDlgGenerated::OnClose ) ); + m_buttonActivateOnline->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ActivationDlgGenerated::OnActivateOnline ), NULL, this ); + m_buttonCopyUrl->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ActivationDlgGenerated::OnCopyUrl ), NULL, this ); + m_textCtrlOfflineActivationKey->Connect( wxEVT_COMMAND_TEXT_ENTER, wxCommandEventHandler( ActivationDlgGenerated::OnOfflineActivationEnter ), NULL, this ); + m_buttonActivateOffline->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ActivationDlgGenerated::OnActivateOffline ), NULL, this ); + m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ActivationDlgGenerated::OnCancel ), NULL, this ); +} + +ActivationDlgGenerated::~ActivationDlgGenerated() +{ +} diff --git a/FreeFileSync/Source/ui/gui_generated.h b/FreeFileSync/Source/ui/gui_generated.h index 9ab016bf..ac691409 100644 --- a/FreeFileSync/Source/ui/gui_generated.h +++ b/FreeFileSync/Source/ui/gui_generated.h @@ -52,6 +52,7 @@ namespace zen { class TripleSplitter; } #include <wx/animate.h> #include <wx/grid.h> #include <wx/calctrl.h> +#include <wx/gauge.h> #include "zen/i18n.h" @@ -82,9 +83,9 @@ protected: wxMenu* m_menuTools; wxMenuItem* m_menuItemOptions; wxMenu* m_menuLanguages; + wxMenuItem* m_menuItemFind; wxMenu* m_menuHelp; wxMenuItem* m_menuItemHelp; - wxMenu* m_menuCheckVersion; wxMenuItem* m_menuItemCheckVersionNow; wxMenuItem* m_menuItemCheckVersionAuto; wxMenuItem* m_menuItemAbout; @@ -373,9 +374,9 @@ protected: FolderHistoryBox* m_versioningFolderPath; wxButton* m_buttonSelectVersioningFolder; wxStaticText* m_staticText93; + wxChoice* m_choiceVersioningStyle; wxHyperlinkCtrl* m_hyperlink17; wxBoxSizer* bSizer192; - wxChoice* m_choiceVersioningStyle; wxStaticText* m_staticTextNamingCvtPart1; wxStaticText* m_staticTextNamingCvtPart2Bold; wxStaticText* m_staticTextNamingCvtPart3; @@ -431,8 +432,8 @@ protected: virtual void OnDeletionRecycler( wxCommandEvent& event ) { event.Skip(); } virtual void OnDeletionPermanent( wxCommandEvent& event ) { event.Skip(); } virtual void OnDeletionVersioning( wxCommandEvent& event ) { event.Skip(); } - virtual void OnHelpVersioning( wxHyperlinkEvent& event ) { event.Skip(); } virtual void OnChangeSyncOption( wxCommandEvent& event ) { event.Skip(); } + virtual void OnHelpVersioning( wxHyperlinkEvent& event ) { event.Skip(); } virtual void OnErrorPopup( wxCommandEvent& event ) { event.Skip(); } virtual void OnErrorIgnore( wxCommandEvent& event ) { event.Skip(); } virtual void OnOkay( wxCommandEvent& event ) { event.Skip(); } @@ -928,9 +929,9 @@ public: }; /////////////////////////////////////////////////////////////////////////////// -/// Class TooltipDialogGenerated +/// Class TooltipDlgGenerated /////////////////////////////////////////////////////////////////////////////// -class TooltipDialogGenerated : public wxDialog +class TooltipDlgGenerated : public wxDialog { private: @@ -940,8 +941,8 @@ public: wxStaticBitmap* m_bitmapLeft; wxStaticText* m_staticTextMain; - TooltipDialogGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxEmptyString, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE ); - ~TooltipDialogGenerated(); + TooltipDlgGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxEmptyString, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE ); + ~TooltipDlgGenerated(); }; @@ -998,10 +999,15 @@ protected: wxHyperlinkCtrl* m_hyperlink10; wxHyperlinkCtrl* m_hyperlink18; wxHyperlinkCtrl* m_hyperlink9; + wxPanel* m_panelThankYou; + wxPanel* m_panel391; + wxStaticBitmap* m_bitmapThanks; + wxStaticText* m_staticTextThanks; + wxButton* m_buttonShowDonationDetails; wxPanel* m_panelDonate; wxPanel* m_panel39; wxStaticBitmap* m_bitmapDonate; - wxStaticText* m_staticText83; + wxStaticText* m_staticTextDonate; wxButton* m_buttonDonate; wxStaticText* m_staticText94; wxStaticBitmap* m_bitmapHomepage; @@ -1022,6 +1028,7 @@ protected: // Virtual event handlers, overide them in your derived class virtual void OnClose( wxCloseEvent& event ) { event.Skip(); } + virtual void OnShowDonationDetails( wxCommandEvent& event ) { event.Skip(); } virtual void OnDonate( wxCommandEvent& event ) { event.Skip(); } virtual void OnOK( wxCommandEvent& event ) { event.Skip(); } @@ -1033,4 +1040,77 @@ public: }; +/////////////////////////////////////////////////////////////////////////////// +/// Class DownloadProgressDlgGenerated +/////////////////////////////////////////////////////////////////////////////// +class DownloadProgressDlgGenerated : public wxDialog +{ +private: + +protected: + wxStaticBitmap* m_bitmapDownloading; + wxStaticText* m_staticTextHeader; + wxGauge* m_gaugeProgress; + wxStaticText* m_staticTextDetails; + wxStaticLine* m_staticline9; + wxBoxSizer* bSizerStdButtons; + wxButton* m_buttonCancel; + + // Virtual event handlers, overide them in your derived class + virtual void OnCancel( wxCommandEvent& event ) { event.Skip(); } + + +public: + + DownloadProgressDlgGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxEmptyString, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = 0 ); + ~DownloadProgressDlgGenerated(); + +}; + +/////////////////////////////////////////////////////////////////////////////// +/// Class ActivationDlgGenerated +/////////////////////////////////////////////////////////////////////////////// +class ActivationDlgGenerated : public wxDialog +{ +private: + +protected: + wxPanel* m_panel35; + wxStaticBitmap* m_bitmapActivation; + wxTextCtrl* m_textCtrlLastError; + wxStaticText* m_staticTextMain; + wxStaticLine* m_staticline181; + wxStaticLine* m_staticline18111; + wxPanel* m_panel3511; + wxStaticText* m_staticText136; + wxButton* m_buttonActivateOnline; + wxStaticLine* m_staticline181111; + wxStaticLine* m_staticline181112; + wxPanel* m_panel351; + wxStaticText* m_staticText1361; + wxButton* m_buttonCopyUrl; + wxTextCtrl* m_textCtrlManualActivationUrl; + wxStaticText* m_staticText13611; + wxTextCtrl* m_textCtrlOfflineActivationKey; + wxButton* m_buttonActivateOffline; + wxStaticLine* m_staticline13; + wxBoxSizer* bSizerStdButtons; + wxButton* m_buttonCancel; + + // Virtual event handlers, overide them in your derived class + virtual void OnClose( wxCloseEvent& event ) { event.Skip(); } + virtual void OnActivateOnline( wxCommandEvent& event ) { event.Skip(); } + virtual void OnCopyUrl( wxCommandEvent& event ) { event.Skip(); } + virtual void OnOfflineActivationEnter( wxCommandEvent& event ) { event.Skip(); } + virtual void OnActivateOffline( wxCommandEvent& event ) { event.Skip(); } + virtual void OnCancel( wxCommandEvent& event ) { event.Skip(); } + + +public: + + ActivationDlgGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("FreeFileSync Donation Edition"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER ); + ~ActivationDlgGenerated(); + +}; + #endif //__GUI_GENERATED_H__ diff --git a/FreeFileSync/Source/ui/gui_status_handler.cpp b/FreeFileSync/Source/ui/gui_status_handler.cpp index b7427321..be40b2bf 100644 --- a/FreeFileSync/Source/ui/gui_status_handler.cpp +++ b/FreeFileSync/Source/ui/gui_status_handler.cpp @@ -362,7 +362,7 @@ StatusHandlerFloatingDialog::~StatusHandlerFloatingDialog() while (progressDlg) { wxTheApp->Yield(); //*first* refresh GUI (removing flicker) before sleeping! - std::this_thread::sleep_for(std::chrono::milliseconds(UI_UPDATE_INTERVAL)); + std::this_thread::sleep_for(std::chrono::milliseconds(UI_UPDATE_INTERVAL_MS)); } } } @@ -403,12 +403,12 @@ ProcessCallback::Response StatusHandlerFloatingDialog::reportError(const std::ws 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 + const int iterations = static_cast<int>(1000 * automaticRetryDelay_ / UI_UPDATE_INTERVAL_MS); //always round down: don't allow for negative remaining time below for (int i = 0; i < iterations; ++i) { reportStatus(_("Error") + L": " + _P("Automatic retry in 1 second...", "Automatic retry in %x seconds...", - (1000 * automaticRetryDelay_ - i * UI_UPDATE_INTERVAL + 999) / 1000)); //integer round up - std::this_thread::sleep_for(std::chrono::milliseconds(UI_UPDATE_INTERVAL)); + (1000 * automaticRetryDelay_ - i * UI_UPDATE_INTERVAL_MS + 999) / 1000)); //integer round up + std::this_thread::sleep_for(std::chrono::milliseconds(UI_UPDATE_INTERVAL_MS)); } return ProcessCallback::RETRY; } diff --git a/FreeFileSync/Source/ui/main_dlg.cpp b/FreeFileSync/Source/ui/main_dlg.cpp index a4aaf819..4d56a4f7 100644 --- a/FreeFileSync/Source/ui/main_dlg.cpp +++ b/FreeFileSync/Source/ui/main_dlg.cpp @@ -346,7 +346,7 @@ xmlAccess::XmlGlobalSettings loadGlobalConfig(const Zstring& globalConfigFile) / Zstring MainDialog::getLastRunConfigPath() { - return zen::getConfigDir() + Zstr("LastRun.ffs_gui"); + return zen::getConfigDirPathPf() + Zstr("LastRun.ffs_gui"); } @@ -463,9 +463,7 @@ MainDialog::MainDialog(const Zstring& globalConfigFile, bool startComparison) : MainDialogGenerated(nullptr), globalConfigFile_(globalConfigFile), - lastRunConfigPath(getLastRunConfigPath()), - folderHistoryLeft (std::make_shared<FolderHistory>()), //make sure it is always bound - folderHistoryRight(std::make_shared<FolderHistory>()) // + lastRunConfigPath(getLastRunConfigPath()) { m_folderPathLeft ->init(folderHistoryLeft); m_folderPathRight->init(folderHistoryRight); @@ -652,21 +650,11 @@ MainDialog::MainDialog(const Zstring& globalConfigFile, m_menuItemSynchronize ->SetBitmap(getResourceImage(L"sync_small")); m_menuItemOptions ->SetBitmap(getResourceImage(L"settings_small")); + m_menuItemFind ->SetBitmap(getResourceImage(L"find_small")); m_menuItemHelp ->SetBitmap(getResourceImage(L"help_small")); m_menuItemAbout->SetBitmap(getResourceImage(L"about_small")); - - if (!manualProgramUpdateRequired()) - { - m_menuItemCheckVersionNow ->Enable(false); - m_menuItemCheckVersionAuto->Enable(false); - - //wxFormbuilder doesn't give us a wxMenuItem for m_menuCheckVersion, so we need this abomination: - wxMenuItemList& items = m_menuHelp->GetMenuItems(); - for (auto it = items.begin(); it != items.end(); ++it) - if ((*it)->GetSubMenu() == m_menuCheckVersion) - (*it)->Enable(false); - } + m_menuItemCheckVersionNow->SetBitmap(getResourceImage(L"update_check_small")); //create language selection menu for (const TranslationInfo& ti : getExistingTranslations()) @@ -686,8 +674,8 @@ MainDialog::MainDialog(const Zstring& globalConfigFile, if (!globalSettings.gui.lastOnlineVersion.empty() && haveNewerVersionOnline(globalSettings.gui.lastOnlineVersion)) { auto menu = new wxMenu(); - wxMenuItem* newItem = new wxMenuItem(menu, wxID_ANY, _("&Download")); - this->Connect(newItem->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnMenuDownloadNewVersion)); + wxMenuItem* newItem = new wxMenuItem(menu, wxID_ANY, _("&Show details")); + this->Connect(newItem->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnMenuUpdateAvailable)); menu->Append(newItem); //pass ownership m_menubar1->Append(menu, L"\u21D2 " + _("A new version of FreeFileSync is available:") + L" \u2605 " + globalSettings.gui.lastOnlineVersion + L" \u2605"); } @@ -1619,7 +1607,7 @@ void MainDialog::flashStatusInformation(const wxString& text) oldStatusMsgs.push_back(m_staticTextStatusCenter->GetLabel()); m_staticTextStatusCenter->SetLabel(text); - m_staticTextStatusCenter->SetForegroundColour(wxColor(31, 57, 226)); //highlight color: blue + m_staticTextStatusCenter->SetForegroundColour(wxColor(31, 57, 226)); //highlight_ color: blue m_staticTextStatusCenter->SetFont(m_staticTextStatusCenter->GetFont().Bold()); m_panelStatusBar->Layout(); @@ -2494,18 +2482,32 @@ void MainDialog::onGridLabelContextRim(Grid& grid, ColumnTypeRim type, bool left { auto colAttr = grid.getColumnConfig(); + Grid::ColumnAttribute* caItemPath = nullptr; + Grid::ColumnAttribute* caToggle = nullptr; + for (Grid::ColumnAttribute& ca : colAttr) - if (ca.type_ == ct) - { - ca.visible_ = !ca.visible_; - grid.setColumnConfig(colAttr); - return; - } + if (ca.type_ == static_cast<ColumnType>(ColumnTypeRim::ITEM_PATH)) + caItemPath = &ca; + else if (ca.type_ == ct) + caToggle = &ca; + + assert(caItemPath && caItemPath->stretch_ > 0 && caItemPath->visible_); + assert(caToggle && caToggle->stretch_ == 0); + + if (caItemPath && caToggle) + { + caToggle->visible_ = !caToggle->visible_; + + //take width of newly visible column from stretched item path column + caItemPath->offset_ -= caToggle->visible_ ? caToggle->offset_ : -caToggle->offset_; + + grid.setColumnConfig(colAttr); + } }; if (const GridData* prov = grid.getDataProvider()) for (const Grid::ColumnAttribute& ca : grid.getColumnConfig()) - menu.addCheckBox(prov->getColumnLabel(ca.type_), [ca, toggleColumn] { toggleColumn(ca.type_); }, + menu.addCheckBox(prov->getColumnLabel(ca.type_), [ct = ca.type_, toggleColumn] { toggleColumn(ct); }, ca.visible_, ca.type_ != static_cast<ColumnType>(ColumnTypeRim::ITEM_PATH)); //do not allow user to hide this column! //---------------------------------------------------------------------------------------------- menu.addSeparator(); @@ -3785,7 +3787,7 @@ void MainDialog::OnCompare(wxCommandEvent& event) //play (optional) sound notification if (!globalCfg.soundFileCompareFinished.empty()) { - const Zstring soundFile = getResourceDir() + globalCfg.soundFileCompareFinished; + const Zstring soundFile = getResourceDirPf() + 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! } @@ -4890,13 +4892,13 @@ void MainDialog::OnMenuExportFileList(wxCommandEvent& event) void MainDialog::OnMenuCheckVersion(wxCommandEvent& event) { - zen::checkForUpdateNow(this, globalCfg.gui.lastOnlineVersion, globalCfg.gui.lastOnlineChangeLog); + zen::checkForUpdateNow(this, globalCfg.gui.lastOnlineVersion); } -void MainDialog::OnMenuDownloadNewVersion(wxCommandEvent& event) +void MainDialog::OnMenuUpdateAvailable(wxCommandEvent& event) { - wxLaunchDefaultBrowser(L"http://www.freefilesync.org/get_latest.php"); + zen::checkForUpdateNow(this, globalCfg.gui.lastOnlineVersion); //show changelog + handle Donation Edition auto-updater (including expiration) } @@ -4913,9 +4915,8 @@ void MainDialog::OnMenuCheckVersionAutomatically(wxCommandEvent& event) { flashStatusInformation(_("Searching for program updates...")); //synchronous update check is sufficient here: - periodicUpdateCheckEval(this, globalCfg.gui.lastUpdateCheck, - globalCfg.gui.lastOnlineVersion, - globalCfg.gui.lastOnlineChangeLog, periodicUpdateCheckRunAsync(periodicUpdateCheckPrepare().get()).get()); + periodicUpdateCheckEval(this, globalCfg.gui.lastUpdateCheck, globalCfg.gui.lastOnlineVersion, + periodicUpdateCheckRunAsync(periodicUpdateCheckPrepare().get()).get()); } } @@ -4925,21 +4926,19 @@ void MainDialog::OnRegularUpdateCheck(wxIdleEvent& event) //execute just once per startup! Disconnect(wxEVT_IDLE, wxIdleEventHandler(MainDialog::OnRegularUpdateCheck), nullptr, this); - if (manualProgramUpdateRequired()) - if (shouldRunPeriodicUpdateCheck(globalCfg.gui.lastUpdateCheck)) - { - flashStatusInformation(_("Searching for program updates...")); + if (shouldRunPeriodicUpdateCheck(globalCfg.gui.lastUpdateCheck)) + { + flashStatusInformation(_("Searching for program updates...")); - std::shared_ptr<UpdateCheckResultPrep> resultPrep = periodicUpdateCheckPrepare(); //run on main thread: + 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<UpdateCheckResult>&& resultAsync) - { - periodicUpdateCheckEval(this, globalCfg.gui.lastUpdateCheck, - globalCfg.gui.lastOnlineVersion, - globalCfg.gui.lastOnlineChangeLog, resultAsync.get()); //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<UpdateCheckResult>&& resultAsync) + { + 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 1bd621c2..0aace163 100644 --- a/FreeFileSync/Source/ui/main_dlg.h +++ b/FreeFileSync/Source/ui/main_dlg.h @@ -257,16 +257,16 @@ private: void OnSearchPanelKeyPressed(wxKeyEvent& event); //menu events - void OnMenuOptions (wxCommandEvent& event) override; - void OnMenuExportFileList(wxCommandEvent& event) override; - void OnMenuResetLayout (wxCommandEvent& event) override { resetLayout(); } - void OnMenuFindItem (wxCommandEvent& event) override; - void OnMenuCheckVersion (wxCommandEvent& event) override; + void OnMenuOptions (wxCommandEvent& event) override; + void OnMenuExportFileList (wxCommandEvent& event) override; + void OnMenuResetLayout (wxCommandEvent& event) override { resetLayout(); } + void OnMenuFindItem (wxCommandEvent& event) override; + void OnMenuCheckVersion (wxCommandEvent& event) override; void OnMenuCheckVersionAutomatically(wxCommandEvent& event) override; - void OnMenuDownloadNewVersion (wxCommandEvent& event); - void OnMenuAbout (wxCommandEvent& event) override; - void OnShowHelp (wxCommandEvent& event) override; - void OnMenuQuit (wxCommandEvent& event) override { Close(); } + void OnMenuUpdateAvailable(wxCommandEvent& event); + void OnMenuAbout (wxCommandEvent& event) override; + void OnShowHelp (wxCommandEvent& event) override; + void OnMenuQuit (wxCommandEvent& event) override { Close(); } void OnMenuLanguageSwitch(wxCommandEvent& event); @@ -329,8 +329,8 @@ private: std::int64_t manualTimeSpanFrom = 0; std::int64_t manualTimeSpanTo = 0; //buffer manual time span selection at session level - std::shared_ptr<FolderHistory> folderHistoryLeft; //shared by all wxComboBox dropdown controls - std::shared_ptr<FolderHistory> folderHistoryRight; //always bound! + std::shared_ptr<FolderHistory> folderHistoryLeft = std::make_shared<FolderHistory>(); //shared by all wxComboBox dropdown controls + std::shared_ptr<FolderHistory> folderHistoryRight = std::make_shared<FolderHistory>(); //always bound! zen::AsyncGuiQueue guiQueue; //schedule and run long-running tasks asynchronously, but process results on GUI queue diff --git a/FreeFileSync/Source/ui/on_completion_box.cpp b/FreeFileSync/Source/ui/on_completion_box.cpp index 898178dc..c38909a3 100644 --- a/FreeFileSync/Source/ui/on_completion_box.cpp +++ b/FreeFileSync/Source/ui/on_completion_box.cpp @@ -10,9 +10,9 @@ #include <algorithm> #include <zen/stl_tools.h> #include <zen/utf.h> -#ifdef ZEN_WIN - #include <zen/win_ver.h> -#endif +//#ifdef ZEN_WIN +// #include <zen/win_ver.h> +//#endif using namespace zen; @@ -31,19 +31,16 @@ std::vector<std::pair<std::wstring, Zstring>> getDefaultCommands() //(gui name/c auto addEntry = [&](const std::wstring& name, const Zstring& value) { output.emplace_back(name, value); }; #ifdef ZEN_WIN - if (zen::vistaOrLater()) - { - addEntry(_("Log off" ), Zstr("shutdown /l")); - addEntry(_("Standby" ), Zstr("rundll32.exe powrprof.dll,SetSuspendState Sleep")); //suspend/Suspend to RAM/sleep - addEntry(_("Shut down"), Zstr("shutdown /s /t 60")); - } - else //XP - { - addEntry(_("Log off" ), Zstr("shutdown -l")); - addEntry(_("Standby" ), Zstr("rundll32.exe powrprof.dll,SetSuspendState")); //this triggers standby OR hibernate, depending on whether hibernate setting is active! - addEntry(_("Shut down"), Zstr("shutdown -s -t 60")); - //no suspend on XP? - } +#ifdef ZEN_WIN_VISTA_AND_LATER + addEntry(_("Log off" ), Zstr("shutdown /l")); + addEntry(_("Standby" ), Zstr("rundll32.exe powrprof.dll,SetSuspendState Sleep")); //suspend/Suspend to RAM/sleep + addEntry(_("Shut down"), Zstr("shutdown /s /t 60")); +#else //XP + addEntry(_("Log off" ), Zstr("shutdown -l")); + addEntry(_("Standby" ), Zstr("rundll32.exe powrprof.dll,SetSuspendState")); //this triggers standby OR hibernate, depending on whether hibernate setting is active! + addEntry(_("Shut down"), Zstr("shutdown -s -t 60")); + //no suspend on XP? +#endif #elif defined ZEN_LINUX addEntry(_("Log off" ), Zstr("gnome-session-quit --no-prompt")); //alternative requiring admin: sudo killall Xorg diff --git a/FreeFileSync/Source/ui/progress_indicator.cpp b/FreeFileSync/Source/ui/progress_indicator.cpp index c5cc1f82..93e721ea 100644 --- a/FreeFileSync/Source/ui/progress_indicator.cpp +++ b/FreeFileSync/Source/ui/progress_indicator.cpp @@ -68,6 +68,7 @@ inline wxColor getColorItemsBackgroundRim() { return { 53, 25, 255 }; } //dark //don't use wxStopWatch for long-running measurements: internally it uses ::QueryPerformanceCounter() which can overflow after only a few days: +// std::chrono::system_clock is not a steady clock, but at least doesn't overflow! //http://www.freefilesync.org/forum/viewtopic.php?t=1426 class StopWatch @@ -75,41 +76,42 @@ class StopWatch public: void pause() { - if (!paused) + if (!paused_) { - paused = true; - elapsedUntilPause += numeric::dist(startTime, wxGetUTCTimeMillis().GetValue()); + paused_ = true; + elapsedUntilPause_ += std::chrono::system_clock::now() - startTime_; } } void resume() { - if (paused) + if (paused_) { - paused = false; - startTime = wxGetUTCTimeMillis().GetValue(); + paused_ = false; + startTime_ = std::chrono::system_clock::now(); } } void restart() { - startTime = wxGetUTCTimeMillis().GetValue(); //uses ::GetSystemTimeAsFileTime() - paused = false; - elapsedUntilPause = 0; + paused_ = false; + startTime_ = std::chrono::system_clock::now(); + elapsedUntilPause_ = std::chrono::nanoseconds::zero(); } int64_t timeMs() const { - int64_t msTotal = elapsedUntilPause; - if (!paused) - msTotal += numeric::dist(startTime, wxGetUTCTimeMillis().GetValue()); - return msTotal; + auto elapsedTotal = elapsedUntilPause_; + if (!paused_) + elapsedTotal += std::chrono::system_clock::now() - startTime_; + + return std::chrono::duration_cast<std::chrono::milliseconds>(elapsedTotal).count(); } private: - wxLongLong_t startTime = wxGetUTCTimeMillis().GetValue(); //alas not a steady clock, but something's got to give! - bool paused = false; - int64_t elapsedUntilPause = 0; + bool paused_ = false; + std::chrono::system_clock::time_point startTime_ = std::chrono::system_clock::now(); //uses ::GetSystemTimePreciseAsFileTime() + std::chrono::nanoseconds elapsedUntilPause_{}; //std::chrono::duration is uninitialized by default! WTF! When will this stupidity end??? }; @@ -1020,7 +1022,7 @@ public: lastSample = std::make_pair(timeNowMs, value); - //allow for at most one sample per 100ms (handles duplicate inserts, too!) => this is unrelated to UI_UPDATE_INTERVAL! + //allow for at most one sample per 100ms (handles duplicate inserts, too!) => this is unrelated to UI_UPDATE_INTERVAL_MS! if (!samples.empty()) //always unconditionally insert first sample! if (timeNowMs / 100 == samples.rbegin()->first / 100) return; @@ -1765,7 +1767,7 @@ void SyncProgressDialogImpl<TopLevelDialog>::updateGuiInt(bool allowYield) { wxTheApp->Yield(); //receive UI message that end pause OR forceful termination! //*first* refresh GUI (removing flicker) before sleeping! - std::this_thread::sleep_for(std::chrono::milliseconds(UI_UPDATE_INTERVAL)); + std::this_thread::sleep_for(std::chrono::milliseconds(UI_UPDATE_INTERVAL_MS)); } //after SyncProgressDialogImpl::OnClose() called wxWindow::Destroy() on OS X this instance is instantly toast! if (wereDead) @@ -2047,7 +2049,7 @@ void SyncProgressDialogImpl<TopLevelDialog>::processHasFinished(SyncResult resul case SyncProgressDialog::RESULT_FINISHED_WITH_SUCCESS: if (!soundFileSyncComplete_.empty()) { - const Zstring soundFile = getResourceDir() + soundFileSyncComplete_; + const Zstring soundFile = getResourceDirPf() + soundFileSyncComplete_; 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! } diff --git a/FreeFileSync/Source/ui/small_dlgs.cpp b/FreeFileSync/Source/ui/small_dlgs.cpp index c59bf681..fdc28196 100644 --- a/FreeFileSync/Source/ui/small_dlgs.cpp +++ b/FreeFileSync/Source/ui/small_dlgs.cpp @@ -11,6 +11,7 @@ #include <zen/stl_tools.h> #include <wx/wupdlock.h> #include <wx/filedlg.h> +#include <wx/clipbrd.h> #include <wx+/choice_enum.h> #include <wx+/bitmap_button.h> #include <wx+/rtl.h> @@ -23,11 +24,13 @@ #include "gui_generated.h" #include "custom_grid.h" #include "folder_selector.h" +#include "version_check.h" #include "../algorithm.h" #include "../synchronization.h" #include "../lib/help_provider.h" #include "../lib/hard_filter.h" #include "../version/version.h" +#include "../lib/status_handler.h" //updateUiIsAllowed() #ifdef ZEN_WIN #include <wx+/mouse_move_dlg.h> @@ -51,6 +54,9 @@ private: void OnOK (wxCommandEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_OKAY); } void OnClose (wxCloseEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } void OnDonate(wxCommandEvent& event) override { wxLaunchDefaultBrowser(L"http://www.freefilesync.org/donate.php"); } +#ifdef ZEN_WIN + void OnShowDonationDetails(wxCommandEvent& event) override { openDonationEditionThankYouPage(this); } +#endif }; @@ -58,14 +64,43 @@ AboutDlg::AboutDlg(wxWindow* parent) : AboutDlgGenerated(parent) { setStandardButtonLayout(*bSizerStdButtons, StdButtons().setAffirmative(m_buttonClose)); - setRelativeFontSize(*m_buttonDonate, 1.25); - assert(m_buttonClose->GetId() == wxID_OK); //we cannot use wxID_CLOSE else Esc key won't work: yet another wxWidgets bug?? m_bitmapHomepage->SetBitmap(getResourceImage(L"website")); m_bitmapEmail ->SetBitmap(getResourceImage(L"email")); m_bitmapGpl ->SetBitmap(getResourceImage(L"gpl")); - m_bitmapDonate ->SetBitmap(getResourceImage(L"paypal")); + +#ifdef ZEN_WIN + std::wstring donorFullName; + try + { + if (Opt<DonationInfo> donationInfo = getDonationInfo()) //throw FileError + donorFullName = donationInfo->donorFullName; + } + catch (const FileError& e) + { + showNotificationDialog(parent, DialogInfoType::ERROR2, PopupDialogCfg(). + setMainInstructions(_("Installation files are corrupted. Please reinstall FreeFileSync.")). + setDetailInstructions(e.toString())); + } + + if (!donorFullName.empty()) + { + m_panelDonate->Hide(); + m_bitmapThanks->SetBitmap(getResourceImage(L"freefilesync-heart")); + m_staticTextThanks->SetLabel(replaceCpy(_("Thank you, %x, for your donation and support!"), L"%x", donorFullName)); + setRelativeFontSize(*m_staticTextThanks, 1.25); + setRelativeFontSize(*m_buttonShowDonationDetails, 1.25); + m_staticTextThanks->Wrap(260); //*after* changing font size + } + else +#endif + { + m_panelThankYou->Hide(); + m_bitmapDonate->SetBitmap(getResourceImage(L"paypal")); + setRelativeFontSize(*m_staticTextDonate, 1.25); + setRelativeFontSize(*m_buttonDonate, 1.25); + } //m_animCtrlWink->SetAnimation(getResourceAnimation(L"wink")); //m_animCtrlWink->Play(); @@ -545,12 +580,12 @@ private: void updateGui(); - const std::vector<const FileSystemObject*>& rowsToDeleteOnLeft; - const std::vector<const FileSystemObject*>& rowsToDeleteOnRight; - const std::chrono::steady_clock::time_point dlgStartTime = std::chrono::steady_clock::now(); + const std::vector<const FileSystemObject*>& rowsToDeleteOnLeft_; + const std::vector<const FileSystemObject*>& rowsToDeleteOnRight_; + const std::chrono::steady_clock::time_point dlgStartTime_ = std::chrono::steady_clock::now(); //output-only parameters: - bool& useRecycleBinOut; + bool& useRecycleBinOut_; }; @@ -559,9 +594,9 @@ DeleteDialog::DeleteDialog(wxWindow* parent, const std::vector<const FileSystemObject*>& rowsOnRight, bool& useRecycleBin) : DeleteDlgGenerated(parent), - rowsToDeleteOnLeft(rowsOnLeft), - rowsToDeleteOnRight(rowsOnRight), - useRecycleBinOut(useRecycleBin) + rowsToDeleteOnLeft_(rowsOnLeft), + rowsToDeleteOnRight_(rowsOnRight), + useRecycleBinOut_(useRecycleBin) { #ifdef ZEN_WIN new zen::MouseMoveWindow(*this); //allow moving main dialog by clicking (nearly) anywhere...; ownership passed to "this" @@ -593,8 +628,8 @@ void DeleteDialog::updateGui() wxWindowUpdateLocker dummy(this); //leads to GUI corruption problems on Linux/OS X! #endif - const std::pair<std::wstring, int> delInfo = zen::getSelectedItemsAsString(rowsToDeleteOnLeft, - rowsToDeleteOnRight); + const std::pair<std::wstring, int> delInfo = zen::getSelectedItemsAsString(rowsToDeleteOnLeft_, + rowsToDeleteOnRight_); wxString header; if (m_checkBoxUseRecycler->GetValue()) { @@ -630,10 +665,10 @@ void DeleteDialog::updateGui() void DeleteDialog::OnOK(wxCommandEvent& event) { //additional safety net, similar to Windows Explorer: time delta between DEL and ENTER must be at least 50ms to avoid accidental deletion! - if (std::chrono::steady_clock::now() < dlgStartTime + std::chrono::milliseconds(50)) + if (std::chrono::steady_clock::now() < dlgStartTime_ + std::chrono::milliseconds(50)) //considers chrono-wrap-around! return; - useRecycleBinOut = m_checkBoxUseRecycler->GetValue(); + useRecycleBinOut_ = m_checkBoxUseRecycler->GetValue(); EndModal(ReturnSmallDlg::BUTTON_OKAY); } @@ -1114,3 +1149,160 @@ ReturnSmallDlg::ButtonPressed zen::showSelectTimespanDlg(wxWindow* parent, std:: SelectTimespanDlg timeSpanDlg(parent, timeFrom, timeTo); return static_cast<ReturnSmallDlg::ButtonPressed>(timeSpanDlg.ShowModal()); } + +//######################################################################################## + +class ActivationDlg : public ActivationDlgGenerated +{ +public: + ActivationDlg(wxWindow* parent, const std::wstring& lastErrorMsg, const std::wstring& manualActivationUrl, std::wstring& manualActivationKey); + +private: + void OnActivateOnline (wxCommandEvent& event) override; + void OnActivateOffline(wxCommandEvent& event) override; + void OnOfflineActivationEnter(wxCommandEvent& event) override { OnActivateOffline(event); } + void OnCopyUrl (wxCommandEvent& event) override; + void OnCancel(wxCommandEvent& event) override { EndModal(static_cast<int>(ReturnActivationDlg::CANCEL)); } + void OnClose (wxCloseEvent& event) override { EndModal(static_cast<int>(ReturnActivationDlg::CANCEL)); } + + std::wstring& manualActivationKeyOut_; //in/out parameter +}; + + +ActivationDlg::ActivationDlg(wxWindow* parent, + const std::wstring& lastErrorMsg, + const std::wstring& manualActivationUrl, + std::wstring& manualActivationKey) : + ActivationDlgGenerated(parent), + manualActivationKeyOut_(manualActivationKey) +{ +#ifdef ZEN_WIN + new zen::MouseMoveWindow(*this); //allow moving main dialog by clicking (nearly) anywhere...; ownership passed to "this" +#endif + setStandardButtonLayout(*bSizerStdButtons, StdButtons().setCancel(m_buttonCancel)); + + //setMainInstructionFont(*m_staticTextMain); + + m_bitmapActivation->SetBitmap(getResourceImage(L"website")); + + m_textCtrlLastError ->ChangeValue(lastErrorMsg); + m_textCtrlManualActivationUrl ->ChangeValue(manualActivationUrl); + m_textCtrlOfflineActivationKey->ChangeValue(manualActivationKey); + + GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize() + //=> works like a charm for GTK2 with window resizing problems and title bar corruption; e.g. Debian!!! + Center(); //needs to be re-applied after a dialog size change! + + m_buttonActivateOnline->SetFocus(); +} + + +void ActivationDlg::OnCopyUrl(wxCommandEvent& event) +{ + if (wxClipboard::Get()->Open()) + { + ZEN_ON_SCOPE_EXIT(wxClipboard::Get()->Close()); + wxClipboard::Get()->SetData(new wxTextDataObject(m_textCtrlManualActivationUrl->GetValue())); //ownership passed + + m_textCtrlManualActivationUrl->SetFocus(); //[!] otherwise selection is lost + m_textCtrlManualActivationUrl->SelectAll(); //some visual feedback + } +} + + +void ActivationDlg::OnActivateOnline(wxCommandEvent& event) +{ + manualActivationKeyOut_ = m_textCtrlOfflineActivationKey->GetValue(); + EndModal(static_cast<int>(ReturnActivationDlg::ACTIVATE_ONLINE)); +} + + +void ActivationDlg::OnActivateOffline(wxCommandEvent& event) +{ + manualActivationKeyOut_ = m_textCtrlOfflineActivationKey->GetValue(); + EndModal(static_cast<int>(ReturnActivationDlg::ACTIVATE_OFFLINE)); +} + + +ReturnActivationDlg zen::showActivationDialog(wxWindow* parent, const std::wstring& lastErrorMsg, const std::wstring& manualActivationUrl, std::wstring& manualActivationKey) +{ + ActivationDlg dlg(parent, lastErrorMsg, manualActivationUrl, manualActivationKey); + return static_cast<ReturnActivationDlg>(dlg.ShowModal()); +} + +//######################################################################################## + +class DownloadProgressWindow::Impl : public DownloadProgressDlgGenerated +{ +public: + Impl(wxWindow* parent, const Zstring& fileName, std::uint64_t fileSize); + + void notifyProgress(std::uint64_t delta) { bytesCurrent_ += delta; } + + void requestUiRefresh() //throw CancelPressed + { + if (cancelled_) + throw CancelPressed(); + + if (updateUiIsAllowed()) + { + updateGui(); + //wxTheApp->Yield(); + ::wxSafeYield(this); //disables user input except for "this" (using wxWindowDisabler instead would move the FFS main dialog into the background: why?) + } + } + +private: + void OnCancel(wxCommandEvent& event) override { cancelled_ = true; } + + void updateGui() + { + const double fraction = bytesTotal_ == 0 ? 0 : 1.0 * bytesCurrent_ / bytesTotal_; + m_staticTextHeader->SetLabel(_("Downloading update...") + L" " + + numberTo<std::wstring>(numeric::round(fraction * 100)) + L"% (" + filesizeToShortString(bytesCurrent_) + L")"); + m_gaugeProgress->SetValue(numeric::round(fraction * GAUGE_FULL_RANGE)); + } + + bool cancelled_ = false; + std::uint64_t bytesCurrent_ = 0; + const std::uint64_t bytesTotal_; + const int GAUGE_FULL_RANGE = 1000000; +}; + + +DownloadProgressWindow::Impl::Impl(wxWindow* parent, const Zstring& filePath, std::uint64_t fileSize) : + DownloadProgressDlgGenerated(parent), + bytesTotal_(fileSize) +{ +#ifdef ZEN_WIN + new zen::MouseMoveWindow(*this); //allow moving main dialog by clicking (nearly) anywhere...; ownership passed to "this" +#endif + + setStandardButtonLayout(*bSizerStdButtons, StdButtons().setCancel(m_buttonCancel)); + + setMainInstructionFont(*m_staticTextHeader); + + m_bitmapDownloading->SetBitmap(getResourceImage(L"website")); + + m_gaugeProgress->SetRange(GAUGE_FULL_RANGE); + + m_staticTextDetails->SetLabel(utfCvrtTo<std::wstring>(filePath)); + + updateGui(); + + GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize() + //=> works like a charm for GTK2 with window resizing problems and title bar corruption; e.g. Debian!!! + Center(); //needs to be re-applied after a dialog size change! + Show(); + + m_buttonCancel->SetFocus(); +} + + +zen::DownloadProgressWindow::DownloadProgressWindow(wxWindow* parent, const Zstring& filePath, std::uint64_t fileSize) : + pimpl_(new DownloadProgressWindow::Impl(parent, filePath, fileSize)) {} + +zen::DownloadProgressWindow::~DownloadProgressWindow() { pimpl_->Destroy(); } + +void zen::DownloadProgressWindow::notifyProgress(std::uint64_t delta) { pimpl_->notifyProgress(delta); } +void zen::DownloadProgressWindow::requestUiRefresh() { pimpl_->requestUiRefresh(); } //throw CancelPressed diff --git a/FreeFileSync/Source/ui/small_dlgs.h b/FreeFileSync/Source/ui/small_dlgs.h index 22a3012e..3eb23184 100644 --- a/FreeFileSync/Source/ui/small_dlgs.h +++ b/FreeFileSync/Source/ui/small_dlgs.h @@ -57,6 +57,30 @@ ReturnSmallDlg::ButtonPressed showSelectTimespanDlg(wxWindow* parent, std::int64 #if defined ZEN_WIN_VISTA_AND_LATER || defined ZEN_MAC ReturnSmallDlg::ButtonPressed showSftpSetupDialog(wxWindow* parent, Zstring& folderPathPhrase); #endif + +enum class ReturnActivationDlg +{ + CANCEL, + ACTIVATE_ONLINE, + ACTIVATE_OFFLINE, +}; +ReturnActivationDlg showActivationDialog(wxWindow* parent, const std::wstring& lastErrorMsg, const std::wstring& manualActivationUrl, std::wstring& manualActivationKey); + + +class DownloadProgressWindow //remporary progress info => life-time: stack +{ +public: + DownloadProgressWindow(wxWindow* parent, const Zstring& filePath, std::uint64_t fileSize); + ~DownloadProgressWindow(); + + struct CancelPressed {}; + void requestUiRefresh(); //throw CancelPressed + void notifyProgress(std::uint64_t delta); + +private: + class Impl; + Impl* const pimpl_; +}; } #endif //SMALL_DLGS_H_8321790875018750245 diff --git a/FreeFileSync/Source/ui/switch_to_gui.h b/FreeFileSync/Source/ui/switch_to_gui.h deleted file mode 100644 index 3ef5f6e0..00000000 --- a/FreeFileSync/Source/ui/switch_to_gui.h +++ /dev/null @@ -1,45 +0,0 @@ -// ***************************************************************************** -// * 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 freefilesync DOT org) - All Rights Reserved * -// ***************************************************************************** - -#ifndef SWITCH_TO_GUI_H_132047815734845 -#define SWITCH_TO_GUI_H_132047815734845 - -#include "../lib/process_xml.h" -#include "main_dlg.h" //in "application.cpp" we have this dependency anyway! - - -namespace zen -{ -//switch from FreeFileSync Batch to GUI modus: opens a new FreeFileSync GUI session asynchronously -class SwitchToGui -{ -public: - SwitchToGui(const Zstring& globalConfigFile, - xmlAccess::XmlGlobalSettings& globalSettings, - const Zstring& referenceFile, - const xmlAccess::XmlBatchConfig& batchCfg) : - globalConfigFile_(globalConfigFile), - globalSettings_(globalSettings), - guiCfg(xmlAccess::convertBatchToGui(batchCfg)) - { - referenceFiles.push_back(referenceFile); - } - - void execute() const - { - MainDialog::create(globalConfigFile_, &globalSettings_, guiCfg, referenceFiles, /*bool startComparison = */ true); //new toplevel window - } - -private: - const Zstring globalConfigFile_; - xmlAccess::XmlGlobalSettings& globalSettings_; - - std::vector<Zstring> referenceFiles; - const xmlAccess::XmlGuiConfig guiCfg; -}; -} - -#endif //SWITCH_TO_GUI_H_132047815734845 diff --git a/FreeFileSync/Source/ui/sync_cfg.cpp b/FreeFileSync/Source/ui/sync_cfg.cpp index 69b35137..9bac8df3 100644 --- a/FreeFileSync/Source/ui/sync_cfg.cpp +++ b/FreeFileSync/Source/ui/sync_cfg.cpp @@ -72,9 +72,9 @@ private: void OnHelpTimeShift (wxHyperlinkEvent& event) override { displayHelpEntry(L"daylight-saving-time", this); } void OnToggleLocalCompSettings(wxCommandEvent& event) override { updateCompGui(); updateSyncGui(); /*affects sync settings, too!*/ } - void OnCompByTimeSize (wxCommandEvent& event) override { localCmpVar = CompareVariant::TIME_SIZE; updateCompGui(); updateSyncGui(); } // - void OnCompByContent (wxCommandEvent& event) override { localCmpVar = CompareVariant::CONTENT; updateCompGui(); updateSyncGui(); } //affects sync settings, too! - void OnCompBySize (wxCommandEvent& event) override { localCmpVar = CompareVariant::SIZE; updateCompGui(); updateSyncGui(); } // + void OnCompByTimeSize (wxCommandEvent& event) override { localCmpVar_ = CompareVariant::TIME_SIZE; updateCompGui(); updateSyncGui(); } // + void OnCompByContent (wxCommandEvent& event) override { localCmpVar_ = CompareVariant::CONTENT; updateCompGui(); updateSyncGui(); } //affects sync settings, too! + void OnCompBySize (wxCommandEvent& event) override { localCmpVar_ = CompareVariant::SIZE; updateCompGui(); updateSyncGui(); } // void OnCompByTimeSizeDouble (wxMouseEvent& event) override; void OnCompBySizeDouble (wxMouseEvent& event) override; void OnCompByContentDouble (wxMouseEvent& event) override; @@ -86,7 +86,7 @@ private: void updateCompGui(); - CompareVariant localCmpVar = CompareVariant::TIME_SIZE; + CompareVariant localCmpVar_ = CompareVariant::TIME_SIZE; //------------- filter panel -------------------------- void OnHelpShowExamples(wxHyperlinkEvent& event) override { displayHelpEntry(L"exclude-items", this); } @@ -100,17 +100,17 @@ private: void updateFilterGui(); - EnumDescrList<UnitTime> enumTimeDescr; - EnumDescrList<UnitSize> enumSizeDescr; + EnumDescrList<UnitTime> enumTimeDescr_; + EnumDescrList<UnitSize> enumSizeDescr_; //------------- synchronization panel ----------------- - void OnSyncTwoWay(wxCommandEvent& event) override { directionCfg.var = DirectionConfig::TWO_WAY; updateSyncGui(); } - void OnSyncMirror(wxCommandEvent& event) override { directionCfg.var = DirectionConfig::MIRROR; updateSyncGui(); } - void OnSyncUpdate(wxCommandEvent& event) override { directionCfg.var = DirectionConfig::UPDATE; updateSyncGui(); } - void OnSyncCustom(wxCommandEvent& event) override { directionCfg.var = DirectionConfig::CUSTOM; updateSyncGui(); } + void OnSyncTwoWay(wxCommandEvent& event) override { directionCfg_.var = DirectionConfig::TWO_WAY; updateSyncGui(); } + void OnSyncMirror(wxCommandEvent& event) override { directionCfg_.var = DirectionConfig::MIRROR; updateSyncGui(); } + void OnSyncUpdate(wxCommandEvent& event) override { directionCfg_.var = DirectionConfig::UPDATE; updateSyncGui(); } + void OnSyncCustom(wxCommandEvent& event) override { directionCfg_.var = DirectionConfig::CUSTOM; updateSyncGui(); } void OnToggleLocalSyncSettings(wxCommandEvent& event) override { updateSyncGui(); } - void OnToggleDetectMovedFiles (wxCommandEvent& event) override { directionCfg.detectMovedFiles = !directionCfg.detectMovedFiles; updateSyncGui(); } //parameter NOT owned by checkbox! + void OnToggleDetectMovedFiles (wxCommandEvent& event) override { directionCfg_.detectMovedFiles = !directionCfg_.detectMovedFiles; updateSyncGui(); } //parameter NOT owned by checkbox! void OnChangeSyncOption (wxCommandEvent& event) override { updateSyncGui(); } void OnSyncTwoWayDouble(wxMouseEvent& event) override; @@ -125,11 +125,11 @@ private: void OnDifferent (wxCommandEvent& event) override; void OnConflict (wxCommandEvent& event) override; - void OnDeletionPermanent (wxCommandEvent& event) override { handleDeletion = DeletionPolicy::PERMANENT; updateSyncGui(); } - void OnDeletionRecycler (wxCommandEvent& event) override { handleDeletion = DeletionPolicy::RECYCLER; updateSyncGui(); } - void OnDeletionVersioning (wxCommandEvent& event) override { handleDeletion = DeletionPolicy::VERSIONING; updateSyncGui(); } + void OnDeletionPermanent (wxCommandEvent& event) override { handleDeletion_ = DeletionPolicy::PERMANENT; updateSyncGui(); } + void OnDeletionRecycler (wxCommandEvent& event) override { handleDeletion_ = DeletionPolicy::RECYCLER; updateSyncGui(); } + void OnDeletionVersioning (wxCommandEvent& event) override { handleDeletion_ = DeletionPolicy::VERSIONING; updateSyncGui(); } - void OnToggleDeletionType(wxCommandEvent& event) override { toggleDeletionPolicy(handleDeletion); updateSyncGui(); } + void OnToggleDeletionType(wxCommandEvent& event) override { toggleDeletionPolicy(handleDeletion_); updateSyncGui(); } void OnHelpDetectMovedFiles(wxHyperlinkEvent& event) override { displayHelpEntry(L"synchronization-settings" , this); } void OnHelpVersioning (wxHyperlinkEvent& event) override { displayHelpEntry(L"versioning", this); } @@ -141,8 +141,8 @@ private: //----------------------------------------------------- - void OnErrorPopup (wxCommandEvent& event) override { onGuiError = ON_GUIERROR_POPUP; updateMiscGui(); } //parameter NOT owned by radio button - void OnErrorIgnore(wxCommandEvent& event) override { onGuiError = ON_GUIERROR_IGNORE; updateMiscGui(); } // + void OnErrorPopup (wxCommandEvent& event) override { onGuiError_ = ON_GUIERROR_POPUP; updateMiscGui(); } //parameter NOT owned by radio button + void OnErrorIgnore(wxCommandEvent& event) override { onGuiError_ = ON_GUIERROR_IGNORE; updateMiscGui(); } // MiscSyncConfig getMiscSyncOptions() const; void setMiscSyncOptions(const MiscSyncConfig& miscCfg); @@ -150,12 +150,12 @@ private: void updateMiscGui(); //parameters with ownership NOT within GUI controls! - DirectionConfig directionCfg; - DeletionPolicy handleDeletion = DeletionPolicy::RECYCLER; //use Recycler, delete permanently or move to user-defined location - OnGuiError onGuiError = ON_GUIERROR_POPUP; + DirectionConfig directionCfg_; + DeletionPolicy handleDeletion_ = DeletionPolicy::RECYCLER; //use Recycler, delete permanently or move to user-defined location + OnGuiError onGuiError_ = ON_GUIERROR_POPUP; - EnumDescrList<VersioningStyle> enumVersioningStyle; - FolderSelector versioningFolder; + EnumDescrList<VersioningStyle> enumVersioningStyle_; + FolderSelector versioningFolder_; //----------------------------------------------------- @@ -163,14 +163,14 @@ private: bool unselectFolderPairConfig(); //returns false on error: shows message box! //output-only parameters - GlobalSyncConfig& globalCfgOut; - std::vector<LocalPairConfig>& folderPairConfigOut; + GlobalSyncConfig& globalCfgOut_; + std::vector<LocalPairConfig>& folderPairConfigOut_; //working copy of ALL config parameters: only one folder pair is selected at a time! GlobalSyncConfig globalCfg_; std::vector<LocalPairConfig> folderPairConfig_; - int selectedPairIndexToShow = EMPTY_PAIR_INDEX_SELECTED; + int selectedPairIndexToShow_ = EMPTY_PAIR_INDEX_SELECTED; static const int EMPTY_PAIR_INDEX_SELECTED = -2; const size_t onCompletionHistoryMax_; @@ -219,9 +219,9 @@ ConfigDialog::ConfigDialog(wxWindow* parent, GlobalSyncConfig& globalCfg, size_t onCompletionHistoryMax) : ConfigDlgGenerated(parent), - versioningFolder(*m_panelVersioning, *m_buttonSelectVersioningFolder, *m_bpButtonSelectAltFolder, *m_versioningFolderPath, nullptr /*staticText*/, nullptr /*wxWindow*/), - globalCfgOut(globalCfg), - folderPairConfigOut(folderPairConfig), + versioningFolder_(*m_panelVersioning, *m_buttonSelectVersioningFolder, *m_bpButtonSelectAltFolder, *m_versioningFolderPath, nullptr /*staticText*/, nullptr /*dropWindow2*/), + globalCfgOut_(globalCfg), + folderPairConfigOut_(folderPairConfig), globalCfg_(globalCfg), folderPairConfig_(folderPairConfig), onCompletionHistoryMax_(onCompletionHistoryMax) @@ -282,7 +282,7 @@ ConfigDialog::ConfigDialog(wxWindow* parent, m_textCtrlInclude->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(ConfigDialog::onFilterKeyEvent), nullptr, this); m_textCtrlExclude->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(ConfigDialog::onFilterKeyEvent), nullptr, this); - enumTimeDescr. + enumTimeDescr_. add(UnitTime::NONE, L"(" + _("None") + L")"). //meta options should be enclosed in parentheses add(UnitTime::TODAY, _("Today")). //add(UnitTime::THIS_WEEK, _("This week")). @@ -290,7 +290,7 @@ ConfigDialog::ConfigDialog(wxWindow* parent, add(UnitTime::THIS_YEAR, _("This year")). add(UnitTime::LAST_X_DAYS, _("Last x days")); - enumSizeDescr. + enumSizeDescr_. add(UnitSize::NONE, L"(" + _("None") + L")"). //meta options should be enclosed in parentheses add(UnitSize::BYTE, _("Byte")). add(UnitSize::KB, _("KB")). @@ -319,7 +319,7 @@ ConfigDialog::ConfigDialog(wxWindow* parent, setRelativeFontSize(*m_toggleBtnUpdate, 1.25); setRelativeFontSize(*m_toggleBtnCustom, 1.25); - enumVersioningStyle. + enumVersioningStyle_. add(VersioningStyle::REPLACE, _("Replace"), _("Move files and replace if existing")). add(VersioningStyle::ADD_TIMESTAMP, _("Time stamp"), _("Append a time stamp to each file name")); @@ -444,7 +444,7 @@ void ConfigDialog::OnSelectFolderPair(wxCommandEvent& event) if (!unselectFolderPairConfig()) { //restore old selection: - m_listBoxFolderPair->SetSelection(selectedPairIndexToShow + 1); + m_listBoxFolderPair->SetSelection(selectedPairIndexToShow_ + 1); return; } selectFolderPairConfig(selPos - 1); @@ -493,7 +493,7 @@ std::shared_ptr<const CompConfig> ConfigDialog::getCompConfig() const return nullptr; CompConfig compCfg; - compCfg.compareVar = localCmpVar; + compCfg.compareVar = localCmpVar_; compCfg.handleSymlinks = !m_checkBoxSymlinksInclude->GetValue() ? SymLinkHandling::EXCLUDE : m_radioBtnSymlinksDirect->GetValue() ? SymLinkHandling::DIRECT : SymLinkHandling::FOLLOW; compCfg.ignoreTimeShiftMinutes = fromTimeShiftPhrase(copyStringTo<std::wstring>(m_textCtrlTimeShift->GetValue())); @@ -508,7 +508,7 @@ void ConfigDialog::setCompConfig(std::shared_ptr<const CompConfig> compCfg) if (!compCfg) //when local settings are inactive, display (current) global settings instead: compCfg = std::make_shared<const CompConfig>(globalCfg_.cmpConfig); - localCmpVar = compCfg->compareVar; + localCmpVar_ = compCfg->compareVar; switch (compCfg->handleSymlinks) { @@ -545,7 +545,7 @@ void ConfigDialog::updateCompGui() m_toggleBtnByContent ->SetValue(false); if (m_checkBoxUseLocalCmpOptions->GetValue()) //help wxWidgets a little to render inactive config state (need on Windows, NOT on Linux!) - switch (localCmpVar) + switch (localCmpVar_) { case CompareVariant::TIME_SIZE: m_toggleBtnByTimeSize->SetValue(true); @@ -566,12 +566,12 @@ void ConfigDialog::updateCompGui() else bmpCtrl.SetBitmap(greyScale(bmp)); }; - setBitmap(*m_bitmapByTimeSize, localCmpVar == CompareVariant::TIME_SIZE, getResourceImage(L"file-time")); - setBitmap(*m_bitmapByContent, localCmpVar == CompareVariant::CONTENT, getResourceImage(L"file-content")); - setBitmap(*m_bitmapBySize, localCmpVar == CompareVariant::SIZE, getResourceImage(L"file-size")); + setBitmap(*m_bitmapByTimeSize, localCmpVar_ == CompareVariant::TIME_SIZE, getResourceImage(L"file-time")); + setBitmap(*m_bitmapByContent, localCmpVar_ == CompareVariant::CONTENT, getResourceImage(L"file-content")); + setBitmap(*m_bitmapBySize, localCmpVar_ == CompareVariant::SIZE, getResourceImage(L"file-size")); //active variant description: - setText(*m_textCtrlCompVarDescription, L"\n" + getCompVariantDescription(localCmpVar)); + setText(*m_textCtrlCompVarDescription, L"\n" + getCompVariantDescription(localCmpVar_)); m_radioBtnSymlinksDirect->Enable(m_checkBoxSymlinksInclude->GetValue()); m_radioBtnSymlinksFollow->Enable(m_checkBoxSymlinksInclude->GetValue()); @@ -611,11 +611,11 @@ FilterConfig ConfigDialog::getFilterConfig() const return FilterConfig(includeFilter, exludeFilter, m_spinCtrlTimespan->GetValue(), - getEnumVal(enumTimeDescr, *m_choiceUnitTimespan), + getEnumVal(enumTimeDescr_, *m_choiceUnitTimespan), m_spinCtrlMinSize->GetValue(), - getEnumVal(enumSizeDescr, *m_choiceUnitMinSize), + getEnumVal(enumSizeDescr_, *m_choiceUnitMinSize), m_spinCtrlMaxSize->GetValue(), - getEnumVal(enumSizeDescr, *m_choiceUnitMaxSize)); + getEnumVal(enumSizeDescr_, *m_choiceUnitMaxSize)); } @@ -624,9 +624,9 @@ void ConfigDialog::setFilterConfig(const FilterConfig& filter) m_textCtrlInclude->ChangeValue(utfCvrtTo<wxString>(filter.includeFilter)); m_textCtrlExclude->ChangeValue(utfCvrtTo<wxString>(filter.excludeFilter)); - setEnumVal(enumTimeDescr, *m_choiceUnitTimespan, filter.unitTimeSpan); - setEnumVal(enumSizeDescr, *m_choiceUnitMinSize, filter.unitSizeMin); - setEnumVal(enumSizeDescr, *m_choiceUnitMaxSize, filter.unitSizeMax); + setEnumVal(enumTimeDescr_, *m_choiceUnitTimespan, filter.unitTimeSpan); + setEnumVal(enumSizeDescr_, *m_choiceUnitMinSize, filter.unitSizeMin); + setEnumVal(enumSizeDescr_, *m_choiceUnitMaxSize, filter.unitSizeMax); m_spinCtrlTimespan->SetValue(static_cast<int>(filter.timeSpan)); m_spinCtrlMinSize ->SetValue(static_cast<int>(filter.sizeMin)); @@ -754,42 +754,42 @@ void toggleCustomSyncConfig(DirectionConfig& directionCfg, SyncDirection& custSy void ConfigDialog::OnExLeftSideOnly(wxCommandEvent& event) { - toggleCustomSyncConfig(directionCfg, directionCfg.custom.exLeftSideOnly); + toggleCustomSyncConfig(directionCfg_, directionCfg_.custom.exLeftSideOnly); updateSyncGui(); } void ConfigDialog::OnExRightSideOnly(wxCommandEvent& event) { - toggleCustomSyncConfig(directionCfg, directionCfg.custom.exRightSideOnly); + toggleCustomSyncConfig(directionCfg_, directionCfg_.custom.exRightSideOnly); updateSyncGui(); } void ConfigDialog::OnLeftNewer(wxCommandEvent& event) { - toggleCustomSyncConfig(directionCfg, directionCfg.custom.leftNewer); + toggleCustomSyncConfig(directionCfg_, directionCfg_.custom.leftNewer); updateSyncGui(); } void ConfigDialog::OnRightNewer(wxCommandEvent& event) { - toggleCustomSyncConfig(directionCfg, directionCfg.custom.rightNewer); + toggleCustomSyncConfig(directionCfg_, directionCfg_.custom.rightNewer); updateSyncGui(); } void ConfigDialog::OnDifferent(wxCommandEvent& event) { - toggleCustomSyncConfig(directionCfg, directionCfg.custom.different); + toggleCustomSyncConfig(directionCfg_, directionCfg_.custom.different); updateSyncGui(); } void ConfigDialog::OnConflict(wxCommandEvent& event) { - toggleCustomSyncConfig(directionCfg, directionCfg.custom.conflict); + toggleCustomSyncConfig(directionCfg_, directionCfg_.custom.conflict); updateSyncGui(); } @@ -878,10 +878,10 @@ std::shared_ptr<const SyncConfig> ConfigDialog::getSyncConfig() const return nullptr; SyncConfig syncCfg; - syncCfg.directionCfg = directionCfg; - syncCfg.handleDeletion = handleDeletion; - syncCfg.versioningFolderPhrase = versioningFolder.getPath(); - syncCfg.versioningStyle = getEnumVal(enumVersioningStyle, *m_choiceVersioningStyle); + syncCfg.directionCfg = directionCfg_; + syncCfg.handleDeletion = handleDeletion_; + syncCfg.versioningFolderPhrase = versioningFolder_.getPath(); + syncCfg.versioningStyle = getEnumVal(enumVersioningStyle_, *m_choiceVersioningStyle); return std::make_shared<const SyncConfig>(syncCfg); } @@ -894,10 +894,10 @@ void ConfigDialog::setSyncConfig(std::shared_ptr<const SyncConfig> syncCfg) if (!syncCfg) //when local settings are inactive, display (current) global settings instead: syncCfg = std::make_shared<const SyncConfig>(globalCfg_.syncCfg); - directionCfg = syncCfg->directionCfg; //make working copy; ownership *not* on GUI - handleDeletion = syncCfg->handleDeletion; - versioningFolder.setPath(syncCfg->versioningFolderPhrase); - setEnumVal(enumVersioningStyle, *m_choiceVersioningStyle, syncCfg->versioningStyle); + directionCfg_ = syncCfg->directionCfg; //make working copy; ownership *not* on GUI + handleDeletion_ = syncCfg->handleDeletion; + versioningFolder_.setPath(syncCfg->versioningFolderPhrase); + setEnumVal(enumVersioningStyle_, *m_choiceVersioningStyle, syncCfg->versioningStyle); updateSyncGui(); } @@ -921,7 +921,7 @@ void ConfigDialog::updateSyncGui() m_notebook->SetPageImage(static_cast<size_t>(SyncConfigPanel::SYNC), static_cast<int>(m_checkBoxUseLocalSyncOptions->GetValue() ? ConfigTypeImage::SYNC: ConfigTypeImage::SYNC_GREY)); - updateSyncDirectionIcons(directionCfg, + updateSyncDirectionIcons(directionCfg_, *m_bpButtonLeftOnly, *m_bpButtonRightOnly, *m_bpButtonLeftNewer, @@ -930,8 +930,8 @@ void ConfigDialog::updateSyncGui() *m_bpButtonConflict); //selecting "detect move files" does not always make sense: - m_checkBoxDetectMove->Enable(detectMovedFilesSelectable(directionCfg)); - m_checkBoxDetectMove->SetValue(detectMovedFilesEnabled(directionCfg)); //parameter NOT owned by checkbox! + m_checkBoxDetectMove->Enable(detectMovedFilesSelectable(directionCfg_)); + m_checkBoxDetectMove->SetValue(detectMovedFilesEnabled(directionCfg_)); //parameter NOT owned by checkbox! auto setBitmap = [&](wxStaticBitmap& bmpCtrl, bool active, const wxBitmap& bmp) { @@ -943,14 +943,14 @@ void ConfigDialog::updateSyncGui() }; //display only relevant sync options - m_bitmapDatabase ->Show(directionCfg.var == DirectionConfig::TWO_WAY); - fgSizerSyncDirections->Show(directionCfg.var != DirectionConfig::TWO_WAY); + m_bitmapDatabase ->Show(directionCfg_.var == DirectionConfig::TWO_WAY); + fgSizerSyncDirections->Show(directionCfg_.var != DirectionConfig::TWO_WAY); - if (directionCfg.var == DirectionConfig::TWO_WAY) + if (directionCfg_.var == DirectionConfig::TWO_WAY) setBitmap(*m_bitmapDatabase, true, getResourceImage(L"database")); else { - const CompareVariant activeCmpVar = m_checkBoxUseLocalCmpOptions->GetValue() ? localCmpVar : globalCfg_.cmpConfig.compareVar; + const CompareVariant activeCmpVar = m_checkBoxUseLocalCmpOptions->GetValue() ? localCmpVar_ : globalCfg_.cmpConfig.compareVar; m_bitmapLeftNewer ->Show(activeCmpVar == CompareVariant::TIME_SIZE); m_bpButtonLeftNewer ->Show(activeCmpVar == CompareVariant::TIME_SIZE); @@ -962,7 +962,7 @@ void ConfigDialog::updateSyncGui() } //active variant description: - setText(*m_textCtrlSyncVarDescription, L"\n" + getSyncVariantDescription(directionCfg.var)); + setText(*m_textCtrlSyncVarDescription, L"\n" + getSyncVariantDescription(directionCfg_.var)); //update toggle buttons -> they have no parameter-ownership at all! m_toggleBtnTwoWay->SetValue(false); @@ -971,7 +971,7 @@ void ConfigDialog::updateSyncGui() m_toggleBtnCustom->SetValue(false); if (m_checkBoxUseLocalSyncOptions->GetValue()) //help wxWidgets a little to render inactive config state (need on Windows, NOT on Linux!) - switch (directionCfg.var) + switch (directionCfg_.var) { case DirectionConfig::TWO_WAY: m_toggleBtnTwoWay->SetValue(true); @@ -987,7 +987,7 @@ void ConfigDialog::updateSyncGui() break; } - switch (handleDeletion) + switch (handleDeletion_) { case DeletionPolicy::PERMANENT: m_radioBtnPermanent->SetValue(true); @@ -1011,15 +1011,15 @@ void ConfigDialog::updateSyncGui() m_bpButtonDeletionType->SetBitmapDisabled(greyScale(m_bpButtonDeletionType->GetBitmap())); //fix wxWidgets' all-too-clever multi-state! - const bool versioningSelected = handleDeletion == DeletionPolicy::VERSIONING; + const bool versioningSelected = handleDeletion_ == DeletionPolicy::VERSIONING; m_panelVersioning->Show(versioningSelected); if (versioningSelected) { - updateTooltipEnumVal(enumVersioningStyle, *m_choiceVersioningStyle); + updateTooltipEnumVal(enumVersioningStyle_, *m_choiceVersioningStyle); const std::wstring pathSep = utfCvrtTo<std::wstring>(FILE_NAME_SEPARATOR); - switch (getEnumVal(enumVersioningStyle, *m_choiceVersioningStyle)) + switch (getEnumVal(enumVersioningStyle_, *m_choiceVersioningStyle)) { case VersioningStyle::REPLACE: setText(*m_staticTextNamingCvtPart1, pathSep + _("Folder") + pathSep + _("File") + L".doc"); @@ -1043,10 +1043,10 @@ void ConfigDialog::updateSyncGui() MiscSyncConfig ConfigDialog::getMiscSyncOptions() const { - assert(selectedPairIndexToShow == -1); + assert(selectedPairIndexToShow_ == -1); MiscSyncConfig miscCfg; - miscCfg.handleError = onGuiError; + miscCfg.handleError = onGuiError_; miscCfg.onCompletionCommand = m_comboBoxOnCompletion->getValue(); miscCfg.onCompletionHistory = m_comboBoxOnCompletion->getHistory(); return miscCfg; @@ -1055,7 +1055,7 @@ MiscSyncConfig ConfigDialog::getMiscSyncOptions() const void ConfigDialog::setMiscSyncOptions(const MiscSyncConfig& miscCfg) { - onGuiError = miscCfg.handleError; + onGuiError_ = miscCfg.handleError; m_comboBoxOnCompletion->setValue(miscCfg.onCompletionCommand); m_comboBoxOnCompletion->setHistory(miscCfg.onCompletionHistory, onCompletionHistoryMax_); @@ -1065,7 +1065,7 @@ void ConfigDialog::setMiscSyncOptions(const MiscSyncConfig& miscCfg) void ConfigDialog::updateMiscGui() { - switch (onGuiError) + switch (onGuiError_) { case ON_GUIERROR_IGNORE: m_radioBtnIgnoreErrors->SetValue(true); @@ -1079,11 +1079,11 @@ void ConfigDialog::updateMiscGui() void ConfigDialog::selectFolderPairConfig(int newPairIndexToShow) { - assert(selectedPairIndexToShow == EMPTY_PAIR_INDEX_SELECTED); + assert(selectedPairIndexToShow_ == EMPTY_PAIR_INDEX_SELECTED); assert(newPairIndexToShow == -1 || makeUnsigned(newPairIndexToShow) < folderPairConfig_.size()); numeric::clamp(newPairIndexToShow, -1, static_cast<int>(folderPairConfig_.size()) - 1); - selectedPairIndexToShow = newPairIndexToShow; + selectedPairIndexToShow_ = newPairIndexToShow; m_listBoxFolderPair->SetSelection(newPairIndexToShow + 1); //show/hide controls that are only relevant for main/local config @@ -1116,16 +1116,16 @@ void ConfigDialog::selectFolderPairConfig(int newPairIndexToShow) } else { - setCompConfig (folderPairConfig_[selectedPairIndexToShow].altCmpConfig); - setSyncConfig (folderPairConfig_[selectedPairIndexToShow].altSyncConfig); - setFilterConfig(folderPairConfig_[selectedPairIndexToShow].localFilter); + setCompConfig (folderPairConfig_[selectedPairIndexToShow_].altCmpConfig); + setSyncConfig (folderPairConfig_[selectedPairIndexToShow_].altSyncConfig); + setFilterConfig(folderPairConfig_[selectedPairIndexToShow_].localFilter); } } bool ConfigDialog::unselectFolderPairConfig() { - assert(selectedPairIndexToShow == -1 || makeUnsigned(selectedPairIndexToShow) < folderPairConfig_.size()); + assert(selectedPairIndexToShow_ == -1 || makeUnsigned(selectedPairIndexToShow_) < folderPairConfig_.size()); auto compCfg = getCompConfig(); auto syncCfg = getSyncConfig(); @@ -1152,7 +1152,7 @@ bool ConfigDialog::unselectFolderPairConfig() m_comboBoxOnCompletion->addItemHistory(); //commit current "on completion" history item - if (selectedPairIndexToShow < 0) + if (selectedPairIndexToShow_ < 0) { globalCfg_.cmpConfig = *compCfg; globalCfg_.syncCfg = *syncCfg; @@ -1161,12 +1161,12 @@ bool ConfigDialog::unselectFolderPairConfig() } else { - folderPairConfig_[selectedPairIndexToShow].altCmpConfig = compCfg; - folderPairConfig_[selectedPairIndexToShow].altSyncConfig = syncCfg; - folderPairConfig_[selectedPairIndexToShow].localFilter = filterCfg; + folderPairConfig_[selectedPairIndexToShow_].altCmpConfig = compCfg; + folderPairConfig_[selectedPairIndexToShow_].altSyncConfig = syncCfg; + folderPairConfig_[selectedPairIndexToShow_].localFilter = filterCfg; } - selectedPairIndexToShow = EMPTY_PAIR_INDEX_SELECTED; + selectedPairIndexToShow_ = EMPTY_PAIR_INDEX_SELECTED; //m_listBoxFolderPair->SetSelection(wxNOT_FOUND); not needed, selectedPairIndexToShow has parameter ownership return true; } @@ -1177,8 +1177,8 @@ void ConfigDialog::OnOkay(wxCommandEvent& event) if (!unselectFolderPairConfig()) return; - globalCfgOut = globalCfg_; - folderPairConfigOut = folderPairConfig_; + globalCfgOut_ = globalCfg_; + folderPairConfigOut_ = folderPairConfig_; EndModal(ReturnSyncConfig::BUTTON_OKAY); } diff --git a/FreeFileSync/Source/ui/taskbar.cpp b/FreeFileSync/Source/ui/taskbar.cpp index e8a61efa..1723957a 100644 --- a/FreeFileSync/Source/ui/taskbar.cpp +++ b/FreeFileSync/Source/ui/taskbar.cpp @@ -86,7 +86,7 @@ public: void setProgress(double fraction) { const wchar_t* errorMsg = nullptr; - setProgress_(assocWindow, fraction * 100000, 100000, errorMsg); + setProgress_(assocWindow, fraction * 1000000, 1000000, errorMsg); if (errorMsg) { ZEN_ON_SCOPE_EXIT(freeString_(errorMsg)); diff --git a/FreeFileSync/Source/ui/tree_view.cpp b/FreeFileSync/Source/ui/tree_view.cpp index 88d4dd49..cbfb5305 100644 --- a/FreeFileSync/Source/ui/tree_view.cpp +++ b/FreeFileSync/Source/ui/tree_view.cpp @@ -299,17 +299,15 @@ void TreeView::sortSingleLevel(std::vector<TreeLine>& items, ColumnTypeNavi colu switch (columnType) { - case ColumnTypeNavi::BYTES: - std::sort(items.begin(), items.end(), makeSortDirection(lessBytes, Int2Type<ascending>())); - break; - - case ColumnTypeNavi::DIRECTORY: + case ColumnTypeNavi::FOLDER_NAME: std::sort(items.begin(), items.end(), LessShortName<ascending>()); break; - case ColumnTypeNavi::ITEM_COUNT: std::sort(items.begin(), items.end(), makeSortDirection(lessCount, Int2Type<ascending>())); break; + case ColumnTypeNavi::BYTES: + std::sort(items.begin(), items.end(), makeSortDirection(lessBytes, Int2Type<ascending>())); + break; } } @@ -467,12 +465,12 @@ bool TreeView::getDefaultSortDirection(ColumnTypeNavi colType) { switch (colType) { - case ColumnTypeNavi::BYTES: - return false; - case ColumnTypeNavi::DIRECTORY: + case ColumnTypeNavi::FOLDER_NAME: return true; case ColumnTypeNavi::ITEM_COUNT: return false; + case ColumnTypeNavi::BYTES: + return false; } assert(false); return true; @@ -808,11 +806,7 @@ private: { switch (static_cast<ColumnTypeNavi>(colType)) { - case ColumnTypeNavi::BYTES: - case ColumnTypeNavi::ITEM_COUNT: - break; - - case ColumnTypeNavi::DIRECTORY: + case ColumnTypeNavi::FOLDER_NAME: if (treeDataView_) if (std::unique_ptr<TreeView::Node> node = treeDataView_->getLine(row)) if (const TreeView::RootNode* root = dynamic_cast<const TreeView::RootNode*>(node.get())) @@ -826,6 +820,10 @@ private: return dirLeft + L" \u2212 \n" + dirRight; //\u2212 = unicode minus } break; + + case ColumnTypeNavi::ITEM_COUNT: + case ColumnTypeNavi::BYTES: + break; } return std::wstring(); } @@ -837,10 +835,7 @@ private: if (std::unique_ptr<TreeView::Node> node = treeDataView_->getLine(row)) switch (static_cast<ColumnTypeNavi>(colType)) { - case ColumnTypeNavi::BYTES: - return filesizeToShortString(node->bytes_); - - case ColumnTypeNavi::DIRECTORY: + case ColumnTypeNavi::FOLDER_NAME: if (const TreeView::RootNode* root = dynamic_cast<const TreeView::RootNode*>(node.get())) return root->displayName_; else if (const TreeView::DirNode* dir = dynamic_cast<const TreeView::DirNode*>(node.get())) @@ -851,6 +846,9 @@ private: case ColumnTypeNavi::ITEM_COUNT: return toGuiString(node->itemCount_); + + case ColumnTypeNavi::BYTES: + return filesizeToShortString(node->bytes_); } } return std::wstring(); @@ -908,7 +906,7 @@ private: // -------------------------------------------------------------------------------- // -> synchronize renderCell() <-> getBestSize() <-> getRowMouseHover() - if (static_cast<ColumnTypeNavi>(colType) == ColumnTypeNavi::DIRECTORY && treeDataView_) + if (static_cast<ColumnTypeNavi>(colType) == ColumnTypeNavi::FOLDER_NAME && treeDataView_) { if (std::unique_ptr<TreeView::Node> node = treeDataView_->getLine(row)) { @@ -1047,7 +1045,7 @@ private: { // -> synchronize renderCell() <-> getBestSize() <-> getRowMouseHover() - if (static_cast<ColumnTypeNavi>(colType) == ColumnTypeNavi::DIRECTORY && treeDataView_) + if (static_cast<ColumnTypeNavi>(colType) == ColumnTypeNavi::FOLDER_NAME && treeDataView_) { if (std::unique_ptr<TreeView::Node> node = treeDataView_->getLine(row)) return node->level_ * widthLevelStep + GAP_SIZE + (showPercentBar ? WIDTH_PERCENTAGE_BAR + 2 * GAP_SIZE : 0) + widthNodeStatus + GAP_SIZE @@ -1065,11 +1063,7 @@ private: { switch (static_cast<ColumnTypeNavi>(colType)) { - case ColumnTypeNavi::BYTES: - case ColumnTypeNavi::ITEM_COUNT: - break; - - case ColumnTypeNavi::DIRECTORY: + case ColumnTypeNavi::FOLDER_NAME: if (treeDataView_) if (std::unique_ptr<TreeView::Node> node = treeDataView_->getLine(row)) { @@ -1082,6 +1076,10 @@ private: return static_cast<HoverArea>(HoverAreaNavi::NODE); } break; + + case ColumnTypeNavi::ITEM_COUNT: + case ColumnTypeNavi::BYTES: + break; } return HoverArea::NONE; } @@ -1090,12 +1088,12 @@ private: { switch (static_cast<ColumnTypeNavi>(colType)) { - case ColumnTypeNavi::BYTES: - return _("Size"); - case ColumnTypeNavi::DIRECTORY: + case ColumnTypeNavi::FOLDER_NAME: return _("Name"); case ColumnTypeNavi::ITEM_COUNT: return _("Items"); + case ColumnTypeNavi::BYTES: + return _("Size"); } return std::wstring(); } @@ -1206,19 +1204,33 @@ private: { auto colAttr = grid_.getColumnConfig(); + Grid::ColumnAttribute* caFolderName = nullptr; + Grid::ColumnAttribute* caToggle = nullptr; + for (Grid::ColumnAttribute& ca : colAttr) - if (ca.type_ == ct) - { - ca.visible_ = !ca.visible_; - grid_.setColumnConfig(colAttr); - return; - } + if (ca.type_ == static_cast<ColumnType>(ColumnTypeNavi::FOLDER_NAME)) + caFolderName = &ca; + else if (ca.type_ == ct) + caToggle = &ca; + + assert(caFolderName && caFolderName->stretch_ > 0 && caFolderName->visible_); + assert(caToggle && caToggle->stretch_ == 0); + + if (caFolderName && caToggle) + { + caToggle->visible_ = !caToggle->visible_; + + //take width of newly visible column from stretched folder name column + caFolderName->offset_ -= caToggle->visible_ ? caToggle->offset_ : -caToggle->offset_; + + grid_.setColumnConfig(colAttr); + } }; for (const Grid::ColumnAttribute& ca : grid_.getColumnConfig()) { - menu.addCheckBox(getColumnLabel(ca.type_), [ca, toggleColumn] { toggleColumn(ca.type_); }, - ca.visible_, ca.type_ != static_cast<ColumnType>(ColumnTypeNavi::DIRECTORY)); //do not allow user to hide file name column! + menu.addCheckBox(getColumnLabel(ca.type_), [ct = ca.type_, toggleColumn] { toggleColumn(ct); }, + ca.visible_, ca.type_ != static_cast<ColumnType>(ColumnTypeNavi::FOLDER_NAME)); //do not allow user to hide file name column! } //-------------------------------------------------------------------------------------------------------- menu.addSeparator(); diff --git a/FreeFileSync/Source/ui/version_check.cpp b/FreeFileSync/Source/ui/version_check.cpp index 442caf5e..da7b0e08 100644 --- a/FreeFileSync/Source/ui/version_check.cpp +++ b/FreeFileSync/Source/ui/version_check.cpp @@ -17,10 +17,15 @@ #include <wx+/http.h> #include <wx+/image_resources.h> #include "../lib/ffs_paths.h" -#include "version_check_impl.h" +#include "small_dlgs.h" #ifdef ZEN_WIN #include <zen/win_ver.h> + #include <ShlObj.h> + #include <zen/com_tools.h> + #include <zen/file_io.h> + #include <zen/long_path_prefix.h> + #include <zen/shell_execute.h> #elif defined ZEN_MAC #include <CoreServices/CoreServices.h> //Gestalt() @@ -35,6 +40,8 @@ namespace const std::thread::id mainThreadId = std::this_thread::get_id(); #endif +const wchar_t ffsUpdateCheckUserAgent[] = L"FFS-Update-Check"; + std::wstring getIso639Language() { @@ -43,23 +50,22 @@ std::wstring getIso639Language() #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 + const 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 const std::wstring localeName(wxLocale::GetLanguageCanonicalName(wxLocale::GetSystemLanguage())); - if (localeName.empty()) - return std::wstring(); - - assert(beforeLast(localeName, L"_", IF_MISSING_RETURN_ALL).size() == 2); - return beforeLast(localeName, L"_", IF_MISSING_RETURN_ALL); + if (!localeName.empty()) + { + assert(beforeLast(localeName, L"_", IF_MISSING_RETURN_ALL).size() == 2); + return beforeLast(localeName, L"_", IF_MISSING_RETURN_ALL); + } #endif + assert(false); + return L"zz"; } @@ -70,22 +76,22 @@ std::wstring getIso3166Country() #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 + const 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 const std::wstring localeName(wxLocale::GetLanguageCanonicalName(wxLocale::GetSystemLanguage())); - if (localeName.empty()) - return std::wstring(); - - return afterLast(localeName, L"_", IF_MISSING_RETURN_NONE); + if (!localeName.empty()) + { + if (contains(localeName, L"_")) + return afterLast(localeName, L"_", IF_MISSING_RETURN_NONE); + } #endif + assert(false); + return L"ZZ"; } @@ -95,7 +101,7 @@ 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.emplace_back("ffs_version", utfCvrtTo<std::string>(zen::ffsVersion)); + params.emplace_back("ffs_version", zen::ffsVersion); params.emplace_back("ffs_type", isPortableVersion() ? "Portable" : "Local"); #ifdef ZEN_WIN @@ -136,53 +142,169 @@ std::vector<std::pair<std::string, std::string>> geHttpPostParameters() #endif #endif - const std::string isoLang = utfCvrtTo<std::string>(getIso639Language()); - const std::string isoCountry = utfCvrtTo<std::string>(getIso3166Country()); - - params.emplace_back("language", !isoLang .empty() ? isoLang : "zz"); - params.emplace_back("country" , !isoCountry.empty() ? isoCountry : "ZZ"); + params.emplace_back("language", utfCvrtTo<std::string>(getIso639Language())); + params.emplace_back("country" , utfCvrtTo<std::string>(getIso3166Country())); return params; } -//access is thread-safe on Windows (WinInet), but not on Linux/OS X (wxWidgets) -std::wstring getOnlineVersion(const std::vector<std::pair<std::string, std::string>>& postParams) //throw SysError +#ifdef ZEN_WIN +void runFreeFileSyncAutoUpdate(wxWindow* parent, const std::wstring& directDownloadUrl, const Zstring& fileName, std::uint64_t fileSize) { - //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", L"FFS-Update-Check", postParams); //throw SysError - const auto version = utfCvrtTo<std::wstring>(buffer); - return trimCpy(version); -} + try + { +#ifdef ZEN_WIN_VISTA_AND_LATER + PWSTR buf = nullptr; + ZEN_COM_CHECK(::SHGetKnownFolderPath(FOLDERID_Downloads, //_In_ REFKNOWNFOLDERID rfid, + KF_FLAG_DONT_VERIFY, //_In_ DWORD dwFlags, + nullptr, //_In_opt_ HANDLE hToken, + &buf)); //_Out_ PWSTR *ppszPath + ZEN_ON_SCOPE_EXIT(::CoTaskMemFree(buf)); +#else + wchar_t buf[MAX_PATH] = {}; + ZEN_COM_CHECK(::SHGetFolderPath(nullptr, //__in HWND hwndOwner, + CSIDL_DESKTOPDIRECTORY | + CSIDL_FLAG_DONT_VERIFY, //__in int nFolder, + nullptr, //__in HANDLE hToken, + 0 /* == SHGFP_TYPE_CURRENT*/, //__in DWORD dwFlags, + buf)); //__out LPTSTR pszPath +#endif + const Zstring dirPath = buf; + const Zstring installerLocalPath = dirPath.empty() ? fileName : appendSeparator(dirPath) + fileName; + const Zstring installerLocalPathTmp = installerLocalPath + L".tmp"; -std::vector<size_t> parseVersion(const std::wstring& version) -{ - std::vector<size_t> output; - for (const std::wstring& digit : split(version, FFS_VERSION_SEPARATOR)) - output.push_back(stringTo<size_t>(digit)); - return output; -} + if (!fileExists(installerLocalPath)) + { + ZEN_ON_SCOPE_FAIL(try { removeFile(installerLocalPathTmp); } + catch (FileError&) {}); + { + DownloadProgressWindow progressWin(parent, installerLocalPath, fileSize); -std::wstring getOnlineChangelogDelta() -{ - try //harmonize with wxHTTP: get_latest_changes.php must be accessible without https!!! + auto notifyProgress = [&](std::int64_t bytesDelta) + { + progressWin.notifyProgress(bytesDelta); + progressWin.requestUiRefresh(); //throw DownloadProgressWindow::CancelPressed + }; + + HttpInputStream httpStreamIn = sendHttpGet(directDownloadUrl, ffsUpdateCheckUserAgent); //throw SysError + + FileOutput fileStreamOut(installerLocalPathTmp, FileOutput::ACC_OVERWRITE); //throw FileError + + unbufferedStreamCopy(httpStreamIn, //throw SysError + fileStreamOut, //throw FileError + notifyProgress); //optional + } + + //operation finished: move temp file -> this should work transactionally: + if (!::MoveFileEx(applyLongPathPrefix(installerLocalPathTmp).c_str(), //__in LPCTSTR lpExistingFileName, + applyLongPathPrefix(installerLocalPath ).c_str(), //__in_opt LPCTSTR lpNewFileName, + MOVEFILE_REPLACE_EXISTING)) //__in DWORD dwFlags + THROW_LAST_FILE_ERROR(replaceCpy(replaceCpy(_("Cannot move file %x to %y."), + L"%x", L"\n" + fmtPath(installerLocalPathTmp)), + L"%y", L"\n" + fmtPath(installerLocalPath)), L"MoveFileEx"); + } + + const Zstring commandLine = Zstr("\"") + installerLocalPath + Zstr("\"") + + Zstr(" /silent") + + Zstr(" /dir=\"") + getInstallDirPath() + Zstr("\"") + + Zstr(" /ffs_type=") + (isPortableVersion() ? Zstr("portable") : Zstr("local")) + + Zstr(" /autoupdate"); +#ifndef NDEBUG + __debugbreak(); //don't mess up the dev-build running a silent install +#endif + shellExecute(commandLine, ExecutionType::EXEC_TYPE_SYNC); //throw FileError + } + catch (const SysError& e) + { + showNotificationDialog(parent, DialogInfoType::ERROR2, PopupDialogCfg().setMainInstructions(_("Failed to retrieve update information.")). + setDetailInstructions(e.toString())); + } + catch (const FileError& e) { - const std::string buffer = sendHttpPost(L"http://www.freefilesync.org/get_latest_changes.php", L"FFS-Update-Check", { { "since", utfCvrtTo<std::string>(zen::ffsVersion) } }); //throw SysError - return utfCvrtTo<std::wstring>(buffer); + showNotificationDialog(parent, DialogInfoType::ERROR2, PopupDialogCfg().setDetailInstructions(e.toString())); } - catch (zen::SysError&) { assert(false); return std::wstring(); } + catch (DownloadProgressWindow::CancelPressed&) { return; } } +#endif -void showUpdateAvailableDialog(wxWindow* parent, const std::wstring& onlineVersion, const std::wstring& onlineChangeLog) +void showUpdateAvailableDialog(wxWindow* parent, const std::string& onlineVersion) { + std::wstring updateDetailsMsg; + try + { + try //harmonize with wxHTTP: get_latest_changes.php must be accessible without https!!! + { + const std::string buf = sendHttpPost(L"http://www.freefilesync.org/get_latest_changes.php", ffsUpdateCheckUserAgent, + { { "since", zen::ffsVersion } }).readAll(); //throw SysError + updateDetailsMsg = utfCvrtTo<std::wstring>(buf); + } + catch (const zen::SysError& e) { throw FileError(_("Failed to retrieve update information."), e.toString()); } + +#ifdef ZEN_WIN + Opt<DonationInfo> donationInfo = getDonationInfo(); //throw FileError + if (!donationInfo) + throw FileError(_("Automatic updates:") + L" " + _("Disabled") + L"\n(" + _("Requires FreeFileSync Donation Edition") + L")"); + + std::wstring directDownloadUrl; + Zstring installerFileName; + std::uint64_t installerFileSize = 0; + try + { + const std::string buf = sendHttpPost(L"http://www.freefilesync.org/donate/get_installer_url.php", ffsUpdateCheckUserAgent, + xWwwFormUrlDecode(donationInfo->installerDirectDownloadUrlParamsEncoded)).readAll(); //throw SysError + + //contains "download_page_url, direct_download_url, file_name, file_size" or "error"; coordinate with donate/get_installer_url.php + for (const std::pair<std::string, std::string>& nv : xWwwFormUrlDecode(buf)) + if (nv.first == "direct_download_url") + directDownloadUrl = utfCvrtTo<std::wstring>(nv.second); + else if (nv.first == "file_name") + installerFileName = utfCvrtTo<Zstring>(nv.second); + else if (nv.first == "file_size") + installerFileSize = stringTo<std::uint64_t>(nv.second); + else if (nv.first == "error") + throw SysError(utfCvrtTo<std::wstring>(nv.second)); //e.g. expiration info + + //check for empty/corrupted HTTP POST result (AV-software!) + if (directDownloadUrl.empty() || installerFileName.empty() || installerFileSize == 0) + throw SysError(L"Update server returned incomplete data."); + } + catch (const SysError& e) { throw FileError(_("Failed to retrieve update information.") + L"\n" + e.toString()); } + + switch (showConfirmationDialog3(parent, DialogInfoType::INFO, PopupDialogCfg3(). + setIcon(getResourceImage(L"update_available")). + setTitle(_("Check for Program Updates")). + setMainInstructions(_("A new version of FreeFileSync is available:") + L" " + utfCvrtTo<std::wstring>(onlineVersion) + L"\n" + + _("Auto-update now or download manually from the FreeFileSync home page?")). + setDetailInstructions(updateDetailsMsg), + _("&Auto-update"), + _("&Home page"))) + { + case ConfirmationButton3::DO_IT: + runFreeFileSyncAutoUpdate(parent, directDownloadUrl, installerFileName, installerFileSize); + break; + case ConfirmationButton3::DONT_DO_IT: + openDonationEditionThankYouPage(parent); + break; + case ConfirmationButton3::CANCEL: + break; + } + return; +#endif + } + catch (const FileError& e) //fall back to regular update info dialog: + { + updateDetailsMsg = e.toString() + L"\n\n" + updateDetailsMsg; + } + switch (showConfirmationDialog(parent, DialogInfoType::INFO, PopupDialogCfg(). - setIcon(getResourceImage(L"download_update")). + setIcon(getResourceImage(L"update_available")). setTitle(_("Check for Program Updates")). - setMainInstructions(_("A new version of FreeFileSync is available:") + L" " + onlineVersion + L"\n" + _("Download now?")). - setDetailInstructions(onlineChangeLog), + setMainInstructions(_("A new version of FreeFileSync is available:") + L" " + utfCvrtTo<std::wstring>(onlineVersion) + L"\n" + _("Download now?")). + setDetailInstructions(updateDetailsMsg), _("&Download"))) { case ConfirmationButton::DO_IT: @@ -192,13 +314,31 @@ void showUpdateAvailableDialog(wxWindow* parent, const std::wstring& onlineVersi break; } } + + +//access is thread-safe on Windows (WinInet), but not on Linux/OS X (wxWidgets) +std::string getOnlineVersion(const std::vector<std::pair<std::string, std::string>>& postParams) //throw SysError +{ + //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", ffsUpdateCheckUserAgent, postParams).readAll(); //throw SysError + return trimCpy(buffer); +} + + +std::vector<size_t> parseVersion(const std::string& version) +{ + std::vector<size_t> output; + for (const std::string& digit : split(version, FFS_VERSION_SEPARATOR)) + output.push_back(stringTo<size_t>(digit)); + return output; +} } -bool zen::haveNewerVersionOnline(const std::wstring& onlineVersion) +bool zen::haveNewerVersionOnline(const std::string& onlineVersion) { - std::vector<size_t> current = parseVersion(zen::ffsVersion); - std::vector<size_t> online = parseVersion(onlineVersion); + const std::vector<size_t> current = parseVersion(zen::ffsVersion); + const std::vector<size_t> online = parseVersion(onlineVersion); if (online.empty() || online[0] == 0) //online version string may be "This website has been moved..." In this case better check for an update return true; @@ -220,18 +360,18 @@ void zen::disableUpdateCheck(time_t& lastUpdateCheck) } -void zen::checkForUpdateNow(wxWindow* parent, std::wstring& lastOnlineVersion, std::wstring& lastOnlineChangeLog) +void zen::checkForUpdateNow(wxWindow* parent, std::string& lastOnlineVersion) { try { - const std::wstring onlineVersion = getOnlineVersion(geHttpPostParameters()); //throw SysError - lastOnlineVersion = onlineVersion; - lastOnlineChangeLog = haveNewerVersionOnline(onlineVersion) ? getOnlineChangelogDelta() : L""; + const std::string onlineVersion = getOnlineVersion(geHttpPostParameters()); //throw SysError + lastOnlineVersion = onlineVersion; if (haveNewerVersionOnline(onlineVersion)) - showUpdateAvailableDialog(parent, lastOnlineVersion, lastOnlineChangeLog); + showUpdateAvailableDialog(parent, lastOnlineVersion); else showNotificationDialog(parent, DialogInfoType::INFO, PopupDialogCfg(). + setIcon(getResourceImage(L"update_check")). setTitle(_("Check for Program Updates")). setMainInstructions(_("FreeFileSync is up to date."))); } @@ -239,8 +379,7 @@ void zen::checkForUpdateNow(wxWindow* parent, std::wstring& lastOnlineVersion, s { if (internetIsAlive()) { - lastOnlineVersion = L"Unknown"; - lastOnlineChangeLog.clear(); + lastOnlineVersion = "Unknown"; switch (showConfirmationDialog(parent, DialogInfoType::ERROR2, PopupDialogCfg(). setTitle(_("Check for Program Updates")). @@ -284,9 +423,9 @@ std::shared_ptr<UpdateCheckResultPrep> zen::periodicUpdateCheckPrepare() struct zen::UpdateCheckResult { UpdateCheckResult() {} - UpdateCheckResult(const std::wstring& ver, const Opt<zen::SysError>& err, bool alive) : onlineVersion(ver), error(err), internetIsAlive(alive) {} + UpdateCheckResult(const std::string& ver, const Opt<zen::SysError>& err, bool alive) : onlineVersion(ver), error(err), internetIsAlive(alive) {} - std::wstring onlineVersion; + std::string onlineVersion; Opt<zen::SysError> error; bool internetIsAlive = false; }; @@ -298,12 +437,12 @@ std::shared_ptr<UpdateCheckResult> zen::periodicUpdateCheckRunAsync(const Update try { //access is thread-safe on Windows only! - const std::wstring onlineVersion = getOnlineVersion(resultPrep->postParameters); //throw SysError + const std::string onlineVersion = getOnlineVersion(resultPrep->postParameters); //throw SysError return std::make_shared<UpdateCheckResult>(onlineVersion, NoValue(), true); } catch (const zen::SysError& e) { - return std::make_shared<UpdateCheckResult>(L"", e, internetIsAlive()); + return std::make_shared<UpdateCheckResult>("", e, internetIsAlive()); } #else return nullptr; @@ -312,7 +451,7 @@ std::shared_ptr<UpdateCheckResult> zen::periodicUpdateCheckRunAsync(const Update //run on main thread: -void zen::periodicUpdateCheckEval(wxWindow* parent, time_t& lastUpdateCheck, std::wstring& lastOnlineVersion, std::wstring& lastOnlineChangeLog, const UpdateCheckResult* resultAsync) +void zen::periodicUpdateCheckEval(wxWindow* parent, time_t& lastUpdateCheck, std::string& lastOnlineVersion, const UpdateCheckResult* resultAsync) { #ifdef ZEN_WIN const UpdateCheckResult& result = *resultAsync; @@ -332,19 +471,17 @@ void zen::periodicUpdateCheckEval(wxWindow* parent, time_t& lastUpdateCheck, std if (!result.error) { - lastUpdateCheck = getVersionCheckCurrentTime(); - lastOnlineVersion = result.onlineVersion; - lastOnlineChangeLog = haveNewerVersionOnline(result.onlineVersion) ? getOnlineChangelogDelta() : L""; + lastUpdateCheck = getVersionCheckCurrentTime(); + lastOnlineVersion = result.onlineVersion; if (haveNewerVersionOnline(result.onlineVersion)) - showUpdateAvailableDialog(parent, lastOnlineVersion, lastOnlineChangeLog); + showUpdateAvailableDialog(parent, lastOnlineVersion); } else { if (result.internetIsAlive) { - lastOnlineVersion = L"Unknown"; - lastOnlineChangeLog.clear(); + lastOnlineVersion = "Unknown"; switch (showConfirmationDialog(parent, DialogInfoType::ERROR2, PopupDialogCfg(). setTitle(_("Check for Program Updates")). @@ -362,3 +499,21 @@ void zen::periodicUpdateCheckEval(wxWindow* parent, time_t& lastUpdateCheck, std //else: ignore this error } } + + +#ifdef ZEN_WIN +void zen::openDonationEditionThankYouPage(wxWindow* parent) +{ + try + { + if (Opt<DonationInfo> donationInfo = getDonationInfo()) //throw FileError + wxLaunchDefaultBrowser(donationInfo->ffsThankYouPageUrl); //don't need urlencode() + else + throw FileError(L"Corrupted installation: transaction ID not found"); + } + catch (const FileError& e) + { + showNotificationDialog(parent, DialogInfoType::ERROR2, PopupDialogCfg().setDetailInstructions(e.toString())); + } +} +#endif diff --git a/FreeFileSync/Source/ui/version_check.h b/FreeFileSync/Source/ui/version_check.h index 01c19519..6dfe1ff9 100644 --- a/FreeFileSync/Source/ui/version_check.h +++ b/FreeFileSync/Source/ui/version_check.h @@ -10,14 +10,14 @@ #include <functional> #include <memory> #include <wx/window.h> - +#include "version_check_impl.h" namespace zen { bool updateCheckActive (time_t lastUpdateCheck); void disableUpdateCheck(time_t& lastUpdateCheck); -bool haveNewerVersionOnline(const std::wstring& onlineVersion); - +bool haveNewerVersionOnline(const std::string& onlineVersion); +//---------------------------------------------------------------------------- //periodic update check: bool shouldRunPeriodicUpdateCheck(time_t lastUpdateCheck); @@ -29,15 +29,15 @@ std::shared_ptr<UpdateCheckResultPrep> periodicUpdateCheckPrepare(); //run on worker thread: (long-running part of the check) std::shared_ptr<UpdateCheckResult> periodicUpdateCheckRunAsync(const UpdateCheckResultPrep* resultPrep); //run on main thread: -void periodicUpdateCheckEval(wxWindow* parent, time_t& lastUpdateCheck, - std::wstring& lastOnlineVersion, - std::wstring& lastOnlineChangeLog, +void periodicUpdateCheckEval(wxWindow* parent, time_t& lastUpdateCheck, std::string& lastOnlineVersion, const UpdateCheckResult* resultAsync); - //---------------------------------------------------------------------------- - //call from main thread: -void checkForUpdateNow(wxWindow* parent, std::wstring& lastOnlineVersion, std::wstring& lastOnlineChangeLog); +void checkForUpdateNow(wxWindow* parent, std::string& lastOnlineVersion); +//---------------------------------------------------------------------------- +#ifdef ZEN_WIN + void openDonationEditionThankYouPage(wxWindow* parent); +#endif } #endif //VERSION_CHECK_H_324872374893274983275 diff --git a/FreeFileSync/Source/ui/version_check_impl.h b/FreeFileSync/Source/ui/version_check_impl.h index 904da735..58412517 100644 --- a/FreeFileSync/Source/ui/version_check_impl.h +++ b/FreeFileSync/Source/ui/version_check_impl.h @@ -16,14 +16,14 @@ time_t getVersionCheckInactiveId() { //use current version to calculate a changing number for the inactive state near UTC begin, in order to always check for updates after installing a new version //=> convert version into 11-based *unique* number (this breaks lexicographical version ordering, but that's irrelevant!) - time_t id = 0; - const wchar_t* first = zen::ffsVersion; - const wchar_t* last = first + zen::strLength(ffsVersion); - std::for_each(first, last, [&](wchar_t c) + int id = 0; + const char* first = zen::ffsVersion; + const char* last = first + zen::strLength(ffsVersion); + std::for_each(first, last, [&](char c) { id *= 11; - if (L'0' <= c && c <= L'9') - id += c - L'0'; + if ('0' <= c && c <= '9') + id += c - '0'; else { assert(c == FFS_VERSION_SEPARATOR); diff --git a/FreeFileSync/Source/version/version.h b/FreeFileSync/Source/version/version.h index 3aefebd8..f34673b8 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.6"; //internal linkage! -const wchar_t FFS_VERSION_SEPARATOR = L'.'; +const char ffsVersion[] = "8.7"; //internal linkage! +const char FFS_VERSION_SEPARATOR = '.'; } #endif diff --git a/wx+/async_task.h b/wx+/async_task.h index 915f9602..7ac03949 100644 --- a/wx+/async_task.h +++ b/wx+/async_task.h @@ -24,6 +24,9 @@ Run a task in an async thread, but process result in GUI event loop 2. schedule async task and synchronous continuation: guiQueue.processAsync(evalAsync, evalOnGui); + +Alternative: use wxWidgets' inter-thread communication (wxEvtHandler::QueueEvent) https://wiki.wxwidgets.org/Inter-Thread_and_Inter-Process_communication + => don't bother, probably too many MT race conditions lurking around */ namespace impl @@ -74,7 +77,7 @@ public: void add(Fun&& evalAsync, Fun2&& evalOnGui) { using ResultType = decltype(evalAsync()); - tasks.push_back(std::make_unique<ConcreteTask<ResultType, Fun2>>(zen::runAsync(std::forward<Fun>(evalAsync)), std::forward<Fun2>(evalOnGui))); + tasks_.push_back(std::make_unique<ConcreteTask<ResultType, Fun2>>(zen::runAsync(std::forward<Fun>(evalAsync)), std::forward<Fun2>(evalOnGui))); } //equivalent to "evalOnGui(evalAsync())" // -> evalAsync: the usual thread-safety requirements apply! @@ -82,14 +85,14 @@ public: void evalResults() //call from gui thread repreatedly { - if (!inRecursion) //prevent implicit recursion, e.g. if we're called from an idle event and spawn another one within the callback below + if (!inRecursion_) //prevent implicit recursion, e.g. if we're called from an idle event and spawn another one within the callback below { - inRecursion = true; - ZEN_ON_SCOPE_EXIT(inRecursion = false); + inRecursion_ = true; + ZEN_ON_SCOPE_EXIT(inRecursion_ = false); std::vector<std::unique_ptr<Task>> readyTasks; //Reentrancy; access to AsyncTasks::add is not protected! => evaluate outside erase_if - erase_if(tasks, [&](std::unique_ptr<Task>& task) + erase_if(tasks_, [&](std::unique_ptr<Task>& task) { if (task->resultReady()) { @@ -104,14 +107,14 @@ public: } } - bool empty() const { return tasks.empty(); } + bool empty() const { return tasks_.empty(); } private: AsyncTasks (const AsyncTasks&) = delete; AsyncTasks& operator=(const AsyncTasks&) = delete; - bool inRecursion = false; - std::vector<std::unique_ptr<Task>> tasks; + bool inRecursion_ = false; + std::vector<std::unique_ptr<Task>> tasks_; }; } @@ -119,27 +122,27 @@ private: class AsyncGuiQueue : private wxEvtHandler { public: - AsyncGuiQueue() { timer.Connect(wxEVT_TIMER, wxEventHandler(AsyncGuiQueue::onTimerEvent), nullptr, this); } + AsyncGuiQueue() { timer_.Connect(wxEVT_TIMER, wxEventHandler(AsyncGuiQueue::onTimerEvent), nullptr, this); } template <class Fun, class Fun2> void processAsync(Fun&& evalAsync, Fun2&& evalOnGui) { - asyncTasks.add(std::forward<Fun >(evalAsync), - std::forward<Fun2>(evalOnGui)); - if (!timer.IsRunning()) - timer.Start(50 /*unit: [ms]*/); + asyncTasks_.add(std::forward<Fun >(evalAsync), + std::forward<Fun2>(evalOnGui)); + if (!timer_.IsRunning()) + timer_.Start(50 /*unit: [ms]*/); } private: void onTimerEvent(wxEvent& event) //schedule and run long-running tasks asynchronously { - asyncTasks.evalResults(); //process results on GUI queue - if (asyncTasks.empty()) - timer.Stop(); + asyncTasks_.evalResults(); //process results on GUI queue + if (asyncTasks_.empty()) + timer_.Stop(); } - impl::AsyncTasks asyncTasks; - wxTimer timer; //don't use wxWidgets' idle handling => repeated idle requests/consumption hogs 100% cpu! + impl::AsyncTasks asyncTasks_; + wxTimer timer_; //don't use wxWidgets' idle handling => repeated idle requests/consumption hogs 100% cpu! }; } diff --git a/wx+/file_drop.h b/wx+/file_drop.h index c3ffe09c..57880ce2 100644 --- a/wx+/file_drop.h +++ b/wx+/file_drop.h @@ -134,6 +134,13 @@ public: private: bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& fileArray) override { + /*Linux, MTP: we get an empty file array + => switching to wxTextDropTarget won't help (much): we'd get the format + mtp://[usb:001,002]/Telefonspeicher/Folder/file.txt + instead of + /run/user/1000/gvfs/mtp:host=%5Busb%3A001%2C002%5D/Telefonspeicher/Folder/file.txt + */ + //wxPoint clientDropPos(x, y) std::vector<Zstring> filePaths; for (const wxString& file : fileArray) diff --git a/wx+/grid.cpp b/wx+/grid.cpp index 13c06704..5d393f08 100644 --- a/wx+/grid.cpp +++ b/wx+/grid.cpp @@ -359,8 +359,18 @@ private: void onPaintEvent(wxPaintEvent& event) { +#ifndef NDEBUG +#ifdef ZEN_WIN + if (runningPaintEvent_ == true) //looks like showing the assert window here would quit the debug session + __debugbreak(); +#else + assert(runningPaintEvent_ == false); //catch unexpected recursion, e.g.: getIconByIndex() seems to run a message loop in rare cases! +#endif + runningPaintEvent_ = true; + ZEN_ON_SCOPE_EXIT(runningPaintEvent_ = false); +#endif //wxAutoBufferedPaintDC dc(this); -> this one happily fucks up for RTL layout by not drawing the first column (x = 0)! - BufferedPaintDC dc(*this, doubleBuffer); + BufferedPaintDC dc(*this, doubleBuffer_); assert(GetSize() == GetClientSize()); @@ -378,7 +388,10 @@ private: void onEraseBackGround(wxEraseEvent& event) {} Grid& parent_; - Opt<wxBitmap> doubleBuffer; + Opt<wxBitmap> doubleBuffer_; +#ifndef NDEBUG + bool runningPaintEvent_ = false; +#endif }; //---------------------------------------------------------------------------------------------------------------- @@ -427,7 +440,7 @@ class Grid::RowLabelWin : public SubWindow public: RowLabelWin(Grid& parent) : SubWindow(parent), - rowHeight(parent.GetCharHeight() + 2 + 1) {} //default height; don't call any functions on "parent" other than those from wxWindow during construction! + rowHeight_(parent.GetCharHeight() + 2 + 1) {} //default height; don't call any functions on "parent" other than those from wxWindow during construction! //2 for some more space, 1 for bottom border (gives 15 + 2 + 1 on Windows, 17 + 2 + 1 on Ubuntu) int getBestWidth(ptrdiff_t rowFrom, ptrdiff_t rowTo) @@ -444,27 +457,27 @@ public: return bestWidth; } - size_t getLogicalHeight() const { return refParent().getRowCount() * rowHeight; } + size_t getLogicalHeight() const { return refParent().getRowCount() * rowHeight_; } ptrdiff_t getRowAtPos(ptrdiff_t posY) const //returns < 0 on invalid input, else row number within: [0, rowCount]; rowCount if out of range { - if (posY >= 0 && rowHeight > 0) + if (posY >= 0 && rowHeight_ > 0) { - const size_t row = posY / rowHeight; + const size_t row = posY / rowHeight_; return std::min(row, refParent().getRowCount()); } return -1; } - int getRowHeight() const { return rowHeight; } //guarantees to return size >= 1 ! - void setRowHeight(int height) { assert(height > 0); rowHeight = std::max(1, height); } + int getRowHeight() const { return rowHeight_; } //guarantees to return size >= 1 ! + void setRowHeight(int height) { assert(height > 0); rowHeight_ = std::max(1, height); } wxRect getRowLabelArea(size_t row) const //returns empty rect if row not found { assert(GetClientAreaOrigin() == wxPoint()); if (row < refParent().getRowCount()) - return wxRect(wxPoint(0, rowHeight * row), - wxSize(GetClientSize().GetWidth(), rowHeight)); + return wxRect(wxPoint(0, rowHeight_ * row), + wxSize(GetClientSize().GetWidth(), rowHeight_)); return wxRect(); } @@ -473,8 +486,8 @@ public: const int yFrom = refParent().CalcUnscrolledPosition(clientRect.GetTopLeft ()).y; const int yTo = refParent().CalcUnscrolledPosition(clientRect.GetBottomRight()).y; - return std::make_pair(std::max(yFrom / rowHeight, 0), - std::min<ptrdiff_t>((yTo / rowHeight) + 1, refParent().getRowCount())); + return std::make_pair(std::max(yFrom / rowHeight_, 0), + std::min<ptrdiff_t>((yTo / rowHeight_) + 1, refParent().getRowCount())); } private: @@ -552,7 +565,7 @@ private: void onMouseMovement(wxMouseEvent& event) override { refParent().redirectRowLabelEvent(event); } void onMouseLeftUp (wxMouseEvent& event) override { refParent().redirectRowLabelEvent(event); } - int rowHeight; + int rowHeight_; }; @@ -670,21 +683,21 @@ private: { if (auto dataView = refParent().getDataProvider()) { - const bool isHighlighted = activeResizing ? col == activeResizing ->getColumn () : //highlight column on mouse-over - activeClickOrMove ? col == activeClickOrMove->getColumnFrom() : - highlightCol ? col == *highlightCol : + const bool isHighlighted = activeResizing_ ? col == activeResizing_ ->getColumn () : //highlight_ column on mouse-over + activeClickOrMove_ ? col == activeClickOrMove_->getColumnFrom() : + highlightCol_ ? col == *highlightCol_ : false; RecursiveDcClipper clip(dc, rect); dataView->renderColumnLabel(refParent(), dc, rect, colType, isHighlighted); //draw move target location - if (refParent().allowColumnMove) - if (activeClickOrMove && activeClickOrMove->isRealMove()) + if (refParent().allowColumnMove_) + if (activeClickOrMove_ && activeClickOrMove_->isRealMove()) { - if (col + 1 == activeClickOrMove->refColumnTo()) //handle pos 1, 2, .. up to "at end" position + if (col + 1 == activeClickOrMove_->refColumnTo()) //handle pos 1, 2, .. up to "at end" position dc.GradientFillLinear(wxRect(rect.GetTopRight(), rect.GetBottomRight() + wxPoint(-2, 0)), getColorLabelGradientFrom(), *wxBLUE, wxSOUTH); - else if (col == activeClickOrMove->refColumnTo() && col == 0) //pos 0 + else if (col == activeClickOrMove_->refColumnTo() && col == 0) //pos 0 dc.GradientFillLinear(wxRect(rect.GetTopLeft(), rect.GetBottomLeft() + wxPoint(2, 0)), getColorLabelGradientFrom(), *wxBLUE, wxSOUTH); } } @@ -695,8 +708,8 @@ private: if (FindFocus() != &refParent().getMainWin()) refParent().getMainWin().SetFocus(); - activeResizing.reset(); - activeClickOrMove.reset(); + activeResizing_.reset(); + activeClickOrMove_.reset(); if (Opt<ColAction> action = refParent().clientPosToColumnAction(event.GetPosition())) { @@ -704,26 +717,26 @@ private: { if (!event.LeftDClick()) //double-clicks never seem to arrive here; why is this checked at all??? if (Opt<int> colWidth = refParent().getColWidth(action->col)) - activeResizing = std::make_unique<ColumnResizing>(*this, action->col, *colWidth, event.GetPosition().x); + activeResizing_ = std::make_unique<ColumnResizing>(*this, action->col, *colWidth, event.GetPosition().x); } else //a move or single click - activeClickOrMove = std::make_unique<ColumnMove>(*this, action->col, event.GetPosition().x); + activeClickOrMove_ = std::make_unique<ColumnMove>(*this, action->col, event.GetPosition().x); } event.Skip(); } void onMouseLeftUp(wxMouseEvent& event) override { - activeResizing.reset(); //nothing else to do, actual work done by onMouseMovement() + activeResizing_.reset(); //nothing else to do, actual work done by onMouseMovement() - if (activeClickOrMove) + if (activeClickOrMove_) { - if (activeClickOrMove->isRealMove()) + if (activeClickOrMove_->isRealMove()) { - if (refParent().allowColumnMove) + if (refParent().allowColumnMove_) { - const size_t colFrom = activeClickOrMove->getColumnFrom(); - size_t colTo = activeClickOrMove->refColumnTo(); + const size_t colFrom = activeClickOrMove_->getColumnFrom(); + size_t colTo = activeClickOrMove_->refColumnTo(); if (colTo > colFrom) //simulate "colFrom" deletion --colTo; @@ -733,10 +746,10 @@ private: } else //notify single label click { - if (const Opt<ColumnType> colType = refParent().colToType(activeClickOrMove->getColumnFrom())) + if (const Opt<ColumnType> colType = refParent().colToType(activeClickOrMove_->getColumnFrom())) sendEventNow(GridLabelClickEvent(EVENT_GRID_COL_LABEL_MOUSE_LEFT, event, *colType)); } - activeClickOrMove.reset(); + activeClickOrMove_.reset(); } refParent().updateWindowSizes(); //looks strange if done during onMouseMovement() @@ -746,8 +759,8 @@ private: void onMouseCaptureLost(wxMouseCaptureLostEvent& event) override { - activeResizing.reset(); - activeClickOrMove.reset(); + activeResizing_.reset(); + activeClickOrMove_.reset(); Refresh(); //event.Skip(); -> we DID handle it! } @@ -770,10 +783,10 @@ private: void onMouseMovement(wxMouseEvent& event) override { - if (activeResizing) + if (activeResizing_) { - const auto col = activeResizing->getColumn(); - const int newWidth = activeResizing->getStartWidth() + event.GetPosition().x - activeResizing->getStartPosX(); + const auto col = activeResizing_->getColumn(); + const int newWidth = activeResizing_->getStartWidth() + event.GetPosition().x - activeResizing_->getStartPosX(); //set width tentatively refParent().setColumnWidth(newWidth, col, ALLOW_GRID_EVENT); @@ -785,23 +798,23 @@ private: refParent().Refresh(); //refresh columns on main grid as well! } - else if (activeClickOrMove) + else if (activeClickOrMove_) { const int clientPosX = event.GetPosition().x; - if (std::abs(clientPosX - activeClickOrMove->getStartPosX()) > COLUMN_MOVE_DELAY) //real move (not a single click) + if (std::abs(clientPosX - activeClickOrMove_->getStartPosX()) > COLUMN_MOVE_DELAY) //real move (not a single click) { - activeClickOrMove->setRealMove(); + activeClickOrMove_->setRealMove(); const ptrdiff_t col = refParent().clientPosToMoveTargetColumn(event.GetPosition()); if (col >= 0) - activeClickOrMove->refColumnTo() = col; + activeClickOrMove_->refColumnTo() = col; } } else { if (const Opt<ColAction> action = refParent().clientPosToColumnAction(event.GetPosition())) { - highlightCol = action->col; + highlightCol_ = action->col; if (action->wantResize) SetCursor(wxCURSOR_SIZEWE); //set window-local only! :) @@ -810,7 +823,7 @@ private: } else { - highlightCol = NoValue(); + highlightCol_ = NoValue(); SetCursor(*wxSTANDARD_CURSOR); } } @@ -832,7 +845,7 @@ private: void onLeaveWindow(wxMouseEvent& event) override { - highlightCol = NoValue(); //wxEVT_LEAVE_WINDOW does not respect mouse capture! -> however highlight is drawn unconditionally during move/resize! + highlightCol_ = NoValue(); //wxEVT_LEAVE_WINDOW does not respect mouse capture! -> however highlight_ is drawn unconditionally during move/resize! Refresh(); event.Skip(); } @@ -853,9 +866,9 @@ private: event.Skip(); } - std::unique_ptr<ColumnResizing> activeResizing; - std::unique_ptr<ColumnMove> activeClickOrMove; - Opt<size_t> highlightCol; //column during mouse-over + std::unique_ptr<ColumnResizing> activeResizing_; + std::unique_ptr<ColumnMove> activeClickOrMove_; + Opt<size_t> highlightCol_; //column during mouse-over }; //---------------------------------------------------------------------------------------------------------------- @@ -877,16 +890,16 @@ public: Connect(EVENT_GRID_HAS_SCROLLED, wxEventHandler(MainWin::onRequestWindowUpdate), nullptr, this); } - ~MainWin() { assert(!gridUpdatePending); } + ~MainWin() { assert(!gridUpdatePending_); } - size_t getCursor() const { return cursorRow; } - size_t getAnchor() const { return selectionAnchor; } + size_t getCursor() const { return cursorRow_; } + size_t getAnchor() const { return selectionAnchor_; } void setCursor(size_t newCursorRow, size_t newAnchorRow) { - cursorRow = newCursorRow; - selectionAnchor = newAnchorRow; - activeSelection.reset(); //e.g. user might search with F3 while holding down left mouse button + cursorRow_ = newCursorRow; + selectionAnchor_ = newAnchorRow; + activeSelection_.reset(); //e.g. user might search with F3 while holding down left mouse button } private: @@ -948,25 +961,25 @@ private: HoverArea getRowHoverToDraw(ptrdiff_t row) const { - if (activeSelection) + if (activeSelection_) { - if (activeSelection->getFirstClick().row_ == row) - return activeSelection->getFirstClick().hoverArea_; + if (activeSelection_->getFirstClick().row_ == row) + return activeSelection_->getFirstClick().hoverArea_; } - else if (highlight.row == row) - return highlight.rowHover; + else if (highlight_.row == row) + return highlight_.rowHover; return HoverArea::NONE; } bool drawAsSelected(size_t row) const { - if (activeSelection) //check if user is currently selecting with mouse + if (activeSelection_) //check if user is currently selecting with mouse { - const size_t rowFrom = std::min(activeSelection->getStartRow(), activeSelection->getCurrentRow()); - const size_t rowTo = std::max(activeSelection->getStartRow(), activeSelection->getCurrentRow()); + const size_t rowFrom = std::min(activeSelection_->getStartRow(), activeSelection_->getCurrentRow()); + const size_t rowTo = std::max(activeSelection_->getStartRow(), activeSelection_->getCurrentRow()); if (rowFrom <= row && row <= rowTo) - return activeSelection->isPositiveSelect(); //overwrite default + return activeSelection_->isPositiveSelect(); //overwrite default } return refParent().isSelected(row); } @@ -1009,15 +1022,15 @@ private: if (!event.RightDown() || !refParent().isSelected(row)) //do NOT start a new selection if user right-clicks on a selected area! { if (event.ControlDown()) - activeSelection = std::make_unique<MouseSelection>(*this, row, !refParent().isSelected(row), mouseEvent); + activeSelection_ = std::make_unique<MouseSelection>(*this, row, !refParent().isSelected(row), mouseEvent); else if (event.ShiftDown()) { - activeSelection = std::make_unique<MouseSelection>(*this, selectionAnchor, true, mouseEvent); + activeSelection_ = std::make_unique<MouseSelection>(*this, selectionAnchor_, true, mouseEvent); refParent().clearSelection(ALLOW_GRID_EVENT); } else { - activeSelection = std::make_unique<MouseSelection>(*this, row, true, mouseEvent); + activeSelection_ = std::make_unique<MouseSelection>(*this, row, true, mouseEvent); refParent().clearSelection(ALLOW_GRID_EVENT); } } @@ -1032,31 +1045,31 @@ private: void onMouseUp(wxMouseEvent& event) { - if (activeSelection) + if (activeSelection_) { const size_t rowCount = refParent().getRowCount(); if (rowCount > 0) { - if (activeSelection->getCurrentRow() < rowCount) + if (activeSelection_->getCurrentRow() < rowCount) { - cursorRow = activeSelection->getCurrentRow(); - selectionAnchor = activeSelection->getStartRow(); //allowed to be "out of range" + cursorRow_ = activeSelection_->getCurrentRow(); + selectionAnchor_ = activeSelection_->getStartRow(); //allowed to be "out of range" } - else if (activeSelection->getStartRow() < rowCount) //don't change cursor if "to" and "from" are out of range + else if (activeSelection_->getStartRow() < rowCount) //don't change cursor if "to" and "from" are out of range { - cursorRow = rowCount - 1; - selectionAnchor = activeSelection->getStartRow(); //allowed to be "out of range" + cursorRow_ = rowCount - 1; + selectionAnchor_ = activeSelection_->getStartRow(); //allowed to be "out of range" } else //total selection "out of range" - selectionAnchor = cursorRow; + selectionAnchor_ = cursorRow_; } //slight deviation from Explorer: change cursor while dragging mouse! -> unify behavior with shift + direction keys - refParent().selectRangeAndNotify(activeSelection->getStartRow (), //from - activeSelection->getCurrentRow(), //to - activeSelection->isPositiveSelect(), - &activeSelection->getFirstClick()); - activeSelection.reset(); + refParent().selectRangeAndNotify(activeSelection_->getStartRow (), //from + activeSelection_->getCurrentRow(), //to + activeSelection_->isPositiveSelect(), + &activeSelection_->getFirstClick()); + activeSelection_.reset(); } if (auto prov = refParent().getDataProvider()) @@ -1070,7 +1083,7 @@ private: sendEventNow(GridClickEvent(event.RightUp() ? EVENT_GRID_MOUSE_RIGHT_UP : EVENT_GRID_MOUSE_LEFT_UP, event, row, rowHover)); } - //update highlight and tooltip: on OS X no mouse movement event is generated after a mouse button click (unlike on Windows) + //update highlight_ and tooltip: on OS X no mouse movement event is generated after a mouse button click (unlike on Windows) event.SetPosition(ScreenToClient(wxGetMousePosition())); //mouse position may have changed within above callbacks (e.g. context menu was shown)! onMouseMovement(event); @@ -1080,8 +1093,8 @@ private: void onMouseCaptureLost(wxMouseCaptureLostEvent& event) override { - activeSelection.reset(); - highlight.row = -1; + activeSelection_.reset(); + highlight_.row = -1; Refresh(); //event.Skip(); -> we DID handle it! } @@ -1104,14 +1117,14 @@ private: }(); setToolTip(toolTip); //show even during mouse selection! - if (activeSelection) - activeSelection->evalMousePos(); //call on both mouse movement + timer event! + if (activeSelection_) + activeSelection_->evalMousePos(); //call on both mouse movement + timer event! else { - refreshHighlight(highlight); - highlight.row = row; - highlight.rowHover = rowHover; - refreshHighlight(highlight); //multiple Refresh() calls are condensed into single one! + refreshHighlight(highlight_); + highlight_.row = row; + highlight_.rowHover = rowHover; + refreshHighlight(highlight_); //multiple Refresh() calls are condensed into single one! } } event.Skip(); @@ -1119,10 +1132,10 @@ private: void onLeaveWindow(wxMouseEvent& event) override //wxEVT_LEAVE_WINDOW does not respect mouse capture! { - if (!activeSelection) + if (!activeSelection_) { - refreshHighlight(highlight); - highlight.row = -1; + refreshHighlight(highlight_); + highlight_.row = -1; } event.Skip(); @@ -1138,8 +1151,8 @@ private: wnd_(wnd), rowStart_(rowStart), rowCurrent_(rowStart), positiveSelect_(positiveSelect), firstClick_(firstClick) { wnd_.CaptureMouse(); - timer.Connect(wxEVT_TIMER, wxEventHandler(MouseSelection::onTimer), nullptr, this); - timer.Start(100); //timer interval in ms + timer_.Connect(wxEVT_TIMER, wxEventHandler(MouseSelection::onTimer), nullptr, this); + timer_.Start(100); //timer interval in ms evalMousePos(); } ~MouseSelection() { if (wnd_.HasCapture()) wnd_.ReleaseMouse(); } @@ -1152,8 +1165,8 @@ private: void evalMousePos() { const auto now = std::chrono::steady_clock::now(); - const double deltaSecs = std::chrono::duration<double>(now - lastEvalTime).count(); //unit: [sec] - lastEvalTime = now; + const double deltaSecs = std::chrono::duration<double>(now - lastEvalTime_).count(); //unit: [sec] + lastEvalTime_ = now; const wxPoint clientPos = wnd_.ScreenToClient(wxGetMousePosition()); const wxSize clientSize = wnd_.GetClientSize(); @@ -1182,14 +1195,14 @@ private: toScroll = 0; }; - autoScroll(overlapPixX, toScrollX); - autoScroll(overlapPixY, toScrollY); + autoScroll(overlapPixX, toScrollX_); + autoScroll(overlapPixY, toScrollY_); - if (static_cast<int>(toScrollX) != 0 || static_cast<int>(toScrollY) != 0) + if (static_cast<int>(toScrollX_) != 0 || static_cast<int>(toScrollY_) != 0) { - wnd_.refParent().scrollDelta(static_cast<int>(toScrollX), static_cast<int>(toScrollY)); // - toScrollX -= static_cast<int>(toScrollX); //rounds down for positive numbers, up for negative, - toScrollY -= static_cast<int>(toScrollY); //exactly what we want + wnd_.refParent().scrollDelta(static_cast<int>(toScrollX_), static_cast<int>(toScrollY_)); // + toScrollX_ -= static_cast<int>(toScrollX_); //rounds down for positive numbers, up for negative, + toScrollY_ -= static_cast<int>(toScrollY_); //exactly what we want } //select current row *after* scrolling @@ -1214,10 +1227,10 @@ private: ptrdiff_t rowCurrent_; const bool positiveSelect_; const GridClickEvent firstClick_; - wxTimer timer; - double toScrollX = 0; //count outstanding scroll unit fractions while dragging mouse - double toScrollY = 0; // - std::chrono::steady_clock::time_point lastEvalTime = std::chrono::steady_clock::now(); + wxTimer timer_; + double toScrollX_ = 0; //count outstanding scroll unit fractions while dragging mouse + double toScrollY_ = 0; // + std::chrono::steady_clock::time_point lastEvalTime_ = std::chrono::steady_clock::now(); }; struct MouseHighlight @@ -1236,12 +1249,12 @@ private: //which *first* calls us, MainWin::ScrollWindow(), and *then* internally updates m_yScrollPosition //=> we cannot use CalcUnscrolledPosition() here which gives the wrong/outdated value!!! //=> we need to update asynchronously: - //=> don't use plain async event => severe performance issues on wxGTK! + //=> don't send async event repeatedly => severe performance issues on wxGTK! //=> can't use idle event neither: too few idle events on Windows, e.g. NO idle events while mouse drag-scrolling! //=> solution: send single async event at most! - if (!gridUpdatePending) //without guarding, the number of outstanding async events can become very high during scrolling!! test case: Ubuntu: 170; Windows: 20 + if (!gridUpdatePending_) //without guarding, the number of outstanding async events can become very high during scrolling!! test case: Ubuntu: 170; Windows: 20 { - gridUpdatePending = true; + gridUpdatePending_ = true; wxCommandEvent scrollEvent(EVENT_GRID_HAS_SCROLLED); AddPendingEvent(scrollEvent); //asynchronously call updateAfterScroll() } @@ -1249,8 +1262,8 @@ private: void onRequestWindowUpdate(wxEvent& event) { - assert(gridUpdatePending); - ZEN_ON_SCOPE_EXIT(gridUpdatePending = false); + assert(gridUpdatePending_); + ZEN_ON_SCOPE_EXIT(gridUpdatePending_ = false); refParent().updateWindowSizes(false); //row label width has changed -> do *not* update scrollbars: recursion on wxGTK! -> still a problem, now that we're called async?? rowLabelWin_.Update(); //update while dragging scroll thumb @@ -1267,19 +1280,19 @@ private: void refreshHighlight(const MouseHighlight& hl) { const ptrdiff_t rowCount = refParent().getRowCount(); - if (0 <= hl.row && hl.row < rowCount && hl.rowHover != HoverArea::NONE) //no highlight? => NOP! + if (0 <= hl.row && hl.row < rowCount && hl.rowHover != HoverArea::NONE) //no highlight_? => NOP! refreshRow(hl.row); } RowLabelWin& rowLabelWin_; ColLabelWin& colLabelWin_; - std::unique_ptr<MouseSelection> activeSelection; //bound while user is selecting with mouse - MouseHighlight highlight; //current mouse highlight (superseeded by activeSelection if available) + std::unique_ptr<MouseSelection> activeSelection_; //bound while user is selecting with mouse + MouseHighlight highlight_; //current mouse highlight_ (superseeded by activeSelection_ if available) - ptrdiff_t cursorRow = 0; - size_t selectionAnchor = 0; - bool gridUpdatePending = false; + ptrdiff_t cursorRow_ = 0; + size_t selectionAnchor_ = 0; + bool gridUpdatePending_ = false; }; //---------------------------------------------------------------------------------------------------------------- @@ -1353,7 +1366,7 @@ void Grid::updateWindowSizes(bool updateScrollbar) const ptrdiff_t logicalHeight = rowLabelWin_->getLogicalHeight(); // int rowLabelWidth = 0; - if (drawRowLabel && logicalHeight > 0) + if (drawRowLabel_ && logicalHeight > 0) { ptrdiff_t yFrom = CalcUnscrolledPosition(wxPoint(0, 0)).y; ptrdiff_t yTo = CalcUnscrolledPosition(wxPoint(0, mainWinHeightGross - 1)).y ; @@ -1406,8 +1419,8 @@ void Grid::updateWindowSizes(bool updateScrollbar) if (logicalHeight <= mainWinHeightGross && getColWidthsSum(mainWinWidthGross) <= mainWinWidthGross && //this special case needs to be considered *only* when both scrollbars are flexible: - showScrollbarX == SB_SHOW_AUTOMATIC && - showScrollbarY == SB_SHOW_AUTOMATIC) + showScrollbarX_ == SB_SHOW_AUTOMATIC && + showScrollbarY_ == SB_SHOW_AUTOMATIC) setScrollbars2(0, 0); //no scrollbars required at all! -> wxScrolledWindow requires active help to detect this special case! else { @@ -1461,7 +1474,7 @@ wxSize Grid::GetSizeAvailableForScrollTarget(const wxSize& size) const ptrdiff_t logicalHeight = rowLabelWin_->getLogicalHeight(); // int rowLabelWidth = 0; - if (drawRowLabel && logicalHeight > 0) + if (drawRowLabel_ && logicalHeight > 0) { ptrdiff_t yFrom = CalcUnscrolledPosition(wxPoint(0, 0)).y; ptrdiff_t yTo = CalcUnscrolledPosition(wxPoint(0, mainWinHeightGross - 1)).y ; @@ -1624,14 +1637,14 @@ void Grid::setColumnLabelHeight(int height) void Grid::showRowLabel(bool show) { - drawRowLabel = show; + drawRowLabel_ = show; updateWindowSizes(); } void Grid::selectAllRows(GridEventPolicy rangeEventPolicy) { - selection.selectAll(); + selection_.selectAll(); mainWin_->Refresh(); if (rangeEventPolicy == ALLOW_GRID_EVENT) //notify event, even if we're not triggered by user interaction @@ -1645,7 +1658,7 @@ void Grid::selectAllRows(GridEventPolicy rangeEventPolicy) void Grid::clearSelection(GridEventPolicy rangeEventPolicy) { - selection.clear(); + selection_.clear(); mainWin_->Refresh(); if (rangeEventPolicy == ALLOW_GRID_EVENT) //notify event, even if we're not triggered by user interaction @@ -1694,14 +1707,14 @@ size_t Grid::getRowCount() const void Grid::Refresh(bool eraseBackground, const wxRect* rect) { const size_t rowCountNew = getRowCount(); - if (rowCountOld != rowCountNew) + if (rowCountOld_ != rowCountNew) { - rowCountOld = rowCountNew; + rowCountOld_ = rowCountNew; updateWindowSizes(); } - if (selection.maxSize() != rowCountNew) //clear selection only when needed (consider setSelectedRows()) - selection.init(rowCountNew); + if (selection_.maxSize() != rowCountNew) //clear selection only when needed (consider setSelectedRows()) + selection_.init(rowCountNew); wxScrolledWindow::Refresh(eraseBackground, rect); } @@ -1718,7 +1731,7 @@ void Grid::setRowHeight(int height) void Grid::setColumnConfig(const std::vector<Grid::ColumnAttribute>& attr) { //hold ownership of non-visible columns - oldColAttributes = attr; + oldColAttributes_ = attr; std::vector<VisibleColumn> visCols; for (const ColumnAttribute& ca : attr) @@ -1729,7 +1742,7 @@ void Grid::setColumnConfig(const std::vector<Grid::ColumnAttribute>& attr) } //"ownership" of visible columns is now within Grid - visibleCols = visCols; + visibleCols_ = visCols; updateWindowSizes(); Refresh(); @@ -1739,10 +1752,10 @@ void Grid::setColumnConfig(const std::vector<Grid::ColumnAttribute>& attr) std::vector<Grid::ColumnAttribute> Grid::getColumnConfig() const { //get non-visible columns (+ outdated visible ones) - std::vector<ColumnAttribute> output = oldColAttributes; + std::vector<ColumnAttribute> output = oldColAttributes_; - auto iterVcols = visibleCols.begin(); - auto iterVcolsend = visibleCols.end(); + auto iterVcols = visibleCols_.begin(); + auto iterVcolsend = visibleCols_.end(); //update visible columns but keep order of non-visible ones! for (ColumnAttribute& ca : output) @@ -1766,11 +1779,11 @@ std::vector<Grid::ColumnAttribute> Grid::getColumnConfig() const void Grid::showScrollBars(Grid::ScrollBarStatus horizontal, Grid::ScrollBarStatus vertical) { - if (showScrollbarX == horizontal && - showScrollbarY == vertical) return; //support polling! + if (showScrollbarX_ == horizontal && + showScrollbarY_ == vertical) return; //support polling! - showScrollbarX = horizontal; - showScrollbarY = vertical; + showScrollbarX_ = horizontal; + showScrollbarY_ = vertical; #if defined ZEN_WIN || defined ZEN_MAC //handled by Grid::SetScrollbar @@ -1812,9 +1825,9 @@ void Grid::SetScrollbar(int orientation, int position, int thumbSize, int range, ScrollBarStatus sbStatus = SB_SHOW_AUTOMATIC; if (orientation == wxHORIZONTAL) - sbStatus = showScrollbarX; + sbStatus = showScrollbarX_; else if (orientation == wxVERTICAL) - sbStatus = showScrollbarY; + sbStatus = showScrollbarY_; else assert(false); @@ -1851,7 +1864,7 @@ Opt<Grid::ColAction> Grid::clientPosToColumnAction(const wxPoint& pos) const const int absPosX = CalcUnscrolledPosition(pos).x; if (absPosX >= 0) { - const int resizeTolerance = allowColumnResize ? COLUMN_RESIZE_TOLERANCE : 0; + const int resizeTolerance = allowColumnResize_ ? COLUMN_RESIZE_TOLERANCE : 0; std::vector<ColumnWidth> absWidths = getColWidths(); //resolve stretched widths int accuWidth = 0; @@ -1880,13 +1893,13 @@ Opt<Grid::ColAction> Grid::clientPosToColumnAction(const wxPoint& pos) const void Grid::moveColumn(size_t colFrom, size_t colTo) { - if (colFrom < visibleCols.size() && - colTo < visibleCols.size() && + if (colFrom < visibleCols_.size() && + colTo < visibleCols_.size() && colTo != colFrom) { - const VisibleColumn colAtt = visibleCols[colFrom]; - visibleCols.erase (visibleCols.begin() + colFrom); - visibleCols.insert(visibleCols.begin() + colTo, colAtt); + const VisibleColumn colAtt = visibleCols_[colFrom]; + visibleCols_.erase (visibleCols_.begin() + colFrom); + visibleCols_.insert(visibleCols_.begin() + colTo, colAtt); } } @@ -1912,8 +1925,8 @@ ptrdiff_t Grid::clientPosToMoveTargetColumn(const wxPoint& pos) const ColumnType Grid::colToType(size_t col) const { - if (col < visibleCols.size()) - return visibleCols[col].type_; + if (col < visibleCols_.size()) + return visibleCols_[col].type_; return ColumnType::NONE; } @@ -1976,7 +1989,7 @@ void Grid::setGridCursor(size_t row) mainWin_->setCursor(row, row); makeRowVisible(row); - selection.clear(); //clear selection, do NOT fire event + selection_.clear(); //clear selection, do NOT fire event selectRangeAndNotify(row, row, true /*positive*/, nullptr /*mouseInitiated*/); //set new selection + fire event mainWin_->Refresh(); @@ -1991,7 +2004,7 @@ void Grid::selectWithCursor(ptrdiff_t row) mainWin_->setCursor(row, anchorRow); makeRowVisible(row); - selection.clear(); //clear selection, do NOT fire event + selection_.clear(); //clear selection, do NOT fire event selectRangeAndNotify(anchorRow, row, true /*positive*/, nullptr /*mouseInitiated*/); //set new selection + fire event mainWin_->Refresh(); @@ -2050,7 +2063,7 @@ void Grid::selectRangeAndNotify(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool positiv numeric::clamp<ptrdiff_t>(rowFirst, 0, rowCount); numeric::clamp<ptrdiff_t>(rowLast, 0, rowCount); - selection.selectRange(rowFirst, rowLast, positive); + selection_.selectRange(rowFirst, rowLast, positive); //notify event GridRangeSelectEvent selectionEvent(rowFirst, rowLast, positive, mouseInitiated); @@ -2101,9 +2114,9 @@ size_t Grid::getGridCursor() const int Grid::getBestColumnSize(size_t col) const { - if (dataView_ && col < visibleCols.size()) + if (dataView_ && col < visibleCols_.size()) { - const ColumnType type = visibleCols[col].type_; + const ColumnType type = visibleCols_[col].type_; wxClientDC dc(mainWin_); dc.SetFont(mainWin_->GetFont()); //harmonize with MainWin::render() @@ -2122,12 +2135,12 @@ int Grid::getBestColumnSize(size_t col) const void Grid::setColumnWidth(int width, size_t col, GridEventPolicy columnResizeEventPolicy, bool notifyAsync) { - if (col < visibleCols.size()) + if (col < visibleCols_.size()) { - VisibleColumn& vcRs = visibleCols[col]; + VisibleColumn& vcRs = visibleCols_[col]; const std::vector<int> stretchedWidths = getColStretchedWidths(mainWin_->GetClientSize().GetWidth()); - if (stretchedWidths.size() != visibleCols.size()) + if (stretchedWidths.size() != visibleCols_.size()) { assert(false); return; @@ -2146,9 +2159,9 @@ void Grid::setColumnWidth(int width, size_t col, GridEventPolicy columnResizeEve //2. shrink main window width so that horizontal scrollbars are shown despite the streched column //3. shrink a fixed-size column so that the scrollbars vanish and columns cover full width again //4. now verify that the stretched column is resizing immediately if main window is enlarged again - for (size_t col2 = 0; col2 < visibleCols.size(); ++col2) - if (visibleCols[col2].stretch_ > 0) //normalize stretched columns only - visibleCols[col2].offset_ = std::max(visibleCols[col2].offset_, COLUMN_MIN_WIDTH - stretchedWidths[col2]); + for (size_t col2 = 0; col2 < visibleCols_.size(); ++col2) + if (visibleCols_[col2].stretch_ > 0) //normalize stretched columns only + visibleCols_[col2].offset_ = std::max(visibleCols_[col2].offset_, COLUMN_MIN_WIDTH - stretchedWidths[col2]); if (columnResizeEventPolicy == ALLOW_GRID_EVENT) { @@ -2169,9 +2182,9 @@ void Grid::setColumnWidth(int width, size_t col, GridEventPolicy columnResizeEve void Grid::autoSizeColumns(GridEventPolicy columnResizeEventPolicy) { - if (allowColumnResize) + if (allowColumnResize_) { - for (size_t col = 0; col < visibleCols.size(); ++col) + for (size_t col = 0; col < visibleCols_.size(); ++col) { const int bestWidth = getBestColumnSize(col); //return -1 on error if (bestWidth >= 0) @@ -2188,7 +2201,7 @@ std::vector<int> Grid::getColStretchedWidths(int clientWidth) const //final widt assert(clientWidth >= 0); clientWidth = std::max(clientWidth, 0); int stretchTotal = 0; - for (const VisibleColumn& vc : visibleCols) + for (const VisibleColumn& vc : visibleCols_) { assert(vc.stretch_ >= 0); stretchTotal += vc.stretch_; @@ -2199,28 +2212,27 @@ std::vector<int> Grid::getColStretchedWidths(int clientWidth) const //final widt std::vector<int> output; if (stretchTotal <= 0) - output.resize(visibleCols.size()); //fill with zeros + output.resize(visibleCols_.size()); //fill with zeros else - for (const VisibleColumn& vc : visibleCols) + { + for (const VisibleColumn& vc : visibleCols_) { const int width = clientWidth * vc.stretch_ / stretchTotal; //rounds down! output.push_back(width); remainingWidth -= width; } - //distribute *all* of clientWidth: should suffice to enlarge the first few stretched columns; no need to minimize total absolute error of distribution - if (stretchTotal > 0) + //distribute *all* of clientWidth: should suffice to enlarge the first few stretched columns; no need to minimize total absolute error of distribution if (remainingWidth > 0) - { - for (size_t col2 = 0; col2 < visibleCols.size(); ++col2) - if (visibleCols[col2].stretch_ > 0) + for (size_t col2 = 0; col2 < visibleCols_.size(); ++col2) + if (visibleCols_[col2].stretch_ > 0) { ++output[col2]; if (--remainingWidth == 0) - return output; + break; } - assert(false); - } + assert(remainingWidth == 0); + } return output; } @@ -2234,12 +2246,12 @@ std::vector<Grid::ColumnWidth> Grid::getColWidths() const std::vector<Grid::ColumnWidth> Grid::getColWidths(int mainWinWidth) const //evaluate stretched columns { const std::vector<int> stretchedWidths = getColStretchedWidths(mainWinWidth); - assert(stretchedWidths.size() == visibleCols.size()); + assert(stretchedWidths.size() == visibleCols_.size()); std::vector<ColumnWidth> output; - for (size_t col2 = 0; col2 < visibleCols.size(); ++col2) + for (size_t col2 = 0; col2 < visibleCols_.size(); ++col2) { - const auto& vc = visibleCols[col2]; + const auto& vc = visibleCols_[col2]; int width = stretchedWidths[col2] + vc.offset_; if (vc.stretch_ > 0) @@ -178,7 +178,7 @@ public: //alternative until wxScrollHelper::ShowScrollbars() becomes available in wxWidgets 2.9 void showScrollBars(ScrollBarStatus horizontal, ScrollBarStatus vertical); - std::vector<size_t> getSelectedRows() const { return selection.get(); } + std::vector<size_t> getSelectedRows() const { return selection_.get(); } void selectAllRows (GridEventPolicy rangeEventPolicy); void clearSelection(GridEventPolicy rangeEventPolicy); //turn off range selection event when calling this function in an event handler to avoid recursion! @@ -202,8 +202,8 @@ public: void refreshCell(size_t row, ColumnType colType); - void enableColumnMove (bool value) { allowColumnMove = value; } - void enableColumnResize(bool value) { allowColumnResize = value; } + void enableColumnMove (bool value) { allowColumnMove_ = value; } + void enableColumnResize(bool value) { allowColumnResize_ = value; } void setGridCursor(size_t row); //set + show + select cursor (+ emit range selection event) size_t getGridCursor() const; //returns row @@ -301,7 +301,7 @@ private: }; std::vector<ColumnWidth> getColWidths() const; // std::vector<ColumnWidth> getColWidths(int mainWinWidth) const; //evaluate stretched columns - int getColWidthsSum(int mainWinWidth) const; + int getColWidthsSum(int mainWinWidth) const; std::vector<int> getColStretchedWidths(int clientWidth) const; //final width = (normalized) (stretchedWidth + offset) Opt<int> getColWidth(size_t col) const @@ -318,7 +318,7 @@ private: void selectRangeAndNotify(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool positive, const GridClickEvent* mouseInitiated); //select inclusive range [rowFrom, rowTo] + notify event! - bool isSelected(size_t row) const { return selection.isSelected(row); } + bool isSelected(size_t row) const { return selection_.isSelected(row); } struct ColAction { @@ -345,21 +345,21 @@ private: ColLabelWin* colLabelWin_; MainWin* mainWin_; - ScrollBarStatus showScrollbarX = SB_SHOW_AUTOMATIC; - ScrollBarStatus showScrollbarY = SB_SHOW_AUTOMATIC; + ScrollBarStatus showScrollbarX_ = SB_SHOW_AUTOMATIC; + ScrollBarStatus showScrollbarY_ = SB_SHOW_AUTOMATIC; int colLabelHeight_ = 0; - bool drawRowLabel = true; + bool drawRowLabel_ = true; std::shared_ptr<GridData> dataView_; - Selection selection; - bool allowColumnMove = true; - bool allowColumnResize = true; + Selection selection_; + bool allowColumnMove_ = true; + bool allowColumnResize_ = true; - std::vector<VisibleColumn> visibleCols; //individual widths, type and total column count - std::vector<ColumnAttribute> oldColAttributes; //visible + nonvisible columns; use for conversion in setColumnConfig()/getColumnConfig() *only*! + std::vector<VisibleColumn> visibleCols_; //individual widths, type and total column count + std::vector<ColumnAttribute> oldColAttributes_; //visible + nonvisible columns; use for conversion in setColumnConfig()/getColumnConfig() *only*! - size_t rowCountOld = 0; //at the time of last Grid::Refresh() + size_t rowCountOld_ = 0; //at the time of last Grid::Refresh() }; } diff --git a/wx+/http.cpp b/wx+/http.cpp index ce3de482..3428546e 100644 --- a/wx+/http.cpp +++ b/wx+/http.cpp @@ -31,186 +31,259 @@ namespace #endif #endif +struct UrlRedirectError +{ + UrlRedirectError(const std::wstring& url) : newUrl(url) {} + std::wstring newUrl; +}; +} -std::string sendHttpRequestImpl(const std::wstring& url, //throw SysError - const std::wstring& userAgent, - const std::string* postParams, //issue POST if bound, GET otherwise - int level = 0) + +class HttpInputStream::Impl { - 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); +public: + Impl(const std::wstring& url, const std::wstring& userAgent, //throw SysError, UrlRedirectError + const std::string* postParams) //issue POST if bound, GET otherwise + { + ZEN_ON_SCOPE_FAIL( cleanup(); /*destructor call would lead to member double clean-up!!!*/ ); + + assert(!startsWith(makeUpperCopy(url), L"HTTPS:")); //not supported by wxHTTP! + const std::wstring urlFmt = startsWith(makeUpperCopy(url), L"HTTP://") || + startsWith(makeUpperCopy(url), L"HTTPS://") ? 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_SYS_ERROR(L"InternetOpen"); - ZEN_ON_SCOPE_EXIT(::InternetCloseHandle(hInternet)); + //WinInet: 1. uses IE proxy settings! :) 2. follows HTTP redirects by default 3. swallows HTTPS if needed + 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_SYS_ERROR(L"InternetOpen"); + + 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_SYS_ERROR(L"InternetConnect"); + + const wchar_t* acceptTypes[] = { L"*/*", nullptr }; + DWORD requestFlags = + //INTERNET_FLAG_KEEP_CONNECTION | + // the combination 1. INTERNET_FLAG_KEEP_CONNECTION (= adds "Connection: Keep-Alive" but NOT "Keep-Alive: timeout" to the header) + // 2. *no* "Keep-Alive: timeout" header entry 3. call from within VM and 4. *no* Fiddler running 5. HTTP POST + // leads to Godaddy blocking the IP: http://www.freefilesync.org/forum/viewtopic.php?t=3855 + // => it seems a broken keep alive header is the trigger: But why is it then working outside the VM or when Fiddler is running??? Why not a problem for HTTP GET? + // note: HTTP/1.1 has keep-alive semantics by default, so this flag is probably useless anyway + INTERNET_FLAG_NO_UI; + + if (postParams) + { + requestFlags |= INTERNET_FLAG_NO_AUTO_REDIRECT; //POST would be re-issued as GET during auto-redirect => handle ourselves! + } + else //HTTP GET + { + requestFlags |= INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS | INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP; + requestFlags |= INTERNET_FLAG_RELOAD; //not relevant for POST (= never cached) + } - 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_SYS_ERROR(L"InternetConnect"); - ZEN_ON_SCOPE_EXIT(::InternetCloseHandle(hSession)); - - const wchar_t* acceptTypes[] = { L"*/*", nullptr }; - DWORD requestFlags = - //INTERNET_FLAG_KEEP_CONNECTION | - // the combination 1. INTERNET_FLAG_KEEP_CONNECTION (= adds "Connection: Keep-Alive" but NOT "Keep-Alive: timeout" to the header) - // 2. *no* "Keep-Alive: timeout" header entry 3. call from within VM and 4. *no* Fiddler running 5. HTTP POST - // leads to Godaddy blocking the IP: http://www.freefilesync.org/forum/viewtopic.php?t=3855 - // => it seems a broken keep alive header is the trigger: But why is it then working outside the VM or when Fiddler is running??? Why not a problem for HTTP GET? - // note: HTTP/1.1 has keep-alive semantics by default, so this flag is probably useless anyway - INTERNET_FLAG_NO_UI; - - if (postParams) - { - requestFlags |= INTERNET_FLAG_NO_AUTO_REDIRECT; //POST would be re-issued as GET during auto-redirect => handle ourselves! - } - else //HTTP GET - { - requestFlags |= INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS | INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP; - requestFlags |= INTERNET_FLAG_RELOAD; //not relevant for POST (= never cached) - } - - 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_SYS_ERROR(L"HttpOpenRequest"); - ZEN_ON_SCOPE_EXIT(::InternetCloseHandle(hRequest)); + 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_SYS_ERROR(L"HttpOpenRequest"); - const std::wstring headers = postParams ? L"Content-type: application/x-www-form-urlencoded" : L""; + const std::wstring headers = postParams ? L"Content-type: application/x-www-form-urlencoded" : L""; - assert(std::all_of(headers.begin(), headers.end(), [](wchar_t c){ return makeUnsigned(c) < 128; })); - //HttpSendRequest has finicky behavior for non-ASCII headers: https://msdn.microsoft.com/en-us/library/windows/desktop/aa384247 + assert(std::all_of(headers.begin(), headers.end(), [](wchar_t c) { return makeUnsigned(c) < 128; })); + //HttpSendRequest has finicky behavior for non-ASCII headers: https://msdn.microsoft.com/en-us/library/windows/desktop/aa384247 - std::string postParamsBuf = postParams ? *postParams : ""; + std::string postParamsBuf = postParams ? *postParams : ""; - if (!::HttpSendRequest(hRequest, //_In_ HINTERNET hRequest, - headers.c_str(), //_In_ LPCTSTR lpszHeaders, - static_cast<DWORD>(headers.size()), //_In_ DWORD dwHeadersLength, - postParamsBuf.empty() ? nullptr : &postParamsBuf[0], //_In_ LPVOID lpOptional, - static_cast<DWORD>(postParamsBuf.size()))) //_In_ DWORD dwOptionalLength - THROW_LAST_SYS_ERROR(L"HttpSendRequest"); + if (!::HttpSendRequest(hRequest_, //_In_ HINTERNET hRequest, + headers.c_str(), //_In_ LPCTSTR lpszHeaders, + static_cast<DWORD>(headers.size()), //_In_ DWORD dwHeadersLength, + postParamsBuf.empty() ? nullptr : &postParamsBuf[0], //_In_ LPVOID lpOptional, + static_cast<DWORD>(postParamsBuf.size()))) //_In_ DWORD dwOptionalLength + THROW_LAST_SYS_ERROR(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_SYS_ERROR(L"HttpQueryInfo: HTTP_QUERY_STATUS_CODE"); - } + 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_SYS_ERROR(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." + //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! { DWORD bufLen = 10000; std::wstring location(bufLen, L'\0'); - if (!::HttpQueryInfo(hRequest, HTTP_QUERY_LOCATION, &*location.begin(), &bufLen, nullptr)) + if (!::HttpQueryInfo(hRequest_, HTTP_QUERY_LOCATION, &*location.begin(), &bufLen, nullptr)) THROW_LAST_SYS_ERROR(L"HttpQueryInfo: HTTP_QUERY_LOCATION"); if (bufLen >= location.size()) //HttpQueryInfo expected to write terminating zero throw SysError(L"HttpQueryInfo: HTTP_QUERY_LOCATION, buffer overflow"); location.resize(bufLen); - if (!location.empty()) - return sendHttpRequestImpl(location, userAgent, postParams, level + 1); + if (location.empty()) + throw SysError(L"Unresolvable redirect. Empty target Location."); + + throw UrlRedirectError(location); + } + + if (sc != HTTP_STATUS_OK) //200 + throw SysError(replaceCpy<std::wstring>(L"HTTP status code %x.", L"%x", numberTo<std::wstring>(sc))); + //e.g. 404 - HTTP_STATUS_NOT_FOUND + +#else + assert(std::this_thread::get_id() == mainThreadId); + assert(wxApp::IsMainLoopRunning()); + + 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 SysError(L"wxHTTP::Connect"); + + if (postParams) + if (!webAccess_.SetPostText(L"application/x-www-form-urlencoded", utfCvrtTo<wxString>(*postParams))) + throw SysError(L"wxHTTP::SetPostText"); + + httpStream_.reset(webAccess_.GetInputStream(page)); //pass ownership + 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! + { + const std::wstring newUrl(webAccess_.GetHeader(L"Location")); + if (newUrl.empty()) + throw SysError(L"Unresolvable redirect. Empty target Location."); + + throw UrlRedirectError(newUrl); } - throw SysError(L"Unresolvable redirect."); + + if (sc != 200) //HTTP_STATUS_OK + throw SysError(replaceCpy<std::wstring>(L"HTTP status code %x.", L"%x", numberTo<std::wstring>(sc))); + + if (!httpStream_ || webAccess_.GetError() != wxPROTO_NOERR) + throw SysError(L"wxHTTP::GetError (" + numberTo<std::wstring>(webAccess_.GetError()) + L")"); +#endif } - if (sc != HTTP_STATUS_OK) //200 - throw SysError(replaceCpy<std::wstring>(L"HTTP status code %x.", L"%x", numberTo<std::wstring>(sc))); - //e.g. 404 - HTTP_STATUS_NOT_FOUND + ~Impl() { cleanup(); } - std::string buffer; - const DWORD blockSize = 64 * 1024; - //internet says "HttpQueryInfo() + HTTP_QUERY_CONTENT_LENGTH" not supported by all http servers... - for (;;) + size_t tryRead(void* buffer, size_t bytesToRead) //throw SysError; may return short, only 0 means EOF! { - buffer.resize(buffer.size() + blockSize); + if (bytesToRead == 0) //"read() with a count of 0 returns zero" => indistinguishable from end of file! => check! + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); +#ifdef ZEN_WIN + //"HttpQueryInfo() + HTTP_QUERY_CONTENT_LENGTH" not supported by all http servers... DWORD bytesRead = 0; - if (!::InternetReadFile(hRequest, //_In_ HINTERNET hFile, - &*(buffer.begin() + buffer.size() - blockSize), //_Out_ LPVOID lpBuffer, - blockSize, //_In_ DWORD dwNumberOfBytesToRead, + if (!::InternetReadFile(hRequest_, //_In_ HINTERNET hFile, + buffer, //_Out_ LPVOID lpBuffer, + static_cast<DWORD>(bytesToRead), //_In_ DWORD dwNumberOfBytesToRead, &bytesRead)) //_Out_ LPDWORD lpdwNumberOfBytesRead THROW_LAST_SYS_ERROR(L"InternetReadFile"); +#else + httpStream_->Read(buffer, bytesToRead); - if (bytesRead > blockSize) //better safe than sorry + const wxStreamError ec = httpStream_->GetLastError(); + if (ec != wxSTREAM_NO_ERROR && ec != wxSTREAM_EOF) + throw SysError(L"wxInputStream::GetLastError (" + numberTo<std::wstring>(httpStream_->GetLastError()) + L")"); + + const size_t bytesRead = httpStream_->LastRead(); + //"if there are not enough bytes in the stream right now, LastRead() value will be + // less than size but greater than 0. If it is 0, it means that EOF has been reached." + assert(bytesRead > 0 || ec == wxSTREAM_EOF); +#endif + if (bytesRead > bytesToRead) //better safe than sorry throw SysError(L"InternetReadFile: buffer overflow."); - if (bytesRead < blockSize) - buffer.resize(buffer.size() - (blockSize - bytesRead)); //caveat: unsigned arithmetics + return bytesRead; //"zero indicates end of file" + } - if (bytesRead == 0) - return buffer; +private: + Impl (const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + + void cleanup() + { +#ifdef ZEN_WIN + if (hRequest_ ) ::InternetCloseHandle(hRequest_); + if (hSession_ ) ::InternetCloseHandle(hSession_); + if (hInternet_) ::InternetCloseHandle(hInternet_); +#endif } +#ifdef ZEN_WIN + HINTERNET hInternet_ = nullptr; + HINTERNET hSession_ = nullptr; + HINTERNET hRequest_ = nullptr; #else - assert(std::this_thread::get_id() == mainThreadId); - assert(wxApp::IsMainLoopRunning()); + wxHTTP webAccess_; + std::unique_ptr<wxInputStream> httpStream_; //must be deleted BEFORE webAccess is closed +#endif +}; - 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 SysError(L"wxHTTP::Connect"); +HttpInputStream::HttpInputStream(std::unique_ptr<Impl>&& pimpl) : pimpl_(std::move(pimpl)) {} - if (postParams) - if (!webAccess.SetPostText(L"application/x-www-form-urlencoded", utfCvrtTo<wxString>(*postParams))) - throw SysError(L"wxHTTP::SetPostText"); +HttpInputStream::~HttpInputStream() {} - std::unique_ptr<wxInputStream> httpStream(webAccess.GetInputStream(page)); //must be deleted BEFORE webAccess is closed - const int sc = webAccess.GetResponse(); +size_t HttpInputStream::tryRead(void* buffer, size_t bytesToRead) { return pimpl_->tryRead(buffer, bytesToRead); } //throw SysError + + +std::string HttpInputStream::readAll() //throw SysError +{ + std::string buffer; + const size_t blockSize = getBlockSize(); - //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! + for (;;) { - 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 SysError(L"Unresolvable redirect."); - } + buffer.resize(buffer.size() + blockSize); - if (sc != 200) //HTTP_STATUS_OK - throw SysError(replaceCpy<std::wstring>(L"HTTP status code %x.", L"%x", numberTo<std::wstring>(sc))); + const size_t bytesRead = pimpl_->tryRead(&*(buffer.end() - blockSize), blockSize); //throw SysError - if (!httpStream || webAccess.GetError() != wxPROTO_NOERR) - throw SysError(L"wxHTTP::GetError"); + if (bytesRead < blockSize) + buffer.resize(buffer.size() - (blockSize - bytesRead)); //caveat: unsigned arithmetics - std::string buffer; - int newValue = 0; - while ((newValue = httpStream->GetC()) != wxEOF) - buffer.push_back(static_cast<char>(newValue)); - return buffer; -#endif + if (bytesRead == 0) + return buffer; + } +} + + +namespace +{ +std::unique_ptr<HttpInputStream::Impl> sendHttpRequestImpl(const std::wstring& url, const std::wstring& userAgent, //throw SysError + const std::string* postParams) //issue POST if bound, GET otherwise +{ + std::wstring urlRed = url; + //"A user agent should not automatically redirect a request more than five times, since such redirections usually indicate an infinite loop." + for (int redirects = 0; redirects < 6; ++redirects) + try + { + return std::make_unique<HttpInputStream::Impl>(urlRed, userAgent, postParams); //throw SysError, UrlRedirectError + } + catch (const UrlRedirectError& e) { urlRed = e.newUrl; } + throw SysError(L"Too many redirects."); } @@ -228,31 +301,72 @@ std::string urlencode(const std::string& str) out += c; else { - const char hexDigits[] = "0123456789ABCDEF"; + const std::pair<char, char> hex = hexify(c); + out += '%'; - out += hexDigits[static_cast<unsigned char>(c) / 16]; - out += hexDigits[static_cast<unsigned char>(c) % 16]; + out += hex.first; + out += hex.second; + } + return out; +} + + +std::string urldecode(const std::string& str) +{ + std::string out; + for (size_t i = 0; i < str.size(); ++i) + { + const char c = str[i]; + if (c == '+') + out += ' '; + else if (c == '%' && str.size() - i >= 3 && + isHexDigit(str[i + 1]) && + isHexDigit(str[i + 2])) + { + out += unhexify(str[i + 1], str[i + 2]); + i += 2; } + else + out += c; + } 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 SysError +std::string zen::xWwwFormUrlEncode(const std::vector<std::pair<std::string, std::string>>& paramPairs) { - //convert post parameters into "application/x-www-form-urlencoded" - std::string flatParams; - for (const auto& pair : postParams) - flatParams += urlencode(pair.first) + '=' + urlencode(pair.second) + '&'; + std::string output; + for (const auto& pair : paramPairs) + output += 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(); + if (!output.empty()) + output.pop_back(); + return output; +} - return sendHttpRequestImpl(url, userAgent, &flatParams); //throw SysError + +std::vector<std::pair<std::string, std::string>> zen::xWwwFormUrlDecode(const std::string& str) +{ + std::vector<std::pair<std::string, std::string>> output; + + for (const std::string& nvPair : split(str, '&')) + if (!nvPair.empty()) + output.emplace_back(urldecode(beforeFirst(nvPair, '=', IF_MISSING_RETURN_ALL)), + urldecode(afterFirst (nvPair, '=', IF_MISSING_RETURN_NONE))); + return output; +} + + +HttpInputStream zen::sendHttpPost(const std::wstring& url, const std::wstring& userAgent, + const std::vector<std::pair<std::string, std::string>>& postParams) //throw SysError +{ + const std::string encodedParams = xWwwFormUrlEncode(postParams); + return sendHttpRequestImpl(url, userAgent, &encodedParams); //throw SysError } -std::string zen::sendHttpGet(const std::wstring& url, const std::wstring& userAgent) //throw SysError +HttpInputStream zen::sendHttpGet(const std::wstring& url, const std::wstring& userAgent) //throw SysError { return sendHttpRequestImpl(url, userAgent, nullptr); //throw SysError } @@ -17,9 +17,32 @@ namespace zen 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 SysError -std::string sendHttpGet (const std::wstring& url, const std::wstring& userAgent); //throw SysError +class HttpInputStream +{ +public: + std::string readAll(); //throw SysError + + //support zen/serialize.h Unbuffered Input Stream Concept + size_t tryRead(void* buffer, size_t bytesToRead); //throw SysError; may return short, only 0 means EOF! => CONTRACT: bytesToRead > 0! + size_t getBlockSize() const { return 64 * 1024; } + + class Impl; + HttpInputStream(std::unique_ptr<Impl>&& pimpl); + HttpInputStream(HttpInputStream&&) = default; + ~HttpInputStream(); + +private: + std::unique_ptr<Impl> pimpl_; +}; + + +HttpInputStream sendHttpGet (const std::wstring& url, const std::wstring& userAgent); //throw SysError +HttpInputStream sendHttpPost(const std::wstring& url, const std::wstring& userAgent, + const std::vector<std::pair<std::string, std::string>>& postParams); //throw SysError bool internetIsAlive(); //noexcept + +std::string xWwwFormUrlEncode(const std::vector<std::pair<std::string, std::string>>& paramPairs); +std::vector<std::pair<std::string, std::string>> xWwwFormUrlDecode(const std::string& str); } #endif //HTTP_h_879083425703425702 diff --git a/wx+/image_tools.cpp b/wx+/image_tools.cpp index a6ecc3d3..90945a44 100644 --- a/wx+/image_tools.cpp +++ b/wx+/image_tools.cpp @@ -160,7 +160,7 @@ wxImage zen::createImageFromText(const wxString& text, const wxFont& font, const //for some reason wxDC::DrawText messes up "weak" bidi characters even when wxLayout_RightToLeft is set! (--> arrows in hebrew/arabic) //=> use mark characters instead: - const wchar_t rtlMark = L'\u200F'; + const wchar_t rtlMark = L'\u200F'; //UTF-8: E2 80 8F if (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft) textFmt = rtlMark + textFmt + rtlMark; diff --git a/wx+/popup_dlg.cpp b/wx+/popup_dlg.cpp index 918f44a5..f96884d9 100644 --- a/wx+/popup_dlg.cpp +++ b/wx+/popup_dlg.cpp @@ -86,27 +86,31 @@ public: #ifdef ZEN_WIN new zen::MouseMoveWindow(*this); //allow moving main dialog by clicking (nearly) anywhere...; ownership passed to "this" #endif - wxString titleTmp = cfg.title; + wxBitmap iconTmp; + wxString titleTmp; switch (type) { case DialogInfoType::INFO: - //"information" is meaningless as caption text! + //"Information" is meaningless as caption text! //confirmation doesn't use info icon - //m_bitmapMsgType->Hide(); - //m_bitmapMsgType->SetSize(30, -1); - //m_bitmapMsgType->SetBitmap(getResourceImage(L"msg_info")); + //iconTmp = getResourceImage(L"msg_info"); break; case DialogInfoType::WARNING: - if (titleTmp.empty()) titleTmp = _("Warning"); - m_bitmapMsgType->SetBitmap(getResourceImage(L"msg_warning")); + iconTmp = getResourceImage(L"msg_warning"); + titleTmp = _("Warning"); break; case DialogInfoType::ERROR2: - if (titleTmp.empty()) titleTmp = _("Error"); - m_bitmapMsgType->SetBitmap(getResourceImage(L"msg_error")); + iconTmp = getResourceImage(L"msg_error"); + titleTmp = _("Error"); break; } if (cfg.icon.IsOk()) - m_bitmapMsgType->SetBitmap(cfg.icon); + iconTmp = cfg.icon; + + if (!cfg.title.empty()) + titleTmp = cfg.title; + //----------------------------------------------- + m_bitmapMsgType->SetBitmap(iconTmp); if (titleTmp.empty()) SetTitle(wxTheApp->GetAppDisplayName()); @@ -140,7 +144,7 @@ public: if (!cfg.textDetail.empty()) { - const wxString& text = L"\n" + cfg.textDetail + L"\n"; //add empty top/bottom lines *instead* of using border space! + const wxString& text = L"\n" + trimCpy(cfg.textDetail) + L"\n"; //add empty top/bottom lines *instead* of using border space! setBestInitialSize(*m_textCtrlTextDetail, text, wxSize(maxWidth, maxHeight)); m_textCtrlTextDetail->ChangeValue(text); } @@ -185,14 +189,14 @@ private: void OnButtonAffirmative(wxCommandEvent& event) override { if (checkBoxValue_) - * checkBoxValue_ = m_checkBoxCustom->GetValue(); + *checkBoxValue_ = m_checkBoxCustom->GetValue(); EndModal(static_cast<int>(ConfirmationButton3::DO_IT)); } void OnButtonNegative(wxCommandEvent& event) override { if (checkBoxValue_) - * checkBoxValue_ = m_checkBoxCustom->GetValue(); + *checkBoxValue_ = m_checkBoxCustom->GetValue(); EndModal(static_cast<int>(ConfirmationButton3::DONT_DO_IT)); } @@ -244,8 +248,8 @@ class zen::ConfirmationDialog3 : public StandardPopupDialog { public: ConfirmationDialog3(wxWindow* parent, DialogInfoType type, const PopupDialogCfg3& cfg, const wxString& labelDoIt, const wxString& labelDontDoIt) : - StandardPopupDialog(parent, type, cfg.pdCfg), - buttonToDisableWhenChecked(cfg.buttonToDisableWhenChecked) + StandardPopupDialog(parent, type, cfg.pdCfg_), + buttonToDisableWhenChecked_(cfg.buttonToDisableWhenChecked_) { assert(contains(labelDoIt, L"&")); assert(contains(labelDontDoIt, L"&")); @@ -269,7 +273,7 @@ private: void updateGui() { - switch (buttonToDisableWhenChecked) + switch (buttonToDisableWhenChecked_) { case ConfirmationButton3::DO_IT: m_buttonAffirmative->Enable(!m_checkBoxCustom->GetValue()); @@ -282,7 +286,7 @@ private: } } - const ConfirmationButton3 buttonToDisableWhenChecked; + const ConfirmationButton3 buttonToDisableWhenChecked_; }; //######################################################################################## diff --git a/wx+/popup_dlg.h b/wx+/popup_dlg.h index 892c7a83..67f73a11 100644 --- a/wx+/popup_dlg.h +++ b/wx+/popup_dlg.h @@ -39,7 +39,7 @@ enum class ConfirmationButton3 enum class ConfirmationButton { - DO_IT = static_cast<int>(ConfirmationButton3::DO_IT), //[!] + DO_IT = static_cast<int>(ConfirmationButton3::DO_IT ), //[!] CANCEL = static_cast<int>(ConfirmationButton3::CANCEL), //Clang requires a "static_cast" }; @@ -73,23 +73,24 @@ private: struct PopupDialogCfg3 { - PopupDialogCfg3& setTitle (const wxString& label) { pdCfg.setTitle (label); return *this; } - PopupDialogCfg3& setMainInstructions (const wxString& label) { pdCfg.setMainInstructions (label); return *this; } //set at least one of these! - PopupDialogCfg3& setDetailInstructions(const wxString& label) { pdCfg.setDetailInstructions(label); return *this; } // - PopupDialogCfg3& setCheckBox(bool& value, const wxString& label) { pdCfg.setCheckBox(value, label); return *this; } + PopupDialogCfg3& setIcon (const wxBitmap& bmp ) { pdCfg_.setIcon (bmp); return *this; } + PopupDialogCfg3& setTitle (const wxString& label) { pdCfg_.setTitle (label); return *this; } + PopupDialogCfg3& setMainInstructions (const wxString& label) { pdCfg_.setMainInstructions (label); return *this; } //set at least one of these! + PopupDialogCfg3& setDetailInstructions(const wxString& label) { pdCfg_.setDetailInstructions(label); return *this; } // + PopupDialogCfg3& setCheckBox(bool& value, const wxString& label) { pdCfg_.setCheckBox(value, label); return *this; } PopupDialogCfg3& setCheckBox(bool& value, const wxString& label, ConfirmationButton3 disableWhenChecked) { assert(disableWhenChecked != ConfirmationButton3::CANCEL); setCheckBox(value, label); - buttonToDisableWhenChecked = disableWhenChecked; + buttonToDisableWhenChecked_ = disableWhenChecked; return *this; } private: friend class ConfirmationDialog3; - PopupDialogCfg pdCfg; - ConfirmationButton3 buttonToDisableWhenChecked = ConfirmationButton3::CANCEL; + PopupDialogCfg pdCfg_; + ConfirmationButton3 buttonToDisableWhenChecked_ = ConfirmationButton3::CANCEL; }; } diff --git a/wx+/popup_dlg_generated.cpp b/wx+/popup_dlg_generated.cpp index b726aa9a..6df18dce 100644 --- a/wx+/popup_dlg_generated.cpp +++ b/wx+/popup_dlg_generated.cpp @@ -34,7 +34,10 @@ PopupDialogGenerated::PopupDialogGenerated( wxWindow* parent, wxWindowID id, con m_staticTextMain = new wxStaticText( m_panel33, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticTextMain->Wrap( -1 ); - bSizer16->Add( m_staticTextMain, 0, wxTOP|wxBOTTOM|wxRIGHT, 5 ); + bSizer16->Add( m_staticTextMain, 0, wxRIGHT, 10 ); + + + bSizer16->Add( 0, 5, 0, 0, 5 ); m_textCtrlTextDetail = new wxTextCtrl( m_panel33, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_READONLY|wxNO_BORDER ); bSizer16->Add( m_textCtrlTextDetail, 1, wxEXPAND, 5 ); diff --git a/wx+/std_button_layout.h b/wx+/std_button_layout.h index 59fda260..3e335882 100644 --- a/wx+/std_button_layout.h +++ b/wx+/std_button_layout.h @@ -16,14 +16,13 @@ namespace zen { struct StdButtons { - StdButtons() : btnYes(nullptr), btnNo(nullptr), btnCancel(nullptr) {} StdButtons& setAffirmative (wxButton* btn) { btnYes = btn; return *this; } StdButtons& setNegative (wxButton* btn) { btnNo = btn; return *this; } StdButtons& setCancel (wxButton* btn) { btnCancel = btn; return *this; } - wxButton* btnYes; - wxButton* btnNo; - wxButton* btnCancel; + wxButton* btnYes = nullptr; + wxButton* btnNo = nullptr; + wxButton* btnCancel = nullptr; }; void setStandardButtonLayout(wxBoxSizer& sizer, const StdButtons& buttons = StdButtons()); diff --git a/wx+/tooltip.cpp b/wx+/tooltip.cpp index c2c562ce..8dc79d73 100644 --- a/wx+/tooltip.cpp +++ b/wx+/tooltip.cpp @@ -16,15 +16,15 @@ using namespace zen; -class Tooltip::TooltipDialogGenerated : public wxDialog +class Tooltip::TooltipDlgGenerated : public wxDialog { public: - TooltipDialogGenerated(wxWindow* parent, - wxWindowID id = wxID_ANY, - const wxString& title = {}, - const wxPoint& pos = wxDefaultPosition, - const wxSize& size = wxDefaultSize, - long style = 0) : wxDialog(parent, id, title, pos, size, style) + TooltipDlgGenerated(wxWindow* parent, + wxWindowID id = wxID_ANY, + const wxString& title = {}, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = 0) : wxDialog(parent, id, title, pos, size, style) { //Suse Linux/X11: needs parent window, else there are z-order issues @@ -33,11 +33,11 @@ public: this->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_INFOTEXT)); // wxBoxSizer* bSizer158 = new wxBoxSizer(wxHORIZONTAL); - m_bitmapLeft = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0); - bSizer158->Add(m_bitmapLeft, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + bitmapLeft_ = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0); + bSizer158->Add(bitmapLeft_, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); - m_staticTextMain = new wxStaticText(this, wxID_ANY, wxString(), wxDefaultPosition, wxDefaultSize, 0); - bSizer158->Add(m_staticTextMain, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5); + staticTextMain_ = new wxStaticText(this, wxID_ANY, wxString(), wxDefaultPosition, wxDefaultSize, 0); + bSizer158->Add(staticTextMain_, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5); this->SetSizer(bSizer158); this->Layout(); @@ -48,57 +48,57 @@ public: #endif } - wxStaticText* m_staticTextMain; - wxStaticBitmap* m_bitmapLeft; + wxStaticText* staticTextMain_; + wxStaticBitmap* bitmapLeft_; }; void Tooltip::show(const wxString& text, wxPoint mousePos, const wxBitmap* bmp) { - if (!tipWindow) - tipWindow = new TooltipDialogGenerated(&parent_); //ownership passed to parent + if (!tipWindow_) + tipWindow_ = new TooltipDlgGenerated(&parent_); //ownership passed to parent const wxBitmap& newBmp = bmp ? *bmp : wxNullBitmap; - if (!isEqual(tipWindow->m_bitmapLeft->GetBitmap(), newBmp)) + if (!isEqual(tipWindow_->bitmapLeft_->GetBitmap(), newBmp)) { - tipWindow->m_bitmapLeft->SetBitmap(newBmp); - tipWindow->Refresh(); //needed if bitmap size changed! + tipWindow_->bitmapLeft_->SetBitmap(newBmp); + tipWindow_->Refresh(); //needed if bitmap size changed! } - if (text != tipWindow->m_staticTextMain->GetLabel()) + if (text != tipWindow_->staticTextMain_->GetLabel()) { - tipWindow->m_staticTextMain->SetLabel(text); - tipWindow->m_staticTextMain->Wrap(600); + tipWindow_->staticTextMain_->SetLabel(text); + tipWindow_->staticTextMain_->Wrap(600); } - tipWindow->GetSizer()->SetSizeHints(tipWindow); //~=Fit() + SetMinSize() + tipWindow_->GetSizer()->SetSizeHints(tipWindow_); //~=Fit() + SetMinSize() //Linux: Fit() seems to be broken => this needs to be called EVERY time inside show, not only if text or bmp change const wxPoint newPos = wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft ? - mousePos - wxPoint(30 + tipWindow->GetSize().GetWidth(), 0) : + mousePos - wxPoint(30 + tipWindow_->GetSize().GetWidth(), 0) : mousePos + wxPoint(30, 0); - if (newPos != tipWindow->GetScreenPosition()) - tipWindow->SetSize(newPos.x, newPos.y, wxDefaultCoord, wxDefaultCoord); + if (newPos != tipWindow_->GetScreenPosition()) + tipWindow_->SetSize(newPos.x, newPos.y, wxDefaultCoord, wxDefaultCoord); //attention!!! possible endless loop: mouse pointer must NOT be within tipWindow! //else it will trigger a wxEVT_LEAVE_WINDOW on middle grid which will hide the window, causing the window to be shown again via this method, etc. - if (!tipWindow->IsShown()) - tipWindow->Show(); + if (!tipWindow_->IsShown()) + tipWindow_->Show(); } void Tooltip::hide() { - if (tipWindow) + if (tipWindow_) { #ifdef ZEN_LINUX //on wxGTK the tooltip is sometimes not shown again after it was hidden: e.g. drag-selection on middle grid - tipWindow->Destroy(); //apply brute force: - tipWindow = nullptr; // + tipWindow_->Destroy(); //apply brute force: + tipWindow_ = nullptr; // #else - tipWindow->Hide(); + tipWindow_->Hide(); #endif } } diff --git a/wx+/tooltip.h b/wx+/tooltip.h index 23c7adb1..f2a7043e 100644 --- a/wx+/tooltip.h +++ b/wx+/tooltip.h @@ -23,8 +23,8 @@ public: void hide(); private: - class TooltipDialogGenerated; - TooltipDialogGenerated* tipWindow = nullptr; + class TooltipDlgGenerated; + TooltipDlgGenerated* tipWindow_ = nullptr; wxWindow& parent_; }; } diff --git a/zen/basic_math.h b/zen/basic_math.h index e9e17466..eed23477 100644 --- a/zen/basic_math.h +++ b/zen/basic_math.h @@ -18,7 +18,7 @@ namespace numeric { template <class T> T abs(T value); -template <class T> T dist(T a, T b); +template <class T> auto dist(T a, T b); template <class T> int sign(T value); //returns one of {-1, 0, 1} template <class T> T min(T a, T b, T c); template <class T> T max(T a, T b, T c); @@ -90,7 +90,7 @@ T abs(T value) } template <class T> inline -T dist(T a, T b) +auto dist(T a, T b) //return type might be different than T, e.g. std::chrono::duration instead of std::chrono::time_point { return a > b ? a - b : b - a; } @@ -19,6 +19,19 @@ namespace zen { +uint16_t getCrc16(const std::string& str); +uint32_t getCrc32(const std::string& str); +template <class ByteIterator> uint16_t getCrc16(ByteIterator first, ByteIterator last); +template <class ByteIterator> uint32_t getCrc32(ByteIterator first, ByteIterator last); + + + + +//------------------------- implementation ------------------------------- +inline uint16_t getCrc16(const std::string& str) { return getCrc16(str.begin(), str.end()); } +inline uint32_t getCrc32(const std::string& str) { return getCrc32(str.begin(), str.end()); } + + template <class ByteIterator> inline uint16_t getCrc16(ByteIterator first, ByteIterator last) { diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp index 12a6a9f4..98190bba 100644 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -9,6 +9,7 @@ #include <set> #include "thread.h" #include "scope_guard.h" +#include "basic_math.h" #ifdef ZEN_WIN #include "device_notify.h" @@ -358,10 +359,11 @@ std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void() //wait until device removal is confirmed, to prevent locking hDir again by some new watch! if (pimpl_->volRemoval->requestReceived()) { - const std::chrono::steady_clock::time_point stopTime = std::chrono::steady_clock::now() + std::chrono::seconds(15); + const auto startTime = std::chrono::steady_clock::now(); //HandleVolumeRemoval::finished() not guaranteed! note: Windows gives unresponsive applications ca. 10 seconds until unmounting the usb stick in worst case - while (!pimpl_->volRemoval->finished() && std::chrono::steady_clock::now() < stopTime) + while (!pimpl_->volRemoval->finished() && + numeric::dist(std::chrono::steady_clock::now(), startTime) < std::chrono::seconds(15)) //handle potential chrono wrap-around! { processGuiMessages(); //DBT_DEVICEREMOVECOMPLETE message is sent here! std::this_thread::sleep_for(std::chrono::milliseconds(50)); diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp index b4599d03..3eb284e1 100644 --- a/zen/file_traverser.cpp +++ b/zen/file_traverser.cpp @@ -62,7 +62,7 @@ void zen::traverseFolder(const Zstring& dirPath, if (ec == ERROR_NO_MORE_FILES) //not an error situation return; //else we have a problem... report it: - throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtPath(dirPath)), formatSystemError(L"FindNextFile", ec)); + throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), formatSystemError(L"FindNextFile", ec)); } //skip "." and ".." @@ -73,7 +73,7 @@ void zen::traverseFolder(const Zstring& dirPath, continue; if (itemNameRaw[0] == 0) - throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtPath(dirPath)), L"FindNextFile: Data corruption; item with empty name."); + throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), L"FindNextFile: Data corruption; item with empty name."); const Zstring& itemPath = appendSeparator(dirPath) + itemNameRaw; @@ -112,7 +112,7 @@ void zen::traverseFolder(const Zstring& dirPath, { struct ::dirent* dirEntry = nullptr; if (::readdir_r(folder, reinterpret_cast< ::dirent*>(&buffer[0]), &dirEntry) != 0) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtPath(dirPath)), L"readdir_r"); + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), L"readdir_r"); //don't retry but restart dir traversal on error! https://blogs.msdn.microsoft.com/oldnewthing/20140612-00/?p=753/ if (!dirEntry) //no more items @@ -133,14 +133,14 @@ void zen::traverseFolder(const Zstring& dirPath, } catch (const SysError& e) //failure is not an item-level error since we don't know the normalized name yet!!! { - throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtPath(dirPath)), + throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), L"Failed to generate normalized file name: " + fmtPath(itemNameRaw) + L"\n" + e.toString()); //too obscure to warrant translation } #else const Zstring& itemName = itemNameRaw; #endif if (itemName.empty()) //checks result of osx::normalizeUtfForPosix, too! - throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtPath(dirPath)), L"readdir_r: Data corruption; item with empty name."); + throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), L"readdir_r: Data corruption; item with empty name."); const Zstring& itemPath = appendSeparator(dirPath) + itemName; diff --git a/zen/fixed_list.h b/zen/fixed_list.h index 4376c13f..81197eb4 100644 --- a/zen/fixed_list.h +++ b/zen/fixed_list.h @@ -15,7 +15,7 @@ namespace zen { //std::list(C++11)-like class for inplace element construction supporting non-copyable/non-movable types //-> no iterator invalidation after emplace_back() - + template <class T> class FixedList { @@ -69,7 +69,7 @@ public: const_reference& back() const { return lastInsert_->val; } template <class... Args> - void emplace_back(Args&&... args) + void emplace_back(Args&& ... args) { Node* newNode = new Node(std::forward<Args>(args)...); @@ -160,10 +160,10 @@ class FixedVector public: FixedVector() {} - /* - class EndIterator {}; //just like FixedList: no iterator invalidation after emplace_back() + /* + class EndIterator {}; //just like FixedList: no iterator invalidation after emplace_back() - template <class V> + template <class V> class FixedIterator : public std::iterator<std::forward_iterator_tag, V> //could make this random-access if needed { public: @@ -174,10 +174,10 @@ public: V& operator* () const { return *cont_[pos_]; } V* operator->() const { return &*cont_[pos_]; } private: - std::vector<std::unique_ptr<T>>& cont_; - size_t pos_ = 0; + std::vector<std::unique_ptr<T>>& cont_; + size_t pos_ = 0; }; - */ + */ template <class IterImpl, class V> class FixedIterator : public std::iterator<std::forward_iterator_tag, V> //could make this bidirectional if needed @@ -188,7 +188,7 @@ public: inline friend bool operator==(const FixedIterator& lhs, const FixedIterator& rhs) { return lhs.it_ == rhs.it_; } inline friend bool operator!=(const FixedIterator& lhs, const FixedIterator& rhs) { return !(lhs == rhs); } V& operator* () const { return **it_; } - V* operator->() const { return &**it_; } + V* operator->() const { return &** it_; } private: IterImpl it_; //TODO: avoid iterator invalidation after emplace_back(); caveat: end() must not store old length! }; @@ -199,10 +199,10 @@ public: using reference = T&; using const_reference = const T&; - iterator begin() { return items_.begin(); } + iterator begin() { return items_.begin(); } iterator end () { return items_.end (); } - const_iterator begin() const { return items_.begin(); } + const_iterator begin() const { return items_.begin(); } const_iterator end () const { return items_.end (); } reference front() { return *items_.front(); } @@ -212,15 +212,15 @@ public: const_reference& back() const { return *items_.back(); } template <class... Args> - void emplace_back(Args&&... args) + void emplace_back(Args&& ... args) { - items_.push_back(std::make_unique<T>(std::forward<Args>(args)...)); + items_.push_back(std::make_unique<T>(std::forward<Args>(args)...)); } template <class Predicate> void remove_if(Predicate pred) { - erase_if(items_, [&](const std::unique_ptr<T>& p){ return pred(*p); }); + erase_if(items_, [&](const std::unique_ptr<T>& p) { return pred(*p); }); } void clear() { items_.clear(); } @@ -232,7 +232,7 @@ private: FixedVector (const FixedVector&) = delete; FixedVector& operator=(const FixedVector&) = delete; - std::vector<std::unique_ptr<T>> items_; + std::vector<std::unique_ptr<T>> items_; }; } diff --git a/zen/format_unit.cpp b/zen/format_unit.cpp index d87a1643..08463778 100644 --- a/zen/format_unit.cpp +++ b/zen/format_unit.cpp @@ -16,7 +16,7 @@ #ifdef ZEN_WIN #include "int64.h" #include "win.h" //includes "windows.h" - #include "win_ver.h" + // #include "win_ver.h" #elif defined ZEN_LINUX || defined ZEN_MAC #include <clocale> //thousands separator @@ -155,6 +155,12 @@ std::wstring zen::remainingTimeToString(double timeInSec) } +//std::wstring zen::fractionToString1Dec(double fraction) +//{ +// return printNumber<std::wstring>(L"%.1f", fraction * 100.0) + L'%'; //no need to internationalize fraction!? +//} + + std::wstring zen::fractionToString(double fraction) { return printNumber<std::wstring>(L"%.2f", fraction * 100.0) + L'%'; //no need to internationalize fraction!? @@ -299,32 +305,29 @@ std::wstring zen::utcToLocalTimeString(std::int64_t utcTime) SYSTEMTIME systemTimeLocal = {}; - static const bool useNewLocalTimeCalculation = zen::vistaOrLater(); - //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 = {}; - if (!::FileTimeToSystemTime(&lastWriteTimeUtc, //__in const FILETIME *lpFileTime, - &systemTimeUtc)) //__out LPSYSTEMTIME lpSystemTime - return errorMsg(); - - if (!::SystemTimeToTzSpecificLocalTime(nullptr, //__in_opt LPTIME_ZONE_INFORMATION lpTimeZone, - &systemTimeUtc, //__in LPSYSTEMTIME lpUniversalTime, - &systemTimeLocal)) //__out LPSYSTEMTIME lpLocalTime - return errorMsg(); - } - else //DST conversion like in Windows 2000 and XP: FAT times stay fixed, while NTFS jumps - { - FILETIME fileTimeLocal = {}; - if (!::FileTimeToLocalFileTime(&lastWriteTimeUtc, //_In_ const FILETIME *lpFileTime, - &fileTimeLocal)) //_Out_ LPFILETIME lpLocalFileTime - return errorMsg(); - - if (!::FileTimeToSystemTime(&fileTimeLocal, //__in const FILETIME *lpFileTime, - &systemTimeLocal)) //__out LPSYSTEMTIME lpSystemTime - return errorMsg(); - } +#ifdef ZEN_WIN_VISTA_AND_LATER + //DST conversion like in Vista and later: NTFS stays fixed, but FAT jumps by one hour + SYSTEMTIME systemTimeUtc = {}; + if (!::FileTimeToSystemTime(&lastWriteTimeUtc, //__in const FILETIME *lpFileTime, + &systemTimeUtc)) //__out LPSYSTEMTIME lpSystemTime + return errorMsg(); + + if (!::SystemTimeToTzSpecificLocalTime(nullptr, //__in_opt LPTIME_ZONE_INFORMATION lpTimeZone, + &systemTimeUtc, //__in LPSYSTEMTIME lpUniversalTime, + &systemTimeLocal)) //__out LPSYSTEMTIME lpLocalTime + return errorMsg(); +#else + //DST conversion like in Windows 2000 and XP: FAT times stay fixed, while NTFS jumps + FILETIME fileTimeLocal = {}; + if (!::FileTimeToLocalFileTime(&lastWriteTimeUtc, //_In_ const FILETIME *lpFileTime, + &fileTimeLocal)) //_Out_ LPFILETIME lpLocalFileTime + return errorMsg(); + + if (!::FileTimeToSystemTime(&fileTimeLocal, //__in const FILETIME *lpFileTime, + &systemTimeLocal)) //__out LPSYSTEMTIME lpSystemTime + return errorMsg(); +#endif zen::TimeComp loc; loc.year = systemTimeLocal.wYear; @@ -98,7 +98,7 @@ std::wstring translate(const std::wstring& singular, const std::wstring& plural, inline -Global<const TranslationHandler>& getGlobalTranslationHandler() +Global<const TranslationHandler>& refGlobalTranslationHandler() { //getTranslator() may be called even after static objects of this translation unit are destroyed! static Global<const TranslationHandler> inst; //external linkage even in header! @@ -110,14 +110,14 @@ Global<const TranslationHandler>& getGlobalTranslationHandler() inline void setTranslator(std::unique_ptr<const TranslationHandler>&& newHandler) { - implementation::getGlobalTranslationHandler().set(std::move(newHandler)); + implementation::refGlobalTranslationHandler().set(std::move(newHandler)); } inline std::shared_ptr<const TranslationHandler> getTranslator() { - return implementation::getGlobalTranslationHandler().get(); + return implementation::refGlobalTranslationHandler().get(); } } @@ -7,12 +7,13 @@ #ifndef PERF_H_83947184145342652456 #define PERF_H_83947184145342652456 +#include <chrono> #include "deprecate.h" -#include "tick_count.h" #include "scope_guard.h" #ifdef ZEN_WIN #include <sstream> + #include "win.h" #else #include <iostream> #endif @@ -28,54 +29,47 @@ namespace zen class PerfTimer { public: - class TimerError {}; + ZEN_DEPRECATE PerfTimer() {} - ZEN_DEPRECATE - PerfTimer() : startTime(getTicksNow()) //throw TimerError - { - //std::clock() - "counts CPU time in Linux GCC and wall time in VC++" - WTF!??? - if (ticksPerSec_ == 0) - throw TimerError(); - } - - ~PerfTimer() { if (!resultShown) try { showResult(); } catch (TimerError&) { assert(false); } } + ~PerfTimer() { if (!resultShown_) showResult(); } void pause() { - if (!paused) + if (!paused_) { - paused = true; - elapsedUntilPause += dist(startTime, getTicksNow()); + paused_ = true; + elapsedUntilPause_ += std::chrono::steady_clock::now() - startTime_; //ignore potential ::QueryPerformanceCounter() wrap-around! } } void resume() { - if (paused) + if (paused_) { - paused = false; - startTime = getTicksNow(); + paused_ = false; + startTime_ = std::chrono::steady_clock::now(); } } void restart() { - startTime = getTicksNow(); - paused = false; - elapsedUntilPause = 0; + paused_ = false; + startTime_ = std::chrono::steady_clock::now(); + elapsedUntilPause_ = std::chrono::nanoseconds::zero(); } int64_t timeMs() const { - int64_t ticksTotal = elapsedUntilPause; - if (!paused) - ticksTotal += dist(startTime, getTicksNow()); - return 1000 * ticksTotal / ticksPerSec_; + auto elapsedTotal = elapsedUntilPause_; + if (!paused_) + elapsedTotal += std::chrono::steady_clock::now() - startTime_; + + return std::chrono::duration_cast<std::chrono::milliseconds>(elapsedTotal).count(); } void showResult() { - const bool wasRunning = !paused; + const bool wasRunning = !paused_; if (wasRunning) pause(); //don't include call to MessageBox()! ZEN_ON_SCOPE_EXIT(if (wasRunning) resume()); @@ -86,23 +80,14 @@ public: #else std::clog << "Perf: duration: " << timeMs() << " ms\n"; #endif - resultShown = true; + resultShown_ = true; } private: - TickVal getTicksNow() const - { - const TickVal now = getTicks(); - if (!now.isValid()) - throw TimerError(); - return now; - } - - const std::int64_t ticksPerSec_ = ticksPerSec(); //return 0 on error - bool resultShown = false; - TickVal startTime; - bool paused = false; - int64_t elapsedUntilPause = 0; + bool resultShown_ = false; + bool paused_ = false; + std::chrono::steady_clock::time_point startTime_ = std::chrono::steady_clock::now(); //uses ::QueryPerformanceCounter() + std::chrono::nanoseconds elapsedUntilPause_{}; //std::chrono::duration is uninitialized by default! WTF! When will this stupidity end??? }; } diff --git a/zen/serialize.h b/zen/serialize.h index bc047fee..7322cb07 100644 --- a/zen/serialize.h +++ b/zen/serialize.h @@ -72,8 +72,8 @@ struct UnbufferedOutputStream size_t tryWrite(const void* buffer, size_t bytesToWrite); //may return short! CONTRACT: bytesToWrite > 0 }; */ -//functions based on unbuffered stream abstraction +//functions based on unbuffered stream abstraction template <class UnbufferedInputStream, class UnbufferedOutputStream> void unbufferedStreamCopy(UnbufferedInputStream& streamIn, UnbufferedOutputStream& streamOut, const std::function<void(std::int64_t bytesDelta)>& notifyProgress); //throw X @@ -104,7 +104,6 @@ struct BufferedOutputStream template <class N, class BufferedOutputStream> void writeNumber (BufferedOutputStream& stream, const N& num); // template <class C, class BufferedOutputStream> void writeContainer(BufferedOutputStream& stream, const C& str); //throw () template < class BufferedOutputStream> void writeArray (BufferedOutputStream& stream, const void* data, size_t len); // - //---------------------------------------------------------------------- class UnexpectedEndOfStreamError {}; template <class N, class BufferedInputStream> N readNumber (BufferedInputStream& stream); //throw UnexpectedEndOfStreamError (corrupted data) @@ -115,21 +114,23 @@ template < class BufferedInputStream> void readArray (BufferedInputSt template <class BinContainer> struct MemoryStreamIn { - MemoryStreamIn(const BinContainer& cont) : buffer(cont) {} //this better be cheap! + MemoryStreamIn(const BinContainer& cont) : buffer_(cont) {} //this better be cheap! size_t read(void* data, size_t len) //return "len" bytes unless end of stream! { static_assert(sizeof(typename BinContainer::value_type) == 1, ""); //expect: bytes - const size_t bytesRead = std::min(len, buffer.size() - pos); - auto itFirst = buffer.begin() + pos; + const size_t bytesRead = std::min(len, buffer_.size() - pos_); + auto itFirst = buffer_.begin() + pos_; std::copy(itFirst, itFirst + bytesRead, static_cast<char*>(data)); - pos += bytesRead; + pos_ += bytesRead; return bytesRead; } + size_t pos() const { return pos_; } + private: - const BinContainer buffer; - size_t pos = 0; + const BinContainer buffer_; + size_t pos_ = 0; }; template <class BinContainer> @@ -138,15 +139,15 @@ struct MemoryStreamOut void write(const void* data, size_t len) { static_assert(sizeof(typename BinContainer::value_type) == 1, ""); //expect: bytes - const size_t oldSize = buffer.size(); - buffer.resize(oldSize + len); - std::copy(static_cast<const char*>(data), static_cast<const char*>(data) + len, buffer.begin() + oldSize); + const size_t oldSize = buffer_.size(); + buffer_.resize(oldSize + len); + std::copy(static_cast<const char*>(data), static_cast<const char*>(data) + len, buffer_.begin() + oldSize); } - const BinContainer& ref() const { return buffer; } + const BinContainer& ref() const { return buffer_; } private: - BinContainer buffer; + BinContainer buffer_; }; diff --git a/zen/shell_execute.h b/zen/shell_execute.h index ee8203c3..2f73fc38 100644 --- a/zen/shell_execute.h +++ b/zen/shell_execute.h @@ -38,7 +38,7 @@ bool shellExecuteImpl(Function fillExecInfo, ExecutionType type) SHELLEXECUTEINFO execInfo = {}; execInfo.cbSize = sizeof(execInfo); execInfo.lpVerb = nullptr; - execInfo.nShow = SW_SHOWNORMAL; + execInfo.nShow = SW_SHOW; execInfo.fMask = type == EXEC_TYPE_SYNC ? (SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NOASYNC) : 0; //don't use SEE_MASK_ASYNCOK -> different async mode than the default which returns successful despite errors! execInfo.fMask |= SEE_MASK_FLAG_NO_UI; //::ShellExecuteEx() shows a non-blocking pop-up dialog on errors -> we want a blocking one @@ -98,7 +98,7 @@ void shellExecute(const Zstring& command, ExecutionType type) //throw FileError { filePath = argv[0]; for (auto it = argv.begin() + 1; it != argv.end(); ++it) - arguments += (it != argv.begin() ? L" " : L"") + + arguments += (it == argv.begin() + 1 ? L"" : L" ") + (it->empty() || std::any_of(it->begin(), it->end(), &isWhiteSpace<wchar_t>) ? L"\"" + *it + L"\"" : *it); } diff --git a/zen/stl_tools.h b/zen/stl_tools.h index 064d5b51..48f475f3 100644 --- a/zen/stl_tools.h +++ b/zen/stl_tools.h @@ -235,7 +235,7 @@ size_t hashBytesAppend(size_t hashVal, ByteIterator first, ByteIterator last) #endif static_assert(sizeof(typename std::iterator_traits<ByteIterator>::value_type) == 1, ""); - for (; first != last; ++first) + for (; first != last; ++first) { hashVal ^= static_cast<size_t>(*first); hashVal *= prime; diff --git a/zen/string_tools.h b/zen/string_tools.h index 9b8e7328..5292dfc6 100644 --- a/zen/string_tools.h +++ b/zen/string_tools.h @@ -24,6 +24,7 @@ namespace zen { template <class Char> bool isWhiteSpace(Char ch); template <class Char> bool isDigit (Char ch); //not exactly the same as "std::isdigit" -> we consider '0'-'9' only! +template <class Char> bool isHexDigit (Char ch); template <class Char> bool isAlpha (Char ch); template <class S, class T> bool startsWith(const S& str, const T& prefix); // @@ -51,6 +52,9 @@ template <class S, class T, class U> S replaceCpy(const S& str, const T& oldT template <class S, class Num> S numberTo(const Num& number); template <class Num, class S > Num stringTo(const S& str); +std::pair<char, char> hexify (unsigned char c, bool upperCase = true); +char unhexify(char high, char low); + template <class S, class T, class Num> S printNumber(const T& format, const Num& number); //format a single number using std::snprintf() //string to string conversion: converts string-like type into char-compatible target string class @@ -101,6 +105,16 @@ bool isDigit(Char ch) //similar to implmenetation of std::::isdigit()! } +template <class Char> inline +bool isHexDigit(Char c) +{ + static_assert(IsSameType<Char, char>::value || IsSameType<Char, wchar_t>::value, ""); + return (static_cast<Char>('0') <= c && c <= static_cast<Char>('9')) || + (static_cast<Char>('A') <= c && c <= static_cast<Char>('F')) || + (static_cast<Char>('a') <= c && c <= static_cast<Char>('f')); +} + + template <> bool isAlpha(char ch) = delete; //probably not a good idea with UTF-8 anyway... template <> inline bool isAlpha(wchar_t ch) { return std::iswalpha(ch) != 0; } @@ -297,7 +311,7 @@ S replaceCpy(const S& str, const T& oldTerm, const U& newTerm, bool replaceAll) return str; const auto* const newBegin = strBegin(newTerm); - const auto* const newEnd = newBegin + strLength(newTerm); + const auto* const newEnd = newBegin + strLength(newTerm); S output; for (;;) @@ -658,6 +672,42 @@ Num stringTo(const S& str) return impl::stringTo<Num>(str, TypeTag()); } + + +inline //hexify beats "printNumber<std::string>("%02X", c)" by a nice factor of 3! +std::pair<char, char> hexify(unsigned char c, bool upperCase) +{ + auto hexifyDigit = [upperCase](int num) -> char //input [0, 15], output 0-9, A-F + { + assert(0 <= num&& num <= 15); //guaranteed by design below! + if (num <= 9) + return static_cast<char>('0' + num); //no signed/unsigned char problem here! + + if (upperCase) + return static_cast<char>('A' + (num - 10)); + else + return static_cast<char>('a' + (num - 10)); + }; + return std::make_pair(hexifyDigit(c / 16), hexifyDigit(c % 16)); +} + + +inline //unhexify beats "::sscanf(&it[3], "%02X", &tmp)" by a factor of 3000 for ~250000 calls!!! +char unhexify(char high, char low) +{ + auto unhexifyDigit = [](char hex) -> int //input 0-9, a-f, A-F; output range: [0, 15] + { + if ('0' <= hex && hex <= '9') //no signed/unsigned char problem here! + return hex - '0'; + else if ('A' <= hex && hex <= 'F') + return (hex - 'A') + 10; + else if ('a' <= hex && hex <= 'f') + return (hex - 'a') + 10; + assert(false); + return 0; + }; + return static_cast<unsigned char>(16 * unhexifyDigit(high) + unhexifyDigit(low)); //[!] convert to unsigned char first, then to char (which may be signed) +} } #endif //STRING_TOOLS_H_213458973046 diff --git a/zen/thread.h b/zen/thread.h index ac94da6a..5bb02a0e 100644 --- a/zen/thread.h +++ b/zen/thread.h @@ -460,9 +460,9 @@ std::uint64_t getThreadId() return ::GetCurrentThreadId(); //no-fail #elif defined ZEN_LINUX - //obviously "gettid()" is not available on Ubuntu/Debian/Suse => use the OpenSSL approach: - static_assert(sizeof(std::uint64_t) >= sizeof(void*), ""); - return reinterpret_cast<std::uint64_t>(static_cast<void*>(&errno)); + //obviously "gettid()" is not available on Ubuntu/Debian/Suse => use the OpenSSL approach: + static_assert(sizeof(std::uint64_t) >= sizeof(void*), ""); + return reinterpret_cast<std::uint64_t>(static_cast<void*>(&errno)); #elif defined ZEN_MAC uint64_t tid = 0; diff --git a/zen/tick_count.h b/zen/tick_count.h deleted file mode 100644 index 5ba4fd1b..00000000 --- a/zen/tick_count.h +++ /dev/null @@ -1,141 +0,0 @@ -// ***************************************************************************** -// * 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 freefilesync DOT org) - All Rights Reserved * -// ***************************************************************************** - -#ifndef TICK_COUNT_H_3807326223463457 -#define TICK_COUNT_H_3807326223463457 - -#include <cstdint> -#include "type_traits.h" -#include "basic_math.h" - -#ifdef ZEN_WIN - #include "win.h" //includes "windows.h" -#elif defined ZEN_LINUX - #include <time.h> //Posix ::clock_gettime() -#elif defined ZEN_MAC - #include <mach/mach_time.h> -#endif - - -namespace zen -{ -//a portable "GetTickCount()" using "wall time equivalent" - e.g. no jumps due to ntp time corrections -class TickVal; -int64_t dist(const TickVal& lhs, const TickVal& rhs); //use absolute difference for paranoid security: even QueryPerformanceCounter "wraps-around" at *some* time - -int64_t ticksPerSec(); //return 0 on error -TickVal getTicks(); //return invalid value on error: !TickVal::isValid() - - - - - - - - - -//############################ implementation ############################## -class TickVal -{ -public: -#ifdef ZEN_WIN - using NativeVal = LARGE_INTEGER; -#elif defined ZEN_LINUX - using NativeVal = timespec; -#elif defined ZEN_MAC - using NativeVal = uint64_t; -#endif - - TickVal() {} - explicit TickVal(const NativeVal& val) : val_(val) {} - - inline friend - int64_t dist(const TickVal& lhs, const TickVal& rhs) - { -#ifdef ZEN_WIN - return numeric::dist(lhs.val_.QuadPart, rhs.val_.QuadPart); //std::abs(a - b) can lead to overflow! -#elif defined ZEN_LINUX - //structure timespec documented with members: - // time_t tv_sec seconds - // long tv_nsec nanoseconds - const int64_t deltaSec = lhs.val_.tv_sec - rhs.val_.tv_sec; - const int64_t deltaNsec = lhs.val_.tv_nsec - rhs.val_.tv_nsec; - return numeric::abs(deltaSec * 1000000000 + deltaNsec); -#elif defined ZEN_MAC - return numeric::dist(lhs.val_, rhs.val_); -#endif - } - - inline friend - bool operator<(const TickVal& lhs, const TickVal& rhs) - { -#ifdef ZEN_WIN - return lhs.val_.QuadPart < rhs.val_.QuadPart; -#elif defined ZEN_LINUX - if (lhs.val_.tv_sec != rhs.val_.tv_sec) - return lhs.val_.tv_sec < rhs.val_.tv_sec; - return lhs.val_.tv_nsec < rhs.val_.tv_nsec; -#elif defined ZEN_MAC - return lhs.val_ < rhs.val_; -#endif - } - - bool isValid() const { return dist(*this, TickVal()) != 0; } - -private: - NativeVal val_ {}; -}; - - -inline -int64_t ticksPerSec() //return 0 on error -{ -#ifdef ZEN_WIN - LARGE_INTEGER frequency = {}; - if (!::QueryPerformanceFrequency(&frequency)) //MSDN promises: "The frequency cannot change while the system is running." - return 0; //MSDN: "This won't occur on any system that runs Windows XP or later." - static_assert(sizeof(int64_t) >= sizeof(frequency.QuadPart), ""); - return frequency.QuadPart; - -#elif defined ZEN_LINUX - return 1000000000; //precision: nanoseconds - -#elif defined ZEN_MAC - mach_timebase_info_data_t tbi = {}; - if (::mach_timebase_info(&tbi) != KERN_SUCCESS) - return 0; - //structure mach_timebase_info_data_t documented with members: - // uint32_t numer; - // uint32_t denom; - return static_cast<int64_t>(1000000000) * tbi.denom / tbi.numer; -#endif -} - - -inline -TickVal getTicks() //return !isValid() on error -{ -#ifdef ZEN_WIN - LARGE_INTEGER now = {}; - if (!::QueryPerformanceCounter(&now)) - return TickVal(); - //detailed info about QPC: https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408 - //- MSDN: "No need to set the thread affinity" - -#elif defined ZEN_LINUX - //gettimeofday() seems fine but is deprecated - timespec now = {}; - if (::clock_gettime(CLOCK_MONOTONIC_RAW, &now) != 0) //CLOCK_MONOTONIC measures time reliably across processors! - return TickVal(); - -#elif defined ZEN_MAC - uint64_t now = ::mach_absolute_time(); //can this call fail??? -#endif - return TickVal(now); -} -} - -#endif //TICK_COUNT_H_3807326223463457 |