diff options
author | B. Stack <bgstack15@gmail.com> | 2022-04-18 09:47:11 -0400 |
---|---|---|
committer | B. Stack <bgstack15@gmail.com> | 2022-04-18 09:47:40 -0400 |
commit | b4b2e4a096fe8fe1ad530a4c181729be05834595 (patch) | |
tree | 84e67ca0a1fb045a12d015fcffca9cd8087c9332 | |
parent | Merge branch 'b11.18' into 'master' (diff) | |
download | FreeFileSync-b4b2e4a096fe8fe1ad530a4c181729be05834595.tar.gz FreeFileSync-b4b2e4a096fe8fe1ad530a4c181729be05834595.tar.bz2 FreeFileSync-b4b2e4a096fe8fe1ad530a4c181729be05834595.zip |
add upstream 11.20
72 files changed, 1346 insertions, 975 deletions
@@ -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 Binary files differindex 69f3e627..9880b5ef 100644 --- a/FreeFileSync/Build/Resources/Languages.zip +++ b/FreeFileSync/Build/Resources/Languages.zip 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 @@ -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()); @@ -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)); } }; |