From fc71e66e5186fb75206290deca2b9a6cfbefa9ac Mon Sep 17 00:00:00 2001 From: "B. Stack" Date: Sat, 12 Jun 2021 09:59:08 -0400 Subject: add upstream 11.11 --- Changelog.txt | 12 +- FreeFileSync/Build/Resources/Languages.zip | Bin 507719 -> 507697 bytes FreeFileSync/Build/Resources/cacert.pem | 216 ++++------- FreeFileSync/Source/afs/abstract.cpp | 1 - FreeFileSync/Source/afs/ftp.cpp | 12 +- FreeFileSync/Source/afs/gdrive.cpp | 575 ++++++++++++++++------------- FreeFileSync/Source/afs/native.cpp | 6 +- FreeFileSync/Source/afs/sftp.cpp | 71 ++-- FreeFileSync/Source/base/algorithm.cpp | 38 +- FreeFileSync/Source/base/algorithm.h | 5 + FreeFileSync/Source/config.cpp | 2 + FreeFileSync/Source/localization.cpp | 6 +- FreeFileSync/Source/parse_plural.h | 2 +- FreeFileSync/Source/ui/cfg_grid.cpp | 1 - FreeFileSync/Source/ui/main_dlg.cpp | 73 ++-- FreeFileSync/Source/ui/main_dlg.h | 2 + FreeFileSync/Source/ui/small_dlgs.cpp | 4 +- FreeFileSync/Source/version/version.h | 2 +- libcurl/curl_wrap.h | 5 +- wx+/file_drop.h | 2 +- zen/file_access.cpp | 2 +- zen/i18n.h | 2 +- zen/legacy_compiler.h | 6 - zen/string_base.h | 4 +- zen/string_tools.h | 3 +- zen/time.h | 14 + 26 files changed, 536 insertions(+), 530 deletions(-) diff --git a/Changelog.txt b/Changelog.txt index 6a5be5a8..9102f8a4 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -1,3 +1,13 @@ +FreeFileSync 11.11 [2021-06-11] +------------------------------- +Fixed Shared Drive synchronization with Google Drive +Directly open exported file list (.CSV) as temporary file +Avoid EIO error for F_PREALLOCATE (macOS) +Watch socket using "poll" instead of "select" (Linux, macOS) +Fixed user-specific time/date format (Windows) +Fixed system_profiler not found error (macOS) + + FreeFileSync 11.10 [2021-05-09] ------------------------------- Fixed comparison results cleared after mouse-scrolling the first folder pair @@ -6,7 +16,7 @@ Disable all file pairs when base folder status cannot be determined Fixed sync statistics if base folder existence test failed Work around glitch in grid scrollbar size calculation Fixed folder drag and drop failing after locale conflict (macOS) -Fixed incorrect mime permissions after installation (Linux) +Fixed incorrect MIME permissions after installation (Linux) Stricter server response validation during update check Fixed incomplete item path in log if source item is missing Fixed installation error when running ConEmu diff --git a/FreeFileSync/Build/Resources/Languages.zip b/FreeFileSync/Build/Resources/Languages.zip index 06eedafa..e120e1f2 100644 Binary files a/FreeFileSync/Build/Resources/Languages.zip and b/FreeFileSync/Build/Resources/Languages.zip differ diff --git a/FreeFileSync/Build/Resources/cacert.pem b/FreeFileSync/Build/Resources/cacert.pem index 3c79c596..264923b3 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 Jan 19 04:12:04 2021 GMT +## Certificate data from Mozilla as of: Tue May 25 03:12:05 2021 GMT ## ## This is a bundle of X.509 certificates of public Certificate Authorities ## (CA). These were automatically extracted from Mozilla's root certificates @@ -14,7 +14,7 @@ ## Just configure this file as the SSLCACertificateFile. ## ## Conversion done with mk-ca-bundle.pl version 1.28. -## SHA256: 3bdc63d1de27058fec943a999a2a8a01fcc6806a611b19221a7727d3d9bbbdfd +## SHA256: e292bd4e2d500c86df45b830d89417be5c42ee670408f1d2c454c63d8a782865 ## @@ -718,51 +718,6 @@ vBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNwi/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7Nz TogVZ96edhBiIL5VaZVDADlN9u6wWk5JRFRYX0KD -----END CERTIFICATE----- -GeoTrust Primary Certification Authority - G2 -============================================= ------BEGIN CERTIFICATE----- -MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDELMAkGA1UEBhMC -VVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChjKSAyMDA3IEdlb1RydXN0IElu -Yy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBD -ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1 -OVowgZgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg -MjAwNyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNVBAMTLUdl -b1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjB2MBAGByqGSM49AgEG -BSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcLSo17VDs6bl8VAsBQps8lL33KSLjHUGMc -KiEIfJo22Av+0SbFWDEwKCXzXV2juLaltJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYD -VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+ -EVXVMAoGCCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGTqQ7m -ndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBuczrD6ogRLQy7rQkgu2 -npaqBA+K ------END CERTIFICATE----- - -VeriSign Universal Root Certification Authority -=============================================== ------BEGIN CERTIFICATE----- -MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCBvTELMAkGA1UE -BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO -ZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVk -IHVzZSBvbmx5MTgwNgYDVQQDEy9WZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9u -IEF1dGhvcml0eTAeFw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJV -UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv -cmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl -IG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0 -aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj -1mCOkdeQmIN65lgZOIzF9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGP -MiJhgsWHH26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+HLL72 -9fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN/BMReYTtXlT2NJ8I -AfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPTrJ9VAMf2CGqUuV/c4DPxhGD5WycR -tPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0G -CCsGAQUFBwEMBGEwX6FdoFswWTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2O -a8PPgGrUSBgsexkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud -DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4sAPmLGd75JR3 -Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+seQxIcaBlVZaDrHC1LGmWazx -Y8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTx -P/jgdFcrGJ2BtMQo2pSXpXDrrB2+BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+P -wGZsY6rp2aQW9IHRlRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4 -mJO37M2CYfE45k+XmCpajQ== ------END CERTIFICATE----- - NetLock Arany (Class Gold) Főtanúsítvány ======================================== -----BEGIN CERTIFICATE----- @@ -938,82 +893,6 @@ Q0iy2+tzJOeRf1SktoA+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1Z WrOZyGlsQyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== -----END CERTIFICATE----- -Chambers of Commerce Root - 2008 -================================ ------BEGIN CERTIFICATE----- -MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYDVQQGEwJFVTFD -MEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNv -bS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMu -QS4xKTAnBgNVBAMTIENoYW1iZXJzIG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEy -Mjk1MFoXDTM4MDczMTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNl -ZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIwEAYDVQQF -EwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJl -cnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC -AQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW928sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKA -XuFixrYp4YFs8r/lfTJqVKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorj -h40G072QDuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR5gN/ -ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfLZEFHcpOrUMPrCXZk -NNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05aSd+pZgvMPMZ4fKecHePOjlO+Bd5g -D2vlGts/4+EhySnB8esHnFIbAURRPHsl18TlUlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331 -lubKgdaX8ZSD6e2wsWsSaR6s+12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ -0wlf2eOKNcx5Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj -ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAxhduub+84Mxh2 -EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNVHQ4EFgQU+SSsD7K1+HnA+mCI -G8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1+HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJ -BgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNh -bWVyZmlybWEuY29tL2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENh -bWVyZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDiC -CQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUH -AgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZIhvcNAQEFBQADggIBAJASryI1 -wqM58C7e6bXpeHxIvj99RZJe6dqxGfwWPJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH -3qLPaYRgM+gQDROpI9CF5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbU -RWpGqOt1glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaHFoI6 -M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2pSB7+R5KBWIBpih1 -YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MDxvbxrN8y8NmBGuScvfaAFPDRLLmF -9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QGtjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcK -zBIKinmwPQN/aUv0NCB9szTqjktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvG -nrDQWzilm1DefhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg -OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZd0jQ ------END CERTIFICATE----- - -Global Chambersign Root - 2008 -============================== ------BEGIN CERTIFICATE----- -MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYDVQQGEwJFVTFD -MEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNv -bS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMu -QS4xJzAlBgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMx -NDBaFw0zODA3MzExMjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUg -Y3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJ -QTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD -aGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDf -VtPkOpt2RbQT2//BthmLN0EYlVJH6xedKYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXf -XjaOcNFccUMd2drvXNL7G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0 -ZJJ0YPP2zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4ddPB -/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyGHoiMvvKRhI9lNNgA -TH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2Id3UwD2ln58fQ1DJu7xsepeY7s2M -H/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3VyJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfe -Ox2YItaswTXbo6Al/3K1dh3ebeksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSF -HTynyQbehP9r6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh -wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsogzCtLkykPAgMB -AAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQWBBS5CcqcHtvTbDprru1U8VuT -BjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDprru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UE -BhMCRVUxQzBBBgNVBAcTOk1hZHJpZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJm -aXJtYS5jb20vYWRkcmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJm -aXJtYSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiCCQDJzdPp -1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0 -dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZIhvcNAQEFBQADggIBAICIf3DekijZBZRG -/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZUohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6 -ReAJ3spED8IXDneRRXozX1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/s -dZ7LoR/xfxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVza2Mg -9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yydYhz2rXzdpjEetrHH -foUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMdSqlapskD7+3056huirRXhOukP9Du -qqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9OAP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETr -P3iZ8ntxPjzxmKfFGBI/5rsoM0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVq -c5iJWzouE4gev8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z -09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B ------END CERTIFICATE----- - Go Daddy Root Certificate Authority - G2 ======================================== -----BEGIN CERTIFICATE----- @@ -1980,36 +1859,6 @@ uglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7 yFz9SO8NdCKoCOJuxUnOxwy8p2Fp8fc74SrL+SvzZpA3 -----END CERTIFICATE----- -Staat der Nederlanden Root CA - G3 -================================== ------BEGIN CERTIFICATE----- -MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJOTDEeMBwGA1UE -CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFhdCBkZXIgTmVkZXJsYW5kZW4g -Um9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloXDTI4MTExMzIzMDAwMFowWjELMAkGA1UEBhMC -TkwxHjAcBgNVBAoMFVN0YWF0IGRlciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5l -ZGVybGFuZGVuIFJvb3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL4y -olQPcPssXFnrbMSkUeiFKrPMSjTysF/zDsccPVMeiAho2G89rcKezIJnByeHaHE6n3WWIkYFsO2t -x1ueKt6c/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+tjzk7FqXxz8ecAgwoNzFs21v0IJy -EavSgWhZghe3eJJg+szeP4TrjTgzkApyI/o1zCZxMdFyKJLZWyNtZrVtB0LrpjPOktvA9mxjeM3K -Tj215VKb8b475lRgsGYeCasH/lSJEULR9yS6YHgamPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUur -mkVLoR9BvUhTFXFkC4az5S6+zqQbwSmEorXLCCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU5 -1nus6+N86U78dULI7ViVDAZCopz35HCz33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7Ngzp -07TKbBlBUgmhHbBqv4LvcFEhMtwFdozL92TkA1CvjJFnq8Xy7ljY3r735zHPbMk7ccHViLVlvMDo -FxcHErVc0qsgk7TmgoNwNsXNo42ti+yjwUOH5kPiNL6VizXtBznaqB16nzaeErAMZRKQFWDZJkBE -41ZgpRDUajz9QdwOWke275dhdU/Z/seyHdTtXUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMB -AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleu -yjWcLhL75LpdINyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB/d6+Oea31BD -U5cqPco8R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwpLiniyMMB8jPq -KqrMCQj3ZWfGzd/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8Ipf3YF3qKS9Ysr1YvY2WTxB1 -v0h7PVGHoTx0IsL8B3+A3MSs/mrBcDCw6Y5p4ixpgZQJut3+TcCDjJRYwEYgr5wfAvg1VUkvRtTA -8KCWAg8zxXHzniN9lLf9OtMJgwYh/WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox+wrZ13+b -8KKaa8MFSu1BYBQw0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko+gE4uZbqKmxnl4mUnrzhVNXkanjvSr0r -mj1AfsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq4BZ+Extq -1z7XnvwtdbLBFNUjA9tbbws+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR1VmiiXTTn74eS9fGbbeI -JG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/QFH1T/U67cjF68IeHRaVesd+QnGTbksV -tzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM94B7IWcnMFk= ------END CERTIFICATE----- - Staat der Nederlanden EV Root CA ================================ -----BEGIN CERTIFICATE----- @@ -3226,3 +3075,64 @@ qqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oG I/hGoiLtk/bdmuYqh7GYVPEi92tF4+KOdh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmg kpzNNIaRkPpkUZ3+/uul9XXeifdy -----END CERTIFICATE----- + +AC RAIZ FNMT-RCM SERVIDORES SEGUROS +=================================== +-----BEGIN CERTIFICATE----- +MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQswCQYDVQQGEwJF +UzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgwFgYDVQRhDA9WQVRFUy1RMjgy +NjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1SQ00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4 +MTIyMDA5MzczM1oXDTQzMTIyMDA5MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQt +UkNNMQ4wDAYDVQQLDAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNB +QyBSQUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuBBAAiA2IA +BPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LHsbI6GA60XYyzZl2hNPk2 +LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oKUm8BA06Oi6NCMEAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqG +SM49BAMDA2kAMGYCMQCuSuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoD +zBOQn5ICMQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJyv+c= +-----END CERTIFICATE----- + +GlobalSign Root R46 +=================== +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUAMEYxCzAJBgNV +BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJv +b3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAX +BgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08Es +CVeJOaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQGvGIFAha/ +r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud316HCkD7rRlr+/fKYIje +2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo0q3v84RLHIf8E6M6cqJaESvWJ3En7YEt +bWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSEy132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvj +K8Cd+RTyG/FWaha/LIWFzXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD4 +12lPFzYE+cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCNI/on +ccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzsx2sZy/N78CsHpdls +eVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqaByFrgY/bxFn63iLABJzjqls2k+g9 +vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEM +BQADggIBAHx47PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg +JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti2kM3S+LGteWy +gxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIkpnnpHs6i58FZFZ8d4kuaPp92 +CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRFFRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZm +OUdkLG5NrmJ7v2B0GbhWrJKsFjLtrWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qq +JZ4d16GLuc1CLgSkZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwye +qiv5u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP4vkYxboz +nxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6N3ec592kD3ZDZopD8p/7 +DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3vouXsXgxT7PntgMTzlSdriVZzH81Xwj3 +QEUxeCp6 +-----END CERTIFICATE----- + +GlobalSign Root E46 +=================== +-----BEGIN CERTIFICATE----- +MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYxCzAJBgNVBAYT +AkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3Qg +RTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNV +BAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkB +jtjqR+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGddyXqBPCCj +QjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQxCpCPtsad0kRL +gLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZk +vLtoURMMA/cVi4RguYv/Uo7njLwcAjA8+RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+ +CAezNIm8BZ/3Hobui3A= +-----END CERTIFICATE----- diff --git a/FreeFileSync/Source/afs/abstract.cpp b/FreeFileSync/Source/afs/abstract.cpp index 614f1290..a0d1ab2c 100644 --- a/FreeFileSync/Source/afs/abstract.cpp +++ b/FreeFileSync/Source/afs/abstract.cpp @@ -172,7 +172,6 @@ AFS::FileCopyResult AFS::copyFileTransactional(const AbstractPath& apSource, con const std::function& onDeleteTargetFile, const IoCallback& notifyUnbufferedIO /*throw X*/) { - auto copyFilePlain = [&](const AbstractPath& apTargetTmp) { //caveat: typeid returns static type for pointers, dynamic type for references!!! diff --git a/FreeFileSync/Source/afs/ftp.cpp b/FreeFileSync/Source/afs/ftp.cpp index 55b35829..d9c41e7d 100644 --- a/FreeFileSync/Source/afs/ftp.cpp +++ b/FreeFileSync/Source/afs/ftp.cpp @@ -182,7 +182,7 @@ std::vector splitFtpResponse(const std::string& buf) class FtpLineParser { public: - FtpLineParser(const std::string& line) : line_(line), it_(line_.begin()) {} + explicit FtpLineParser(const std::string& line) : line_(line), it_(line_.begin()) {} template std::string readRange(size_t count, Function acceptChar) //throw SysError @@ -269,14 +269,14 @@ std::wstring formatFtpStatus(int sc) //================================================================================================================ //================================================================================================================ -constinit2 Global globalFtpSessionCount; +constinit Global globalFtpSessionCount; GLOBAL_RUN_ONCE(globalFtpSessionCount.set(createUniSessionCounter())); class FtpSession { public: - FtpSession(const FtpSessionId& sessionId) : //throw SysError + explicit FtpSession(const FtpSessionId& sessionId) : //throw SysError sessionId_(sessionId), libsshCurlUnifiedInitCookie_(getLibsshCurlUnifiedInitCookie(globalFtpSessionCount)), //throw SysError lastSuccessfulUseTime_(std::chrono::steady_clock::now()) {} @@ -738,7 +738,7 @@ private: { if (!featureCache_) { - static constinit2 FunStatGlobal> globalServerFeatures; + static constinit FunStatGlobal> globalServerFeatures; globalServerFeatures.initOnce([] { return std::make_unique>(); }); const auto sf = globalServerFeatures.get(); @@ -925,7 +925,7 @@ private: //-------------------------------------------------------------------------------------- UniInitializer globalStartupInitFtp(*globalFtpSessionCount.get()); -constinit2 Global globalFtpSessionManager; //caveat: life time must be subset of static UniInitializer! +constinit Global globalFtpSessionManager; //caveat: life time must be subset of static UniInitializer! //-------------------------------------------------------------------------------------- void accessFtpSession(const FtpLogin& login, const std::function& useFtpSession /*throw X*/) //throw SysError, X @@ -1932,7 +1932,7 @@ private: class FtpFileSystem : public AbstractFileSystem { public: - FtpFileSystem(const FtpLogin& login) : login_(login) {} + explicit FtpFileSystem(const FtpLogin& login) : login_(login) {} const FtpLogin& getLogin() const { return login_; } diff --git a/FreeFileSync/Source/afs/gdrive.cpp b/FreeFileSync/Source/afs/gdrive.cpp index 74703ebb..0526c183 100644 --- a/FreeFileSync/Source/afs/gdrive.cpp +++ b/FreeFileSync/Source/afs/gdrive.cpp @@ -25,6 +25,7 @@ #include #include "abstract_impl.h" #include "init_curl_libssh2.h" + #include using namespace zen; using namespace fff; @@ -54,7 +55,7 @@ std::weak_ordering operator<=>(const GdriveRawPath& lhs, const GdriveRawPath& rh return compareNativePath(lhs.itemName, rhs.itemName); } -constinit2 Global> globalGdrivePathAccessLocker; +constinit Global> globalGdrivePathAccessLocker; GLOBAL_RUN_ONCE(globalGdrivePathAccessLocker.set(std::make_unique>())); template <> std::shared_ptr> PathAccessLocker::getGlobalInstance() { return globalGdrivePathAccessLocker.get(); } @@ -83,7 +84,7 @@ const char gdriveFolderMimeType [] = "application/vnd.google-apps.folder"; const char gdriveShortcutMimeType[] = "application/vnd.google-apps.shortcut"; //= symbolic link! const char DB_FILE_DESCR[] = "FreeFileSync"; -const int DB_FILE_VERSION = 4; //2020-07-03 +const int DB_FILE_VERSION = 5; //2021-05-15 std::string getGdriveClientId () { return ""; } // => replace with live credentials std::string getGdriveClientSecret() { return ""; } // @@ -179,7 +180,7 @@ AFS::FingerPrint getGdriveFilePrint(const std::string& itemId) //---------------------------------------------------------------------------------------------------------------- -constinit2 Global httpSessionCount; +constinit Global httpSessionCount; GLOBAL_RUN_ONCE(httpSessionCount.set(createUniSessionCounter())); UniInitializer startupInitHttp(*httpSessionCount.get()); @@ -301,7 +302,7 @@ private: }; //-------------------------------------------------------------------------------------- -constinit2 Global globalHttpSessionManager; //caveat: life time must be subset of static UniInitializer! +constinit Global globalHttpSessionManager; //caveat: life time must be subset of static UniInitializer! //-------------------------------------------------------------------------------------- @@ -526,18 +527,14 @@ GdriveAccessInfo gdriveAuthorizeAccess(const std::string& gdriveLoginHint, const { if (updateGui) updateGui(); //throw X - fd_set readfds = {}; //covers FD_ZERO - FD_SET(socket, &readfds); + const int waitTimeMs = 100; + pollfd fds[] = {{socket, POLLIN}}; - timeval tv = {}; - tv.tv_usec = static_cast(100 /*ms*/) * 1000; - - //WSAPoll broken, even ::poll() on OS X? https://daniel.haxx.se/blog/2012/10/10/wsapoll-is-broken/ - //perf: no significant difference compared to ::WSAPoll() - if (const int rc = ::select(socket + 1, &readfds, nullptr /*writefds*/, nullptr /*errorfds*/, &tv); - rc < 0) - THROW_LAST_SYS_ERROR_WSA("select"); - else if (rc != 0) + const char* functionName = "poll"; + if (const int rv = ::poll(fds, std::size(fds), waitTimeMs); //int timeout + rv < 0) + THROW_LAST_SYS_ERROR_WSA(functionName); + else if (rv != 0) break; //else: time-out! } @@ -776,7 +773,7 @@ std::vector getSharedDrives(const std::string& accessToken) //thro { std::optional driveId = getPrimitiveFromJsonObject(driveVal, "id"); std::optional driveName = getPrimitiveFromJsonObject(driveVal, "name"); - if (!driveId || !driveName) + if (!driveId || !driveName || driveName->empty()) throw SysError(formatGdriveErrorRaw(serializeJson(driveVal))); sharedDrives.push_back({std::move(*driveId), utfTo(*driveName)}); @@ -1004,23 +1001,28 @@ struct ChangesDelta std::vector fileChanges; std::vector driveChanges; }; -ChangesDelta getChangesDelta(const std::string& startPageToken, const std::string& accessToken) //throw SysError +ChangesDelta getChangesDelta(const std::string& sharedDriveId /*empty for "My Drive"*/, const std::string& startPageToken, const std::string& accessToken) //throw SysError { //https://developers.google.com/drive/api/v3/reference/changes/list ChangesDelta delta; std::optional nextPageToken = startPageToken; for (;;) { - const std::string& queryParams = xWwwFormUrlEncode( + std::string queryParams = xWwwFormUrlEncode( { {"pageToken", *nextPageToken}, {"fields", "kind,nextPageToken,newStartPageToken,changes(kind,changeType,removed,fileId,file(trashed,name,mimeType,ownedByMe,size,modifiedTime,parents,shortcutDetails(targetId)),driveId,drive(name))"}, - {"includeItemsFromAllDrives", "true"}, + {"includeItemsFromAllDrives", "true"}, //semantics are a mess https://developers.google.com/drive/api/v3/enable-shareddrives https://freefilesync.org/forum/viewtopic.php?t=7827&start=30#p29712 + //in short: if driveId is set: required, but blatant lie; only drive-specific file changes returned + // if no driveId set: optional, but blatant lie; only changes to drive objects are returned, but not contained files (with a few exceptions) {"pageSize", "1000"}, //"[1, 1000] Default: 100" {"spaces", "drive"}, {"supportsAllDrives", "true"}, //do NOT "restrictToMyDrive": we're also interested in "Shared with me" items, which might be referenced by a shortcut in "My Drive" }); + if (!sharedDriveId.empty()) + queryParams += '&' + xWwwFormUrlEncode({{"driveId", sharedDriveId}}); //only allowed for shared drives! + std::string response; gdriveHttpsRequest("/drive/v3/changes?" + queryParams, {"Authorization: Bearer " + accessToken}, {} /*extraOptions*/, //throw SysError [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast(buffer), bytesToWrite); }, nullptr /*readRequest*/); @@ -1104,13 +1106,16 @@ ChangesDelta getChangesDelta(const std::string& startPageToken, const std::strin } -std::string /*startPageToken*/ getChangesCurrentToken(const std::string& accessToken) //throw SysError +std::string /*startPageToken*/ getChangesCurrentToken(const std::string& sharedDriveId /*empty for "My Drive"*/, const std::string& accessToken) //throw SysError { //https://developers.google.com/drive/api/v3/reference/changes/getStartPageToken - const std::string& queryParams = xWwwFormUrlEncode( + std::string queryParams = xWwwFormUrlEncode( { {"supportsAllDrives", "true"}, }); + if (!sharedDriveId.empty()) + queryParams += '&' + xWwwFormUrlEncode({{"driveId", sharedDriveId}}); //only allowed for shared drives! + std::string response; gdriveHttpsRequest("/drive/v3/changes/startPageToken?" + queryParams, {"Authorization: Bearer " + accessToken}, {} /*extraOptions*/, //throw SysError [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast(buffer), bytesToWrite); }, nullptr /*readRequest*/); @@ -1696,10 +1701,10 @@ std::string /*itemId*/ getMyDriveId(const std::string& accessToken) //throw SysE } -class GdriveAccessBuffer //per-user-session! => serialize access (perf: amortized fully buffered!) +class GdriveAccessBuffer //per-user-session & drive! => serialize access (perf: amortized fully buffered!) { public: - GdriveAccessBuffer(const GdriveAccessInfo& accessInfo) : accessInfo_(accessInfo) {} + explicit GdriveAccessBuffer(const GdriveAccessInfo& accessInfo) : accessInfo_(accessInfo) {} GdriveAccessBuffer(MemoryStreamIn& stream) //throw SysError { @@ -1747,35 +1752,27 @@ private: }; -class GdrivePersistentSessions; +class GdriveDrivesBuffer; class GdriveFileState //per-user-session! => serialize access (perf: amortized fully buffered!) { public: - GdriveFileState(GdriveAccessBuffer& accessBuf) : //throw SysError + GdriveFileState(const std::string& driveId, //ID of shared drive or "My Drive": not empty! + const Zstring& sharedDriveName, //*empty* for "My Drive" + GdriveAccessBuffer& accessBuf) : //throw SysError /* issue getChangesCurrentToken() as the very first Google Drive query! */ - lastSyncToken_(getChangesCurrentToken(accessBuf.getAccessToken())), //throw SysError - myDriveId_ (getMyDriveId (accessBuf.getAccessToken())), // - accessBuf_(accessBuf) - { - for (const DriveDetails& drive : getSharedDrives(accessBuf.getAccessToken())) //throw SysError - sharedDrives_.emplace(drive.driveId, drive.driveName); - } + lastSyncToken_(getChangesCurrentToken(sharedDriveName.empty() ? std::string() : driveId, accessBuf.getAccessToken())), //throw SysError + driveId_(driveId), + sharedDriveName_(sharedDriveName), + accessBuf_(accessBuf) { assert(sharedDriveName != Zstr("My Drive")); } GdriveFileState(MemoryStreamIn& stream, GdriveAccessBuffer& accessBuf) : //throw SysError accessBuf_(accessBuf) { - lastSyncToken_ = readContainer(stream); //SysErrorUnexpectedEos - myDriveId_ = readContainer(stream); // - - size_t sharedDrivesCount = readNumber(stream); //SysErrorUnexpectedEos - while (sharedDrivesCount-- != 0) - { - std::string driveId = readContainer(stream); //SysErrorUnexpectedEos - std::string driveName = readContainer(stream); // - sharedDrives_.emplace(driveId, utfTo(driveName)); - } + lastSyncToken_ = readContainer(stream); // + driveId_ = readContainer(stream); //SysErrorUnexpectedEos + sharedDriveName_ = utfTo(readContainer(stream)); // for (;;) { @@ -1810,14 +1807,8 @@ public: void serialize(MemoryStreamOut& stream) const { writeContainer(stream, lastSyncToken_); - writeContainer(stream, myDriveId_); - - writeNumber(stream, static_cast(sharedDrives_.size())); - for (const auto& [driveId, driveName]: sharedDrives_) - { - writeContainer(stream, driveId); - writeContainer(stream, utfTo(driveName)); - } + writeContainer(stream, driveId_); + writeContainer(stream, utfTo(sharedDriveName_)); for (const auto& [folderId, content] : folderContents_) if (folderId.empty()) @@ -1865,15 +1856,10 @@ public: writeContainer(stream, std::string()); //sentinel } - std::vector listSharedDrives() const - { - std::vector sharedDriveNames; - - for (const auto& [driveId, driveName]: sharedDrives_) - sharedDriveNames.push_back(driveName); + std::string getDriveId() const { return driveId_; } - return sharedDriveNames; - } + Zstring getSharedDriveName() const { return sharedDriveName_; } + void setSharedDriveName(const Zstring& sharedDriveName) { sharedDriveName_ = sharedDriveName; } struct PathStatus { @@ -1882,76 +1868,42 @@ public: AfsPath existingPath; //input path =: existingPath + relPath std::vector relPath; // }; - PathStatus getPathStatus(const Zstring& sharedDriveName, const AfsPath& afsPath, bool followLeafShortcut) //throw SysError + PathStatus getPathStatus(const AfsPath& afsPath, bool followLeafShortcut) //throw SysError { - const std::string driveId = [&] - { - if (sharedDriveName.empty()) - return myDriveId_; - - auto itFound = sharedDrives_.cend(); - for (auto it = sharedDrives_.begin(); it != sharedDrives_.end(); ++it) - if (const auto& [sharedDriveId, driveName] = *it; - equalNativePath(driveName, sharedDriveName)) - { - if (itFound != sharedDrives_.end()) - 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; - } - if (itFound == sharedDrives_.end()) - throw SysError(replaceCpy(_("Cannot find %x."), L"%x", - fmtPath(getGdriveDisplayPath({{accessBuf_.getUserEmail(), sharedDriveName}, AfsPath()})))); - - return itFound->first; - }(); - const std::vector relPath = split(afsPath.value, FILE_NAME_SEPARATOR, SplitOnEmpty::skip); if (relPath.empty()) - return {driveId, GdriveItemType::folder, AfsPath(), {}}; + return {driveId_, GdriveItemType::folder, AfsPath(), {}}; else - return getPathStatusSub(driveId, sharedDriveName, AfsPath(), relPath, followLeafShortcut); //throw SysError + return getPathStatusSub(driveId_, AfsPath(), relPath, followLeafShortcut); //throw SysError } - std::string /*itemId*/ getItemId(const Zstring& sharedDriveName, const AfsPath& afsPath, bool followLeafShortcut) //throw SysError + std::string /*itemId*/ getItemId(const AfsPath& afsPath, bool followLeafShortcut) //throw SysError { - const GdriveFileState::PathStatus& ps = getPathStatus(sharedDriveName, afsPath, followLeafShortcut); //throw SysError + const GdriveFileState::PathStatus& ps = getPathStatus(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(getGdriveDisplayPath({{accessBuf_.getUserEmail(), sharedDriveName}, afsPathMissingChild})))); + throw SysError(replaceCpy(_("Cannot find %x."), L"%x", fmtPath(getDisplayPath(afsPathMissingChild)))); } - std::pair getFileAttributes(const Zstring& sharedDriveName, const AfsPath& afsPath, bool followLeafShortcut) //throw SysError + std::pair getFileAttributes(const AfsPath& afsPath, bool followLeafShortcut) //throw SysError { - const std::string itemId = getItemId(sharedDriveName, afsPath, followLeafShortcut); //throw SysError + const std::string itemId = getItemId(afsPath, followLeafShortcut); //throw SysError if (afsPath.value.empty()) //root drives obviously not covered by itemDetails_ { GdriveItemDetails rootDetails = {}; rootDetails.type = GdriveItemType::folder; - if (itemId == myDriveId_) - { - rootDetails.itemName = Zstr("My Drive"); - rootDetails.owner = FileOwner::me; - return {itemId, std::move(rootDetails)}; - } - - if (auto it = sharedDrives_.find(itemId); - it != sharedDrives_.end()) - { - rootDetails.itemName = it->second; - rootDetails.owner = FileOwner::none; - return {itemId, std::move(rootDetails)}; - } + //rootDetails.itemName =... => better leave empty for a root item! + rootDetails.owner = sharedDriveName_.empty() ? FileOwner::me : FileOwner::none; + return {itemId, std::move(rootDetails)}; } else if (auto it = itemDetails_.find(itemId); it != itemDetails_.end()) return *it; - //itemId was found! => must either be a (shared) drive root or buffered in itemDetails_ + //itemId was found! => must either be a drive root or buffered in itemDetails_ throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); } @@ -2082,7 +2034,13 @@ private: GdriveFileState (const GdriveFileState&) = delete; GdriveFileState& operator=(const GdriveFileState&) = delete; - friend class GdrivePersistentSessions; + friend class GdriveDrivesBuffer; + + std::wstring getDisplayPath(const AfsPath& afsPath) const + { + const GdriveLogin login{accessBuf_.getUserEmail(), sharedDriveName_}; + return getGdriveDisplayPath({login, afsPath}); + } void notifyItemUpdated(const FileStateDelta& stateDelta, const std::string& itemId, const GdriveItemDetails* details) { @@ -2112,14 +2070,11 @@ private: void syncWithGoogle() //throw SysError { - const ChangesDelta delta = getChangesDelta(lastSyncToken_, accessBuf_.getAccessToken()); //throw SysError + const ChangesDelta delta = getChangesDelta(sharedDriveName_.empty() ? std::string() : driveId_, lastSyncToken_, accessBuf_.getAccessToken()); //throw SysError for (const FileChange& change : delta.fileChanges) updateItemState(change.itemId, get(change.details)); - for (const DriveChange& change : delta.driveChanges) - updateSharedDriveState(change.driveId, change.driveName); - lastSyncToken_ = delta.newStartPageToken; lastSyncTime_ = std::chrono::steady_clock::now(); @@ -2127,7 +2082,7 @@ private: //Same goes for any other change that is undone in between change notification syncs. } - PathStatus getPathStatusSub(const std::string& folderId, const Zstring& sharedDriveName, const AfsPath& folderPath, const std::vector& relPath, bool followLeafShortcut) //throw SysError + PathStatus getPathStatusSub(const std::string& folderId, const AfsPath& folderPath, const std::vector& relPath, bool followLeafShortcut) //throw SysError { assert(!relPath.empty()); @@ -2144,13 +2099,13 @@ private: auto itFound = itemDetails_.cend(); for (const DetailsIterator& itChild : itKnown->second.childItems) - //Since Google Drive has no concept of a file path, we have to roll our own "path to id" mapping => let's use the platform-native style + //Since Google Drive has no concept of a file path, we have to roll our own "path to ID" mapping => let's use the platform-native style if (equalNativePath(itChild->second.itemName, relPath.front())) { if (itFound != itemDetails_.end()) throw SysError(replaceCpy(_("Cannot find %x."), L"%x", - fmtPath(getGdriveDisplayPath({{accessBuf_.getUserEmail(), sharedDriveName}, 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()))); + fmtPath(getDisplayPath(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; } @@ -2190,7 +2145,7 @@ private: return {childId, childDetails.type, childItemPath, childRelPath}; case GdriveItemType::folder: - return getPathStatusSub(childId, sharedDriveName, childItemPath, childRelPath, followLeafShortcut); //throw SysError + return getPathStatusSub(childId, childItemPath, childRelPath, followLeafShortcut); //throw SysError case GdriveItemType::shortcut: switch (getItemDetailsBuffered(childDetails.targetId).type) @@ -2199,12 +2154,12 @@ private: return {childDetails.targetId, GdriveItemType::file, childItemPath, childRelPath}; //resolve symlinks if in the *middle* of a path! case GdriveItemType::folder: //parent/folder-symlink/child-rel-path... => always follow - return getPathStatusSub(childDetails.targetId, sharedDriveName, childItemPath, childRelPath, followLeafShortcut); //throw SysError + return getPathStatusSub(childDetails.targetId, childItemPath, childRelPath, followLeafShortcut); //throw SysError 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(getGdriveDisplayPath({{accessBuf_.getUserEmail(), sharedDriveName}, AfsPath(nativeAppendPaths(folderPath.value, relPath.front()))}))) + L' ' + - L"Google Drive Shortcut points to another Shortcut."); + fmtPath(getDisplayPath(AfsPath(nativeAppendPaths(folderPath.value, relPath.front()))))) + L' ' + + L"Google Drive Shortcut points to another Shortcut."); } break; } @@ -2274,62 +2229,174 @@ private: itemDetails_.erase(it); } - if (auto itP = folderContents_.find(itemId); - itP != folderContents_.end()) + if (auto itP = folderContents_.find(itemId); itP != folderContents_.end()) { - for (auto itChild : itP->second.childItems) //2. delete as parent from child items (don't wait for change notifications of children) + //2. delete as parent from child items (don't wait for change notifications of children) + // what if e.g. single change notification "folder removed", then folder reapears, + // and no notifications for child items: possible with Google drive!? + // => no problem: FolderContent::isKnownFolder will be false for this restored folder => only a rescan needed + for (auto itChild : itP->second.childItems) std::erase_if(itChild->second.parentIds, [&](const std::string& id) { return id == itemId; }); folderContents_.erase(itP); } } } - void updateSharedDriveState(const std::string& driveId, const Zstring& driveName /*empty if shared drive was deleted*/) - { - if (!driveName.empty()) - sharedDrives_[driveId] = driveName; - else //delete - { - sharedDrives_.erase(driveId); - - //when a shared drive is deleted, we also receive change notifications for the contained files: nice! - if (auto itP = folderContents_.find(driveId); - itP != folderContents_.end()) - { - for (auto itChild : itP->second.childItems) //delete as parent from child items (don't wait for change notifications of children) - std::erase_if(itChild->second.parentIds, [&](const std::string& id) { return id == driveId; }); - folderContents_.erase(itP); - } - } - } - using DetailsIterator = std::unordered_map::iterator; struct FolderContent { - bool isKnownFolder = false; //=we've seen its full content at least once; further changes are calculated via change notifications! + bool isKnownFolder = false; //:= we've seen its full content at least once; further changes are calculated via change notifications std::vector childItems; }; std::unordered_map folderContents_; std::unordered_map itemDetails_; //contains ALL known, existing items! - std::string lastSyncToken_; //marker corresponding to last sync with Google's change notifications + std::string lastSyncToken_; //drive-specific(!) marker corresponding to last sync with Google's change notifications std::chrono::steady_clock::time_point lastSyncTime_ = std::chrono::steady_clock::now() - GDRIVE_SYNC_INTERVAL; //... with Google Drive (default: sync is due) std::vector> changeLog_; //track changed items since FileStateDelta was created (includes sync with Google + our own intermediate change notifications) - std::string myDriveId_; - std::unordered_map sharedDrives_; + std::string driveId_; //ID of shared drive or "My Drive": not empty! + Zstring sharedDriveName_; //name of shared drive: empty for "My Drive"! + GdriveAccessBuffer& accessBuf_; }; + +class GdriveDrivesBuffer +{ +public: + explicit GdriveDrivesBuffer(GdriveAccessBuffer& accessBuf) : + accessBuf_(accessBuf), + myDrive_(getMyDriveId(accessBuf.getAccessToken()), Zstring() /*sharedDriveName*/, accessBuf) {} //throw SysError + + GdriveDrivesBuffer(MemoryStreamIn& stream, GdriveAccessBuffer& accessBuf) : //throw SysError + accessBuf_(accessBuf), + myDrive_(stream, accessBuf) //throw SysError + { + size_t sharedDrivesCount = readNumber(stream); //SysErrorUnexpectedEos + while (sharedDrivesCount-- != 0) + { + auto fileState = makeSharedRef(stream, accessBuf); //throw SysError + sharedDrives_.emplace(fileState.ref().getDriveId(), fileState); + } + } + + void serialize(MemoryStreamOut& stream) const + { + myDrive_.serialize(stream); + + writeNumber(stream, static_cast(sharedDrives_.size())); + for (const auto& [driveId, fileState] : sharedDrives_) + fileState.ref().serialize(stream); + } + + std::vector listSharedDrives() //throw SysError + { + if (syncIsDue()) + syncWithGoogle(); //throw SysError + + std::vector sharedDriveNames; + + for (const auto& [driveId, fileState] : sharedDrives_) + sharedDriveNames.push_back(fileState.ref().getSharedDriveName()); + + return sharedDriveNames; + } + + std::pair prepareAccess(const Zstring& sharedDriveName) //throw SysError + { + //checking for added/renamed/deleted shared drives *every* GDRIVE_SYNC_INTERVAL is needlessly excessive! + // => check 1. once per FFS run + // 2. on drive access error + if (lastSyncTime_ == std::chrono::steady_clock::time_point()) + syncWithGoogle(); //throw SysError + + GdriveFileState* fileState = nullptr; + try + { + fileState = &getDrive(sharedDriveName); //throw SysError + } + catch (SysError&) + { + if (syncIsDue()) + syncWithGoogle(); //throw SysError + + fileState = &getDrive(sharedDriveName); //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 + + return {fileState, fileState->registerFileStateDelta()}; + } + +private: + bool syncIsDue() const { return std::chrono::steady_clock::now() >= lastSyncTime_ + GDRIVE_SYNC_INTERVAL; } + + void syncWithGoogle() //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 + for (const auto& [driveId, driveName] : getSharedDrives(accessBuf_.getAccessToken())) //throw SysError + { + auto fileState = [&, &driveId /*clang bug*/= driveId, &driveName /*clang bug*/= driveName] + { + if (auto it = sharedDrives_.find(driveId); + it != sharedDrives_.end()) + { + it->second.ref().setSharedDriveName(driveName); + return it->second; + } + else + return makeSharedRef(driveId, driveName, accessBuf_); //throw SysError + }(); + currentDrives.emplace(driveId, fileState); + } + + sharedDrives_.swap(currentDrives); //transaction! + lastSyncTime_ = std::chrono::steady_clock::now(); //...(uhm, mostly, except for setSharedDriveName()) + } + + GdriveFileState& getDrive(const Zstring& sharedDriveName) //throw SysError + { + if (sharedDriveName.empty()) + return myDrive_; + + auto itFound = sharedDrives_.end(); + for (auto it = sharedDrives_.begin(); it != sharedDrives_.end(); ++it) + if (equalNativePath(it->second.ref().getSharedDriveName(), sharedDriveName)) + { + if (itFound != sharedDrives_.end()) + 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; + } + if (itFound == sharedDrives_.end()) + throw SysError(replaceCpy(_("Cannot find %x."), L"%x", + fmtPath(getGdriveDisplayPath({{accessBuf_.getUserEmail(), sharedDriveName}, AfsPath()})))); + + return itFound->second.ref(); + } + + GdriveAccessBuffer& accessBuf_; + std::chrono::steady_clock::time_point lastSyncTime_; //... with Google Drive (default: sync is due) + + GdriveFileState myDrive_; + std::unordered_map> sharedDrives_; +}; + //========================================================================================== //========================================================================================== class GdrivePersistentSessions { public: - GdrivePersistentSessions(const Zstring& configDirPath) : configDirPath_(configDirPath) {} + explicit GdrivePersistentSessions(const Zstring& configDirPath) : configDirPath_(configDirPath) {} void saveActiveSessions() //throw FileError { @@ -2375,8 +2442,8 @@ public: else { auto accessBuf = makeSharedRef(accessInfo); - auto fileState = makeSharedRef(accessBuf.ref()); //throw SysError - userSession = {accessBuf, fileState}; + auto drivesBuf = makeSharedRef(accessBuf.ref()); //throw SysError + userSession = {accessBuf, drivesBuf}; } }); @@ -2394,7 +2461,7 @@ public: }); } catch (SysError&) { assert(false); } //best effort: try to invalidate the access token - //=> expected to fail if offline => not worse than removing FFS via "Uninstall Programs" + //=> expected to fail 1. if offline => not worse than removing FFS via "Uninstall Programs" 2. already revoked 3. if DB is corrupted try { @@ -2457,30 +2524,41 @@ public: return emails; } + std::vector listSharedDrives(const std::string& accountEmail) //throw SysError + { + std::vector sharedDriveNames; + + accessUserSession(accountEmail, [&](std::optional& userSession) //throw SysError + { + if (!userSession) + throw SysError(replaceCpy(_("Please authorize access to user account %x."), L"%x", utfTo(accountEmail))); + + sharedDriveNames = userSession->drivesBuf.ref().listSharedDrives(); //throw SysError + }); + return sharedDriveNames; + } + struct AsyncAccessInfo { std::string accessToken; //don't allow (long-running) web requests while holding the global session lock! GdriveFileState::FileStateDelta stateDelta; }; //perf: amortized fully buffered! - AsyncAccessInfo accessGlobalFileState(const std::string& accountEmail, const std::function& useFileState /*throw X*/) //throw SysError, X + AsyncAccessInfo accessGlobalFileState(const GdriveLogin& login, const std::function& useFileState /*throw X*/) //throw SysError, X { std::string accessToken; GdriveFileState::FileStateDelta stateDelta; - accessUserSession(accountEmail, [&](std::optional& userSession) //throw SysError + accessUserSession(login.email, [&](std::optional& userSession) //throw SysError { if (!userSession) - throw SysError(replaceCpy(_("Please authorize access to user account %x."), L"%x", utfTo(accountEmail))); + throw SysError(replaceCpy(_("Please authorize access to user account %x."), L"%x", utfTo(login.email))); - //manage last sync time here rather than in GdriveFileState, so that "lastSyncToken" remains stable while accessing GdriveFileState in the callback - if (userSession->fileState.ref().syncIsDue()) - userSession->fileState.ref().syncWithGoogle(); //throw SysError + GdriveFileState* fileState = nullptr; + accessToken = userSession->accessBuf.ref().getAccessToken(); //throw SysError + std::tie(fileState, stateDelta) = userSession->drivesBuf.ref().prepareAccess(login.sharedDriveName); //throw SysError - accessToken = userSession->accessBuf.ref().getAccessToken(); //throw SysError - stateDelta = userSession->fileState.ref().registerFileStateDelta(); - - useFileState(userSession->fileState.ref()); //throw X + useFileState(*fileState); //throw X }); return {accessToken, stateDelta}; } @@ -2525,7 +2603,7 @@ private: MemoryStreamOut streamOutBody; userSession.accessBuf.ref().serialize(streamOutBody); - userSession.fileState.ref().serialize(streamOutBody); + userSession.drivesBuf.ref().serialize(streamOutBody); try { @@ -2576,14 +2654,11 @@ private: version != 3) //TODO: remove migration code at some time! 2020-07-03 throw SysError(_("Unsupported data format.") + L' ' + replaceCpy(_("Version: %x"), L"%x", numberTo(version))); + //version 1 + 2: fully discard old state due to missing "ownedByMe" attribute + shortcut support + //version 3: fully discard old state due to revamped shared drive handling auto accessBuf = makeSharedRef(streamIn2); //throw SysError - auto fileState = - //TODO: remove migration code at some time! 2020-06-11 - version <= 2 ? //fully discard old state due to missing "ownedByMe" attribute + shortcut support - makeSharedRef( accessBuf.ref()) : //throw SysError - makeSharedRef(streamIn2, accessBuf.ref()); // - - return UserSession{accessBuf, fileState}; + auto drivesBuf = makeSharedRef(accessBuf.ref()); //throw SysError + return UserSession{accessBuf, drivesBuf}; } else { @@ -2591,15 +2666,22 @@ private: throw SysError(_("File content is corrupted.") + L" (invalid header)"); const int version = readNumber(streamIn); - if (version != DB_FILE_VERSION) + if (version != 4 && + version != DB_FILE_VERSION) throw SysError(_("Unsupported data format.") + L' ' + replaceCpy(_("Version: %x"), L"%x", numberTo(version))); MemoryStreamIn streamInBody(decompress(std::string(byteStream.begin() + streamIn.pos(), byteStream.end()))); //throw SysError auto accessBuf = makeSharedRef(streamInBody); //throw SysError - auto fileState = makeSharedRef(streamInBody, accessBuf.ref()); //throw SysError - - return UserSession{accessBuf, fileState}; + auto drivesBuf = [&] + { + //TODO: remove migration code at some time! 2021-05-15 + if (version <= 4) //fully discard old state due to revamped shared drive handling + return makeSharedRef(accessBuf.ref()); //throw SysError + else + return makeSharedRef(streamInBody, accessBuf.ref()); //throw SysError + }(); + return UserSession{accessBuf, drivesBuf}; } } catch (const SysError& e) @@ -2611,7 +2693,7 @@ private: struct UserSession { SharedRef accessBuf; - SharedRef fileState; + SharedRef drivesBuf; }; struct SessionHolder @@ -2625,13 +2707,13 @@ private: const Zstring configDirPath_; }; //========================================================================================== -constinit2 Global globalGdriveSessions; +constinit Global globalGdriveSessions; //========================================================================================== -GdrivePersistentSessions::AsyncAccessInfo accessGlobalFileState(const std::string& accountEmail, const std::function& useFileState /*throw X*/) //throw SysError, X +GdrivePersistentSessions::AsyncAccessInfo accessGlobalFileState(const GdriveLogin& login, const std::function& useFileState /*throw X*/) //throw SysError, X { if (const std::shared_ptr gps = globalGdriveSessions.get()) - return gps->accessGlobalFileState(accountEmail, useFileState); //throw SysError, X + return gps->accessGlobalFileState(login, useFileState); //throw SysError, X throw SysError(formatSystemError("accessGlobalFileState", L"", L"Function call not allowed during init/shutdown.")); } @@ -2654,9 +2736,9 @@ struct GetDirDetails { std::string folderId; std::optional> childItemsBuf; - const GdrivePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(folderPath_.gdriveLogin.email, [&](GdriveFileState& fileState) //throw SysError + const GdrivePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(folderPath_.gdriveLogin, [&](GdriveFileState& fileState) //throw SysError { - const auto& [itemId, itemDetails] = fileState.getFileAttributes(folderPath_.gdriveLogin.sharedDriveName, folderPath_.itemPath, true /*followLeafShortcut*/); //throw SysError + const auto& [itemId, itemDetails] = fileState.getFileAttributes(folderPath_.itemPath, true /*followLeafShortcut*/); //throw SysError if (itemDetails.type != GdriveItemType::folder) //check(!) or readFolderContent() will return empty (without failing!) throw SysError(replaceCpy(L"%x is not a directory.", L"%x", fmtPath(utfTo(itemDetails.itemName)))); @@ -2670,7 +2752,7 @@ struct GetDirDetails childItemsBuf = readFolderContent(folderId, aai.accessToken); //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.email, [&](GdriveFileState& fileState) //throw SysError + accessGlobalFileState(folderPath_.gdriveLogin, [&](GdriveFileState& fileState) //throw SysError { fileState.notifyFolderContent(aai.stateDelta, folderId, *childItemsBuf); }); @@ -2705,7 +2787,7 @@ struct GetShortcutTargetDetails try { std::optional targetDetailsBuf; - const GdrivePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(shortcutPath_.gdriveLogin.email, [&](GdriveFileState& fileState) //throw SysError + const GdrivePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(shortcutPath_.gdriveLogin, [&](GdriveFileState& fileState) //throw SysError { targetDetailsBuf = fileState.tryGetBufferedItemDetails(shortcutDetails_.targetId); }); @@ -2714,7 +2796,7 @@ struct GetShortcutTargetDetails targetDetailsBuf = getItemDetails(shortcutDetails_.targetId, aai.accessToken); //throw SysError //buffer new file state ASAP - accessGlobalFileState(shortcutPath_.gdriveLogin.email, [&](GdriveFileState& fileState) //throw SysError + accessGlobalFileState(shortcutPath_.gdriveLogin, [&](GdriveFileState& fileState) //throw SysError { fileState.notifyItemUpdated(aai.stateDelta, {shortcutDetails_.targetId, *targetDetailsBuf}); }); @@ -2839,9 +2921,9 @@ struct InputStreamGdrive : public AFS::InputStream std::string fileId; try { - accessToken = accessGlobalFileState(gdrivePath.gdriveLogin.email, [&](GdriveFileState& fileState) //throw SysError + accessToken = accessGlobalFileState(gdrivePath.gdriveLogin, [&](GdriveFileState& fileState) //throw SysError { - fileId = fileState.getItemId(gdrivePath.gdriveLogin.sharedDriveName, gdrivePath.itemPath, true /*followLeafShortcut*/); //throw SysError + fileId = fileState.getItemId(gdrivePath.itemPath, true /*followLeafShortcut*/); //throw SysError }).accessToken; } catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot open file %x."), L"%x", fmtPath(getGdriveDisplayPath(gdrivePath))), e.toString()); } @@ -2883,9 +2965,9 @@ struct InputStreamGdrive : public AFS::InputStream AFS::StreamAttributes attr = {}; try { - accessGlobalFileState(gdrivePath_.gdriveLogin.email, [&](GdriveFileState& fileState) //throw SysError + accessGlobalFileState(gdrivePath_.gdriveLogin, [&](GdriveFileState& fileState) //throw SysError { - const auto& [itemId, itemDetails] = fileState.getFileAttributes(gdrivePath_.gdriveLogin.sharedDriveName, gdrivePath_.itemPath, true /*followLeafShortcut*/); //throw SysError + const auto& [itemId, itemDetails] = fileState.getFileAttributes(gdrivePath_.itemPath, true /*followLeafShortcut*/); //throw SysError attr.modTime = itemDetails.modTime; attr.fileSize = itemDetails.fileSize; attr.filePrint = getGdriveFilePrint(itemId); @@ -2929,15 +3011,15 @@ 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.email, [&](GdriveFileState& fileState) //throw SysError + /*const*/ GdrivePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(gdrivePath.gdriveLogin, [&](GdriveFileState& fileState) //throw SysError { - const GdriveFileState::PathStatus& ps = fileState.getPathStatus(gdrivePath.gdriveLogin.sharedDriveName, gdrivePath.itemPath, false /*followLeafShortcut*/); //throw SysError + const GdriveFileState::PathStatus& ps = fileState.getPathStatus(gdrivePath.itemPath, false /*followLeafShortcut*/); //throw SysError if (ps.relPath.empty()) throw SysError(replaceCpy(_("The name %x is already used by another item."), L"%x", fmtPath(fileName))); if (ps.relPath.size() > 1) //parent folder missing throw SysError(replaceCpy(_("Cannot find %x."), L"%x", - fmtPath(getGdriveDisplayPath({ gdrivePath.gdriveLogin, AfsPath(nativeAppendPaths(ps.existingPath.value, ps.relPath.front()))})))); + fmtPath(getGdriveDisplayPath({gdrivePath.gdriveLogin, AfsPath(nativeAppendPaths(ps.existingPath.value, ps.relPath.front()))})))); parentId = ps.existingItemId; }); @@ -2975,7 +3057,7 @@ struct OutputStreamGdrive : public AFS::OutputStreamImpl newFileItem.details.modTime = *modTime; newFileItem.details.parentIds.push_back(parentId); - accessGlobalFileState(gdrivePath.gdriveLogin.email, [&](GdriveFileState& fileState) //throw SysError + accessGlobalFileState(gdrivePath.gdriveLogin, [&](GdriveFileState& fileState) //throw SysError { fileState.notifyItemCreated(aai.stateDelta, newFileItem); }); @@ -3051,9 +3133,9 @@ public: try { GdriveFileState::PathStatus ps; - accessGlobalFileState(gdriveLogin_.email, [&](GdriveFileState& fileState) //throw SysError + accessGlobalFileState(gdriveLogin_, [&](GdriveFileState& fileState) //throw SysError { - ps = fileState.getPathStatus(gdriveLogin_.sharedDriveName, folderPath, true /*followLeafShortcut*/); //throw SysError + ps = fileState.getPathStatus(folderPath, true /*followLeafShortcut*/); //throw SysError }); if (!ps.relPath.empty()) @@ -3077,9 +3159,9 @@ private: throw SysError(L"Item is device root"); std::string parentId; - accessGlobalFileState(gdriveLogin_.email, [&](GdriveFileState& fileState) //throw SysError + accessGlobalFileState(gdriveLogin_, [&](GdriveFileState& fileState) //throw SysError { - parentId = fileState.getItemId(gdriveLogin_.sharedDriveName, *parentPath, true /*followLeafShortcut*/); //throw SysError + parentId = fileState.getItemId(*parentPath, true /*followLeafShortcut*/); //throw SysError }); return { std::move(parentId), getItemName(afsPath)}; } @@ -3115,9 +3197,9 @@ private: try { GdriveFileState::PathStatus ps; - accessGlobalFileState(gdriveLogin_.email, [&](GdriveFileState& fileState) //throw SysError + accessGlobalFileState(gdriveLogin_, [&](GdriveFileState& fileState) //throw SysError { - ps = fileState.getPathStatus(gdriveLogin_.sharedDriveName, afsPath, false /*followLeafShortcut*/); //throw SysError + ps = fileState.getPathStatus(afsPath, false /*followLeafShortcut*/); //throw SysError }); if (ps.relPath.empty()) switch (ps.existingType) @@ -3144,9 +3226,9 @@ private: const Zstring folderName = getItemName(afsPath); std::string parentId; - const GdrivePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(gdriveLogin_.email, [&](GdriveFileState& fileState) //throw SysError + const GdrivePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(gdriveLogin_, [&](GdriveFileState& fileState) //throw SysError { - const GdriveFileState::PathStatus& ps = fileState.getPathStatus(gdriveLogin_.sharedDriveName, afsPath, false /*followLeafShortcut*/); //throw SysError + const GdriveFileState::PathStatus& ps = fileState.getPathStatus(afsPath, false /*followLeafShortcut*/); //throw SysError if (ps.relPath.empty()) throw SysError(replaceCpy(_("The name %x is already used by another item."), L"%x", fmtPath(folderName))); @@ -3159,7 +3241,7 @@ private: const std::string folderIdNew = gdriveCreateFolderPlain(folderName, parentId, aai.accessToken); //throw SysError //buffer new file state ASAP (don't wait GDRIVE_SYNC_INTERVAL) - accessGlobalFileState(gdriveLogin_.email, [&](GdriveFileState& fileState) //throw SysError + accessGlobalFileState(gdriveLogin_, [&](GdriveFileState& fileState) //throw SysError { fileState.notifyFolderCreated(aai.stateDelta, folderIdNew, folderName, parentId); }); @@ -3171,18 +3253,18 @@ private: { std::string itemId; std::optional parentIdToUnlink; - const GdrivePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(gdriveLogin_.email, [&](GdriveFileState& fileState) //throw SysError + const GdrivePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(gdriveLogin_, [&](GdriveFileState& fileState) //throw SysError { const std::optional parentPath = getParentPath(afsPath); if (!parentPath) throw SysError(L"Item is device root"); GdriveItemDetails itemDetails; - std::tie(itemId, itemDetails) = fileState.getFileAttributes(gdriveLogin_.sharedDriveName, afsPath, false /*followLeafShortcut*/); //throw SysError - assert(std::find(itemDetails.parentIds.begin(), itemDetails.parentIds.end(), fileState.getItemId(gdriveLogin_.sharedDriveName, *parentPath, true /*followLeafShortcut*/)) != itemDetails.parentIds.end()); + std::tie(itemId, itemDetails) = fileState.getFileAttributes(afsPath, false /*followLeafShortcut*/); //throw SysError + assert(std::find(itemDetails.parentIds.begin(), itemDetails.parentIds.end(), fileState.getItemId(*parentPath, true /*followLeafShortcut*/)) != itemDetails.parentIds.end()); //hard-link handling applies to shared files as well: 1. it's the right thing (TM) 2. if we're not the owner: deleting would fail if (itemDetails.parentIds.size() > 1 || itemDetails.owner == FileOwner::other) //FileOwner::other behaves like a followed symlink! i.e. vanishes if owner deletes it! - parentIdToUnlink = fileState.getItemId(gdriveLogin_.sharedDriveName, *parentPath, true /*followLeafShortcut*/); //throw SysError + parentIdToUnlink = fileState.getItemId(*parentPath, true /*followLeafShortcut*/); //throw SysError }); if (parentIdToUnlink) @@ -3190,7 +3272,7 @@ private: gdriveUnlinkParent(itemId, *parentIdToUnlink, aai.accessToken); //throw SysError //buffer new file state ASAP (don't wait GDRIVE_SYNC_INTERVAL) - accessGlobalFileState(gdriveLogin_.email, [&](GdriveFileState& fileState) //throw SysError + accessGlobalFileState(gdriveLogin_, [&](GdriveFileState& fileState) //throw SysError { fileState.notifyParentRemoved(aai.stateDelta, itemId, *parentIdToUnlink); }); @@ -3203,7 +3285,7 @@ private: gdriveMoveToTrash(itemId, aai.accessToken); //throw SysError //buffer new file state ASAP (don't wait GDRIVE_SYNC_INTERVAL) - accessGlobalFileState(gdriveLogin_.email, [&](GdriveFileState& fileState) //throw SysError + accessGlobalFileState(gdriveLogin_, [&](GdriveFileState& fileState) //throw SysError { fileState.notifyItemDeleted(aai.stateDelta, itemId); }); @@ -3260,9 +3342,9 @@ private: try { std::string targetId; - const GdrivePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(gdriveFs.gdriveLogin_.email, [&](GdriveFileState& fileState) //throw SysError + const GdrivePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(gdriveFs.gdriveLogin_, [&](GdriveFileState& fileState) //throw SysError { - const GdriveItemDetails& itemDetails = fileState.getFileAttributes(gdriveFs.gdriveLogin_.sharedDriveName, afsPath, false /*followLeafShortcut*/).second; //throw SysError + const GdriveItemDetails& itemDetails = fileState.getFileAttributes(afsPath, false /*followLeafShortcut*/).second; //throw SysError if (itemDetails.type != GdriveItemType::shortcut) throw SysError(L"Not a Google Drive Shortcut."); @@ -3338,21 +3420,20 @@ private: const Zstring itemNameNew = getItemName(apTarget); std::string itemIdSrc; - time_t modTime = 0; - uint64_t fileSize = 0; - std::string parentIdTrg; - const GdrivePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(gdriveLogin_.email, [&](GdriveFileState& fileState) //throw SysError + GdriveItemDetails itemDetailsSrc; + /*const GdrivePersistentSessions::AsyncAccessInfo aaiSrc =*/ accessGlobalFileState(gdriveLogin_, [&](GdriveFileState& fileState) //throw SysError { - GdriveItemDetails itemDetails; - std::tie(itemIdSrc, itemDetails) = fileState.getFileAttributes(gdriveLogin_.sharedDriveName, afsSource, true /*followLeafShortcut*/); //throw SysError - modTime = itemDetails.modTime; - fileSize = itemDetails.fileSize; + std::tie(itemIdSrc, itemDetailsSrc) = fileState.getFileAttributes(afsSource, true /*followLeafShortcut*/); //throw SysError - assert(itemDetails.type == GdriveItemType::file); //Google Drive *should* fail trying to copy folder: "This file cannot be copied by the user." - if (itemDetails.type != GdriveItemType::file) //=> don't trust + improve error message + assert(itemDetailsSrc.type == GdriveItemType::file); //Google Drive *should* fail trying to copy folder: "This file cannot be copied by the user." + if (itemDetailsSrc.type != GdriveItemType::file) //=> don't trust + improve error message throw SysError(replaceCpy(L"%x is not a file.", L"%x", fmtPath(getItemName(afsSource)))); + }); - const GdriveFileState::PathStatus psTo = fileState.getPathStatus(fsTarget.gdriveLogin_.sharedDriveName, apTarget.afsPath, false /*followLeafShortcut*/); //throw SysError + std::string parentIdTrg; + const GdrivePersistentSessions::AsyncAccessInfo aaiTrg = accessGlobalFileState(fsTarget.gdriveLogin_, [&](GdriveFileState& fileState) //throw SysError + { + const GdriveFileState::PathStatus psTo = fileState.getPathStatus(apTarget.afsPath, false /*followLeafShortcut*/); //throw SysError if (psTo.relPath.empty()) throw SysError(replaceCpy(_("The name %x is already used by another item."), L"%x", fmtPath(itemNameNew))); @@ -3363,25 +3444,25 @@ private: }); //already existing: creates duplicate - const std::string fileIdTrg = gdriveCopyFile(itemIdSrc, parentIdTrg, itemNameNew, modTime, aai.accessToken); //throw SysError + const std::string fileIdTrg = gdriveCopyFile(itemIdSrc, parentIdTrg, itemNameNew, itemDetailsSrc.modTime, aaiTrg.accessToken); //throw SysError //buffer new file state ASAP (don't wait GDRIVE_SYNC_INTERVAL) - accessGlobalFileState(gdriveLogin_.email, [&](GdriveFileState& fileState) //throw SysError + accessGlobalFileState(fsTarget.gdriveLogin_, [&](GdriveFileState& fileState) //throw SysError { GdriveItem newFileItem = {}; newFileItem.itemId = fileIdTrg; newFileItem.details.itemName = itemNameNew; newFileItem.details.type = GdriveItemType::file; - newFileItem.details.owner = FileOwner::me; - newFileItem.details.fileSize = fileSize; - newFileItem.details.modTime = modTime; + newFileItem.details.owner = fsTarget.gdriveLogin_.sharedDriveName.empty() ? FileOwner::me : FileOwner::none; + newFileItem.details.fileSize = itemDetailsSrc.fileSize; + newFileItem.details.modTime = itemDetailsSrc.modTime; newFileItem.details.parentIds.push_back(parentIdTrg); - fileState.notifyItemCreated(aai.stateDelta, newFileItem); + fileState.notifyItemCreated(aaiTrg.stateDelta, newFileItem); }); FileCopyResult result; - result.fileSize = fileSize; - result.modTime = modTime; + result.fileSize = itemDetailsSrc.fileSize; + result.modTime = itemDetailsSrc.modTime; result.sourceFilePrint = getGdriveFilePrint(itemIdSrc); result.targetFilePrint = getGdriveFilePrint(fileIdTrg); /*result.errorModTime = */ @@ -3412,9 +3493,9 @@ private: try { std::string targetId; - accessGlobalFileState(gdriveLogin_.email, [&](GdriveFileState& fileState) //throw SysError + accessGlobalFileState(gdriveLogin_, [&](GdriveFileState& fileState) //throw SysError { - const GdriveItemDetails& itemDetails = fileState.getFileAttributes(gdriveLogin_.sharedDriveName, afsSource, false /*followLeafShortcut*/).second; //throw SysError + const GdriveItemDetails& itemDetails = fileState.getFileAttributes(afsSource, false /*followLeafShortcut*/).second; //throw SysError if (itemDetails.type != GdriveItemType::shortcut) throw SysError(L"Not a Google Drive Shortcut."); @@ -3428,9 +3509,9 @@ private: const Zstring shortcutName = getItemName(apTarget.afsPath); std::string parentId; - const GdrivePersistentSessions::AsyncAccessInfo aaiTrg = accessGlobalFileState(fsTarget.gdriveLogin_.email, [&](GdriveFileState& fileState) //throw SysError + const GdrivePersistentSessions::AsyncAccessInfo aaiTrg = accessGlobalFileState(fsTarget.gdriveLogin_, [&](GdriveFileState& fileState) //throw SysError { - const GdriveFileState::PathStatus& ps = fileState.getPathStatus(fsTarget.gdriveLogin_.sharedDriveName, apTarget.afsPath, false /*followLeafShortcut*/); //throw SysError + const GdriveFileState::PathStatus& ps = fileState.getPathStatus(apTarget.afsPath, false /*followLeafShortcut*/); //throw SysError if (ps.relPath.empty()) throw SysError(replaceCpy(_("The name %x is already used by another item."), L"%x", fmtPath(shortcutName))); @@ -3443,7 +3524,7 @@ private: const std::string shortcutIdNew = gdriveCreateShortcutPlain(shortcutName, parentId, targetId, aaiTrg.accessToken); //throw SysError //buffer new file state ASAP (don't wait GDRIVE_SYNC_INTERVAL) - accessGlobalFileState(fsTarget.gdriveLogin_.email, [&](GdriveFileState& fileState) //throw SysError + accessGlobalFileState(fsTarget.gdriveLogin_, [&](GdriveFileState& fileState) //throw SysError { fileState.notifyShortcutCreated(aaiTrg.stateDelta, shortcutIdNew, shortcutName, parentId, targetId); }); @@ -3465,14 +3546,14 @@ private: L"%y", L'\n' + fmtPath(AFS::getDisplayPath(pathTo))); }; - const GdriveFileSystem& fsTarget = static_cast(pathTo.afsDevice.ref()); - - if (!equalAsciiNoCase(gdriveLogin_.email, fsTarget.gdriveLogin_.email)) + if (std::is_neq(compareDeviceSameAfsType(pathTo.afsDevice.ref()))) throw ErrorMoveUnsupported(generateErrorMsg(), _("Operation not supported between different devices.")); - //else: moving files within account works, e.g. between My Drive <-> shared drives - + //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)? try { + const GdriveFileSystem& fsTarget = static_cast(pathTo.afsDevice.ref()); + //avoid duplicate Google Drive item creation by multiple threads PathAccessLock pal(fsTarget.getGdriveRawPath(pathTo.afsPath), PathBlockType::otherWait); //throw SysError @@ -3484,23 +3565,21 @@ private: if (!parentPathTo ) throw SysError(L"Target is device root"); std::string itemId; - time_t modTime = 0; + GdriveItemDetails itemDetails; std::string parentIdFrom; std::string parentIdTo; - const GdrivePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(gdriveLogin_.email, [&](GdriveFileState& fileState) //throw SysError + const GdrivePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(gdriveLogin_, [&](GdriveFileState& fileState) //throw SysError { - GdriveItemDetails itemDetails; - std::tie(itemId, itemDetails) = fileState.getFileAttributes(gdriveLogin_.sharedDriveName, pathFrom, false /*followLeafShortcut*/); //throw SysError + std::tie(itemId, itemDetails) = fileState.getFileAttributes(pathFrom, false /*followLeafShortcut*/); //throw SysError - modTime = itemDetails.modTime; - parentIdFrom = fileState.getItemId(gdriveLogin_.sharedDriveName, *parentPathFrom, true /*followLeafShortcut*/); //throw SysError + parentIdFrom = fileState.getItemId(*parentPathFrom, true /*followLeafShortcut*/); //throw SysError - const GdriveFileState::PathStatus psTo = fileState.getPathStatus(fsTarget.gdriveLogin_.sharedDriveName, pathTo.afsPath, false /*followLeafShortcut*/); //throw SysError + const GdriveFileState::PathStatus psTo = fileState.getPathStatus(pathTo.afsPath, false /*followLeafShortcut*/); //throw SysError //e.g. changing file name case only => this is not an "already exists" situation! //also: hardlink referenced by two different paths, the source one will be unlinked if (psTo.relPath.empty() && psTo.existingItemId == itemId) - parentIdTo = fileState.getItemId(fsTarget.gdriveLogin_.sharedDriveName, *parentPathTo, true /*followLeafShortcut*/); //throw SysError + parentIdTo = fileState.getItemId(*parentPathTo, true /*followLeafShortcut*/); //throw SysError else { if (psTo.relPath.empty()) @@ -3517,10 +3596,10 @@ private: return; //nothing to do //already existing: creates duplicate - gdriveMoveAndRenameItem(itemId, parentIdFrom, parentIdTo, itemNameNew, modTime, aai.accessToken); //throw SysError + gdriveMoveAndRenameItem(itemId, parentIdFrom, parentIdTo, itemNameNew, itemDetails.modTime, aai.accessToken); //throw SysError //buffer new file state ASAP (don't wait GDRIVE_SYNC_INTERVAL) - accessGlobalFileState(gdriveLogin_.email, [&](GdriveFileState& fileState) //throw SysError + accessGlobalFileState(gdriveLogin_, [&](GdriveFileState& fileState) //throw SysError { fileState.notifyMoveAndRename(aai.stateDelta, itemId, parentIdFrom, parentIdTo, itemNameNew); }); @@ -3565,7 +3644,7 @@ private: try { - const std::string& accessToken = accessGlobalFileState(gdriveLogin_.email, [](GdriveFileState& fileState) {}).accessToken; //throw SysError + const std::string& accessToken = accessGlobalFileState(gdriveLogin_, [](GdriveFileState& fileState) {}).accessToken; //throw SysError return gdriveGetMyDriveFreeSpace(accessToken); //throw SysError; returns < 0 if not available } catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot determine free disk space for %x."), L"%x", fmtPath(getDisplayPath(afsPath))), e.toString()); } @@ -3673,12 +3752,10 @@ std::vector fff::gdriveListSharedDrives(const std:: { try { - std::vector sharedDriveNames; - accessGlobalFileState(accountEmail, [&](GdriveFileState& fileState) //throw SysError - { - sharedDriveNames = fileState.listSharedDrives(); - }); - return sharedDriveNames; + if (const std::shared_ptr gps = globalGdriveSessions.get()) + return gps->listSharedDrives(accountEmail); //throw SysError + + throw SysError(formatSystemError("gdriveListSharedDrives", 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()); } } diff --git a/FreeFileSync/Source/afs/native.cpp b/FreeFileSync/Source/afs/native.cpp index bf9c6ea4..e4285d66 100644 --- a/FreeFileSync/Source/afs/native.cpp +++ b/FreeFileSync/Source/afs/native.cpp @@ -113,7 +113,7 @@ std::vector getDirContentFlat(const Zstring& dirPath) //throw FileError std::vector output; for (;;) { - /* Linux: http://man7.org/linux/man-pages/man3/readdir_r.3.html + /* Linux: https://man7.org/linux/man-pages/man3/readdir_r.3.html "It is recommended that applications use readdir(3) instead of readdir_r" "... in modern implementations (including the glibc implementation), concurrent calls to readdir(3) that specify different directory streams are thread-safe" @@ -312,7 +312,7 @@ void traverseFolderRecursiveNative(const std::vector& notifyDeletionStatus /*throw X*/) override; //throw FileError, X @@ -398,7 +398,7 @@ private: class NativeFileSystem : public AbstractFileSystem { public: - NativeFileSystem(const Zstring& rootPath) : rootPath_(rootPath) {} + explicit NativeFileSystem(const Zstring& rootPath) : rootPath_(rootPath) {} Zstring getNativePath(const AfsPath& afsPath) const { return isNullFileSystem() ? Zstring() : nativeAppendPaths(rootPath_, afsPath.value); } diff --git a/FreeFileSync/Source/afs/sftp.cpp b/FreeFileSync/Source/afs/sftp.cpp index 74dc1657..92a48420 100644 --- a/FreeFileSync/Source/afs/sftp.cpp +++ b/FreeFileSync/Source/afs/sftp.cpp @@ -10,7 +10,6 @@ #include #include #include -//#include #include #include #include @@ -18,6 +17,7 @@ #include "init_curl_libssh2.h" #include "ftp_common.h" #include "abstract_impl.h" + #include using namespace zen; using namespace fff; @@ -178,7 +178,7 @@ std::wstring getSftpDisplayPath(const Zstring& serverName, const AfsPath& afsPat class FatalSshError //=> consider SshSession corrupted and stop use ASAP! same conceptual level like SysError { public: - FatalSshError(const std::wstring& details) : details_(details) {} + explicit FatalSshError(const std::wstring& details) : details_(details) {} const std::wstring& toString() const { return details_; } private: @@ -186,7 +186,7 @@ private: }; -constinit2 Global globalSftpSessionCount; +constinit Global globalSftpSessionCount; GLOBAL_RUN_ONCE(globalSftpSessionCount.set(createUniSessionCounter())); @@ -514,13 +514,7 @@ public: static void waitForTraffic(const std::vector& sshSessions, int timeoutSec) //throw FatalSshError { //reference: session.c: _libssh2_wait_socket() - - SocketType nfds = 0; - fd_set readfds = {}; //covers FD_ZERO - fd_set writefds = {}; // - int readCount = 0; //sigh: using fd_set::fd_count is not portable - int writeCount = 0; // - + std::vector fds; std::chrono::steady_clock::time_point startTimeMin = std::chrono::steady_clock::time_point::max(); for (SshSession* session : sshSessions) @@ -528,57 +522,44 @@ public: assert(::libssh2_session_last_errno(session->sshSession_) == LIBSSH2_ERROR_EAGAIN); assert(session->nbInfo_.commandPending || std::any_of(session->sftpChannels_.begin(), session->sftpChannels_.end(), [](SftpChannelInfo& ci) { return ci.nbInfo.commandPending; })); + pollfd pfd = {session->socket_->get()}; + const int dir = ::libssh2_session_block_directions(session->sshSession_); assert(dir != 0); //we assert a blocked direction after libssh2 returned LIBSSH2_ERROR_EAGAIN! if (dir & LIBSSH2_SESSION_BLOCK_INBOUND) - { - if (readCount++ >= FD_SETSIZE) - throw FatalSshError(formatSystemError("FD_SET(readfds)", L"", _P("Cannot wait on more than 1 connection at a time.", - "Cannot wait on more than %x connections at a time.", FD_SETSIZE))); - FD_SET(session->socket_->get(), &readfds); - } + pfd.events |= POLLIN; if (dir & LIBSSH2_SESSION_BLOCK_OUTBOUND) - { - if (writeCount++ >= FD_SETSIZE) - throw FatalSshError(formatSystemError("FD_SET(writefds)", L"", _P("Cannot wait on more than 1 connection at a time.", - "Cannot wait on more than %x connections at a time.", FD_SETSIZE))); - FD_SET(session->socket_->get(), &writefds); - } + pfd.events |= POLLOUT; - nfds = std::max(nfds, session->socket_->get()); + if (pfd.events != 0) + fds.push_back(pfd); - for (SftpChannelInfo& ci : session->sftpChannels_) + for (const SftpChannelInfo& ci : session->sftpChannels_) if (ci.nbInfo.commandPending) startTimeMin = std::min(startTimeMin, ci.nbInfo.commandStartTime); if (session->nbInfo_.commandPending) startTimeMin = std::min(startTimeMin, session->nbInfo_.commandStartTime); } - if (readCount > 0 || writeCount > 0) + if (!fds.empty()) { assert(startTimeMin != std::chrono::steady_clock::time_point::max()); const auto now = std::chrono::steady_clock::now(); const auto endTime = startTimeMin + std::chrono::seconds(timeoutSec); - if (now > endTime) + if (now >= endTime) return; //time-out! => let next tryNonBlocking() call fail with detailed error! const auto waitTimeMs = std::chrono::duration_cast(endTime - now).count(); - timeval tv = {}; - tv.tv_sec = static_cast(waitTimeMs / 1000); - tv.tv_usec = static_cast(waitTimeMs - tv.tv_sec * 1000) * 1000; - - //WSAPoll is broken, ::poll() on macOS even worse: https://daniel.haxx.se/blog/2012/10/10/wsapoll-is-broken/ - //perf: no significant difference compared to ::WSAPoll() - const int rc = ::select(nfds + 1, //int nfds - readCount > 0 ? &readfds : nullptr, //fd_set* readfds - writeCount > 0 ? &writefds : nullptr, //fd_set* writefds - nullptr, //fd_set* exceptfds - &tv); //struct timeval* timeout - if (rc == 0) - return; //time-out! => let next tryNonBlocking() call fail with detailed error! - if (rc < 0) - //consider SSH sessions corrupted! => isHealthy() will see pending commands - throw FatalSshError(formatSystemError("select", getLastError())); + //is poll() on macOS broken? https://daniel.haxx.se/blog/2016/10/11/poll-on-mac-10-12-is-broken/ + // it seems Daniel only takes issue with "empty" input handling!? => not an issue for us + const char* functionName = "poll"; + const int rv = ::poll(&fds[0], //struct pollfd* fds + fds.size(), //nfds_t nfds + waitTimeMs); //int timeout [ms] + if (rv == 0) //time-out! => let next tryNonBlocking() call fail with detailed error! + return; + if (rv < 0) //consider SSH sessions corrupted! => isHealthy() will see pending commands + throw FatalSshError(formatSystemError(functionName, getLastError())); } else assert(false); } @@ -968,7 +949,7 @@ private: //-------------------------------------------------------------------------------------- UniInitializer globalStartupInitSftp(*globalSftpSessionCount.get()); -constinit2 Global globalSftpSessionManager; //caveat: life time must be subset of static UniInitializer! +constinit Global globalSftpSessionManager; //caveat: life time must be subset of static UniInitializer! //-------------------------------------------------------------------------------------- @@ -1515,7 +1496,7 @@ private: class SftpFileSystem : public AbstractFileSystem { public: - SftpFileSystem(const SftpLogin& login) : login_(login) {} + explicit SftpFileSystem(const SftpLogin& login) : login_(login) {} const SftpLogin& getLogin() const { return login_; } @@ -1774,7 +1755,7 @@ private: [&](const SshSession::Details& sd) //noexcept! { /* LIBSSH2_SFTP_RENAME_NATIVE: "The server is free to do the rename operation in whatever way it chooses. Any other set flags are to be taken as hints to the server." No, thanks! - LIBSSH2_SFTP_RENAME_OVERWRITE: "No overwriting rename in [SFTP] v3/v4" http://www.greenend.org.uk/rjk/sftp/sftpversions.html + LIBSSH2_SFTP_RENAME_OVERWRITE: "No overwriting rename in [SFTP] v3/v4" https://www.greenend.org.uk/rjk/sftp/sftpversions.html Test: LIBSSH2_SFTP_RENAME_OVERWRITE is not honored on freefilesync.org, no matter if LIBSSH2_SFTP_RENAME_NATIVE is set or not => makes sense since SFTP v3 does not honor the additional flags that libssh2 sends! diff --git a/FreeFileSync/Source/base/algorithm.cpp b/FreeFileSync/Source/base/algorithm.cpp index 37b1c21a..0a2cf79f 100644 --- a/FreeFileSync/Source/base/algorithm.cpp +++ b/FreeFileSync/Source/base/algorithm.cpp @@ -1686,6 +1686,30 @@ TempFileBuffer::~TempFileBuffer() } +void TempFileBuffer::createTempFolderPath() //throw FileError +{ + if (tempFolderPath_.empty()) + { + //generate random temp folder path e.g. C:\Users\Zenju\AppData\Local\Temp\FFS-068b2e88 + const uint32_t shortGuid = getCrc32(generateGUID()); //no need for full-blown (pseudo-)random numbers for this one-time invocation + + const Zstring& tempPathTmp = appendSeparator(getTempFolderPath()) + //throw FileError + Zstr("FFS-") + printNumber(Zstr("%08x"), static_cast(shortGuid)); + + createDirectoryIfMissingRecursion(tempPathTmp); //throw FileError + + tempFolderPath_ = tempPathTmp; + } +} + + + Zstring TempFileBuffer::getAndCreateFolderPath() //throw FileError + { + createTempFolderPath(); //throw FileError + return tempFolderPath_; + } + + //returns empty if not available (item not existing, error during copy) Zstring TempFileBuffer::getTempPath(const FileDescriptor& descr) const { @@ -1705,25 +1729,13 @@ void TempFileBuffer::createTempFiles(const std::set& workLoad, P bytesTotal += descr.attr.fileSize; callback.initNewPhase(itemTotal, bytesTotal, ProcessPhase::none); //throw X - //------------------------------------------------------------------------------ - if (tempFolderPath_.empty()) - { const std::wstring errMsg = tryReportingError([&] { - //generate random temp folder path e.g. C:\Users\Zenju\AppData\Local\Temp\FFS-068b2e88 - const uint32_t shortGuid = getCrc32(generateGUID()); //no need for full-blown (pseudo-)random numbers for this one-time invocation - - const Zstring& tempPathTmp = appendSeparator(getTempFolderPath()) + //throw FileError - Zstr("FFS-") + printNumber(Zstr("%08x"), static_cast(shortGuid)); - - createDirectoryIfMissingRecursion(tempPathTmp); //throw FileError - - tempFolderPath_ = tempPathTmp; + createTempFolderPath(); //throw FileError }, callback); //throw X if (!errMsg.empty()) return; - } for (const FileDescriptor& descr : workLoad) { diff --git a/FreeFileSync/Source/base/algorithm.h b/FreeFileSync/Source/base/algorithm.h index 7b2f8a84..75b7e7a6 100644 --- a/FreeFileSync/Source/base/algorithm.h +++ b/FreeFileSync/Source/base/algorithm.h @@ -90,15 +90,20 @@ public: TempFileBuffer() {} ~TempFileBuffer(); + Zstring getAndCreateFolderPath(); //throw FileError + 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& workLoad, ProcessCallback& callback); + private: TempFileBuffer (const TempFileBuffer&) = delete; TempFileBuffer& operator=(const TempFileBuffer&) = delete; +void createTempFolderPath(); //throw FileError + std::map tempFilePaths_; Zstring tempFolderPath_; }; diff --git a/FreeFileSync/Source/config.cpp b/FreeFileSync/Source/config.cpp index 3b9311a6..c9383329 100644 --- a/FreeFileSync/Source/config.cpp +++ b/FreeFileSync/Source/config.cpp @@ -2426,6 +2426,8 @@ void writeConfig(const XmlGlobalSettings& cfg, XmlOut& out) out["FolderHistory" ].attribute("MaxSize", cfg.folderHistoryMax); + warn_static("csvFileLastSelected is obsolete: get rid?") + out["CsvExport" ].attribute("LastSelected", cfg.csvFileLastSelected); out["SftpKeyFile"].attribute("LastSelected", cfg.sftpKeyFileLastSelected); diff --git a/FreeFileSync/Source/localization.cpp b/FreeFileSync/Source/localization.cpp index dacd5711..e2406bdf 100644 --- a/FreeFileSync/Source/localization.cpp +++ b/FreeFileSync/Source/localization.cpp @@ -442,8 +442,10 @@ public: //const char* currentLocale = std::setlocale(LC_ALL, nullptr); //call std::setlocale()? - //wxWidgets overwrites the default locale "C" with a locale matching sysLng_, e.g. "en_US.UTF-8" - //which may be *different* from actual user-preferred locale as set up in "Region & Language/Formats"! => fix: + //Linux: wxWidgets overwrites the default locale "C" with a locale matching sysLng_, e.g. "en_US.UTF-8" + // which may be *different* from actual user-preferred locale as set up in "Region & Language/Formats"! => fix: + + //Windows: apparently needed: https://freefilesync.org/forum/viewtopic.php?t=8455 [[maybe_unused]] const char* newLocale = std::setlocale(LC_ALL, "" /*== user-preferred locale*/); assert(newLocale); diff --git a/FreeFileSync/Source/parse_plural.h b/FreeFileSync/Source/parse_plural.h index 67f70b27..2484b265 100644 --- a/FreeFileSync/Source/parse_plural.h +++ b/FreeFileSync/Source/parse_plural.h @@ -67,7 +67,7 @@ private: //--------------------------- implementation --------------------------- //https://www.gnu.org/software/hello/manual/gettext/Plural-forms.html -//http://translate.sourceforge.net/wiki/l10n/pluralforms +//https://translate.sourceforge.net/wiki/l10n/pluralforms /* Grammar for Plural forms parser ------------------------------- diff --git a/FreeFileSync/Source/ui/cfg_grid.cpp b/FreeFileSync/Source/ui/cfg_grid.cpp index 98a8d50b..2f1f1776 100644 --- a/FreeFileSync/Source/ui/cfg_grid.cpp +++ b/FreeFileSync/Source/ui/cfg_grid.cpp @@ -6,7 +6,6 @@ #include "cfg_grid.h" #include -//#include #include #include #include diff --git a/FreeFileSync/Source/ui/main_dlg.cpp b/FreeFileSync/Source/ui/main_dlg.cpp index 837250ab..a5d8d1c1 100644 --- a/FreeFileSync/Source/ui/main_dlg.cpp +++ b/FreeFileSync/Source/ui/main_dlg.cpp @@ -1971,7 +1971,7 @@ void MainDialog::onGridKeyEvent(wxKeyEvent& event, Grid& grid, bool leftSide) { case 'C': case WXK_INSERT: //CTRL + C || CTRL + INS - copySelectionToClipboard({ m_gridMainL, m_gridMainR} ); + copySelectionToClipboard({m_gridMainL, m_gridMainR}); return; // -> swallow event! don't allow default grid commands! case 'T': //CTRL + T @@ -2882,15 +2882,20 @@ void MainDialog::cfgHistoryRemoveObsolete(const std::vector& filePaths) } -void MainDialog::updateUnsavedCfgStatus() +std::vector MainDialog::getJobNames() const { - const Zstring activeCfgFilePath = activeConfigFiles_.size() == 1 && !equalNativePath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstring(); - std::vector jobNames; for (const Zstring& cfgFilePath : activeConfigFiles_) jobNames.push_back(equalNativePath(cfgFilePath, lastRunConfigPath_) ? L'[' + _("Last session") + L']' : extractJobName(cfgFilePath)); + return jobNames; +} + + +void MainDialog::updateUnsavedCfgStatus() +{ + const Zstring activeCfgFilePath = activeConfigFiles_.size() == 1 && !equalNativePath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstring(); const bool haveUnsavedCfg = lastSavedCfg_ != getConfig(); @@ -2918,9 +2923,10 @@ void MainDialog::updateUnsavedCfgStatus() title += utfTo(activeCfgFilePath); else if (activeConfigFiles_.size() > 1) { - title += jobNames[0]; - std::for_each(jobNames.begin() + 1, jobNames.end(), [&](const std::wstring& jobName) - { title += L" + " + jobName; }); + for (const std::wstring& jobName : getJobNames()) + title += jobName + L" + "; + if (endsWith(title, L" + ")) + title.resize(title.size() - 3); } else { @@ -4117,7 +4123,7 @@ void MainDialog::updateGui() updateTopButton(*m_buttonSync, loadImage("start_sync"), getVariantName(syncVar), syncVarIconName, folderCmp_.empty()); m_panelTopButtons->Layout(); - m_menuItemExportList->Enable(!folderCmp_.empty()); //a CSV without even folder names confuses users: https://freefilesync.org/forum/viewtopic.php?t=4787 + m_menuItemExportList->Enable(!folderCmp_.empty()); //empty CSV confuses users: https://freefilesync.org/forum/viewtopic.php?t=4787 //auiMgr_.Update(); -> doesn't seem to be needed } @@ -4231,12 +4237,6 @@ void MainDialog::onStartSync(wxCommandEvent& event) const std::chrono::system_clock::time_point syncStartTime = std::chrono::system_clock::now(); - std::vector jobNames; - for (const Zstring& cfgFilePath : activeConfigFiles_) - jobNames.push_back(equalNativePath(cfgFilePath, lastRunConfigPath_) ? - L'[' + _("Last session") + L']' : - extractJobName(cfgFilePath)); - using FinalRequest = StatusHandlerFloatingDialog::FinalRequest; FinalRequest finalRequest = FinalRequest::none; { @@ -4245,7 +4245,7 @@ void MainDialog::onStartSync(wxCommandEvent& event) //run this->enableGuiElements() BEFORE "finalRequest" buf AFTER StatusHandlerFloatingDialog::reportResults() //class handling status updates and error messages - StatusHandlerFloatingDialog statusHandler(this, jobNames, syncStartTime, + StatusHandlerFloatingDialog statusHandler(this, getJobNames(), syncStartTime, guiCfg.mainCfg.ignoreErrors, guiCfg.mainCfg.autoRetryCount, guiCfg.mainCfg.autoRetryDelay, @@ -5470,20 +5470,6 @@ void MainDialog::onMenuOptions(wxCommandEvent& event) void MainDialog::onMenuExportFileList(wxCommandEvent& event) { - std::optional defaultFolderPath = getParentFolderPath(globalCfg_.csvFileLastSelected); - - const Zstring defaultFileName = !globalCfg_.csvFileLastSelected.empty() ? - afterLast(globalCfg_.csvFileLastSelected, FILE_NAME_SEPARATOR, IfNotFoundReturn::all) : - Zstr("FileList.csv"); - - wxFileDialog fileSelector(this, wxString() /*message*/, utfTo(defaultFolderPath ? *defaultFolderPath : Zstr("")), utfTo(defaultFileName), - _("Comma-separated values") + L" (*.csv)|*.csv" + L"|" +_("All files") + L" (*.*)|*", - wxFD_SAVE | wxFD_OVERWRITE_PROMPT); - if (fileSelector.ShowModal() != wxID_OK) - return; - const Zstring csvFilePath = globalCfg_.csvFileLastSelected = utfTo(fileSelector.GetPath()); - //--------------------------------------------------------------------- - wxBusyCursor dummy; //https://en.wikipedia.org/wiki/Comma-separated_values @@ -5502,10 +5488,10 @@ void MainDialog::onMenuExportFileList(wxCommandEvent& event) return std::move(tmp); }; + //generate header std::string header; //perf: wxString doesn't model exponential growth and so is out, std::string doesn't give performance guarantee! header += BYTE_ORDER_MARK_UTF8; - //base folders header += fmtValue(_("Folder Pairs")) + LINE_BREAK; std::for_each(begin(folderCmp_), end(folderCmp_), [&](BaseFolderPair& baseFolder) { @@ -5514,7 +5500,6 @@ void MainDialog::onMenuExportFileList(wxCommandEvent& event) }); header += LINE_BREAK; - //write header auto provLeft = m_gridMainL->getDataProvider(); auto provCenter = m_gridMainC->getDataProvider(); auto provRight = m_gridMainR->getDataProvider(); @@ -5553,15 +5538,27 @@ void MainDialog::onMenuExportFileList(wxCommandEvent& event) try { + Zstring title = Zstr("FreeFileSync"); + if (const std::vector& jobNames = getJobNames(); + !jobNames.empty()) + { + title = utfTo(jobNames[0]); + std::for_each(jobNames.begin() + 1, jobNames.end(), [&](const std::wstring& jobName) + { title += Zstr(" + ") + utfTo(jobName); }); + } + + const Zstring shortGuid = printNumber(Zstr("%04x"), static_cast(getCrc16(generateGUID()))); + const Zstring csvFilePath = appendSeparator(tempFileBuf_.getAndCreateFolderPath()) + //throw FileError + title + Zstr("~") + shortGuid + Zstr(".csv"); + + TempFileOutput fileOut(csvFilePath, nullptr /*notifyUnbufferedIO*/); //throw FileError fileOut.write(&header[0], header.size()); //throw FileError, (X) - //main grid: write rows one after the other instead of creating one big string: memory allocation might fail; think 1 million rows! - /* - performance test case "export 600.000 rows" to CSV: - aproach 1. assemble single temporary string, then write file: 4.6s - aproach 2. write to buffered file output directly for each row: 6.4s - */ + /* main grid: write rows one after the other instead of creating one big string: memory allocation might fail; think 1 million rows! + performance test case "export 600.000 rows" to CSV: + aproach 1. assemble single temporary string, then write file: 4.6s + aproach 2. write to buffered file output directly for each row: 6.4s */ std::string buffer; const size_t rowCount = m_gridMainL->getRowCount(); for (size_t row = 0; row < rowCount; ++row) @@ -5590,6 +5587,8 @@ void MainDialog::onMenuExportFileList(wxCommandEvent& event) } fileOut.commit(); //throw FileError, (X) + openWithDefaultApp(csvFilePath); //throw FileError + flashStatusInformation(_("File list exported")); } catch (const FileError& e) diff --git a/FreeFileSync/Source/ui/main_dlg.h b/FreeFileSync/Source/ui/main_dlg.h index dc0ea8d1..2064d156 100644 --- a/FreeFileSync/Source/ui/main_dlg.h +++ b/FreeFileSync/Source/ui/main_dlg.h @@ -104,6 +104,8 @@ private: void updateStatistics(); // more fine-grained updaters void updateUnsavedCfgStatus(); // + std::vector getJobNames() const; + //context menu functions std::vector getGridSelection(bool fromLeft = true, bool fromRight = true) const; std::vector getTreeSelection() const; diff --git a/FreeFileSync/Source/ui/small_dlgs.cpp b/FreeFileSync/Source/ui/small_dlgs.cpp index b691547a..f1d7c501 100644 --- a/FreeFileSync/Source/ui/small_dlgs.cpp +++ b/FreeFileSync/Source/ui/small_dlgs.cpp @@ -815,7 +815,7 @@ CopyToDialog::CopyToDialog(wxWindow* parent, it re-enables all windows that are supposed to be disabled during the current modal loop! This only affects Ubuntu/wxGTK! No such issue on Debian/wxGTK or Suse/wxGTK => another Unity problem like the following? - http://trac.wxwidgets.org/ticket/14823 "Menu not disabled when showing modal dialogs in wxGTK under Unity" */ + https://trac.wxwidgets.org/ticket/14823 "Menu not disabled when showing modal dialogs in wxGTK under Unity" */ const auto& [itemList, itemCount] = getSelectedItemsAsString(rowsOnLeft, rowsOnRight); @@ -941,7 +941,7 @@ DeleteDialog::DeleteDialog(wxWindow* parent, it re-enables all windows that are supposed to be disabled during the current modal loop! This only affects Ubuntu/wxGTK! No such issue on Debian/wxGTK or Suse/wxGTK => another Unity problem like the following? - http://trac.wxwidgets.org/ticket/14823 "Menu not disabled when showing modal dialogs in wxGTK under Unity" */ + https://trac.wxwidgets.org/ticket/14823 "Menu not disabled when showing modal dialogs in wxGTK under Unity" */ m_checkBoxUseRecycler->SetValue(useRecycleBin); diff --git a/FreeFileSync/Source/version/version.h b/FreeFileSync/Source/version/version.h index dad288ec..b34f3ea4 100644 --- a/FreeFileSync/Source/version/version.h +++ b/FreeFileSync/Source/version/version.h @@ -3,7 +3,7 @@ namespace fff { -const char ffsVersion[] = "11.10"; //internal linkage! +const char ffsVersion[] = "11.11"; //internal linkage! const char FFS_VERSION_SEPARATOR = '.'; } diff --git a/libcurl/curl_wrap.h b/libcurl/curl_wrap.h index 2c445771..2ab52d47 100644 --- a/libcurl/curl_wrap.h +++ b/libcurl/curl_wrap.h @@ -136,11 +136,12 @@ std::wstring formatCurlStatusCode(CURLcode sc) ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_RECURSIVE_API_CALL); ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_AUTH_ERROR); ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_HTTP3); - ZEN_CHECK_CASE_FOR_CONSTANT(CURL_LAST); ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_QUIC_CONNECT_ERROR); ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_PROXY); + ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SSL_CLIENTCERT); + ZEN_CHECK_CASE_FOR_CONSTANT(CURL_LAST); } - static_assert(CURL_LAST == CURLE_PROXY + 1); + static_assert(CURL_LAST == CURLE_SSL_CLIENTCERT + 1); return replaceCpy(L"Curl status %x", L"%x", numberTo(static_cast(sc))); } diff --git a/wx+/file_drop.h b/wx+/file_drop.h index e5de8f95..68024091 100644 --- a/wx+/file_drop.h +++ b/wx+/file_drop.h @@ -18,7 +18,7 @@ namespace zen { /* register simple file drop event (without issue of freezing dialogs and without wxFileDropTarget overdesign) CAVEAT: a drop target window must not be directly or indirectly contained within a wxStaticBoxSizer until the following wxGTK bug - is fixed. According to wxWidgets release cycles this is expected to be: never http://trac.wxwidgets.org/ticket/2763 + is fixed. According to wxWidgets release cycles this is expected to be: never https://trac.wxwidgets.org/ticket/2763 1. setup a window to emit EVENT_DROP_FILE: - simple file system paths: setupFileDrop diff --git a/zen/file_access.cpp b/zen/file_access.cpp index fb770f19..db4f2505 100644 --- a/zen/file_access.cpp +++ b/zen/file_access.cpp @@ -668,7 +668,7 @@ FileCopyResult zen::copyNewFile(const Zstring& sourceFile, const Zstring& target /* we cannot set the target file times (::futimes) while the file descriptor is still open after a write operation: this triggers bugs on Samba shares where the modification time is set to current time instead. Linux: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=340236 - http://comments.gmane.org/gmane.linux.file-systems.cifs/2854 + http://comments.gmane.org/gmane.linux.file-systems.cifs/2854 macOS: https://freefilesync.org/forum/viewtopic.php?t=356 */ setWriteTimeNative(targetFile, sourceInfo.st_mtim, ProcSymlink::follow); //throw FileError } diff --git a/zen/i18n.h b/zen/i18n.h index 160b9625..31bb3df8 100644 --- a/zen/i18n.h +++ b/zen/i18n.h @@ -58,7 +58,7 @@ std::shared_ptr getTranslator(); namespace impl { //getTranslator() may be called even after static objects of this translation unit are destroyed! -inline constinit2 Global globalTranslationHandler; +inline constinit Global globalTranslationHandler; } inline diff --git a/zen/legacy_compiler.h b/zen/legacy_compiler.h index 66b750c9..50d340ca 100644 --- a/zen/legacy_compiler.h +++ b/zen/legacy_compiler.h @@ -24,15 +24,9 @@ namespace std { - - } //--------------------------------------------------------------------------------- -//constinit - #define constinit2 constinit //GCC, clang have it - - namespace zen { double fromChars(const char* first, const char* last); diff --git a/zen/string_base.h b/zen/string_base.h index f0899433..5c17fb47 100644 --- a/zen/string_base.h +++ b/zen/string_base.h @@ -10,7 +10,7 @@ #include #include #include "string_tools.h" -#include "legacy_compiler.h" //constinit2 +#include "legacy_compiler.h" //constinit //Zbase - a policy based string class optimizing performance and flexibility @@ -209,7 +209,7 @@ private: static_assert(offsetof(GlobalEmptyString, nullTerm) - offsetof(GlobalEmptyString, descr) == sizeof(Descriptor), "no gap!"); static_assert(std::is_trivially_destructible_v, "this memory needs to live forever"); - inline static constinit2 GlobalEmptyString globalEmptyString; //constinit: dodge static initialization order fiasco! + inline static constinit GlobalEmptyString globalEmptyString; //constinit: dodge static initialization order fiasco! }; diff --git a/zen/string_tools.h b/zen/string_tools.h index 8150df05..5f9273c9 100644 --- a/zen/string_tools.h +++ b/zen/string_tools.h @@ -568,9 +568,8 @@ template inline S printNumber(const T& format, const Num& number) //format a single number using ::sprintf { #ifdef __cpp_lib_format -#error refactor +#error refactor? #endif - static_assert(std::is_same_v, GetCharTypeT>); const int BUFFER_SIZE = 128; diff --git a/zen/time.h b/zen/time.h index b3b903b6..9b5f670e 100644 --- a/zen/time.h +++ b/zen/time.h @@ -159,11 +159,23 @@ TimeComp getUtcTime2(time_t utc) { //1. convert: seconds since year 1: //... + //assert(time_t is signed) //TODO: what if < 0? long long remDays = utc / (24 * 3600); long long remSecs = utc % (24 * 3600); + //days per year + const int dpYearStd = 365; + const int dpYearLeap = dpYearStd + 1; + const int dp4Years = 3 * dpYearStd + dpYearLeap; + const int dp100YearsStd = 25 * dp4Years - 1; //no leap days for centuries... + const int dp100YearsExc = 25 * dp4Years; //...except if divisible by 400 + const int dp400Years = 3 * dp100YearsStd + dp100YearsExc; + + + + const int daysPer4Years = 4 * 365 /*usual days per year*/ + 1 /*including leap day*/; const int daysPerYear = 365; //non-leap const int daysPer100Years = 25 * daysPer4Years - 1; @@ -197,6 +209,8 @@ TimeComp getUtcTime2(time_t utc) + const char daysPerMonth[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + //first four years of century: -- cgit