diff options
author | Daniel Wilhelm <shieldwed@outlook.com> | 2016-10-29 11:35:33 +0200 |
---|---|---|
committer | Daniel Wilhelm <shieldwed@outlook.com> | 2016-10-29 11:35:33 +0200 |
commit | 8dd4a066ca0312ff03595b96a75abc8c6123f576 (patch) | |
tree | cf6aac6897f1ae4244b4b309627fc28902da2df9 | |
parent | 8.4 (diff) | |
download | FreeFileSync-8dd4a066ca0312ff03595b96a75abc8c6123f576.tar.gz FreeFileSync-8dd4a066ca0312ff03595b96a75abc8c6123f576.tar.bz2 FreeFileSync-8dd4a066ca0312ff03595b96a75abc8c6123f576.zip |
8.5
59 files changed, 891 insertions, 621 deletions
diff --git a/FreeFileSync/Build/Changelog.txt b/FreeFileSync/Build/Changelog.txt index ba892f7d..ebc332b5 100644 --- a/FreeFileSync/Build/Changelog.txt +++ b/FreeFileSync/Build/Changelog.txt @@ -1,5 +1,21 @@ -FreeFileSync 8.4 +FreeFileSync 8.5 ---------------- +Support multiple SSH connections per SFTP folder traversal: N times speed up +Support multiple SFTP channels per SSH connection: additional N times speed up +Fixed installer crashes by using correct DEP-compatibility +Fixed notification area icon being generated too often +Thread-safe SFTP deinitialization on shutdown +Thread-safe mini-dump creation during shutdown +Fixed case-insensitive migration of new csidl macro names +Reduced SFTP access serialization overhead +Buffer SFTP sessions independently from usage context +Detect and discard timed-out SSH sessions +Pre-empt SFTP session disconnect via dedicated SFTP cleanup thread +Run SFTP tasks directly on worker threads without helper thread overhead + + +FreeFileSync 8.4 [2016-08-12] +----------------------------- Mark temporary copies created by %local_path% read-only Fixed crash when accessing Bitvise SFTP Servers Support nanosecond-precision file time copying (Linux) @@ -8,7 +24,7 @@ Fixed crash while setting privileges during shutdown Fixed crash when failing to clean up log files Fixed EOPNOTSUPP error when copying file to gvfs Samba share (Linux) Fixed default external applications command line (Linux) -Thread-safe translation change even during app shutdown +Thread-safe translation access and change during app shutdown Don't consider port and password when comparing SFTP paths Updated translation files diff --git a/FreeFileSync/Build/Help/html/tips-and-tricks.html b/FreeFileSync/Build/Help/html/tips-and-tricks.html index 0a196ea9..8266d7a7 100644 --- a/FreeFileSync/Build/Help/html/tips-and-tricks.html +++ b/FreeFileSync/Build/Help/html/tips-and-tricks.html @@ -26,7 +26,7 @@ <div class="separation_line"></div> <div class="tip" id="sync-multiple-folders"> - Synchronize multiple folder pairs at a time: + Synchronize multiple folder pairs at a time with different configurations: </div> <img src="../images/add-folder-pair.png" alt="Add folder pair"> <div class="separation_line"></div> diff --git a/FreeFileSync/Build/Help/images/add-folder-pair.png b/FreeFileSync/Build/Help/images/add-folder-pair.png Binary files differindex 916447e8..ee525bcb 100644 --- a/FreeFileSync/Build/Help/images/add-folder-pair.png +++ b/FreeFileSync/Build/Help/images/add-folder-pair.png diff --git a/FreeFileSync/Build/Help/images/filter.png b/FreeFileSync/Build/Help/images/filter.png Binary files differindex a260649f..e6251537 100644 --- a/FreeFileSync/Build/Help/images/filter.png +++ b/FreeFileSync/Build/Help/images/filter.png diff --git a/FreeFileSync/Build/Help/images/remove-drop-down-path.png b/FreeFileSync/Build/Help/images/remove-drop-down-path.png Binary files differindex 03ee4c6d..2d41fdbb 100644 --- a/FreeFileSync/Build/Help/images/remove-drop-down-path.png +++ b/FreeFileSync/Build/Help/images/remove-drop-down-path.png diff --git a/FreeFileSync/Build/Help/images/remove-local-settings.png b/FreeFileSync/Build/Help/images/remove-local-settings.png Binary files differindex cccdcd2b..ac39ad83 100644 --- a/FreeFileSync/Build/Help/images/remove-local-settings.png +++ b/FreeFileSync/Build/Help/images/remove-local-settings.png diff --git a/FreeFileSync/Build/Help/images/select-time-span.png b/FreeFileSync/Build/Help/images/select-time-span.png Binary files differindex 4579788d..ea6be45f 100644 --- a/FreeFileSync/Build/Help/images/select-time-span.png +++ b/FreeFileSync/Build/Help/images/select-time-span.png diff --git a/FreeFileSync/Build/Help/images/setup-batch-job.png b/FreeFileSync/Build/Help/images/setup-batch-job.png Binary files differindex dccdd9dc..ef7b5b9d 100644 --- a/FreeFileSync/Build/Help/images/setup-batch-job.png +++ b/FreeFileSync/Build/Help/images/setup-batch-job.png diff --git a/FreeFileSync/Build/Help/images/sftp-login.png b/FreeFileSync/Build/Help/images/sftp-login.png Binary files differindex df2a74cd..17d64f0a 100644 --- a/FreeFileSync/Build/Help/images/sftp-login.png +++ b/FreeFileSync/Build/Help/images/sftp-login.png diff --git a/FreeFileSync/Build/Languages/arabic.lng b/FreeFileSync/Build/Languages/arabic.lng index 6f2edbf5..1d1bb221 100644 --- a/FreeFileSync/Build/Languages/arabic.lng +++ b/FreeFileSync/Build/Languages/arabic.lng @@ -7,6 +7,21 @@ <plural_definition>n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5</plural_definition> </header> +<source>%x: Invalid plural form definition</source> +<target></target> + +<source>Active connections: %x</source> +<target></target> + +<source> +<pluralform>Cannot wait on more than 1 connection at a time.</pluralform> +<pluralform>Cannot wait on more than %x connections at a time.</pluralform> +</source> +<target></target> + +<source>SFTP timeout reached.</source> +<target></target> + <source>Both sides have changed since last synchronization.</source> <target>كلا الجانبين قد تغير منذ المزامنة الأخيرة.</target> @@ -113,7 +128,7 @@ <target>تعذر العثور على المجلدات التالية:</target> <source>If this error is ignored the folders will be considered empty. Missing folders are created automatically when needed.</source> -<target></target> +<target>إذا تم تجاهل هذا الخطأ سيتم اعتبار المجلدات فارغة. يتم إنشاء المجلدات المفقودة تلقائيا عند الحاجة.</target> <source>A folder input field is empty.</source> <target>حقل إدخال خاص بمجلد فارغ.</target> @@ -155,10 +170,10 @@ <target>نسخ ملفات آمن من الفشل</target> <source>Enabled</source> -<target></target> +<target>ممكن</target> <source>Disabled</source> -<target></target> +<target>معطل</target> <source>Copy locked files</source> <target>نسخ الملفات المقفلة</target> @@ -167,22 +182,22 @@ <target>نسخ أذونات الوصول إلى الملف</target> <source>File time tolerance</source> -<target></target> +<target>التفاوت في وقت الملف</target> <source>Folder access timeout</source> -<target></target> +<target>مهلة وصول المجلد</target> <source>Run with background priority</source> -<target></target> +<target>تشغيل مع أولوية في الخلفية</target> <source>Lock directories during sync</source> -<target></target> +<target>قفل المسارات أثناء المزامنة</target> <source>Verify copied files</source> -<target></target> +<target>التحقق من الملفات التي تم نسخها</target> <source>Using non-default global settings:</source> -<target></target> +<target>استخدام إعدادات عامة غير افتراضية:</target> <source>Starting comparison</source> <target>بدأ عملية المقارنة</target> @@ -273,6 +288,12 @@ Actual: %y bytes <source>Cannot move file %x to %y.</source> <target>لا يمكن نقل الملف %x إلى %y.</target> +<source>Unable to connect to %x.</source> +<target>لا يمكن الاتصال بـ %x.</target> + +<source>Cannot open directory %x.</source> +<target>لا يمكن فتح المسار %x.</target> + <source>Cannot enumerate directory %x.</source> <target>لا يمكن تعداد المسار %x.</target> @@ -309,9 +330,6 @@ Actual: %y bytes <source>Cannot resolve symbolic link %x.</source> <target>لا يمكن حل الارتباط الرمزي %x.</target> -<source>Cannot open directory %x.</source> -<target>لا يمكن فتح المسار %x.</target> - <source>Cannot determine free disk space for %x.</source> <target>لا يمكن تحديد مساحة القرص الحرة لـ %x.</target> @@ -321,12 +339,6 @@ Actual: %y bytes <source>Incorrect command line:</source> <target>سطر أوامر خاطئ:</target> -<source>Error Code %x:</source> -<target>خطأ رقم %x:</target> - -<source>Unable to connect to %x.</source> -<target></target> - <source> <pluralform>1 byte</pluralform> <pluralform>%x bytes</pluralform> @@ -451,7 +463,7 @@ Actual: %y bytes <target>لا يمكن الوصول إلى خدمة "نسخ الظل لوحدة التخزين".</target> <source>Please run the 64-bit version of FreeFileSync to create shadow copies on this system.</source> -<target></target> +<target>الرجاء تشغيل نسخة 64-بت من FreeFileSync لإنشاء نسخ الظل على هذا النظام.</target> <source>Cannot determine volume name for %x.</source> <target>تعذر تحديد اسم الوسط %x.</target> @@ -654,7 +666,7 @@ The command is triggered if: <target>العناصر التالية لم تحل اختلافاتها، و لن يتم مزامنتها:</target> <source>The following folders are significantly different. Please check that the correct folders are selected for synchronization.</source> -<target></target> +<target>المجلدات التالية تختلف اختلافا كبيرا. يرجى التحقق من تحديد المجلدات الصحيحة لإجراء التزامن.</target> <source>Not enough free disk space available in:</source> <target>المساحة الحرة المتوفرة على القرص غير كافية:</target> @@ -748,10 +760,10 @@ The command is triggered if: <target>المسار الكامل</target> <source>Relative path</source> -<target></target> +<target>المسار النسبي</target> <source>Item name</source> -<target></target> +<target>اسم العنصر</target> <source>Size</source> <target>الحجم</target> @@ -928,7 +940,7 @@ The command is triggered if: <target>زوج المجلدات:</target> <source>Main settings:</source> -<target></target> +<target>الإعدادات الرئيسية:</target> <source>Use local settings:</source> <target>استخدام الإعدادات المحلية:</target> @@ -1098,10 +1110,10 @@ The command is triggered if: <target>الوقت المنقضي:</target> <source>Bytes:</source> -<target></target> +<target>بايت:</target> <source>Items:</source> -<target></target> +<target>العناصر:</target> <source>Synchronizing...</source> <target>مزامنة...</target> @@ -1269,7 +1281,7 @@ This guarantees a consistent state even in case of a serious error. <target>يتوفر إصدار جديد من FreeFileSync:</target> <source>Local path not available for %x.</source> -<target></target> +<target>المسار المحلي غير متوفر لـ %x.</target> <source>Confirm</source> <target>تأكيد</target> @@ -1573,16 +1585,16 @@ This guarantees a consistent state even in case of a serious error. <target>دمج تطبيقات خارجية في قائمة السياق. تتوفر وحدات الماكرو التالية:</target> <source>Full file or folder path</source> -<target></target> +<target>المسار الكامل للملف أو المجلد</target> <source>Parent folder path</source> -<target></target> +<target>مسار المجلد الحاوي</target> <source>Temporary local copy for SFTP and MTP storage</source> -<target></target> +<target>نسخة محلية مؤقتة لتخزين SFTP and MTP</target> <source>Parameters for opposite side</source> -<target></target> +<target>معلمات الجانب المعاكس</target> <source>Show hidden dialogs and warning messages again?</source> <target>إظهار التنبيهات و نوافذ الحوار المخفية مرة ثانية؟</target> @@ -1690,16 +1702,16 @@ This guarantees a consistent state even in case of a serious error. <target>تنزيل الآن؟</target> <source>FreeFileSync is up to date.</source> -<target></target> +<target>FreeFileSync بأحدث إصدار بالفعل.</target> <source>Cannot find current FreeFileSync version number online. A newer version is likely available. Check manually now?</source> -<target></target> +<target>لا يمكن إيجاد رقم النسخة الحالية لـ FreeFileSync عبر الإنترنت. ربما تتوفر نسخة جديدة، هل تريد فعل ذلك يدويا؟</target> <source>&Check</source> <target>&تحقق</target> <source>Consistency check failed for %x.</source> -<target></target> +<target>فشل فحص التناسق لـ %x.</target> <source>Cannot find system function %x.</source> <target>لا يمكن العثور على وظيفة نظام %x.</target> @@ -1708,7 +1720,7 @@ This guarantees a consistent state even in case of a serious error. <target>تعذر التسجيل لاستقبال رسائل النظام.</target> <source>Installation files are corrupt. Please reinstall FreeFileSync.</source> -<target></target> +<target>ملفات التثبيت تالفة. يرجى إعادة تثبيت FreeFileSync.</target> <source>Unable to register device notifications for %x.</source> <target>تعذر تسجيل تنبيهات الجهاز لـ %x.</target> @@ -1797,6 +1809,9 @@ This guarantees a consistent state even in case of a serious error. <source>Cannot change process I/O priorities.</source> <target>تعذر تغيير أولويات I/O للعملية.</target> +<source>Error Code %x:</source> +<target>خطأ رقم %x:</target> + <source>Checking recycle bin failed for folder %x.</source> <target>فشل تصفح سلة المهملات من أجل الملف %x.</target> @@ -1831,7 +1846,7 @@ This guarantees a consistent state even in case of a serious error. <target>حفظ الإعدادات إلى "%APPDATA%\FreeFileSync"</target> <source>Register FreeFileSync file extensions</source> -<target></target> +<target>تسجيل امتدادات الملفات لـ FreeFileSync</target> <source>Create Explorer context menu entries</source> <target>إنشاء مدخلات القوائم المحلية في متصفح الملفات</target> @@ -1858,10 +1873,10 @@ This guarantees a consistent state even in case of a serious error. <target>قائمة إبدأ</target> <source>Registering FreeFileSync file extensions</source> -<target></target> +<target>جار تسجيل امتدادات الملفات لـ FreeFileSync</target> <source>Unregistering FreeFileSync file extensions</source> -<target></target> +<target>جار إلغاء تسجيل امتدادات الملفات لـ FreeFileSync</target> <source>FreeFileSync Configuration</source> <target>تضبيطات FreeFileSync</target> @@ -1882,14 +1897,14 @@ This guarantees a consistent state even in case of a serious error. <target>شكرا على تبرعك ودعمك !</target> <source>This FreeFileSync installer for donors has reached its daily installation limit.</source> -<target></target> +<target>بلغ هذا المثبت لـ FreeFileSync للمتبرعين حد التركيب اليومي له.</target> <source>Download the regular version from the FreeFileSync homepage now?</source> -<target></target> +<target>تنزيل النسخة العادية من موقع FreeFileSync الآن ؟</target> <source>The portable version cannot install into the selected folder.</source> -<target></target> +<target>النسخة المحمولة لا يمكن تثبيتها في المجلد المحدد.</target> <source>Please choose the local installation type or select a different folder for installation.</source> -<target></target> +<target>الرجاء اختيار نوع التثبيت المحلي أو اختيار مجلد آخر للتثبيت.</target> diff --git a/FreeFileSync/Build/Languages/german.lng b/FreeFileSync/Build/Languages/german.lng index e1be7c17..6f808e12 100644 --- a/FreeFileSync/Build/Languages/german.lng +++ b/FreeFileSync/Build/Languages/german.lng @@ -273,6 +273,12 @@ Tatsächlich: %y bytes <source>Cannot move file %x to %y.</source> <target>Die Datei %x kann nicht nach %y verschoben werden.</target> +<source>Unable to connect to %x.</source> +<target>Es kann keine Verbindung zu %x aufgebaut werden.</target> + +<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> @@ -309,9 +315,6 @@ Tatsächlich: %y bytes <source>Cannot resolve symbolic link %x.</source> <target>Die symbolische Verknüpfung %x kann nicht aufgelöst werden.</target> -<source>Cannot open directory %x.</source> -<target>Das Verzeichnis %x kann nicht geöffnet werden.</target> - <source>Cannot determine free disk space for %x.</source> <target>Der freie Speicherplatz für %x konnte nicht ermittelt werden.</target> @@ -321,11 +324,32 @@ Tatsächlich: %y bytes <source>Incorrect command line:</source> <target>Ungültige Befehlszeile:</target> -<source>Error Code %x:</source> -<target>Fehlercode %x:</target> +<source>Unable to access %x.</source> +<target>Auf %x kann nicht zugegriffen werden.</target> -<source>Unable to connect to %x.</source> -<target>Es kann keine Verbindung zu %x aufgebaut werden.</target> +<source> +<pluralform>Operation timed out after 1 second.</pluralform> +<pluralform>Operation timed out after %x seconds.</pluralform> +</source> +<target> +<pluralform>Der Vorgang hat das Zeitlimit von 1 Sekunde überschritten.</pluralform> +<pluralform>Der Vorgang hat das Zeitlimit von %x Sekunden überschritten.</pluralform> +</target> + +<source> +<pluralform>Cannot wait on more than 1 connection at a time.</pluralform> +<pluralform>Cannot wait on more than %x connections at a time.</pluralform> +</source> +<target> +<pluralform>Auf mehr als 1 Verbindung kann nicht gleichzeitig gewartet werden.</pluralform> +<pluralform>Auf mehr als %x Verbindungen kann nicht gleichzeitig gewartet werden.</pluralform> +</target> + +<source>Active connections: %x</source> +<target>Aktive Verbindungen: %x</target> + +<source>Failed to open SFTP channel number %x.</source> +<target>Der SFTP Kanal Nummer %x konnte nicht geöffnet werden.</target> <source> <pluralform>1 byte</pluralform> @@ -448,7 +472,7 @@ Tatsächlich: %y bytes <target>Laufwerksname %x ist kein Teil des Dateipfades %y.</target> <source>Stop requested: Waiting for current operation to finish...</source> -<target>Unterbrechung wurde eingeleitet: Warte bis die aktuelle Operation beendet ist...</target> +<target>Unterbrechung wurde eingeleitet: Warte bis der aktuelle Vorgang beendet ist...</target> <source>Unable to create time stamp for versioning:</source> <target>Der Zeitstempel für die Versionierung kann nicht erstellt werden:</target> @@ -717,7 +741,7 @@ Die Befehlszeile wird ausgelöst, wenn: <target>&Nachfolgende Fehler ignorieren</target> <source>Retrying operation...</source> -<target>Wiederhole Operation...</target> +<target>Wiederhole Vorgang...</target> <source>Serious Error</source> <target>Schwerer Fehler</target> @@ -1057,6 +1081,27 @@ Die Befehlszeile wird ausgelöst, wenn: <source>Directory on server:</source> <target>Verzeichnis auf Server:</target> +<source>Performance settings:</source> +<target>Leistungseinstellungen:</target> + +<source>How to get best performance?</source> +<target>Wie erreicht man die beste Leistung?</target> + +<source>SSH connections for directory reading:</source> +<target>SSH Verbindungen zum Lesen des Verzeichnisses:</target> + +<source>Suggested range: [1 - 10]</source> +<target>Empfohlener Bereich: [1 - 10]</target> + +<source>SFTP channels per connection:</source> +<target>SFTP Kanäle je Verbindung:</target> + +<source>Detect server limit</source> +<target>Ermittle Serverlimit</target> + +<source>2 connections x 10 channels = 20 times faster directory reading</source> +<target>2 Verbindungen x 10 Kanäle = 20 mal schnelleres Lesen des Verzeichnisses</target> + <source>Select a directory on the server:</source> <target>Verzeichnis auf dem Server auswählen:</target> @@ -1741,6 +1786,9 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. <source>Cannot change process I/O priorities.</source> <target>Die Eingabe/Ausgabe Prioritäten für den Prozess können nicht geändert werden.</target> +<source>Error Code %x:</source> +<target>Fehlercode %x:</target> + <source>Checking recycle bin failed for folder %x.</source> <target>Die Prüfung des Papierkorbs für Ordner %x ist fehlgeschlagen.</target> diff --git a/FreeFileSync/Source/RealTimeSync/application.h b/FreeFileSync/Source/RealTimeSync/application.h index 502634ea..5b6a72a2 100644 --- a/FreeFileSync/Source/RealTimeSync/application.h +++ b/FreeFileSync/Source/RealTimeSync/application.h @@ -8,7 +8,7 @@ #define APPLICATION_H_18506781708176342677 #ifdef ZEN_WIN -#include <zen/win.h> //include before <wx/msw/wrapwin.h> + #include <zen/win.h> //include before <wx/msw/wrapwin.h> #endif #include <wx/app.h> diff --git a/FreeFileSync/Source/RealTimeSync/gui_generated.cpp b/FreeFileSync/Source/RealTimeSync/gui_generated.cpp index b15f0e5a..1b025037 100644 --- a/FreeFileSync/Source/RealTimeSync/gui_generated.cpp +++ b/FreeFileSync/Source/RealTimeSync/gui_generated.cpp @@ -192,7 +192,7 @@ MainDlgGenerated::MainDlgGenerated( wxWindow* parent, wxWindowID id, const wxStr bSizer14->Add( m_spinCtrlDelay, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 5 ); - bSizer1->Add( bSizer14, 0, wxALIGN_RIGHT|wxEXPAND|wxALL, 5 ); + bSizer1->Add( bSizer14, 0, wxALL|wxEXPAND, 5 ); m_staticline211 = new wxStaticLine( m_panelMain, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); bSizer1->Add( m_staticline211, 0, wxEXPAND, 5 ); diff --git a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp index b657f25d..241178f1 100644 --- a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp +++ b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp @@ -71,7 +71,8 @@ void MainDialog::create(const Zstring& cfgFile) MainDialog::MainDialog(wxDialog* dlg, const Zstring& cfgFileName) - : MainDlgGenerated(dlg) + : MainDlgGenerated(dlg), + lastRunConfigPath(zen::getConfigDir() + Zstr("LastRun.ffs_real")) { #ifdef ZEN_WIN new MouseMoveWindow(*this); //ownership passed to "this" @@ -98,9 +99,9 @@ MainDialog::MainDialog(wxDialog* dlg, const Zstring& cfgFileName) //--------------------------- load config values ------------------------------------ xmlAccess::XmlRealConfig newConfig; - const Zstring currentConfigFile = cfgFileName.empty() ? lastConfigFileName() : cfgFileName; + const Zstring currentConfigFile = cfgFileName.empty() ? lastRunConfigPath : cfgFileName; bool loadCfgSuccess = false; - if (!cfgFileName.empty() || fileExists(lastConfigFileName())) + if (!cfgFileName.empty() || fileExists(lastRunConfigPath)) try { std::wstring warningMsg; @@ -159,7 +160,7 @@ MainDialog::~MainDialog() try //write config to XML { - writeConfig(currentCfg, lastConfigFileName()); //throw FileError + writeConfig(currentCfg, lastRunConfigPath); //throw FileError } catch (const FileError& e) { @@ -170,18 +171,11 @@ MainDialog::~MainDialog() void MainDialog::onQueryEndSession() { - try { writeConfig(getConfiguration(), lastConfigFileName()); } //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! } -const Zstring& MainDialog::lastConfigFileName() -{ - static Zstring instance = zen::getConfigDir() + Zstr("LastRun.ffs_real"); - return instance; -} - - void MainDialog::OnShowHelp(wxCommandEvent& event) { zen::displayHelpEntry(L"realtimesync", this); @@ -311,7 +305,7 @@ void MainDialog::loadConfig(const Zstring& filepath) void MainDialog::setLastUsedConfig(const Zstring& filepath) { //set title - if (filepath == lastConfigFileName()) + if (filepath == lastRunConfigPath) { SetTitle(L"RealTimeSync - " + _("Automated Synchronization")); currentConfigFileName.clear(); diff --git a/FreeFileSync/Source/RealTimeSync/main_dlg.h b/FreeFileSync/Source/RealTimeSync/main_dlg.h index 52e72903..f540bd5d 100644 --- a/FreeFileSync/Source/RealTimeSync/main_dlg.h +++ b/FreeFileSync/Source/RealTimeSync/main_dlg.h @@ -58,11 +58,11 @@ private: void removeAddFolder(size_t pos); void clearAddFolders(); - static const Zstring& lastConfigFileName(); - std::unique_ptr<zen::FolderSelector2> dirpathFirst; std::vector<DirectoryPanel*> dirpathsExtra; //additional pairs to the standard pair + + const Zstring lastRunConfigPath; Zstring currentConfigFileName; zen::AsyncGuiQueue guiQueue; //schedule and run long-running tasks asynchronously, but process results on GUI queue diff --git a/FreeFileSync/Source/RealTimeSync/monitor.cpp b/FreeFileSync/Source/RealTimeSync/monitor.cpp index 3baf7de5..b8b9633a 100644 --- a/FreeFileSync/Source/RealTimeSync/monitor.cpp +++ b/FreeFileSync/Source/RealTimeSync/monitor.cpp @@ -10,7 +10,7 @@ #include <zen/file_access.h> #include <zen/dir_watcher.h> #include <zen/thread.h> -#include <zen/tick_count.h> +//#include <zen/tick_count.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 @@ -93,14 +93,13 @@ WaitResult waitForChanges(const std::vector<Zstring>& folderPathPhrases, //throw } } - const std::int64_t TICKS_DIR_CHECK_INTERVAL = CHECK_FOLDER_INTERVAL * ticksPerSec(); //0 on error - TickVal lastCheck = getTicks(); //0 on error + auto lastCheck = std::chrono::steady_clock::now(); for (;;) { const bool checkDirExistNow = [&]() -> bool //checking once per sec should suffice { - const TickVal now = getTicks(); //0 on error - if (dist(lastCheck, now) >= TICKS_DIR_CHECK_INTERVAL) + const auto now = std::chrono::steady_clock::now(); + if (now >= lastCheck + std::chrono::seconds(CHECK_FOLDER_INTERVAL)) { lastCheck = now; return true; diff --git a/FreeFileSync/Source/algorithm.cpp b/FreeFileSync/Source/algorithm.cpp index 0707dcfc..47a8eb72 100644 --- a/FreeFileSync/Source/algorithm.cpp +++ b/FreeFileSync/Source/algorithm.cpp @@ -19,8 +19,8 @@ #include "fs/concrete.h" #include "fs/native.h" -#ifdef ZEN_WIN -#include <zen/long_path_prefix.h> // +#ifdef ZEN_WIN + #include <zen/long_path_prefix.h> // #endif using namespace zen; @@ -1621,8 +1621,8 @@ void TempFileBuffer::createTempFiles(const std::set<FileDetails>& workLoad, Proc auto onNotifyCopyStatus = [&](std::int64_t bytesDelta) { statReporter.reportDelta(0, bytesDelta); }; AFS::copyFileTransactional(details.path, createItemPathNative(tempFilePath), //throw FileError, ErrorFileLocked false /*copyFilePermissions*/, true /*transactionalCopy*/, nullptr /*onDeleteTargetFile*/, onNotifyCopyStatus); -#ifdef ZEN_WIN - ::SetFileAttributes(applyLongPathPrefix(tempFilePath).c_str(), FILE_ATTRIBUTE_READONLY); //try to... => user get's a warning within 3rd-party apps +#ifdef ZEN_WIN + ::SetFileAttributes(applyLongPathPrefix(tempFilePath).c_str(), FILE_ATTRIBUTE_READONLY); //try to... => user get's a warning within 3rd-party apps #endif statReporter.reportDelta(1, 0); diff --git a/FreeFileSync/Source/application.cpp b/FreeFileSync/Source/application.cpp index 922ab2a3..1a86d915 100644 --- a/FreeFileSync/Source/application.cpp +++ b/FreeFileSync/Source/application.cpp @@ -26,7 +26,7 @@ #ifdef ZEN_WIN #include <zen/win_ver.h> - #include <zen/dll.h> + //#include <zen/dll.h> #include "lib/app_user_mode_id.h" #include <zen/service_notification.h> @@ -51,27 +51,9 @@ int _matherr(_Inout_ struct _exception* except) namespace { -#ifdef ZEN_WIN -void enableCrashingOnCrashes() //should be needed for 32-bit code only: http://randomascii.wordpress.com/2012/07/05/when-even-crashing-doesnt-work -{ - using GetProcessUserModeExceptionPolicyFun = BOOL (WINAPI*)(LPDWORD lpFlags); - using SetProcessUserModeExceptionPolicyFun = BOOL (WINAPI*)( DWORD dwFlags); - const DWORD EXCEPTION_SWALLOWING = 0x1; - - const SysDllFun<GetProcessUserModeExceptionPolicyFun> getProcessUserModeExceptionPolicy(L"kernel32.dll", "GetProcessUserModeExceptionPolicy"); - const SysDllFun<SetProcessUserModeExceptionPolicyFun> setProcessUserModeExceptionPolicy(L"kernel32.dll", "SetProcessUserModeExceptionPolicy"); - if (getProcessUserModeExceptionPolicy && setProcessUserModeExceptionPolicy) //available since Windows 7 SP1 - { - DWORD dwFlags = 0; - if (getProcessUserModeExceptionPolicy(&dwFlags) && (dwFlags & EXCEPTION_SWALLOWING)) - setProcessUserModeExceptionPolicy(dwFlags & ~EXCEPTION_SWALLOWING); - } -} - #ifdef _MSC_VER void crtInvalidParameterHandler(const wchar_t* expression, const wchar_t* function, const wchar_t* file, unsigned int line, uintptr_t pReserved) { assert(false); } #endif -#endif std::vector<Zstring> getCommandlineArgs(const wxApp& app) @@ -133,7 +115,6 @@ const wxEventType EVENT_ENTER_EVENT_LOOP = wxNewEventType(); bool Application::OnInit() { #ifdef ZEN_WIN - enableCrashingOnCrashes(); #ifdef _MSC_VER _set_invalid_parameter_handler(crtInvalidParameterHandler); //see comment in <zen/time.h> #endif diff --git a/FreeFileSync/Source/application.h b/FreeFileSync/Source/application.h index 1701c082..209fad0e 100644 --- a/FreeFileSync/Source/application.h +++ b/FreeFileSync/Source/application.h @@ -10,7 +10,7 @@ #include <vector> #include <zen/zstring.h> #ifdef ZEN_WIN -#include <zen/win.h> //include before <wx/msw/wrapwin.h> + #include <zen/win.h> //include before <wx/msw/wrapwin.h> #endif #include <wx/app.h> #include "lib/return_codes.h" diff --git a/FreeFileSync/Source/file_hierarchy.cpp b/FreeFileSync/Source/file_hierarchy.cpp index 79c7028a..143e51b1 100644 --- a/FreeFileSync/Source/file_hierarchy.cpp +++ b/FreeFileSync/Source/file_hierarchy.cpp @@ -12,6 +12,11 @@ using namespace zen; +#ifndef NDEBUG + const std::thread::id ObjectMgr<FileSystemObject>::mainThreadId = std::this_thread::get_id(); +#endif + + void HierarchyObject::removeEmptyRec() { bool emptyExisting = false; diff --git a/FreeFileSync/Source/file_hierarchy.h b/FreeFileSync/Source/file_hierarchy.h index efa96d03..45bf0f1d 100644 --- a/FreeFileSync/Source/file_hierarchy.h +++ b/FreeFileSync/Source/file_hierarchy.h @@ -16,8 +16,10 @@ #include <zen/zstring.h> #include <zen/fixed_list.h> #include <zen/stl_tools.h> -#include "structures.h" #include <zen/file_id_def.h> +#ifndef NDEBUG + #include <zen/thread.h> +#endif #include "structures.h" #include "lib/hard_filter.h" #include "fs/abstract.h" @@ -320,6 +322,7 @@ struct FSObjectVisitor virtual void visit(const FolderPair& folder ) = 0; }; + //inherit from this class to allow safe random access by id instead of unsafe raw pointer //allow for similar semantics like std::weak_ptr without having to use std::shared_ptr template <class T> @@ -347,7 +350,18 @@ private: ObjectMgr (const ObjectMgr& rhs) = delete; ObjectMgr& operator=(const ObjectMgr& rhs) = delete; //it's not well-defined what copying an objects means regarding object-identity in this context - static std::unordered_set<const ObjectMgr*>& activeObjects() { static std::unordered_set<const ObjectMgr*> inst; return inst; } //external linkage (even in header file!) + static std::unordered_set<const ObjectMgr*>& activeObjects() + { +#ifndef NDEBUG + assert(std::this_thread::get_id() == mainThreadId); //our global ObjectMgr is not thread-safe (and currently does not need to be!) +#endif + static std::unordered_set<const ObjectMgr*> inst; + return inst; //external linkage (even in header file!) + } + +#ifndef NDEBUG + static const std::thread::id mainThreadId; +#endif }; //------------------------------------------------------------------ diff --git a/FreeFileSync/Source/fs/abstract.h b/FreeFileSync/Source/fs/abstract.h index 59289f9c..302c99db 100644 --- a/FreeFileSync/Source/fs/abstract.h +++ b/FreeFileSync/Source/fs/abstract.h @@ -159,13 +159,13 @@ struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model t struct SymlinkInfo { - const Zstring& itemName; + const Zstring itemName; std::int64_t lastWriteTime; //number of seconds since Jan. 1st 1970 UTC }; struct FileInfo { - const Zstring& itemName; + const Zstring itemName; std::uint64_t fileSize; //unit: bytes! std::int64_t lastWriteTime; //number of seconds since Jan. 1st 1970 UTC const FileId id; //optional: empty if not supported! @@ -174,7 +174,7 @@ struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model t struct DirInfo { - const Zstring& itemName; + const Zstring itemName; }; enum HandleLink @@ -189,9 +189,9 @@ struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model t ON_ERROR_IGNORE }; - virtual void onFile (const FileInfo& fi) = 0; - virtual HandleLink onSymlink(const SymlinkInfo& si) = 0; - virtual std::unique_ptr<TraverserCallback> onDir (const DirInfo& di) = 0; + virtual void onFile (const FileInfo& fi) = 0; // + virtual HandleLink onSymlink(const SymlinkInfo& si) = 0; //throw X + virtual std::unique_ptr<TraverserCallback> onDir (const DirInfo& di) = 0; // //nullptr: ignore directory, non-nullptr: traverse into, using the (new) callback virtual HandleError reportDirError (const std::wstring& msg, size_t retryNumber) = 0; //failed directory traversal -> consider directory data at current level as incomplete! @@ -357,7 +357,7 @@ bool tryReportingDirError(Command cmd, AbstractFileSystem::TraverserCallback& ca } catch (const FileError& e) { - switch (callback.reportDirError(e.toString(), retryNumber)) + switch (callback.reportDirError(e.toString(), retryNumber)) //throw X { case AbstractFileSystem::TraverserCallback::ON_ERROR_RETRY: break; @@ -379,7 +379,7 @@ bool tryReportingItemError(Command cmd, AbstractFileSystem::TraverserCallback& c } catch (const FileError& e) { - switch (callback.reportItemError(e.toString(), retryNumber, itemName)) + switch (callback.reportItemError(e.toString(), retryNumber, itemName)) //throw X { case AbstractFileSystem::TraverserCallback::ON_ERROR_RETRY: break; diff --git a/FreeFileSync/Source/lib/binary.cpp b/FreeFileSync/Source/lib/binary.cpp index 65612e43..38fc0fda 100644 --- a/FreeFileSync/Source/lib/binary.cpp +++ b/FreeFileSync/Source/lib/binary.cpp @@ -6,7 +6,8 @@ #include "binary.h" #include <vector> -#include <zen/tick_count.h> +#include <chrono> +//#include <zen/tick_count.h> using namespace zen; using AFS = AbstractFileSystem; @@ -33,7 +34,6 @@ buffer MB/s */ const size_t BLOCK_SIZE_MAX = 16 * 1024 * 1024; -const std::int64_t TICKS_PER_SEC = ticksPerSec(); struct StreamReader @@ -50,13 +50,13 @@ struct StreamReader assert(!eof); if (eof) return; - const TickVal startTime = getTicks(); + 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 - const TickVal stopTime = getTicks(); + const auto stopTime = std::chrono::steady_clock::now(); //report bytes processed if (notifyProgress_) @@ -72,26 +72,23 @@ struct StreamReader return; } - if (TICKS_PER_SEC > 0) + size_t proposedBlockSize = 0; + const auto loopTimeMs = std::chrono::duration_cast<std::chrono::milliseconds>(stopTime - startTime).count(); + + if (loopTimeMs >= 100) + 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)) { - size_t proposedBlockSize = 0; - const std::int64_t loopTimeMs = dist(startTime, stopTime) * 1000 / TICKS_PER_SEC; //unit: [ms] - - if (loopTimeMs >= 100) - 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 (dist(lastDelayViolation, stopTime) / TICKS_PER_SEC >= 2) - { - lastDelayViolation = stopTime; - proposedBlockSize = dynamicBlockSize * 2; - } - if (loopTimeMs > 500) - proposedBlockSize = dynamicBlockSize / 2; - - if (defaultBlockSize <= proposedBlockSize && proposedBlockSize <= BLOCK_SIZE_MAX) - dynamicBlockSize = proposedBlockSize; + lastDelayViolation = stopTime; + proposedBlockSize = dynamicBlockSize * 2; } + if (loopTimeMs > 500) + proposedBlockSize = dynamicBlockSize / 2; + + if (defaultBlockSize <= proposedBlockSize && proposedBlockSize <= BLOCK_SIZE_MAX) + dynamicBlockSize = proposedBlockSize; } bool isEof() const { return eof; } @@ -102,7 +99,7 @@ private: size_t dynamicBlockSize; const std::function<void(std::int64_t bytesDelta)> notifyProgress_; size_t& unevenBytes_; - TickVal lastDelayViolation = getTicks(); + std::chrono::steady_clock::time_point lastDelayViolation = std::chrono::steady_clock::now(); bool eof = false; }; } diff --git a/FreeFileSync/Source/lib/cmp_filetime.h b/FreeFileSync/Source/lib/cmp_filetime.h index f3ee0291..d8672e7e 100644 --- a/FreeFileSync/Source/lib/cmp_filetime.h +++ b/FreeFileSync/Source/lib/cmp_filetime.h @@ -69,10 +69,6 @@ enum class TimeResult inline TimeResult compareFileTime(std::int64_t lhs, std::int64_t rhs, int tolerance, const std::vector<unsigned int>& ignoreTimeShiftMinutes) { -#if defined _MSC_VER && _MSC_VER < 1900 -#error function scope static initialization is not yet thread-safe! -#endif - //number of seconds since Jan 1st 1970 + 1 year (needn't be too precise) static const std::int64_t oneYearFromNow = std::time(nullptr) + 365 * 24 * 3600; diff --git a/FreeFileSync/Source/lib/dir_lock.cpp b/FreeFileSync/Source/lib/dir_lock.cpp index 39341577..dc4d66e6 100644 --- a/FreeFileSync/Source/lib/dir_lock.cpp +++ b/FreeFileSync/Source/lib/dir_lock.cpp @@ -5,16 +5,17 @@ // ***************************************************************************** #include "dir_lock.h" #include <map> -#include <wx/log.h> #include <memory> +//#include <chrono> #include <zen/sys_error.h> #include <zen/thread.h> #include <zen/scope_guard.h> #include <zen/guid.h> -#include <zen/tick_count.h> +//#include <zen/tick_count.h> #include <zen/file_access.h> #include <zen/file_io.h> #include <zen/optional.h> +#include <wx/log.h> #ifdef ZEN_WIN #include <zen/win_process.h> @@ -34,7 +35,6 @@ #endif using namespace zen; -using namespace std::rel_ops; namespace @@ -82,6 +82,7 @@ public: } } +private: void emitLifeSign() const //try to append one byte..., throw() { #ifdef ZEN_WIN @@ -131,8 +132,7 @@ public: #endif } -private: - const Zstring lockFilePath_; //thread local! atomic ref-count => binary value-type semantics! + const Zstring lockFilePath_; //thread-local! }; @@ -390,11 +390,9 @@ ProcessStatus getProcessStatus(const LockInformation& lockInfo) //throw FileErro } -const std::int64_t TICKS_PER_SEC = ticksPerSec(); //= 0 on error - - void waitOnDirLock(const Zstring& lockFilePath, DirLockCallback* callback) //throw FileError { + using namespace std::chrono; std::wstring infoMsg = _("Waiting while directory is locked:") + L' ' + fmtPath(lockFilePath); if (callback) @@ -426,16 +424,13 @@ void waitOnDirLock(const Zstring& lockFilePath, DirLockCallback* callback) //thr catch (FileError&) {} //logfile may be only partly written -> this is no error! std::uint64_t fileSizeOld = 0; - TickVal lastLifeSign = getTicks(); + auto lastLifeSign = steady_clock::now(); for (;;) { - const TickVal now = getTicks(); + const auto now = steady_clock::now(); const std::uint64_t fileSizeNew = getFilesize(lockFilePath); //throw FileError - if (TICKS_PER_SEC <= 0 || !lastLifeSign.isValid() || !now.isValid()) - throw FileError(L"System timer failed."); //no i18n: "should" never throw ;) - if (fileSizeNew != fileSizeOld) //received life sign from lock { fileSizeOld = fileSizeNew; @@ -443,7 +438,7 @@ void waitOnDirLock(const Zstring& lockFilePath, DirLockCallback* callback) //thr } if (lockOwnderDead || //no need to wait any longer... - dist(lastLifeSign, now) / TICKS_PER_SEC >= DETECT_ABANDONED_INTERVAL) + now >= lastLifeSign + seconds(DETECT_ABANDONED_INTERVAL)) { DirLock dummy(abandonedLockDeletionName(lockFilePath), callback); //throw FileError @@ -465,14 +460,14 @@ void waitOnDirLock(const Zstring& lockFilePath, DirLockCallback* callback) //thr for (size_t i = 0; i < 1000 * POLL_LIFE_SIGN_INTERVAL / GUI_CALLBACK_INTERVAL; ++i) { if (callback) callback->requestUiRefresh(); - std::this_thread::sleep_for(std::chrono::milliseconds(GUI_CALLBACK_INTERVAL)); + std::this_thread::sleep_for(milliseconds(GUI_CALLBACK_INTERVAL)); if (callback) { //one signal missed: it's likely this is an abandoned lock => show countdown - if (dist(lastLifeSign, now) / TICKS_PER_SEC > EMIT_LIFE_SIGN_INTERVAL) + if (now >= lastLifeSign + seconds(EMIT_LIFE_SIGN_INTERVAL + 1)) { - const int remainingSeconds = std::max<int>(0, DETECT_ABANDONED_INTERVAL - dist(lastLifeSign, getTicks()) / TICKS_PER_SEC); + const int remainingSeconds = std::max<int>(0, DETECT_ABANDONED_INTERVAL - duration_cast<seconds>(steady_clock::now() - lastLifeSign).count()); const std::wstring remSecMsg = replaceCpy(_P("1 sec", "%x sec", remainingSeconds), L"%x", toGuiString(remainingSeconds)); callback->reportStatus(infoMsg + L" | " + _("Detecting abandoned lock...") + L' ' + remSecMsg); } diff --git a/FreeFileSync/Source/lib/dir_lock.h b/FreeFileSync/Source/lib/dir_lock.h index 46d327fb..4ae4fe5d 100644 --- a/FreeFileSync/Source/lib/dir_lock.h +++ b/FreeFileSync/Source/lib/dir_lock.h @@ -29,7 +29,7 @@ RAII structure to place a directory lock against other FFS processes: - detects and resolves abandoned locks (instantly if lock is associated with local pc, else after 30 seconds) - temporary locks created during abandoned lock resolution keep "lockFilePath"'s extension - race-free (Windows, almost on Linux(NFS)) - - NOT thread-safe! (1. global LockAdmin 2. directory name aliases must be resolved sequentially!) + - NOT thread-safe! (1. global LockAdmin 2. locks for directory aliases should be created sequentially to detect duplicate locks!) */ class DirLock { diff --git a/FreeFileSync/Source/lib/hard_filter.cpp b/FreeFileSync/Source/lib/hard_filter.cpp index 24e69301..36ad6871 100644 --- a/FreeFileSync/Source/lib/hard_filter.cpp +++ b/FreeFileSync/Source/lib/hard_filter.cpp @@ -26,11 +26,17 @@ bool zen::operator<(const HardFilter& lhs, const HardFilter& rhs) namespace { -//constructing these in addFilterEntry becomes perf issue for large filter lists: -const Zstring asterisk = Zstr("*"); -const Zstring sepAsterisk = FILE_NAME_SEPARATOR + asterisk; -const Zstring asteriskSep = asterisk + FILE_NAME_SEPARATOR; -const Zstring asteriskSepAsterisk = asteriskSep + asterisk; +//constructing Zstrings of these in addFilterEntry becomes perf issue for large filter lists => use global POD! +#ifdef ZEN_WIN +const Zchar sepAsterisk[] = Zstr("\\*"); +const Zchar asteriskSep[] = Zstr("*\\"); +static_assert(FILE_NAME_SEPARATOR == '\\', ""); + +#elif defined ZEN_LINUX || defined ZEN_MAC +const Zchar sepAsterisk[] = Zstr("/*"); +const Zchar asteriskSep[] = Zstr("*/"); +static_assert(FILE_NAME_SEPARATOR == '/', ""); +#endif void addFilterEntry(const Zstring& filterPhrase, std::vector<Zstring>& masksFileFolder, std::vector<Zstring>& masksFolder) @@ -341,10 +347,20 @@ bool NameFilter::isNull(const Zstring& includePhrase, const Zstring& excludePhra bool NameFilter::isNull() const { - static NameFilter nullInstance(Zstr("*"), Zstring()); - return *this == nullInstance; + return includeMasksFileFolder.size() == 1 && includeMasksFileFolder[0] == Zstr("*") && + includeMasksFolder .empty() && + excludeMasksFileFolder.empty() && + excludeMasksFolder .empty(); + //avoid static non-POD null-NameFilter instance; instead test manually and verify function on startup: } +#ifndef NDEBUG +struct OnStartup +{ + OnStartup() { assert(NameFilter(Zstr("*"), Zstring()).isNull()); } +} startupCheckIsNullNameFilter; +#endif + bool NameFilter::cmpLessSameType(const HardFilter& other) const { diff --git a/FreeFileSync/Source/lib/help_provider.h b/FreeFileSync/Source/lib/help_provider.h index 50c3219b..a42995be 100644 --- a/FreeFileSync/Source/lib/help_provider.h +++ b/FreeFileSync/Source/lib/help_provider.h @@ -46,7 +46,7 @@ namespace impl class FfsHelpController { public: - static FfsHelpController& getInstance() + static FfsHelpController& instance() { static FfsHelpController inst; //external linkage, despite inline definition! return inst; @@ -54,7 +54,13 @@ public: void openSection(const wxString& section, wxWindow* parent) { - init(); + //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 @@ -74,30 +80,18 @@ private: FfsHelpController() {} ~FfsHelpController() { assert(!chmHlp); } - void init() //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"); - } - } - std::unique_ptr<wxCHMHelpController> chmHlp; }; #elif defined ZEN_LINUX || defined ZEN_MAC -class FfsHelpController +struct FfsHelpController { -public: - static FfsHelpController& getInstance() + static FfsHelpController& instance() { static FfsHelpController inst; return inst; } - void uninitialize() {} - void openSection(const wxString& section, wxWindow* parent) { wxHtmlModalHelp dlg(parent, utfCvrtTo<wxString>(zen::getResourceDir()) + L"Help/FreeFileSync.hhp", section, @@ -107,6 +101,7 @@ public: //-> 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() {} }; #endif } @@ -115,20 +110,20 @@ public: inline void displayHelpEntry(const wxString& topic, wxWindow* parent) { - impl::FfsHelpController::getInstance().openSection(L"html/" + topic + L".html", parent); + impl::FfsHelpController::instance().openSection(L"html/" + topic + L".html", parent); } inline void displayHelpEntry(wxWindow* parent) { - impl::FfsHelpController::getInstance().openSection(wxString(), parent); + impl::FfsHelpController::instance().openSection(wxString(), parent); } inline void uninitializeHelp() { - impl::FfsHelpController::getInstance().uninitialize(); + impl::FfsHelpController::instance().uninitialize(); } } diff --git a/FreeFileSync/Source/lib/icon_buffer.cpp b/FreeFileSync/Source/lib/icon_buffer.cpp index 9c3020f4..010298ba 100644 --- a/FreeFileSync/Source/lib/icon_buffer.cpp +++ b/FreeFileSync/Source/lib/icon_buffer.cpp @@ -53,23 +53,33 @@ wxBitmap extractWxBitmap(ImageHolder&& ih) #ifdef ZEN_WIN -#pragma warning(suppress: 4592) //"symbol will be dynamically initialized (implementation limitation)" -const std::set<Zstring, LessFilePath> linkExt { L"lnk", L"pif", L"url", L"website" }; +inline +bool hasWindowsLinkExtension(const Zstring& filePath) +{ + const Zstring ext = getFileExtension(filePath); + + return equalNoCase(ext, L"lnk") || //no need for non-POD global with these few comparisons! + equalNoCase(ext, L"pif") || + equalNoCase(ext, L"url") || + equalNoCase(ext, L"website"); +} //test for extension for non-thumbnail icons that can have a stock icon which does not have to be physically read from disc inline bool hasStandardIconExtension(const Zstring& filePath) { - static const std::set<Zstring, LessFilePath> customIconExt { L"ani", L"cur", L"exe", L"ico", L"msc", L"scr" }; //function-scope statics are not (yet) thread-safe in VC12 -#if defined _MSC_VER && _MSC_VER < 1900 -#error function scope static initialization is not yet thread-safe! -#endif + const Zstring ext = getFileExtension(filePath); - const Zstring extension(getFileExtension(filePath)); + if (equalNoCase(ext, L"ani") || //no need for non-POD global with these few comparisons! + equalNoCase(ext, L"cur") || + equalNoCase(ext, L"exe") || + equalNoCase(ext, L"ico") || + equalNoCase(ext, L"msc") || + equalNoCase(ext, L"scr")) + return false; - return customIconExt.find(extension) == customIconExt.end() && - linkExt.find(extension) == linkExt.end(); + return !hasWindowsLinkExtension(filePath); } #endif } @@ -527,8 +537,7 @@ wxBitmap IconBuffer::linkOverlayIcon(IconSize sz) bool zen::hasLinkExtension(const Zstring& filepath) { #ifdef ZEN_WIN - const Zstring& extension = getFileExtension(filepath); - return linkExt.find(extension) != linkExt.end(); + return hasWindowsLinkExtension(filepath); #elif defined ZEN_LINUX const Zstring& extension = getFileExtension(filepath); diff --git a/FreeFileSync/Source/lib/localization.cpp b/FreeFileSync/Source/lib/localization.cpp index 31c29344..2e715c59 100644 --- a/FreeFileSync/Source/lib/localization.cpp +++ b/FreeFileSync/Source/lib/localization.cpp @@ -162,26 +162,26 @@ std::vector<TranslationInfo> loadTranslations() TranslationInfo newEntry; newEntry.languageID = wxLANGUAGE_ENGLISH_US; newEntry.languageName = L"English (US)"; - newEntry.languageFile = L""; newEntry.translatorName = L"Zenju"; newEntry.languageFlag = L"flag_usa.png"; + newEntry.langFilePath = Zstr(""); locMapping.push_back(newEntry); } //search language files available - std::vector<Zstring> lngFiles; + std::vector<Zstring> lngFilePaths; traverseFolder(zen::getResourceDir() + Zstr("Languages"), [&](const zen::FileInfo& fi) //FileInfo is ambiguous on OS X { if (pathEndsWith(fi.fullPath, Zstr(".lng"))) - lngFiles.push_back(fi.fullPath); + lngFilePaths.push_back(fi.fullPath); }, nullptr, nullptr, [&](const std::wstring& errorMsg) { assert(false); }); //errors are not really critical in this context - for (const Zstring& filepath : lngFiles) + for (const Zstring& filePath : lngFilePaths) { try { - const std::string stream = loadBinContainer<std::string>(filepath, nullptr); //throw FileError + const std::string stream = loadBinContainer<std::string>(filePath, nullptr); //throw FileError lngfile::TransHeader lngHeader; lngfile::parseHeader(stream, lngHeader); //throw ParsingError @@ -203,9 +203,9 @@ std::vector<TranslationInfo> loadTranslations() TranslationInfo newEntry; newEntry.languageID = static_cast<wxLanguage>(locInfo->Language); newEntry.languageName = utfCvrtTo<std::wstring>(lngHeader.languageName); - newEntry.languageFile = utfCvrtTo<std::wstring>(filepath); newEntry.translatorName = utfCvrtTo<std::wstring>(lngHeader.translatorName); newEntry.languageFlag = utfCvrtTo<std::wstring>(lngHeader.flagFile); + newEntry.langFilePath = filePath; locMapping.push_back(newEntry); } else assert(false); @@ -401,7 +401,7 @@ public: locLng = lng; } - void release() { locale.reset(); locLng = wxLANGUAGE_UNKNOWN; } + void tearDown() { locale.reset(); locLng = wxLANGUAGE_UNKNOWN; } wxLanguage getLanguage() const { return locLng; } @@ -425,8 +425,8 @@ const std::vector<TranslationInfo>& zen::getExistingTranslations() void zen::releaseWxLocale() { - wxWidgetsLocale::getInstance().release(); - zen::setTranslator(nullptr); //good place for clean up rather than some time during static destruction: is there an actual benefit??? + wxWidgetsLocale::getInstance().tearDown(); + zen::setTranslator(nullptr); //good place for clean up rather than some time during static destruction: is this an actual benefit??? } @@ -436,38 +436,38 @@ void zen::setLanguage(wxLanguage lng) //throw FileError return; //support polling //(try to) retrieve language file - std::wstring languageFile; + Zstring langFilePath; for (const TranslationInfo& e : getExistingTranslations()) if (e.languageID == lng) { - languageFile = e.languageFile; + langFilePath = e.langFilePath; break; } //load language file into buffer - if (languageFile.empty()) //if languageFile is empty, texts will be english by default + if (langFilePath.empty()) //if languageFile is empty, texts will be english by default zen::setTranslator(nullptr); else try { - zen::setTranslator(std::make_unique<FFSTranslation>(utfCvrtTo<Zstring>(languageFile), lng)); //throw lngfile::ParsingError, parse_plural::ParsingError + zen::setTranslator(std::make_unique<FFSTranslation>(langFilePath, lng)); //throw lngfile::ParsingError, parse_plural::ParsingError } catch (lngfile::ParsingError& e) { throw FileError(replaceCpy(replaceCpy(replaceCpy(_("Error parsing file %x, row %y, column %z."), - L"%x", fmtPath(utfCvrtTo<Zstring>(languageFile))), + L"%x", fmtPath(langFilePath)), L"%y", numberTo<std::wstring>(e.row_ + 1)), L"%z", numberTo<std::wstring>(e.col_ + 1)) + L"\n\n" + e.msg_); } catch (parse_plural::ParsingError&) { - throw FileError(L"Invalid plural form definition"); //user should never see this! + throw FileError(replaceCpy<std::wstring>(L"%x: Invalid plural form definition", L"%x", fmtPath(langFilePath))); //user should never see this! } //handle RTL swapping: we need wxWidgets to do this - wxWidgetsLocale::getInstance().init(languageFile.empty() ? wxLANGUAGE_ENGLISH : lng); + wxWidgetsLocale::getInstance().init(langFilePath.empty() ? wxLANGUAGE_ENGLISH : lng); } diff --git a/FreeFileSync/Source/lib/localization.h b/FreeFileSync/Source/lib/localization.h index 0e503cf1..4f88e54e 100644 --- a/FreeFileSync/Source/lib/localization.h +++ b/FreeFileSync/Source/lib/localization.h @@ -17,9 +17,9 @@ struct TranslationInfo { wxLanguage languageID = wxLANGUAGE_UNKNOWN; std::wstring languageName; - std::wstring languageFile; std::wstring translatorName; std::wstring languageFlag; + Zstring langFilePath; }; const std::vector<TranslationInfo>& getExistingTranslations(); diff --git a/FreeFileSync/Source/lib/parse_lng.h b/FreeFileSync/Source/lib/parse_lng.h index a3038845..89cae0f0 100644 --- a/FreeFileSync/Source/lib/parse_lng.h +++ b/FreeFileSync/Source/lib/parse_lng.h @@ -193,56 +193,55 @@ struct Token class KnownTokens { public: + KnownTokens() {} //clang wants it, clang gets it + using TokenMap = std::map<Token::Type, std::string>; - static const TokenMap& getList() - { - static const TokenMap tokens = getTokens(); - return tokens; - } + const TokenMap& getList() const { return tokens; } - static std::string text(Token::Type t) + std::string text(Token::Type t) const { - auto it = getList().find(t); - return it != getList().end() ? it->second : std::string(); + auto it = tokens.find(t); + if (it != tokens.end()) + return it->second; + assert(false); + return std::string(); } private: - static TokenMap getTokens() + const TokenMap tokens = { - TokenMap tokens; //header information - tokens.emplace(Token::TK_HEADER_BEGIN, "<header>"); - tokens.emplace(Token::TK_HEADER_END, "</header>"); - tokens.emplace(Token::TK_LANG_NAME_BEGIN, "<language>"); - tokens.emplace(Token::TK_LANG_NAME_END, "</language>"); - tokens.emplace(Token::TK_TRANS_NAME_BEGIN, "<translator>"); - tokens.emplace(Token::TK_TRANS_NAME_END, "</translator>"); - tokens.emplace(Token::TK_LOCALE_NAME_BEGIN, "<locale>"); - tokens.emplace(Token::TK_LOCALE_NAME_END, "</locale>"); - tokens.emplace(Token::TK_FLAG_FILE_BEGIN, "<image>"); - tokens.emplace(Token::TK_FLAG_FILE_END, "</image>"); - tokens.emplace(Token::TK_PLURAL_COUNT_BEGIN, "<plural_count>"); - tokens.emplace(Token::TK_PLURAL_COUNT_END, "</plural_count>"); - tokens.emplace(Token::TK_PLURAL_DEF_BEGIN, "<plural_definition>"); - tokens.emplace(Token::TK_PLURAL_DEF_END, "</plural_definition>"); + { Token::TK_HEADER_BEGIN, "<header>" }, + { Token::TK_HEADER_END, "</header>" }, + { Token::TK_LANG_NAME_BEGIN, "<language>" }, + { Token::TK_LANG_NAME_END, "</language>" }, + { Token::TK_TRANS_NAME_BEGIN, "<translator>" }, + { Token::TK_TRANS_NAME_END, "</translator>" }, + { Token::TK_LOCALE_NAME_BEGIN, "<locale>" }, + { Token::TK_LOCALE_NAME_END, "</locale>" }, + { Token::TK_FLAG_FILE_BEGIN, "<image>" }, + { Token::TK_FLAG_FILE_END, "</image>" }, + { Token::TK_PLURAL_COUNT_BEGIN, "<plural_count>" }, + { Token::TK_PLURAL_COUNT_END, "</plural_count>" }, + { Token::TK_PLURAL_DEF_BEGIN, "<plural_definition>" }, + { Token::TK_PLURAL_DEF_END, "</plural_definition>" }, //item level - tokens.emplace(Token::TK_SRC_BEGIN, "<source>"); - tokens.emplace(Token::TK_SRC_END, "</source>"); - tokens.emplace(Token::TK_TRG_BEGIN, "<target>"); - tokens.emplace(Token::TK_TRG_END, "</target>"); - tokens.emplace(Token::TK_PLURAL_BEGIN, "<pluralform>"); - tokens.emplace(Token::TK_PLURAL_END, "</pluralform>"); - return tokens; - } + { Token::TK_SRC_BEGIN, "<source>" }, + { Token::TK_SRC_END, "</source>" }, + { Token::TK_TRG_BEGIN, "<target>" }, + { Token::TK_TRG_END, "</target>" }, + { Token::TK_PLURAL_BEGIN, "<pluralform>" }, + { Token::TK_PLURAL_END, "</pluralform>" }, + }; }; class Scanner { public: - Scanner(const std::string& fileStream) : stream(fileStream), pos(stream.begin()) + Scanner(const std::string& byteStream) : stream(byteStream), pos(stream.begin()) { if (zen::startsWith(stream, zen::BYTE_ORDER_MARK_UTF8)) pos += zen::strLength(zen::BYTE_ORDER_MARK_UTF8); @@ -256,7 +255,7 @@ public: if (pos == stream.end()) return Token(Token::TK_END); - for (const auto& token : KnownTokens::getList()) + for (const auto& token : tokens.getList()) if (startsWith(token.second)) { pos += token.second.size(); @@ -304,7 +303,7 @@ public: private: bool startsWithKnownTag() const { - return std::any_of(KnownTokens::getList().begin(), KnownTokens::getList().end(), + return std::any_of(tokens.getList().begin(), tokens.getList().end(), [&](const KnownTokens::TokenMap::value_type& p) { return startsWith(p.second); }); } @@ -330,6 +329,7 @@ private: const std::string stream; std::string::const_iterator pos; + const KnownTokens tokens; //no need for static non-POD! }; @@ -459,10 +459,10 @@ private: if (original.empty()) throw ParsingError(L"Translation source text is empty", scn.posRow(), scn.posCol()); - if (!isValidUtf8(original)) - throw ParsingError(L"Translation source text contains UTF-8 encoding error", scn.posRow(), scn.posCol()); - if (!isValidUtf8(translation)) - throw ParsingError(L"Translation text contains UTF-8 encoding error", scn.posRow(), scn.posCol()); + if (!isValidUtf8(original)) + throw ParsingError(L"Translation source text contains UTF-8 encoding error", scn.posRow(), scn.posCol()); + if (!isValidUtf8(translation)) + throw ParsingError(L"Translation text contains UTF-8 encoding error", scn.posRow(), scn.posCol()); if (!translation.empty()) { @@ -535,10 +535,10 @@ private: if (!contains(original.second, "%x")) throw ParsingError(L"Plural form source text does not contain %x placeholder", scn.posRow(), scn.posCol()); - if (!isValidUtf8(original.first) || !isValidUtf8(original.second)) - throw ParsingError(L"Translation source text contains UTF-8 encoding error", scn.posRow(), scn.posCol()); - if (std::any_of(translation.begin(), translation.end(), [](const std::string& pform) { return !isValidUtf8(pform); })) - throw ParsingError(L"Translation text contains UTF-8 encoding error", scn.posRow(), scn.posCol()); + if (!isValidUtf8(original.first) || !isValidUtf8(original.second)) + throw ParsingError(L"Translation source text contains UTF-8 encoding error", scn.posRow(), scn.posCol()); + if (std::any_of(translation.begin(), translation.end(), [](const std::string& pform) { return !isValidUtf8(pform); })) + throw ParsingError(L"Translation text contains UTF-8 encoding error", scn.posRow(), scn.posCol()); if (!translation.empty()) { @@ -655,35 +655,37 @@ void formatMultiLineText(std::string& text) std::string generateLng(const TranslationUnorderedList& in, const TransHeader& header) { + const KnownTokens tokens; //no need for static non-POD! + std::string out; //header - out += KnownTokens::text(Token::TK_HEADER_BEGIN) + '\n'; + out += tokens.text(Token::TK_HEADER_BEGIN) + '\n'; - out += '\t' + KnownTokens::text(Token::TK_LANG_NAME_BEGIN); + out += '\t' + tokens.text(Token::TK_LANG_NAME_BEGIN); out += header.languageName; - out += KnownTokens::text(Token::TK_LANG_NAME_END) + '\n'; + out += tokens.text(Token::TK_LANG_NAME_END) + '\n'; - out += '\t' + KnownTokens::text(Token::TK_TRANS_NAME_BEGIN); + out += '\t' + tokens.text(Token::TK_TRANS_NAME_BEGIN); out += header.translatorName; - out += KnownTokens::text(Token::TK_TRANS_NAME_END) + '\n'; + out += tokens.text(Token::TK_TRANS_NAME_END) + '\n'; - out += '\t' + KnownTokens::text(Token::TK_LOCALE_NAME_BEGIN); + out += '\t' + tokens.text(Token::TK_LOCALE_NAME_BEGIN); out += header.localeName; - out += KnownTokens::text(Token::TK_LOCALE_NAME_END) + '\n'; + out += tokens.text(Token::TK_LOCALE_NAME_END) + '\n'; - out += '\t' + KnownTokens::text(Token::TK_FLAG_FILE_BEGIN); + out += '\t' + tokens.text(Token::TK_FLAG_FILE_BEGIN); out += header.flagFile; - out += KnownTokens::text(Token::TK_FLAG_FILE_END) + '\n'; + out += tokens.text(Token::TK_FLAG_FILE_END) + '\n'; - out += '\t' + KnownTokens::text(Token::TK_PLURAL_COUNT_BEGIN); + out += '\t' + tokens.text(Token::TK_PLURAL_COUNT_BEGIN); out += zen::numberTo<std::string>(header.pluralCount); - out += KnownTokens::text(Token::TK_PLURAL_COUNT_END) + '\n'; + out += tokens.text(Token::TK_PLURAL_COUNT_END) + '\n'; - out += '\t' + KnownTokens::text(Token::TK_PLURAL_DEF_BEGIN); + out += '\t' + tokens.text(Token::TK_PLURAL_DEF_BEGIN); out += header.pluralDefinition; - out += KnownTokens::text(Token::TK_PLURAL_DEF_END) + '\n'; + out += tokens.text(Token::TK_PLURAL_DEF_END) + '\n'; - out += KnownTokens::text(Token::TK_HEADER_END) + '\n'; + out += tokens.text(Token::TK_HEADER_END) + '\n'; out += '\n'; @@ -696,13 +698,13 @@ std::string generateLng(const TranslationUnorderedList& in, const TransHeader& h formatMultiLineText(original); formatMultiLineText(translation); - out += KnownTokens::text(Token::TK_SRC_BEGIN); + out += tokens.text(Token::TK_SRC_BEGIN); out += original; - out += KnownTokens::text(Token::TK_SRC_END) + '\n'; + out += tokens.text(Token::TK_SRC_END) + '\n'; - out += KnownTokens::text(Token::TK_TRG_BEGIN); + out += tokens.text(Token::TK_TRG_BEGIN); out += translation; - out += KnownTokens::text(Token::TK_TRG_END) + '\n' + '\n'; + out += tokens.text(Token::TK_TRG_END) + '\n' + '\n'; }, [&](const TranslationPluralMap::value_type& transPlural) { @@ -713,27 +715,27 @@ std::string generateLng(const TranslationUnorderedList& in, const TransHeader& h formatMultiLineText(engSingular); formatMultiLineText(engPlural); - out += KnownTokens::text(Token::TK_SRC_BEGIN) + '\n'; - out += KnownTokens::text(Token::TK_PLURAL_BEGIN); + out += tokens.text(Token::TK_SRC_BEGIN) + '\n'; + out += tokens.text(Token::TK_PLURAL_BEGIN); out += engSingular; - out += KnownTokens::text(Token::TK_PLURAL_END) + '\n'; - out += KnownTokens::text(Token::TK_PLURAL_BEGIN); + out += tokens.text(Token::TK_PLURAL_END) + '\n'; + out += tokens.text(Token::TK_PLURAL_BEGIN); out += engPlural; - out += KnownTokens::text(Token::TK_PLURAL_END) + '\n'; - out += KnownTokens::text(Token::TK_SRC_END) + '\n'; + out += tokens.text(Token::TK_PLURAL_END) + '\n'; + out += tokens.text(Token::TK_SRC_END) + '\n'; - out += KnownTokens::text(Token::TK_TRG_BEGIN); + out += tokens.text(Token::TK_TRG_BEGIN); if (!forms.empty()) out += '\n'; for (std::string plForm : forms) { formatMultiLineText(plForm); - out += KnownTokens::text(Token::TK_PLURAL_BEGIN); + out += tokens.text(Token::TK_PLURAL_BEGIN); out += plForm; - out += KnownTokens::text(Token::TK_PLURAL_END) + '\n'; + out += tokens.text(Token::TK_PLURAL_END) + '\n'; } - out += KnownTokens::text(Token::TK_TRG_END) + '\n' + '\n'; + out += tokens.text(Token::TK_TRG_END) + '\n' + '\n'; }); assert(!zen::contains(out, "\r\n") && !zen::contains(out, "\r")); diff --git a/FreeFileSync/Source/lib/process_xml.cpp b/FreeFileSync/Source/lib/process_xml.cpp index 9ed35b78..60bded66 100644 --- a/FreeFileSync/Source/lib/process_xml.cpp +++ b/FreeFileSync/Source/lib/process_xml.cpp @@ -910,15 +910,23 @@ void readConfig(const XmlIn& in, FolderPairEnh& enhPair) in["Left" ](enhPair.folderPathPhraseLeft_); in["Right"](enhPair.folderPathPhraseRight_); - warn_static("remove after migration - 2016-07-24") - replace(enhPair.folderPathPhraseLeft_, Zstr("%csidl_MyDocuments%"), Zstr("%csidl_Documents%")); - replace(enhPair.folderPathPhraseLeft_, Zstr("%csidl_MyMusic%" ), Zstr("%csidl_Music%")); - replace(enhPair.folderPathPhraseLeft_, Zstr("%csidl_MyPictures%" ), Zstr("%csidl_Pictures%")); - replace(enhPair.folderPathPhraseLeft_, Zstr("%csidl_MyVideos%" ), Zstr("%csidl_Videos%")); - replace(enhPair.folderPathPhraseRight_, Zstr("%csidl_MyDocuments%"), Zstr("%csidl_Documents%")); - replace(enhPair.folderPathPhraseRight_, Zstr("%csidl_MyMusic%" ), Zstr("%csidl_Music%")); - replace(enhPair.folderPathPhraseRight_, Zstr("%csidl_MyPictures%" ), Zstr("%csidl_Pictures%")); - replace(enhPair.folderPathPhraseRight_, Zstr("%csidl_MyVideos%" ), Zstr("%csidl_Videos%")); + warn_static("remove after migration - 2016-07-24") + auto ciReplace = [](Zstring& pathPhrase, const Zstring& oldTerm, const Zstring& newTerm) + { + auto tmpPath = makeUpperCopy(pathPhrase); + auto tmpOld = makeUpperCopy(oldTerm); + size_t pos = tmpPath.find(tmpOld); + if (pos != Zstring::npos) + pathPhrase = Zstring(pathPhrase.c_str(), pos) + newTerm + (pathPhrase.c_str() + pos + oldTerm.size()); + }; + ciReplace(enhPair.folderPathPhraseLeft_, Zstr("%csidl_MyDocuments%"), Zstr("%csidl_Documents%")); + ciReplace(enhPair.folderPathPhraseLeft_, Zstr("%csidl_MyMusic%" ), Zstr("%csidl_Music%")); + ciReplace(enhPair.folderPathPhraseLeft_, Zstr("%csidl_MyPictures%" ), Zstr("%csidl_Pictures%")); + ciReplace(enhPair.folderPathPhraseLeft_, Zstr("%csidl_MyVideos%" ), Zstr("%csidl_Videos%")); + ciReplace(enhPair.folderPathPhraseRight_, Zstr("%csidl_MyDocuments%"), Zstr("%csidl_Documents%")); + ciReplace(enhPair.folderPathPhraseRight_, Zstr("%csidl_MyMusic%" ), Zstr("%csidl_Music%")); + ciReplace(enhPair.folderPathPhraseRight_, Zstr("%csidl_MyPictures%" ), Zstr("%csidl_Pictures%")); + ciReplace(enhPair.folderPathPhraseRight_, Zstr("%csidl_MyVideos%" ), Zstr("%csidl_Videos%")); //########################################################### //alternate comp configuration (optional) @@ -1175,10 +1183,10 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& config, int formatVer) replace(item.second, Zstr("%item_path2%"), Zstr("%local_path2%")); } } - warn_static("remove macro migration after some time! 2016-07-18") + warn_static("remove macro migration after some time! 2016-07-18") #ifdef ZEN_LINUX //small bug fix: "Browse directory" command used wrong macro in formatVer == 3 - for (auto& item : config.gui.externelApplications) - replace(item.second, Zstr("%item_folder%"), Zstr("%folder_path%")); + for (auto& item : config.gui.externelApplications) + replace(item.second, Zstr("%item_folder%"), Zstr("%folder_path%")); #endif //last update check diff --git a/FreeFileSync/Source/lib/resolve_path.cpp b/FreeFileSync/Source/lib/resolve_path.cpp index e49cd88d..86cad8ec 100644 --- a/FreeFileSync/Source/lib/resolve_path.cpp +++ b/FreeFileSync/Source/lib/resolve_path.cpp @@ -6,6 +6,7 @@ #include <zen/utf.h> #include <zen/optional.h> #include <zen/scope_guard.h> +#include <zen/globals.h> #ifdef ZEN_WIN #include <zen/long_path_prefix.h> @@ -143,13 +144,10 @@ class CsidlConstants public: using CsidlToDirMap = std::map<Zstring, Zstring, LessFilePath>; //case-insensitive! - static const CsidlToDirMap& get() + static std::shared_ptr<const CsidlToDirMap> get() { -#if defined _MSC_VER && _MSC_VER < 1900 -#error function scope static initialization is not yet thread-safe! -#endif - static const CsidlToDirMap inst = createCsidlMapping(); - return inst; + static Global<const CsidlToDirMap> inst(std::make_unique<const CsidlToDirMap>(createCsidlMapping())); + return inst.get(); } private: @@ -202,7 +200,7 @@ private: addCsidl(CSIDL_MYMUSIC, L"csidl_Music"); // C:\Users\<user>\Music addCsidl(CSIDL_COMMON_MUSIC, L"csidl_PublicMusic"); // C:\Users\Public\Music - + addCsidl(CSIDL_MYVIDEO, L"csidl_Videos"); // C:\Users\<user>\Videos addCsidl(CSIDL_COMMON_VIDEO, L"csidl_PublicVideos"); // C:\Users\Public\Videos @@ -300,12 +298,14 @@ Opt<Zstring> resolveMacro(const Zstring& macro, //macro without %-characters #ifdef ZEN_WIN //try to resolve as CSIDL value + if (auto csidlMap = CsidlConstants::get()) { - const auto& csidlMap = CsidlConstants::get(); - auto it = csidlMap.find(macro); - if (it != csidlMap.end()) + auto it = csidlMap->find(macro); + if (it != csidlMap->end()) return it->second; } + else + assert(false); //access during shut down? #endif return NoValue(); @@ -512,8 +512,11 @@ void getDirectoryAliasesRecursive(const Zstring& dirpath, std::set<Zstring, Less addEnvVar(L"Temp"); // C:\Windows\Temp //add CSIDL values: https://msdn.microsoft.com/en-us/library/bb762494 - const auto& csidlMap = CsidlConstants::get(); - envToDir.insert(csidlMap.begin(), csidlMap.end()); + + if (auto csidlMap = CsidlConstants::get()) + envToDir.insert(csidlMap->begin(), csidlMap->end()); + else + assert(false); //access during shut down? #elif defined ZEN_LINUX || defined ZEN_MAC addEnvVar("HOME"); //Linux: /home/<user> Mac: /Users/<user> @@ -609,11 +612,12 @@ namespace class NetworkConnector { public: - static NetworkConnector& getInstance() + static std::shared_ptr<NetworkConnector> getInstance() { - static NetworkConnector inst; - return inst; //meyers singleton: avoid static initialization order problem in global namespace! + static Global<NetworkConnector> inst(std::make_unique<NetworkConnector>()); + return inst.get(); //meyers singleton: avoid static initialization order problem in global namespace! } + NetworkConnector() {} struct Credentials { @@ -670,7 +674,6 @@ public: } private: - NetworkConnector() {} NetworkConnector (const NetworkConnector&) = delete; NetworkConnector& operator=(const NetworkConnector&) = delete; @@ -801,7 +804,11 @@ void zen::connectNetworkShare(const Zstring& dirpathOrig, bool allowUserInteract cred.localName = driveLetter; cred.remoteName = networkShare; cred.allowUserInteraction = allowUserInteraction; - NetworkConnector::getInstance().establishConnection(cred); //throw FileError + + if (std::shared_ptr<NetworkConnector> nc = NetworkConnector::getInstance()) + nc->establishConnection(cred); //throw FileError + else + throw FileError(replaceCpy(_("Unable to connect to %x."), L"%x", fmtPath(networkShare)), L"NetworkConnector already shut down."); } } } @@ -827,7 +834,10 @@ void zen::connectNetworkShare(const Zstring& dirpathOrig, bool allowUserInteract NetworkConnector::Credentials cred = {}; cred.remoteName = networkShare; cred.allowUserInteraction = allowUserInteraction; - NetworkConnector::getInstance().establishConnection(cred); //throw FileError + if (std::shared_ptr<NetworkConnector> nc = NetworkConnector::getInstance()) + nc->establishConnection(cred); //throw FileError + else + throw FileError(replaceCpy(_("Unable to connect to %x."), L"%x", fmtPath(networkShare)), L"NetworkConnector already shut down."); } } } diff --git a/FreeFileSync/Source/ui/custom_grid.cpp b/FreeFileSync/Source/ui/custom_grid.cpp index bf9e3a16..3d068b6b 100644 --- a/FreeFileSync/Source/ui/custom_grid.cpp +++ b/FreeFileSync/Source/ui/custom_grid.cpp @@ -292,7 +292,7 @@ protected: if (dispTp != DisplayType::NORMAL && dispTp == getRowDisplayType(row + 1)) { - wxDCPenChanger dummy2(dc, wxPen(getColorGridLine(), 1, wxSOLID)); + wxDCPenChanger dummy2(dc, getColorGridLine()); dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight() + wxPoint(1, 0)); } } diff --git a/FreeFileSync/Source/ui/gui_generated.cpp b/FreeFileSync/Source/ui/gui_generated.cpp index ce1ce2ba..a26b28a5 100644 --- a/FreeFileSync/Source/ui/gui_generated.cpp +++ b/FreeFileSync/Source/ui/gui_generated.cpp @@ -2073,7 +2073,7 @@ SftpSetupDlgGenerated::SftpSetupDlgGenerated( wxWindow* parent, wxWindowID id, c m_panel41->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); wxBoxSizer* bSizer185; - bSizer185 = new wxBoxSizer( wxHORIZONTAL ); + bSizer185 = new wxBoxSizer( wxVERTICAL ); wxFlexGridSizer* fgSizer16; fgSizer16 = new wxFlexGridSizer( 0, 2, 0, 0 ); @@ -2176,13 +2176,97 @@ SftpSetupDlgGenerated::SftpSetupDlgGenerated( wxWindow* parent, wxWindowID id, c fgSizer16->Add( bSizer206, 0, wxALIGN_CENTER_VERTICAL|wxALL|wxEXPAND, 5 ); - bSizer185->Add( fgSizer16, 1, wxALL, 5 ); + bSizer185->Add( fgSizer16, 0, wxALL|wxEXPAND, 5 ); m_panel41->SetSizer( bSizer185 ); m_panel41->Layout(); bSizer185->Fit( m_panel41 ); - bSizer134->Add( m_panel41, 1, wxEXPAND, 5 ); + bSizer134->Add( m_panel41, 0, wxEXPAND, 5 ); + + m_staticline571 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + bSizer134->Add( m_staticline571, 0, wxEXPAND, 5 ); + + wxBoxSizer* bSizer219; + bSizer219 = new wxBoxSizer( wxHORIZONTAL ); + + m_staticText1361 = new wxStaticText( this, wxID_ANY, _("Performance settings:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText1361->Wrap( -1 ); + bSizer219->Add( m_staticText1361, 0, wxALL|wxALIGN_CENTER_VERTICAL, 10 ); + + + bSizer219->Add( 0, 0, 1, wxEXPAND, 5 ); + + m_hyperlink171 = new wxHyperlinkCtrl( this, wxID_ANY, _("How to get best performance?"), wxEmptyString, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + bSizer219->Add( m_hyperlink171, 0, wxALL|wxALIGN_CENTER_VERTICAL, 10 ); + + + bSizer134->Add( bSizer219, 0, wxEXPAND, 5 ); + + m_staticline57 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + bSizer134->Add( m_staticline57, 0, wxEXPAND, 5 ); + + m_panel411 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 ); + m_panel411->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + + wxBoxSizer* bSizer1851; + bSizer1851 = new wxBoxSizer( wxVERTICAL ); + + wxFlexGridSizer* fgSizer1611; + fgSizer1611 = new wxFlexGridSizer( 0, 3, 0, 0 ); + fgSizer1611->AddGrowableCol( 1 ); + fgSizer1611->SetFlexibleDirection( wxBOTH ); + fgSizer1611->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED ); + + m_staticText12341 = new wxStaticText( m_panel411, wxID_ANY, _("SSH connections for directory reading:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText12341->Wrap( -1 ); + fgSizer1611->Add( m_staticText12341, 0, wxTOP|wxBOTTOM|wxLEFT|wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT, 5 ); + + m_spinCtrlConnectionCount = new wxSpinCtrl( m_panel411, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( 70,-1 ), wxSP_ARROW_KEYS, 1, 2000000000, 1 ); + fgSizer1611->Add( m_spinCtrlConnectionCount, 0, wxALL, 5 ); + + m_staticText138111 = new wxStaticText( m_panel411, wxID_ANY, _("Suggested range: [1 - 10]"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText138111->Wrap( -1 ); + m_staticText138111->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) ); + + fgSizer1611->Add( m_staticText138111, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); + + m_staticText1231111 = new wxStaticText( m_panel411, wxID_ANY, _("SFTP channels per connection:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText1231111->Wrap( -1 ); + fgSizer1611->Add( m_staticText1231111, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT|wxTOP|wxBOTTOM|wxLEFT, 5 ); + + m_spinCtrlChannelCount = new wxSpinCtrl( m_panel411, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( 70,-1 ), wxSP_ARROW_KEYS, 1, 2000000000, 1 ); + fgSizer1611->Add( m_spinCtrlChannelCount, 0, wxALL, 5 ); + + m_button42 = new wxButton( m_panel411, wxID_ANY, _("Detect server limit"), wxDefaultPosition, wxDefaultSize, 0 ); + fgSizer1611->Add( m_button42, 0, wxALL, 5 ); + + + bSizer1851->Add( fgSizer1611, 0, wxALL, 5 ); + + wxBoxSizer* bSizer220; + bSizer220 = new wxBoxSizer( wxHORIZONTAL ); + + m_staticText138112 = new wxStaticText( m_panel411, wxID_ANY, _("Example:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText138112->Wrap( -1 ); + m_staticText138112->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) ); + + bSizer220->Add( m_staticText138112, 0, wxRIGHT|wxALIGN_CENTER_VERTICAL, 5 ); + + m_staticText13811 = new wxStaticText( m_panel411, wxID_ANY, _("2 connections x 10 channels = 20 times faster directory reading"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText13811->Wrap( -1 ); + m_staticText13811->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) ); + + bSizer220->Add( m_staticText13811, 0, wxALIGN_CENTER_VERTICAL, 5 ); + + + bSizer1851->Add( bSizer220, 0, wxBOTTOM|wxRIGHT|wxLEFT, 10 ); + + + m_panel411->SetSizer( bSizer1851 ); + m_panel411->Layout(); + bSizer1851->Fit( m_panel411 ); + bSizer134->Add( m_panel411, 1, wxEXPAND, 5 ); m_staticline12 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); bSizer134->Add( m_staticline12, 0, wxEXPAND, 5 ); @@ -2212,6 +2296,8 @@ SftpSetupDlgGenerated::SftpSetupDlgGenerated( wxWindow* parent, wxWindowID id, c this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( SftpSetupDlgGenerated::OnClose ) ); m_checkBoxShowPassword->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( SftpSetupDlgGenerated::OnToggleShowPassword ), NULL, this ); m_buttonSelectFolder->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SftpSetupDlgGenerated::OnBrowseSftpFolder ), NULL, this ); + m_hyperlink171->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( SftpSetupDlgGenerated::OnHelpSftpPerformance ), NULL, this ); + m_button42->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SftpSetupDlgGenerated::OnDetectServerChannelLimit ), NULL, this ); m_buttonOkay->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SftpSetupDlgGenerated::OnOkay ), NULL, this ); m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SftpSetupDlgGenerated::OnCancel ), NULL, this ); } diff --git a/FreeFileSync/Source/ui/gui_generated.h b/FreeFileSync/Source/ui/gui_generated.h index 9cbd581f..adbde8c1 100644 --- a/FreeFileSync/Source/ui/gui_generated.h +++ b/FreeFileSync/Source/ui/gui_generated.h @@ -505,6 +505,19 @@ class SftpSetupDlgGenerated : public wxDialog wxStaticText* m_staticText1232; wxTextCtrl* m_textCtrlServerPath; wxButton* m_buttonSelectFolder; + wxStaticLine* m_staticline571; + wxStaticText* m_staticText1361; + wxHyperlinkCtrl* m_hyperlink171; + wxStaticLine* m_staticline57; + wxPanel* m_panel411; + wxStaticText* m_staticText12341; + wxSpinCtrl* m_spinCtrlConnectionCount; + wxStaticText* m_staticText138111; + wxStaticText* m_staticText1231111; + wxSpinCtrl* m_spinCtrlChannelCount; + wxButton* m_button42; + wxStaticText* m_staticText138112; + wxStaticText* m_staticText13811; wxStaticLine* m_staticline12; wxBoxSizer* bSizerStdButtons; wxButton* m_buttonOkay; @@ -514,6 +527,8 @@ class SftpSetupDlgGenerated : public wxDialog virtual void OnClose( wxCloseEvent& event ) { event.Skip(); } virtual void OnToggleShowPassword( wxCommandEvent& event ) { event.Skip(); } virtual void OnBrowseSftpFolder( wxCommandEvent& event ) { event.Skip(); } + virtual void OnHelpSftpPerformance( wxHyperlinkEvent& event ) { event.Skip(); } + virtual void OnDetectServerChannelLimit( wxCommandEvent& event ) { event.Skip(); } virtual void OnOkay( wxCommandEvent& event ) { event.Skip(); } virtual void OnCancel( wxCommandEvent& event ) { event.Skip(); } diff --git a/FreeFileSync/Source/ui/main_dlg.cpp b/FreeFileSync/Source/ui/main_dlg.cpp index 00e64afd..60bf05ec 100644 --- a/FreeFileSync/Source/ui/main_dlg.cpp +++ b/FreeFileSync/Source/ui/main_dlg.cpp @@ -301,7 +301,9 @@ const int TOP_BUTTON_OPTIMAL_WIDTH = 180; void updateTopButton(wxBitmapButton& btn, const wxBitmap& bmp, const wxString& variantName, bool makeGrey) { wxImage labelImage = createImageFromText(btn.GetLabel(), btn.GetFont(), wxSystemSettings::GetColour(makeGrey ? wxSYS_COLOUR_GRAYTEXT : wxSYS_COLOUR_BTNTEXT)); - wxImage variantImage = createImageFromText(variantName, wxFont(wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxBOLD), wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT)); + wxImage variantImage = createImageFromText(variantName, + wxFont(wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD), + wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT)); wxImage descrImage = stackImages(labelImage, variantImage, ImageStackLayout::VERTICAL, ImageStackAlignment::CENTER); const wxImage& iconImage = makeGrey ? greyScale(bmp.ConvertToImage()) : bmp.ConvertToImage(); @@ -341,6 +343,12 @@ xmlAccess::XmlGlobalSettings loadGlobalConfig(const Zstring& globalConfigFile) / } +Zstring MainDialog::getLastRunConfigPath() +{ + return zen::getConfigDir() + Zstr("LastRun.ffs_gui"); +} + + void MainDialog::create(const Zstring& globalConfigFile) { using namespace xmlAccess; @@ -375,8 +383,9 @@ void MainDialog::create(const Zstring& globalConfigFile) if (cfgFilePaths.empty()) { - if (zen::fileExists(lastRunConfigName())) //3. try to load auto-save config - cfgFilePaths.push_back(lastRunConfigName()); + const Zstring lastRunConfigFilePath = getLastRunConfigPath(); + if (zen::fileExists(lastRunConfigFilePath)) //3. try to load auto-save config + cfgFilePaths.push_back(lastRunConfigFilePath); } XmlGuiConfig guiCfg; //structure to receive gui settings with default values @@ -453,6 +462,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>()) // { @@ -833,7 +843,7 @@ MainDialog::~MainDialog() try //save "LastRun.ffs_gui" { - writeConfig(getConfig(), lastRunConfigName()); //throw FileError + writeConfig(getConfig(), lastRunConfigPath); //throw FileError } //don't annoy users on read-only drives: it's enough to show a single error message when saving global config catch (const FileError&) {} @@ -860,7 +870,7 @@ void MainDialog::onQueryEndSession() try { writeConfig(getGlobalCfgBeforeExit(), globalConfigFile_); } catch (const FileError&) {} //we try our best to do something useful in this extreme situation - no reason to notify or even log errors here! - try { writeConfig(getConfig(), lastRunConfigName()); } + try { writeConfig(getConfig(), lastRunConfigPath); } catch (const FileError&) {} } @@ -943,7 +953,7 @@ void MainDialog::setGlobalCfgOnInit(const xmlAccess::XmlGlobalSettings& globalSe std::reverse(cfgFilePaths.begin(), cfgFilePaths.end()); //list is stored with last used files first in xml, however addFileToCfgHistory() needs them last!!! - cfgFilePaths.push_back(lastRunConfigName()); //make sure <Last session> is always part of history list (if existing) + cfgFilePaths.push_back(lastRunConfigPath); //make sure <Last session> is always part of history list (if existing) addFileToCfgHistory(cfgFilePaths); removeObsoleteCfgHistoryItems(cfgFilePaths); //remove non-existent items (we need this only on startup) @@ -2727,7 +2737,7 @@ void MainDialog::addFileToCfgHistory(const std::vector<Zstring>& filepaths) wxString label; unsigned int newPos = 0; - if (equalFilePath(filepath, lastRunConfigName())) + if (equalFilePath(filepath, lastRunConfigPath)) label = lastSessionLabel; else { @@ -2808,7 +2818,7 @@ void MainDialog::removeCfgHistoryItems(const std::vector<Zstring>& filePaths) void MainDialog::updateUnsavedCfgStatus() { - const Zstring activeCfgFilename = activeConfigFiles.size() == 1 && !equalFilePath(activeConfigFiles[0], lastRunConfigName()) ? activeConfigFiles[0] : Zstring(); + const Zstring activeCfgFilename = activeConfigFiles.size() == 1 && !equalFilePath(activeConfigFiles[0], lastRunConfigPath) ? activeConfigFiles[0] : Zstring(); const bool haveUnsavedCfg = lastConfigurationSaved != getConfig(); @@ -2849,7 +2859,7 @@ void MainDialog::updateUnsavedCfgStatus() void MainDialog::OnConfigSave(wxCommandEvent& event) { - const Zstring activeCfgFilename = activeConfigFiles.size() == 1 && !equalFilePath(activeConfigFiles[0], lastRunConfigName()) ? activeConfigFiles[0] : Zstring(); + const Zstring activeCfgFilename = activeConfigFiles.size() == 1 && !equalFilePath(activeConfigFiles[0], lastRunConfigPath) ? activeConfigFiles[0] : Zstring(); using namespace xmlAccess; @@ -2905,7 +2915,7 @@ bool MainDialog::trySaveConfig(const Zstring* guiFilename) //return true if save } else { - Zstring defaultFileName = activeConfigFiles.size() == 1 && !equalFilePath(activeConfigFiles[0], lastRunConfigName()) ? activeConfigFiles[0] : Zstr("SyncSettings.ffs_gui"); + Zstring defaultFileName = activeConfigFiles.size() == 1 && !equalFilePath(activeConfigFiles[0], lastRunConfigPath) ? activeConfigFiles[0] : Zstr("SyncSettings.ffs_gui"); //attention: activeConfigFiles 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_gui"); @@ -2946,7 +2956,7 @@ bool MainDialog::trySaveBatchConfig(const Zstring* batchFileToUpdate) //essentially behave like trySaveConfig(): the collateral damage of not saving GUI-only settings "m_bpButtonViewTypeSyncAction" is negliable - const Zstring activeCfgFilename = activeConfigFiles.size() == 1 && !equalFilePath(activeConfigFiles[0], lastRunConfigName()) ? activeConfigFiles[0] : Zstring(); + const Zstring activeCfgFilename = activeConfigFiles.size() == 1 && !equalFilePath(activeConfigFiles[0], lastRunConfigPath) ? activeConfigFiles[0] : Zstring(); const XmlGuiConfig guiCfg = getConfig(); //prepare batch config: reuse existing batch-specific settings from file if available @@ -3031,7 +3041,7 @@ bool MainDialog::saveOldConfig() //return false on user abort { if (lastConfigurationSaved != getConfig()) { - const Zstring activeCfgFilename = activeConfigFiles.size() == 1 && !equalFilePath(activeConfigFiles[0], lastRunConfigName()) ? activeConfigFiles[0] : Zstring(); + const Zstring activeCfgFilename = activeConfigFiles.size() == 1 && !equalFilePath(activeConfigFiles[0], lastRunConfigPath) ? activeConfigFiles[0] : Zstring(); //notify user about changed settings if (globalCfg.optDialogs.popupOnConfigChange) @@ -3091,7 +3101,7 @@ bool MainDialog::saveOldConfig() //return false on user abort void MainDialog::OnConfigLoad(wxCommandEvent& event) { - const Zstring activeCfgFilename = activeConfigFiles.size() == 1 && !equalFilePath(activeConfigFiles[0], lastRunConfigName()) ? activeConfigFiles[0] : Zstring(); + const Zstring activeCfgFilename = activeConfigFiles.size() == 1 && !equalFilePath(activeConfigFiles[0], lastRunConfigPath) ? activeConfigFiles[0] : Zstring(); wxFileDialog filePicker(this, wxString(), @@ -3367,13 +3377,6 @@ xmlAccess::XmlGuiConfig MainDialog::getConfig() const } -const Zstring& MainDialog::lastRunConfigName() -{ - static Zstring instance = zen::getConfigDir() + Zstr("LastRun.ffs_gui"); - return instance; -} - - void MainDialog::updateGuiDelayedIf(bool condition) { const int delay = 400; @@ -3916,7 +3919,7 @@ void MainDialog::OnStartSync(wxCommandEvent& event) try { //PERF_START; - const Zstring activeCfgFilename = activeConfigFiles.size() == 1 && !equalFilePath(activeConfigFiles[0], lastRunConfigName()) ? activeConfigFiles[0] : Zstring(); + const Zstring activeCfgFilename = activeConfigFiles.size() == 1 && !equalFilePath(activeConfigFiles[0], lastRunConfigPath) ? activeConfigFiles[0] : Zstring(); const auto& guiCfg = getConfig(); diff --git a/FreeFileSync/Source/ui/main_dlg.h b/FreeFileSync/Source/ui/main_dlg.h index 46de3e09..1bd621c2 100644 --- a/FreeFileSync/Source/ui/main_dlg.h +++ b/FreeFileSync/Source/ui/main_dlg.h @@ -82,8 +82,6 @@ private: bool trySaveBatchConfig(const Zstring* batchFileToUpdate); // bool saveOldConfig(); //return false on user abort - static const Zstring& lastRunConfigName(); - void updateGlobalFilterButton(); void initViewFilterButtons(); @@ -295,6 +293,9 @@ private: std::vector<Zstring> activeConfigFiles; //name of currently loaded config file (may be more than 1) xmlAccess::XmlGuiConfig lastConfigurationSaved; //support for: "Save changed configuration?" dialog + + static Zstring getLastRunConfigPath(); + const Zstring lastRunConfigPath; //let's not use another static... //------------------------------------- //UI view of FolderComparison structure (partially owns folderCmp) diff --git a/FreeFileSync/Source/ui/on_completion_box.cpp b/FreeFileSync/Source/ui/on_completion_box.cpp index c7346c61..898178dc 100644 --- a/FreeFileSync/Source/ui/on_completion_box.cpp +++ b/FreeFileSync/Source/ui/on_completion_box.cpp @@ -19,9 +19,9 @@ using namespace zen; namespace { -const std::wstring cmdTxtCloseProgressDlg = L"Close progress dialog"; //special command //mark for extraction: _("Close progress dialog") +std::wstring getCmdTxtCloseProgressDlg() { return L"Close progress dialog"; } //special command //mark for extraction: _("Close progress dialog") -const std::wstring separationLine(L"---------------------------------------------------------------------------------------------------------------"); +std::wstring getSeparationLine() { return L"---------------------------------------------------------------------------------------------------------------"; } std::vector<std::pair<std::wstring, Zstring>> getDefaultCommands() //(gui name/command) pairs @@ -65,7 +65,7 @@ const wxEventType wxEVT_VALIDATE_USER_SELECTION = wxNewEventType(); bool isCloseProgressDlgCommand(const Zstring& value) { - return trimCpy(utfCvrtTo<std::wstring>(value)) == cmdTxtCloseProgressDlg; + return trimCpy(utfCvrtTo<std::wstring>(value)) == getCmdTxtCloseProgressDlg(); } @@ -99,8 +99,8 @@ void OnCompletionBox::addItemHistory() { const Zstring command = trimCpy(getValue()); - if (command == utfCvrtTo<Zstring>(separationLine) || //do not add sep. line - command == utfCvrtTo<Zstring>(cmdTxtCloseProgressDlg) || //do not add special command + if (command == utfCvrtTo<Zstring>(getSeparationLine()) || //do not add sep. line + command == utfCvrtTo<Zstring>(getCmdTxtCloseProgressDlg()) || //do not add special command command.empty()) return; @@ -123,8 +123,8 @@ Zstring OnCompletionBox::getValue() const { auto value = trimCpy(copyStringTo<std::wstring>(GetValue())); - if (value == implementation::translate(cmdTxtCloseProgressDlg)) //undo translation for config file storage - value = cmdTxtCloseProgressDlg; + if (value == implementation::translate(getCmdTxtCloseProgressDlg())) //undo translation for config file storage + value = getCmdTxtCloseProgressDlg(); return utfCvrtTo<Zstring>(value); } @@ -134,8 +134,8 @@ void OnCompletionBox::setValue(const Zstring& value) { auto tmp = trimCpy(utfCvrtTo<std::wstring>(value)); - if (tmp == cmdTxtCloseProgressDlg) - tmp = implementation::translate(cmdTxtCloseProgressDlg); //have this symbolic constant translated properly + if (tmp == getCmdTxtCloseProgressDlg()) + tmp = implementation::translate(getCmdTxtCloseProgressDlg()); //have this symbolic constant translated properly setValueAndUpdateList(tmp); } @@ -149,7 +149,7 @@ void OnCompletionBox::setValueAndUpdateList(const std::wstring& value) std::deque<std::wstring> items; //1. special command - items.push_back(implementation::translate(cmdTxtCloseProgressDlg)); + items.push_back(implementation::translate(getCmdTxtCloseProgressDlg())); //2. built in commands for (const auto& item : defaultCommands) @@ -158,7 +158,7 @@ void OnCompletionBox::setValueAndUpdateList(const std::wstring& value) //3. history elements if (!history_.empty()) { - items.push_back(separationLine); + items.push_back(getSeparationLine()); for (const Zstring& hist : history_) items.push_back(utfCvrtTo<std::wstring>(hist)); std::sort(items.end() - history_.size(), items.end()); @@ -170,7 +170,7 @@ void OnCompletionBox::setValueAndUpdateList(const std::wstring& value) if (std::find(items.begin(), items.end(), value) == items.end()) { if (!value.empty()) - items.push_front(separationLine); + items.push_front(getSeparationLine()); items.push_front(value); } @@ -198,7 +198,7 @@ void OnCompletionBox::OnValidateSelection(wxCommandEvent& event) { const auto value = copyStringTo<std::wstring>(GetValue()); - if (value == separationLine) + if (value == getSeparationLine()) return setValueAndUpdateList(std::wstring()); for (const auto& item : defaultCommands) diff --git a/FreeFileSync/Source/ui/progress_indicator.cpp b/FreeFileSync/Source/ui/progress_indicator.cpp index b7b629b7..4dbd40fc 100644 --- a/FreeFileSync/Source/ui/progress_indicator.cpp +++ b/FreeFileSync/Source/ui/progress_indicator.cpp @@ -653,7 +653,7 @@ public: wxRect rectTmp = rect; //-------------- draw item separation line ----------------- - wxDCPenChanger dummy2(dc, wxPen(getColorGridLine(), 1, wxSOLID)); + wxDCPenChanger dummy2(dc, getColorGridLine()); const bool drawBottomLine = [&] //don't separate multi-line messages { if (msgView_) diff --git a/FreeFileSync/Source/ui/progress_indicator.h b/FreeFileSync/Source/ui/progress_indicator.h index e8742d31..4488ba3b 100644 --- a/FreeFileSync/Source/ui/progress_indicator.h +++ b/FreeFileSync/Source/ui/progress_indicator.h @@ -11,7 +11,7 @@ #include <zen/error_log.h> #include <zen/zstring.h> #ifdef ZEN_WIN -#include <zen/win.h> //include before <wx/msw/wrapwin.h> + #include <zen/win.h> //include before <wx/msw/wrapwin.h> #endif #include <wx/frame.h> #include "../lib/status_handler.h" diff --git a/FreeFileSync/Source/ui/small_dlgs.cpp b/FreeFileSync/Source/ui/small_dlgs.cpp index 634aa26b..18606ee0 100644 --- a/FreeFileSync/Source/ui/small_dlgs.cpp +++ b/FreeFileSync/Source/ui/small_dlgs.cpp @@ -5,11 +5,11 @@ // ***************************************************************************** #include "small_dlgs.h" -#include <wx/wupdlock.h> +#include <chrono> #include <zen/format_unit.h> #include <zen/build_info.h> -#include <zen/tick_count.h> #include <zen/stl_tools.h> +#include <wx/wupdlock.h> #include <wx+/choice_enum.h> #include <wx+/bitmap_button.h> #include <wx+/rtl.h> @@ -162,8 +162,11 @@ private: void OnOkay (wxCommandEvent& event) override; void OnCancel(wxCommandEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } void OnClose (wxCloseEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } + + void OnDetectServerChannelLimit(wxCommandEvent& event) override; void OnToggleShowPassword(wxCommandEvent& event) override; void OnBrowseSftpFolder (wxCommandEvent& event) override; + void OnHelpSftpPerformance(wxHyperlinkEvent& event) override { displayHelpEntry(L"synchronize-with-sftp", this); } std::pair<SftpLoginInfo, Zstring> getSftpLogin() const; @@ -195,6 +198,8 @@ SftpSetupDlg::SftpSetupDlg(wxWindow* parent, Zstring& folderPathPhrase) : SftpSe m_textCtrlUserName ->ChangeValue(utfCvrtTo<wxString>(login.username)); m_textCtrlPasswordHidden->ChangeValue(utfCvrtTo<wxString>(login.password)); m_textCtrlServerPath ->ChangeValue(utfCvrtTo<wxString>(serverRelPath)); + m_spinCtrlConnectionCount->SetValue(login.traverserConnectionCount); + m_spinCtrlChannelCount ->SetValue(login.traverserChannelsPerConnection); } GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize() @@ -205,6 +210,20 @@ SftpSetupDlg::SftpSetupDlg(wxWindow* parent, Zstring& folderPathPhrase) : SftpSe } +void SftpSetupDlg::OnDetectServerChannelLimit(wxCommandEvent& event) +{ + try + { + const int channelCountMax = getServerMaxChannelsPerConnection(getSftpLogin().first); //throw FileError + m_spinCtrlChannelCount->SetValue(channelCountMax); + } + catch (const FileError& e) + { + showNotificationDialog(this, DialogInfoType::ERROR2, PopupDialogCfg().setDetailInstructions(e.toString())); + } +} + + void SftpSetupDlg::OnToggleShowPassword(wxCommandEvent& event) { if (m_checkBoxShowPassword->GetValue()) @@ -230,6 +249,8 @@ std::pair<SftpLoginInfo, Zstring /*host-relative path*/> SftpSetupDlg::getSftpLo login.port = stringTo<int> (m_textCtrlPort ->GetValue()); //0 if empty login.username = utfCvrtTo<Zstring>(m_textCtrlUserName->GetValue()); login.password = utfCvrtTo<Zstring>((m_checkBoxShowPassword->GetValue() ? m_textCtrlPasswordVisible : m_textCtrlPasswordHidden)->GetValue()); + login.traverserConnectionCount = m_spinCtrlConnectionCount->GetValue(); + login.traverserChannelsPerConnection = m_spinCtrlChannelCount ->GetValue(); Zstring serverRelPath = utfCvrtTo<Zstring>(m_textCtrlServerPath->GetValue()); @@ -420,7 +441,7 @@ private: const std::vector<const FileSystemObject*>& rowsToDeleteOnLeft; const std::vector<const FileSystemObject*>& rowsToDeleteOnRight; - const TickVal tickCountStartup; + const std::chrono::steady_clock::time_point dlgStartTime = std::chrono::steady_clock::now(); //output-only parameters: bool& useRecycleBinOut; @@ -434,7 +455,6 @@ DeleteDialog::DeleteDialog(wxWindow* parent, DeleteDlgGenerated(parent), rowsToDeleteOnLeft(rowsOnLeft), rowsToDeleteOnRight(rowsOnRight), - tickCountStartup(getTicks()), useRecycleBinOut(useRecycleBin) { #ifdef ZEN_WIN @@ -504,11 +524,8 @@ 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! - const TickVal now = getTicks(); //0 on error - std::int64_t tps = ticksPerSec(); // - if (now.isValid() && tickCountStartup.isValid() && tps != 0) - if (dist(tickCountStartup, now) * 1000 / tps < 50) - return; + if (std::chrono::steady_clock::now() < dlgStartTime + std::chrono::milliseconds(50)) + return; useRecycleBinOut = m_checkBoxUseRecycler->GetValue(); diff --git a/FreeFileSync/Source/ui/tray_icon.cpp b/FreeFileSync/Source/ui/tray_icon.cpp index 01863265..ff315826 100644 --- a/FreeFileSync/Source/ui/tray_icon.cpp +++ b/FreeFileSync/Source/ui/tray_icon.cpp @@ -49,21 +49,41 @@ void fillRange(wxImage& img, int pixelFirst, int pixelLast, const wxColor& col) } } +//------------------------------------------------------------------------------------------------ -wxIcon generateProgressIcon(const wxImage& logo, double fraction) //generate icon with progress indicator +enum Selection { - if (!logo.IsOk() || logo.GetWidth() <= 0 || logo.GetHeight() <= 0) + CONTEXT_RESTORE = 1 //wxWidgets: "A MenuItem ID of zero does not work under Mac" +}; +} + + +//generate icon with progress indicator +class FfsTrayIcon::ProgressIconGenerator +{ +public: + ProgressIconGenerator(const wxImage& logo) : logo_(logo) {} + + wxIcon get(double fraction); + +private: + const wxImage logo_; + wxIcon iconBuf_; + int startPixBuf_ = -1; +}; + + +wxIcon FfsTrayIcon::ProgressIconGenerator::get(double fraction) +{ + if (!logo_.IsOk() || logo_.GetWidth() <= 0 || logo_.GetHeight() <= 0) return wxIcon(); - const int pixelCount = logo.GetWidth() * logo.GetHeight(); + const int pixelCount = logo_.GetWidth() * logo_.GetHeight(); const int startFillPixel = numeric::clampCpy(numeric::round(fraction * pixelCount), 0, pixelCount); - //minor optimization - static std::pair<int, wxIcon> buffer = std::make_pair(-1, wxNullIcon); - - if (buffer.first != startFillPixel) + if (startPixBuf_ != startFillPixel) { - wxImage genImage(logo.Copy()); //workaround wxWidgets' screwed-up design from hell: their copy-construction implements reference-counting WITHOUT copy-on-write! + wxImage genImage(logo_.Copy()); //workaround wxWidgets' screwed-up design from hell: their copy-construction implements reference-counting WITHOUT copy-on-write! //gradually make FFS icon brighter while nearing completion zen::brighten(genImage, -200 * (1 - fraction)); @@ -72,10 +92,10 @@ wxIcon generateProgressIcon(const wxImage& logo, double fraction) //generate ico if (startFillPixel <= pixelCount - genImage.GetWidth()) { /* - -------- - ---bbbbb - bbbbSyyy S : start yellow remainder - yyyyyyyy + -------- + ---bbbbb + bbbbSyyy S : start yellow remainder + yyyyyyyy */ int bStart = startFillPixel - genImage.GetWidth(); if (bStart % genImage.GetWidth() != 0) //add one more black pixel, see ascii-art @@ -86,10 +106,10 @@ wxIcon generateProgressIcon(const wxImage& logo, double fraction) //generate ico { //special handling for last row /* - -------- - -------- - ---bbbbb - ---bSyyy S : start yellow remainder + -------- + -------- + ---bbbbb + ---bSyyy S : start yellow remainder */ int bStart = startFillPixel - genImage.GetWidth() - 1; int bEnd = (bStart / genImage.GetWidth() + 1) * genImage.GetWidth(); @@ -101,18 +121,11 @@ wxIcon generateProgressIcon(const wxImage& logo, double fraction) //generate ico //fill yellow remainder fillRange(genImage, startFillPixel, pixelCount, wxColor(240, 200, 0)); - buffer.second.CopyFromBitmap(wxBitmap(genImage)); + iconBuf_.CopyFromBitmap(wxBitmap(genImage)); + startPixBuf_ = startFillPixel; } - return buffer.second; -} - -//------------------------------------------------------------------------------------------------ - -enum Selection -{ - CONTEXT_RESTORE = 1 //wxWidgets: "A MenuItem ID of zero does not work under Mac" -}; + return iconBuf_; } @@ -187,14 +200,13 @@ private: FfsTrayIcon::FfsTrayIcon(const std::function<void()>& onRequestResume) : trayIcon(new TaskBarImpl(onRequestResume)), - activeFraction(1), //show FFS logo by default #if defined ZEN_WIN || defined ZEN_MAC //16x16 seems to be the only size that is shown correctly on OS X - logo(getResourceImage(L"FFS_tray_16x16").ConvertToImage()) + iconGenerator(std::make_unique<ProgressIconGenerator>(getResourceImage(L"FFS_tray_16x16").ConvertToImage())) #elif defined ZEN_LINUX - logo(getResourceImage(L"FFS_tray_24x24").ConvertToImage()) + iconGenerator(std::make_unique<ProgressIconGenerator>(getResourceImage(L"FFS_tray_24x24").ConvertToImage())) #endif { - trayIcon->SetIcon(generateProgressIcon(logo, activeFraction), L"FreeFileSync"); + trayIcon->SetIcon(iconGenerator->get(activeFraction), activeToolTip); } @@ -223,12 +235,12 @@ FfsTrayIcon::~FfsTrayIcon() void FfsTrayIcon::setToolTip(const wxString& toolTip) { activeToolTip = toolTip; - trayIcon->SetIcon(generateProgressIcon(logo, activeFraction), activeToolTip); //another wxWidgets design bug: non-orthogonal method! + trayIcon->SetIcon(iconGenerator->get(activeFraction), activeToolTip); //another wxWidgets design bug: non-orthogonal method! } void FfsTrayIcon::setProgress(double fraction) { activeFraction = fraction; - trayIcon->SetIcon(generateProgressIcon(logo, activeFraction), activeToolTip); + trayIcon->SetIcon(iconGenerator->get(activeFraction), activeToolTip); } diff --git a/FreeFileSync/Source/ui/tray_icon.h b/FreeFileSync/Source/ui/tray_icon.h index 3fe4f6e0..0e2bbbb9 100644 --- a/FreeFileSync/Source/ui/tray_icon.h +++ b/FreeFileSync/Source/ui/tray_icon.h @@ -8,6 +8,7 @@ #define TRAY_ICON_H_84217830427534285 #include <functional> +#include <memory> #include <wx/image.h> /* @@ -37,9 +38,11 @@ private: class TaskBarImpl; TaskBarImpl* trayIcon; - wxString activeToolTip; - double activeFraction; - wxImage logo; + class ProgressIconGenerator; + std::unique_ptr<ProgressIconGenerator> iconGenerator; + + wxString activeToolTip = L"FreeFileSync"; + double activeFraction = 1; }; #endif //TRAY_ICON_H_84217830427534285 diff --git a/FreeFileSync/Source/version/version.h b/FreeFileSync/Source/version/version.h index cbaea6a1..3d224d48 100644 --- a/FreeFileSync/Source/version/version.h +++ b/FreeFileSync/Source/version/version.h @@ -3,7 +3,7 @@ namespace zen { -const wchar_t ffsVersion[] = L"8.4"; //internal linkage! +const wchar_t ffsVersion[] = L"8.5"; //internal linkage! const wchar_t FFS_VERSION_SEPARATOR = L'.'; } diff --git a/wx+/graph.cpp b/wx+/graph.cpp index 14661311..192e5513 100644 --- a/wx+/graph.cpp +++ b/wx+/graph.cpp @@ -150,7 +150,7 @@ void drawXLabel(wxDC& dc, double xMin, double xMax, int blockCount, const Conver if (blockCount <= 0) return; - wxDCPenChanger dummy(dc, wxPen(wxColor(192, 192, 192))); //light grey => not accessible! but no big deal... + wxDCPenChanger dummy(dc, wxColor(192, 192, 192)); //light grey => not accessible! but no big deal... wxDCTextColourChanger dummy2(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); //use user setting for labels const double valRangePerBlock = (xMax - xMin) / blockCount; @@ -178,7 +178,7 @@ void drawYLabel(wxDC& dc, double yMin, double yMax, int blockCount, const Conver if (blockCount <= 0) return; - wxDCPenChanger dummy(dc, wxPen(wxColor(192, 192, 192))); //light grey => not accessible! but no big deal... + wxDCPenChanger dummy(dc, wxColor(192, 192, 192)); //light grey => not accessible! but no big deal... wxDCTextColourChanger dummy2(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); //use user setting for labels const double valRangePerBlock = (yMax - yMin) / blockCount; diff --git a/wx+/grid.cpp b/wx+/grid.cpp index ec0534d2..94bcadd9 100644 --- a/wx+/grid.cpp +++ b/wx+/grid.cpp @@ -7,12 +7,13 @@ #include "grid.h" #include <cassert> #include <set> +#include <chrono> #include <wx/settings.h> #include <wx/listbox.h> #include <wx/tooltip.h> #include <wx/timer.h> #include <wx/utils.h> -#include <zen/tick_count.h> +//#include <zen/tick_count.h> #include <zen/string_tools.h> #include <zen/scope_guard.h> #include <zen/utf.h> @@ -100,7 +101,7 @@ int GridData::getBestSize(wxDC& dc, size_t row, ColumnType colType) wxRect GridData::drawCellBorder(wxDC& dc, const wxRect& rect) //returns remaining rectangle { - wxDCPenChanger dummy2(dc, wxPen(getColorGridLine(), 1, wxSOLID)); + wxDCPenChanger dummy2(dc, getColorGridLine()); dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight()); dc.DrawLine(rect.GetBottomRight(), rect.GetTopRight() + wxPoint(0, -1)); @@ -212,7 +213,7 @@ wxRect GridData::drawColumnLabelBorder(wxDC& dc, const wxRect& rect) //returns r //draw border (with gradient) { - wxDCPenChanger dummy(dc, wxPen(wxSystemSettings::GetColour(wxSYS_COLOUR_3DSHADOW), 1, wxSOLID)); + wxDCPenChanger dummy(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_3DSHADOW)); dc.GradientFillLinear(wxRect(rect.GetTopRight(), rect.GetBottomRight()), getColorLabelGradientFrom(), dc.GetPen().GetColour(), wxSOUTH); dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight() + wxPoint(1, 0)); } @@ -397,7 +398,7 @@ private: dc.GradientFillLinear(clientRect, getColorLabelGradientFrom(), getColorLabelGradientTo(), wxSOUTH); - dc.SetPen(wxPen(wxSystemSettings::GetColour(wxSYS_COLOUR_3DSHADOW), 1, wxSOLID)); + dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_3DSHADOW)); { wxDCPenChanger dummy(dc, getColorLabelGradientFrom()); @@ -540,7 +541,7 @@ private: dc.DrawLine(rect.GetTopLeft(), rect.GetTopRight()); } { - wxDCPenChanger dummy(dc, wxPen(wxSystemSettings::GetColour(wxSYS_COLOUR_3DSHADOW), 1, wxSOLID)); + wxDCPenChanger dummy(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_3DSHADOW)); dc.DrawLine(rect.GetTopLeft(), rect.GetBottomLeft()); dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight()); dc.DrawLine(rect.GetBottomRight(), rect.GetTopRight() + wxPoint(0, -1)); @@ -633,7 +634,7 @@ private: wxDCTextColourChanger dummy(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); //use user setting for labels - const int colLabelHeight = refParent().colLabelHeight; + const int colLabelHeight = refParent().colLabelHeight_; wxPoint labelAreaTL(refParent().CalcScrolledPosition(wxPoint(0, 0)).x, 0); //client coordinates @@ -1150,13 +1151,9 @@ private: void evalMousePos() { - double deltaTime = 0; - if (ticksPerSec_ > 0) - { - const TickVal now = getTicks(); //!isValid() on error - deltaTime = static_cast<double>(dist(tickCountLast, now)) / ticksPerSec_; //unit: [sec] - tickCountLast = now; - } + const auto now = std::chrono::steady_clock::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(); @@ -1179,7 +1176,7 @@ private: if (overlapPix != 0) { const double scrollSpeed = overlapPix * mouseDragSpeedIncScrollU; //unit: [scroll units / sec] - toScroll += scrollSpeed * deltaTime; + toScroll += scrollSpeed * deltaSecs; } else toScroll = 0; @@ -1220,8 +1217,7 @@ private: wxTimer timer; double toScrollX = 0; //count outstanding scroll unit fractions while dragging mouse double toScrollY = 0; // - TickVal tickCountLast = getTicks(); - const std::int64_t ticksPerSec_ = ticksPerSec(); + std::chrono::steady_clock::time_point lastEvalTime = std::chrono::steady_clock::now(); }; struct MouseHighlight @@ -1243,7 +1239,7 @@ private: //=> don't use plain async event => 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 get 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; wxCommandEvent scrollEvent(EVENT_GRID_HAS_SCROLLED); @@ -1301,7 +1297,7 @@ Grid::Grid(wxWindow* parent, colLabelWin_ = new ColLabelWin(*this); // mainWin_ = new MainWin (*this, *rowLabelWin_, *colLabelWin_); // - colLabelHeight = 2 * DEFAULT_COL_LABEL_BORDER + [&]() -> int + colLabelHeight_ = 2 * DEFAULT_COL_LABEL_BORDER + [&]() -> int { //coordinate with ColLabelWin::render(): wxFont labelFont = colLabelWin_->GetFont(); @@ -1353,7 +1349,7 @@ void Grid::updateWindowSizes(bool updateScrollbar) //harmonize with Grid::GetSizeAvailableForScrollTarget()! //1. calculate row label width independent from scrollbars - const int mainWinHeightGross = std::max(GetSize().GetHeight() - colLabelHeight, 0); //independent from client sizes and scrollbars! + const int mainWinHeightGross = std::max(GetSize().GetHeight() - colLabelHeight_, 0); //independent from client sizes and scrollbars! const ptrdiff_t logicalHeight = rowLabelWin_->getLogicalHeight(); // int rowLabelWidth = 0; @@ -1370,7 +1366,7 @@ void Grid::updateWindowSizes(bool updateScrollbar) rowLabelWidth = rowLabelWin_->getBestWidth(rowFrom, rowTo); } - auto getMainWinSize = [&](const wxSize& clientSize) { return wxSize(std::max(0, clientSize.GetWidth() - rowLabelWidth), std::max(0, clientSize.GetHeight() - colLabelHeight)); }; + auto getMainWinSize = [&](const wxSize& clientSize) { return wxSize(std::max(0, clientSize.GetWidth() - rowLabelWidth), std::max(0, clientSize.GetHeight() - colLabelHeight_)); }; auto setScrollbars2 = [&](int logWidth, int logHeight) //replace SetScrollbars, which loses precision of pixelsPerUnitX for some brain-dead reason { @@ -1393,10 +1389,10 @@ void Grid::updateWindowSizes(bool updateScrollbar) //this ensures mainWin_->SetVirtualSize() and AdjustScrollbars() are working with the correct main window size, unless sb change later, which triggers a recalculation anyway! const wxSize mainWinSize = getMainWinSize(GetClientSize()); - cornerWin_ ->SetSize(0, 0, rowLabelWidth, colLabelHeight); - rowLabelWin_->SetSize(0, colLabelHeight, rowLabelWidth, mainWinSize.GetHeight()); - colLabelWin_->SetSize(rowLabelWidth, 0, mainWinSize.GetWidth(), colLabelHeight); - mainWin_ ->SetSize(rowLabelWidth, colLabelHeight, mainWinSize.GetWidth(), mainWinSize.GetHeight()); + cornerWin_ ->SetSize(0, 0, rowLabelWidth, colLabelHeight_); + rowLabelWin_->SetSize(0, colLabelHeight_, rowLabelWidth, mainWinSize.GetHeight()); + colLabelWin_->SetSize(rowLabelWidth, 0, mainWinSize.GetWidth(), colLabelHeight_); + mainWin_ ->SetSize(rowLabelWidth, colLabelHeight_, mainWinSize.GetWidth(), mainWinSize.GetHeight()); //avoid flicker in wxWindowMSW::HandleSize() when calling ::EndDeferWindowPos() where the sub-windows are moved only although they need to be redrawn! colLabelWin_->Refresh(); @@ -1461,7 +1457,7 @@ wxSize Grid::GetSizeAvailableForScrollTarget(const wxSize& size) //harmonize with Grid::updateWindowSizes()! //1. calculate row label width independent from scrollbars - const int mainWinHeightGross = std::max(size.GetHeight() - colLabelHeight, 0); //independent from client sizes and scrollbars! + const int mainWinHeightGross = std::max(size.GetHeight() - colLabelHeight_, 0); //independent from client sizes and scrollbars! const ptrdiff_t logicalHeight = rowLabelWin_->getLogicalHeight(); // int rowLabelWidth = 0; @@ -1478,7 +1474,7 @@ wxSize Grid::GetSizeAvailableForScrollTarget(const wxSize& size) rowLabelWidth = rowLabelWin_->getBestWidth(rowFrom, rowTo); } - return size - wxSize(rowLabelWidth, colLabelHeight); + return size - wxSize(rowLabelWidth, colLabelHeight_); } @@ -1621,7 +1617,7 @@ void Grid::onKeyDown(wxKeyEvent& event) void Grid::setColumnLabelHeight(int height) { - colLabelHeight = std::max(0, height); + colLabelHeight_ = std::max(0, height); updateWindowSizes(); } @@ -1955,7 +1951,7 @@ wxRect Grid::getColumnLabelArea(ColumnType colType) const for (auto it = absWidths.begin(); it != itCol; ++it) posX += it->width_; - return wxRect(wxPoint(posX, 0), wxSize(itCol->width_, colLabelHeight)); + return wxRect(wxPoint(posX, 0), wxSize(itCol->width_, colLabelHeight_)); } return wxRect(); } @@ -348,7 +348,7 @@ private: ScrollBarStatus showScrollbarX = SB_SHOW_AUTOMATIC; ScrollBarStatus showScrollbarY = SB_SHOW_AUTOMATIC; - int colLabelHeight = 0; + int colLabelHeight_ = 0; bool drawRowLabel = true; std::shared_ptr<GridData> dataView_; diff --git a/wx+/image_resources.cpp b/wx+/image_resources.cpp index 1157e15b..110539e7 100644 --- a/wx+/image_resources.cpp +++ b/wx+/image_resources.cpp @@ -7,11 +7,13 @@ #include "image_resources.h" #include <memory> #include <map> +#include <zen/utf.h> +#include <zen/globals.h> +#include <zen/thread.h> #include <wx/wfstream.h> #include <wx/zipstrm.h> #include <wx/image.h> #include <wx/mstream.h> -#include <zen/utf.h> #include "image_tools.h" using namespace zen; @@ -19,6 +21,10 @@ using namespace zen; namespace { +#ifndef NDEBUG + const std::thread::id mainThreadId = std::this_thread::get_id(); +#endif + void loadAnimFromZip(wxZipInputStream& zipInput, wxAnimation& anim) { //work around wxWidgets bug: @@ -37,18 +43,19 @@ void loadAnimFromZip(wxZipInputStream& zipInput, wxAnimation& anim) } -class GlobalResources +class GlobalBitmaps { public: - static GlobalResources& instance() + static std::shared_ptr<GlobalBitmaps> instance() { -#if defined _MSC_VER && _MSC_VER < 1900 -#error function scope static initialization is not yet thread-safe! -#endif - static GlobalResources inst; - return inst; + static Global<GlobalBitmaps> inst(std::make_unique<GlobalBitmaps>()); + assert(std::this_thread::get_id() == mainThreadId); //wxWidgets is not thread-safe! + return inst.get(); } + GlobalBitmaps() {} + ~GlobalBitmaps() { assert(bitmaps.empty() && anims.empty()); } //don't leave wxWidgets objects for static destruction! + void init(const Zstring& filepath); void cleanup() { @@ -60,17 +67,15 @@ public: const wxAnimation& getAnimation(const wxString& name) const; private: - GlobalResources() {} - ~GlobalResources() { assert(bitmaps.empty() && anims.empty()); } //don't leave wxWidgets objects for static destruction! - GlobalResources (const GlobalResources&) = delete; - GlobalResources& operator=(const GlobalResources&) = delete; + GlobalBitmaps (const GlobalBitmaps&) = delete; + GlobalBitmaps& operator=(const GlobalBitmaps&) = delete; std::map<wxString, wxBitmap> bitmaps; std::map<wxString, wxAnimation> anims; }; -void GlobalResources::init(const Zstring& filepath) +void GlobalBitmaps::init(const Zstring& filepath) { assert(bitmaps.empty() && anims.empty()); @@ -109,32 +114,58 @@ void GlobalResources::init(const Zstring& filepath) } -const wxBitmap& GlobalResources::getImage(const wxString& name) const +const wxBitmap& GlobalBitmaps::getImage(const wxString& name) const { auto it = bitmaps.find(contains(name, L'.') ? name : name + L".png"); //assume .png ending if nothing else specified if (it != bitmaps.end()) return it->second; - assert(false); return wxNullBitmap; } -const wxAnimation& GlobalResources::getAnimation(const wxString& name) const +const wxAnimation& GlobalBitmaps::getAnimation(const wxString& name) const { auto it = anims.find(contains(name, L'.') ? name : name + L".gif"); if (it != anims.end()) return it->second; - assert(false); return wxNullAnimation; } } -void zen::initResourceImages(const Zstring& filepath) { GlobalResources::instance().init(filepath); } -void zen::cleanupResourceImages() { GlobalResources::instance().cleanup(); } +void zen::initResourceImages(const Zstring& filepath) +{ + if (std::shared_ptr<GlobalBitmaps> inst = GlobalBitmaps::instance()) + inst->init(filepath); + else + assert(false); +} + + +void zen::cleanupResourceImages() +{ + if (std::shared_ptr<GlobalBitmaps> inst = GlobalBitmaps::instance()) + inst->cleanup(); + else + assert(false); +} + + +const wxBitmap& zen::getResourceImage(const wxString& name) +{ + if (std::shared_ptr<GlobalBitmaps> inst = GlobalBitmaps::instance()) + return inst->getImage(name); + assert(false); + return wxNullBitmap; +} -const wxBitmap& zen::getResourceImage(const wxString& name) { return GlobalResources::instance().getImage(name); } -const wxAnimation& zen::getResourceAnimation(const wxString& name) { return GlobalResources::instance().getAnimation(name); } +const wxAnimation& zen::getResourceAnimation(const wxString& name) +{ + if (std::shared_ptr<GlobalBitmaps> inst = GlobalBitmaps::instance()) + return inst->getAnimation(name); + assert(false); + return wxNullAnimation; +} diff --git a/wx+/popup_dlg.cpp b/wx+/popup_dlg.cpp index e05a4240..189343e9 100644 --- a/wx+/popup_dlg.cpp +++ b/wx+/popup_dlg.cpp @@ -121,9 +121,12 @@ public: int maxWidth = 500; int maxHeight = 400; //try to determine better value based on actual display resolution: - int disPos = wxDisplay::GetFromWindow(parent); //window must be visible + if (parent) + { + const int disPos = wxDisplay::GetFromWindow(parent); //window must be visible if (disPos != wxNOT_FOUND) maxHeight = wxDisplay(disPos).GetClientArea().GetHeight() * 2 / 3; + } assert(!cfg.textMain.empty() || !cfg.textDetail.empty()); if (!cfg.textMain.empty()) diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp index 0c55a963..769aa4f2 100644 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -344,9 +344,9 @@ DirWatcher::~DirWatcher() if (pimpl_->worker.joinable()) //= thread::detach() precondition! -> may already be joined by HandleVolumeRemoval::onRequestRemoval() { pimpl_->worker.interrupt(); - pimpl_->worker.detach(); //we don't have time to wait... will take ~50ms anyway: + pimpl_->worker.detach(); //we don't have time to wait... would take ~50ms + //Windows caveat: exitting the app will kill the thread and leak memory! } - //caveat: exitting the app may simply kill this thread! } diff --git a/zen/file_access.cpp b/zen/file_access.cpp index d8a1f3b7..2e9d93f8 100644 --- a/zen/file_access.cpp +++ b/zen/file_access.cpp @@ -695,7 +695,6 @@ void zen::renameFile(const Zstring& pathSource, const Zstring& pathTarget) //thr namespace { - #ifdef ZEN_WIN void setFileTimeByHandle(HANDLE hFile, //throw FileError const FILETIME* creationTime, //optional @@ -721,16 +720,6 @@ void setFileTimeByHandle(HANDLE hFile, //throw FileError //function may fail if file is read-only: https://sourceforge.net/tracker/?func=detail&atid=1093080&aid=3514569&group_id=234430 if (ec == ERROR_ACCESS_DENIED) { - auto setFileInfo = [&](FILE_BASIC_INFO basicInfo) //throw FileError; no const& since SetFileInformationByHandle() requires non-const parameter! - { - if (!::SetFileInformationByHandle(hFile, //__in HANDLE hFile, - FileBasicInfo, //__in FILE_INFO_BY_HANDLE_CLASS FileInformationClass, - &basicInfo, //__in LPVOID lpFileInformation, - sizeof(basicInfo))) //__in DWORD dwBufferSize - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtPath(filePath)), L"SetFileInformationByHandle"); - }; - //--------------------------------------------------------------------------- - BY_HANDLE_FILE_INFORMATION fileInfo = {}; if (::GetFileInformationByHandle(hFile, &fileInfo)) if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY) @@ -741,16 +730,20 @@ void setFileTimeByHandle(HANDLE hFile, //throw FileError if (creationTime) basicInfo.CreationTime = toLargeInteger(*creationTime); - //set file time + attributes - setFileInfo(basicInfo); //throw FileError - - try //... to restore original file attributes - { - FILE_BASIC_INFO basicInfo2 = {}; - basicInfo2.FileAttributes = fileInfo.dwFileAttributes; - setFileInfo(basicInfo2); //throw FileError - } - catch (FileError&) {} + //set file time + attributes + if (!::SetFileInformationByHandle(hFile, //__in HANDLE hFile, + FileBasicInfo, //__in FILE_INFO_BY_HANDLE_CLASS FileInformationClass, + &basicInfo, //__in LPVOID lpFileInformation, + sizeof(basicInfo))) //__in DWORD dwBufferSize + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtPath(filePath)), L"SetFileInformationByHandle"); + + //(try to) restore original file attributes + FILE_BASIC_INFO basicInfo2 = {}; + basicInfo2.FileAttributes = fileInfo.dwFileAttributes; + ::SetFileInformationByHandle(hFile, //__in HANDLE hFile, + FileBasicInfo, //__in FILE_INFO_BY_HANDLE_CLASS FileInformationClass, + &basicInfo2, //__in LPVOID lpFileInformation, + sizeof(basicInfo2)); //__in DWORD dwBufferSize return; } } @@ -836,26 +829,26 @@ void setWriteTimeNative(const Zstring& itemPath, } */ //temporarily reset read-only flag if required - DWORD attribs = INVALID_FILE_ATTRIBUTES; + DWORD attribsToRestore = INVALID_FILE_ATTRIBUTES; ZEN_ON_SCOPE_EXIT( - if (attribs != INVALID_FILE_ATTRIBUTES) - ::SetFileAttributes(applyLongPathPrefix(itemPath).c_str(), attribs); + if (attribsToRestore != INVALID_FILE_ATTRIBUTES) + ::SetFileAttributes(applyLongPathPrefix(itemPath).c_str(), attribsToRestore); ); auto removeReadonly = [&]() -> bool //throw FileError; may need to remove the readonly-attribute (e.g. on FAT usb drives) { - if (attribs == INVALID_FILE_ATTRIBUTES) + if (attribsToRestore == INVALID_FILE_ATTRIBUTES) { - const DWORD tmpAttr = ::GetFileAttributes(applyLongPathPrefix(itemPath).c_str()); - if (tmpAttr == INVALID_FILE_ATTRIBUTES) + const DWORD attribs = ::GetFileAttributes(applyLongPathPrefix(itemPath).c_str()); + if (attribs == INVALID_FILE_ATTRIBUTES) THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"GetFileAttributes"); - if (tmpAttr & FILE_ATTRIBUTE_READONLY) + if (attribs & FILE_ATTRIBUTE_READONLY) { if (!::SetFileAttributes(applyLongPathPrefix(itemPath).c_str(), FILE_ATTRIBUTE_NORMAL)) THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtPath(itemPath)), L"SetFileAttributes"); - attribs = tmpAttr; //reapplied on scope exit + attribsToRestore = attribs; //reapplied on scope exit return true; } } @@ -866,7 +859,7 @@ void setWriteTimeNative(const Zstring& itemPath, { return ::CreateFile(applyLongPathPrefix(itemPath).c_str(), //_In_ LPCTSTR lpFileName, (conservativeApproach ? - //some NAS seem to have issues with FILE_WRITE_ATTRIBUTES, even worse, they may fail silently! + //some NAS seem to have issues with FILE_WRITE_ATTRIBUTES: they silently fail later during SetFileTime()! //http://sourceforge.net/tracker/?func=detail&atid=1093081&aid=3536680&group_id=234430 //Citrix shares seem to have this issue, too, but at least fail with "access denied" => try generic access first: GENERIC_READ | GENERIC_WRITE : @@ -881,7 +874,6 @@ void setWriteTimeNative(const Zstring& itemPath, FILE_FLAG_BACKUP_SEMANTICS, /*needed to open a directory*/ //_In_ DWORD dwFlagsAndAttributes, nullptr); //_In_opt_ HANDLE hTemplateFile }; - { //extra scope for debug check below @@ -889,7 +881,7 @@ void setWriteTimeNative(const Zstring& itemPath, for (int i = 0; i < 2; ++i) //we will get this handle, no matter what! :) { //1. be conservative - hFile = openFile(true); + hFile = openFile(true /*GENERIC_WRITE*/); if (hFile == INVALID_HANDLE_VALUE) { if (::GetLastError() == ERROR_ACCESS_DENIED) //fails if file is read-only (or for "other" reasons) @@ -897,7 +889,7 @@ void setWriteTimeNative(const Zstring& itemPath, continue; //2. be a *little* fancy - hFile = openFile(false); + hFile = openFile(false /*FILE_WRITE_ATTRIBUTES*/); if (hFile == INVALID_HANDLE_VALUE) { const DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls! @@ -913,6 +905,25 @@ void setWriteTimeNative(const Zstring& itemPath, } ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile)); +#if 0 //waiting for user feedback... +#ifdef ZEN_WIN_VISTA_AND_LATER + //bugs, bugs, bugs.... on "SharePoint" SetFileAttributes() seems to affect file modification time: http://www.freefilesync.org/forum/viewtopic.php?t=3699 + //on Vista we can avoid reopening the file (and the SharePoint bug) +ZEN_ON_SCOPE_EXIT( + if (attribsToRestore != INVALID_FILE_ATTRIBUTES) + { + FILE_BASIC_INFO basicInfo = {}; + basicInfo.FileAttributes = attribsToRestore; + ::SetFileInformationByHandle(hFile, //__in HANDLE hFile, + FileBasicInfo, //__in FILE_INFO_BY_HANDLE_CLASS FileInformationClass, + &basicInfo, //__in LPVOID lpFileInformation, + sizeof(basicInfo)); //__in DWORD dwBufferSize + attribsToRestore = INVALID_FILE_ATTRIBUTES; + } + ); +#endif +#endif + setFileTimeByHandle(hFile, creationTime, lastWriteTime, itemPath); //throw FileError } #ifndef NDEBUG //verify written data @@ -951,8 +962,8 @@ void setWriteTimeNative(const Zstring& itemPath, const struct ::timespec& modTim [2015-03-09] - cannot reproduce issues with NTFS and utimensat() on Ubuntu - utimensat() is supposed to obsolete utime/utimes and is also used by "cp" and "touch" - => let's give utimensat another chance: - using open()/futimens() for regular files and utimensat(AT_SYMLINK_NOFOLLOW) for symlinks is consistent with "cp" and "touch"! + => let's give utimensat another chance: + using open()/futimens() for regular files and utimensat(AT_SYMLINK_NOFOLLOW) for symlinks is consistent with "cp" and "touch"! */ struct ::timespec newTimes[2] = {}; newTimes[0].tv_sec = ::time(nullptr); //access time; using UTIME_OMIT for tv_nsec would trigger even more bugs: http://www.freefilesync.org/forum/viewtopic.php?t=1701 @@ -960,12 +971,12 @@ void setWriteTimeNative(const Zstring& itemPath, const struct ::timespec& modTim if (procSl == ProcSymlink::FOLLOW) { - //hell knows why files on gvfs-mounted Samba shares fail to open(O_WRONLY) returning EOPNOTSUPP: - //http://www.freefilesync.org/forum/viewtopic.php?t=2803 => utimensat() works + //hell knows why files on gvfs-mounted Samba shares fail to open(O_WRONLY) returning EOPNOTSUPP: + //http://www.freefilesync.org/forum/viewtopic.php?t=2803 => utimensat() works if (::utimensat(AT_FDCWD, itemPath.c_str(), newTimes, 0) == 0) - return; + return; - //in other cases utimensat() returns EINVAL for CIFS/NTFS drives, but open+futimens works: http://www.freefilesync.org/forum/viewtopic.php?t=387 + //in other cases utimensat() returns EINVAL for CIFS/NTFS drives, but open+futimens works: http://www.freefilesync.org/forum/viewtopic.php?t=387 const int fdFile = ::open(itemPath.c_str(), O_WRONLY); if (fdFile == -1) THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(itemPath)), L"open"); @@ -1659,7 +1670,7 @@ void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool if (::lstat(sourceLink.c_str(), &sourceInfo) != 0) THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourceLink)), L"lstat"); -#ifdef ZEN_LINUX +#ifdef ZEN_LINUX setWriteTimeNative(targetLink, sourceInfo.st_mtim, ProcSymlink::DIRECT); //throw FileError #elif defined ZEN_MAC if (hasNativeSupportForExtendedAtrributes(targetLink)) //throw FileError @@ -1899,14 +1910,9 @@ InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw throw FileError(errorMsg, errorDescr); } -#ifndef ZEN_WIN_VISTA_AND_LATER - ZEN_ON_SCOPE_FAIL(try { removeFile(targetFile); } - catch (FileError&) {} ); //transactional behavior: guard just after opening target and before managing hFileTarget -#endif - +#ifdef ZEN_WIN_VISTA_AND_LATER ZEN_ON_SCOPE_EXIT(::CloseHandle(hFileTarget)); -#ifdef ZEN_WIN_VISTA_AND_LATER //no need for ::DeleteFile(), we already have an open handle! Maybe this also prevents needless buffer-flushing in ::CloseHandle()??? Anyway, same behavior like ::CopyFileEx() ZEN_ON_SCOPE_FAIL ( @@ -1918,6 +1924,11 @@ InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw sizeof(di))) //_In_ DWORD dwBufferSize assert(false); ); +#else + ZEN_ON_SCOPE_FAIL(try { removeFile(targetFile); } + catch (FileError&) {} ); //transactional behavior: guard just after opening target and before managing hFileTarget + + ZEN_ON_SCOPE_EXIT(::CloseHandle(hFileTarget)); #endif //---------------------------------------------------------------------- @@ -1985,8 +1996,8 @@ InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw LPVOID contextWrite = nullptr; // ZEN_ON_SCOPE_EXIT( - if (contextRead ) ::BackupRead (0, nullptr, 0, nullptr, true, false, &contextRead); //MSDN: "lpContext must be passed [...] all other parameters are ignored." - if (contextWrite) ::BackupWrite(0, nullptr, 0, nullptr, true, false, &contextWrite); ); + if (contextRead ) ::BackupRead (0, nullptr, 0, nullptr, true, false, &contextRead); //MSDN: "lpContext must be passed [...] all other parameters are ignored." + if (contextWrite) ::BackupWrite(0, nullptr, 0, nullptr, true, false, &contextWrite); ); // //stream-copy sourceFile to targetFile bool eof = false; diff --git a/zen/file_io.h b/zen/file_io.h index e6da486d..89cf77d5 100644 --- a/zen/file_io.h +++ b/zen/file_io.h @@ -18,9 +18,9 @@ namespace zen { #ifdef ZEN_WIN - static const char LINE_BREAK[] = "\r\n"; + const char LINE_BREAK[] = "\r\n"; #elif defined ZEN_LINUX || defined ZEN_MAC - static const char LINE_BREAK[] = "\n"; //since OS X apple uses newline, too + const char LINE_BREAK[] = "\n"; //since OS X apple uses newline, too #endif //OS-buffered file IO optimized for sequential read/write accesses + better error reporting + long path support + following symlinks diff --git a/zen/format_unit.cpp b/zen/format_unit.cpp index c7b2504d..71bb8688 100644 --- a/zen/format_unit.cpp +++ b/zen/format_unit.cpp @@ -5,12 +5,13 @@ // ***************************************************************************** #include "format_unit.h" -#include "basic_math.h" -#include "i18n.h" -#include "time.h" #include <cwchar> //swprintf #include <ctime> #include <cstdio> +#include "basic_math.h" +#include "i18n.h" +#include "time.h" +#include "globals.h" #ifdef ZEN_WIN #include "int64.h" @@ -163,49 +164,18 @@ std::wstring zen::fractionToString(double fraction) #ifdef ZEN_WIN namespace { -bool getUserSetting(LCTYPE lt, UINT& setting) -{ - return ::GetLocaleInfo(LOCALE_USER_DEFAULT, //__in LCID Locale, - lt | LOCALE_RETURN_NUMBER, //__in LCTYPE LCType, - reinterpret_cast<LPTSTR>(&setting), //__out LPTSTR lpLCData, - sizeof(setting) / sizeof(TCHAR)) > 0; //__in int cchData -} - - -bool getUserSetting(LCTYPE lt, std::wstring& setting) -{ - int bufferSize = ::GetLocaleInfo(LOCALE_USER_DEFAULT, lt, nullptr, 0); - if (bufferSize > 0) - { - std::vector<wchar_t> buffer(bufferSize); - if (::GetLocaleInfo(LOCALE_USER_DEFAULT, //__in LCID Locale, - lt, //__in LCTYPE LCType, - &buffer[0], //__out LPTSTR lpLCData, - bufferSize) > 0) //__in int cchData - { - setting = &buffer[0]; //GetLocaleInfo() returns char count *including* 0-termination! - return true; - } - } - return false; -} - class IntegerFormat { public: - static const NUMBERFMT& get() { return getInst().fmt; } - static bool isValid() { return getInst().valid; } - -private: - static const IntegerFormat& getInst() + static std::shared_ptr<const IntegerFormat> instance() { -#if defined _MSC_VER && _MSC_VER < 1900 -#error function scope static initialization is not yet thread-safe! -#endif - static const IntegerFormat inst; - return inst; + static Global<const IntegerFormat> inst(std::make_unique<const IntegerFormat>()); + return inst.get(); } + bool isValid() const { return valid; } + const NUMBERFMT& get() const { return fmt; } + IntegerFormat() { //all we want is default NUMBERFMT, but set NumDigits to 0 @@ -219,7 +189,7 @@ private: getUserSetting(LOCALE_STHOUSAND, thousandSep) && getUserSetting(LOCALE_INEGNUMBER, fmt.NegativeOrder)) { - fmt.lpDecimalSep = &decimalSep[0]; //not used + fmt.lpDecimalSep = &decimalSep[0]; //don't need it fmt.lpThousandSep = &thousandSep[0]; //convert LOCALE_SGROUPING to Grouping: https://blogs.msdn.microsoft.com/oldnewthing/20060418-11/?p=31493/ @@ -233,6 +203,36 @@ private: } } +private: + IntegerFormat (const IntegerFormat&) = delete; + IntegerFormat& operator=(const IntegerFormat&) = delete; + + static bool getUserSetting(LCTYPE lt, UINT& setting) + { + return ::GetLocaleInfo(LOCALE_USER_DEFAULT, //__in LCID Locale, + lt | LOCALE_RETURN_NUMBER, //__in LCTYPE LCType, + reinterpret_cast<LPTSTR>(&setting), //__out LPTSTR lpLCData, + sizeof(setting) / sizeof(TCHAR)) > 0; //__in int cchData + } + + static bool getUserSetting(LCTYPE lt, std::wstring& setting) + { + const int bufferSize = ::GetLocaleInfo(LOCALE_USER_DEFAULT, lt, nullptr, 0); + if (bufferSize > 0) + { + std::vector<wchar_t> buffer(bufferSize); + if (::GetLocaleInfo(LOCALE_USER_DEFAULT, //__in LCID Locale, + lt, //__in LCTYPE LCType, + &buffer[0], //__out LPTSTR lpLCData, + bufferSize) > 0) //__in int cchData + { + setting = &buffer[0]; //GetLocaleInfo() returns char count *including* 0-termination! + return true; + } + } + return false; + } + NUMBERFMT fmt = {}; std::wstring thousandSep; std::wstring decimalSep; @@ -245,21 +245,23 @@ private: std::wstring zen::ffs_Impl::includeNumberSeparator(const std::wstring& number) { #ifdef ZEN_WIN - if (IntegerFormat::isValid()) - { - int bufferSize = ::GetNumberFormat(LOCALE_USER_DEFAULT, 0, number.c_str(), &IntegerFormat::get(), nullptr, 0); - if (bufferSize > 0) + if (std::shared_ptr<const IntegerFormat> fmt = IntegerFormat::instance()) + if (fmt->isValid()) { - std::vector<wchar_t> buffer(bufferSize); - if (::GetNumberFormat(LOCALE_USER_DEFAULT, //__in LCID Locale, - 0, //__in DWORD dwFlags, - number.c_str(), //__in LPCTSTR lpValue, - &IntegerFormat::get(), //__in_opt const NUMBERFMT *lpFormat, - &buffer[0], //__out_opt LPTSTR lpNumberStr, - bufferSize) > 0) //__in int cchNumber - return &buffer[0]; //GetNumberFormat() returns char count *including* 0-termination! + const int bufferSize = ::GetNumberFormat(LOCALE_USER_DEFAULT, 0, number.c_str(), &fmt->get(), nullptr, 0); + if (bufferSize > 0) + { + std::vector<wchar_t> buffer(bufferSize); + if (::GetNumberFormat(LOCALE_USER_DEFAULT, //__in LCID Locale, + 0, //__in DWORD dwFlags, + number.c_str(), //__in LPCTSTR lpValue, + &fmt->get(), //__in_opt const NUMBERFMT *lpFormat, + &buffer[0], //__out_opt LPTSTR lpNumberStr, + bufferSize) > 0) //__in int cchNumber + return &buffer[0]; //GetNumberFormat() returns char count *including* 0-termination! + } } - } + assert(false); //what's the problem? return number; #elif defined ZEN_LINUX || defined ZEN_MAC @@ -297,9 +299,6 @@ std::wstring zen::utcToLocalTimeString(std::int64_t utcTime) SYSTEMTIME systemTimeLocal = {}; -#if defined _MSC_VER && _MSC_VER < 1900 -#error function scope static initialization is not yet thread-safe! -#endif static const bool useNewLocalTimeCalculation = zen::vistaOrLater(); //https://msdn.microsoft.com/en-us/library/ms724277 diff --git a/zen/globals.h b/zen/globals.h index 5f3dd64a..ff8c890d 100644 --- a/zen/globals.h +++ b/zen/globals.h @@ -13,21 +13,21 @@ namespace zen { -//solve static destruction order fiasco by providing scoped ownership and serialized access to global variables +//solve static destruction order fiasco by providing shared ownership and serialized access to global variables template <class T> class Global { public: - Global() {} + Global() { static_assert(std::is_trivially_destructible<Pod>::value, "this memory needs to live forever"); } explicit Global(std::unique_ptr<T>&& newInst) { set(std::move(newInst)); } ~Global() { set(nullptr); } std::shared_ptr<T> get() //=> return std::shared_ptr to let instance life time be handled by caller (MT usage!) { - while (spinLock.exchange(true)) ; - ZEN_ON_SCOPE_EXIT(spinLock = false); - if (inst) - return *inst; + while (pod.spinLock.exchange(true)) ; + ZEN_ON_SCOPE_EXIT(pod.spinLock = false); + if (pod.inst) + return *pod.inst; return nullptr; } @@ -37,21 +37,28 @@ public: if (newInst) tmpInst = new std::shared_ptr<T>(std::move(newInst)); { - while (spinLock.exchange(true)) ; - ZEN_ON_SCOPE_EXIT(spinLock = false); - std::swap(inst, tmpInst); + while (pod.spinLock.exchange(true)) ; + ZEN_ON_SCOPE_EXIT(pod.spinLock = false); + std::swap(pod.inst, tmpInst); } delete tmpInst; } private: - //avoid static destruction order fiasco: there may be accesses to "Global<T>::get()" during process shutdown - //e.g. show message in debug_minidump.cpp or some detached thread assembling an error message! - //=> use trivially-destructible POD only!!! - std::shared_ptr<T>* inst = nullptr; - //serialize access: can't use std::mutex because of non-trival destructor - std::atomic<bool> spinLock { false }; + //avoid static destruction order fiasco: there may be accesses to "Global<T>::get()" during process shutdown + //e.g. _("") used by message in debug_minidump.cpp or by some detached thread assembling an error message! + //=> use trivially-destructible POD only!!! + struct Pod + { + std::shared_ptr<T>* inst = nullptr; + //serialize access; can't use std::mutex: has non-trival destructor + std::atomic<bool> spinLock { false }; + } pod; }; + +#if defined _MSC_VER && _MSC_VER < 1900 +#error function scope static initialization is not yet thread-safe! +#endif } #endif //GLOBALS_H_8013740213748021573485 @@ -98,7 +98,7 @@ std::wstring translate(const std::wstring& singular, const std::wstring& plural, inline -Global<const TranslationHandler>& refTranslationGlobals() +Global<const TranslationHandler>& getGlobalTranslationHandler() { //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>& refTranslationGlobals() inline void setTranslator(std::unique_ptr<const TranslationHandler>&& newHandler) { - implementation::refTranslationGlobals().set(std::move(newHandler)); + implementation::getGlobalTranslationHandler().set(std::move(newHandler)); } inline std::shared_ptr<const TranslationHandler> getTranslator() { - return implementation::refTranslationGlobals().get(); + return implementation::getGlobalTranslationHandler().get(); } } diff --git a/zen/scope_guard.h b/zen/scope_guard.h index 69d4b060..8ab58901 100644 --- a/zen/scope_guard.h +++ b/zen/scope_guard.h @@ -9,9 +9,7 @@ #include <cassert> #include <exception> -#include <type_traits> //std::decay - -//best of Zen, Loki and C++17 +#include "type_tools.h" #ifdef ZEN_WIN @@ -20,9 +18,9 @@ inline int getUncaughtExceptionCount() { return std::uncaught_exceptions(); } #elif defined ZEN_LINUX || defined ZEN_MAC //std::uncaught_exceptions() currently unsupported on GCC and Clang => clean up ASAP #ifdef ZEN_LINUX - static_assert(__GNUC__ < 6 || (__GNUC__ == 6 && (__GNUC_MINOR__ < 1 || (__GNUC_MINOR__ == 1 && __GNUC_PATCHLEVEL__ <= 1))), "check std::uncaught_exceptions support"); -#else - static_assert(__clang_major__ < 7 || (__clang_major__ == 7 && __clang_minor__ <= 3), "check std::uncaught_exceptions support"); + static_assert(__GNUC__ < 6 || (__GNUC__ == 6 && (__GNUC_MINOR__ < 2 || (__GNUC_MINOR__ == 2 && __GNUC_PATCHLEVEL__ <= 1))), "check std::uncaught_exceptions support"); +#else //std::uncaught_exceptions() requires "mmacosx-version-min=10.12" + static_assert(__clang_major__ < 8 || (__clang_major__ == 8 && __clang_minor__ <= 0), "check std::uncaught_exceptions support"); #endif namespace __cxxabiv1 @@ -37,6 +35,7 @@ inline int getUncaughtExceptionCount() } #endif +//best of Zen, Loki and C++17 namespace zen { @@ -60,45 +59,32 @@ enum class ScopeGuardRunMode }; -template <ScopeGuardRunMode runMode, typename F> -struct ScopeGuardDestructor; - -//specialize scope guard destructor code and get rid of those pesky MSVC "4127 conditional expression is constant" -template <typename F> -struct ScopeGuardDestructor<ScopeGuardRunMode::ON_EXIT, F> +//partially specialize scope guard destructor code and get rid of those pesky MSVC "4127 conditional expression is constant" +template <typename F> inline +void runScopeGuardDestructor(F& fun, int /*exeptionCountOld*/, StaticEnum<ScopeGuardRunMode, ScopeGuardRunMode::ON_EXIT>) { - static void run(F& fun, int exeptionCountOld) - { - (void)exeptionCountOld; //silence unused parameter warning - try { fun(); } - catch (...) { assert(false); } //consistency: don't expect exceptions for ON_EXIT even if "!failed"! - } -}; + try { fun(); } + catch (...) { assert(false); } //consistency: don't expect exceptions for ON_EXIT even if "!failed"! +} -template <typename F> -struct ScopeGuardDestructor<ScopeGuardRunMode::ON_SUCCESS, F> +template <typename F> inline +void runScopeGuardDestructor(F& fun, int exeptionCountOld, StaticEnum<ScopeGuardRunMode, ScopeGuardRunMode::ON_SUCCESS>) { - static void run(F& fun, int exeptionCountOld) - { - const bool failed = getUncaughtExceptionCount() > exeptionCountOld; - if (!failed) - fun(); //throw X - } -}; + const bool failed = getUncaughtExceptionCount() > exeptionCountOld; + if (!failed) + fun(); //throw X +} -template <typename F> -struct ScopeGuardDestructor<ScopeGuardRunMode::ON_FAIL, F> +template <typename F> inline +void runScopeGuardDestructor(F& fun, int exeptionCountOld, StaticEnum<ScopeGuardRunMode, ScopeGuardRunMode::ON_FAIL>) { - static void run(F& fun, int exeptionCountOld) - { - const bool failed = getUncaughtExceptionCount() > exeptionCountOld; - if (failed) - try { fun(); } - catch (...) { assert(false); } - } -}; + const bool failed = getUncaughtExceptionCount() > exeptionCountOld; + if (failed) + try { fun(); } + catch (...) { assert(false); } +} template <ScopeGuardRunMode runMode, typename F> @@ -115,7 +101,7 @@ public: ~ScopeGuard() noexcept(runMode != ScopeGuardRunMode::ON_SUCCESS) { if (!dismissed) - ScopeGuardDestructor<runMode, F>::run(fun_, exeptionCount); + runScopeGuardDestructor(fun_, exeptionCount, StaticEnum<ScopeGuardRunMode, runMode>()); } void dismiss() { dismissed = true; } diff --git a/zen/string_tools.h b/zen/string_tools.h index 3bda665f..525227d6 100644 --- a/zen/string_tools.h +++ b/zen/string_tools.h @@ -229,12 +229,11 @@ template <class S, class T> inline std::vector<S> split(const S& str, const T& delimiter) { static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, ""); - std::vector<S> output; const size_t delimLen = strLength(delimiter); if (delimLen == 0) - output.push_back(str); + return { str }; else { const auto* const delimFirst = strBegin(delimiter); @@ -242,6 +241,8 @@ std::vector<S> split(const S& str, const T& delimiter) const auto* blockStart = strBegin(str); const auto* const strLast = blockStart + strLength(str); + + std::vector<S> output; for (;;) { @@ -249,12 +250,11 @@ std::vector<S> split(const S& str, const T& delimiter) delimFirst, delimLast); output.emplace_back(blockStart, blockEnd - blockStart); - if (blockEnd == strLast) - break; + if (blockEnd == strLast) //clients expect: if delimiter not found, return str + return output; blockStart = blockEnd + delimLen; } } - return output; } |