summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorB. Stack <bgstack15@gmail.com>2022-04-18 09:47:11 -0400
committerB. Stack <bgstack15@gmail.com>2022-04-18 09:47:40 -0400
commitb4b2e4a096fe8fe1ad530a4c181729be05834595 (patch)
tree84e67ca0a1fb045a12d015fcffca9cd8087c9332
parentMerge branch 'b11.18' into 'master' (diff)
downloadFreeFileSync-b4b2e4a096fe8fe1ad530a4c181729be05834595.tar.gz
FreeFileSync-b4b2e4a096fe8fe1ad530a4c181729be05834595.tar.bz2
FreeFileSync-b4b2e4a096fe8fe1ad530a4c181729be05834595.zip
add upstream 11.20
-rw-r--r--Bugs.txt20
-rw-r--r--Changelog.txt18
-rw-r--r--FreeFileSync/Build/Resources/Languages.zipbin507823 -> 507946 bytes
-rw-r--r--FreeFileSync/Build/Resources/cacert.pem411
-rw-r--r--FreeFileSync/Source/Makefile2
-rw-r--r--FreeFileSync/Source/RealTimeSync/Makefile2
-rw-r--r--FreeFileSync/Source/RealTimeSync/application.cpp24
-rw-r--r--FreeFileSync/Source/RealTimeSync/application.h1
-rw-r--r--FreeFileSync/Source/RealTimeSync/main_dlg.cpp24
-rw-r--r--FreeFileSync/Source/RealTimeSync/main_dlg.h6
-rw-r--r--FreeFileSync/Source/afs/abstract.cpp2
-rw-r--r--FreeFileSync/Source/afs/abstract.h2
-rw-r--r--FreeFileSync/Source/afs/concrete.cpp5
-rw-r--r--FreeFileSync/Source/afs/concrete.h2
-rw-r--r--FreeFileSync/Source/afs/ftp.cpp39
-rw-r--r--FreeFileSync/Source/afs/gdrive.cpp465
-rw-r--r--FreeFileSync/Source/afs/gdrive.h7
-rw-r--r--FreeFileSync/Source/afs/init_curl_libssh2.h2
-rw-r--r--FreeFileSync/Source/afs/native.cpp2
-rw-r--r--FreeFileSync/Source/afs/sftp.cpp18
-rw-r--r--FreeFileSync/Source/afs/sftp.h4
-rw-r--r--FreeFileSync/Source/application.cpp44
-rw-r--r--FreeFileSync/Source/application.h1
-rw-r--r--FreeFileSync/Source/base/algorithm.cpp35
-rw-r--r--FreeFileSync/Source/base/algorithm.h7
-rw-r--r--FreeFileSync/Source/base/dir_exist_async.h13
-rw-r--r--FreeFileSync/Source/base/path_filter.cpp261
-rw-r--r--FreeFileSync/Source/base/path_filter.h45
-rw-r--r--FreeFileSync/Source/base/structures.h18
-rw-r--r--FreeFileSync/Source/base/versioning.cpp2
-rw-r--r--FreeFileSync/Source/config.cpp14
-rw-r--r--FreeFileSync/Source/fatal_error.h6
-rw-r--r--FreeFileSync/Source/localization.cpp46
-rw-r--r--FreeFileSync/Source/log_file.cpp5
-rw-r--r--FreeFileSync/Source/ui/batch_config.cpp6
-rw-r--r--FreeFileSync/Source/ui/batch_status_handler.cpp10
-rw-r--r--FreeFileSync/Source/ui/file_grid.cpp1
-rw-r--r--FreeFileSync/Source/ui/file_view.cpp2
-rw-r--r--FreeFileSync/Source/ui/folder_pair.h4
-rw-r--r--FreeFileSync/Source/ui/folder_selector.cpp3
-rw-r--r--FreeFileSync/Source/ui/gui_generated.cpp2
-rw-r--r--FreeFileSync/Source/ui/gui_status_handler.cpp16
-rw-r--r--FreeFileSync/Source/ui/gui_status_handler.h4
-rw-r--r--FreeFileSync/Source/ui/main_dlg.cpp156
-rw-r--r--FreeFileSync/Source/ui/main_dlg.h6
-rw-r--r--FreeFileSync/Source/ui/progress_indicator.cpp41
-rw-r--r--FreeFileSync/Source/ui/progress_indicator.h6
-rw-r--r--FreeFileSync/Source/ui/small_dlgs.cpp112
-rw-r--r--FreeFileSync/Source/ui/sync_cfg.cpp62
-rw-r--r--FreeFileSync/Source/version/version.h2
-rw-r--r--License.txt8
-rw-r--r--wx+/bitmap_button.h21
-rw-r--r--wx+/context_menu.h36
-rw-r--r--wx+/dc.h15
-rw-r--r--wx+/image_tools.cpp26
-rw-r--r--wx+/popup_dlg.cpp3
-rw-r--r--wx+/rtl.h2
-rw-r--r--wx+/std_button_layout.h2
-rw-r--r--wx+/tooltip.cpp4
-rw-r--r--zen/file_traverser.cpp1
-rw-r--r--zen/globals.h2
-rw-r--r--zen/http.cpp6
-rw-r--r--zen/legacy_compiler.h1
-rw-r--r--zen/open_ssl.cpp1
-rw-r--r--zen/resolve_path.cpp1
-rw-r--r--zen/shutdown.cpp41
-rw-r--r--zen/shutdown.h6
-rw-r--r--zen/stl_tools.h15
-rw-r--r--zen/string_tools.h122
-rw-r--r--zen/sys_info.cpp18
-rw-r--r--zen/zstring.cpp2
-rw-r--r--zen/zstring.h2
72 files changed, 1346 insertions, 975 deletions
diff --git a/Bugs.txt b/Bugs.txt
index 1529327e..7e0845ad 100644
--- a/Bugs.txt
+++ b/Bugs.txt
@@ -64,20 +64,18 @@ move the following constants from src/sftp.h to include/libssh2_sftp.h:
__________________________________________________________________________________________________________
-------------------------------
-| wxWidgets 3.1.5-2020-12-26 |
-------------------------------
+-------------------
+| wxWidgets 3.1.6 |
+-------------------
__________________________________________________________________________________________________________
-/include/wx/window.h
+/include/wx/features.h
wxWidgets/GTK2 on some Linux systems incorrectly detects high DPI: https://freefilesync.org/forum/viewtopic.php?t=6114
=> hack away high-DPI support for GTK2 (= pretend GTK2 has device independent pixels, which it clearly has not!)
- #include "wx/gtk/window.h"
-- #ifdef __WXGTK3__
-+ //#ifdef __WXGTK3__
- #define wxHAVE_DPI_INDEPENDENT_PIXELS
-- #endif
-+ //#endif
++ #if defined(__WXGTK20__)
++ #define wxHAS_DPI_INDEPENDENT_PIXELS
++ #endif
+
__________________________________________________________________________________________________________
/src/aui/framemanager.cpp:
Fix incorrect pane height calculations:
@@ -166,5 +164,3 @@ Backspace not working in filter dialog: http://www.freefilesync.org/forum/viewto
g_signal_connect (widget, "key_press_event",
G_CALLBACK (gtk_window_key_press_callback), this);
-
-__________________________________________________________________________________________________________
diff --git a/Changelog.txt b/Changelog.txt
index 8badc140..60be0c17 100644
--- a/Changelog.txt
+++ b/Changelog.txt
@@ -1,3 +1,21 @@
+FreeFileSync 11.20 [2022-04-16]
+-------------------------------
+Fixed broken icon scaling on high-DPI displays
+
+
+FreeFileSync 11.19 [2022-04-16]
+-------------------------------
+Improved performance for huge exclusion filter lists: linear to constant(!) time
+Support sync with Google Drive starred folders
+Access "My Computers" (as created by Google Backup and Sync) if starred
+Western Digital Mycloud NAS: fixed ERROR_ALREADY_EXISTS when changing case
+Added per-file progress for "copy to" function
+Have filter wildcard ? not match path separator
+Work around WBEM_E_INVALID_NAMESPACE error during installation
+Fixed login user incorrectly displayed as root (macOS)
+Save Google Drive buffer before system shutdown
+
+
FreeFileSync 11.18 [2022-03-07]
-------------------------------
Add comparison time to sync log when using GUI
diff --git a/FreeFileSync/Build/Resources/Languages.zip b/FreeFileSync/Build/Resources/Languages.zip
index 69f3e627..9880b5ef 100644
--- a/FreeFileSync/Build/Resources/Languages.zip
+++ b/FreeFileSync/Build/Resources/Languages.zip
Binary files differ
diff --git a/FreeFileSync/Build/Resources/cacert.pem b/FreeFileSync/Build/Resources/cacert.pem
index 0bf312fe..e91e25fa 100644
--- a/FreeFileSync/Build/Resources/cacert.pem
+++ b/FreeFileSync/Build/Resources/cacert.pem
@@ -1,7 +1,7 @@
##
## Bundle of CA Root Certificates
##
-## Certificate data from Mozilla as of: Tue Oct 26 03:12:05 2021 GMT
+## Certificate data from Mozilla as of: Fri Mar 18 12:29:51 2022 GMT
##
## This is a bundle of X.509 certificates of public Certificate Authorities
## (CA). These were automatically extracted from Mozilla's root certificates
@@ -13,8 +13,8 @@
## an Apache+mod_ssl webserver for SSL client authentication.
## Just configure this file as the SSLCACertificateFile.
##
-## Conversion done with mk-ca-bundle.pl version 1.28.
-## SHA256: bb36818a81feaa4cca61101e6d6276cd09e972efcb08112dfed846918ca41d7f
+## Conversion done with mk-ca-bundle.pl version 1.29.
+## SHA256: 187ef9dc231135324fe78830cf4462f1ecdeab3e6c9d5e38d623391e88dc5d3c
##
@@ -39,28 +39,6 @@ hm4qxFYxldBniYUr+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveC
X4XSQRjbgbMEHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==
-----END CERTIFICATE-----
-GlobalSign Root CA - R2
-=======================
------BEGIN CERTIFICATE-----
-MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4GA1UECxMXR2xv
-YmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh
-bFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT
-aWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln
-bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6
-ErPLv4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8eoLrvozp
-s6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklqtTleiDTsvHgMCJiEbKjN
-S7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzdC9XZzPnqJworc5HGnRusyMvo4KD0L5CL
-TfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pazq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6C
-ygPCm48CAwEAAaOBnDCBmTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
-FgQUm+IHV2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5nbG9i
-YWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG3lm0mi3f3BmGLjAN
-BgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4GsJ0/WwbgcQ3izDJr86iw8bmEbTUsp
-9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu
-01yiPqFbQfXf5WRDLenVOavSot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG7
-9G+dwfCMNYxdAfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7
-TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg==
------END CERTIFICATE-----
-
Entrust.net Premium 2048 Secure Server CA
=========================================
-----BEGIN CERTIFICATE-----
@@ -573,28 +551,6 @@ PBS1xp81HlDQwY9qcEQCYsuuHWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY
WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg==
-----END CERTIFICATE-----
-Cybertrust Global Root
-======================
------BEGIN CERTIFICATE-----
-MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYGA1UEChMPQ3li
-ZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBSb290MB4XDTA2MTIxNTA4
-MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQD
-ExZDeWJlcnRydXN0IEdsb2JhbCBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
-+Mi8vRRQZhP/8NN57CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW
-0ozSJ8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2yHLtgwEZL
-AfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iPt3sMpTjr3kfb1V05/Iin
-89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNzFtApD0mpSPCzqrdsxacwOUBdrsTiXSZT
-8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAYXSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAP
-BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2
-MDSgMqAwhi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3JsMB8G
-A1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUAA4IBAQBW7wojoFRO
-lZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMjWqd8BfP9IjsO0QbE2zZMcwSO5bAi
-5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUxXOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2
-hO0j9n0Hq0V+09+zv+mKts2oomcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+T
-X3EJIrduPuocA06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW
-WL1WMRJOEcgh4LMRkWXbtKaIOM5V
------END CERTIFICATE-----
-
ePKI Root Certification Authority
=================================
-----BEGIN CERTIFICATE-----
@@ -1037,36 +993,6 @@ tnRGEmyR7jTV7JqR50S+kDFy1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29
mvVXIwAHIRc/SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03
-----END CERTIFICATE-----
-EC-ACC
-======
------BEGIN CERTIFICATE-----
-MIIFVjCCBD6gAwIBAgIQ7is969Qh3hSoYqwE893EATANBgkqhkiG9w0BAQUFADCB8zELMAkGA1UE
-BhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2VydGlmaWNhY2lvIChOSUYgUS0w
-ODAxMTc2LUkpMSgwJgYDVQQLEx9TZXJ2ZWlzIFB1YmxpY3MgZGUgQ2VydGlmaWNhY2lvMTUwMwYD
-VQQLEyxWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAoYykwMzE1MDMGA1UE
-CxMsSmVyYXJxdWlhIEVudGl0YXRzIGRlIENlcnRpZmljYWNpbyBDYXRhbGFuZXMxDzANBgNVBAMT
-BkVDLUFDQzAeFw0wMzAxMDcyMzAwMDBaFw0zMTAxMDcyMjU5NTlaMIHzMQswCQYDVQQGEwJFUzE7
-MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8gKE5JRiBRLTA4MDExNzYt
-SSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBDZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZl
-Z2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQubmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJh
-cnF1aWEgRW50aXRhdHMgZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUND
-MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsyLHT+KXQpWIR4NA9h0X84NzJB5R85iK
-w5K4/0CQBXCHYMkAqbWUZRkiFRfCQ2xmRJoNBD45b6VLeqpjt4pEndljkYRm4CgPukLjbo73FCeT
-ae6RDqNfDrHrZqJyTxIThmV6PttPB/SnCWDaOkKZx7J/sxaVHMf5NLWUhdWZXqBIoH7nF2W4onW4
-HvPlQn2v7fOKSGRdghST2MDk/7NQcvJ29rNdQlB50JQ+awwAvthrDk4q7D7SzIKiGGUzE3eeml0a
-E9jD2z3Il3rucO2n5nzbcc8tlGLfbdb1OL4/pYUKGbio2Al1QnDE6u/LDsg0qBIimAy4E5S2S+zw
-0JDnJwIDAQABo4HjMIHgMB0GA1UdEQQWMBSBEmVjX2FjY0BjYXRjZXJ0Lm5ldDAPBgNVHRMBAf8E
-BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUoMOLRKo3pUW/l4Ba0fF4opvpXY0wfwYD
-VR0gBHgwdjB0BgsrBgEEAfV4AQMBCjBlMCwGCCsGAQUFBwIBFiBodHRwczovL3d3dy5jYXRjZXJ0
-Lm5ldC92ZXJhcnJlbDA1BggrBgEFBQcCAjApGidWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5l
-dC92ZXJhcnJlbCAwDQYJKoZIhvcNAQEFBQADggEBAKBIW4IB9k1IuDlVNZyAelOZ1Vr/sXE7zDkJ
-lF7W2u++AVtd0x7Y/X1PzaBB4DSTv8vihpw3kpBWHNzrKQXlxJ7HNd+KDM3FIUPpqojlNcAZQmNa
-Al6kSBg6hW/cnbw/nZzBh7h6YQjpdwt/cKt63dmXLGQehb+8dJahw3oS7AwaboMMPOhyRp/7SNVe
-l+axofjk70YllJyJ22k4vuxcDlbHZVHlUIiIv0LVKz3l+bqeLrPK9HOSAgu+TGbrIP65y7WZf+a2
-E/rKS03Z7lNGBjvGTq2TWoF+bCpLagVFjPIhpDGQh2xlnJ2lYJU6Un/10asIbvPuW/mIPX64b24D
-5EI=
------END CERTIFICATE-----
-
Hellenic Academic and Research Institutions RootCA 2011
=======================================================
-----BEGIN CERTIFICATE-----
@@ -1737,20 +1663,6 @@ HU6+4WMBzzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbWRNZu
9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg=
-----END CERTIFICATE-----
-GlobalSign ECC Root CA - R4
-===========================
------BEGIN CERTIFICATE-----
-MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEkMCIGA1UECxMb
-R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD
-EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb
-R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD
-EwpHbG9iYWxTaWduMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprl
-OQcJFspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAwDgYDVR0P
-AQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61FuOJAf/sKbvu+M8k8o4TV
-MAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGXkPoUVy0D7O48027KqGx2vKLeuwIgJ6iF
-JzWbVsaj8kfSt24bAgAXqmemFZHe+pTsewv4n4Q=
------END CERTIFICATE-----
-
GlobalSign ECC Root CA - R5
===========================
-----BEGIN CERTIFICATE-----
@@ -2472,96 +2384,6 @@ AwMDaAAwZQIwJsdpW9zV57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtk
AjEA2zQgMgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9
-----END CERTIFICATE-----
-GTS Root R1
-===========
------BEGIN CERTIFICATE-----
-MIIFWjCCA0KgAwIBAgIQbkepxUtHDA3sM9CJuRz04TANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQG
-EwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJv
-b3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAG
-A1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIi
-MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx
-9vaMf/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7wCl7r
-aKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjwTcLCeoiKu7rPWRnW
-r4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0PfyblqAj+lug8aJRT7oM6iCsVlgmy4HqM
-LnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly
-4cpk9+aCEI3oncKKiPo4Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr
-06zqkUspzBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92
-wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70paDPvOmbsB4om
-3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrNVjzRlwW5y0vtOUucxD/SVRNu
-JLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD
-VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEM
-BQADggIBADiWCu49tJYeX++dnAsznyvgyv3SjgofQXSlfKqE1OXyHuY3UjKcC9FhHb8owbZEKTV1
-d5iyfNm9dKyKaOOpMQkpAWBz40d8U6iQSifvS9efk+eCNs6aaAyC58/UEBZvXw6ZXPYfcX3v73sv
-fuo21pdwCxXu11xWajOl40k4DLh9+42FpLFZXvRq4d2h9mREruZRgyFmxhE+885H7pwoHyXa/6xm
-ld01D1zvICxi/ZG6qcz8WpyTgYMpl0p8WnK0OdC3d8t5/Wk6kjftbjhlRn7pYL15iJdfOBL07q9b
-gsiG1eGZbYwE8na6SfZu6W0eX6DvJ4J2QPim01hcDyxC2kLGe4g0x8HYRZvBPsVhHdljUEn2NIVq
-4BjFbkerQUIpm/ZgDdIx02OYI5NaAIFItO/Nis3Jz5nu2Z6qNuFoS3FJFDYoOj0dzpqPJeaAcWEr
-tXvM+SUWgeExX6GjfhaknBZqlxi9dnKlC54dNuYvoS++cJEPqOba+MSSQGwlfnuzCdyyF62ARPBo
-pY+Udf90WuioAnwMCeKpSwughQtiue+hMZL77/ZRBIls6Kl0obsXs7X9SQ98POyDGCBDTtWTurQ0
-sR8WNh8M5mQ5Fkzc4P4dyKliPUDqysU0ArSuiYgzNdwsE3PYJ/HQcu51OyLemGhmW/HGY0dVHLql
-CFF1pkgl
------END CERTIFICATE-----
-
-GTS Root R2
-===========
------BEGIN CERTIFICATE-----
-MIIFWjCCA0KgAwIBAgIQbkepxlqz5yDFMJo/aFLybzANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQG
-EwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJv
-b3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAG
-A1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIi
-MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTuk
-k3LvCvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY6Dlo
-7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAuMC6C/Pq8tBcKSOWI
-m8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7kRXuJVfeKH2JShBKzwkCX44ofR5Gm
-dFrS+LFjKBC4swm4VndAoiaYecb+3yXuPuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbu
-ak7MkogwTZq9TwtImoS1mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscsz
-cTJGr61K8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RW
-Ir9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKaG73Vululycsl
-aVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCqgc7dGtxRcw1PcOnlthYhGXmy
-5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD
-VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEM
-BQADggIBALZp8KZ3/p7uC4Gt4cCpx/k1HUCCq+YEtN/L9x0Pg/B+E02NjO7jMyLDOfxA325BS0JT
-vhaI8dI4XsRomRyYUpOM52jtG2pzegVATX9lO9ZY8c6DR2Dj/5epnGB3GFW1fgiTz9D2PGcDFWEJ
-+YF59exTpJ/JjwGLc8R3dtyDovUMSRqodt6Sm2T4syzFJ9MHwAiApJiS4wGWAqoC7o87xdFtCjMw
-c3i5T1QWvwsHoaRc5svJXISPD+AVdyx+Jn7axEvbpxZ3B7DNdehyQtaVhJ2Gg/LkkM0JR9SLA3Da
-WsYDQvTtN6LwG1BUSw7YhN4ZKJmBR64JGz9I0cNv4rBgF/XuIwKl2gBbbZCr7qLpGzvpx0QnRY5r
-n/WkhLx3+WuXrD5RRaIRpsyF7gpo8j5QOHokYh4XIDdtak23CZvJ/KRY9bb7nE4Yu5UC56Gtmwfu
-Nmsk0jmGwZODUNKBRqhfYlcsu2xkiAhu7xNUX90txGdj08+JN7+dIPT7eoOboB6BAFDC5AwiWVIQ
-7UNWhwD4FFKnHYuTjKJNRn8nxnGbJN7k2oaLDX5rIMHAnuFl2GqjpuiFizoHCBy69Y9Vmhh1fuXs
-gWbRIXOhNUQLgD1bnF5vKheW0YMjiGZt5obicDIvUiLnyOd/xCxgXS/Dr55FBcOEArf9LAhST4Ld
-o/DUhgkC
------END CERTIFICATE-----
-
-GTS Root R3
-===========
------BEGIN CERTIFICATE-----
-MIICDDCCAZGgAwIBAgIQbkepx2ypcyRAiQ8DVd2NHTAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJV
-UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg
-UjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE
-ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcq
-hkjOPQIBBgUrgQQAIgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUU
-Rout736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL24Cej
-QjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTB8Sa6oC2uhYHP
-0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEAgFukfCPAlaUs3L6JbyO5o91lAFJekazInXJ0
-glMLfalAvWhgxeG4VDvBNhcl2MG9AjEAnjWSdIUlUfUk7GRSJFClH9voy8l27OyCbvWFGFPouOOa
-KaqW04MjyaR7YbPMAuhd
------END CERTIFICATE-----
-
-GTS Root R4
-===========
------BEGIN CERTIFICATE-----
-MIICCjCCAZGgAwIBAgIQbkepyIuUtui7OyrYorLBmTAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJV
-UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg
-UjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE
-ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcq
-hkjOPQIBBgUrgQQAIgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa
-6zzuhXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvRHYqj
-QjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSATNbrdP9JNqPV
-2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNnADBkAjBqUFJ0CMRw3J5QdCHojXohw0+WbhXRIjVhLfoI
-N+4Zba3bssx9BzT1YBkstTTZbyACMANxsbqjYAuG7ZoIapVon+Kz4ZNkfF6Tpt95LY2F45TPI11x
-zPKwTdb+mciUqXWi4w==
------END CERTIFICATE-----
-
UCA Global G2 Root
==================
-----BEGIN CERTIFICATE-----
@@ -3230,3 +3052,230 @@ ECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW
rcJRQO9gcS3ujwLEXQNwSaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/Qw
CZ61IygNnxS2PFOiTAZpffpskcYqSUXm7LcT4Tps
-----END CERTIFICATE-----
+
+Autoridad de Certificacion Firmaprofesional CIF A62634068
+=========================================================
+-----BEGIN CERTIFICATE-----
+MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCRVMxQjBA
+BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2
+MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIw
+QAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBB
+NjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDD
+Utd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQMcas9UX4P
+B99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefGL9ItWY16Ck6WaVICqjaY
+7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15iNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqH
+ECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyI
+plD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctX
+MbScyJCyZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsX
+LZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLK
+bpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgU
+vzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1Ud
+DgQWBBRlzeurNR4APn7VdMActHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4w
+gZswgZgGBFUdIAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j
+b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABCAG8AbgBhAG4A
+bwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAwADEANzAOBgNVHQ8BAf8EBAMC
+AQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9miWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL
+4QjbEwj4KKE1soCzC1HA01aajTNFSa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDb
+LIpgD7dvlAceHabJhfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1il
+I45PVf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZEEAEeiGaP
+cjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV1aUsIC+nmCjuRfzxuIgA
+LI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2tCsvMo2ebKHTEm9caPARYpoKdrcd7b/+A
+lun4jWq9GJAd/0kakFI3ky88Al2CdgtR5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH
+9IBk9W6VULgRfhVwOEqwf9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpf
+NIbnYrX9ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNKGbqE
+ZycPvEJdvSRUDewdcAZfpLz6IHxV
+-----END CERTIFICATE-----
+
+vTrus ECC Root CA
+=================
+-----BEGIN CERTIFICATE-----
+MIICDzCCAZWgAwIBAgIUbmq8WapTvpg5Z6LSa6Q75m0c1towCgYIKoZIzj0EAwMwRzELMAkGA1UE
+BhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBS
+b290IENBMB4XDTE4MDczMTA3MjY0NFoXDTQzMDczMTA3MjY0NFowRzELMAkGA1UEBhMCQ04xHDAa
+BgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBSb290IENBMHYw
+EAYHKoZIzj0CAQYFK4EEACIDYgAEZVBKrox5lkqqHAjDo6LN/llWQXf9JpRCux3NCNtzslt188+c
+ToL0v/hhJoVs1oVbcnDS/dtitN9Ti72xRFhiQgnH+n9bEOf+QP3A2MMrMudwpremIFUde4BdS49n
+TPEQo0IwQDAdBgNVHQ4EFgQUmDnNvtiyjPeyq+GtJK97fKHbH88wDwYDVR0TAQH/BAUwAwEB/zAO
+BgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDaAAwZQIwV53dVvHH4+m4SVBrm2nDb+zDfSXkV5UT
+QJtS0zvzQBm8JsctBp61ezaf9SXUY2sAAjEA6dPGnlaaKsyh2j/IZivTWJwghfqrkYpwcBE4YGQL
+YgmRWAD5Tfs0aNoJrSEGGJTO
+-----END CERTIFICATE-----
+
+vTrus Root CA
+=============
+-----BEGIN CERTIFICATE-----
+MIIFVjCCAz6gAwIBAgIUQ+NxE9izWRRdt86M/TX9b7wFjUUwDQYJKoZIhvcNAQELBQAwQzELMAkG
+A1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xFjAUBgNVBAMTDXZUcnVzIFJv
+b3QgQ0EwHhcNMTgwNzMxMDcyNDA1WhcNNDMwNzMxMDcyNDA1WjBDMQswCQYDVQQGEwJDTjEcMBoG
+A1UEChMTaVRydXNDaGluYSBDby4sTHRkLjEWMBQGA1UEAxMNdlRydXMgUm9vdCBDQTCCAiIwDQYJ
+KoZIhvcNAQEBBQADggIPADCCAgoCggIBAL1VfGHTuB0EYgWgrmy3cLRB6ksDXhA/kFocizuwZots
+SKYcIrrVQJLuM7IjWcmOvFjai57QGfIvWcaMY1q6n6MLsLOaXLoRuBLpDLvPbmyAhykUAyyNJJrI
+ZIO1aqwTLDPxn9wsYTwaP3BVm60AUn/PBLn+NvqcwBauYv6WTEN+VRS+GrPSbcKvdmaVayqwlHeF
+XgQPYh1jdfdr58tbmnDsPmcF8P4HCIDPKNsFxhQnL4Z98Cfe/+Z+M0jnCx5Y0ScrUw5XSmXX+6KA
+YPxMvDVTAWqXcoKv8R1w6Jz1717CbMdHflqUhSZNO7rrTOiwCcJlwp2dCZtOtZcFrPUGoPc2BX70
+kLJrxLT5ZOrpGgrIDajtJ8nU57O5q4IikCc9Kuh8kO+8T/3iCiSn3mUkpF3qwHYw03dQ+A0Em5Q2
+AXPKBlim0zvc+gRGE1WKyURHuFE5Gi7oNOJ5y1lKCn+8pu8fA2dqWSslYpPZUxlmPCdiKYZNpGvu
+/9ROutW04o5IWgAZCfEF2c6Rsffr6TlP9m8EQ5pV9T4FFL2/s1m02I4zhKOQUqqzApVg+QxMaPnu
+1RcN+HFXtSXkKe5lXa/R7jwXC1pDxaWG6iSe4gUH3DRCEpHWOXSuTEGC2/KmSNGzm/MzqvOmwMVO
+9fSddmPmAsYiS8GVP1BkLFTltvA8Kc9XAgMBAAGjQjBAMB0GA1UdDgQWBBRUYnBj8XWEQ1iO0RYg
+scasGrz2iTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOC
+AgEAKbqSSaet8PFww+SX8J+pJdVrnjT+5hpk9jprUrIQeBqfTNqK2uwcN1LgQkv7bHbKJAs5EhWd
+nxEt/Hlk3ODg9d3gV8mlsnZwUKT+twpw1aA08XXXTUm6EdGz2OyC/+sOxL9kLX1jbhd47F18iMjr
+jld22VkE+rxSH0Ws8HqA7Oxvdq6R2xCOBNyS36D25q5J08FsEhvMKar5CKXiNxTKsbhm7xqC5PD4
+8acWabfbqWE8n/Uxy+QARsIvdLGx14HuqCaVvIivTDUHKgLKeBRtRytAVunLKmChZwOgzoy8sHJn
+xDHO2zTlJQNgJXtxmOTAGytfdELSS8VZCAeHvsXDf+eW2eHcKJfWjwXj9ZtOyh1QRwVTsMo554Wg
+icEFOwE30z9J4nfrI8iIZjs9OXYhRvHsXyO466JmdXTBQPfYaJqT4i2pLr0cox7IdMakLXogqzu4
+sEb9b91fUlV1YvCXoHzXOP0l382gmxDPi7g4Xl7FtKYCNqEeXxzP4padKar9mK5S4fNBUvupLnKW
+nyfjqnN9+BojZns7q2WwMgFLFT49ok8MKzWixtlnEjUwzXYuFrOZnk1PTi07NEPhmg4NpGaXutIc
+SkwsKouLgU9xGqndXHt7CMUADTdA43x7VF8vhV929vensBxXVsFy6K2ir40zSbofitzmdHxghm+H
+l3s=
+-----END CERTIFICATE-----
+
+ISRG Root X2
+============
+-----BEGIN CERTIFICATE-----
+MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQswCQYDVQQGEwJV
+UzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElT
+UkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVT
+MSkwJwYDVQQKEyBJbnRlcm5ldCBTZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNS
+RyBSb290IFgyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0H
+ttwW+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9ItgKbppb
+d9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
+HQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZIzj0EAwMDaAAwZQIwe3lORlCEwkSHRhtF
+cP9Ymd70/aTSVaYgLXTWNLxBo1BfASdWtL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5
+U6VR5CmD1/iQMVtCnwr1/q4AaOeMSQ+2b1tbFfLn
+-----END CERTIFICATE-----
+
+HiPKI Root CA - G1
+==================
+-----BEGIN CERTIFICATE-----
+MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBPMQswCQYDVQQG
+EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xGzAZBgNVBAMMEkhpUEtJ
+IFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRaFw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYT
+AlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kg
+Um9vdCBDQSAtIEcxMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0
+o9QwqNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twvVcg3Px+k
+wJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6lZgRZq2XNdZ1AYDgr/SE
+YYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnzQs7ZngyzsHeXZJzA9KMuH5UHsBffMNsA
+GJZMoYFL3QRtU6M9/Aes1MU3guvklQgZKILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfd
+hSi8MEyr48KxRURHH+CKFgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj
+1jOXTyFjHluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDry+K4
+9a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ/W3c1pzAtH2lsN0/
+Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgMa/aOEmem8rJY5AIJEzypuxC00jBF
+8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6fsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYD
+VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQD
+AgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi
+7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqcSE5XCV0vrPSl
+tJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6FzaZsT0pPBWGTMpWmWSBUdGSquE
+wx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9TcXzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07Q
+JNBAsNB1CI69aO4I1258EHBGG3zgiLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv
+5wiZqAxeJoBF1PhoL5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+Gpz
+jLrFNe85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wrkkVbbiVg
+hUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+vhV4nYWBSipX3tUZQ9rb
+yltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQUYDksswBVLuT1sw5XxJFBAJw/6KXf6vb/
+yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ==
+-----END CERTIFICATE-----
+
+GlobalSign ECC Root CA - R4
+===========================
+-----BEGIN CERTIFICATE-----
+MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYDVQQLExtHbG9i
+YWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds
+b2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgwMTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9i
+YWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds
+b2JhbFNpZ24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkW
+ymOxuYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNVHQ8BAf8E
+BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/+wpu+74zyTyjhNUwCgYI
+KoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147bmF0774BxL4YSFlhgjICICadVGNA3jdg
+UM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm
+-----END CERTIFICATE-----
+
+GTS Root R1
+===========
+-----BEGIN CERTIFICATE-----
+MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQGEwJV
+UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg
+UjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE
+ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0G
+CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM
+f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7wCl7raKb0
+xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjwTcLCeoiKu7rPWRnWr4+w
+B7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0PfyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXW
+nOunVmSPlk9orj2XwoSPwLxAwAtcvfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk
+9+aCEI3oncKKiPo4Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zq
+kUspzBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92wO1A
+K/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70paDPvOmbsB4om3xPX
+V2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDW
+cfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0T
+AQH/BAUwAwEB/zAdBgNVHQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQAD
+ggIBAJ+qQibbC5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe
+QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuyh6f88/qBVRRi
+ClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM47HLwEXWdyzRSjeZ2axfG34ar
+J45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8JZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYci
+NuaCp+0KueIHoI17eko8cdLiA6EfMgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5me
+LMFrUKTX5hgUvYU/Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJF
+fbdT6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ0E6yove+
+7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm2tIMPNuzjsmhDYAPexZ3
+FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bbbP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3
+gm3c
+-----END CERTIFICATE-----
+
+GTS Root R2
+===========
+-----BEGIN CERTIFICATE-----
+MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQGEwJV
+UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg
+UjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE
+ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0G
+CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3Lv
+CvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY6Dlo7JUl
+e3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAuMC6C/Pq8tBcKSOWIm8Wb
+a96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7kRXuJVfeKH2JShBKzwkCX44ofR5GmdFrS
++LFjKBC4swm4VndAoiaYecb+3yXuPuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7M
+kogwTZq9TwtImoS1mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJG
+r61K8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RWIr9q
+S34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKaG73VululycslaVNV
+J1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCqgc7dGtxRcw1PcOnlthYhGXmy5okL
+dWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0T
+AQH/BAUwAwEB/zAdBgNVHQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQAD
+ggIBAB/Kzt3HvqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8
+0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyCB19m3H0Q/gxh
+swWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2uNmSRXbBoGOqKYcl3qJfEycel
+/FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMgyALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVn
+jWQye+mew4K6Ki3pHrTgSAai/GevHyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y5
+9PYjJbigapordwj6xLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M
+7YNRTOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924SgJPFI/2R8
+0L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV7LXTWtiBmelDGDfrs7vR
+WGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjW
+HYbL
+-----END CERTIFICATE-----
+
+GTS Root R3
+===========
+-----BEGIN CERTIFICATE-----
+MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJVUzEi
+MCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMw
+HhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZ
+R29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjO
+PQIBBgUrgQQAIgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout
+736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL24CejQjBA
+MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTB8Sa6oC2uhYHP0/Eq
+Er24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azT
+L818+FsuVbu/3ZL3pAzcMeGiAjEA/JdmZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV
+11RZt+cRLInUue4X
+-----END CERTIFICATE-----
+
+GTS Root R4
+===========
+-----BEGIN CERTIFICATE-----
+MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJVUzEi
+MCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQw
+HhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZ
+R29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjO
+PQIBBgUrgQQAIgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzu
+hXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvRHYqjQjBA
+MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSATNbrdP9JNqPV2Py1
+PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/C
+r8deVl5c1RxYIigL9zC2L7F8AjEA8GE8p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh
+4rsUecrNIdSUtUlD
+-----END CERTIFICATE-----
diff --git a/FreeFileSync/Source/Makefile b/FreeFileSync/Source/Makefile
index 2abdfbbb..69cf2536 100644
--- a/FreeFileSync/Source/Makefile
+++ b/FreeFileSync/Source/Makefile
@@ -1,6 +1,6 @@
exeName = FreeFileSync_$(shell arch)
-cxxFlags = -std=c++2a -pipe -DWXINTL_NO_GETTEXT_MACRO -I../.. -I../../zenXml -include "zen/i18n.h" -include "zen/warn_static.h" \
+cxxFlags = -std=c++2b -pipe -DWXINTL_NO_GETTEXT_MACRO -I../.. -I../../zenXml -include "zen/i18n.h" -include "zen/warn_static.h" \
-Wall -Wfatal-errors -Wmissing-include-dirs -Wswitch-enum -Wcast-align -Wnon-virtual-dtor -Wno-unused-function -Wshadow -Wno-maybe-uninitialized \
-O3 -DNDEBUG `wx-config --cxxflags --debug=no` -pthread
diff --git a/FreeFileSync/Source/RealTimeSync/Makefile b/FreeFileSync/Source/RealTimeSync/Makefile
index 3a69ce60..26d370c0 100644
--- a/FreeFileSync/Source/RealTimeSync/Makefile
+++ b/FreeFileSync/Source/RealTimeSync/Makefile
@@ -1,6 +1,6 @@
exeName = RealTimeSync_$(shell arch)
-cxxFlags = -std=c++2a -pipe -DWXINTL_NO_GETTEXT_MACRO -I../../.. -I../../../zenXml -include "zen/i18n.h" -include "zen/warn_static.h" \
+cxxFlags = -std=c++2b -pipe -DWXINTL_NO_GETTEXT_MACRO -I../../.. -I../../../zenXml -include "zen/i18n.h" -include "zen/warn_static.h" \
-Wall -Wfatal-errors -Wmissing-include-dirs -Wswitch-enum -Wcast-align -Wnon-virtual-dtor -Wno-unused-function -Wshadow -Wno-maybe-uninitialized \
-O3 -DNDEBUG `wx-config --cxxflags --debug=no` -pthread
diff --git a/FreeFileSync/Source/RealTimeSync/application.cpp b/FreeFileSync/Source/RealTimeSync/application.cpp
index 1cf99261..873d1d54 100644
--- a/FreeFileSync/Source/RealTimeSync/application.cpp
+++ b/FreeFileSync/Source/RealTimeSync/application.cpp
@@ -83,7 +83,7 @@ bool Application::OnInit()
}
catch (const SysError& e)
{
- logInitError(e.toString() + L"\n" L"Loading GTK3\'s old CSS format instead...\n");
+ std::cerr << "RealTimeSync" << utfTo<std::string>(SPACED_DASH + e.toString()) + "\n" "Loading GTK3\'s old CSS format instead..." "\n";
try
{
loadCSS("Gtk3Styles.old.css"); //throw SysError
@@ -108,7 +108,7 @@ bool Application::OnInit()
//Windows User Experience Interaction Guidelines: tool tips should have 5s timeout, info tips no timeout => compromise:
- wxToolTip::Enable(true); //yawn, a wxWidgets screw-up: wxToolTip::SetAutoPop is no-op if global tooltip window is not yet constructed: wxToolTip::Enable creates it
+ wxToolTip::Enable(true); //wxWidgets screw-up: wxToolTip::SetAutoPop is no-op if global tooltip window is not yet constructed: wxToolTip::Enable creates it
wxToolTip::SetAutoPop(10'000); //https://docs.microsoft.com/en-us/windows/win32/uxguide/ctrl-tooltips-and-infotips
SetAppName(L"RealTimeSync");
@@ -121,8 +121,15 @@ bool Application::OnInit()
catch (const FileError& e) { logInitError(e.toString()); }
- Bind(wxEVT_QUERY_END_SESSION, [this](wxCloseEvent& event) { onQueryEndSession(event); }); //can veto
- Bind(wxEVT_END_SESSION, [this](wxCloseEvent& event) { onQueryEndSession(event); }); //can *not* veto
+ auto onSystemShutdown = []
+ {
+ onSystemShutdownRunTasks();
+
+ //it's futile to try and clean up while the process is in full swing (CRASH!) => just terminate!
+ terminateProcess(fff::FFS_EXIT_ABORTED);
+ };
+ Bind(wxEVT_QUERY_END_SESSION, [onSystemShutdown](wxCloseEvent& event) { onSystemShutdown(); }); //can veto
+ Bind(wxEVT_END_SESSION, [onSystemShutdown](wxCloseEvent& event) { onSystemShutdown(); }); //can *not* veto
//Note: app start is deferred: -> see FreeFileSync
Bind(EVENT_ENTER_EVENT_LOOP, &Application::onEnterEventLoop, this);
@@ -200,12 +207,3 @@ void Application::OnUnhandledException() //handles both wxApp::OnInit() + wxApp:
}
//catch (...) -> Windows: let it crash and create mini dump!!! Linux/macOS: std::exception::what() logged to console
}
-
-
-void Application::onQueryEndSession(wxEvent& event)
-{
- if (auto mainWin = dynamic_cast<MainDialog*>(GetTopWindow()))
- mainWin->onQueryEndSession();
- //it's futile to try and clean up while the process is in full swing (CRASH!) => just terminate!
- terminateProcess(fff::FFS_EXIT_ABORTED);
-}
diff --git a/FreeFileSync/Source/RealTimeSync/application.h b/FreeFileSync/Source/RealTimeSync/application.h
index c6dbccaa..b2b52028 100644
--- a/FreeFileSync/Source/RealTimeSync/application.h
+++ b/FreeFileSync/Source/RealTimeSync/application.h
@@ -23,7 +23,6 @@ private:
wxLayoutDirection GetLayoutDirection() const override;
void onEnterEventLoop(wxEvent& event);
- void onQueryEndSession(wxEvent& event);
};
}
diff --git a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp
index 46c79ec0..92a0b23c 100644
--- a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp
+++ b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp
@@ -14,6 +14,7 @@
#include <zen/file_access.h>
#include <zen/file_path.h>
#include <zen/build_info.h>
+#include <zen/shutdown.h>
#include <zen/time.h>
#include "config.h"
#include "tray_menu.h"
@@ -21,6 +22,7 @@
#include "../icon_buffer.h"
#include "../ffs_paths.h"
#include "../version/version.h"
+#include "../fatal_error.h"
#include <gtk/gtk.h>
@@ -51,7 +53,7 @@ public:
FolderGenerated(parent),
folderSelector_(parent, *this, *m_buttonSelectFolder, *m_txtCtrlDirectory, folderLastSelected, nullptr /*staticText*/)
{
- m_bpButtonRemoveFolder->SetBitmapLabel(loadImage("item_remove"));
+ setImage(*m_bpButtonRemoveFolder, loadImage("item_remove"));
}
void setPath(const Zstring& dirpath) { folderSelector_.setPath(dirpath); }
@@ -85,12 +87,12 @@ MainDialog::MainDialog(const Zstring& cfgFileName) :
m_bpButtonRemoveTopFolder->Hide();
m_panelMainFolder->Layout();
- m_bitmapBatch ->SetBitmap(loadImage("cfg_batch_sicon"));
- m_bitmapFolders->SetBitmap(fff::IconBuffer::genericDirIcon(fff::IconBuffer::SIZE_SMALL));
- m_bitmapConsole->SetBitmap(loadImage("command_line", fastFromDIP(20)));
+ setImage(*m_bitmapBatch, loadImage("cfg_batch_sicon"));
+ setImage(*m_bitmapFolders, fff::IconBuffer::genericDirIcon(fff::IconBuffer::SIZE_SMALL));
+ setImage(*m_bitmapConsole, loadImage("command_line", fastFromDIP(20)));
- m_bpButtonAddFolder ->SetBitmapLabel(loadImage("item_add"));
- m_bpButtonRemoveTopFolder->SetBitmapLabel(loadImage("item_remove"));
+ setImage(*m_bpButtonAddFolder, loadImage("item_add"));
+ setImage(*m_bpButtonRemoveTopFolder, loadImage("item_remove"));
setBitmapTextLabel(*m_buttonStart, loadImage("startRts"), m_buttonStart->GetLabelText(), fastFromDIP(5), fastFromDIP(8));
Bind(wxEVT_CHAR_HOOK, [this](wxKeyEvent& event) { onLocalKeyEvent(event); });
@@ -134,6 +136,8 @@ MainDialog::MainDialog(const Zstring& cfgFileName) :
setLastUsedConfig(currentConfigFile);
//-----------------------------------------------------------------------------------------
+ onSystemShutdownRegister(onBeforeSystemShutdownCookie_);
+
Center(); //needs to be re-applied after a dialog size change! (see addFolder() within setConfiguration())
if (startWatchingImmediately) //start watch mode directly
@@ -156,10 +160,8 @@ MainDialog::MainDialog(const Zstring& cfgFileName) :
MainDialog::~MainDialog()
{
- //save current configuration
const XmlRealConfig currentCfg = getConfiguration();
-
- try //write config to XML
+ try
{
writeConfig(currentCfg, lastRunConfigPath_); //throw FileError
}
@@ -170,10 +172,10 @@ MainDialog::~MainDialog()
}
-void MainDialog::onQueryEndSession()
+void MainDialog::onBeforeSystemShutdown()
{
try { writeConfig(getConfiguration(), lastRunConfigPath_); } //throw FileError
- catch (FileError&) {} //we try our best do to something useful in this extreme situation - no reason to notify or even log errors here!
+ catch (const FileError& e) { fff::logFatalError(e.toString()); }
}
diff --git a/FreeFileSync/Source/RealTimeSync/main_dlg.h b/FreeFileSync/Source/RealTimeSync/main_dlg.h
index 8ce409a6..f83c5440 100644
--- a/FreeFileSync/Source/RealTimeSync/main_dlg.h
+++ b/FreeFileSync/Source/RealTimeSync/main_dlg.h
@@ -28,12 +28,12 @@ class MainDialog: public MainDlgGenerated
public:
static void create(const Zstring& cfgFile);
- void onQueryEndSession(); //last chance to do something useful before killing the application!
-
private:
MainDialog(const Zstring& cfgFileName);
~MainDialog();
+ void onBeforeSystemShutdown(); //last chance to do something useful before killing the application!
+
void loadConfig(const Zstring& filepath);
void onClose (wxCloseEvent& event ) override { Destroy(); }
@@ -67,6 +67,8 @@ private:
Zstring folderLastSelected_;
zen::AsyncGuiQueue guiQueue_; //schedule and run long-running tasks asynchronously, but process results on GUI queue
+
+ const zen::SharedRef<std::function<void()>> onBeforeSystemShutdownCookie_ = zen::makeSharedRef<std::function<void()>>([this] { onBeforeSystemShutdown(); });
};
}
diff --git a/FreeFileSync/Source/afs/abstract.cpp b/FreeFileSync/Source/afs/abstract.cpp
index 76ad0c6e..de534470 100644
--- a/FreeFileSync/Source/afs/abstract.cpp
+++ b/FreeFileSync/Source/afs/abstract.cpp
@@ -41,7 +41,7 @@ std::weak_ordering AFS::compareDevice(const AbstractFileSystem& lhs, const Abstr
//note: in worst case, order is guaranteed to be stable only during each program run
//caveat: typeid returns static type for pointers, dynamic type for references!!!
if (const std::strong_ordering cmp = std::type_index(typeid(lhs)) <=> std::type_index(typeid(rhs));
- std::is_neq(cmp))
+ cmp != std::strong_ordering::equal)
return cmp;
return lhs.compareDeviceSameAfsType(rhs);
diff --git a/FreeFileSync/Source/afs/abstract.h b/FreeFileSync/Source/afs/abstract.h
index 0e2bdc88..87bcd558 100644
--- a/FreeFileSync/Source/afs/abstract.h
+++ b/FreeFileSync/Source/afs/abstract.h
@@ -411,7 +411,7 @@ private:
inline std::weak_ordering operator<=>(const AfsDevice& lhs, const AfsDevice& rhs) { return AbstractFileSystem::compareDevice(lhs.ref(), rhs.ref()); }
-inline bool operator== (const AfsDevice& lhs, const AfsDevice& rhs) { return std::is_eq(lhs <=> rhs); }
+inline bool operator== (const AfsDevice& lhs, const AfsDevice& rhs) { return (lhs <=> rhs) == std::weak_ordering::equivalent; }
inline
std::weak_ordering operator<=>(const AbstractPath& lhs, const AbstractPath& rhs)
diff --git a/FreeFileSync/Source/afs/concrete.cpp b/FreeFileSync/Source/afs/concrete.cpp
index faa7dd01..40a7f015 100644
--- a/FreeFileSync/Source/afs/concrete.cpp
+++ b/FreeFileSync/Source/afs/concrete.cpp
@@ -22,11 +22,12 @@ void fff::initAfs(const AfsConfig& cfg)
}
-void fff::teardownAfs()
+std::wstring /*warningMsg*/ fff::teardownAfs()
{
- gdriveTeardown();
+ std::wstring warningMsg = gdriveTeardown();
sftpTeardown();
ftpTeardown();
+ return warningMsg;
}
diff --git a/FreeFileSync/Source/afs/concrete.h b/FreeFileSync/Source/afs/concrete.h
index 0a524f0e..ac1306b2 100644
--- a/FreeFileSync/Source/afs/concrete.h
+++ b/FreeFileSync/Source/afs/concrete.h
@@ -17,7 +17,7 @@ struct AfsConfig
Zstring configDirPathPf; //directory to store AFS-specific files
};
void initAfs(const AfsConfig& cfg);
-void teardownAfs();
+[[nodiscard]] std::wstring /*warningMsg*/ teardownAfs();
AbstractPath getNullPath();
AbstractPath createAbstractPath(const Zstring& itemPathPhrase); //noexcept
diff --git a/FreeFileSync/Source/afs/ftp.cpp b/FreeFileSync/Source/afs/ftp.cpp
index 4a05fb73..23497fef 100644
--- a/FreeFileSync/Source/afs/ftp.cpp
+++ b/FreeFileSync/Source/afs/ftp.cpp
@@ -47,7 +47,7 @@ int getEffectivePort(int portOption)
{
if (portOption > 0)
return portOption;
- return DEFAULT_PORT_FTP;
+ return DEFAULT_PORT_FTP;
}
}
@@ -58,7 +58,7 @@ std::weak_ordering operator<=>(const FtpSessionId& lhs, const FtpSessionId& rhs)
{
//exactly the type of case insensitive comparison we need for server names!
if (const std::weak_ordering cmp = compareAsciiNoCase(lhs.server, rhs.server); //https://docs.microsoft.com/en-us/windows/win32/api/ws2tcpip/nf-ws2tcpip-getaddrinfow#IDNs
- std::is_neq(cmp))
+ cmp != std::weak_ordering::equivalent)
return cmp;
const int portLhs = getEffectivePort(lhs.port);
@@ -157,7 +157,7 @@ std::wstring getCurlDisplayPath(const FtpSessionId& sessionId, const AfsPath& af
if (!sessionId.username.empty()) //show username! consider AFS::compareDeviceSameAfsType()
displayPath += sessionId.username + Zstr('@');
-
+
displayPath += sessionId.server;
if (const int port = getEffectivePort(sessionId.port);
@@ -176,17 +176,14 @@ std::vector<std::string> splitFtpResponse(const std::string& buf)
{
std::vector<std::string> lines;
- const auto isLb = [](char c) { return isLineBreak(c) || c == '\0'; };
- auto it = buf.begin();
- for (;;)
+ split2(buf, [](char c) { return isLineBreak(c) || c == '\0'; }, //is 0-char check even needed?
+ [&lines](const char* blockFirst, const char* blockLast)
{
- auto itLineBegin = std::find_if_not(it, buf.end(), isLb);
- if (itLineBegin == buf.end())
- return lines;
+ if (blockFirst != blockLast)
+ lines.emplace_back(blockFirst, blockLast);
+ });
- it = std::find_if(itLineBegin + 1, buf.end(), isLb);
- lines.emplace_back(itLineBegin, it);
- }
+ return lines;
}
@@ -352,7 +349,7 @@ public:
options.emplace_back(CURLOPT_PASSWORD, password.c_str());
}
- options.emplace_back(CURLOPT_PORT, getEffectivePort(sessionId_.port));
+ options.emplace_back(CURLOPT_PORT, getEffectivePort(sessionId_.port));
options.emplace_back(CURLOPT_NOSIGNAL, 1); //thread-safety: https://curl.haxx.se/libcurl/c/threadsafe.html
@@ -1972,16 +1969,16 @@ private:
//exactly the type of case insensitive comparison we need for server names!
if (const std::weak_ordering cmp = compareAsciiNoCase(lhs.server, rhs.server); //https://docs.microsoft.com/en-us/windows/win32/api/ws2tcpip/nf-ws2tcpip-getaddrinfow#IDNs
- std::is_neq(cmp))
+ cmp != std::weak_ordering::equivalent)
return cmp;
//port DOES create a *different* data source! https://freefilesync.org/forum/viewtopic.php?t=9047
- const int portLhs = getEffectivePort(lhs.port);
- const int portRhs = getEffectivePort(rhs.port);
-
+ const int portLhs = getEffectivePort(lhs.port);
+ const int portRhs = getEffectivePort(rhs.port);
+
//username: usually creates different folder view for FTP
return std::tie(portLhs, lhs.username) <=> //username: case sensitive!
- std::tie(portRhs, rhs.username);
+ std::tie(portRhs, rhs.username);
}
//----------------------------------------------------------------------------------------------------------------
@@ -2214,7 +2211,7 @@ private:
L"%y", L'\n' + fmtPath(AFS::getDisplayPath(pathTo)));
};
- if (std::is_neq(compareDeviceSameAfsType(pathTo.afsDevice.ref())))
+ if (compareDeviceSameAfsType(pathTo.afsDevice.ref()) != std::weak_ordering::equivalent)
throw ErrorMoveUnsupported(generateErrorMsg(), _("Operation not supported between different devices."));
try
@@ -2390,7 +2387,7 @@ AbstractPath fff::createItemPathFtp(const Zstring& itemPathPhrase) //noexcept
const Zstring fullPathOpt = afterFirst(pathPhrase, Zstr('@'), IfNotFoundReturn::all);
FtpLogin login;
- login.username = decodeFtpUsername(beforeFirst(credentials, Zstr(':'), IfNotFoundReturn::all)); //support standard FTP syntax, even though
+ login.username = decodeFtpUsername(beforeFirst(credentials, Zstr(':'), IfNotFoundReturn::all)); //support standard FTP syntax, even though
login.password = afterFirst(credentials, Zstr(':'), IfNotFoundReturn::none); //concatenateFtpFolderPathPhrase() uses "pass64" instead
const Zstring fullPath = beforeFirst(fullPathOpt, Zstr('|'), IfNotFoundReturn::all);
@@ -2404,7 +2401,7 @@ AbstractPath fff::createItemPathFtp(const Zstring& itemPathPhrase) //noexcept
const Zstring port = afterLast(serverPort, Zstr(':'), IfNotFoundReturn::none);
login.port = stringTo<int>(port); //0 if empty
- for (const Zstring& optPhrase : split(options, Zstr("|"), SplitOnEmpty::skip))
+ for (const Zstring& optPhrase : split(options, Zstr('|'), SplitOnEmpty::skip))
if (startsWith(optPhrase, Zstr("timeout=")))
login.timeoutSec = stringTo<int>(afterFirst(optPhrase, Zstr("="), IfNotFoundReturn::none));
else if (optPhrase == Zstr("ssl"))
diff --git a/FreeFileSync/Source/afs/gdrive.cpp b/FreeFileSync/Source/afs/gdrive.cpp
index e1b33519..df065225 100644
--- a/FreeFileSync/Source/afs/gdrive.cpp
+++ b/FreeFileSync/Source/afs/gdrive.cpp
@@ -21,6 +21,7 @@
#include <zen/resolve_path.h>
#include <zen/process_exec.h>
#include <zen/socket.h>
+#include <zen/shutdown.h>
#include <zen/time.h>
#include <zen/zlib_wrap.h>
#include "abstract_impl.h"
@@ -49,7 +50,7 @@ inline
std::weak_ordering operator<=>(const GdriveRawPath& lhs, const GdriveRawPath& rhs)
{
if (const std::strong_ordering cmp = lhs.parentId <=> rhs.parentId;
- std::is_neq(cmp))
+ cmp != std::strong_ordering::equal)
return cmp;
return compareNativePath(lhs.itemName, rhs.itemName);
@@ -115,8 +116,8 @@ std::wstring getGdriveDisplayPath(const GdrivePath& gdrivePath)
displayPath += utfTo<Zstring>(gdrivePath.gdriveLogin.email);
- if (!gdrivePath.gdriveLogin.sharedDriveName.empty())
- displayPath += Zstr(':') + gdrivePath.gdriveLogin.sharedDriveName;
+ if (!gdrivePath.gdriveLogin.locationName.empty())
+ displayPath += Zstr(':') + gdrivePath.gdriveLogin.locationName;
if (!gdrivePath.itemPath.value.empty())
displayPath += FILE_NAME_SEPARATOR + gdrivePath.itemPath.value;
@@ -176,7 +177,7 @@ AFS::FingerPrint getGdriveFilePrint(const std::string& itemId)
constinit Global<UniSessionCounter> httpSessionCount;
GLOBAL_RUN_ONCE(httpSessionCount.set(createUniSessionCounter()));
-UniInitializer startupInitHttp(*httpSessionCount.get());
+UniInitializer globalInitHttp(*httpSessionCount.get());
//----------------------------------------------------------------------------------------------------------------
@@ -714,7 +715,7 @@ void gdriveRevokeAccess(const GdriveAccess& access) //throw SysError
}
-int64_t gdriveGetMyDriveFreeSpace(const GdriveAccess& access) //throw SysError; returns < 0 if not available
+int64_t gdriveGetMyDriveFreeSpace(const GdriveAccess& access) //throw SysError
{
//https://developers.google.com/drive/api/v3/reference/about
std::string response;
@@ -746,6 +747,32 @@ int64_t gdriveGetMyDriveFreeSpace(const GdriveAccess& access) //throw SysError;
}
+//instead of the "root" alias Google uses an actual ID in file metadata
+std::string /*itemId*/ getMyDriveId(const GdriveAccess& access) //throw SysError
+{
+ //https://developers.google.com/drive/api/v3/reference/files/get
+ const std::string& queryParams = xWwwFormUrlEncode(
+ {
+ {"supportsAllDrives", "true"},
+ {"fields", "id"},
+ });
+ std::string response;
+ gdriveHttpsRequest("/drive/v3/files/root?" + queryParams, {} /*extraHeaders*/, {} /*extraOptions*/,
+ [&](std::span<const char> buf) { response.append(buf.data(), buf.size()); },
+ nullptr /*readRequest*/, nullptr /*receiveHeader*/, access); //throw SysError
+
+ JsonValue jresponse;
+ try { jresponse = parseJson(response); }
+ catch (JsonParsingError&) {}
+
+ const std::optional<std::string> itemId = getPrimitiveFromJsonObject(jresponse, "id");
+ if (!itemId)
+ throw SysError(formatGdriveErrorRaw(response));
+
+ return *itemId;
+}
+
+
struct DriveDetails
{
std::string driveId;
@@ -797,6 +824,69 @@ std::vector<DriveDetails> getSharedDrives(const GdriveAccess& access) //throw Sy
}
+struct StarredFolderDetails
+{
+ std::string folderId;
+ Zstring folderName;
+ std::string sharedDriveId; //empty if on "My Drive"
+};
+std::vector<StarredFolderDetails> getStarredFolders(const GdriveAccess& access) //throw SysError
+{
+ //https://developers.google.com/drive/api/v3/reference/files/list
+ std::vector<StarredFolderDetails> starredFolders;
+ {
+ std::optional<std::string> nextPageToken;
+ do
+ {
+ std::string queryParams = xWwwFormUrlEncode(
+ {
+ {"corpora", "allDrives"}, //"The 'user' corpus includes all files in "My Drive" and "Shared with me" https://developers.google.com/drive/api/v3/reference/files/list
+ {"includeItemsFromAllDrives", "true"},
+ {"pageSize", "1000"}, //"[1, 1000] Default: 100"
+ {"q", std::string("not trashed and starred and mimeType = '") + gdriveFolderMimeType + "'"},
+ {"spaces", "drive"},
+ {"supportsAllDrives", "true"},
+ {"fields", "nextPageToken,incompleteSearch,files(id,name,driveId)"}, //https://developers.google.com/drive/api/v3/reference/files
+ });
+ if (nextPageToken)
+ queryParams += '&' + xWwwFormUrlEncode({{"pageToken", *nextPageToken}});
+
+ std::string response;
+ gdriveHttpsRequest("/drive/v3/files?" + queryParams, {} /*extraHeaders*/, {} /*extraOptions*/,
+ [&](std::span<const char> buf) { response.append(buf.data(), buf.size()); },
+ nullptr /*readRequest*/, nullptr /*receiveHeader*/, access); //throw SysError
+
+ JsonValue jresponse;
+ try { jresponse = parseJson(response); }
+ catch (JsonParsingError&) {}
+
+ /**/ nextPageToken = getPrimitiveFromJsonObject(jresponse, "nextPageToken");
+ const std::optional<std::string> incompleteSearch = getPrimitiveFromJsonObject(jresponse, "incompleteSearch");
+ const JsonValue* files = getChildFromJsonObject (jresponse, "files");
+ if (!incompleteSearch || *incompleteSearch != "false" || !files || files->type != JsonValue::Type::array)
+ throw SysError(formatGdriveErrorRaw(response));
+
+ for (const JsonValue& childVal : files->arrayVal)
+ {
+ assert(childVal.type == JsonValue::Type::object);
+ const std::optional<std::string> itemId = getPrimitiveFromJsonObject(childVal, "id");
+ const std::optional<std::string> itemName = getPrimitiveFromJsonObject(childVal, "name");
+ const std::optional<std::string> driveId = getPrimitiveFromJsonObject(childVal, "driveId");
+
+ if (!itemId || itemId->empty() || !itemName || itemName->empty())
+ throw SysError(formatGdriveErrorRaw(serializeJson(childVal)));
+
+ starredFolders.push_back({std::move(*itemId),
+ utfTo<Zstring>(*itemName),
+ driveId ? *driveId : ""});
+ }
+ }
+ while (nextPageToken);
+ }
+ return starredFolders;
+}
+
+
enum class GdriveItemType : unsigned char
{
file,
@@ -837,7 +927,7 @@ GdriveItemDetails extractItemDetails(const JsonValue& jvalue) //throw SysError
const JsonValue* parents = getChildFromJsonObject (jvalue, "parents");
const JsonValue* shortcut = getChildFromJsonObject (jvalue, "shortcutDetails");
- if (!itemName || !mimeType || !modifiedTime)
+ if (!itemName || itemName->empty() || !mimeType || !modifiedTime)
throw SysError(formatGdriveErrorRaw(serializeJson(jvalue)));
const GdriveItemType type = *mimeType == gdriveFolderMimeType ? GdriveItemType::folder :
@@ -857,7 +947,7 @@ GdriveItemDetails extractItemDetails(const JsonValue& jvalue) //throw SysError
throw SysError(L"Modification time could not be parsed. (" + utfTo<std::wstring>(*modifiedTime) + L')');
std::vector<std::string> parentIds;
- if (parents) //item without parents is possible! e.g. shared item located in "Shared with me", referenced via a Shortcut
+ if (parents) //item without "parents" array is possible! e.g. 1. shared item located in "Shared with me", referenced via a Shortcut 2. root folder under "Computers"
for (const JsonValue& parentVal : parents->arrayVal)
{
if (parentVal.type != JsonValue::Type::string)
@@ -930,7 +1020,7 @@ std::vector<GdriveItem> readFolderContent(const std::string& folderId, const Gdr
{"corpora", "allDrives"}, //"The 'user' corpus includes all files in "My Drive" and "Shared with me" https://developers.google.com/drive/api/v3/reference/files/list
{"includeItemsFromAllDrives", "true"},
{"pageSize", "1000"}, //"[1, 1000] Default: 100"
- {"q", "trashed=false and '" + folderId + "' in parents"},
+ {"q", "not trashed and '" + folderId + "' in parents"},
{"spaces", "drive"},
{"supportsAllDrives", "true"},
{"fields", "nextPageToken,incompleteSearch,files(id,name,mimeType,ownedByMe,size,modifiedTime,parents,shortcutDetails(targetId))"}, //https://developers.google.com/drive/api/v3/reference/files
@@ -1658,32 +1748,6 @@ std::string /*itemId*/ gdriveUploadFile(const Zstring& fileName, const std::stri
}
-//instead of the "root" alias Google uses an actual ID in file metadata
-std::string /*itemId*/ getMyDriveId(const GdriveAccess& access) //throw SysError
-{
- //https://developers.google.com/drive/api/v3/reference/files/get
- const std::string& queryParams = xWwwFormUrlEncode(
- {
- {"supportsAllDrives", "true"},
- {"fields", "id"},
- });
- std::string response;
- gdriveHttpsRequest("/drive/v3/files/root?" + queryParams, {} /*extraHeaders*/, {} /*extraOptions*/,
- [&](std::span<const char> buf) { response.append(buf.data(), buf.size()); },
- nullptr /*readRequest*/, nullptr /*receiveHeader*/, access); //throw SysError
-
- JsonValue jresponse;
- try { jresponse = parseJson(response); }
- catch (JsonParsingError&) {}
-
- const std::optional<std::string> itemId = getPrimitiveFromJsonObject(jresponse, "id");
- if (!itemId)
- throw SysError(formatGdriveErrorRaw(response));
-
- return *itemId;
-}
-
-
class GdriveAccessBuffer //per-user-session & drive! => serialize access (perf: amortized fully buffered!)
{
public:
@@ -1766,14 +1830,14 @@ class GdriveDrivesBuffer;
class GdriveFileState //per-user-session! => serialize access (perf: amortized fully buffered!)
{
public:
- GdriveFileState(const std::string& driveId, //ID of shared drive or "My Drive": not empty!
+ GdriveFileState(const std::string& driveId, //ID of shared drive or "My Drive": never empty!
const Zstring& sharedDriveName, //*empty* for "My Drive"
GdriveAccessBuffer& accessBuf) : //throw SysError
/* issue getChangesCurrentToken() as the very first Google Drive query! */
lastSyncToken_(getChangesCurrentToken(sharedDriveName.empty() ? std::string() : driveId, accessBuf.getAccessToken())), //throw SysError
driveId_(driveId),
sharedDriveName_(sharedDriveName),
- accessBuf_(accessBuf) { assert(sharedDriveName != Zstr("My Drive")); }
+ accessBuf_(accessBuf) { assert(!driveId.empty() && sharedDriveName != Zstr("My Drive")); }
GdriveFileState(MemoryStreamIn<std::string>& stream, GdriveAccessBuffer& accessBuf) : //throw SysError
accessBuf_(accessBuf)
@@ -1866,7 +1930,8 @@ public:
std::string getDriveId() const { return driveId_; }
- Zstring getSharedDriveName() const { return sharedDriveName_; }
+ Zstring getSharedDriveName() const { return sharedDriveName_; } //*empty* for "My Drive"
+
void setSharedDriveName(const Zstring& sharedDriveName) { sharedDriveName_ = sharedDriveName; }
struct PathStatus
@@ -1876,42 +1941,42 @@ public:
AfsPath existingPath; //input path =: existingPath + relPath
std::vector<Zstring> relPath; //
};
- PathStatus getPathStatus(const AfsPath& afsPath, bool followLeafShortcut) //throw SysError
+ PathStatus getPathStatus(const std::string& locationRootId, const AfsPath& afsPath, bool followLeafShortcut) //throw SysError
{
const std::vector<Zstring> relPath = split(afsPath.value, FILE_NAME_SEPARATOR, SplitOnEmpty::skip);
if (relPath.empty())
- return {driveId_, GdriveItemType::folder, AfsPath(), {}};
+ return {locationRootId, GdriveItemType::folder, AfsPath(), {}};
else
- return getPathStatusSub(driveId_, AfsPath(), relPath, followLeafShortcut); //throw SysError
+ return getPathStatusSub(locationRootId, AfsPath(), relPath, followLeafShortcut); //throw SysError
}
- std::string /*itemId*/ getItemId(const AfsPath& afsPath, bool followLeafShortcut) //throw SysError
+ std::string /*itemId*/ getItemId(const std::string& locationRootId, const AfsPath& afsPath, bool followLeafShortcut) //throw SysError
{
- const GdriveFileState::PathStatus& ps = getPathStatus(afsPath, followLeafShortcut); //throw SysError
+ const GdriveFileState::PathStatus& ps = getPathStatus(locationRootId, afsPath, followLeafShortcut); //throw SysError
if (ps.relPath.empty())
return ps.existingItemId;
const AfsPath afsPathMissingChild(nativeAppendPaths(ps.existingPath.value, ps.relPath.front()));
- throw SysError(replaceCpy(_("Cannot find %x."), L"%x", fmtPath(getDisplayPath(afsPathMissingChild))));
+ throw SysError(replaceCpy(_("Cannot find %x."), L"%x", fmtPath(getShortDisplayPath(afsPathMissingChild))));
}
- std::pair<std::string /*itemId*/, GdriveItemDetails> getFileAttributes(const AfsPath& afsPath, bool followLeafShortcut) //throw SysError
+ std::pair<std::string /*itemId*/, GdriveItemDetails> getFileAttributes(const std::string& locationRootId, const AfsPath& afsPath, bool followLeafShortcut) //throw SysError
{
- const std::string itemId = getItemId(afsPath, followLeafShortcut); //throw SysError
-
- if (afsPath.value.empty()) //root drives obviously not covered by itemDetails_
+ if (afsPath.value.empty()) //location root not covered by itemDetails_
{
GdriveItemDetails rootDetails = {};
rootDetails.type = GdriveItemType::folder;
//rootDetails.itemName =... => better leave empty for a root item!
rootDetails.owner = sharedDriveName_.empty() ? FileOwner::me : FileOwner::none;
- return {itemId, std::move(rootDetails)};
+ return {locationRootId, std::move(rootDetails)};
}
- else if (auto it = itemDetails_.find(itemId);
- it != itemDetails_.end())
+
+ const std::string itemId = getItemId(locationRootId, afsPath, followLeafShortcut); //throw SysError
+ if (auto it = itemDetails_.find(itemId);
+ it != itemDetails_.end())
return *it;
- //itemId was found! => must either be a drive root or buffered in itemDetails_
+ //itemId was found! => (must either be a location root) or buffered in itemDetails_
throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__));
}
@@ -2044,10 +2109,9 @@ private:
friend class GdriveDrivesBuffer;
- std::wstring getDisplayPath(const AfsPath& afsPath) const
+ std::wstring getShortDisplayPath(const AfsPath& afsPath) const
{
- const GdriveLogin login{accessBuf_.getUserEmail(), sharedDriveName_};
- return getGdriveDisplayPath({login, afsPath});
+ return utfTo<std::wstring>(FILE_NAME_SEPARATOR + afsPath.value); //sufficient info for SysError + we don't have a locationName anyway
}
void notifyItemUpdated(const FileStateDelta& stateDelta, const std::string& itemId, const GdriveItemDetails* details)
@@ -2112,7 +2176,7 @@ private:
{
if (itFound != itemDetails_.end())
throw SysError(replaceCpy(_("Cannot find %x."), L"%x",
- fmtPath(getDisplayPath(AfsPath(nativeAppendPaths(folderPath.value, relPath.front()))))) + L' ' +
+ fmtPath(getShortDisplayPath(AfsPath(nativeAppendPaths(folderPath.value, relPath.front()))))) + L' ' +
replaceCpy(_("The name %x is used by more than one item in the folder."), L"%x", fmtPath(relPath.front())));
itFound = itChild;
@@ -2166,7 +2230,7 @@ private:
case GdriveItemType::shortcut: //should never happen: creating shortcuts to shortcuts fails with "Internal Error"
throw SysError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x",
- fmtPath(getDisplayPath(AfsPath(nativeAppendPaths(folderPath.value, relPath.front()))))) + L' ' +
+ fmtPath(getShortDisplayPath(AfsPath(nativeAppendPaths(folderPath.value, relPath.front()))))) + L' ' +
L"Google Drive Shortcut points to another Shortcut.");
}
break;
@@ -2265,13 +2329,41 @@ private:
std::vector<std::weak_ptr<ItemIdDelta>> changeLog_; //track changed items since FileStateDelta was created (includes sync with Google + our own intermediate change notifications)
- std::string driveId_; //ID of shared drive or "My Drive": not empty!
+ std::string driveId_; //ID of shared drive or "My Drive": never empty!
Zstring sharedDriveName_; //name of shared drive: empty for "My Drive"!
GdriveAccessBuffer& accessBuf_;
};
+class GdriveFileStateAtLocation
+{
+public:
+ GdriveFileStateAtLocation(GdriveFileState& fileState, const std::string& locationRootId) : fileState_(fileState), locationRootId_(locationRootId) {}
+
+ GdriveFileState::PathStatus getPathStatus(const AfsPath& afsPath, bool followLeafShortcut) //throw SysError
+ {
+ return fileState_.getPathStatus(locationRootId_, afsPath, followLeafShortcut); //throw SysError
+ }
+
+ std::string /*itemId*/ getItemId(const AfsPath& afsPath, bool followLeafShortcut) //throw SysError
+ {
+ return fileState_.getItemId(locationRootId_, afsPath, followLeafShortcut); //throw SysError
+ }
+
+ std::pair<std::string /*itemId*/, GdriveItemDetails> getFileAttributes(const AfsPath& afsPath, bool followLeafShortcut) //throw SysError
+ {
+ return fileState_.getFileAttributes(locationRootId_, afsPath, followLeafShortcut); //throw SysError
+ }
+
+ GdriveFileState& all() { return fileState_; }
+
+private:
+ GdriveFileState& fileState_;
+ const std::string locationRootId_;
+};
+
+
class GdriveDrivesBuffer
{
public:
@@ -2298,22 +2390,27 @@ public:
writeNumber(stream, static_cast<uint32_t>(sharedDrives_.size()));
for (const auto& [driveId, fileState] : sharedDrives_)
fileState.ref().serialize(stream);
+
+ //starredFolders_? no, will be fully restored by syncWithGoogle()
}
- std::vector<Zstring /*sharedDriveName*/> listSharedDrives() //throw SysError
+ std::vector<Zstring /*locationName*/> listLocations() //throw SysError
{
if (syncIsDue())
syncWithGoogle(); //throw SysError
- std::vector<Zstring> sharedDriveNames;
+ std::vector<Zstring> locationNames;
for (const auto& [driveId, fileState] : sharedDrives_)
- sharedDriveNames.push_back(fileState.ref().getSharedDriveName());
+ locationNames.push_back(fileState.ref().getSharedDriveName());
+
+ for (const StarredFolderDetails& sfd : starredFolders_)
+ locationNames.push_back(sfd.folderName);
- return sharedDriveNames;
+ return locationNames;
}
- std::pair<GdriveFileState*, GdriveFileState::FileStateDelta> prepareAccess(const Zstring& sharedDriveName) //throw SysError
+ std::pair<GdriveFileStateAtLocation, GdriveFileState::FileStateDelta> prepareAccess(const Zstring& locationName) //throw SysError
{
//checking for added/renamed/deleted shared drives *every* GDRIVE_SYNC_INTERVAL is needlessly excessive!
// => check 1. once per FFS run
@@ -2321,24 +2418,26 @@ public:
if (lastSyncTime_ == std::chrono::steady_clock::time_point())
syncWithGoogle(); //throw SysError
- GdriveFileState* fileState = nullptr;
- try
+ GdriveFileStateAtLocation fileState = [&]
{
- fileState = &getDrive(sharedDriveName); //throw SysError
- }
- catch (SysError&)
- {
- if (syncIsDue())
- syncWithGoogle(); //throw SysError
+ try
+ {
+ return getFileState(locationName); //throw SysError
+ }
+ catch (SysError&)
+ {
+ if (syncIsDue())
+ syncWithGoogle(); //throw SysError
- fileState = &getDrive(sharedDriveName); //throw SysError
- }
+ return getFileState(locationName); //throw SysError
+ }
+ }();
//manage last sync time here so that "lastSyncToken" remains stable while accessing GdriveFileState in the callback
- if (fileState->syncIsDue())
- fileState->syncWithGoogle(); //throw SysError
+ if (fileState.all().syncIsDue())
+ fileState.all().syncWithGoogle(); //throw SysError
- return {fileState, fileState->registerFileStateDelta()};
+ return {fileState, fileState.all().registerFileStateDelta()};
}
private:
@@ -2346,6 +2445,9 @@ private:
void syncWithGoogle() //throw SysError
{
+ //run in parallel with getSharedDrives()
+ auto ftStarredFolders = runAsync([access = accessBuf_.getAccessToken() /*throw SysError*/] { return getStarredFolders(access); /*throw SysError*/ });
+
decltype(sharedDrives_) currentDrives;
//getSharedDrives() should be fast enough to avoid the unjustified complexity of change notifications: https://freefilesync.org/forum/viewtopic.php?t=7827&start=30#p29712
@@ -2365,30 +2467,57 @@ private:
currentDrives.emplace(driveId, fileState);
}
- sharedDrives_.swap(currentDrives); //transaction!
+ starredFolders_ = ftStarredFolders.get(); //throw SysError //
+ sharedDrives_.swap(currentDrives); //transaction!
lastSyncTime_ = std::chrono::steady_clock::now(); //...(uhm, mostly, except for setSharedDriveName())
}
- GdriveFileState& getDrive(const Zstring& sharedDriveName) //throw SysError
+ GdriveFileStateAtLocation getFileState(const Zstring& locationName) //throw SysError
{
- if (sharedDriveName.empty())
- return myDrive_;
+ if (locationName.empty())
+ return {myDrive_, myDrive_.getDriveId()};
+
+ GdriveFileState* fileState = nullptr;
+ std::string locationRootId;
+
+ for (auto& [driveId, fileStateRef] : sharedDrives_)
+ if (equalNativePath(fileStateRef.ref().getSharedDriveName(), locationName))
+ {
+ if (fileState)
+ throw SysError(replaceCpy(_("Cannot find %x."), L"%x",
+ fmtPath(getGdriveDisplayPath({{accessBuf_.getUserEmail(), locationName}, AfsPath()}))) + L' ' +
+ replaceCpy(_("The name %x is used by more than one item in the folder."), L"%x", fmtPath(locationName)));
+
+ fileState = &fileStateRef.ref();
+ locationRootId = driveId;
+ }
- auto itFound = sharedDrives_.end();
- for (auto it = sharedDrives_.begin(); it != sharedDrives_.end(); ++it)
- if (equalNativePath(it->second.ref().getSharedDriveName(), sharedDriveName))
+ for (const StarredFolderDetails& sfd : starredFolders_)
+ if (equalNativePath(sfd.folderName, locationName))
{
- if (itFound != sharedDrives_.end())
+ if (fileState)
throw SysError(replaceCpy(_("Cannot find %x."), L"%x",
- fmtPath(getGdriveDisplayPath({{accessBuf_.getUserEmail(), sharedDriveName}, AfsPath()}))) + L' ' +
- replaceCpy(_("The name %x is used by more than one item in the folder."), L"%x", fmtPath(sharedDriveName)));
- itFound = it;
+ fmtPath(getGdriveDisplayPath({{accessBuf_.getUserEmail(), locationName}, AfsPath()}))) + L' ' +
+ replaceCpy(_("The name %x is used by more than one item in the folder."), L"%x", fmtPath(locationName)));
+
+ if (sfd.sharedDriveId.empty()) //=> My Drive
+ fileState = &myDrive_;
+ else
+ {
+ auto it = sharedDrives_.find(sfd.sharedDriveId);
+ if (it == sharedDrives_.end())
+ break;
+
+ fileState = &it->second.ref();
+ }
+ locationRootId = sfd.folderId;
}
- if (itFound == sharedDrives_.end())
+
+ if (!fileState)
throw SysError(replaceCpy(_("Cannot find %x."), L"%x",
- fmtPath(getGdriveDisplayPath({{accessBuf_.getUserEmail(), sharedDriveName}, AfsPath()}))));
+ fmtPath(getGdriveDisplayPath({{accessBuf_.getUserEmail(), locationName}, AfsPath()}))));
- return itFound->second.ref();
+ return {*fileState, locationRootId};
}
GdriveAccessBuffer& accessBuf_;
@@ -2396,6 +2525,8 @@ private:
GdriveFileState myDrive_;
std::unordered_map<std::string /*drive ID*/, SharedRef<GdriveFileState>> sharedDrives_;
+
+ std::vector<StarredFolderDetails> starredFolders_;
};
//==========================================================================================
@@ -2404,7 +2535,10 @@ private:
class GdrivePersistentSessions
{
public:
- explicit GdrivePersistentSessions(const Zstring& configDirPath) : configDirPath_(configDirPath) {}
+ explicit GdrivePersistentSessions(const Zstring& configDirPath) : configDirPath_(configDirPath)
+ {
+ onSystemShutdownRegister(onBeforeSystemShutdownCookie_);
+ }
void saveActiveSessions() //throw FileError
{
@@ -2534,18 +2668,18 @@ public:
return emails;
}
- std::vector<Zstring /*sharedDriveName*/> listSharedDrives(const std::string& accountEmail, int timeoutSec) //throw SysError
+ std::vector<Zstring /*locationName*/> listLocations(const std::string& accountEmail, int timeoutSec) //throw SysError
{
- std::vector<Zstring> sharedDriveNames;
+ std::vector<Zstring> locationNames;
accessUserSession(accountEmail, timeoutSec, [&](std::optional<UserSession>& userSession) //throw SysError
{
if (!userSession)
- throw SysError(replaceCpy(_("Please authorize access to user account %x."), L"%x", utfTo<std::wstring>(accountEmail)));
+ throw SysError(replaceCpy(_("Please add a connection to user account %x first."), L"%x", utfTo<std::wstring>(accountEmail)));
- sharedDriveNames = userSession->drivesBuf.ref().listSharedDrives(); //throw SysError
+ locationNames = userSession->drivesBuf.ref().listLocations(); //throw SysError
});
- return sharedDriveNames;
+ return locationNames;
}
struct AsyncAccessInfo
@@ -2554,7 +2688,7 @@ public:
GdriveFileState::FileStateDelta stateDelta;
};
//perf: amortized fully buffered!
- AsyncAccessInfo accessGlobalFileState(const GdriveLogin& login, const std::function<void(GdriveFileState& fileState)>& useFileState /*throw X*/) //throw SysError, X
+ AsyncAccessInfo accessGlobalFileState(const GdriveLogin& login, const std::function<void(GdriveFileStateAtLocation& fileState)>& useFileState /*throw X*/) //throw SysError, X
{
GdriveAccess access;
GdriveFileState::FileStateDelta stateDelta;
@@ -2562,13 +2696,13 @@ public:
accessUserSession(login.email, login.timeoutSec, [&](std::optional<UserSession>& userSession) //throw SysError
{
if (!userSession)
- throw SysError(replaceCpy(_("Please authorize access to user account %x."), L"%x", utfTo<std::wstring>(login.email)));
+ throw SysError(replaceCpy(_("Please add a connection to user account %x first."), L"%x", utfTo<std::wstring>(login.email)));
- GdriveFileState* fileState = nullptr;
- access = userSession->accessBuf.ref().getAccessToken(); //throw SysError
- std::tie(fileState, stateDelta) = userSession->drivesBuf.ref().prepareAccess(login.sharedDriveName); //throw SysError
+ access = userSession->accessBuf.ref().getAccessToken(); //throw SysError
+ auto [fileState, stateDelta2] = userSession->drivesBuf.ref().prepareAccess(login.locationName); //throw SysError
+ stateDelta = std::move(stateDelta2);
- useFileState(*fileState); //throw X
+ useFileState(fileState); //throw X
});
return {access, stateDelta};
}
@@ -2724,12 +2858,19 @@ private:
Protected<GlobalSessions> globalSessions_;
const Zstring configDirPath_;
+
+ const SharedRef<std::function<void()>> onBeforeSystemShutdownCookie_ = makeSharedRef<std::function<void()>>([this]
+ {
+ try //let's not lose Google Drive data due to unexpected system shutdown:
+ { saveActiveSessions(); } //throw FileError
+ catch (FileError&) { assert(false); }
+ });
};
//==========================================================================================
constinit Global<GdrivePersistentSessions> globalGdriveSessions;
//==========================================================================================
-GdrivePersistentSessions::AsyncAccessInfo accessGlobalFileState(const GdriveLogin& login, const std::function<void(GdriveFileState& fileState)>& useFileState /*throw X*/) //throw SysError, X
+GdrivePersistentSessions::AsyncAccessInfo accessGlobalFileState(const GdriveLogin& login, const std::function<void(GdriveFileStateAtLocation& fileState)>& useFileState /*throw X*/) //throw SysError, X
{
if (const std::shared_ptr<GdrivePersistentSessions> gps = globalGdriveSessions.get())
return gps->accessGlobalFileState(login, useFileState); //throw SysError, X
@@ -2755,7 +2896,7 @@ struct GetDirDetails
{
std::string folderId;
std::optional<std::vector<GdriveItem>> childItemsBuf;
- const GdrivePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(folderPath_.gdriveLogin, [&](GdriveFileState& fileState) //throw SysError
+ const GdrivePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(folderPath_.gdriveLogin, [&](GdriveFileStateAtLocation& fileState) //throw SysError
{
const auto& [itemId, itemDetails] = fileState.getFileAttributes(folderPath_.itemPath, true /*followLeafShortcut*/); //throw SysError
@@ -2763,7 +2904,7 @@ struct GetDirDetails
throw SysError(replaceCpy<std::wstring>(L"%x is not a directory.", L"%x", fmtPath(utfTo<Zstring>(itemDetails.itemName))));
folderId = itemId;
- childItemsBuf = fileState.tryGetBufferedFolderContent(folderId);
+ childItemsBuf = fileState.all().tryGetBufferedFolderContent(folderId);
});
if (!childItemsBuf)
@@ -2771,9 +2912,9 @@ struct GetDirDetails
childItemsBuf = readFolderContent(folderId, aai.access); //throw SysError
//buffer new file state ASAP => make sure accessGlobalFileState() has amortized constant access (despite the occasional internal readFolderContent() on non-leaf folders)
- accessGlobalFileState(folderPath_.gdriveLogin, [&](GdriveFileState& fileState) //throw SysError
+ accessGlobalFileState(folderPath_.gdriveLogin, [&](GdriveFileStateAtLocation& fileState) //throw SysError
{
- fileState.notifyFolderContent(aai.stateDelta, folderId, *childItemsBuf);
+ fileState.all().notifyFolderContent(aai.stateDelta, folderId, *childItemsBuf);
});
}
@@ -2806,18 +2947,18 @@ struct GetShortcutTargetDetails
try
{
std::optional<GdriveItemDetails> targetDetailsBuf;
- const GdrivePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(shortcutPath_.gdriveLogin, [&](GdriveFileState& fileState) //throw SysError
+ const GdrivePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(shortcutPath_.gdriveLogin, [&](GdriveFileStateAtLocation& fileState) //throw SysError
{
- targetDetailsBuf = fileState.tryGetBufferedItemDetails(shortcutDetails_.targetId);
+ targetDetailsBuf = fileState.all().tryGetBufferedItemDetails(shortcutDetails_.targetId);
});
if (!targetDetailsBuf)
{
targetDetailsBuf = getItemDetails(shortcutDetails_.targetId, aai.access); //throw SysError
//buffer new file state ASAP
- accessGlobalFileState(shortcutPath_.gdriveLogin, [&](GdriveFileState& fileState) //throw SysError
+ accessGlobalFileState(shortcutPath_.gdriveLogin, [&](GdriveFileStateAtLocation& fileState) //throw SysError
{
- fileState.notifyItemUpdated(aai.stateDelta, {shortcutDetails_.targetId, *targetDetailsBuf});
+ fileState.all().notifyItemUpdated(aai.stateDelta, {shortcutDetails_.targetId, *targetDetailsBuf});
});
}
@@ -2940,7 +3081,7 @@ struct InputStreamGdrive : public AFS::InputStream
std::string fileId;
try
{
- access = accessGlobalFileState(gdrivePath.gdriveLogin, [&](GdriveFileState& fileState) //throw SysError
+ access = accessGlobalFileState(gdrivePath.gdriveLogin, [&](GdriveFileStateAtLocation& fileState) //throw SysError
{
fileId = fileState.getItemId(gdrivePath.itemPath, true /*followLeafShortcut*/); //throw SysError
}).access;
@@ -2984,7 +3125,7 @@ struct InputStreamGdrive : public AFS::InputStream
AFS::StreamAttributes attr = {};
try
{
- accessGlobalFileState(gdrivePath_.gdriveLogin, [&](GdriveFileState& fileState) //throw SysError
+ accessGlobalFileState(gdrivePath_.gdriveLogin, [&](GdriveFileStateAtLocation& fileState) //throw SysError
{
const auto& [itemId, itemDetails] = fileState.getFileAttributes(gdrivePath_.itemPath, true /*followLeafShortcut*/); //throw SysError
attr.modTime = itemDetails.modTime;
@@ -3030,7 +3171,7 @@ struct OutputStreamGdrive : public AFS::OutputStreamImpl
// otherwise ~OutputStreamImpl() will delete the already existing file! => don't check asynchronously!
const Zstring fileName = AFS::getItemName(gdrivePath.itemPath);
std::string parentId;
- /*const*/ GdrivePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(gdrivePath.gdriveLogin, [&](GdriveFileState& fileState) //throw SysError
+ /*const*/ GdrivePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(gdrivePath.gdriveLogin, [&](GdriveFileStateAtLocation& fileState) //throw SysError
{
const GdriveFileState::PathStatus& ps = fileState.getPathStatus(gdrivePath.itemPath, false /*followLeafShortcut*/); //throw SysError
if (ps.relPath.empty())
@@ -3076,9 +3217,9 @@ struct OutputStreamGdrive : public AFS::OutputStreamImpl
newFileItem.details.modTime = *modTime;
newFileItem.details.parentIds.push_back(parentId);
- accessGlobalFileState(gdrivePath.gdriveLogin, [&](GdriveFileState& fileState) //throw SysError
+ accessGlobalFileState(gdrivePath.gdriveLogin, [&](GdriveFileStateAtLocation& fileState) //throw SysError
{
- fileState.notifyItemCreated(aai.stateDelta, newFileItem);
+ fileState.all().notifyItemCreated(aai.stateDelta, newFileItem);
});
pFilePrint.set_value(getGdriveFilePrint(fileIdNew));
@@ -3152,7 +3293,7 @@ public:
try
{
GdriveFileState::PathStatus ps;
- accessGlobalFileState(gdriveLogin_, [&](GdriveFileState& fileState) //throw SysError
+ accessGlobalFileState(gdriveLogin_, [&](GdriveFileStateAtLocation& fileState) //throw SysError
{
ps = fileState.getPathStatus(folderPath, true /*followLeafShortcut*/); //throw SysError
});
@@ -3178,7 +3319,7 @@ private:
throw SysError(L"Item is device root");
std::string parentId;
- accessGlobalFileState(gdriveLogin_, [&](GdriveFileState& fileState) //throw SysError
+ accessGlobalFileState(gdriveLogin_, [&](GdriveFileStateAtLocation& fileState) //throw SysError
{
parentId = fileState.getItemId(*parentPath, true /*followLeafShortcut*/); //throw SysError
});
@@ -3197,10 +3338,10 @@ private:
const GdriveLogin& rhs = static_cast<const GdriveFileSystem&>(afsRhs).gdriveLogin_;
if (const std::weak_ordering cmp = compareAsciiNoCase(lhs.email, rhs.email);
- std::is_neq(cmp))
+ cmp != std::weak_ordering::equivalent)
return cmp;
- return compareNativePath(lhs.sharedDriveName, rhs.sharedDriveName);
+ return compareNativePath(lhs.locationName, rhs.locationName);
}
//----------------------------------------------------------------------------------------------------------------
@@ -3216,7 +3357,7 @@ private:
try
{
GdriveFileState::PathStatus ps;
- accessGlobalFileState(gdriveLogin_, [&](GdriveFileState& fileState) //throw SysError
+ accessGlobalFileState(gdriveLogin_, [&](GdriveFileStateAtLocation& fileState) //throw SysError
{
ps = fileState.getPathStatus(afsPath, false /*followLeafShortcut*/); //throw SysError
});
@@ -3245,7 +3386,7 @@ private:
const Zstring folderName = getItemName(afsPath);
std::string parentId;
- const GdrivePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(gdriveLogin_, [&](GdriveFileState& fileState) //throw SysError
+ const GdrivePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(gdriveLogin_, [&](GdriveFileStateAtLocation& fileState) //throw SysError
{
const GdriveFileState::PathStatus& ps = fileState.getPathStatus(afsPath, false /*followLeafShortcut*/); //throw SysError
if (ps.relPath.empty())
@@ -3260,9 +3401,9 @@ private:
const std::string folderIdNew = gdriveCreateFolderPlain(folderName, parentId, aai.access); //throw SysError
//buffer new file state ASAP (don't wait GDRIVE_SYNC_INTERVAL)
- accessGlobalFileState(gdriveLogin_, [&](GdriveFileState& fileState) //throw SysError
+ accessGlobalFileState(gdriveLogin_, [&](GdriveFileStateAtLocation& fileState) //throw SysError
{
- fileState.notifyFolderCreated(aai.stateDelta, folderIdNew, folderName, parentId);
+ fileState.all().notifyFolderCreated(aai.stateDelta, folderIdNew, folderName, parentId);
});
}
catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot create directory %x."), L"%x", fmtPath(getDisplayPath(afsPath))), e.toString()); }
@@ -3272,7 +3413,7 @@ private:
{
std::string itemId;
std::optional<std::string> parentIdToUnlink;
- const GdrivePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(gdriveLogin_, [&](GdriveFileState& fileState) //throw SysError
+ const GdrivePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(gdriveLogin_, [&](GdriveFileStateAtLocation& fileState) //throw SysError
{
const std::optional<AfsPath> parentPath = getParentPath(afsPath);
if (!parentPath) throw SysError(L"Item is device root");
@@ -3291,9 +3432,9 @@ private:
gdriveUnlinkParent(itemId, *parentIdToUnlink, aai.access); //throw SysError
//buffer new file state ASAP (don't wait GDRIVE_SYNC_INTERVAL)
- accessGlobalFileState(gdriveLogin_, [&](GdriveFileState& fileState) //throw SysError
+ accessGlobalFileState(gdriveLogin_, [&](GdriveFileStateAtLocation& fileState) //throw SysError
{
- fileState.notifyParentRemoved(aai.stateDelta, itemId, *parentIdToUnlink);
+ fileState.all().notifyParentRemoved(aai.stateDelta, itemId, *parentIdToUnlink);
});
}
else
@@ -3304,9 +3445,9 @@ private:
gdriveMoveToTrash(itemId, aai.access); //throw SysError
//buffer new file state ASAP (don't wait GDRIVE_SYNC_INTERVAL)
- accessGlobalFileState(gdriveLogin_, [&](GdriveFileState& fileState) //throw SysError
+ accessGlobalFileState(gdriveLogin_, [&](GdriveFileStateAtLocation& fileState) //throw SysError
{
- fileState.notifyItemDeleted(aai.stateDelta, itemId);
+ fileState.all().notifyItemDeleted(aai.stateDelta, itemId);
});
}
}
@@ -3361,7 +3502,7 @@ private:
try
{
std::string targetId;
- const GdrivePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(gdriveFs.gdriveLogin_, [&](GdriveFileState& fileState) //throw SysError
+ const GdrivePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(gdriveFs.gdriveLogin_, [&](GdriveFileStateAtLocation& fileState) //throw SysError
{
const GdriveItemDetails& itemDetails = fileState.getFileAttributes(afsPath, false /*followLeafShortcut*/).second; //throw SysError
if (itemDetails.type != GdriveItemType::shortcut)
@@ -3440,7 +3581,7 @@ private:
const Zstring itemNameNew = getItemName(apTarget);
std::string itemIdSrc;
GdriveItemDetails itemDetailsSrc;
- /*const GdrivePersistentSessions::AsyncAccessInfo aaiSrc =*/ accessGlobalFileState(gdriveLogin_, [&](GdriveFileState& fileState) //throw SysError
+ /*const GdrivePersistentSessions::AsyncAccessInfo aaiSrc =*/ accessGlobalFileState(gdriveLogin_, [&](GdriveFileStateAtLocation& fileState) //throw SysError
{
std::tie(itemIdSrc, itemDetailsSrc) = fileState.getFileAttributes(afsSource, true /*followLeafShortcut*/); //throw SysError
@@ -3450,7 +3591,7 @@ private:
});
std::string parentIdTrg;
- const GdrivePersistentSessions::AsyncAccessInfo aaiTrg = accessGlobalFileState(fsTarget.gdriveLogin_, [&](GdriveFileState& fileState) //throw SysError
+ const GdrivePersistentSessions::AsyncAccessInfo aaiTrg = accessGlobalFileState(fsTarget.gdriveLogin_, [&](GdriveFileStateAtLocation& fileState) //throw SysError
{
const GdriveFileState::PathStatus psTo = fileState.getPathStatus(apTarget.afsPath, false /*followLeafShortcut*/); //throw SysError
if (psTo.relPath.empty())
@@ -3466,17 +3607,17 @@ private:
const std::string fileIdTrg = gdriveCopyFile(itemIdSrc, parentIdTrg, itemNameNew, itemDetailsSrc.modTime, aaiTrg.access); //throw SysError
//buffer new file state ASAP (don't wait GDRIVE_SYNC_INTERVAL)
- accessGlobalFileState(fsTarget.gdriveLogin_, [&](GdriveFileState& fileState) //throw SysError
+ accessGlobalFileState(fsTarget.gdriveLogin_, [&](GdriveFileStateAtLocation& fileState) //throw SysError
{
GdriveItem newFileItem = {};
newFileItem.itemId = fileIdTrg;
newFileItem.details.itemName = itemNameNew;
newFileItem.details.type = GdriveItemType::file;
- newFileItem.details.owner = fsTarget.gdriveLogin_.sharedDriveName.empty() ? FileOwner::me : FileOwner::none;
+ newFileItem.details.owner = fileState.all().getSharedDriveName().empty() ? FileOwner::me : FileOwner::none;
newFileItem.details.fileSize = itemDetailsSrc.fileSize;
newFileItem.details.modTime = itemDetailsSrc.modTime;
newFileItem.details.parentIds.push_back(parentIdTrg);
- fileState.notifyItemCreated(aaiTrg.stateDelta, newFileItem);
+ fileState.all().notifyItemCreated(aaiTrg.stateDelta, newFileItem);
});
FileCopyResult result;
@@ -3512,7 +3653,7 @@ private:
try
{
std::string targetId;
- accessGlobalFileState(gdriveLogin_, [&](GdriveFileState& fileState) //throw SysError
+ accessGlobalFileState(gdriveLogin_, [&](GdriveFileStateAtLocation& fileState) //throw SysError
{
const GdriveItemDetails& itemDetails = fileState.getFileAttributes(afsSource, false /*followLeafShortcut*/).second; //throw SysError
if (itemDetails.type != GdriveItemType::shortcut)
@@ -3528,7 +3669,7 @@ private:
const Zstring shortcutName = getItemName(apTarget.afsPath);
std::string parentId;
- const GdrivePersistentSessions::AsyncAccessInfo aaiTrg = accessGlobalFileState(fsTarget.gdriveLogin_, [&](GdriveFileState& fileState) //throw SysError
+ const GdrivePersistentSessions::AsyncAccessInfo aaiTrg = accessGlobalFileState(fsTarget.gdriveLogin_, [&](GdriveFileStateAtLocation& fileState) //throw SysError
{
const GdriveFileState::PathStatus& ps = fileState.getPathStatus(apTarget.afsPath, false /*followLeafShortcut*/); //throw SysError
if (ps.relPath.empty())
@@ -3543,9 +3684,9 @@ private:
const std::string shortcutIdNew = gdriveCreateShortcutPlain(shortcutName, parentId, targetId, aaiTrg.access); //throw SysError
//buffer new file state ASAP (don't wait GDRIVE_SYNC_INTERVAL)
- accessGlobalFileState(fsTarget.gdriveLogin_, [&](GdriveFileState& fileState) //throw SysError
+ accessGlobalFileState(fsTarget.gdriveLogin_, [&](GdriveFileStateAtLocation& fileState) //throw SysError
{
- fileState.notifyShortcutCreated(aaiTrg.stateDelta, shortcutIdNew, shortcutName, parentId, targetId);
+ fileState.all().notifyShortcutCreated(aaiTrg.stateDelta, shortcutIdNew, shortcutName, parentId, targetId);
});
}
catch (const SysError& e)
@@ -3565,7 +3706,7 @@ private:
L"%y", L'\n' + fmtPath(AFS::getDisplayPath(pathTo)));
};
- if (std::is_neq(compareDeviceSameAfsType(pathTo.afsDevice.ref())))
+ if (compareDeviceSameAfsType(pathTo.afsDevice.ref()) != std::weak_ordering::equivalent)
throw ErrorMoveUnsupported(generateErrorMsg(), _("Operation not supported between different devices."));
//note: moving files within account works, e.g. between My Drive <-> shared drives
// BUT: not supported by our model with separate GdriveFileStates; e.g. how to handle complexity of a moved folder (tree)?
@@ -3587,7 +3728,7 @@ private:
GdriveItemDetails itemDetails;
std::string parentIdFrom;
std::string parentIdTo;
- const GdrivePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(gdriveLogin_, [&](GdriveFileState& fileState) //throw SysError
+ const GdrivePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(gdriveLogin_, [&](GdriveFileStateAtLocation& fileState) //throw SysError
{
std::tie(itemId, itemDetails) = fileState.getFileAttributes(pathFrom, false /*followLeafShortcut*/); //throw SysError
@@ -3618,9 +3759,9 @@ private:
gdriveMoveAndRenameItem(itemId, parentIdFrom, parentIdTo, itemNameNew, itemDetails.modTime, aai.access); //throw SysError
//buffer new file state ASAP (don't wait GDRIVE_SYNC_INTERVAL)
- accessGlobalFileState(gdriveLogin_, [&](GdriveFileState& fileState) //throw SysError
+ accessGlobalFileState(gdriveLogin_, [&](GdriveFileStateAtLocation& fileState) //throw SysError
{
- fileState.notifyMoveAndRename(aai.stateDelta, itemId, parentIdFrom, parentIdTo, itemNameNew);
+ fileState.all().notifyMoveAndRename(aai.stateDelta, itemId, parentIdFrom, parentIdTo, itemNameNew);
});
}
catch (const SysError& e) { throw FileError(generateErrorMsg(), e.toString()); }
@@ -3658,13 +3799,16 @@ private:
int64_t getFreeDiskSpace(const AfsPath& afsPath) const override //throw FileError, returns < 0 if not available
{
- if (!gdriveLogin_.sharedDriveName.empty())
- return -1;
-
+ bool onMyDrive = false;
try
{
- const GdriveAccess& access = accessGlobalFileState(gdriveLogin_, [](GdriveFileState& fileState) {}).access; //throw SysError
- return gdriveGetMyDriveFreeSpace(access); //throw SysError; returns < 0 if not available
+ const GdriveAccess& access = accessGlobalFileState(gdriveLogin_, [&](GdriveFileStateAtLocation& fileState)
+ { onMyDrive = fileState.all().getSharedDriveName().empty(); }).access; //throw SysError
+
+ if (onMyDrive)
+ return gdriveGetMyDriveFreeSpace(access); //throw SysError
+ else
+ return -1;
}
catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot determine free disk space for %x."), L"%x", fmtPath(getDisplayPath(afsPath))), e.toString()); }
}
@@ -3703,8 +3847,8 @@ private:
Zstring concatenateGdriveFolderPathPhrase(const GdrivePath& gdrivePath) //noexcept
{
Zstring emailAndDrive = utfTo<Zstring>(gdrivePath.gdriveLogin.email);
- if (!gdrivePath.gdriveLogin.sharedDriveName.empty())
- emailAndDrive += Zstr(':') + gdrivePath.gdriveLogin.sharedDriveName;
+ if (!gdrivePath.gdriveLogin.locationName.empty())
+ emailAndDrive += Zstr(':') + gdrivePath.gdriveLogin.locationName;
Zstring options;
if (gdrivePath.gdriveLogin.timeoutSec != GdriveLogin().timeoutSec)
@@ -3732,20 +3876,23 @@ void fff::gdriveInit(const Zstring& configDirPath, const Zstring& caCertFilePath
}
-void fff::gdriveTeardown()
+std::wstring /*warningMsg*/ fff::gdriveTeardown()
{
+ std::wstring warningMsg;
try //don't use ~GdrivePersistentSessions() to save! Might never happen, e.g. detached thread waiting for Google Drive authentication; terminated on exit!
{
if (const std::shared_ptr<GdrivePersistentSessions> gps = globalGdriveSessions.get())
gps->saveActiveSessions(); //throw FileError
}
- catch (FileError&) { assert(false); }
+ catch (const FileError& e) { warningMsg = e.toString(); }
assert(globalGdriveSessions.get());
globalGdriveSessions.set(nullptr);
assert(globalHttpSessionManager.get());
globalHttpSessionManager.set(nullptr);
+
+ return warningMsg;
}
@@ -3788,14 +3935,14 @@ std::vector<std::string /*account email*/> fff::gdriveListAccounts() //throw Fil
}
-std::vector<Zstring /*sharedDriveName*/> fff::gdriveListSharedDrives(const std::string& accountEmail, int timeoutSec) //throw FileError
+std::vector<Zstring /*locationName*/> fff::gdriveListLocations(const std::string& accountEmail, int timeoutSec) //throw FileError
{
try
{
if (const std::shared_ptr<GdrivePersistentSessions> gps = globalGdriveSessions.get())
- return gps->listSharedDrives(accountEmail, timeoutSec); //throw SysError
+ return gps->listLocations(accountEmail, timeoutSec); //throw SysError
- throw SysError(formatSystemError("gdriveListSharedDrives", L"", L"Function call not allowed during init/shutdown."));
+ throw SysError(formatSystemError("gdriveListLocations", L"", L"Function call not allowed during init/shutdown."));
}
catch (const SysError& e) { throw FileError(replaceCpy(_("Unable to access %x."), L"%x", fmtPath(getGdriveDisplayPath({{accountEmail, Zstr("")}, AfsPath()}))), e.toString()); }
}
@@ -3827,7 +3974,7 @@ Zstring fff::getGoogleDriveFolderUrl(const AbstractPath& folderPath) //throw Fil
{
if (const auto gdriveDevice = dynamic_cast<const GdriveFileSystem*>(&folderPath.afsDevice.ref()))
return gdriveDevice->getFolderUrl(folderPath.afsPath); //throw FileError
-
+ assert(false);
return {};
}
@@ -3843,7 +3990,7 @@ bool fff::acceptsItemPathPhraseGdrive(const Zstring& itemPathPhrase) //noexcept
/* syntax: gdrive:\<email>[:<shared drive>]\<relative-path>[|option_name=value]
e.g.: gdrive:\john@gmail.com\folder\file.txt
- gdrive:\john@gmail.com:SharedDrive\folder\file.txt|option_name=value */
+ gdrive:\john@gmail.com:location\folder\file.txt|option_name=value */
AbstractPath fff::createItemPathGdrive(const Zstring& itemPathPhrase) //noexcept
{
Zstring pathPhrase = expandMacros(itemPathPhrase); //expand before trimming!
@@ -3861,10 +4008,10 @@ AbstractPath fff::createItemPathGdrive(const Zstring& itemPathPhrase) //noexcept
const AfsPath afsPath = sanitizeDeviceRelativePath({it, fullPath.end()});
GdriveLogin login;
- login.email = utfTo<std::string>(beforeFirst(emailAndDrive, Zstr(':'), IfNotFoundReturn::all));
- login.sharedDriveName = afterFirst (emailAndDrive, Zstr(':'), IfNotFoundReturn::none);
+ login.email = utfTo<std::string>(beforeFirst(emailAndDrive, Zstr(':'), IfNotFoundReturn::all));
+ login.locationName = afterFirst (emailAndDrive, Zstr(':'), IfNotFoundReturn::none);
- for (const Zstring& optPhrase : split(options, Zstr("|"), SplitOnEmpty::skip))
+ for (const Zstring& optPhrase : split(options, Zstr('|'), SplitOnEmpty::skip))
if (startsWith(optPhrase, Zstr("timeout=")))
login.timeoutSec = stringTo<int>(afterFirst(optPhrase, Zstr("="), IfNotFoundReturn::none));
else
diff --git a/FreeFileSync/Source/afs/gdrive.h b/FreeFileSync/Source/afs/gdrive.h
index de32c191..648c6d9e 100644
--- a/FreeFileSync/Source/afs/gdrive.h
+++ b/FreeFileSync/Source/afs/gdrive.h
@@ -16,20 +16,21 @@ AbstractPath createItemPathGdrive (const Zstring& itemPathPhrase); //noexc
void gdriveInit(const Zstring& configDirPath, //directory to store Google-Drive-specific files
const Zstring& caCertFilePath); //cacert.pem
-void gdriveTeardown();
+[[nodiscard]] std::wstring /*warningMsg*/ gdriveTeardown();
//-------------------------------------------------------
+//caveat: gdriveAddUser() blocks indefinitely if user doesn't log in with Google! timeoutSec is only regarding HTTP requests
std::string /*account email*/ gdriveAddUser(const std::function<void()>& updateGui /*throw X*/, int timeoutSec); //throw FileError, X
void gdriveRemoveUser(const std::string& accountEmail, int timeoutSec); //throw FileError
std::vector<std::string /*account email*/> gdriveListAccounts(); //throw FileError
-std::vector<Zstring /*sharedDriveName*/> gdriveListSharedDrives(const std::string& accountEmail, int timeoutSec); //throw FileError
+std::vector<Zstring /*locationName*/> gdriveListLocations(const std::string& accountEmail, int timeoutSec); //throw FileError
struct GdriveLogin
{
std::string email;
- Zstring sharedDriveName; //empty for "My Drive"
+ Zstring locationName; //empty for "My Drive"; can be a shared drive or starred folder name
int timeoutSec = 15; //Gdrive can "hang" for 20 seconds when "scanning for viruses": https://freefilesync.org/forum/viewtopic.php?t=9116
};
diff --git a/FreeFileSync/Source/afs/init_curl_libssh2.h b/FreeFileSync/Source/afs/init_curl_libssh2.h
index 57204e78..93efb9f0 100644
--- a/FreeFileSync/Source/afs/init_curl_libssh2.h
+++ b/FreeFileSync/Source/afs/init_curl_libssh2.h
@@ -32,7 +32,7 @@ class UniCounterCookie;
std::shared_ptr<UniCounterCookie> getLibsshCurlUnifiedInitCookie(Global<UniSessionCounter>& globalSftpSessionCount); //throw SysError
-//3. Create static "UniInitializer globalStartupInitSftp(*globalSftpSessionCount.get());" instance *before* constructing objects like "SftpSessionManager"
+//3. Create static "UniInitializer globalInitSftp(*globalSftpSessionCount.get());" instance *before* constructing objects like "SftpSessionManager"
// => ~SftpSessionManager will run first and all remaining sessions are on non-main threads => can be waited on in ~UniInitializer
class UniInitializer
{
diff --git a/FreeFileSync/Source/afs/native.cpp b/FreeFileSync/Source/afs/native.cpp
index 2551234a..de3cea86 100644
--- a/FreeFileSync/Source/afs/native.cpp
+++ b/FreeFileSync/Source/afs/native.cpp
@@ -612,7 +612,7 @@ private:
{
//perf test: detecting different volumes by path is ~30 times faster than having ::MoveFileEx() fail with ERROR_NOT_SAME_DEVICE (6µs vs 190µs)
//=> maybe we can even save some actual I/O in some cases?
- if (std::is_neq(compareDeviceSameAfsType(pathTo.afsDevice.ref())))
+ if (compareDeviceSameAfsType(pathTo.afsDevice.ref()) != std::weak_ordering::equivalent)
throw ErrorMoveUnsupported(replaceCpy(replaceCpy(_("Cannot move file %x to %y."),
L"%x", L'\n' + fmtPath(getDisplayPath(pathFrom))),
L"%y", L'\n' + fmtPath(AFS::getDisplayPath(pathTo))),
diff --git a/FreeFileSync/Source/afs/sftp.cpp b/FreeFileSync/Source/afs/sftp.cpp
index 3bcbe40e..1d6546f8 100644
--- a/FreeFileSync/Source/afs/sftp.cpp
+++ b/FreeFileSync/Source/afs/sftp.cpp
@@ -117,7 +117,7 @@ std::weak_ordering operator<=>(const SshSessionId& lhs, const SshSessionId& rhs)
{
//exactly the type of case insensitive comparison we need for server names!
if (const std::weak_ordering cmp = compareAsciiNoCase(lhs.server, rhs.server); //https://docs.microsoft.com/en-us/windows/win32/api/ws2tcpip/nf-ws2tcpip-getaddrinfow#IDNs
- std::is_neq(cmp))
+ cmp != std::weak_ordering::equivalent)
return cmp;
const int portLhs = getEffectivePort(lhs.port);
@@ -125,7 +125,7 @@ std::weak_ordering operator<=>(const SshSessionId& lhs, const SshSessionId& rhs)
if (const std::strong_ordering cmp = std::tie(portLhs, lhs.username, lhs.authType, lhs.allowZlib) <=> //username: case sensitive!
std::tie(portRhs, rhs.username, rhs.authType, rhs.allowZlib);
- std::is_neq(cmp))
+ cmp != std::strong_ordering::equal)
return cmp;
switch (lhs.authType)
@@ -949,7 +949,7 @@ private:
};
//--------------------------------------------------------------------------------------
-UniInitializer globalStartupInitSftp(*globalSftpSessionCount.get());
+UniInitializer globalInitSftp(*globalSftpSessionCount.get());
constinit Global<SftpSessionManager> globalSftpSessionManager; //caveat: life time must be subset of static UniInitializer!
//--------------------------------------------------------------------------------------
@@ -1527,16 +1527,16 @@ private:
//exactly the type of case insensitive comparison we need for server names!
if (const std::weak_ordering cmp = compareAsciiNoCase(lhs.server, rhs.server); //https://docs.microsoft.com/en-us/windows/win32/api/ws2tcpip/nf-ws2tcpip-getaddrinfow#IDNs
- std::is_neq(cmp))
+ cmp != std::weak_ordering::equivalent)
return cmp;
//port DOES create a *different* data source! https://freefilesync.org/forum/viewtopic.php?t=9047
- const int portLhs = getEffectivePort(lhs.port);
- const int portRhs = getEffectivePort(rhs.port);
+ const int portLhs = getEffectivePort(lhs.port);
+ const int portRhs = getEffectivePort(rhs.port);
//consider username: different users may have different views and folder access rights!
return std::tie(portLhs, lhs.username) <=> //username: case sensitive!
- std::tie(portRhs, rhs.username);
+ std::tie(portRhs, rhs.username);
}
//----------------------------------------------------------------------------------------------------------------
@@ -1750,7 +1750,7 @@ private:
L"%y", L'\n' + fmtPath(AFS::getDisplayPath(pathTo)));
};
- if (std::is_neq(compareDeviceSameAfsType(pathTo.afsDevice.ref())))
+ if (compareDeviceSameAfsType(pathTo.afsDevice.ref()) != std::weak_ordering::equivalent)
throw ErrorMoveUnsupported(generateErrorMsg(), _("Operation not supported between different devices."));
try
@@ -2013,7 +2013,7 @@ AbstractPath fff::createItemPathSftp(const Zstring& itemPathPhrase) //noexcept
assert(login.allowZlib == false);
- for (const Zstring& optPhrase : split(options, Zstr("|"), SplitOnEmpty::skip))
+ for (const Zstring& optPhrase : split(options, Zstr('|'), SplitOnEmpty::skip))
if (startsWith(optPhrase, Zstr("timeout=")))
login.timeoutSec = stringTo<int>(afterFirst(optPhrase, Zstr("="), IfNotFoundReturn::none));
else if (startsWith(optPhrase, Zstr("chan=")))
diff --git a/FreeFileSync/Source/afs/sftp.h b/FreeFileSync/Source/afs/sftp.h
index abb4b3bc..0a03ec47 100644
--- a/FreeFileSync/Source/afs/sftp.h
+++ b/FreeFileSync/Source/afs/sftp.h
@@ -39,8 +39,8 @@ struct SshSessionId
bool allowZlib = false;
};
const int DEFAULT_PORT_SFTP = 22;
- //SFTP default port: 22, see %WINDIR%\system32\drivers\etc\services
- //=> we could use the "ssh" alias, but let's be explicit
+//SFTP default port: 22, see %WINDIR%\system32\drivers\etc\services
+//=> we could use the "ssh" alias, but let's be explicit
struct SftpLogin : SshSessionId
{
diff --git a/FreeFileSync/Source/application.cpp b/FreeFileSync/Source/application.cpp
index d1739b55..c9acda62 100644
--- a/FreeFileSync/Source/application.cpp
+++ b/FreeFileSync/Source/application.cpp
@@ -57,6 +57,7 @@ bool Application::OnInit()
{
//do not call wxApp::OnInit() to avoid using wxWidgets command line parser
+ warn_static("log or show popup? which one is it!?")
auto logInitError = [&](const std::wstring& msg)
{
//error handling strategy unknown and no sync log output available at this point!
@@ -108,7 +109,7 @@ bool Application::OnInit()
}
catch (const SysError& e)
{
- logInitError(e.toString() + L"\n" L"Loading GTK3\'s old CSS format instead...");
+ std::cerr << "FreeFileSync" << utfTo<std::string>(SPACED_DASH + e.toString()) + "\n" "Loading GTK3\'s old CSS format instead..." "\n";
try
{
loadCSS("Gtk3Styles.old.css"); //throw SysError
@@ -133,7 +134,7 @@ bool Application::OnInit()
//Windows User Experience Interaction Guidelines: tool tips should have 5s timeout, info tips no timeout => compromise:
- wxToolTip::Enable(true); //yawn, a wxWidgets screw-up: wxToolTip::SetAutoPop is no-op if global tooltip window is not yet constructed: wxToolTip::Enable creates it
+ wxToolTip::Enable(true); //wxWidgets screw-up: wxToolTip::SetAutoPop is no-op if global tooltip window is not yet constructed: wxToolTip::Enable creates it
wxToolTip::SetAutoPop(10'000); //https://docs.microsoft.com/en-us/windows/win32/uxguide/ctrl-tooltips-and-infotips
SetAppName(L"FreeFileSync"); //if not set, the default is the executable's name!
@@ -142,11 +143,21 @@ bool Application::OnInit()
try { localizationInit(getResourceDirPf() + Zstr("Languages.zip")); } //throw FileError
catch (const FileError& e) { logInitError(e.toString()); }
- initAfs({getResourceDirPf(), getConfigDirPathPf()}); //bonus: using FTP Gdrive implicitly inits OpenSSL (used in runSanityChecks() on Linux) alredy during globals init
+ initAfs({getResourceDirPf(), getConfigDirPathPf()}); //bonus: using FTP Gdrive implicitly inits OpenSSL (used in runSanityChecks() on Linux) already during globals init
- Bind(wxEVT_QUERY_END_SESSION, [this](wxCloseEvent& event) { onQueryEndSession(event); }); //can veto
- Bind(wxEVT_END_SESSION, [this](wxCloseEvent& event) { onQueryEndSession(event); }); //can *not* veto
+
+ auto onSystemShutdown = []
+ {
+ onSystemShutdownRunTasks();
+
+ //- it's futile to try and clean up while the process is in full swing (CRASH!) => just terminate!
+ //- system sends close events to all open dialogs: If one of these calls wxCloseEvent::Veto(),
+ // e.g. user clicking cancel on save prompt, this would cancel the shutdown
+ terminateProcess(FFS_EXIT_ABORTED);
+ };
+ Bind(wxEVT_QUERY_END_SESSION, [onSystemShutdown](wxCloseEvent& event) { onSystemShutdown(); }); //can veto
+ Bind(wxEVT_END_SESSION, [onSystemShutdown](wxCloseEvent& event) { onSystemShutdown(); }); //can *not* veto
//Note: app start is deferred: batch mode requires the wxApp eventhandler to be established for UI update events. This is not the case at the time of OnInit()!
Bind(EVENT_ENTER_EVENT_LOOP, &Application::onEnterEventLoop, this);
@@ -157,7 +168,7 @@ bool Application::OnInit()
void Application::onEnterEventLoop(wxEvent& event)
{
- [[maybe_unused]] bool ubOk = Unbind(EVENT_ENTER_EVENT_LOOP, &Application::onEnterEventLoop, this);
+ [[maybe_unused]] const bool ubOk = Unbind(EVENT_ENTER_EVENT_LOOP, &Application::onEnterEventLoop, this);
assert(ubOk);
launch(getCommandlineArgs(*this)); //determine FFS mode of operation
@@ -168,7 +179,13 @@ int Application::OnExit()
{
localizationCleanup();
imageResourcesCleanup();
- teardownAfs();
+
+ if (const std::wstring& warningMsg = teardownAfs();
+ !warningMsg.empty())
+ {
+ }
+ warn_static("log?")
+
return wxApp::OnExit();
}
@@ -176,8 +193,6 @@ int Application::OnExit()
wxLayoutDirection Application::GetLayoutDirection() const { return getLayoutDirection(); }
-
-
int Application::OnRun()
{
[[maybe_unused]] const int rc = wxApp::OnRun();
@@ -203,14 +218,6 @@ void Application::OnUnhandledException() //handles both wxApp::OnInit() + wxApp:
}
-void Application::onQueryEndSession(wxEvent& event)
-{
- if (auto mainWin = dynamic_cast<MainDialog*>(GetTopWindow()))
- mainWin->onQueryEndSession();
- //it's futile to try and clean up while the process is in full swing (CRASH!) => just terminate!
- //also: avoid wxCloseEvent::Veto() cancels shutdown when dialogs receive a close event from the system
- terminateProcess(FFS_EXIT_ABORTED);
-}
void runGuiMode (const Zstring& globalConfigFile);
@@ -227,6 +234,7 @@ void Application::launch(const std::vector<Zstring>& commandArgs)
auto notifyFatalError = [&](const std::wstring& msg, const std::wstring& title)
{
+ warn_static("that doesnt seem right generally:")
logFatalError(msg);
//error handling strategy unknown and no sync log output available at this point!
@@ -695,7 +703,7 @@ void runBatchMode(const Zstring& globalConfigFilePath, const XmlBatchConfig& bat
try
{
shutdownSystem(); //throw FileError
- terminateProcess(exitCode); //no point in continuing and saving cfg again in onQueryEndSession() while the OS will kill us anytime!
+ terminateProcess(exitCode); //no point in continuing and saving cfg again in onSystemShutdown() while the OS will kill us anytime!
}
catch (const FileError& e) { notifyError(e.toString(), FFS_EXIT_ERROR); }
break;
diff --git a/FreeFileSync/Source/application.h b/FreeFileSync/Source/application.h
index 78025120..c5b06006 100644
--- a/FreeFileSync/Source/application.h
+++ b/FreeFileSync/Source/application.h
@@ -25,7 +25,6 @@ private:
void OnUnhandledException () override;
wxLayoutDirection GetLayoutDirection() const override;
void onEnterEventLoop(wxEvent& event);
- void onQueryEndSession(wxEvent& event);
void launch(const std::vector<Zstring>& commandArgs);
FfsExitCode exitCode_ = FFS_EXIT_SUCCESS;
diff --git a/FreeFileSync/Source/base/algorithm.cpp b/FreeFileSync/Source/base/algorithm.cpp
index 95f346d7..0486c66b 100644
--- a/FreeFileSync/Source/base/algorithm.cpp
+++ b/FreeFileSync/Source/base/algorithm.cpp
@@ -1288,7 +1288,7 @@ void copyToAlternateFolderFrom(const std::vector<const FileSystemObject*>& rowsT
const AbstractPath& targetFolderPath,
bool keepRelPaths,
bool overwriteIfExists,
- ProcessCallback& callback)
+ ProcessCallback& callback /*throw X*/) //throw X
{
auto notifyItemCopy = [&](const std::wstring& statusText, const std::wstring& displayPath)
{
@@ -1300,7 +1300,7 @@ void copyToAlternateFolderFrom(const std::vector<const FileSystemObject*>& rowsT
const std::wstring txtCreatingFolder(_("Creating folder %x" ));
const std::wstring txtCreatingLink (_("Creating symbolic link %x"));
- auto copyItem = [&](const AbstractPath& targetPath, ItemStatReporter<ProcessCallback>& statReporter, //throw FileError
+ auto copyItem = [&](const AbstractPath& targetPath, //throw FileError
const std::function<void(const std::function<void()>& deleteTargetItem)>& copyItemPlain) //throw FileError
{
//start deleting existing target as required by copyFileTransactional():
@@ -1366,25 +1366,26 @@ void copyToAlternateFolderFrom(const std::vector<const FileSystemObject*>& rowsT
[&](const FilePair& file)
{
- ItemStatReporter statReporter(1, file.getFileSize<side>(), callback);
- notifyItemCopy(txtCreatingFile, AFS::getDisplayPath(targetPath));
+ std::wstring statusMsg = replaceCpy(txtCreatingFile, L"%x", fmtPath(AFS::getDisplayPath(targetPath)));
+ callback.logInfo(statusMsg); //throw X
+ PercentStatReporter statReporter(std::move(statusMsg), file.getFileSize<side>(), callback); //throw X
const FileAttributes attr = file.getAttributes<side>();
const AFS::StreamAttributes sourceAttr{attr.modTime, attr.fileSize, attr.filePrint};
- copyItem(targetPath, statReporter, [&](const std::function<void()>& deleteTargetItem) //throw FileError
+ copyItem(targetPath, [&](const std::function<void()>& deleteTargetItem) //throw FileError
{
- auto notifyUnbufferedIO = [&](int64_t bytesDelta)
- {
- statReporter.reportDelta(0, bytesDelta);
- callback.requestUiUpdate(); //throw X
- };
//already existing + !overwriteIfExists: undefined behavior! (e.g. fail/overwrite/auto-rename)
/*const AFS::FileCopyResult result =*/ AFS::copyFileTransactional(sourcePath, sourceAttr, targetPath, //throw FileError, ErrorFileLocked, X
- false /*copyFilePermissions*/, true /*transactionalCopy*/, deleteTargetItem, notifyUnbufferedIO);
+ false /*copyFilePermissions*/, true /*transactionalCopy*/, deleteTargetItem,
+ [&](int64_t bytesDelta)
+ {
+ statReporter.updateStatus(0, bytesDelta); //throw X
+ callback.requestUiUpdate(); //throw X => not reliably covered by PercentStatReporter::updateStatus()! e.g. during first few seconds: STATUS_PERCENT_DELAY!
+ });
//result.errorModTime? => probably irrelevant (behave like Windows Explorer)
});
- statReporter.reportDelta(1, 0);
+ statReporter.updateStatus(1, 0); //throw X
},
[&](const SymlinkPair& symlink)
@@ -1392,7 +1393,7 @@ void copyToAlternateFolderFrom(const std::vector<const FileSystemObject*>& rowsT
ItemStatReporter statReporter(1, 0, callback);
notifyItemCopy(txtCreatingLink, AFS::getDisplayPath(targetPath));
- copyItem(targetPath, statReporter, [&](const std::function<void()>& deleteTargetItem) //throw FileError
+ copyItem(targetPath, [&](const std::function<void()>& deleteTargetItem) //throw FileError
{
deleteTargetItem(); //throw FileError
AFS::copySymlink(sourcePath, targetPath, false /*copyFilePermissions*/); //throw FileError
@@ -1412,7 +1413,7 @@ void fff::copyToAlternateFolder(std::span<const FileSystemObject* const> rowsToC
bool keepRelPaths,
bool overwriteIfExists,
WarningDialogs& warnings,
- ProcessCallback& callback)
+ ProcessCallback& callback /*throw X*/) //throw X
{
std::vector<const FileSystemObject*> itemSelectionLeft (rowsToCopyOnLeft .begin(), rowsToCopyOnLeft .end());
std::vector<const FileSystemObject*> itemSelectionRight(rowsToCopyOnRight.begin(), rowsToCopyOnRight.end());
@@ -1581,7 +1582,7 @@ void fff::deleteFromGridAndHD(const std::vector<FileSystemObject*>& rowsToDelete
const std::vector<std::pair<BaseFolderPair*, SyncDirectionConfig>>& directCfgs, //attention: rows will be physically deleted!
bool useRecycleBin,
bool& warnRecyclerMissing,
- ProcessCallback& callback)
+ ProcessCallback& callback /*throw X*/) //throw X
{
if (directCfgs.empty())
return;
@@ -1682,6 +1683,7 @@ TempFileBuffer::~TempFileBuffer()
removeDirectoryPlainRecursion(tempFolderPath_); //throw FileError
}
catch (FileError&) { assert(false); }
+ warn_static("log, maybe?")
}
@@ -1719,7 +1721,7 @@ Zstring TempFileBuffer::getTempPath(const FileDescriptor& descr) const
}
-void TempFileBuffer::createTempFiles(const std::set<FileDescriptor>& workLoad, ProcessCallback& callback)
+void TempFileBuffer::createTempFiles(const std::set<FileDescriptor>& workLoad, ProcessCallback& callback /*throw X*/) //throw X
{
const int itemTotal = static_cast<int>(workLoad.size());
int64_t bytesTotal = 0;
@@ -1773,7 +1775,6 @@ void TempFileBuffer::createTempFiles(const std::set<FileDescriptor>& workLoad, P
{
statReporter.updateStatus(0, bytesDelta); //throw X
callback.requestUiUpdate(); //throw X => not reliably covered by PercentStatReporter::updateStatus()! e.g. during first few seconds: STATUS_PERCENT_DELAY!
-
});
//result.errorModTime? => irrelevant for temp files!
statReporter.updateStatus(1, 0); //throw X
diff --git a/FreeFileSync/Source/base/algorithm.h b/FreeFileSync/Source/base/algorithm.h
index 4ef7f9bb..b1ddcce3 100644
--- a/FreeFileSync/Source/base/algorithm.h
+++ b/FreeFileSync/Source/base/algorithm.h
@@ -64,7 +64,7 @@ void copyToAlternateFolder(std::span<const FileSystemObject* const> rowsToCopyOn
bool keepRelPaths,
bool overwriteIfExists,
WarningDialogs& warnings,
- ProcessCallback& callback);
+ ProcessCallback& callback /*throw X*/); //throw X
//manual deletion of files on main grid
void deleteFromGridAndHD(const std::vector<FileSystemObject*>& rowsToDeleteOnLeft, //refresh GUI grid after deletion to remove invalid rows
@@ -73,7 +73,7 @@ void deleteFromGridAndHD(const std::vector<FileSystemObject*>& rowsToDeleteOnLef
bool useRecycleBin,
//global warnings:
bool& warnRecyclerMissing,
- ProcessCallback& callback);
+ ProcessCallback& callback /*throw X*/); //throw X
struct FileDescriptor
{
@@ -95,8 +95,7 @@ public:
Zstring getTempPath(const FileDescriptor& descr) const; //returns empty if not in buffer (item not existing, error during copy)
//contract: only add files not yet in the buffer!
- void createTempFiles(const std::set<FileDescriptor>& workLoad, ProcessCallback& callback);
-
+ void createTempFiles(const std::set<FileDescriptor>& workLoad, ProcessCallback& callback /*throw X*/); //throw X
private:
TempFileBuffer (const TempFileBuffer&) = delete;
diff --git a/FreeFileSync/Source/base/dir_exist_async.h b/FreeFileSync/Source/base/dir_exist_async.h
index 593dc3b9..1d2a82ef 100644
--- a/FreeFileSync/Source/base/dir_exist_async.h
+++ b/FreeFileSync/Source/base/dir_exist_async.h
@@ -9,7 +9,6 @@
#include <zen/thread.h>
#include <zen/file_error.h>
-//#include <zen/basic_math.h>
#include "process_callback.h"
#include "../afs/abstract.h"
@@ -74,10 +73,10 @@ FolderStatus getFolderStatusNonBlocking(const std::set<AbstractPath>& folderPath
return static_cast<bool>(AFS::itemStillExists(folderPath)); //throw FileError
//consider ItemType::file a failure instead? Meanwhile: return "false" IFF nothing (of any type) exists
});
- auto fut = pt.get_future();
+ auto ftIsExisting = pt.get_future();
threadGroup.run(std::move(pt));
- futureDetails.emplace_back(folderPath, std::move(fut));
+ futureDetails.emplace_back(folderPath, std::move(ftIsExisting));
}
}
@@ -86,7 +85,7 @@ FolderStatus getFolderStatusNonBlocking(const std::set<AbstractPath>& folderPath
FolderStatus output;
- for (auto& [folderPath, future] : futureDetails)
+ for (auto& [folderPath, ftIsExisting] : futureDetails)
{
const std::wstring& displayPathFmt = fmtPath(AFS::getDisplayPath(folderPath));
@@ -99,17 +98,17 @@ FolderStatus getFolderStatusNonBlocking(const std::set<AbstractPath>& folderPath
const auto timeoutTime = startTime + std::chrono::seconds(deviceTimeOutSec);
while (std::chrono::steady_clock::now() < timeoutTime &&
- future.wait_for(UI_UPDATE_INTERVAL / 2) == std::future_status::timeout)
+ ftIsExisting.wait_for(UI_UPDATE_INTERVAL / 2) == std::future_status::timeout)
procCallback.requestUiUpdate(); //throw X
- if (!isReady(future))
+ if (!isReady(ftIsExisting))
output.failedChecks.emplace(folderPath, FileError(replaceCpy(_("Timeout while searching for folder %x."), L"%x", displayPathFmt) +
L" [" + _P("1 sec", "%x sec", deviceTimeOutSec) + L']'));
else
try
{
//call future::get() only *once*! otherwise: undefined behavior!
- if (future.get()) //throw FileError
+ if (ftIsExisting.get()) //throw FileError
output.existing.emplace(folderPath);
else
output.notExisting.insert(folderPath);
diff --git a/FreeFileSync/Source/base/path_filter.cpp b/FreeFileSync/Source/base/path_filter.cpp
index 65add3da..afb61878 100644
--- a/FreeFileSync/Source/base/path_filter.cpp
+++ b/FreeFileSync/Source/base/path_filter.cpp
@@ -21,7 +21,7 @@ std::strong_ordering fff::operator<=>(const FilterRef& lhs, const FilterRef& rhs
//caveat: typeid returns static type for pointers, dynamic type for references!!!
if (const std::strong_ordering cmp = std::type_index(typeid(lhs.ref())) <=>
std::type_index(typeid(rhs.ref()));
- std::is_neq(cmp))
+ cmp != std::strong_ordering::equal)
return cmp;
return lhs.ref().compareSameType(rhs.ref());
@@ -30,25 +30,28 @@ std::strong_ordering fff::operator<=>(const FilterRef& lhs, const FilterRef& rhs
std::vector<Zstring> fff::splitByDelimiter(const Zstring& filterPhrase)
{
- //delimiters may be FILTER_ITEM_SEPARATOR or '\n'
std::vector<Zstring> output;
- for (const Zstring& str : split(filterPhrase, FILTER_ITEM_SEPARATOR, SplitOnEmpty::skip)) //split by less common delimiter first (create few, large strings)
- for (Zstring entry : split(str, Zstr('\n'), SplitOnEmpty::skip))
- {
- trim(entry);
- if (!entry.empty())
- output.push_back(std::move(entry));
- }
+ split2(filterPhrase, [](Zchar c) { return c == FILTER_ITEM_SEPARATOR || c == Zstr('\n'); }, //delimiters
+ [&output](const Zchar* blockFirst, const Zchar* blockLast)
+ {
+ std::tie(blockFirst, blockLast) = trimCpy(blockFirst, blockLast, true /*fromLeft*/, true /*fromRight*/, [](Zchar c) { return isWhiteSpace(c); });
+ if (blockFirst != blockLast)
+ output.emplace_back(blockFirst, blockLast);
+ });
return output;
}
-namespace
-{
-void parseFilterPhrase(const Zstring& filterPhrase, std::vector<Zstring>& masksFileFolder, std::vector<Zstring>& masksFolder)
+void NameFilter::parseFilterPhrase(const Zstring& filterPhrase, FilterSet& filter)
{
+ //normalize filter: 1. ignore Unicode normalization form 2. ignore case
+ Zstring filterPhraseFmt = getUpperCase(filterPhrase);
+ //3. fix path separator
+ if constexpr (FILE_NAME_SEPARATOR != Zstr('/' )) replace(filterPhraseFmt, Zstr('/'), FILE_NAME_SEPARATOR);
+ if constexpr (FILE_NAME_SEPARATOR != Zstr('\\')) replace(filterPhraseFmt, Zstr('\\'), FILE_NAME_SEPARATOR);
+
const Zstring sepAsterisk = Zstr("/*");
const Zstring asteriskSep = Zstr("*/");
static_assert(FILE_NAME_SEPARATOR == '/');
@@ -60,18 +63,14 @@ void parseFilterPhrase(const Zstring& filterPhrase, std::vector<Zstring>& masksF
{
const Zstring dirPhrase = beforeLast(phrase, FILE_NAME_SEPARATOR, IfNotFoundReturn::none);
if (!dirPhrase.empty())
- masksFolder.push_back(dirPhrase);
+ filter.folderMasks.insert(dirPhrase);
}
else if (!phrase.empty())
- masksFileFolder.push_back(phrase);
+ filter.fileFolderMasks.insert(phrase);
};
- for (const Zstring& itemPhrase : splitByDelimiter(filterPhrase))
+ for (const Zstring& itemPhrase : splitByDelimiter(filterPhraseFmt))
{
- //normalize filter input: 1. ignore Unicode normalization form 2. ignore case 3. ignore path separator
- Zstring phraseFmt = getUpperCase(itemPhrase);
- if constexpr (FILE_NAME_SEPARATOR != Zstr('/' )) replace(phraseFmt, Zstr('/'), FILE_NAME_SEPARATOR);
- if constexpr (FILE_NAME_SEPARATOR != Zstr('\\')) replace(phraseFmt, Zstr('\\'), FILE_NAME_SEPARATOR);
/* phrase | action
+---------+--------
| \blah | remove \
@@ -92,55 +91,36 @@ void parseFilterPhrase(const Zstring& filterPhrase, std::vector<Zstring>& masksF
| blah*\* | remove \*; folder only
+---------+-------- */
- if (startsWith(phraseFmt, FILE_NAME_SEPARATOR)) // \abc
- processTail(afterFirst(phraseFmt, FILE_NAME_SEPARATOR, IfNotFoundReturn::none));
+ if (startsWith(itemPhrase, FILE_NAME_SEPARATOR)) // \abc
+ processTail(afterFirst(itemPhrase, FILE_NAME_SEPARATOR, IfNotFoundReturn::none));
else
{
- processTail(phraseFmt);
- if (startsWith(phraseFmt, asteriskSep)) // *\abc
- processTail(afterFirst(phraseFmt, asteriskSep, IfNotFoundReturn::none));
+ processTail(itemPhrase);
+ if (startsWith(itemPhrase, asteriskSep)) // *\abc
+ processTail(afterFirst(itemPhrase, asteriskSep, IfNotFoundReturn::none));
}
}
}
-template <class Char> inline
-const Char* cStringFind(const Char* str, Char ch) //= strchr(), wcschr()
+void NameFilter::MaskMatcher::insert(const Zstring& mask)
{
- for (;;)
+ assert(mask == getUpperCase(mask));
+ if (contains(mask, Zstr('?')) ||
+ contains(mask, Zstr('*')))
+ realMasks_.insert(mask);
+ else
{
- const Char s = *str;
- if (s == ch) //ch is allowed to be 0 by contract! must return end of string in this case
- return str;
-
- if (s == 0)
- return nullptr;
- ++str;
+ relPaths_ .emplace(mask);
+ relPathsCmp_.emplace(mask); //little memory wasted thanks to COW string!
}
}
-/* struct FullMatch
- {
- static bool matchesMaskEnd (const Zchar* path) { return *path == 0; }
- static bool matchesMaskStar(const Zchar* path) { return true; }
- }; */
-
-struct ParentFolderMatch //strict match of parent folder path!
+namespace
{
- static bool matchesMaskEnd (const Zchar* path) { return *path == FILE_NAME_SEPARATOR; }
- static bool matchesMaskStar(const Zchar* path) { return cStringFind(path, FILE_NAME_SEPARATOR) != nullptr; }
-};
-
-struct AnyMatch
-{
- static bool matchesMaskEnd (const Zchar* path) { return *path == 0 || *path == FILE_NAME_SEPARATOR; }
- static bool matchesMaskStar(const Zchar* path) { return true; }
-};
-
-
-template <class PathEndMatcher>
-bool matchesMask(const Zchar* path, const Zchar* mask)
+//"true" if path or any parent path matches the mask
+bool matchesMask(const Zchar* path, const Zchar* const pathEnd, const Zchar* mask /*0-terminated*/)
{
for (;; ++mask, ++path)
{
@@ -148,10 +128,10 @@ bool matchesMask(const Zchar* path, const Zchar* mask)
switch (m)
{
case 0:
- return PathEndMatcher::matchesMaskEnd(path);
+ return path == pathEnd || *path == FILE_NAME_SEPARATOR; //"full" or parent path match
- case Zstr('?'):
- if (*path == 0)
+ case Zstr('?'): //should not match FILE_NAME_SEPARATOR
+ if (path == pathEnd || *path == FILE_NAME_SEPARATOR)
return false;
break;
@@ -163,102 +143,111 @@ bool matchesMask(const Zchar* path, const Zchar* mask)
while (m == Zstr('*'));
if (m == 0) //mask ends with '*':
- return PathEndMatcher::matchesMaskStar(path);
-
- //*? - pattern
- if (m == Zstr('?'))
- {
- ++mask;
- while (*path++ != 0)
- if (matchesMask<PathEndMatcher>(path, mask))
- return true;
- return false;
- }
+ return true;
- //*[letter] - pattern
++mask;
- for (;;)
+ if (m == Zstr('?')) //*? pattern
{
- path = cStringFind(path, m);
- if (!path)
- return false;
-
- ++path;
- if (matchesMask<PathEndMatcher>(path, mask))
- return true;
+ while (path != pathEnd)
+ if (*path++ != FILE_NAME_SEPARATOR)
+ if (matchesMask(path, pathEnd, mask))
+ return true;
}
+ else //*[letter or /] pattern
+ while (path != pathEnd)
+ if (*path++ == m)
+ if (matchesMask(path, pathEnd, mask))
+ return true;
+ return false;
default:
- if (*path != m)
+ if (path == pathEnd || *path != m)
return false;
}
}
}
-//returns true if string matches at least the beginning of mask
-inline
-bool matchesMaskBegin(const Zchar* str, const Zchar* mask)
+//"true" if path matches (only!) the beginning of mask
+template <bool haveWildcards> bool matchesMaskBegin(const Zstring& relPath, const Zstring& mask);
+
+template <> inline
+bool matchesMaskBegin<true /*haveWildcards*/>(const Zstring& relPath, const Zstring& mask)
{
- for (;; ++mask, ++str)
+ auto itP = relPath.begin();
+ for (auto itM = mask.begin(); itM != mask.end(); ++itM, ++itP)
{
- const Zchar m = *mask;
+ const Zchar m = *itM;
switch (m)
{
- case 0:
- return *str == 0;
-
case Zstr('?'):
- if (*str == 0)
- return true;
+ if (itP == relPath.end() || *itP == FILE_NAME_SEPARATOR)
+ return false;
break;
case Zstr('*'):
return true;
default:
- if (*str != m)
- return *str == 0;
+ if (itP == relPath.end())
+ return m == FILE_NAME_SEPARATOR && mask.end() - itM > 1; //require strict sub match
+
+ if (*itP != m)
+ return false;
}
}
+ return false; //not a strict sub match
}
-
-template <class PathEndMatcher> inline
-bool matchesMask(const Zstring& name, const std::vector<Zstring>& masks)
+template <> inline //perf: going overboard? remaining fruits are hanging higher and higher...
+bool matchesMaskBegin<false /*haveWildcards*/>(const Zstring& relPath, const Zstring& mask)
{
- return std::any_of(masks.begin(), masks.end(), [&](const Zstring& mask) { return matchesMask<PathEndMatcher>(name.c_str(), mask.c_str()); });
+ return mask.size() > relPath.size() + 1 && //room for FILE_NAME_SEPARATOR *and* at least one more char
+ mask[relPath.size()] == FILE_NAME_SEPARATOR &&
+ startsWith(mask, relPath);
+}
}
-inline
-bool matchesMaskBegin(const Zstring& name, const std::vector<Zstring>& masks)
+bool NameFilter::MaskMatcher::matches(const Zchar* pathFirst, const Zchar* pathLast) const
{
- return std::any_of(masks.begin(), masks.end(), [&](const Zstring& mask) { return matchesMaskBegin(name.c_str(), mask.c_str()); });
+ if (std::any_of(realMasks_.begin(), realMasks_.end(), [&](const Zstring& mask) { return matchesMask(pathFirst, pathLast, mask.c_str()); }))
+ /**/return true;
+
+ //perf: for relPaths_ we can go from linear to *constant* time!!! => annihilates https://freefilesync.org/forum/viewtopic.php?t=7768#p26519
+ const Zchar* sepPos = pathFirst;
+ for (;;) //check all parent paths!
+ {
+ sepPos = std::find(sepPos, pathLast, FILE_NAME_SEPARATOR);
+
+ if (relPaths_.contains(makeStringView(pathFirst, sepPos))) //heterogenous lookup!
+ return true;
+
+ if (sepPos == pathLast)
+ return false;
+
+ ++sepPos;
+ }
}
+
+bool NameFilter::MaskMatcher::matchesBegin(const Zstring& relPath) const
+{
+ return std::any_of(realMasks_.begin(), realMasks_.end(), [&](const Zstring& mask) { return matchesMaskBegin<true /*haveWildcards*/>(relPath, mask); }) ||
+ /**/ std::any_of(relPaths_ .begin(), relPaths_ .end(), [&](const Zstring& mask) { return matchesMaskBegin<false /*haveWildcards*/>(relPath, mask); });
}
//#################################################################################################
NameFilter::NameFilter(const Zstring& includePhrase, const Zstring& excludePhrase)
{
- //setup include/exclude filters for files and directories
- parseFilterPhrase(includePhrase, includeMasksFileFolder, includeMasksFolder);
- parseFilterPhrase(excludePhrase, excludeMasksFileFolder, excludeMasksFolder);
-
- removeDuplicates(includeMasksFileFolder);
- removeDuplicates(includeMasksFolder );
- removeDuplicates(excludeMasksFileFolder);
- removeDuplicates(excludeMasksFolder );
+ parseFilterPhrase(includePhrase, includeFilter);
+ parseFilterPhrase(excludePhrase, excludeFilter);
}
void NameFilter::addExclusion(const Zstring& excludePhrase)
{
- parseFilterPhrase(excludePhrase, excludeMasksFileFolder, excludeMasksFolder);
-
- removeDuplicates(excludeMasksFileFolder);
- removeDuplicates(excludeMasksFolder );
+ parseFilterPhrase(excludePhrase, excludeFilter);
}
@@ -269,12 +258,14 @@ bool NameFilter::passFileFilter(const Zstring& relFilePath) const
//normalize input: 1. ignore Unicode normalization form 2. ignore case
const Zstring& pathFmt = getUpperCase(relFilePath);
- if (matchesMask<AnyMatch >(pathFmt, excludeMasksFileFolder) || //either full match on file or partial match on any parent folder
- matchesMask<ParentFolderMatch>(pathFmt, excludeMasksFolder)) //partial match on any parent folder only
+ const Zchar* sepPos = findLast(pathFmt.begin(), pathFmt.end(), FILE_NAME_SEPARATOR);
+
+ if (excludeFilter.fileFolderMasks.matches(pathFmt.begin(), pathFmt.end()) || //either match on file or any parent folder
+ (sepPos != pathFmt.end() && excludeFilter.folderMasks.matches(pathFmt.begin(), sepPos))) //match on any parent folder only
return false;
- return matchesMask<AnyMatch >(pathFmt, includeMasksFileFolder) ||
- matchesMask<ParentFolderMatch>(pathFmt, includeMasksFolder);
+ return includeFilter.fileFolderMasks.matches(pathFmt.begin(), pathFmt.end()) ||
+ (sepPos != pathFmt.end() && includeFilter.folderMasks.matches(pathFmt.begin(), sepPos));
}
@@ -286,56 +277,42 @@ bool NameFilter::passDirFilter(const Zstring& relDirPath, bool* childItemMightMa
//normalize input: 1. ignore Unicode normalization form 2. ignore case
const Zstring& pathFmt = getUpperCase(relDirPath);
- if (matchesMask<AnyMatch>(pathFmt, excludeMasksFileFolder) ||
- matchesMask<AnyMatch>(pathFmt, excludeMasksFolder))
+ if (excludeFilter.fileFolderMasks.matches(pathFmt.begin(), pathFmt.end()) ||
+ excludeFilter.folderMasks .matches(pathFmt.begin(), pathFmt.end()))
{
if (childItemMightMatch)
*childItemMightMatch = false; //perf: no need to traverse deeper; subfolders/subfiles would be excluded by filter anyway!
- /* Attention: the design choice that "childItemMightMatch" is optional implies that the filter must provide correct results no matter if this
- value is considered by the client!
- In particular, if *childItemMightMatch == false, then any filter evaluations for child items must also return "false"!
+ /* Attention: If *childItemMightMatch == false, then any direct filter evaluation for a child item must also return "false"!
+
This is not a problem for folder traversal which stops at the first *childItemMightMatch == false anyway, but other code continues recursing further,
- e.g. the database update code in db_file.cpp recurses unconditionally without filter check! It's possible to construct edge cases with incorrect
- behavior if "childItemMightMatch" were not optional:
- 1. two folders including a subfolder with some files are in sync with up-to-date database files
- 2. deny access to this subfolder on both sides and start sync ignoring errors
- 3. => database entries of this subfolder are incorrectly deleted! (if sub-folder is excluded, but child items are not!) */
+ e.g. the database update code in db_file.cpp recurses unconditionally without *childItemMightMatch check! */
return false;
}
- if (matchesMask<AnyMatch>(pathFmt, includeMasksFileFolder) ||
- matchesMask<AnyMatch>(pathFmt, includeMasksFolder))
+ if (includeFilter.fileFolderMasks.matches(pathFmt.begin(), pathFmt.end()) ||
+ includeFilter.folderMasks .matches(pathFmt.begin(), pathFmt.end()))
return true;
if (childItemMightMatch)
- {
- const Zstring& childPathBegin = pathFmt + FILE_NAME_SEPARATOR;
-
- *childItemMightMatch = matchesMaskBegin(childPathBegin, includeMasksFileFolder) || //might match a file or folder in subdirectory
- matchesMaskBegin(childPathBegin, includeMasksFolder ); //
- }
+ *childItemMightMatch = includeFilter.fileFolderMasks.matchesBegin(pathFmt) || //might match a file or folder in subdirectory
+ includeFilter.folderMasks .matchesBegin(pathFmt); //
return false;
}
bool NameFilter::isNull(const Zstring& includePhrase, const Zstring& excludePhrase)
{
- const Zstring include = trimCpy(includePhrase);
- const Zstring exclude = trimCpy(excludePhrase);
-
- return include == Zstr("*") && exclude.empty();
+ return trimCpy(includePhrase) == Zstr("*") &&
+ trimCpy(excludePhrase).empty();
//return NameFilter(includePhrase, excludePhrase).isNull(); -> very expensive for huge lists
}
bool NameFilter::isNull() const
{
- 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:
+ return compareSameType(NameFilter(Zstr("*"), Zstr(""))) == std::strong_ordering::equal;
+ //avoid static non-POD null-NameFilter instance
}
@@ -346,6 +323,6 @@ std::strong_ordering NameFilter::compareSameType(const PathFilter& other) const
const NameFilter& lhs = *this;
const NameFilter& rhs = static_cast<const NameFilter&>(other);
- return std::tie(lhs.includeMasksFileFolder, lhs.includeMasksFolder, lhs.excludeMasksFileFolder, lhs.excludeMasksFolder) <=>
- std::tie(rhs.includeMasksFileFolder, rhs.includeMasksFolder, rhs.excludeMasksFileFolder, rhs.excludeMasksFolder);
+ return std::tie(lhs.includeFilter, lhs.excludeFilter) <=>
+ std::tie(rhs.includeFilter, rhs.excludeFilter);
}
diff --git a/FreeFileSync/Source/base/path_filter.h b/FreeFileSync/Source/base/path_filter.h
index 2760f427..4640996c 100644
--- a/FreeFileSync/Source/base/path_filter.h
+++ b/FreeFileSync/Source/base/path_filter.h
@@ -9,6 +9,7 @@
#include <vector>
#include <memory>
+#include <unordered_set>
#include <zen/zstring.h>
@@ -87,11 +88,43 @@ private:
friend class CombinedFilter;
std::strong_ordering compareSameType(const PathFilter& other) const override;
- //upper-case + Unicode-normalized by construction:
- std::vector<Zstring> includeMasksFileFolder;
- std::vector<Zstring> includeMasksFolder;
- std::vector<Zstring> excludeMasksFileFolder;
- std::vector<Zstring> excludeMasksFolder;
+ class MaskMatcher
+ {
+ public:
+ void insert(const Zstring& mask); //expected: upper-case + Unicode-normalized!
+ bool matches(const Zchar* pathFirst, const Zchar* pathLast) const;
+ bool matchesBegin(const Zstring& relPath) const;
+
+ inline friend std::strong_ordering operator<=>(const MaskMatcher& lhs, const MaskMatcher& rhs)
+ {
+ return std::tie(lhs.realMasks_, lhs.relPathsCmp_) <=>
+ std::tie(rhs.realMasks_, rhs.relPathsCmp_);
+ }
+ //can't "= default" because std::unordered_set doesn't support <=>!
+ //CAVEAT: when operator<=> is not "default" we also don't get operator== for free! declare manually:
+ bool operator==(const MaskMatcher&) const;
+ //why declare, but not define? if undeclared, "std::tie <=> std::tie" incorrectly deduces std::weak_ordering
+ //=> bug? no, looks like "C++ standard nonsense": https://cplusplus.github.io/LWG/issue3431
+ //std::three_way_comparable requires __WeaklyEqualityComparableWith!! this is stupid on first sight. And on second. And on third.
+
+ private:
+ std::set<Zstring> realMasks_; //always containing ? or * (use std::set<> to scrap duplicates!)
+ std::unordered_set<Zstring, zen::StringHash, zen::StringEqual> relPaths_; //never containing ? or *
+ std::set<Zstring> relPathsCmp_; //req. for operator<=> only :(
+ };
+
+ struct FilterSet
+ {
+ MaskMatcher fileFolderMasks;
+ MaskMatcher folderMasks;
+
+ std::strong_ordering operator<=>(const FilterSet&) const = default;
+ };
+
+ static void parseFilterPhrase(const Zstring& filterPhrase, FilterSet& filter);
+
+ FilterSet includeFilter;
+ FilterSet excludeFilter;
};
@@ -193,7 +226,7 @@ std::strong_ordering CombinedFilter::compareSameType(const PathFilter& other) co
const CombinedFilter& rhs = static_cast<const CombinedFilter&>(other);
if (const std::strong_ordering cmp = lhs.first_.compareSameType(rhs.first_);
- std::is_neq(cmp))
+ cmp != std::strong_ordering::equal)
return cmp;
return lhs.second_.compareSameType(rhs.second_);
diff --git a/FreeFileSync/Source/base/structures.h b/FreeFileSync/Source/base/structures.h
index 6671f1ff..da118913 100644
--- a/FreeFileSync/Source/base/structures.h
+++ b/FreeFileSync/Source/base/structures.h
@@ -282,20 +282,16 @@ struct FilterConfig
sizeMax (sizeMaxIn),
unitSizeMax (unitSizeMaxIn) {}
- /*
- Semantics of PathFilter:
- 1. using it creates a NEW folder hierarchy! -> must be considered by <Two way> variant! (fortunately it turns out, doing nothing already has perfect semantics :)
- 2. it applies equally to both sides => it always matches either both sides or none! => can be used while traversing a single folder!
- */
+ /* Semantics of PathFilter:
+ 1. using it creates a NEW folder hierarchy! -> must be considered by <Two way> variant! (fortunately it turns out, doing nothing already has perfect semantics :)
+ 2. it applies equally to both sides => it always matches either both sides or none! => can be used while traversing a single folder! */
Zstring includeFilter = Zstr("*");
Zstring excludeFilter;
- /*
- Semantics of SoftFilter:
- 1. It potentially may match only one side => it MUST NOT be applied while traversing a single folder to avoid mismatches
- 2. => it is applied after traversing and just marks rows, (NO deletions after comparison are allowed)
- 3. => equivalent to a user temporarily (de-)selecting rows -> not relevant for <Two way> variant! ;)
- */
+ /* Semantics of SoftFilter:
+ 1. It potentially may match only one side => it MUST NOT be applied while traversing a single folder to avoid mismatches
+ 2. => it is applied after traversing and just marks rows, (NO deletions after comparison are allowed)
+ 3. => equivalent to a user temporarily (de-)selecting rows -> not relevant for <Two way> variant! ;) */
size_t timeSpan = 0;
UnitTime unitTimeSpan = UnitTime::none;
diff --git a/FreeFileSync/Source/base/versioning.cpp b/FreeFileSync/Source/base/versioning.cpp
index 79939ee7..245e0bb1 100644
--- a/FreeFileSync/Source/base/versioning.cpp
+++ b/FreeFileSync/Source/base/versioning.cpp
@@ -387,7 +387,7 @@ std::weak_ordering fff::operator<=>(const VersioningLimitFolder& lhs, const Vers
{
if (const std::weak_ordering cmp = std::tie(lhs.versioningFolderPath, lhs.versionMaxAgeDays) <=>
std::tie(rhs.versioningFolderPath, rhs.versionMaxAgeDays);
- std::is_neq(cmp))
+ cmp != std::weak_ordering::equivalent)
return cmp;
if (lhs.versionMaxAgeDays > 0)
diff --git a/FreeFileSync/Source/config.cpp b/FreeFileSync/Source/config.cpp
index f0b0f99f..818a2342 100644
--- a/FreeFileSync/Source/config.cpp
+++ b/FreeFileSync/Source/config.cpp
@@ -150,13 +150,13 @@ void writeText(const wxLanguage& value, std::string& output)
{
//use description as unique wxLanguage identifier, see localization.cpp
//=> handle changes to wxLanguage enum between wxWidgets versions
- if (const wxLanguageInfo* lngInfo = wxLocale::GetLanguageInfo(value))
+ const wxLanguageInfo* lngInfo = wxLocale::GetLanguageInfo(value);
+ assert(lngInfo);
+ if (!lngInfo)
+ lngInfo = wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH_US);
+
+ if (lngInfo)
output = utfTo<std::string>(lngInfo->Description);
- else
- {
- assert(false);
- output = "English (U.S.)";
- }
}
template <> inline
@@ -1174,7 +1174,7 @@ void readConfig(const XmlIn& in, LocalPairConfig& lpc, std::map<AfsDevice, size_
if (startsWithAsciiNoCase(folderPathPhrase, "sftp:") ||
startsWithAsciiNoCase(folderPathPhrase, "ftp:"))
{
- for (const Zstring& optPhrase : split(folderPathPhrase, Zstr("|"), SplitOnEmpty::skip))
+ for (const Zstring& optPhrase : split(folderPathPhrase, Zstr('|'), SplitOnEmpty::skip))
if (startsWith(optPhrase, Zstr("con=")))
parallelOps = stringTo<int>(afterFirst(optPhrase, Zstr("con="), IfNotFoundReturn::none));
}
diff --git a/FreeFileSync/Source/fatal_error.h b/FreeFileSync/Source/fatal_error.h
index 4749ac75..8d310b3b 100644
--- a/FreeFileSync/Source/fatal_error.h
+++ b/FreeFileSync/Source/fatal_error.h
@@ -30,6 +30,12 @@ void logFatalError(const std::wstring& msg); //noexcept
inline
void logFatalError(const std::wstring& msg) //noexcept
{
+ warn_static("new semantics: logErrorWhileBusy or logErrorShowLater + show upon next FFS start!?")
+ warn_static("this really should append!")
+ //create time-stamped file path + show if newer than last FFS run? (save in GlobalSettings.xml)
+ //replace calls to ::MessageBox() and std::cerr ?
+ //save std::time()
+
using namespace zen;
assert(false); //this is stuff we like to debug
diff --git a/FreeFileSync/Source/localization.cpp b/FreeFileSync/Source/localization.cpp
index 8891c956..1f56eb66 100644
--- a/FreeFileSync/Source/localization.cpp
+++ b/FreeFileSync/Source/localization.cpp
@@ -368,9 +368,9 @@ public:
stringsList.append(original.c_str(), original.size() + 1); //include 0-termination
}
- for (const auto& [original, trans] : transMapping)
+ for (const auto& [original, translationW] : transMapping)
{
- const auto& translation = utfTo<std::string>(trans);
+ const auto& translation = utfTo<std::string>(translationW);
writeNumber<uint32_t>(moBuf_, translation.size()); //string length
writeNumber<uint32_t>(moBuf_, stringsOffset + stringsList.size()); //string offset
stringsList.append(translation.c_str(), translation.size() + 1); //include 0-termination
@@ -381,6 +381,12 @@ public:
wxMsgCatalog* LoadCatalog(const wxString& domain, const wxString& lang) override
{
+ auto extractIsoLangCode = [](wxString langCode)
+ {
+ langCode = beforeLast(langCode, L".", IfNotFoundReturn::all);
+ return beforeLast(langCode, L"_", IfNotFoundReturn::all);
+ };
+
//"lang" is NOT (exactly) what we return from GetAvailableTranslations(), but has a little "extra", e.g.: de_DE.WINDOWS-1252 or ar.WINDOWS-1252
if (equalAsciiNoCase(extractIsoLangCode(lang), extractIsoLangCode(canonicalName_)))
return wxMsgCatalog::CreateFromData(wxScopedCharBuffer::CreateNonOwned(moBuf_.ref().c_str(), moBuf_.ref().size()), domain);
@@ -396,24 +402,18 @@ public:
}
private:
- static wxString extractIsoLangCode(wxString langCode)
- {
- langCode = beforeLast(langCode, L".", IfNotFoundReturn::all);
- return beforeLast(langCode, L"_", IfNotFoundReturn::all);
- }
-
const wxString canonicalName_;
MemoryStreamOut<std::string> moBuf_;
};
-//global wxWidgets localization: sets up C localization runtime as well!
-class wxWidgetsLocale
+//global wxWidgets localization: sets up C locale as well!
+class ZenLocale
{
public:
- static wxWidgetsLocale& getInstance()
+ static ZenLocale& getInstance()
{
- static wxWidgetsLocale inst;
+ static ZenLocale inst;
return inst;
}
@@ -439,7 +439,7 @@ public:
locale_ = std::make_unique<wxLocale>(wxLANGUAGE_DEFAULT, wxLOCALE_DONT_LOAD_DEFAULT /*we're not using wxwin.mo*/);
//wxLANGUAGE_DEFAULT => internally calls std::setlocale(LC_ALL, "" /*== user-preferred locale*/) on Windows/Linux (but not macOS)
/* => exactly what's needed on Windows/Linux
-
+
but not needed on macOS; even detrimental:
- breaks wxWidgets file drag and drop! https://freefilesync.org/forum/viewtopic.php?t=8215
- even wxWidgets knows: "under macOS C locale must not be changed, as doing this exposes bugs in the system"
@@ -463,16 +463,18 @@ public:
void tearDown() { locale_.reset(); lng_ = wxLANGUAGE_UNKNOWN; layoutDir_ = wxLayout_Default; }
- wxLanguage getLanguage () const { return lng_; }
+ wxLanguage getLanguage() const { return lng_; }
wxLayoutDirection getLayoutDirection() const { return layoutDir_; }
private:
- wxWidgetsLocale() {}
- ~wxWidgetsLocale() { assert(!locale_); }
+ ZenLocale() {}
+ ~ZenLocale() { assert(!locale_); }
wxLanguage lng_ = wxLANGUAGE_UNKNOWN;
wxLayoutDirection layoutDir_ = wxLayout_Default;
std::unique_ptr<wxLocale> locale_;
+ //use wxWidgets 3.1.6 wxUILocale? wxLocale already does *exactly* what we need, while wxLocale does a half-arsed job (as expected):
+ //setlocale() is only called on wxGTK, but not on Windows + wxTranslations is not initialized.
};
@@ -498,7 +500,7 @@ void fff::localizationInit(const Zstring& zipPath) //throw FileError
void fff::localizationCleanup()
{
assert(!globalTranslations.empty());
- wxWidgetsLocale::getInstance().tearDown();
+ ZenLocale::getInstance().tearDown();
setTranslator(nullptr); //good place for clean up rather than some time during static destruction: is this an actual benefit???
globalTranslations.clear();
}
@@ -546,7 +548,7 @@ void fff::setLanguage(wxLanguage lng) //throw FileError
}
//handle RTL swapping: we need wxWidgets to do this
- wxWidgetsLocale::getInstance().init(lng);
+ ZenLocale::getInstance().init(lng);
//add translation for wxWidgets-internal strings:
assert(wxTranslations::Get()); //already initialized by wxLocale
@@ -555,7 +557,7 @@ void fff::setLanguage(wxLanguage lng) //throw FileError
std::map<std::string, std::wstring> transMapping =
{
};
- wxtrans->SetLanguage(lng); //!= wxLocale's language, which could be wxLANGUAGE_DEFAULT (see wxWidgetsLocale)
+ wxtrans->SetLanguage(lng); //!= wxLocale's language, which could be wxLANGUAGE_DEFAULT (see ZenLocale)
wxtrans->SetLoader(new MemoryTranslationLoader(lng, std::move(transMapping)));
[[maybe_unused]] const bool catalogAdded = wxtrans->AddCatalog(wxString());
assert(catalogAdded || lng == wxLANGUAGE_ENGLISH_US);
@@ -565,7 +567,7 @@ void fff::setLanguage(wxLanguage lng) //throw FileError
wxLanguage fff::getDefaultLanguage()
{
- static const wxLanguage defaultLng = static_cast<wxLanguage>(wxLocale::GetSystemLanguage());
+ static const wxLanguage defaultLng = static_cast<wxLanguage>(wxLocale::GetSystemLanguage());
//uses GetUserDefaultUILanguage(): https://github.com/wxWidgets/wxWidgets/commit/9600c29ff2ca13ef66b76eabadaac5ec8654b792
return defaultLng;
@@ -574,11 +576,11 @@ wxLanguage fff::getDefaultLanguage()
wxLanguage fff::getLanguage()
{
- return wxWidgetsLocale::getInstance().getLanguage();
+ return ZenLocale::getInstance().getLanguage();
}
wxLayoutDirection fff::getLayoutDirection()
{
- return wxWidgetsLocale::getInstance().getLayoutDirection();
+ return ZenLocale::getInstance().getLayoutDirection();
}
diff --git a/FreeFileSync/Source/log_file.cpp b/FreeFileSync/Source/log_file.cpp
index abc39f3b..27670aef 100644
--- a/FreeFileSync/Source/log_file.cpp
+++ b/FreeFileSync/Source/log_file.cpp
@@ -107,7 +107,7 @@ std::string generateLogFooterTxt(const std::wstring& logFilePath /*optional*/, i
output += " [...] " + utfTo<std::string>(replaceCpy(_P("Showing %y of 1 item", "Showing %y of %x items", logItemsTotal), //%x used as plural form placeholder!
L"%y", formatNumber(logPreviewItemsMax))) + '\n';
- output += '\n' + std::string(SEPARATION_LINE_LEN, '_') + '\n' +
+ output += std::string(SEPARATION_LINE_LEN, '_') + '\n' +
utfTo<std::string>(getOsDescription() + /*throw FileError*/ +
L" - " + utfTo<std::wstring>(getUserDescription()) /*throw FileError*/ +
@@ -342,8 +342,7 @@ std::string generateLogFooterHtml(const std::wstring& logFilePath /*optional*/,
htmlTxt(replaceCpy(_P("Showing %y of 1 item", "Showing %y of %x items", logItemsTotal), //%x used as plural form placeholder!
L"%y", formatNumber(logPreviewItemsMax))) + "</div>\n";
- output += R"( <br>
-
+ output += R"(
<div style="border-bottom:1px solid #AAA; margin:5px 0;"></div>
<div style="font-size:small;">
<img src="https://freefilesync.org/images/log/)" + osImage + R"(" width="24" height="24" alt="" style="vertical-align:middle;">
diff --git a/FreeFileSync/Source/ui/batch_config.cpp b/FreeFileSync/Source/ui/batch_config.cpp
index 4bc1995c..ecde2d00 100644
--- a/FreeFileSync/Source/ui/batch_config.cpp
+++ b/FreeFileSync/Source/ui/batch_config.cpp
@@ -70,7 +70,7 @@ BatchDialog::BatchDialog(wxWindow* parent, BatchDialogConfig& dlgCfg) :
m_staticTextHeader->SetLabelText(replaceCpy(m_staticTextHeader->GetLabelText(), L"%x", L"FreeFileSync.exe <" + _("configuration file") + L">.ffs_batch"));
m_staticTextHeader->Wrap(fastFromDIP(520));
- m_bitmapBatchJob->SetBitmap(loadImage("cfg_batch"));
+ setImage(*m_bitmapBatchJob, loadImage("cfg_batch"));
enumPostSyncAction_.
add(PostSyncAction::none, L"").
@@ -93,12 +93,12 @@ void BatchDialog::updateGui() //re-evaluate gui after config changes
{
const BatchDialogConfig dlgCfg = getConfig(); //resolve parameter ownership: some on GUI controls, others member variables
- m_bitmapIgnoreErrors->SetBitmap(greyScaleIfDisabled(loadImage("error_ignore_active"), dlgCfg.ignoreErrors));
+ setImage(*m_bitmapIgnoreErrors, greyScaleIfDisabled(loadImage("error_ignore_active"), dlgCfg.ignoreErrors));
m_radioBtnErrorDialogShow ->Enable(!dlgCfg.ignoreErrors);
m_radioBtnErrorDialogCancel->Enable(!dlgCfg.ignoreErrors);
- m_bitmapMinimizeToTray->SetBitmap(greyScaleIfDisabled(loadImage("minimize_to_tray"), dlgCfg.batchExCfg.runMinimized));
+ setImage(*m_bitmapMinimizeToTray, greyScaleIfDisabled(loadImage("minimize_to_tray"), dlgCfg.batchExCfg.runMinimized));
}
diff --git a/FreeFileSync/Source/ui/batch_status_handler.cpp b/FreeFileSync/Source/ui/batch_status_handler.cpp
index 048461f2..f6b36854 100644
--- a/FreeFileSync/Source/ui/batch_status_handler.cpp
+++ b/FreeFileSync/Source/ui/batch_status_handler.cpp
@@ -71,7 +71,7 @@ BatchStatusHandler::Result BatchStatusHandler::reportResults(const Zstring& post
const std::string& emailNotifyAddress, ResultsNotification emailNotifyCondition) //noexcept!!
{
//keep correct summary window stats considering count down timer, system sleep
- const std::chrono::milliseconds totalTime = progressDlg_->pauseAndGetTotalTime();
+ const std::chrono::milliseconds totalTime = progressDlg_->pauseAndGetTotalTime();
//determine post-sync status irrespective of further errors during tear-down
const SyncResult syncResult = [&]
@@ -146,7 +146,7 @@ BatchStatusHandler::Result BatchStatusHandler::reportResults(const Zstring& post
catch (const FileError& e) { errorLog_.logMsg(e.toString(), MSG_TYPE_ERROR); }
//--------------------- post sync actions ----------------------
- auto mayRunAfterCountDown = [&](const std::wstring& operationName)
+ auto proceedWithShutdown = [&](const std::wstring& operationName)
{
if (progressDlg_->getWindowIfVisible())
try
@@ -161,7 +161,7 @@ BatchStatusHandler::Result BatchStatusHandler::reportResults(const Zstring& post
throw;
}
};
- delayAndCountDown(std::chrono::steady_clock::now() + std::chrono::seconds(5), notifyStatusThrowOnCancel); //throw AbortProcess
+ delayAndCountDown(std::chrono::steady_clock::now() + std::chrono::seconds(10), notifyStatusThrowOnCancel); //throw AbortProcess
}
catch (AbortProcess&) { return false; }
@@ -177,14 +177,14 @@ BatchStatusHandler::Result BatchStatusHandler::reportResults(const Zstring& post
assert(false);
break;
case PostSyncAction2::sleep:
- if (mayRunAfterCountDown(_("System: Sleep")))
+ if (proceedWithShutdown(_("System: Sleep")))
{
autoClose = progressDlg_->getOptionAutoCloseDialog();
suspend = true;
}
break;
case PostSyncAction2::shutdown:
- if (mayRunAfterCountDown(_("System: Shut down")))
+ if (proceedWithShutdown(_("System: Shut down")))
{
autoClose = true;
finalRequest = FinalRequest::shutdown; //system shutdown must be handled by calling context!
diff --git a/FreeFileSync/Source/ui/file_grid.cpp b/FreeFileSync/Source/ui/file_grid.cpp
index 01a30d48..807dd081 100644
--- a/FreeFileSync/Source/ui/file_grid.cpp
+++ b/FreeFileSync/Source/ui/file_grid.cpp
@@ -10,7 +10,6 @@
#include <wx/settings.h>
#include <zen/i18n.h>
#include <zen/file_error.h>
-//#include <zen/basic_math.h>
#include <zen/format_unit.h>
#include <zen/scope_guard.h>
#include <wx+/tooltip.h>
diff --git a/FreeFileSync/Source/ui/file_view.cpp b/FreeFileSync/Source/ui/file_view.cpp
index 2f040797..6f7d6ad0 100644
--- a/FreeFileSync/Source/ui/file_view.cpp
+++ b/FreeFileSync/Source/ui/file_view.cpp
@@ -500,7 +500,7 @@ bool lessFilePath(const FileSystemObject::ObjectId& lhs, const FileSystemObject:
//different components...
if (const std::weak_ordering cmp = compareNatural((*itL)->getItemNameAny(), (*itR)->getItemNameAny());
- std::is_neq(cmp))
+ cmp != std::weak_ordering::equivalent)
{
if constexpr (ascending)
return std::is_lt(cmp);
diff --git a/FreeFileSync/Source/ui/folder_pair.h b/FreeFileSync/Source/ui/folder_pair.h
index 0357ae4c..c47bfeae 100644
--- a/FreeFileSync/Source/ui/folder_pair.h
+++ b/FreeFileSync/Source/ui/folder_pair.h
@@ -44,12 +44,14 @@ public:
FolderPairPanelBasic(GuiPanel& basicPanel) : //takes reference on basic panel to be enhanced
basicPanel_(basicPanel)
{
+ using namespace zen;
+
//register events for removal of alternate configuration
basicPanel_.m_bpButtonLocalCompCfg ->Bind(wxEVT_RIGHT_DOWN, [this](wxMouseEvent& event) { onLocalCompCfgContext (event); });
basicPanel_.m_bpButtonLocalSyncCfg ->Bind(wxEVT_RIGHT_DOWN, [this](wxMouseEvent& event) { onLocalSyncCfgContext (event); });
basicPanel_.m_bpButtonLocalFilter ->Bind(wxEVT_RIGHT_DOWN, [this](wxMouseEvent& event) { onLocalFilterCfgContext(event); });
- basicPanel_.m_bpButtonRemovePair->SetBitmapLabel(zen::loadImage("item_remove"));
+ setImage(*basicPanel_.m_bpButtonRemovePair, loadImage("item_remove"));
}
private:
diff --git a/FreeFileSync/Source/ui/folder_selector.cpp b/FreeFileSync/Source/ui/folder_selector.cpp
index 88b5153d..15a3d412 100644
--- a/FreeFileSync/Source/ui/folder_selector.cpp
+++ b/FreeFileSync/Source/ui/folder_selector.cpp
@@ -9,6 +9,7 @@
#include <zen/file_access.h>
#include <wx/dirdlg.h>
#include <wx/scrolwin.h>
+#include <wx+/bitmap_button.h>
#include <wx+/popup_dlg.h>
#include <wx+/context_menu.h>
#include <wx+/image_resources.h>
@@ -94,7 +95,7 @@ FolderSelector::FolderSelector(wxWindow* parent,
if (dropWindow2_)
setupDragDrop(*dropWindow2_);
- selectAltFolderButton_.SetBitmapLabel(loadImage("cloud_small"));
+ setImage(selectAltFolderButton_, loadImage("cloud_small"));
//keep folderSelector and dirpath synchronous
folderComboBox_ .Bind(wxEVT_MOUSEWHEEL, &FolderSelector::onMouseWheel, this);
diff --git a/FreeFileSync/Source/ui/gui_generated.cpp b/FreeFileSync/Source/ui/gui_generated.cpp
index 83dd284e..cb0e03fe 100644
--- a/FreeFileSync/Source/ui/gui_generated.cpp
+++ b/FreeFileSync/Source/ui/gui_generated.cpp
@@ -2681,7 +2681,7 @@ CloudSetupDlgGenerated::CloudSetupDlgGenerated( wxWindow* parent, wxWindowID id,
bSizer3041->Add( bSizer305, 0, wxALL, 5 );
- m_listBoxGdriveDrives = new wxListBox( m_panel41, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, NULL, wxLB_NEEDED_SB|wxLB_SINGLE|wxLB_SORT );
+ m_listBoxGdriveDrives = new wxListBox( m_panel41, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, NULL, wxLB_NEEDED_SB|wxLB_SINGLE );
bSizer3041->Add( m_listBoxGdriveDrives, 1, wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT, 5 );
diff --git a/FreeFileSync/Source/ui/gui_status_handler.cpp b/FreeFileSync/Source/ui/gui_status_handler.cpp
index a0c1fe10..e87b7c2d 100644
--- a/FreeFileSync/Source/ui/gui_status_handler.cpp
+++ b/FreeFileSync/Source/ui/gui_status_handler.cpp
@@ -352,7 +352,7 @@ StatusHandlerFloatingDialog::StatusHandlerFloatingDialog(wxFrame* parentDlg,
const Zstring& soundFileAlertPending,
const wxSize& progressDlgSize, bool dlgMaximize,
bool autoCloseDialog,
- const ErrorLog* errorLogStart) :
+ const ErrorLog* errorLogStart) :
jobNames_(jobNames),
startTime_(startTime),
autoRetryCount_(autoRetryCount),
@@ -360,7 +360,7 @@ StatusHandlerFloatingDialog::StatusHandlerFloatingDialog(wxFrame* parentDlg,
soundFileSyncComplete_(soundFileSyncComplete),
soundFileAlertPending_(soundFileAlertPending),
progressDlg_(SyncProgressDialog::create(progressDlgSize, dlgMaximize, [this] { userRequestAbort(); }, *this, parentDlg, true /*showProgress*/, autoCloseDialog,
-jobNames, std::chrono::system_clock::to_time_t(startTime), ignoreErrors, autoRetryCount, PostSyncAction2::none))
+jobNames, std::chrono::system_clock::to_time_t(startTime), ignoreErrors, autoRetryCount, PostSyncAction2::none))
{
if (errorLogStart)
errorLog_ = *errorLogStart;
@@ -380,7 +380,7 @@ StatusHandlerFloatingDialog::Result StatusHandlerFloatingDialog::reportResults(c
const std::string& emailNotifyAddress, ResultsNotification emailNotifyCondition)
{
//keep correct summary window stats considering count down timer, system sleep
- const std::chrono::milliseconds totalTime = progressDlg_->pauseAndGetTotalTime();
+ const std::chrono::milliseconds totalTime = progressDlg_->pauseAndGetTotalTime();
//determine post-sync status irrespective of further errors during tear-down
const SyncResult syncResult = [&]
@@ -456,7 +456,7 @@ StatusHandlerFloatingDialog::Result StatusHandlerFloatingDialog::reportResults(c
catch (const FileError& e) { errorLog_.logMsg(e.toString(), MSG_TYPE_ERROR); }
//--------------------- post sync actions ----------------------
- auto mayRunAfterCountDown = [&](const std::wstring& operationName)
+ auto proceedWithShutdown = [&](const std::wstring& operationName)
{
if (progressDlg_->getWindowIfVisible())
try
@@ -471,7 +471,7 @@ StatusHandlerFloatingDialog::Result StatusHandlerFloatingDialog::reportResults(c
throw;
}
};
- delayAndCountDown(std::chrono::steady_clock::now() + std::chrono::seconds(5), notifyStatusThrowOnCancel); //throw AbortProcess
+ delayAndCountDown(std::chrono::steady_clock::now() + std::chrono::seconds(10), notifyStatusThrowOnCancel); //throw AbortProcess
}
catch (AbortProcess&) { return false; }
@@ -488,14 +488,14 @@ StatusHandlerFloatingDialog::Result StatusHandlerFloatingDialog::reportResults(c
finalRequest = FinalRequest::exit; //program exit must be handled by calling context!
break;
case PostSyncAction2::sleep:
- if (mayRunAfterCountDown(_("System: Sleep")))
+ if (proceedWithShutdown(_("System: Sleep")))
{
autoClose = progressDlg_->getOptionAutoCloseDialog();
suspend = true;
}
break;
case PostSyncAction2::shutdown:
- if (mayRunAfterCountDown(_("System: Shut down")))
+ if (proceedWithShutdown(_("System: Shut down")))
{
autoClose = true;
finalRequest = FinalRequest::shutdown; //system shutdown must be handled by calling context!
@@ -603,6 +603,8 @@ ProcessCallback::Response StatusHandlerFloatingDialog::reportError(const ErrorIn
//auto-retry
if (errorInfo.retryNumber < autoRetryCount_)
{
+ warn_static("maybe we should consider errorInfo.failTime, and not 'now' when logging the error?")
+
errorLog_.logMsg(errorInfo.msg + L"\n-> " + _("Automatic retry"), MSG_TYPE_INFO);
delayAndCountDown(errorInfo.failTime + autoRetryDelay_,
[&, statusPrefix = _("Automatic retry") +
diff --git a/FreeFileSync/Source/ui/gui_status_handler.h b/FreeFileSync/Source/ui/gui_status_handler.h
index 8d8fc972..da595af6 100644
--- a/FreeFileSync/Source/ui/gui_status_handler.h
+++ b/FreeFileSync/Source/ui/gui_status_handler.h
@@ -74,8 +74,8 @@ public:
const Zstring& soundFileSyncComplete,
const Zstring& soundFileAlertPending,
const wxSize& progressDlgSize, bool dlgMaximize,
- bool autoCloseDialog,
- const zen::ErrorLog* errorLogStart /*optional*/); //noexcept!
+ bool autoCloseDialog,
+ const zen::ErrorLog* errorLogStart /*optional*/); //noexcept!
~StatusHandlerFloatingDialog();
void initNewPhase (int itemsTotal, int64_t bytesTotal, ProcessPhase phaseID) override; //
diff --git a/FreeFileSync/Source/ui/main_dlg.cpp b/FreeFileSync/Source/ui/main_dlg.cpp
index d6e443dc..f5433a72 100644
--- a/FreeFileSync/Source/ui/main_dlg.cpp
+++ b/FreeFileSync/Source/ui/main_dlg.cpp
@@ -49,6 +49,7 @@
#include "../base/icon_loader.h"
#include "../ffs_paths.h"
#include "../localization.h"
+#include "../fatal_error.h"
#include "../version/version.h"
#include "../afs/gdrive.h"
@@ -468,23 +469,23 @@ MainDialog::MainDialog(const Zstring& globalConfigFilePath,
return layOver(backImg, loadImage(layoverName, backImg.GetWidth() * 7 / 10), wxALIGN_TOP | wxALIGN_RIGHT);
};
- m_bpButtonCmpConfig ->SetBitmapLabel(loadImage("options_compare"));
- m_bpButtonSyncConfig->SetBitmapLabel(loadImage("options_sync"));
+ setImage(*m_bpButtonCmpConfig, loadImage("options_compare"));
+ setImage(*m_bpButtonSyncConfig, loadImage("options_sync"));
- m_bpButtonCmpContext ->SetBitmapLabel(mirrorIfRtl(loadImage("button_arrow_right")));
- m_bpButtonFilterContext->SetBitmapLabel(mirrorIfRtl(loadImage("button_arrow_right")));
- m_bpButtonSyncContext ->SetBitmapLabel(mirrorIfRtl(loadImage("button_arrow_right")));
- m_bpButtonViewFilterContext->SetBitmapLabel(mirrorIfRtl(loadImage("button_arrow_right")));
+ setImage(*m_bpButtonCmpContext, mirrorIfRtl(loadImage("button_arrow_right")));
+ setImage(*m_bpButtonFilterContext, mirrorIfRtl(loadImage("button_arrow_right")));
+ setImage(*m_bpButtonSyncContext, mirrorIfRtl(loadImage("button_arrow_right")));
+ setImage(*m_bpButtonViewFilterContext, mirrorIfRtl(loadImage("button_arrow_right")));
//m_bpButtonNew ->set dynamically
- m_bpButtonOpen ->SetBitmapLabel(loadImage("cfg_load"));
+ setImage(*m_bpButtonOpen, loadImage("cfg_load"));
//m_bpButtonSave ->set dynamically
- m_bpButtonSaveAs ->SetBitmapLabel(generateSaveAsImage("start_sync"));
- m_bpButtonSaveAsBatch->SetBitmapLabel(generateSaveAsImage("cfg_batch"));
+ setImage(*m_bpButtonSaveAs, generateSaveAsImage("start_sync"));
+ setImage(*m_bpButtonSaveAsBatch, generateSaveAsImage("cfg_batch"));
- m_bpButtonAddPair ->SetBitmapLabel(loadImage("item_add"));
- m_bpButtonHideSearch ->SetBitmapLabel(loadImage("close_panel"));
- m_bpButtonToggleLog ->SetBitmapLabel(loadImage("log_file"));
+ setImage(*m_bpButtonAddPair, loadImage("item_add"));
+ setImage(*m_bpButtonHideSearch, loadImage("close_panel"));
+ setImage(*m_bpButtonToggleLog, loadImage("log_file"));
m_bpButtonFilter ->SetMinSize({loadImage("options_filter").GetWidth() + fastFromDIP(27), -1}); //make the filter button wider
m_textCtrlSearchTxt->SetMinSize({fastFromDIP(220), -1});
@@ -527,10 +528,10 @@ MainDialog::MainDialog(const Zstring& globalConfigFilePath,
//init log panel
setRelativeFontSize(*m_staticTextSyncResult, 1.5);
- m_bitmapItemStat->SetBitmap(imgFile);
+ setImage(*m_bitmapItemStat, imgFile);
wxImage imgTime = loadImage("time", -1 /*maxWidth*/, imgFile.GetHeight());
- m_bitmapTimeStat->SetBitmap(imgTime);
+ setImage(*m_bitmapTimeStat, imgTime);
m_bitmapTimeStat->SetMinSize({-1, imgFile.GetHeight()});
logPanel_ = new LogPanel(m_panelLog); //pass ownership
@@ -719,49 +720,32 @@ MainDialog::MainDialog(const Zstring& globalConfigFilePath,
//m_bpButtonSyncContext->SetToolTip(m_bpButtonSyncConfig->GetToolTipText());
- m_bitmapSmallDirectoryLeft ->SetBitmap(imgDir);
- m_bitmapSmallFileLeft ->SetBitmap(imgFile);
- m_bitmapSmallDirectoryRight->SetBitmap(imgDir);
- m_bitmapSmallFileRight ->SetBitmap(imgFile);
+ setImage(*m_bitmapSmallDirectoryLeft, imgDir);
+ setImage(*m_bitmapSmallFileLeft, imgFile);
+ setImage(*m_bitmapSmallDirectoryRight, imgDir);
+ setImage(*m_bitmapSmallFileRight, imgFile);
//---------------------- menu bar----------------------------
- m_menuItemNew ->SetBitmap(loadImage("cfg_new_sicon"));
- m_menuItemLoad ->SetBitmap(loadImage("cfg_load_sicon"));
- m_menuItemSave ->SetBitmap(loadImage("cfg_save_sicon"));
- m_menuItemSaveAsBatch->SetBitmap(loadImage("cfg_batch_sicon"));
+ setImage(*m_menuItemNew, loadImage("cfg_new_sicon"));
+ setImage(*m_menuItemLoad, loadImage("cfg_load_sicon"));
+ setImage(*m_menuItemSave, loadImage("cfg_save_sicon"));
+ setImage(*m_menuItemSaveAsBatch, loadImage("cfg_batch_sicon"));
+
+ setImage(*m_menuItemShowLog, loadImage("log_file_sicon"));
+ setImage(*m_menuItemCompare, loadImage("compare_sicon"));
+ setImage(*m_menuItemCompSettings, loadImage("options_compare_sicon"));
+ setImage(*m_menuItemFilter, loadImage("options_filter_sicon"));
+ setImage(*m_menuItemSyncSettings, loadImage("options_sync_sicon"));
+ setImage(*m_menuItemSynchronize, loadImage("start_sync_sicon"));
+
+ setImage(*m_menuItemOptions, loadImage("settings_sicon"));
+ setImage(*m_menuItemFind, loadImage("find_sicon"));
+ setImage(*m_menuItemResetLayout, loadImage("reset_sicon"));
+
+ setImage(*m_menuItemHelp, loadImage("help_sicon"));
+ setImage(*m_menuItemAbout, loadImage("about_sicon"));
+ setImage(*m_menuItemCheckVersionNow, loadImage("update_check_sicon"));
- m_menuItemShowLog ->SetBitmap(loadImage("log_file_sicon"));
- m_menuItemCompare ->SetBitmap(loadImage("compare_sicon"));
- m_menuItemCompSettings->SetBitmap(loadImage("options_compare_sicon"));
- m_menuItemFilter ->SetBitmap(loadImage("options_filter_sicon"));
- m_menuItemSyncSettings->SetBitmap(loadImage("options_sync_sicon"));
- m_menuItemSynchronize ->SetBitmap(loadImage("start_sync_sicon"));
-
- m_menuItemOptions ->SetBitmap(loadImage("settings_sicon"));
- m_menuItemFind ->SetBitmap(loadImage("find_sicon"));
- m_menuItemResetLayout->SetBitmap(loadImage("reset_sicon"));
-
- m_menuItemHelp ->SetBitmap(loadImage("help_sicon"));
- m_menuItemAbout->SetBitmap(loadImage("about_sicon"));
- m_menuItemCheckVersionNow->SetBitmap(loadImage("update_check_sicon"));
-
- auto fixMenuIcons = [](wxMenu& menu) //GTK: image must be set *before* adding wxMenuItem to menu or it won't show
- {
- std::vector<std::pair<wxMenuItem*, size_t /*pos*/>> itemsWithBmp;
- {
- size_t pos = 0;
- for (wxMenuItem* item : menu.GetMenuItems())
- {
- if (item->GetBitmap().IsOk())
- itemsWithBmp.emplace_back(item, pos);
- ++pos;
- }
- }
-
- for (const auto& [item, pos] : itemsWithBmp)
- if (!menu.Insert(pos, menu.Remove(item))) //detach + reinsert
- assert(false);
- };
fixMenuIcons(*m_menuFile);
fixMenuIcons(*m_menuActions);
fixMenuIcons(*m_menuTools);
@@ -771,7 +755,7 @@ MainDialog::MainDialog(const Zstring& globalConfigFilePath,
for (const TranslationInfo& ti : getAvailableTranslations())
{
wxMenuItem* newItem = new wxMenuItem(m_menuLanguages, wxID_ANY, ti.languageName);
- newItem->SetBitmap(loadImage(ti.languageFlag)); //GTK: set *before* inserting into menu
+ setImage(*newItem, loadImage(ti.languageFlag)); //GTK: set *before* inserting into menu
m_menuLanguages->Bind(wxEVT_COMMAND_MENU_SELECTED, [this, langId = ti.languageID](wxCommandEvent&) { switchProgramLanguage(langId); }, newItem->GetId());
m_menuLanguages->Append(newItem); //pass ownership
@@ -883,6 +867,8 @@ MainDialog::MainDialog(const Zstring& globalConfigFilePath,
m_gridCfgHistory->makeRowVisible(selectedRows.front());
+onSystemShutdownRegister(onBeforeSystemShutdownCookie_);
+
//start up: user most likely wants to change config, or start comparison by pressing ENTER
m_gridCfgHistory->SetFocus();
@@ -954,22 +940,22 @@ MainDialog::MainDialog(const Zstring& globalConfigFilePath,
MainDialog::~MainDialog()
{
- std::optional<FileError> firstError;
- try //save "LastRun.ffs_gui"
+ std::wstring errorMsg;
+ try //LastRun.ffs_gui
{
writeConfig(getConfig(), lastRunConfigPath_); //throw FileError
}
- catch (const FileError& e) { if (!firstError) firstError = e; }
+ catch (const FileError& e) { errorMsg += e.toString() + L"\n\n"; }
- try //save "GlobalSettings.xml"
+ try //GlobalSettings.xml
{
writeConfig(getGlobalCfgBeforeExit(), globalConfigFilePath_); //throw FileError
}
- catch (const FileError& e) { if (!firstError) firstError = e; }
+ catch (const FileError& e) { errorMsg += e.toString() + L"\n\n"; }
//don't annoy users on read-only drives: it's enough to show a single error message
- if (firstError)
- showNotificationDialog(this, DialogInfoType::error, PopupDialogCfg().setDetailInstructions(firstError->toString()));
+ if (!errorMsg.empty())
+ showNotificationDialog(this, DialogInfoType::error, PopupDialogCfg().setDetailInstructions(trimCpy(errorMsg)));
//auiMgr_.UnInit(); - "since wxWidgets 3.1.4 [...] it will be called automatically when this window is destroyed, as well as when the manager itself is."
@@ -981,20 +967,19 @@ MainDialog::~MainDialog()
//-------------------------------------------------------------------------------------------------------------------------------------
-void MainDialog::onQueryEndSession()
+void MainDialog::onBeforeSystemShutdown()
{
- //we try our best to do something useful in this extreme situation - no reason to notify or even log errors here!
- try { writeConfig(getGlobalCfgBeforeExit(), globalConfigFilePath_); }
- catch (FileError&) {}
-
try { writeConfig(getConfig(), lastRunConfigPath_); }
- catch (FileError&) {}
+ catch (const FileError& e) { logFatalError(e.toString()); }
+
+ try { writeConfig(getGlobalCfgBeforeExit(), globalConfigFilePath_); }
+ catch (const FileError& e) { logFatalError(e.toString()); }
}
void MainDialog::onClose(wxCloseEvent& event)
{
- //attention: system shutdown is handled in onQueryEndSession()!
+ //wxEVT_END_SESSION is already handled by application.cpp::onSystemShutdown()!
//regular destruction handling
if (event.CanVeto())
@@ -1110,7 +1095,7 @@ void MainDialog::setGlobalCfgOnInit(const XmlGlobalSettings& globalSettings)
}
//--------------------------------------------------------------------------------
- //if MainDialog::onQueryEndSession() is called while comparison is active, this panel is saved and restored as "visible"
+ //if MainDialog::onBeforeSystemShutdown() is called while comparison is active, this panel is saved and restored as "visible"
progPane.Hide();
auiMgr_.GetPane(m_panelSearch).Hide(); //no need to show it on startup
@@ -4254,7 +4239,7 @@ void MainDialog::updateStatistics()
txtControl.SetFont(fnt);
txtControl.SetLabelText(valueAsString);
- bmpControl.SetBitmap(greyScaleIfDisabled(mirrorIfRtl(loadImage(imageName)), !isZeroValue));
+ setImage(bmpControl, greyScaleIfDisabled(mirrorIfRtl(loadImage(imageName)), !isZeroValue));
}
};
@@ -4430,9 +4415,10 @@ void MainDialog::onStartSync(wxCommandEvent& event)
case FinalRequest::shutdown: //run *after* last sync stats were updated and saved! https://freefilesync.org/forum/viewtopic.php?t=5761
try
{
- onQueryEndSession(); //(try to) save GlobalSettings.xml => don't block shutdown if failed!!!
shutdownSystem(); //throw FileError
- terminateProcess(FFS_EXIT_SUCCESS); //no point in continuing and saving cfg again in ~MainDialog()/onQueryEndSession() while the OS will kill us anytime!
+ terminateProcess(FFS_EXIT_SUCCESS);
+ //1. no point in continuing and saving cfg again in ~MainDialog()/onBeforeSystemShutdown() while the OS will kill us anytime!
+ //2. is seems wxEVT_END_SESSION is not implemented on wxGTK anyway!
}
catch (const FileError& e) { showNotificationDialog(this, DialogInfoType::error, PopupDialogCfg().setDetailInstructions(e.toString())); }
//[!] ignores current error handling setting, BUT this is not a sync error!
@@ -4641,7 +4627,7 @@ void MainDialog::setLastOperationLog(const ProcessSummary& summary, const std::s
return wxNullImage;
}();
- m_bitmapSyncResult->SetBitmap(syncResultImage);
+ setImage(*m_bitmapSyncResult, syncResultImage);
m_staticTextSyncResult->SetLabelText(getSyncResultLabel(summary.syncResult));
@@ -4793,6 +4779,26 @@ void MainDialog::onSwapSides(wxEvent& event)
void MainDialog::swapSides()
{
+ warn_static("fix swap button design mess: https://freefilesync.org/forum/viewtopic.php?t=9204")
+#if 0
+ if (globalCfg_.confirmDlgs.confirmSwapSides)
+ {
+ bool dontWarnAgain = false;
+ switch (showConfirmationDialog(this, DialogInfoType::info,
+ PopupDialogCfg().setMainInstructions(_("Please confirm you want to swap sides.")).
+ setCheckBox(dontWarnAgain, _("&Don't show this dialog again")),
+ _("&Swap")))
+ {
+ case ConfirmationButton::accept: //swap
+ globalCfg_.confirmDlgs.confirmSwapSides = !dontWarnAgain;
+ break;
+ case ConfirmationButton::cancel:
+ return;
+ }
+ }
+ //------------------------------------------------------
+#endif
+
//swap directory names:
LocalPairConfig lpc1st = firstFolderPair_->getValues();
std::swap(lpc1st.folderPathPhraseLeft, lpc1st.folderPathPhraseRight);
@@ -5462,7 +5468,7 @@ void MainDialog::insertAddFolderPair(const std::vector<LocalPairConfig>& newPair
newPair->m_folderPathRight->setHistory(folderHistoryRight_);
const wxSize optionsIconSize = loadImage("item_add").GetSize();
- newPair->m_bpButtonFolderPairOptions->SetBitmapLabel(resizeCanvas(mirrorIfRtl(loadImage("button_arrow_right")), optionsIconSize, wxALIGN_CENTER));
+ setImage(*(newPair->m_bpButtonFolderPairOptions), resizeCanvas(mirrorIfRtl(loadImage("button_arrow_right")), optionsIconSize, wxALIGN_CENTER));
//set width of left folder panel
const int width = m_panelTopLeft->GetSize().GetWidth();
diff --git a/FreeFileSync/Source/ui/main_dlg.h b/FreeFileSync/Source/ui/main_dlg.h
index 18603585..04a69d1a 100644
--- a/FreeFileSync/Source/ui/main_dlg.h
+++ b/FreeFileSync/Source/ui/main_dlg.h
@@ -23,6 +23,7 @@
#include "../base/algorithm.h"
#include "../return_codes.h"
+
namespace fff
{
class FolderPairFirst;
@@ -46,7 +47,6 @@ public:
const std::vector<Zstring>& referenceFiles,
bool startComparison);
- void onQueryEndSession(); //last chance to do something useful before killing the application!
private:
MainDialog(const Zstring& globalConfigFilePath,
const XmlGuiConfig& guiCfg,
@@ -55,6 +55,8 @@ private:
bool startComparison);
~MainDialog();
+ void onBeforeSystemShutdown(); //last chance to do something useful before killing the application!
+
friend class StatusHandlerTemporaryPanel;
friend class StatusHandlerFloatingDialog;
friend class FolderPairFirst;
@@ -366,6 +368,8 @@ private:
TempFileBuffer tempFileBuf_; //buffer temporary copies of non-native files for %local_path%
const wxImage imgTrashSmall_;
+
+ const zen::SharedRef<std::function<void()>> onBeforeSystemShutdownCookie_ = zen::makeSharedRef<std::function<void()>>([this]{ onBeforeSystemShutdown(); });
};
}
diff --git a/FreeFileSync/Source/ui/progress_indicator.cpp b/FreeFileSync/Source/ui/progress_indicator.cpp
index 89035258..118325d1 100644
--- a/FreeFileSync/Source/ui/progress_indicator.cpp
+++ b/FreeFileSync/Source/ui/progress_indicator.cpp
@@ -153,10 +153,10 @@ public:
}
bool timerIsRunning() const { return !stopWatch_.isPaused(); }
- std::chrono::milliseconds pauseAndGetTotalTime()
+ std::chrono::milliseconds pauseAndGetTotalTime()
{
- stopWatch_.pause();
- return std::chrono::duration_cast<std::chrono::milliseconds>(stopWatch_.elapsed());
+ stopWatch_.pause();
+ return std::chrono::duration_cast<std::chrono::milliseconds>(stopWatch_.elapsed());
}
private:
@@ -191,14 +191,14 @@ CompareProgressPanel::Impl::Impl(wxFrame& parentWindow) :
parentWindow_(parentWindow)
{
const wxImage& imgFile = IconBuffer::genericFileIcon(IconBuffer::SIZE_SMALL);
- m_bitmapItemStat->SetBitmap(imgFile);
+ setImage(*m_bitmapItemStat, imgFile);
const wxImage imgTime = loadImage("time", -1 /*maxWidth*/, imgFile.GetHeight());
- m_bitmapTimeStat->SetBitmap(imgTime);
+ setImage(*m_bitmapTimeStat, imgTime);
m_bitmapTimeStat->SetMinSize({-1, imgFile.GetHeight()});
- m_bitmapIgnoreErrors->SetBitmap(loadImage("error_ignore_active"));
- m_bitmapRetryErrors ->SetBitmap(loadImage("error_retry"));
+ setImage(*m_bitmapIgnoreErrors, loadImage("error_ignore_active"));
+ setImage(*m_bitmapRetryErrors, loadImage("error_retry"));
//make sure that standard height matches ProcessPhase::comparingContent statistics layout (== largest)
@@ -250,7 +250,7 @@ void CompareProgressPanel::Impl::init(const Statistics& syncStat, bool ignoreErr
void CompareProgressPanel::Impl::teardown()
{
-assert(stopWatch_.isPaused()); //why wasn't pauseAndGetTotalTime() called?
+ assert(stopWatch_.isPaused()); //why wasn't pauseAndGetTotalTime() called?
syncStat_ = nullptr;
parentWindow_.SetTitle(parentTitleBackup_);
@@ -410,7 +410,7 @@ CompareProgressPanel::CompareProgressPanel(wxFrame& parentWindow) : pimpl_(new I
wxWindow* CompareProgressPanel::getAsWindow() { return pimpl_; }
void CompareProgressPanel::init(const Statistics& syncStat, bool ignoreErrors, size_t autoRetryCount) { pimpl_->init(syncStat, ignoreErrors, autoRetryCount); }
void CompareProgressPanel::teardown() { pimpl_->teardown(); }
-void CompareProgressPanel::initNewPhase(){ pimpl_->initNewPhase(); }
+void CompareProgressPanel::initNewPhase() { pimpl_->initNewPhase(); }
void CompareProgressPanel::updateGui() { pimpl_->updateProgressGui(); }
bool CompareProgressPanel::getOptionIgnoreErrors() const { return pimpl_->getOptionIgnoreErrors(); }
void CompareProgressPanel::setOptionIgnoreErrors(bool ignoreErrors) { pimpl_->setOptionIgnoreErrors(ignoreErrors); }
@@ -684,10 +684,10 @@ public:
bool timerIsRunning() const override { return !stopWatch_.isPaused(); }
- std::chrono::milliseconds pauseAndGetTotalTime() override
+ std::chrono::milliseconds pauseAndGetTotalTime() override
{
- stopWatch_.pause();
- return std::chrono::duration_cast<std::chrono::milliseconds>(stopWatch_.elapsed());
+ stopWatch_.pause();
+ return std::chrono::duration_cast<std::chrono::milliseconds>(stopWatch_.elapsed());
}
private:
@@ -835,17 +835,17 @@ dlgSizeBuf_(dlgSize)
//set std order after button visibility was set
setStandardButtonLayout(*pnl_.bSizerStdButtons, StdButtons().setAffirmative(pnl_.m_buttonPause).setCancel(pnl_.m_buttonStop));
- pnl_.m_bpButtonMinimizeToTray->SetBitmapLabel(loadImage("minimize_to_tray"));
+ setImage(*pnl_.m_bpButtonMinimizeToTray, loadImage("minimize_to_tray"));
const wxImage& imgFile = IconBuffer::genericFileIcon(IconBuffer::SIZE_SMALL);
- pnl_.m_bitmapItemStat->SetBitmap(imgFile);
+ setImage(*pnl_.m_bitmapItemStat, imgFile);
const wxImage imgTime = loadImage("time", -1 /*maxWidth*/, imgFile.GetHeight());
- pnl_.m_bitmapTimeStat->SetBitmap(imgTime);
+ setImage(*pnl_.m_bitmapTimeStat, imgTime);
pnl_.m_bitmapTimeStat->SetMinSize({-1, imgFile.GetHeight()});
- pnl_.m_bitmapIgnoreErrors->SetBitmap(loadImage("error_ignore_active"));
- pnl_.m_bitmapRetryErrors ->SetBitmap(loadImage("error_retry"));
+ setImage(*pnl_.m_bitmapIgnoreErrors, loadImage("error_ignore_active"));
+ setImage(*pnl_.m_bitmapRetryErrors, loadImage("error_retry"));
//init graph
const int xLabelHeight = this->GetCharHeight() + fastFromDIP(2) /*margin*/; //use same height for both graphs to make sure they stretch evenly
@@ -882,6 +882,7 @@ dlgSizeBuf_(dlgSize)
wxMemoryDC dc(bmpSquare);
drawInsetRectangle(dc, wxRect(bmpSquare.GetSize()), fastFromDIP(1), borderCol, fillCol);
}
+ bmpSquare.SetScaleFactor(static_cast<double>(getDPI()) / defaultDpi);
return bmpSquare;
};
pnl_.m_bitmapGraphKeyBytes->SetBitmap(generateSquareBitmap(getColorBytes(), getColorBytesRim()));
@@ -1273,7 +1274,7 @@ void SyncProgressDialogImpl<TopLevelDialog>::updateStaticGui() //depends on "syn
assert(false);
return wxNullImage;
}();
- pnl_.m_bitmapStatus->SetBitmap(statusImage);
+ setImage(*pnl_.m_bitmapStatus, statusImage);
//show status on Windows 7 taskbar
if (taskbar_.get())
@@ -1373,7 +1374,7 @@ void SyncProgressDialogImpl<TopLevelDialog>::showSummary(SyncResult syncResult,
assert(false);
return wxNullImage;
}();
- pnl_.m_bitmapStatus->SetBitmap(statusImage);
+ setImage(*pnl_.m_bitmapStatus, statusImage);
pnl_.m_staticTextPhase->SetLabelText(getSyncResultLabel(syncResult));
//pnl_.m_bitmapStatus->SetToolTip(); -> redundant
@@ -1542,7 +1543,7 @@ template <class TopLevelDialog>
void SyncProgressDialogImpl<TopLevelDialog>::onClose(wxCloseEvent& event)
{
assert(event.CanVeto()); //this better be true: if "this" is parent of a modal error dialog, there is NO way (in hell) we allow destruction here!!!
- //note: close cannot be prevented on Windows during system shutdown! => already handled via Application::onQueryEndSession()
+ //wxEVT_END_SESSION is already handled by application.cpp::onSystemShutdown()!
event.Veto();
closePressed_ = true; //"temporary" auto-close: preempt closing results dialog
diff --git a/FreeFileSync/Source/ui/progress_indicator.h b/FreeFileSync/Source/ui/progress_indicator.h
index 82870296..08548b44 100644
--- a/FreeFileSync/Source/ui/progress_indicator.h
+++ b/FreeFileSync/Source/ui/progress_indicator.h
@@ -69,10 +69,10 @@ struct SyncProgressDialog
bool ignoreErrors,
size_t autoRetryCount,
PostSyncAction2 postSyncAction);
- struct Result
+ struct Result
{
- bool autoCloseDialog;
- wxSize dlgSize;
+ bool autoCloseDialog;
+ wxSize dlgSize;
bool dlgIsMaximized;
};
virtual Result destroy(bool autoClose, bool restoreParentFrame, SyncResult syncResult, const zen::SharedRef<const zen::ErrorLog>& log) = 0;
diff --git a/FreeFileSync/Source/ui/small_dlgs.cpp b/FreeFileSync/Source/ui/small_dlgs.cpp
index 722d245b..87b3c221 100644
--- a/FreeFileSync/Source/ui/small_dlgs.cpp
+++ b/FreeFileSync/Source/ui/small_dlgs.cpp
@@ -18,6 +18,7 @@
#include <wx/filedlg.h>
#include <wx/clipbrd.h>
#include <wx/sound.h>
+//#include <wx+/bitmap_button.h>
#include <wx+/choice_enum.h>
#include <wx+/bitmap_button.h>
#include <wx+/rtl.h>
@@ -78,8 +79,8 @@ AboutDlg::AboutDlg(wxWindow* parent) : AboutDlgGenerated(parent)
assert(m_buttonClose->GetId() == wxID_OK); //we cannot use wxID_CLOSE else Esc key won't work: yet another wxWidgets bug??
- m_bitmapLogo ->SetBitmap(loadImage("logo"));
- m_bitmapLogoLeft->SetBitmap(loadImage("logo-left"));
+ setImage(*m_bitmapLogo, loadImage("logo"));
+ setImage(*m_bitmapLogoLeft, loadImage("logo-left"));
//------------------------------------
@@ -102,7 +103,7 @@ AboutDlg::AboutDlg(wxWindow* parent) : AboutDlgGenerated(parent)
//------------------------------------
{
m_panelThankYou->Hide();
- m_bitmapDonate->SetBitmap(loadImage("ffs_heart"));
+ setImage(*m_bitmapDonate, loadImage("ffs_heart"));
setRelativeFontSize(*m_staticTextDonate, 1.25);
setRelativeFontSize(*m_buttonDonate, 1.25);
}
@@ -111,14 +112,14 @@ AboutDlg::AboutDlg(wxWindow* parent) : AboutDlgGenerated(parent)
wxImage forumImage = stackImages(loadImage("ffs_forum"),
createImageFromText(L"FreeFileSync Forum", *wxNORMAL_FONT, m_bpButtonForum->GetForegroundColour()),
ImageStackLayout::vertical, ImageStackAlignment::center, fastFromDIP(5));
- m_bpButtonForum->SetBitmapLabel(forumImage);
+ setImage(*m_bpButtonForum, forumImage);
setBitmapTextLabel(*m_bpButtonHomepage, loadImage("ffs_homepage"), L"FreeFileSync.org");
setBitmapTextLabel(*m_bpButtonEmail, loadImage("ffs_email" ), L"zenju@" L"freefilesync.org");
m_bpButtonEmail->SetToolTip(L"mailto:zenju@" L"freefilesync.org");
//------------------------------------
- m_bpButtonGpl->SetBitmapLabel(loadImage("gpl"));
+ setImage(*m_bpButtonGpl, loadImage("gpl"));
//have the GPL text wrap to two lines:
wxMemoryDC dc;
@@ -189,7 +190,7 @@ private:
void onGdriveUserAdd (wxCommandEvent& event) override;
void onGdriveUserRemove(wxCommandEvent& event) override;
void onGdriveUserSelect(wxCommandEvent& event) override;
- void gdriveUpdateDrivesAndSelect(const std::string& accountEmail, const Zstring& sharedDriveName);
+ void gdriveUpdateDrivesAndSelect(const std::string& accountEmail, const Zstring& locationToSelect);
void onDetectServerChannelLimit(wxCommandEvent& event) override;
void onToggleShowPassword(wxCommandEvent& event) override;
@@ -248,7 +249,7 @@ CloudSetupDlg::CloudSetupDlg(wxWindow* parent, Zstring& folderPathPhrase, Zstrin
{
setStandardButtonLayout(*bSizerStdButtons, StdButtons().setAffirmative(m_buttonOkay).setCancel(m_buttonCancel));
- m_toggleBtnGdrive->SetBitmap(loadImage("google_drive"));
+ setImage(*m_toggleBtnGdrive, loadImage("google_drive"));
setRelativeFontSize(*m_toggleBtnGdrive, 1.25);
setRelativeFontSize(*m_toggleBtnSftp, 1.25);
@@ -257,12 +258,12 @@ CloudSetupDlg::CloudSetupDlg(wxWindow* parent, Zstring& folderPathPhrase, Zstrin
setBitmapTextLabel(*m_buttonGdriveAddUser, loadImage("user_add", fastFromDIP(20)), m_buttonGdriveAddUser ->GetLabelText());
setBitmapTextLabel(*m_buttonGdriveRemoveUser, loadImage("user_remove", fastFromDIP(20)), m_buttonGdriveRemoveUser->GetLabelText());
- m_bitmapGdriveUser ->SetBitmap(loadImage("user", fastFromDIP(20)));
- m_bitmapGdriveDrive->SetBitmap(loadImage("drive", fastFromDIP(20)));
- m_bitmapServer ->SetBitmap(loadImage("server", fastFromDIP(20)));
- m_bitmapCloud ->SetBitmap(loadImage("cloud"));
- m_bitmapPerf ->SetBitmap(loadImage("speed"));
- m_bitmapServerDir->SetBitmap(IconBuffer::genericDirIcon(IconBuffer::SIZE_SMALL));
+ setImage(*m_bitmapGdriveUser, loadImage("user", fastFromDIP(20)));
+ setImage(*m_bitmapGdriveDrive, loadImage("drive", fastFromDIP(20)));
+ setImage(*m_bitmapServer, loadImage("server", fastFromDIP(20)));
+ setImage(*m_bitmapCloud, loadImage("cloud"));
+ setImage(*m_bitmapPerf, loadImage("speed"));
+ setImage(*m_bitmapServerDir, IconBuffer::genericDirIcon(IconBuffer::SIZE_SMALL));
m_checkBoxShowPassword->SetValue(false);
m_textCtrlServer->SetHint(_("Example:") + L" website.com 66.198.240.22");
@@ -316,7 +317,7 @@ CloudSetupDlg::CloudSetupDlg(wxWindow* parent, Zstring& folderPathPhrase, Zstrin
{
m_listBoxGdriveUsers->EnsureVisible(selPos);
m_listBoxGdriveUsers->SetSelection(selPos);
- gdriveUpdateDrivesAndSelect(login.email, login.sharedDriveName);
+ gdriveUpdateDrivesAndSelect(login.email, login.locationName);
}
else
{
@@ -455,21 +456,21 @@ void CloudSetupDlg::onGdriveUserSelect(wxCommandEvent& event)
}
-void CloudSetupDlg::gdriveUpdateDrivesAndSelect(const std::string& accountEmail, const Zstring& sharedDriveName)
+void CloudSetupDlg::gdriveUpdateDrivesAndSelect(const std::string& accountEmail, const Zstring& locationToSelect)
{
m_listBoxGdriveDrives->Clear();
m_listBoxGdriveDrives->Append(txtLoading_);
guiQueue_.processAsync([accountEmail, timeoutSec = extractGdriveLogin(getFolderPath().afsDevice).timeoutSec]() ->
- std::variant<std::vector<Zstring /*sharedDriveName*/>, FileError>
+ std::variant<std::vector<Zstring /*locationName*/>, FileError>
{
try
{
- return gdriveListSharedDrives(accountEmail, timeoutSec); //throw FileError
+ return gdriveListLocations(accountEmail, timeoutSec); //throw FileError
}
catch (const FileError& e) { return e; }
},
- [this, accountEmail, sharedDriveName](const std::variant<std::vector<Zstring /*sharedDriveName*/>, FileError>& result)
+ [this, accountEmail, locationToSelect](std::variant<std::vector<Zstring /*locationName*/>, FileError>&& result)
{
if (const int selPos = m_listBoxGdriveUsers->GetSelection();
selPos == wxNOT_FOUND || utfTo<std::string>(m_listBoxGdriveUsers->GetString(selPos)) != accountEmail)
@@ -481,14 +482,17 @@ void CloudSetupDlg::gdriveUpdateDrivesAndSelect(const std::string& accountEmail,
showNotificationDialog(this, DialogInfoType::error, PopupDialogCfg().setDetailInstructions(e->toString()));
else
{
- m_listBoxGdriveDrives->Append(txtMyDrive_);
+ auto& locationNames = std::get<std::vector<Zstring>>(result);
+ std::sort(locationNames.begin(), locationNames.end(), LessNaturalSort());
- for (const Zstring& driveName : std::get<std::vector<Zstring>>(result))
- m_listBoxGdriveDrives->Append(utfTo<wxString>(driveName));
+ m_listBoxGdriveDrives->Append(txtMyDrive_); //sort locations, but keep "My Drive" at top
- const wxString driveNameLabel = sharedDriveName.empty() ? txtMyDrive_ : utfTo<wxString>(sharedDriveName);
+ for (const Zstring& itemLabel : locationNames)
+ m_listBoxGdriveDrives->Append(utfTo<wxString>(itemLabel));
- if (const int selPos = m_listBoxGdriveDrives->FindString(driveNameLabel, true /*caseSensitive*/);
+ const wxString labelToSelect = locationToSelect.empty() ? txtMyDrive_ : utfTo<wxString>(locationToSelect);
+
+ if (const int selPos = m_listBoxGdriveDrives->FindString(labelToSelect, true /*caseSensitive*/);
selPos != wxNOT_FOUND)
{
m_listBoxGdriveDrives->EnsureVisible(selPos);
@@ -603,9 +607,9 @@ void CloudSetupDlg::updateGui()
m_radioBtnKeyfile ->SetValue(false);
m_radioBtnAgent ->SetValue(false);
- m_textCtrlPort->SetHint(numberTo<wxString>(DEFAULT_PORT_SFTP));
+ m_textCtrlPort->SetHint(numberTo<wxString>(DEFAULT_PORT_SFTP));
- switch (sftpAuthType_) //*not* owned by GUI controls
+ switch (sftpAuthType_) //*not* owned by GUI controls
{
case SftpAuthType::password:
m_radioBtnPassword->SetValue(true);
@@ -622,8 +626,8 @@ void CloudSetupDlg::updateGui()
break;
case CloudType::ftp:
- m_textCtrlPort->SetHint(numberTo<wxString>(DEFAULT_PORT_FTP));
- m_staticTextPassword->SetLabelText(_("Password:"));
+ m_textCtrlPort->SetHint(numberTo<wxString>(DEFAULT_PORT_FTP));
+ m_staticTextPassword->SetLabelText(_("Password:"));
break;
}
@@ -656,10 +660,10 @@ AbstractPath CloudSetupDlg::getFolderPath() const
if (const int selPos2 = m_listBoxGdriveDrives->GetSelection();
selPos2 != wxNOT_FOUND)
{
- if (const wxString& sharedDriveName = m_listBoxGdriveDrives->GetString(selPos2);
- sharedDriveName != txtMyDrive_ &&
- sharedDriveName != txtLoading_)
- login.sharedDriveName = utfTo<Zstring>(sharedDriveName);
+ if (const wxString& locationName = m_listBoxGdriveDrives->GetString(selPos2);
+ locationName != txtMyDrive_ &&
+ locationName != txtLoading_)
+ login.locationName = utfTo<Zstring>(locationName);
}
}
login.timeoutSec = m_spinCtrlTimeout->GetValue();
@@ -799,7 +803,7 @@ CopyToDialog::CopyToDialog(wxWindow* parent,
setMainInstructionFont(*m_staticTextHeader);
- m_bitmapCopyTo->SetBitmap(loadImage("copy_to"));
+ setImage(*m_bitmapCopyTo, loadImage("copy_to"));
targetFolder = std::make_unique<FolderSelector>(this, *this, *m_buttonSelectTargetFolder, *m_bpButtonSelectAltTargetFolder, *m_targetFolderPath,
targetFolderLastSelected, sftpKeyFileLastSelected, nullptr /*staticText*/, nullptr /*wxWindow*/, nullptr /*droppedPathsFilter*/,
@@ -958,14 +962,14 @@ void DeleteDialog::updateGui()
{
if (m_checkBoxUseRecycler->GetValue())
{
- m_bitmapDeleteType->SetBitmap(imgTrash_);
+ setImage(*m_bitmapDeleteType, imgTrash_);
m_staticTextHeader->SetLabelText(_P("Do you really want to move the following item to the recycle bin?",
"Do you really want to move the following %x items to the recycle bin?", itemCount_));
m_buttonOK->SetLabelText(_("Move")); //no access key needed: use ENTER!
}
else
{
- m_bitmapDeleteType->SetBitmap(loadImage("delete_permanently"));
+ setImage(*m_bitmapDeleteType, loadImage("delete_permanently"));
m_staticTextHeader->SetLabelText(_P("Do you really want to delete the following item?",
"Do you really want to delete the following %x items?", itemCount_));
m_buttonOK->SetLabelText(wxControl::RemoveMnemonics(_("&Delete"))); //no access key needed: use ENTER!
@@ -1038,7 +1042,7 @@ SyncConfirmationDlg::SyncConfirmationDlg(wxWindow* parent,
setStandardButtonLayout(*bSizerStdButtons, StdButtons().setAffirmative(m_buttonStartSync).setCancel(m_buttonCancel));
setMainInstructionFont(*m_staticTextCaption);
- m_bitmapSync->SetBitmap(loadImage(syncSelection ? "start_sync_selection" : "start_sync"));
+ setImage(*m_bitmapSync, loadImage(syncSelection ? "start_sync_selection" : "start_sync"));
m_staticTextCaption->SetLabelText(syncSelection ?_("Start to synchronize the selection?") : _("Start synchronization now?"));
m_staticTextSyncVar->SetLabelText(getVariantName(syncVar));
@@ -1055,7 +1059,7 @@ SyncConfirmationDlg::SyncConfirmationDlg(wxWindow* parent,
//*INDENT-ON*
}
if (varImgName)
- m_bitmapSyncVar->SetBitmap(loadImage(varImgName, -1 /*maxWidth*/, getDefaultMenuIconSize()));
+ setImage(*m_bitmapSyncVar, loadImage(varImgName, -1 /*maxWidth*/, getDefaultMenuIconSize()));
m_checkBoxDontShowAgain->SetValue(dontShowAgain);
@@ -1070,7 +1074,7 @@ SyncConfirmationDlg::SyncConfirmationDlg(wxWindow* parent,
setText(txtControl, valueAsString);
- bmpControl.SetBitmap(greyScaleIfDisabled(mirrorIfRtl(loadImage(imageName)), !isZeroValue));
+ setImage(bmpControl, greyScaleIfDisabled(mirrorIfRtl(loadImage(imageName)), !isZeroValue));
};
auto setIntValue = [&setValue](wxStaticText& txtControl, int value, wxStaticBitmap& bmpControl, const char* imageName)
@@ -1192,19 +1196,19 @@ OptionsDlg::OptionsDlg(wxWindow* parent, XmlGlobalSettings& globalSettings) :
m_hyperlinkLogFolder->SetLabel(utfTo<wxString>(getLogFolderDefaultPath()));
setRelativeFontSize(*m_hyperlinkLogFolder, 1.2);
- m_bitmapSettings ->SetBitmap (loadImage("settings"));
- m_bitmapWarnings ->SetBitmap (loadImage("msg_warning", fastFromDIP(20)));
- m_bitmapLogFile ->SetBitmap (loadImage("log_file", fastFromDIP(20)));
- m_bitmapNotificationSounds->SetBitmap (loadImage("notification_sounds"));
- m_bitmapConsole ->SetBitmap (loadImage("command_line", fastFromDIP(20)));
- m_bitmapCompareDone ->SetBitmap (loadImage("compare_sicon"));
- m_bitmapSyncDone ->SetBitmap (loadImage("start_sync_sicon"));
- m_bitmapAlertPending ->SetBitmap (loadImage("msg_error", loadImage("compare_sicon").GetSize().x));
- m_bpButtonPlayCompareDone ->SetBitmapLabel(loadImage("play_sound"));
- m_bpButtonPlaySyncDone ->SetBitmapLabel(loadImage("play_sound"));
- m_bpButtonPlayAlertPending->SetBitmapLabel(loadImage("play_sound"));
- m_bpButtonAddRow ->SetBitmapLabel(loadImage("item_add"));
- m_bpButtonRemoveRow ->SetBitmapLabel(loadImage("item_remove"));
+ setImage(*m_bitmapSettings, loadImage("settings"));
+ setImage(*m_bitmapWarnings, loadImage("msg_warning", fastFromDIP(20)));
+ setImage(*m_bitmapLogFile, loadImage("log_file", fastFromDIP(20)));
+ setImage(*m_bitmapNotificationSounds, loadImage("notification_sounds"));
+ setImage(*m_bitmapConsole, loadImage("command_line", fastFromDIP(20)));
+ setImage(*m_bitmapCompareDone, loadImage("compare_sicon"));
+ setImage(*m_bitmapSyncDone, loadImage("start_sync_sicon"));
+ setImage(*m_bitmapAlertPending, loadImage("msg_error", loadImage("compare_sicon").GetSize().x));
+ setImage(*m_bpButtonPlayCompareDone, loadImage("play_sound"));
+ setImage(*m_bpButtonPlaySyncDone, loadImage("play_sound"));
+ setImage(*m_bpButtonPlayAlertPending, loadImage("play_sound"));
+ setImage(*m_bpButtonAddRow, loadImage("item_add"));
+ setImage(*m_bpButtonRemoveRow, loadImage("item_remove"));
m_staticTextAllDialogsShown->SetLabelText(L'(' + _("No dialogs hidden") + L')');
@@ -1332,7 +1336,7 @@ void OptionsDlg::playSoundWithDiagnostics(const wxString& filePath)
//::PlaySound() => NO failure indication on Windows! does not set last last error!
//wxSound::Play(..., wxSOUND_SYNC) can return false, but does not provide details!
//=> check file access manually:
- [[maybe_unused]] std::string stream = getFileContent(utfTo<Zstring>(filePath), nullptr /*notifyUnbufferedIO*/); //throw FileError
+ [[maybe_unused]] const std::string& stream = getFileContent(utfTo<Zstring>(filePath), nullptr /*notifyUnbufferedIO*/); //throw FileError
[[maybe_unused]] const bool success = wxSound::Play(filePath, wxSOUND_ASYNC);
}
@@ -1684,7 +1688,7 @@ ActivationDlg::ActivationDlg(wxWindow* parent,
m_textCtrlOfflineActivationKey->SetMinSize({fastFromDIP(260), -1});
//setMainInstructionFont(*m_staticTextMain);
- m_bitmapActivation->SetBitmap(loadImage("internet"));
+ setImage(*m_bitmapActivation, loadImage("internet"));
m_textCtrlOfflineActivationKey->ForceUpper();
setTextWithUrls(*m_richTextLastError, lastErrorMsg);
@@ -1727,7 +1731,7 @@ void ActivationDlg::onActivateOffline(wxCommandEvent& event)
if (trimCpy(manualActivationKeyOut_).empty()) //alternative: disable button? => user thinks option is not available!
{
showNotificationDialog(this, DialogInfoType::info, PopupDialogCfg().setMainInstructions(_("Please enter a key for offline activation.")));
- m_textCtrlOfflineActivationKey->SetFocus();
+ m_textCtrlOfflineActivationKey->SetFocus();
return;
}
@@ -1797,7 +1801,7 @@ DownloadProgressWindow::Impl::Impl(wxWindow* parent, int64_t fileSizeTotal) :
m_staticTextDetails->SetMinSize({fastFromDIP(550), -1});
- m_bitmapDownloading->SetBitmap(loadImage("internet"));
+ setImage(*m_bitmapDownloading, loadImage("internet"));
m_gaugeProgress->SetRange(GAUGE_FULL_RANGE);
diff --git a/FreeFileSync/Source/ui/sync_cfg.cpp b/FreeFileSync/Source/ui/sync_cfg.cpp
index 83ce130c..3eb0b187 100644
--- a/FreeFileSync/Source/ui/sync_cfg.cpp
+++ b/FreeFileSync/Source/ui/sync_cfg.cpp
@@ -401,7 +401,7 @@ showMultipleCfgs_(showMultipleCfgs)
m_staticTextCompVarDescription->SetMinSize({fastFromDIP(CFG_DESCRIPTION_WIDTH_DIP), -1});
m_scrolledWindowPerf->SetMinSize({fastFromDIP(220), -1});
- m_bitmapPerf->SetBitmap(greyScaleIfDisabled(loadImage("speed"), enableExtraFeatures_));
+ setImage(*m_bitmapPerf, greyScaleIfDisabled(loadImage("speed"), enableExtraFeatures_));
const int scrollDelta = GetCharHeight();
m_scrolledWindowPerf->SetScrollRate(scrollDelta, scrollDelta);
@@ -424,7 +424,7 @@ showMultipleCfgs_(showMultipleCfgs)
m_staticTextFilterDescr->Wrap(fastFromDIP(450));
- m_bpButtonDefaultContext->SetBitmapLabel(mirrorIfRtl(loadImage("button_arrow_right")));
+ setImage(*m_bpButtonDefaultContext, mirrorIfRtl(loadImage("button_arrow_right")));
enumTimeDescr_.
add(UnitTime::none, L'(' + _("None") + L')'). //meta options should be enclosed in parentheses
@@ -446,12 +446,12 @@ showMultipleCfgs_(showMultipleCfgs)
m_buttonUpdate->SetToolTip(getSyncVariantDescription(SyncVariant::update));
m_buttonCustom->SetToolTip(getSyncVariantDescription(SyncVariant::custom));
- m_bitmapLeftOnly ->SetBitmap(mirrorIfRtl(greyScale(loadImage("cat_left_only" ))));
- m_bitmapRightOnly ->SetBitmap(mirrorIfRtl(greyScale(loadImage("cat_right_only" ))));
- m_bitmapLeftNewer ->SetBitmap(mirrorIfRtl(greyScale(loadImage("cat_left_newer" ))));
- m_bitmapRightNewer->SetBitmap(mirrorIfRtl(greyScale(loadImage("cat_right_newer"))));
- m_bitmapDifferent ->SetBitmap(mirrorIfRtl(greyScale(loadImage("cat_different" ))));
- m_bitmapConflict ->SetBitmap(mirrorIfRtl(greyScale(loadImage("cat_conflict" ))));
+ setImage(*m_bitmapLeftOnly, mirrorIfRtl(greyScale(loadImage("cat_left_only" ))));
+ setImage(*m_bitmapRightOnly, mirrorIfRtl(greyScale(loadImage("cat_right_only" ))));
+ setImage(*m_bitmapLeftNewer, mirrorIfRtl(greyScale(loadImage("cat_left_newer" ))));
+ setImage(*m_bitmapRightNewer, mirrorIfRtl(greyScale(loadImage("cat_right_newer"))));
+ setImage(*m_bitmapDifferent, mirrorIfRtl(greyScale(loadImage("cat_different" ))));
+ setImage(*m_bitmapConflict, mirrorIfRtl(greyScale(loadImage("cat_conflict" ))));
setRelativeFontSize(*m_buttonTwoWay, 1.25);
setRelativeFontSize(*m_buttonMirror, 1.25);
@@ -745,13 +745,13 @@ void ConfigDialog::updateCompGui()
{
case CompareVariant::timeSize:
//help wxWidgets a little to render inactive config state (needed on Windows, NOT on Linux!)
- m_bitmapCompVariant->SetBitmap(greyScaleIfDisabled(loadImage("cmp_time"), compOptionsEnabled));
+ setImage(*m_bitmapCompVariant, greyScaleIfDisabled(loadImage("cmp_time"), compOptionsEnabled));
break;
case CompareVariant::content:
- m_bitmapCompVariant->SetBitmap(greyScaleIfDisabled(loadImage("cmp_content"), compOptionsEnabled));
+ setImage(*m_bitmapCompVariant, greyScaleIfDisabled(loadImage("cmp_content"), compOptionsEnabled));
break;
case CompareVariant::size:
- m_bitmapCompVariant->SetBitmap(greyScaleIfDisabled(loadImage("cmp_size"), compOptionsEnabled));
+ setImage(*m_bitmapCompVariant, greyScaleIfDisabled(loadImage("cmp_size"), compOptionsEnabled));
break;
}
@@ -832,10 +832,10 @@ void ConfigDialog::updateFilterGui()
m_notebook->SetPageImage(static_cast<size_t>(SyncConfigPanel::filter),
static_cast<int>(!isNullFilter(activeCfg) ? ConfigTypeImage::filter: ConfigTypeImage::filterGrey));
- m_bitmapInclude ->SetBitmap(greyScaleIfDisabled(loadImage("filter_include"), !NameFilter::isNull(activeCfg.includeFilter, FilterConfig().excludeFilter)));
- m_bitmapExclude ->SetBitmap(greyScaleIfDisabled(loadImage("filter_exclude"), !NameFilter::isNull(FilterConfig().includeFilter, activeCfg.excludeFilter)));
- m_bitmapFilterDate->SetBitmap(greyScaleIfDisabled(loadImage("cmp_time"), activeCfg.unitTimeSpan != UnitTime::none));
- m_bitmapFilterSize->SetBitmap(greyScaleIfDisabled(loadImage("cmp_size"), activeCfg.unitSizeMin != UnitSize::none || activeCfg.unitSizeMax != UnitSize::none));
+ setImage(*m_bitmapInclude, greyScaleIfDisabled(loadImage("filter_include"), !NameFilter::isNull(activeCfg.includeFilter, FilterConfig().excludeFilter)));
+ setImage(*m_bitmapExclude, greyScaleIfDisabled(loadImage("filter_exclude"), !NameFilter::isNull(FilterConfig().includeFilter, activeCfg.excludeFilter)));
+ setImage(*m_bitmapFilterDate, greyScaleIfDisabled(loadImage("cmp_time"), activeCfg.unitTimeSpan != UnitTime::none));
+ setImage(*m_bitmapFilterSize, greyScaleIfDisabled(loadImage("cmp_size"), activeCfg.unitSizeMin != UnitSize::none || activeCfg.unitSizeMax != UnitSize::none));
m_spinCtrlTimespan->Enable(activeCfg.unitTimeSpan == UnitTime::lastDays);
m_spinCtrlMinSize ->Enable(activeCfg.unitSizeMin != UnitSize::none);
@@ -1015,8 +1015,8 @@ void updateSyncDirectionIcons(const SyncDirectionConfig& directionCfg,
break;
}
wxImage img = mirrorIfRtl(loadImage(imgName));
- button.SetBitmapLabel(img);
- button.SetBitmapDisabled(greyScale(img)); //fix wxWidgets' all-too-clever multi-state!
+ button.SetBitmapLabel (toBitmapBundle( img));
+ button.SetBitmapDisabled(toBitmapBundle(greyScale(img))); //fix wxWidgets' all-too-clever multi-state!
//=> the disabled bitmap is generated during first SetBitmapLabel() call but never updated again by wxWidgets!
};
@@ -1107,7 +1107,7 @@ void ConfigDialog::updateSyncGui()
bSizerSyncDirections->Show(directionCfg_.var != SyncVariant::twoWay);
if (directionCfg_.var == SyncVariant::twoWay)
- m_bitmapDatabase->SetBitmap(greyScaleIfDisabled(loadImage("database"), syncOptionsEnabled));
+ setImage(*m_bitmapDatabase, greyScaleIfDisabled(loadImage("database"), syncOptionsEnabled));
else
{
const CompareVariant activeCmpVar = m_checkBoxUseLocalCmpOptions->GetValue() ? localCmpVar_ : globalPairCfg_.cmpCfg.compareVar;
@@ -1145,16 +1145,16 @@ void ConfigDialog::updateSyncGui()
try { imgTrash = extractWxImage(fff::getTrashIcon(imgTrash.GetHeight())); /*throw SysError*/ }
catch (SysError&) { assert(false); }
- m_bitmapDeletionType->SetBitmap(greyScaleIfDisabled(imgTrash, syncOptionsEnabled));
+ setImage(*m_bitmapDeletionType, greyScaleIfDisabled(imgTrash, syncOptionsEnabled));
setText(*m_staticTextDeletionTypeDescription, _("Retain deleted and overwritten files in the recycle bin"));
}
break;
case DeletionPolicy::permanent:
- m_bitmapDeletionType->SetBitmap(greyScaleIfDisabled(loadImage("delete_permanently"), syncOptionsEnabled));
+ setImage(*m_bitmapDeletionType, greyScaleIfDisabled(loadImage("delete_permanently"), syncOptionsEnabled));
setText(*m_staticTextDeletionTypeDescription, _("Delete and overwrite files permanently"));
break;
case DeletionPolicy::versioning:
- m_bitmapVersioning->SetBitmap(greyScaleIfDisabled(loadImage("delete_versioning"), syncOptionsEnabled));
+ setImage(*m_bitmapVersioning, greyScaleIfDisabled(loadImage("delete_versioning"), syncOptionsEnabled));
break;
}
//m_staticTextDeletionTypeDescription->Wrap(fastFromDIP(200)); //needs to be reapplied after SetLabel()
@@ -1240,7 +1240,7 @@ MiscSyncConfig ConfigDialog::getMiscSyncOptions() const
++i;
}
//----------------------------------------------------------------------------
- miscCfg.ignoreErrors = m_checkBoxIgnoreErrors ->GetValue();
+ miscCfg.ignoreErrors = m_checkBoxIgnoreErrors ->GetValue();
miscCfg.autoRetryCount = m_checkBoxAutoRetry ->GetValue() ? m_spinCtrlAutoRetryCount->GetValue() : 0;
miscCfg.autoRetryDelay = std::chrono::seconds(m_spinCtrlAutoRetryDelay->GetValue());
//----------------------------------------------------------------------------
@@ -1334,15 +1334,15 @@ void ConfigDialog::updateMiscGui()
{
const MiscSyncConfig miscCfg = getMiscSyncOptions();
- m_bitmapIgnoreErrors->SetBitmap(greyScaleIfDisabled(loadImage("error_ignore_active"), miscCfg.ignoreErrors));
- m_bitmapRetryErrors ->SetBitmap(greyScaleIfDisabled(loadImage("error_retry"), miscCfg.autoRetryCount > 0 ));
+ setImage(*m_bitmapIgnoreErrors, greyScaleIfDisabled(loadImage("error_ignore_active"), miscCfg.ignoreErrors));
+ setImage(*m_bitmapRetryErrors , greyScaleIfDisabled(loadImage("error_retry"), miscCfg.autoRetryCount > 0 ));
fgSizerAutoRetry->Show(miscCfg.autoRetryCount > 0);
m_panelComparisonSettings->Layout(); //showing "retry count" can affect bSizerPerformance!
//----------------------------------------------------------------------------
const bool sendEmailEnabled = m_checkBoxSendEmail->GetValue();
- m_bitmapEmail->SetBitmap(greyScaleIfDisabled(loadImage("email"), sendEmailEnabled));
+ setImage(*m_bitmapEmail, greyScaleIfDisabled(loadImage("email"), sendEmailEnabled));
m_comboBoxEmail->Show(sendEmailEnabled);
auto updateButton = [successIcon = loadImage("msg_success", getDefaultMenuIconSize()),
@@ -1374,8 +1374,8 @@ void ConfigDialog::updateMiscGui()
label = resizeCanvas(label, {label.GetWidth() + successIcon.GetWidth(), label.GetHeight()}, wxALIGN_LEFT);
button.SetToolTip(tooltip);
- button.SetBitmapLabel(notifyCondition == emailNotifyCondition_ && sendEmailEnabled ? label : greyScale(label));
- button.SetBitmapDisabled(greyScale(label)); //fix wxWidgets' all-too-clever multi-state!
+ button.SetBitmapLabel (toBitmapBundle(notifyCondition == emailNotifyCondition_ && sendEmailEnabled ? label : greyScale(label)));
+ button.SetBitmapDisabled(toBitmapBundle(greyScale(label))); //fix wxWidgets' all-too-clever multi-state!
//=> the disabled bitmap is generated during first SetBitmapLabel() call but never updated again by wxWidgets!
}
};
@@ -1386,7 +1386,7 @@ void ConfigDialog::updateMiscGui()
m_hyperlinkPerfDeRequired2->Show(!enableExtraFeatures_); //required after each bSizerSyncMisc->Show()
//----------------------------------------------------------------------------
- m_bitmapLogFile->SetBitmap(greyScaleIfDisabled(loadImage("log_file", fastFromDIP(20)), m_checkBoxOverrideLogPath->GetValue()));
+ setImage(*m_bitmapLogFile, greyScaleIfDisabled(loadImage("log_file", fastFromDIP(20)), m_checkBoxOverrideLogPath->GetValue()));
m_logFolderPath ->Enable(m_checkBoxOverrideLogPath->GetValue()); //
m_buttonSelectLogFolder ->Show(m_checkBoxOverrideLogPath->GetValue()); //enabled status can't be derived from resolved config!
m_bpButtonSelectAltLogFolder->Show(m_checkBoxOverrideLogPath->GetValue()); //
@@ -1464,9 +1464,9 @@ void ConfigDialog::selectFolderPairConfig(int newPairIndexToShow)
}
else
{
- setCompConfig (get(localPairCfg_[selectedPairIndexToShow_].localCmpCfg ));
- setSyncConfig (get(localPairCfg_[selectedPairIndexToShow_].localSyncCfg));
- setFilterConfig(localPairCfg_[selectedPairIndexToShow_].localFilter);
+ setCompConfig(get(localPairCfg_[selectedPairIndexToShow_].localCmpCfg ));
+ setSyncConfig(get(localPairCfg_[selectedPairIndexToShow_].localSyncCfg));
+ setFilterConfig (localPairCfg_[selectedPairIndexToShow_].localFilter);
}
}
diff --git a/FreeFileSync/Source/version/version.h b/FreeFileSync/Source/version/version.h
index ea21104e..5139a888 100644
--- a/FreeFileSync/Source/version/version.h
+++ b/FreeFileSync/Source/version/version.h
@@ -3,7 +3,7 @@
namespace fff
{
-const char ffsVersion[] = "11.18"; //internal linkage!
+const char ffsVersion[] = "11.20"; //internal linkage!
const char FFS_VERSION_SEPARATOR = '.';
}
diff --git a/License.txt b/License.txt
index 7c78f826..2cd32bd9 100644
--- a/License.txt
+++ b/License.txt
@@ -3,7 +3,7 @@ B. OpenSSL License
C. curl License
D. libssh2 License
-====================================================================
+==================================================================
A. GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
@@ -625,7 +625,7 @@ an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
-====================================================================
+==================================================================
B. OpenSSL License
@@ -804,7 +804,7 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
-====================================================================
+==================================================================
C. curl License
@@ -831,7 +831,7 @@ Except as contained in this notice, the name of a copyright holder shall not
be used in advertising or otherwise to promote the sale, use or other dealings
in this Software without prior written authorization of the copyright holder.
-====================================================================
+==================================================================
D. libssh2 License
diff --git a/wx+/bitmap_button.h b/wx+/bitmap_button.h
index a3e6b0f6..5ab5411b 100644
--- a/wx+/bitmap_button.h
+++ b/wx+/bitmap_button.h
@@ -9,6 +9,7 @@
#include <wx/bmpbuttn.h>
#include <wx/settings.h>
+#include <wx/statbmp.h>
#include "image_tools.h"
#include "dc.h"
@@ -37,7 +38,8 @@ public:
void setBitmapTextLabel(wxBitmapButton& btn, const wxImage& img, const wxString& text, int gap = fastFromDIP(5), int border = fastFromDIP(5));
//set bitmap label flicker free:
-void setImage(wxBitmapButton& button, const wxImage& bmp);
+void setImage(wxAnyButton& button, const wxImage& bmp);
+void setImage(wxStaticBitmap& staticBmp, const wxImage& img);
wxBitmap renderSelectedButton(const wxSize& sz);
wxBitmap renderPressedButton(const wxSize& sz);
@@ -68,14 +70,12 @@ void setBitmapTextLabel(wxBitmapButton& btn, const wxImage& img, const wxString&
btn.SetMinSize({imgTxt.GetWidth () + 2 * border,
std::max(imgTxt.GetHeight() + 2 * border, defaultHeight)});
- btn.SetBitmapLabel(imgTxt);
- //SetLabel() calls confuse wxBitmapButton in the disabled state and it won't show the image! workaround:
- btn.SetBitmapDisabled(imgTxt.ConvertToDisabled());
+ setImage(btn, imgTxt);
}
inline
-void setImage(wxBitmapButton& button, const wxImage& img)
+void setImage(wxAnyButton& button, const wxImage& img)
{
if (!img.IsOk())
{
@@ -84,11 +84,18 @@ void setImage(wxBitmapButton& button, const wxImage& img)
return;
}
- button.SetBitmapLabel(img);
+ button.SetBitmapLabel(toBitmapBundle(img));
//wxWidgets excels at screwing up consistently once again:
//the first call to SetBitmapLabel() *implicitly* sets the disabled bitmap, too, subsequent calls, DON'T!
- button.SetBitmapDisabled(img.ConvertToDisabled()); //inefficiency: wxBitmap::ConvertToDisabled() implicitly converts to wxImage!
+ button.SetBitmapDisabled(toBitmapBundle(img.ConvertToDisabled())); //inefficiency: wxBitmap::ConvertToDisabled() implicitly converts to wxImage!
+}
+
+
+inline
+void setImage(wxStaticBitmap& staticBmp, const wxImage& img)
+{
+ staticBmp.SetBitmap(toBitmapBundle(img));
}
diff --git a/wx+/context_menu.h b/wx+/context_menu.h
index c53cec39..728da173 100644
--- a/wx+/context_menu.h
+++ b/wx+/context_menu.h
@@ -12,6 +12,10 @@
#include <functional>
#include <wx/menu.h>
#include <wx/app.h>
+#include "dc.h"
+
+warn_static("remove after test")
+#include "image_tools.h"
/* A context menu supporting lambda callbacks!
@@ -23,6 +27,13 @@
namespace zen
{
+inline
+void setImage(wxMenuItem& menuItem, const wxImage& img)
+{
+ menuItem.SetBitmap(toBitmapBundle(img));
+}
+
+
class ContextMenu : private wxEvtHandler
{
public:
@@ -32,7 +43,7 @@ public:
{
wxMenuItem* newItem = new wxMenuItem(menu_.get(), wxID_ANY, label); //menu owns item!
if (img.IsOk())
- newItem->SetBitmap(img); //do not set AFTER appending item! wxWidgets screws up for yet another crappy reason
+ setImage(*newItem, img); //do not set AFTER appending item! wxWidgets screws up for yet another crappy reason
menu_->Append(newItem);
if (!enabled)
newItem->Enable(false); //do not enable BEFORE appending item! wxWidgets screws up for yet another crappy reason
@@ -69,7 +80,7 @@ public:
wxMenuItem* newItem = new wxMenuItem(menu_.get(), wxID_ANY, label, L"", wxITEM_NORMAL, submenu.menu_.release()); //menu owns item, item owns submenu!
if (img.IsOk())
- newItem->SetBitmap(img); //do not set AFTER appending item! wxWidgets screws up for yet another crappy reason
+ setImage(*newItem, img); //do not set AFTER appending item! wxWidgets screws up for yet another crappy reason
menu_->Append(newItem);
if (!enabled)
newItem->Enable(false);
@@ -93,6 +104,27 @@ private:
std::unique_ptr<wxMenu> menu_ = std::make_unique<wxMenu>();
std::map<int /*item id*/, std::function<void()> /*command*/> commandList_;
};
+
+
+//GTK: image must be set *before* adding wxMenuItem to menu or it won't show => workaround:
+inline //also needed on Windows + macOS since wxWidgets 3.1.6 (thanks?)
+void fixMenuIcons(wxMenu& menu)
+{
+ std::vector<std::pair<wxMenuItem*, size_t /*pos*/>> itemsWithBmp;
+ {
+ size_t pos = 0;
+ for (wxMenuItem* item : menu.GetMenuItems())
+ {
+ if (item->GetBitmap().IsOk())
+ itemsWithBmp.emplace_back(item, pos);
+ ++pos;
+ }
+ }
+
+ for (const auto& [item, pos] : itemsWithBmp)
+ if (!menu.Insert(pos, menu.Remove(item))) //detach + reinsert
+ assert(false);
+}
}
#endif //CONTEXT_MENU_H_18047302153418174632141234
diff --git a/wx+/dc.h b/wx+/dc.h
index c5b78119..d303de4c 100644
--- a/wx+/dc.h
+++ b/wx+/dc.h
@@ -12,6 +12,7 @@
#include <zen/basic_math.h>
#include <wx/dcbuffer.h> //for macro: wxALWAYS_NATIVE_DOUBLE_BUFFER
#include <wx/dcscreen.h>
+#include <wx/bmpbndl.h>
#include <gtk/gtk.h>
@@ -91,8 +92,8 @@ constexpr int defaultDpi = 96;
inline
int getDPI()
{
-#ifndef wxHAVE_DPI_INDEPENDENT_PIXELS
-#error why is wxHAVE_DPI_INDEPENDENT_PIXELS not defined?
+#ifndef wxHAS_DPI_INDEPENDENT_PIXELS
+#error why is wxHAS_DPI_INDEPENDENT_PIXELS not defined?
#endif
//GTK2 doesn't properly support high DPI: https://freefilesync.org/forum/viewtopic.php?t=6114
//=> requires general fix at wxWidgets-level
@@ -117,6 +118,16 @@ int getDpiScalePercent()
}
+inline
+wxBitmapBundle toBitmapBundle(const wxImage& img /*expected to be DPI-scaled!*/)
+{
+ //return wxBitmap(img, -1 /*depth*/, static_cast<double>(getDPI()) / defaultDpi); not (yet) implemented
+ wxBitmap bmpScaled(img);
+ bmpScaled.SetScaleFactor(static_cast<double>(getDPI()) / defaultDpi);
+ return bmpScaled;
+}
+
+
//---------------------- implementation ------------------------
class RecursiveDcClipper
{
diff --git a/wx+/image_tools.cpp b/wx+/image_tools.cpp
index b8876dc3..335cf7b9 100644
--- a/wx+/image_tools.cpp
+++ b/wx+/image_tools.cpp
@@ -7,6 +7,7 @@
#include "image_tools.h"
#include <zen/string_tools.h>
#include <zen/zstring.h>
+#include <zen/scope_guard.h>
#include <wx/app.h>
#include <xBRZ/src/xbrz_tools.h>
@@ -113,19 +114,6 @@ void copyImageLayover(const wxImage& src,
}
}
}
-
-
-std::vector<std::pair<wxString, wxSize>> getTextExtentInfo(const wxString& text, const wxFont& font)
-{
- wxMemoryDC dc; //the context used for bitmaps
- dc.SetFont(font); //the font parameter of GetMultiLineTextExtent() is not evaluated on OS X, wxWidgets 2.9.5, so apply it to the DC directly!
-
- std::vector<std::pair<wxString, wxSize>> lineInfo; //text + extent
- for (const wxString& line : split(text, L'\n', SplitOnEmpty::allow))
- lineInfo.emplace_back(line, line.empty() ? wxSize() : dc.GetTextExtent(line));
-
- return lineInfo;
-}
}
@@ -180,7 +168,13 @@ wxImage zen::stackImages(const wxImage& img1, const wxImage& img2, ImageStackLay
wxImage zen::createImageFromText(const wxString& text, const wxFont& font, const wxColor& col, ImageStackAlignment textAlign)
{
- const std::vector<std::pair<wxString, wxSize>> lineInfo = getTextExtentInfo(text, font);
+ wxMemoryDC dc; //the context used for bitmaps
+ dc.SetFont(font); //the font parameter of GetMultiLineTextExtent() is not evaluated on OS X, wxWidgets 2.9.5, so apply it to the DC directly!
+
+ std::vector<std::pair<wxString, wxSize>> lineInfo; //text + extent
+ for (const wxString& line : split(text, L'\n', SplitOnEmpty::allow))
+ lineInfo.emplace_back(line, line.empty() ? wxSize() : dc.GetTextExtent(line));
+ //------------------------------------------------------------------------------------------------
int maxWidth = 0;
int lineHeight = 0;
@@ -194,7 +188,8 @@ wxImage zen::createImageFromText(const wxString& text, const wxFont& font, const
wxBitmap newBitmap(maxWidth, lineHeight * lineInfo.size()); //seems we don't need to pass 24-bit depth here even for high-contrast color schemes
{
- wxMemoryDC dc(newBitmap);
+ dc.SelectObject(newBitmap);
+ ZEN_ON_SCOPE_EXIT(dc.SelectObject(wxNullBitmap));
if (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft)
dc.SetLayoutDirection(wxLayout_RightToLeft); //handle e.g. "weak" bidi characters: -> arrows in hebrew/arabic
@@ -204,7 +199,6 @@ wxImage zen::createImageFromText(const wxString& text, const wxFont& font, const
dc.SetTextForeground(*wxBLACK); //for proper alpha-channel calculation
dc.SetTextBackground(*wxWHITE); //
- dc.SetFont(font);
int posY = 0;
for (const auto& [lineText, lineSize] : lineInfo)
diff --git a/wx+/popup_dlg.cpp b/wx+/popup_dlg.cpp
index 703371c2..c163b037 100644
--- a/wx+/popup_dlg.cpp
+++ b/wx+/popup_dlg.cpp
@@ -10,6 +10,7 @@
#include <wx/app.h>
#include <wx/display.h>
#include <wx/sound.h>
+#include "bitmap_button.h"
#include "no_flicker.h"
#include "font_size.h"
#include "image_resources.h"
@@ -141,7 +142,7 @@ public:
titleTmp = cfg.title;
//-----------------------------------------------
if (iconTmp.IsOk())
- m_bitmapMsgType->SetBitmap(iconTmp);
+ setImage(*m_bitmapMsgType, iconTmp);
if (titleTmp.empty())
SetTitle(wxTheApp->GetAppDisplayName());
diff --git a/wx+/rtl.h b/wx+/rtl.h
index 7b1c7926..b59b5d14 100644
--- a/wx+/rtl.h
+++ b/wx+/rtl.h
@@ -72,7 +72,7 @@ void drawBitmapRtlMirror(wxDC& dc, const wxImage& img, const wxRect& rect, int a
memDc.Blit(wxPoint(0, 0), rect.GetSize(), &dc, rect.GetTopLeft()); //blit in: background is mirrored due to memDc, dc having different layout direction!
impl::drawBitmapAligned(memDc, img, wxRect(0, 0, rect.width, rect.height), alignment);
- //note: we cannot simply use memDc.SetLayoutDirection(wxLayout_RightToLeft) due to some strange 1 pixel bug!
+ //note: we cannot simply use memDc.SetLayoutDirection(wxLayout_RightToLeft) due to some strange 1 pixel bug! 2022-04-04: maybe fixed in wxWidgets 3.1.6?
dc.Blit(rect.GetTopLeft(), rect.GetSize(), &memDc, wxPoint(0, 0)); //blit out: mirror once again
}
diff --git a/wx+/std_button_layout.h b/wx+/std_button_layout.h
index e84b0c78..72756041 100644
--- a/wx+/std_button_layout.h
+++ b/wx+/std_button_layout.h
@@ -78,7 +78,7 @@ void setStandardButtonLayout(wxBoxSizer& sizer, const StdButtons& buttons)
if (wxSizerItem& item = *sizer.GetItem(pos);
item.IsSpacer() && item.GetProportion() == 0 && item.GetSize().y == 0)
{
- [[maybe_unused]] bool rv = sizer.Detach(pos);
+ [[maybe_unused]] const bool rv = sizer.Detach(pos);
assert(rv);
}
diff --git a/wx+/tooltip.cpp b/wx+/tooltip.cpp
index 4a3c571f..a3fa770d 100644
--- a/wx+/tooltip.cpp
+++ b/wx+/tooltip.cpp
@@ -3,7 +3,6 @@
// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 *
// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
// *****************************************************************************
-
#include "tooltip.h"
#include <wx/dialog.h>
#include <wx/stattext.h>
@@ -12,6 +11,7 @@
#include <wx/settings.h>
#include <wx/app.h>
#include "image_tools.h"
+#include "bitmap_button.h"
#include "dc.h"
#include <gtk/gtk.h>
@@ -64,7 +64,7 @@ void Tooltip::show(const wxString& text, wxPoint mousePos, const wxImage* img)
if (!lastUsedImg_.IsSameAs(newImg))
{
lastUsedImg_ = newImg;
- tipWindow_->bitmapLeft_->SetBitmap(newImg);
+ setImage(*tipWindow_->bitmapLeft_, newImg);
tipWindow_->Refresh(); //needed if bitmap size changed!
}
diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp
index f1b5519b..515580ae 100644
--- a/zen/file_traverser.cpp
+++ b/zen/file_traverser.cpp
@@ -8,7 +8,6 @@
#include "file_error.h"
- //#include <unistd.h> //::pathconf()
#include <sys/stat.h>
#include <dirent.h>
diff --git a/zen/globals.h b/zen/globals.h
index 6a50a497..dd0dfed9 100644
--- a/zen/globals.h
+++ b/zen/globals.h
@@ -219,6 +219,8 @@ bool PodSpinMutex::tryLock()
}
+
+
inline
void PodSpinMutex::lock()
{
diff --git a/zen/http.cpp b/zen/http.cpp
index 24c5aa73..4fe43ede 100644
--- a/zen/http.cpp
+++ b/zen/http.cpp
@@ -144,7 +144,7 @@ public:
statusCode_ = stringTo<int>(statusItems[1]);
- for (const std::string& line : split(headersBuf, "\r\n", SplitOnEmpty::skip))
+ for (const std::string& line : split(headersBuf, '\n', SplitOnEmpty::skip)) //careful: actual line separator is "\r\n"!
responseHeaders_[trimCpy(beforeFirst(line, ':', IfNotFoundReturn::all))] =
/**/ trimCpy(afterFirst (line, ':', IfNotFoundReturn::none));
@@ -344,7 +344,7 @@ bool zen::internetIsAlive() //noexcept
{
try
{
- auto response = std::make_unique<HttpInputStream::Impl>(Zstr("http://www.google.com/"),
+ auto response = std::make_unique<HttpInputStream::Impl>(Zstr("https://www.google.com/"), //https more appropriate than http for testing? (different ports!)
nullptr /*postParams*/,
"" /*contentType*/,
true /*disableGetCache*/,
@@ -353,7 +353,7 @@ bool zen::internetIsAlive() //noexcept
nullptr /*notifyUnbufferedIO*/); //throw SysError
const int statusCode = response->getStatusCode();
- //attention: http://www.google.com/ might redirect to "https" => don't follow, just return "true"!!!
+ //attention: google.com might redirect to https://consent.google.com => don't follow, just return "true"!!!
return statusCode / 100 == 2 || //e.g. 200
statusCode / 100 == 3; //e.g. 301, 302, 303, 307... when in doubt, consider internet alive!
}
diff --git a/zen/legacy_compiler.h b/zen/legacy_compiler.h
index 50d340ca..6c9381ee 100644
--- a/zen/legacy_compiler.h
+++ b/zen/legacy_compiler.h
@@ -22,6 +22,7 @@
Clang https://clang.llvm.org/cxx_status.html#cxx20
libc++ https://libcxx.llvm.org/cxx2a_status.html */
+
namespace std
{
}
diff --git a/zen/open_ssl.cpp b/zen/open_ssl.cpp
index 5e4c370d..99d7582e 100644
--- a/zen/open_ssl.cpp
+++ b/zen/open_ssl.cpp
@@ -5,6 +5,7 @@
// *****************************************************************************
#include "open_ssl.h"
+#include <bit> //std::endian (needed for macOS)
#include "base64.h"
#include "thread.h"
#include <openssl/pem.h>
diff --git a/zen/resolve_path.cpp b/zen/resolve_path.cpp
index 17d3b777..f0a49976 100644
--- a/zen/resolve_path.cpp
+++ b/zen/resolve_path.cpp
@@ -145,7 +145,6 @@ std::optional<Zstring> tryResolveMacro(const Zstring& macro) //macro without %-c
if (std::optional<Zstring> value = getEnvironmentVar(macro))
return *value;
-
return {};
}
diff --git a/zen/shutdown.cpp b/zen/shutdown.cpp
index f46caf6b..a812d6ae 100644
--- a/zen/shutdown.cpp
+++ b/zen/shutdown.cpp
@@ -5,6 +5,7 @@
// *****************************************************************************
#include "shutdown.h"
+#include "thread.h"
#include <zen/process_exec.h>
@@ -15,6 +16,9 @@ using namespace zen;
void zen::shutdownSystem() //throw FileError
{
+ assert(runningOnMainThread());
+ if (runningOnMainThread())
+ onSystemShutdownRunTasks();
try
{
//https://linux.die.net/man/2/reboot => needs admin rights!
@@ -62,3 +66,40 @@ void zen::terminateProcess(int exitCode)
// alternative requiring admin: sudo killall Xorg
// alternative without admin: dbus-send --session --print-reply --dest=org.gnome.SessionManager /org/gnome/SessionManager org.gnome.SessionManager.Logout uint32:1
+
+
+namespace
+{
+using ShutdownTaskList = std::vector<std::weak_ptr<const std::function<void()>>>;
+constinit Global<ShutdownTaskList> globalShutdownTasks;
+GLOBAL_RUN_ONCE(globalShutdownTasks.set(std::make_unique<ShutdownTaskList>()));
+}
+
+
+void zen::onSystemShutdownRegister(const SharedRef<std::function<void()>>& task)
+{
+ assert(runningOnMainThread());
+
+ const auto& tasks = globalShutdownTasks.get();
+ assert(tasks);
+ if (tasks)
+ tasks->push_back(task.ptr());
+}
+
+
+void zen::onSystemShutdownRunTasks()
+{
+ assert(runningOnMainThread()); //no multithreading! else: after taskWeak.lock() task() references may go out of scope! (e.g. "this")
+
+ const auto& tasks = globalShutdownTasks.get();
+ assert(tasks);
+ if (tasks)
+ for (const std::weak_ptr<const std::function<void()>>& taskWeak : *tasks)
+ if (const std::shared_ptr<const std::function<void()>>& task = taskWeak.lock();
+ task)
+ try
+ { (*task)(); }
+ catch (...) { assert(false); }
+
+ globalShutdownTasks.set(nullptr); //trigger assert in onSystemShutdownRegister(), just in case...
+}
diff --git a/zen/shutdown.h b/zen/shutdown.h
index 20354a14..b4d51f69 100644
--- a/zen/shutdown.h
+++ b/zen/shutdown.h
@@ -7,6 +7,7 @@
#ifndef SHUTDOWN_H_3423847870238407783265
#define SHUTDOWN_H_3423847870238407783265
+#include <functional>
#include "file_error.h"
@@ -15,6 +16,11 @@ namespace zen
void shutdownSystem(); //throw FileError
void suspendSystem(); //
[[noreturn]] void terminateProcess(int exitCode);
+
+void onSystemShutdownRegister(const SharedRef<std::function<void()>>& task /*noexcept*/); //save important/user data!
+void onSystemShutdownRegister( SharedRef<std::function<void()>>&& task) = delete; //no temporaries! shared_ptr should manage life time!
+void onSystemShutdownRunTasks(); //call at appropriate time, e.g. when receiving wxEVT_QUERY_END_SESSION/wxEVT_END_SESSION
+//+ also called by shutdownSystem()
}
#endif //SHUTDOWN_H_3423847870238407783265
diff --git a/zen/stl_tools.h b/zen/stl_tools.h
index c2e8eff3..0d359641 100644
--- a/zen/stl_tools.h
+++ b/zen/stl_tools.h
@@ -290,13 +290,26 @@ Num hashArray(ByteIterator first, ByteIterator last)
struct StringHash //support for custom string classes with std::unordered_set/map
{
+ using is_transparent = int; //allow heterogenous lookup!
+
template <class String>
size_t operator()(const String& str) const
{
- const auto* strFirst = strBegin(str);
+ const auto* const strFirst = strBegin(str);
return hashArray<size_t>(strFirst, strFirst + strLength(str));
}
};
+
+struct StringEqual
+{
+ using is_transparent = int; //allow heterogenous lookup!
+
+ template <class String1, class String2>
+ bool operator()(const String1& lhs, const String2& rhs) const
+ {
+ return equalString(lhs, rhs);
+ }
+};
}
#endif //STL_TOOLS_H_84567184321434
diff --git a/zen/string_tools.h b/zen/string_tools.h
index 5f9273c9..ee4e5613 100644
--- a/zen/string_tools.h
+++ b/zen/string_tools.h
@@ -68,12 +68,15 @@ enum class SplitOnEmpty
allow,
skip
};
-template <class S, class T> std::vector<S> split(const S& str, const T& delimiter, SplitOnEmpty soe);
+template <class S, class Char> [[nodiscard]] std::vector<S> split(const S& str, Char delimiter, SplitOnEmpty soe);
+template <class S, class Function1, class Function2> void split2(const S& str, Function1 isDelimiter, Function2 onStringPart);
-template <class S> [[nodiscard]] S trimCpy(S str, bool fromLeft = true, bool fromRight = true);
+template <class S> [[nodiscard]] S trimCpy(S str, bool fromLeft = true, bool fromRight = true);
+template <class Char, class Function> [[nodiscard]] std::pair<Char*, Char*> trimCpy(Char* first, Char* last, bool fromLeft, bool fromRight, Function trimThisChar);
template <class S> void trim (S& str, bool fromLeft = true, bool fromRight = true);
template <class S, class Function> void trim(S& str, bool fromLeft, bool fromRight, Function trimThisChar);
+
template <class S, class T, class U> [[nodiscard]] S replaceCpy(S str, const T& oldTerm, const U& newTerm, bool replaceAll = true);
template <class S, class T, class U> void replace (S& str, const T& oldTerm, const U& newTerm, bool replaceAll = true);
@@ -210,7 +213,7 @@ template <class S, class T> inline
bool startsWith(const S& str, const T& prefix)
{
const size_t pfLen = strLength(prefix);
- return strLength(str) >= pfLen && std::is_eq(impl::strcmpWithNulls(strBegin(str), strBegin(prefix), pfLen));
+ return strLength(str) >= pfLen && impl::strcmpWithNulls(strBegin(str), strBegin(prefix), pfLen) == std::strong_ordering::equal;
}
@@ -218,7 +221,7 @@ template <class S, class T> inline
bool startsWithAsciiNoCase(const S& str, const T& prefix)
{
const size_t pfLen = strLength(prefix);
- return strLength(str) >= pfLen && std::is_eq(impl::strcmpAsciiNoCase(strBegin(str), strBegin(prefix), pfLen));
+ return strLength(str) >= pfLen && impl::strcmpAsciiNoCase(strBegin(str), strBegin(prefix), pfLen) == std::weak_ordering::equivalent;
}
@@ -227,7 +230,7 @@ bool endsWith(const S& str, const T& postfix)
{
const size_t strLen = strLength(str);
const size_t pfLen = strLength(postfix);
- return strLen >= pfLen && std::is_eq(impl::strcmpWithNulls(strBegin(str) + strLen - pfLen, strBegin(postfix), pfLen));
+ return strLen >= pfLen && impl::strcmpWithNulls(strBegin(str) + strLen - pfLen, strBegin(postfix), pfLen) == std::strong_ordering::equal;
}
@@ -236,7 +239,7 @@ bool endsWithAsciiNoCase(const S& str, const T& postfix)
{
const size_t strLen = strLength(str);
const size_t pfLen = strLength(postfix);
- return strLen >= pfLen && std::is_eq(impl::strcmpAsciiNoCase(strBegin(str) + strLen - pfLen, strBegin(postfix), pfLen));
+ return strLen >= pfLen && impl::strcmpAsciiNoCase(strBegin(str) + strLen - pfLen, strBegin(postfix), pfLen) == std::weak_ordering::equivalent;
}
@@ -244,7 +247,7 @@ template <class S, class T> inline
bool equalString(const S& lhs, const T& rhs)
{
const size_t lhsLen = strLength(lhs);
- return lhsLen == strLength(rhs) && std::is_eq(impl::strcmpWithNulls(strBegin(lhs), strBegin(rhs), lhsLen));
+ return lhsLen == strLength(rhs) && impl::strcmpWithNulls(strBegin(lhs), strBegin(rhs), lhsLen) == std::strong_ordering::equal;
}
@@ -252,7 +255,7 @@ template <class S, class T> inline
bool equalAsciiNoCase(const S& lhs, const T& rhs)
{
const size_t lhsLen = strLength(lhs);
- return lhsLen == strLength(rhs) && std::is_eq(impl::strcmpAsciiNoCase(strBegin(lhs), strBegin(rhs), lhsLen));
+ return lhsLen == strLength(rhs) && impl::strcmpAsciiNoCase(strBegin(lhs), strBegin(rhs), lhsLen) == std::weak_ordering::equivalent;
}
@@ -265,7 +268,7 @@ std::strong_ordering compareString(const S& lhs, const T& rhs)
//length check *after* strcmpWithNulls(): we DO care about natural ordering: e.g. for "compareString(getUpperCase(lhs), getUpperCase(rhs))"
if (const std::strong_ordering cmp = impl::strcmpWithNulls(strBegin(lhs), strBegin(rhs), std::min(lhsLen, rhsLen));
- std::is_neq(cmp))
+ cmp != std::strong_ordering::equal)
return cmp;
return lhsLen <=> rhsLen;
}
@@ -279,7 +282,7 @@ std::weak_ordering compareAsciiNoCase(const S& lhs, const T& rhs)
const size_t rhsLen = strLength(rhs);
if (const std::weak_ordering cmp = impl::strcmpAsciiNoCase(strBegin(lhs), strBegin(rhs), std::min(lhsLen, rhsLen));
- std::is_neq(cmp))
+ cmp != std::weak_ordering::equivalent)
return cmp;
return lhsLen <=> rhsLen;
}
@@ -385,37 +388,37 @@ S beforeFirst(const S& str, const T& term, IfNotFoundReturn infr)
}
-template <class S, class T> inline
-std::vector<S> split(const S& str, const T& delimiter, SplitOnEmpty soe)
+template <class S, class Function1, class Function2> inline
+void split2(const S& str, Function1 isDelimiter, Function2 onStringPart)
{
- static_assert(std::is_same_v<GetCharTypeT<S>, GetCharTypeT<T>>);
- const size_t delimLen = strLength(delimiter);
- assert(delimLen > 0);
- if (delimLen == 0)
+ const auto* blockFirst = strBegin(str);
+ const auto* const strEnd = blockFirst + strLength(str);
+
+ for (;;)
{
- if (str.empty() && soe == SplitOnEmpty::skip)
- return {};
- return {str};
- }
+ const auto* const blockLast = std::find_if(blockFirst, strEnd, isDelimiter);
+ onStringPart(blockFirst, blockLast);
+
+ if (blockLast == strEnd)
+ return;
- const auto* const delimFirst = strBegin(delimiter);
- const auto* const delimLast = delimFirst + delimLen;
+ blockFirst = blockLast + 1;
+ }
+}
- const auto* blockStart = strBegin(str);
- const auto* const strLast = blockStart + strLength(str);
+template <class S, class Char> inline
+std::vector<S> split(const S& str, Char delimiter, SplitOnEmpty soe)
+{
+ static_assert(std::is_same_v<GetCharTypeT<S>, Char>);
std::vector<S> output;
- for (;;)
+
+ split2(str, [delimiter](Char c) { return c == delimiter; }, [&](const Char* blockFirst, const Char* blockLast)
{
- const auto* const blockEnd = std::search(blockStart, strLast,
- delimFirst, delimLast);
- if (blockStart != blockEnd || soe == SplitOnEmpty::allow)
- output.emplace_back(blockStart, blockEnd - blockStart);
-
- if (blockEnd == strLast)
- return output;
- blockStart = blockEnd + delimLen;
- }
+ if (blockFirst != blockLast || soe == SplitOnEmpty::allow)
+ output.emplace_back(blockFirst, blockLast);
+ });
+ return output;
}
@@ -484,25 +487,33 @@ void replace(S& str, const T& oldTerm, const U& newTerm, bool replaceAll)
}
-template <class S, class Function> inline
-void trim(S& str, bool fromLeft, bool fromRight, Function trimThisChar)
+template <class Char, class Function> inline
+std::pair<Char*, Char*> trimCpy(Char* first, Char* last, bool fromLeft, bool fromRight, Function trimThisChar)
{
assert(fromLeft || fromRight);
- const auto* const oldBegin = strBegin(str);
- const auto* newBegin = oldBegin;
- const auto* newEnd = oldBegin + strLength(str);
-
if (fromRight)
- while (newBegin != newEnd && trimThisChar(newEnd[-1]))
- --newEnd;
+ while (first != last && trimThisChar(last[-1]))
+ --last;
if (fromLeft)
- while (newBegin != newEnd && trimThisChar(*newBegin))
- ++newBegin;
+ while (first != last && trimThisChar(*first))
+ ++first;
+
+ return {first, last};
+}
+
+
+template <class S, class Function> inline
+void trim(S& str, bool fromLeft, bool fromRight, Function trimThisChar)
+{
+ assert(fromLeft || fromRight);
+
+ const auto* const oldBegin = strBegin(str);
+ const auto& [newBegin, newEnd] = trimCpy(oldBegin, oldBegin + strLength(str), fromLeft, fromRight, trimThisChar);
if (newBegin != oldBegin)
- str = S(newBegin, newEnd - newBegin); //minor inefficiency: in case "str" is not shared, we could save an allocation and do a memory move only
+ str = S(newBegin, newEnd); //minor inefficiency: in case "str" is not shared, we could save an allocation and do a memory move only
else
str.resize(newEnd - newBegin);
}
@@ -613,8 +624,10 @@ S numberTo(const Num& number, std::integral_constant<NumberType, NumberType::flo
const char* strEnd = toChars(std::begin(buffer), std::end(buffer), number);
S output;
- std::for_each(static_cast<const char*>(buffer), strEnd,
- [&](char c) { output += static_cast<GetCharTypeT<S>>(c); });
+
+ for (const char c : makeStringView(static_cast<const char*>(buffer), strEnd))
+ output += static_cast<GetCharTypeT<S>>(c);
+
return output;
}
@@ -716,8 +729,10 @@ double stringToFloat(const char* first, const char* last)
inline
double stringToFloat(const wchar_t* first, const wchar_t* last)
{
- std::string buf(last - first, '\0');
- std::transform(first, last, buf.begin(), [](wchar_t c) { return static_cast<char>(c); });
+ std::string buf; //let's rely on SSO
+
+ for (const wchar_t c : makeStringView(first, last))
+ buf += static_cast<char>(c);
return fromChars(buf.c_str(), buf.c_str() + buf.size());
}
@@ -756,17 +771,16 @@ Num extractInteger(const S& str, bool& hasMinusSign) //very fast conversion to i
}
Num number = 0;
- for (const CharType* it = first; it != last; ++it)
- {
- const CharType c = *it;
+
+ for (const CharType c : makeStringView(first, last))
if (static_cast<CharType>('0') <= c && c <= static_cast<CharType>('9'))
{
number *= 10;
number += c - static_cast<CharType>('0');
}
else //rest of string should contain whitespace only, it's NOT a bug if there is something else!
- break; //assert(std::all_of(iter, last, isWhiteSpace<CharType>)); -> this is NO assert situation
- }
+ break; //assert(std::all_of(it, last, isWhiteSpace<CharType>)); -> this is NO assert situation
+
return number;
}
diff --git a/zen/sys_info.cpp b/zen/sys_info.cpp
index cc852510..d208cc98 100644
--- a/zen/sys_info.cpp
+++ b/zen/sys_info.cpp
@@ -26,6 +26,14 @@ using namespace zen;
Zstring zen::getLoginUser() //throw FileError
{
+ auto tryGetNonRootUser = [](const char* varName) -> const char*
+ {
+ if (const char* buf = ::getenv(varName)) //no extended error reporting
+ if (strLength(buf) > 0 && !equalString(buf, "root"))
+ return buf;
+ return nullptr;
+ };
+
const uid_t userIdNo = ::getuid(); //never fails
if (userIdNo != 0) //nofail; non-root
@@ -54,21 +62,15 @@ Zstring zen::getLoginUser() //throw FileError
return loginUser;
//BUT: getlogin() can fail with ENOENT on Linux Mint: https://freefilesync.org/forum/viewtopic.php?t=8181
- auto tryGetNonRootUser = [](const char* varName) -> const char*
- {
- if (const char* buf = ::getenv(varName)) //no extended error reporting
- if (strLength(buf) > 0 && !equalString(buf, "root"))
- return buf;
- return nullptr;
- };
//getting a little desperate: variables used by installer.sh
if (const char* userName = tryGetNonRootUser("USER")) return userName;
if (const char* userName = tryGetNonRootUser("SUDO_USER")) return userName;
if (const char* userName = tryGetNonRootUser("LOGNAME")) return userName;
+
//apparently the current user really IS root: https://freefilesync.org/forum/viewtopic.php?t=8405
+ assert(getuid() == 0);
return "root";
-
}
diff --git a/zen/zstring.cpp b/zen/zstring.cpp
index 34d52b2c..635fb47d 100644
--- a/zen/zstring.cpp
+++ b/zen/zstring.cpp
@@ -240,7 +240,7 @@ std::weak_ordering compareNatural(const Zstring& lhs, const Zstring& rhs)
while (strR != strEndR && !isWhiteSpace(*strR) && !isDigit(*strR)) ++strR;
if (const std::weak_ordering cmp = compareNoCaseUtf8(textBeginL, strL - textBeginL, textBeginR, strR - textBeginR);
- std::is_neq(cmp))
+ cmp != std::weak_ordering::equivalent)
return cmp;
}
diff --git a/zen/zstring.h b/zen/zstring.h
index b8dfb9a3..15735cb0 100644
--- a/zen/zstring.h
+++ b/zen/zstring.h
@@ -61,7 +61,7 @@ struct ZstringNoCase //use as STL container key: avoid needless upper-case conve
macOS: ignore case + Unicode normalization forms */
std::weak_ordering compareNativePath(const Zstring& lhs, const Zstring& rhs);
-inline bool equalNativePath(const Zstring& lhs, const Zstring& rhs) { return std::is_eq(compareNativePath(lhs, rhs)); }
+inline bool equalNativePath(const Zstring& lhs, const Zstring& rhs) { return compareNativePath(lhs, rhs) == std::weak_ordering::equivalent; }
struct LessNativePath { bool operator()(const Zstring& lhs, const Zstring& rhs) const { return std::is_lt(compareNativePath(lhs, rhs)); } };
bgstack15