summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorB. Stack <bgstack15@gmail.com>2023-09-13 13:49:38 -0400
committerB. Stack <bgstack15@gmail.com>2023-09-13 13:49:38 -0400
commit7ed2158034ef5c26eed7fed9aa3c118d79a06fa8 (patch)
tree979e6628193f88d2140e722a3893321904869896
parentadd extra_log.h (diff)
downloadFreeFileSync-7ed2158034ef5c26eed7fed9aa3c118d79a06fa8.tar.gz
FreeFileSync-7ed2158034ef5c26eed7fed9aa3c118d79a06fa8.tar.bz2
FreeFileSync-7ed2158034ef5c26eed7fed9aa3c118d79a06fa8.zip
add upstream 13.013.0
-rw-r--r--Changelog.txt13
-rw-r--r--FreeFileSync/Build/Resources/Icons.zipbin360707 -> 360142 bytes
-rw-r--r--FreeFileSync/Build/Resources/Languages.zipbin543736 -> 558159 bytes
-rw-r--r--FreeFileSync/Build/Resources/cacert.pem190
-rw-r--r--FreeFileSync/Source/afs/abstract.h23
-rw-r--r--FreeFileSync/Source/afs/ftp.cpp11
-rw-r--r--FreeFileSync/Source/afs/gdrive.cpp9
-rw-r--r--FreeFileSync/Source/afs/native.cpp18
-rw-r--r--FreeFileSync/Source/afs/sftp.cpp9
-rw-r--r--FreeFileSync/Source/application.cpp2
-rw-r--r--FreeFileSync/Source/base/algorithm.cpp725
-rw-r--r--FreeFileSync/Source/base/algorithm.h31
-rw-r--r--FreeFileSync/Source/base/cmp_filetime.h8
-rw-r--r--FreeFileSync/Source/base/comparison.cpp285
-rw-r--r--FreeFileSync/Source/base/db_file.cpp102
-rw-r--r--FreeFileSync/Source/base/db_file.h32
-rw-r--r--FreeFileSync/Source/base/dir_exist_async.h2
-rw-r--r--FreeFileSync/Source/base/dir_lock.cpp4
-rw-r--r--FreeFileSync/Source/base/file_hierarchy.cpp438
-rw-r--r--FreeFileSync/Source/base/file_hierarchy.h968
-rw-r--r--FreeFileSync/Source/base/multi_rename.cpp6
-rw-r--r--FreeFileSync/Source/base/multi_rename.h6
-rw-r--r--FreeFileSync/Source/base/parallel_scan.cpp12
-rw-r--r--FreeFileSync/Source/base/soft_filter.h12
-rw-r--r--FreeFileSync/Source/base/status_handler_impl.h8
-rw-r--r--FreeFileSync/Source/base/structures.cpp220
-rw-r--r--FreeFileSync/Source/base/structures.h194
-rw-r--r--FreeFileSync/Source/base/synchronization.cpp375
-rw-r--r--FreeFileSync/Source/base/synchronization.h14
-rw-r--r--FreeFileSync/Source/base/versioning.cpp7
-rw-r--r--FreeFileSync/Source/config.cpp244
-rw-r--r--FreeFileSync/Source/icon_buffer.cpp10
-rw-r--r--FreeFileSync/Source/localization.cpp8
-rw-r--r--FreeFileSync/Source/parse_lng.h94
-rw-r--r--FreeFileSync/Source/ui/cfg_grid.cpp2
-rw-r--r--FreeFileSync/Source/ui/file_grid.cpp150
-rw-r--r--FreeFileSync/Source/ui/file_view.cpp66
-rw-r--r--FreeFileSync/Source/ui/file_view.h4
-rw-r--r--FreeFileSync/Source/ui/folder_pair.h2
-rw-r--r--FreeFileSync/Source/ui/gui_generated.cpp228
-rw-r--r--FreeFileSync/Source/ui/gui_generated.h57
-rw-r--r--FreeFileSync/Source/ui/gui_status_handler.cpp2
-rw-r--r--FreeFileSync/Source/ui/log_panel.cpp2
-rw-r--r--FreeFileSync/Source/ui/main_dlg.cpp479
-rw-r--r--FreeFileSync/Source/ui/main_dlg.h4
-rw-r--r--FreeFileSync/Source/ui/rename_dlg.cpp264
-rw-r--r--FreeFileSync/Source/ui/small_dlgs.cpp24
-rw-r--r--FreeFileSync/Source/ui/small_dlgs.h3
-rw-r--r--FreeFileSync/Source/ui/sync_cfg.cpp511
-rw-r--r--FreeFileSync/Source/ui/tree_grid.cpp75
-rw-r--r--FreeFileSync/Source/ui/tree_grid.h2
-rw-r--r--FreeFileSync/Source/ui/version_check.cpp129
-rw-r--r--FreeFileSync/Source/ui/version_check.h7
-rw-r--r--FreeFileSync/Source/version/version.h2
-rw-r--r--wx+/grid.cpp5
-rw-r--r--wx+/grid.h1
-rw-r--r--wx+/image_tools.cpp2
-rw-r--r--wx+/popup_dlg_generated.cpp126
-rw-r--r--wx+/popup_dlg_generated.h60
-rw-r--r--wx+/tooltip.cpp6
-rw-r--r--wx+/tooltip.h1
-rw-r--r--zen/file_access.cpp21
-rw-r--r--zen/http.cpp12
-rw-r--r--zen/zstring.h6
64 files changed, 3709 insertions, 2624 deletions
diff --git a/Changelog.txt b/Changelog.txt
index 50d6e0af..551fd1d2 100644
--- a/Changelog.txt
+++ b/Changelog.txt
@@ -1,3 +1,16 @@
+FreeFileSync 13.0 [2023-09-12]
+------------------------------
+Rename (multiple) files manually (F2 key)
+Configure individual directions for DB-based sync
+Detect moved files with "Update" sync variant (requires sync.ffs_db files)
+Update variant: Do not restore files that were deleted on target
+Distinguish file renames from file moves and simplify grid display
+Fixed ERROR_NOT_SUPPORTED when copying files with NTFS extended attributes
+Fixed error during process initialization while connecting with quick launch
+Avoid redundant file reopen when setting file times during copy
+Set working directory to match FFS configuration file when double-clicking (Linux)
+
+
FreeFileSync 12.5 [2023-07-21]
------------------------------
Merge logs of individual steps (comparison, manual operation, sync)
diff --git a/FreeFileSync/Build/Resources/Icons.zip b/FreeFileSync/Build/Resources/Icons.zip
index 12822e10..dc23279f 100644
--- a/FreeFileSync/Build/Resources/Icons.zip
+++ b/FreeFileSync/Build/Resources/Icons.zip
Binary files differ
diff --git a/FreeFileSync/Build/Resources/Languages.zip b/FreeFileSync/Build/Resources/Languages.zip
index 8a828e86..3437d30b 100644
--- a/FreeFileSync/Build/Resources/Languages.zip
+++ b/FreeFileSync/Build/Resources/Languages.zip
Binary files differ
diff --git a/FreeFileSync/Build/Resources/cacert.pem b/FreeFileSync/Build/Resources/cacert.pem
index 6b93dc34..9551dfd8 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 May 30 03:12:04 2023 GMT
+## Certificate data from Mozilla as of: Tue Aug 22 03:12:04 2023 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.29.
-## SHA256: c47475103fb05bb562bbadff0d1e72346b03236154e1448a6ca191b740f83507
+## SHA256: 0ff137babc6a5561a9cfbe9f29558972e5b528202681b7d3803d03a3e82922bd
##
@@ -3222,55 +3222,6 @@ AwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozmut6Dacpps6kFtZaSF4fC0urQe87YQVt8
rgIwRt7qy12a7DLCZRawTDBcMPPaTnOGBtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR
-----END CERTIFICATE-----
-E-Tugra Global Root CA RSA v3
-=============================
------BEGIN CERTIFICATE-----
-MIIF8zCCA9ugAwIBAgIUDU3FzRYilZYIfrgLfxUGNPt5EDQwDQYJKoZIhvcNAQELBQAwgYAxCzAJ
-BgNVBAYTAlRSMQ8wDQYDVQQHEwZBbmthcmExGTAXBgNVBAoTEEUtVHVncmEgRUJHIEEuUy4xHTAb
-BgNVBAsTFEUtVHVncmEgVHJ1c3QgQ2VudGVyMSYwJAYDVQQDEx1FLVR1Z3JhIEdsb2JhbCBSb290
-IENBIFJTQSB2MzAeFw0yMDAzMTgwOTA3MTdaFw00NTAzMTIwOTA3MTdaMIGAMQswCQYDVQQGEwJU
-UjEPMA0GA1UEBxMGQW5rYXJhMRkwFwYDVQQKExBFLVR1Z3JhIEVCRyBBLlMuMR0wGwYDVQQLExRF
-LVR1Z3JhIFRydXN0IENlbnRlcjEmMCQGA1UEAxMdRS1UdWdyYSBHbG9iYWwgUm9vdCBDQSBSU0Eg
-djMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCiZvCJt3J77gnJY9LTQ91ew6aEOErx
-jYG7FL1H6EAX8z3DeEVypi6Q3po61CBxyryfHUuXCscxuj7X/iWpKo429NEvx7epXTPcMHD4QGxL
-sqYxYdE0PD0xesevxKenhOGXpOhL9hd87jwH7eKKV9y2+/hDJVDqJ4GohryPUkqWOmAalrv9c/SF
-/YP9f4RtNGx/ardLAQO/rWm31zLZ9Vdq6YaCPqVmMbMWPcLzJmAy01IesGykNz709a/r4d+ABs8q
-QedmCeFLl+d3vSFtKbZnwy1+7dZ5ZdHPOrbRsV5WYVB6Ws5OUDGAA5hH5+QYfERaxqSzO8bGwzrw
-bMOLyKSRBfP12baqBqG3q+Sx6iEUXIOk/P+2UNOMEiaZdnDpwA+mdPy70Bt4znKS4iicvObpCdg6
-04nmvi533wEKb5b25Y08TVJ2Glbhc34XrD2tbKNSEhhw5oBOM/J+JjKsBY04pOZ2PJ8QaQ5tndLB
-eSBrW88zjdGUdjXnXVXHt6woq0bM5zshtQoK5EpZ3IE1S0SVEgpnpaH/WwAH0sDM+T/8nzPyAPiM
-bIedBi3x7+PmBvrFZhNb/FAHnnGGstpvdDDPk1Po3CLW3iAfYY2jLqN4MpBs3KwytQXk9TwzDdbg
-h3cXTJ2w2AmoDVf3RIXwyAS+XF1a4xeOVGNpf0l0ZAWMowIDAQABo2MwYTAPBgNVHRMBAf8EBTAD
-AQH/MB8GA1UdIwQYMBaAFLK0ruYt9ybVqnUtdkvAG1Mh0EjvMB0GA1UdDgQWBBSytK7mLfcm1ap1
-LXZLwBtTIdBI7zAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIBAImocn+M684uGMQQ
-gC0QDP/7FM0E4BQ8Tpr7nym/Ip5XuYJzEmMmtcyQ6dIqKe6cLcwsmb5FJ+Sxce3kOJUxQfJ9emN4
-38o2Fi+CiJ+8EUdPdk3ILY7r3y18Tjvarvbj2l0Upq7ohUSdBm6O++96SmotKygY/r+QLHUWnw/q
-ln0F7psTpURs+APQ3SPh/QMSEgj0GDSz4DcLdxEBSL9htLX4GdnLTeqjjO/98Aa1bZL0SmFQhO3s
-SdPkvmjmLuMxC1QLGpLWgti2omU8ZgT5Vdps+9u1FGZNlIM7zR6mK7L+d0CGq+ffCsn99t2HVhjY
-sCxVYJb6CH5SkPVLpi6HfMsg2wY+oF0Dd32iPBMbKaITVaA9FCKvb7jQmhty3QUBjYZgv6Rn7rWl
-DdF/5horYmbDB7rnoEgcOMPpRfunf/ztAmgayncSd6YAVSgU7NbHEqIbZULpkejLPoeJVF3Zr52X
-nGnnCv8PWniLYypMfUeUP95L6VPQMPHF9p5J3zugkaOj/s1YzOrfr28oO6Bpm4/srK4rVJ2bBLFH
-IK+WEj5jlB0E5y67hscMmoi/dkfv97ALl2bSRM9gUgfh1SxKOidhd8rXj+eHDjD/DLsE4mHDosiX
-YY60MGo8bcIHX0pzLz/5FooBZu+6kcpSV3uu1OYP3Qt6f4ueJiDPO++BcYNZ
------END CERTIFICATE-----
-
-E-Tugra Global Root CA ECC v3
-=============================
------BEGIN CERTIFICATE-----
-MIICpTCCAiqgAwIBAgIUJkYZdzHhT28oNt45UYbm1JeIIsEwCgYIKoZIzj0EAwMwgYAxCzAJBgNV
-BAYTAlRSMQ8wDQYDVQQHEwZBbmthcmExGTAXBgNVBAoTEEUtVHVncmEgRUJHIEEuUy4xHTAbBgNV
-BAsTFEUtVHVncmEgVHJ1c3QgQ2VudGVyMSYwJAYDVQQDEx1FLVR1Z3JhIEdsb2JhbCBSb290IENB
-IEVDQyB2MzAeFw0yMDAzMTgwOTQ2NThaFw00NTAzMTIwOTQ2NThaMIGAMQswCQYDVQQGEwJUUjEP
-MA0GA1UEBxMGQW5rYXJhMRkwFwYDVQQKExBFLVR1Z3JhIEVCRyBBLlMuMR0wGwYDVQQLExRFLVR1
-Z3JhIFRydXN0IENlbnRlcjEmMCQGA1UEAxMdRS1UdWdyYSBHbG9iYWwgUm9vdCBDQSBFQ0MgdjMw
-djAQBgcqhkjOPQIBBgUrgQQAIgNiAASOmCm/xxAeJ9urA8woLNheSBkQKczLWYHMjLiSF4mDKpL2
-w6QdTGLVn9agRtwcvHbB40fQWxPa56WzZkjnIZpKT4YKfWzqTTKACrJ6CZtpS5iB4i7sAnCWH/31
-Rs7K3IKjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU/4Ixcj75xGZsrTie0bBRiKWQ
-zPUwHQYDVR0OBBYEFP+CMXI++cRmbK04ntGwUYilkMz1MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjO
-PQQDAwNpADBmAjEA5gVYaWHlLcoNy/EZCL3W/VGSGn5jVASQkZo1kTmZ+gepZpO6yGjUij/67W4W
-Aie3AjEA3VoXK3YdZUKWpqxdinlW2Iob35reX8dQj7FbcQwm32pAAOwzkSFxvmjkI6TZraE3
------END CERTIFICATE-----
-
Security Communication RootCA3
==============================
-----BEGIN CERTIFICATE-----
@@ -3361,3 +3312,140 @@ SR9BIgmwUVJY1is0j8USRhTFiy8shP8sbqjV8QnjAyEUxEM9fMEsxEtqSs3ph+B99iK++kpRuDCK
W9f+qdJUDkpd0m2xQNz0Q9XSSpkZElaA94M04TVOSG0ED1cxMDAtsaqdAzjbBgIxAMvMh1PLet8g
UXOQwKhbYdDFUDn9hf7B43j4ptZLvZuHjw/l1lOWqzzIQNph91Oj9w==
-----END CERTIFICATE-----
+
+Sectigo Public Server Authentication Root E46
+=============================================
+-----BEGIN CERTIFICATE-----
+MIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQswCQYDVQQGEwJH
+QjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBTZXJ2
+ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5
+WjBfMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0
+aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUr
+gQQAIgNiAAR2+pmpbiDt+dd34wc7qNs9Xzjoq1WmVk/WSOrsfy2qw7LFeeyZYX8QeccCWvkEN/U0
+NSt3zn8gj1KjAIns1aeibVvjS5KToID1AZTc8GgHHs3u/iVStSBDHBv+6xnOQ6OjQjBAMB0GA1Ud
+DgQWBBTRItpMWfFLXyY4qp3W7usNw/upYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB
+/zAKBggqhkjOPQQDAwNnADBkAjAn7qRaqCG76UeXlImldCBteU/IvZNeWBj7LRoAasm4PdCkT0RH
+lAFWovgzJQxC36oCMB3q4S6ILuH5px0CMk7yn2xVdOOurvulGu7t0vzCAxHrRVxgED1cf5kDW21U
+SAGKcw==
+-----END CERTIFICATE-----
+
+Sectigo Public Server Authentication Root R46
+=============================================
+-----BEGIN CERTIFICATE-----
+MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBfMQswCQYDVQQG
+EwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBT
+ZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwHhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1
+OTU5WjBfMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T
+ZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwggIiMA0GCSqGSIb3
+DQEBAQUAA4ICDwAwggIKAoICAQCTvtU2UnXYASOgHEdCSe5jtrch/cSV1UgrJnwUUxDaef0rty2k
+1Cz66jLdScK5vQ9IPXtamFSvnl0xdE8H/FAh3aTPaE8bEmNtJZlMKpnzSDBh+oF8HqcIStw+Kxwf
+GExxqjWMrfhu6DtK2eWUAtaJhBOqbchPM8xQljeSM9xfiOefVNlI8JhD1mb9nxc4Q8UBUQvX4yMP
+FF1bFOdLvt30yNoDN9HWOaEhUTCDsG3XME6WW5HwcCSrv0WBZEMNvSE6Lzzpng3LILVCJ8zab5vu
+ZDCQOc2TZYEhMbUjUDM3IuM47fgxMMxF/mL50V0yeUKH32rMVhlATc6qu/m1dkmU8Sf4kaWD5Qaz
+Yw6A3OASVYCmO2a0OYctyPDQ0RTp5A1NDvZdV3LFOxxHVp3i1fuBYYzMTYCQNFu31xR13NgESJ/A
+wSiItOkcyqex8Va3e0lMWeUgFaiEAin6OJRpmkkGj80feRQXEgyDet4fsZfu+Zd4KKTIRJLpfSYF
+plhym3kT2BFfrsU4YjRosoYwjviQYZ4ybPUHNs2iTG7sijbt8uaZFURww3y8nDnAtOFr94MlI1fZ
+EoDlSfB1D++N6xybVCi0ITz8fAr/73trdf+LHaAZBav6+CuBQug4urv7qv094PPK306Xlynt8xhW
+6aWWrL3DkJiy4Pmi1KZHQ3xtzwIDAQABo0IwQDAdBgNVHQ4EFgQUVnNYZJX5khqwEioEYnmhQBWI
+IUkwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAC9c
+mTz8Bl6MlC5w6tIyMY208FHVvArzZJ8HXtXBc2hkeqK5Duj5XYUtqDdFqij0lgVQYKlJfp/imTYp
+E0RHap1VIDzYm/EDMrraQKFz6oOht0SmDpkBm+S8f74TlH7Kph52gDY9hAaLMyZlbcp+nv4fjFg4
+exqDsQ+8FxG75gbMY/qB8oFM2gsQa6H61SilzwZAFv97fRheORKkU55+MkIQpiGRqRxOF3yEvJ+M
+0ejf5lG5Nkc/kLnHvALcWxxPDkjBJYOcCj+esQMzEhonrPcibCTRAUH4WAP+JWgiH5paPHxsnnVI
+84HxZmduTILA7rpXDhjvLpr3Etiga+kFpaHpaPi8TD8SHkXoUsCjvxInebnMMTzD9joiFgOgyY9m
+pFuiTdaBJQbpdqQACj7LzTWb4OE4y2BThihCQRxEV+ioratF4yUQvNs+ZUH7G6aXD+u5dHn5Hrwd
+Vw1Hr8Mvn4dGp+smWg9WY7ViYG4A++MnESLn/pmPNPW56MORcr3Ywx65LvKRRFHQV80MNNVIIb/b
+E/FmJUNS0nAiNs2fxBx1IK1jcmMGDw4nztJqDby1ORrp0XZ60Vzk50lJLVU3aPAaOpg+VBeHVOmm
+J1CJeyAvP/+/oYtKR5j/K3tJPsMpRmAYQqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL
+-----END CERTIFICATE-----
+
+SSL.com TLS RSA Root CA 2022
+============================
+-----BEGIN CERTIFICATE-----
+MIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQG
+EwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxTU0wuY29tIFRMUyBSU0Eg
+Um9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloXDTQ2MDgxOTE2MzQyMVowTjELMAkGA1UEBhMC
+VVMxGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgUlNBIFJv
+b3QgQ0EgMjAyMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANCkCXJPQIgSYT41I57u
+9nTPL3tYPc48DRAokC+X94xI2KDYJbFMsBFMF3NQ0CJKY7uB0ylu1bUJPiYYf7ISf5OYt6/wNr/y
+7hienDtSxUcZXXTzZGbVXcdotL8bHAajvI9AI7YexoS9UcQbOcGV0insS657Lb85/bRi3pZ7Qcac
+oOAGcvvwB5cJOYF0r/c0WRFXCsJbwST0MXMwgsadugL3PnxEX4MN8/HdIGkWCVDi1FW24IBydm5M
+R7d1VVm0U3TZlMZBrViKMWYPHqIbKUBOL9975hYsLfy/7PO0+r4Y9ptJ1O4Fbtk085zx7AGL0SDG
+D6C1vBdOSHtRwvzpXGk3R2azaPgVKPC506QVzFpPulJwoxJF3ca6TvvC0PeoUidtbnm1jPx7jMEW
+TO6Af77wdr5BUxIzrlo4QqvXDz5BjXYHMtWrifZOZ9mxQnUjbvPNQrL8VfVThxc7wDNY8VLS+YCk
+8OjwO4s4zKTGkH8PnP2L0aPP2oOnaclQNtVcBdIKQXTbYxE3waWglksejBYSd66UNHsef8JmAOSq
+g+qKkK3ONkRN0VHpvB/zagX9wHQfJRlAUW7qglFA35u5CCoGAtUjHBPW6dvbxrB6y3snm/vg1UYk
+7RBLY0ulBY+6uB0rpvqR4pJSvezrZ5dtmi2fgTIFZzL7SAg/2SW4BCUvAgMBAAGjYzBhMA8GA1Ud
+EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU+y437uOEeicuzRk1sTN8/9REQrkwHQYDVR0OBBYEFPsu
+N+7jhHonLs0ZNbEzfP/UREK5MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAjYlt
+hEUY8U+zoO9opMAdrDC8Z2awms22qyIZZtM7QbUQnRC6cm4pJCAcAZli05bg4vsMQtfhWsSWTVTN
+j8pDU/0quOr4ZcoBwq1gaAafORpR2eCNJvkLTqVTJXojpBzOCBvfR4iyrT7gJ4eLSYwfqUdYe5by
+iB0YrrPRpgqU+tvT5TgKa3kSM/tKWTcWQA673vWJDPFs0/dRa1419dvAJuoSc06pkZCmF8NsLzjU
+o3KUQyxi4U5cMj29TH0ZR6LDSeeWP4+a0zvkEdiLA9z2tmBVGKaBUfPhqBVq6+AL8BQx1rmMRTqo
+ENjwuSfr98t67wVylrXEj5ZzxOhWc5y8aVFjvO9nHEMaX3cZHxj4HCUp+UmZKbaSPaKDN7Egkaib
+MOlqbLQjk2UEqxHzDh1TJElTHaE/nUiSEeJ9DU/1172iWD54nR4fK/4huxoTtrEoZP2wAgDHbICi
+vRZQIA9ygV/MlP+7mea6kMvq+cYMwq7FGc4zoWtcu358NFcXrfA/rs3qr5nsLFR+jM4uElZI7xc7
+P0peYNLcdDa8pUNjyw9bowJWCZ4kLOGGgYz+qxcs+sjiMho6/4UIyYOf8kpIEFR3N+2ivEC+5BB0
+9+Rbu7nzifmPQdjH5FCQNYA+HLhNkNPU98OwoX6EyneSMSy4kLGCenROmxMmtNVQZlR4rmA=
+-----END CERTIFICATE-----
+
+SSL.com TLS ECC Root CA 2022
+============================
+-----BEGIN CERTIFICATE-----
+MIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQswCQYDVQQGEwJV
+UzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxTU0wuY29tIFRMUyBFQ0MgUm9v
+dCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2MDgxOTE2MzM0N1owTjELMAkGA1UEBhMCVVMx
+GDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgRUNDIFJvb3Qg
+Q0EgMjAyMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABEUpNXP6wrgjzhR9qLFNoFs27iosU8NgCTWy
+JGYmacCzldZdkkAZDsalE3D07xJRKF3nzL35PIXBz5SQySvOkkJYWWf9lCcQZIxPBLFNSeR7T5v1
+5wj4A4j3p8OSSxlUgaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSJjy+j6CugFFR7
+81a4Jl9nOAuc0DAdBgNVHQ4EFgQUiY8vo+groBRUe/NWuCZfZzgLnNAwDgYDVR0PAQH/BAQDAgGG
+MAoGCCqGSM49BAMDA2gAMGUCMFXjIlbp15IkWE8elDIPDAI2wv2sdDJO4fscgIijzPvX6yv/N33w
+7deedWo1dlJF4AIxAMeNb0Igj762TVntd00pxCAgRWSGOlDGxK0tk/UYfXLtqc/ErFc2KAhl3zx5
+Zn6g6g==
+-----END CERTIFICATE-----
+
+Atos TrustedRoot Root CA ECC TLS 2021
+=====================================
+-----BEGIN CERTIFICATE-----
+MIICFTCCAZugAwIBAgIQPZg7pmY9kGP3fiZXOATvADAKBggqhkjOPQQDAzBMMS4wLAYDVQQDDCVB
+dG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgRUNDIFRMUyAyMDIxMQ0wCwYDVQQKDARBdG9zMQswCQYD
+VQQGEwJERTAeFw0yMTA0MjIwOTI2MjNaFw00MTA0MTcwOTI2MjJaMEwxLjAsBgNVBAMMJUF0b3Mg
+VHJ1c3RlZFJvb3QgUm9vdCBDQSBFQ0MgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYT
+AkRFMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEloZYKDcKZ9Cg3iQZGeHkBQcfl+3oZIK59sRxUM6K
+DP/XtXa7oWyTbIOiaG6l2b4siJVBzV3dscqDY4PMwL502eCdpO5KTlbgmClBk1IQ1SQ4AjJn8ZQS
+b+/Xxd4u/RmAo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR2KCXWfeBmmnoJsmo7jjPX
+NtNPojAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIwW5kp85wxtolrbNa9d+F851F+
+uDrNozZffPc8dz7kUK2o59JZDCaOMDtuCCrCp1rIAjEAmeMM56PDr9NJLkaCI2ZdyQAUEv049OGY
+a3cpetskz2VAv9LcjBHo9H1/IISpQuQo
+-----END CERTIFICATE-----
+
+Atos TrustedRoot Root CA RSA TLS 2021
+=====================================
+-----BEGIN CERTIFICATE-----
+MIIFZDCCA0ygAwIBAgIQU9XP5hmTC/srBRLYwiqipDANBgkqhkiG9w0BAQwFADBMMS4wLAYDVQQD
+DCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgUlNBIFRMUyAyMDIxMQ0wCwYDVQQKDARBdG9zMQsw
+CQYDVQQGEwJERTAeFw0yMTA0MjIwOTIxMTBaFw00MTA0MTcwOTIxMDlaMEwxLjAsBgNVBAMMJUF0
+b3MgVHJ1c3RlZFJvb3QgUm9vdCBDQSBSU0EgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNV
+BAYTAkRFMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtoAOxHm9BYx9sKOdTSJNy/BB
+l01Z4NH+VoyX8te9j2y3I49f1cTYQcvyAh5x5en2XssIKl4w8i1mx4QbZFc4nXUtVsYvYe+W/CBG
+vevUez8/fEc4BKkbqlLfEzfTFRVOvV98r61jx3ncCHvVoOX3W3WsgFWZkmGbzSoXfduP9LVq6hdK
+ZChmFSlsAvFr1bqjM9xaZ6cF4r9lthawEO3NUDPJcFDsGY6wx/J0W2tExn2WuZgIWWbeKQGb9Cpt
+0xU6kGpn8bRrZtkh68rZYnxGEFzedUlnnkL5/nWpo63/dgpnQOPF943HhZpZnmKaau1Fh5hnstVK
+PNe0OwANwI8f4UDErmwh3El+fsqyjW22v5MvoVw+j8rtgI5Y4dtXz4U2OLJxpAmMkokIiEjxQGMY
+sluMWuPD0xeqqxmjLBvk1cbiZnrXghmmOxYsL3GHX0WelXOTwkKBIROW1527k2gV+p2kHYzygeBY
+Br3JtuP2iV2J+axEoctr+hbxx1A9JNr3w+SH1VbxT5Aw+kUJWdo0zuATHAR8ANSbhqRAvNncTFd+
+rrcztl524WWLZt+NyteYr842mIycg5kDcPOvdO3GDjbnvezBc6eUWsuSZIKmAMFwoW4sKeFYV+xa
+fJlrJaSQOoD0IJ2azsct+bJLKZWD6TWNp0lIpw9MGZHQ9b8Q4HECAwEAAaNCMEAwDwYDVR0TAQH/
+BAUwAwEB/zAdBgNVHQ4EFgQUdEmZ0f+0emhFdcN+tNzMzjkz2ggwDgYDVR0PAQH/BAQDAgGGMA0G
+CSqGSIb3DQEBDAUAA4ICAQAjQ1MkYlxt/T7Cz1UAbMVWiLkO3TriJQ2VSpfKgInuKs1l+NsW4AmS
+4BjHeJi78+xCUvuppILXTdiK/ORO/auQxDh1MoSf/7OwKwIzNsAQkG8dnK/haZPso0UvFJ/1TCpl
+Q3IM98P4lYsU84UgYt1UU90s3BiVaU+DR3BAM1h3Egyi61IxHkzJqM7F78PRreBrAwA0JrRUITWX
+AdxfG/F851X6LWh3e9NpzNMOa7pNdkTWwhWaJuywxfW70Xp0wmzNxbVe9kzmWy2B27O3Opee7c9G
+slA9hGCZcbUztVdF5kJHdWoOsAgMrr3e97sPWD2PAzHoPYJQyi9eDF20l74gNAf0xBLh7tew2Vkt
+afcxBPTy+av5EzH4AXcOPUIjJsyacmdRIXrMPIWo6iFqO9taPKU0nprALN+AnCng33eU0aKAQv9q
+TFsR0PXNor6uzFFcw9VUewyu1rkGd4Di7wcaaMxZUa1+XGdrudviB0JbuAEFWDlN5LuYo7Ey7Nmj
+1m+UI/87tyll5gfp77YZ6ufCOB0yiJA8EytuzO+rdwY0d4RPcuSBhPm5dDTedk+SKlOxJTnbPP/l
+PqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcEoji2jbDwN/zIIX8/syQbPYtuzE2wFg2W
+HYMfRsCbvUOZ58SWLs5fyQ==
+-----END CERTIFICATE-----
diff --git a/FreeFileSync/Source/afs/abstract.h b/FreeFileSync/Source/afs/abstract.h
index 4a94d1cf..6fc74c37 100644
--- a/FreeFileSync/Source/afs/abstract.h
+++ b/FreeFileSync/Source/afs/abstract.h
@@ -9,7 +9,6 @@
#include <functional>
#include <chrono>
-//#include <variant>
#include <zen/file_error.h>
#include <zen/file_path.h>
#include <zen/serialize.h> //InputStream/OutputStream support buffered stream concept
@@ -261,6 +260,9 @@ struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model t
//already existing: undefined behavior! (e.g. fail/overwrite)
static void moveAndRenameItem(const AbstractPath& pathFrom, const AbstractPath& pathTo); //throw FileError, ErrorMoveUnsupported
+ static std::wstring generateMoveErrorMsg(const AbstractPath& pathFrom, const AbstractPath& pathTo) { return pathFrom.afsDevice.ref().generateMoveErrorMsg(pathFrom.afsPath, pathTo); }
+
+
//Note: it MAY happen that copyFileTransactional() leaves temp files behind, e.g. temporary network drop.
// => clean them up at an appropriate time (automatically set sync directions to delete them). They have the following ending:
static inline constexpr ZstringView TEMP_FILE_ENDING = Zstr(".ffs_tmp"); //don't use Zstring as global constant: avoid static initialization order problem in global namespace!
@@ -351,6 +353,21 @@ protected:
FileCopyResult copyFileAsStream(const AfsPath& sourcePath, const StreamAttributes& attrSource, //throw FileError, ErrorFileLocked, X
const AbstractPath& targetPath, const zen::IoCallback& notifyUnbufferedIO /*throw X*/) const;
+
+ std::wstring generateMoveErrorMsg(const AfsPath& pathFrom, const AbstractPath& pathTo) const
+ {
+ using namespace zen;
+
+ if (getParentPath(pathFrom) == getParentPath(pathTo.afsPath)) //pure "rename"
+ return replaceCpy(replaceCpy(_("Cannot rename %x to %y."),
+ L"%x", fmtPath(getDisplayPath(pathFrom))),
+ L"%y", fmtPath(getItemName(pathTo)));
+ else //"move" or "move + rename"
+ return trimCpy(replaceCpy(replaceCpy(_("Cannot move %x to %y."),
+ L"%x", L'\n' + fmtPath(getDisplayPath(pathFrom))),
+ L"%y", L'\n' + fmtPath(getDisplayPath(pathTo))));
+ }
+
private:
virtual std::optional<Zstring> getNativeItemPath(const AfsPath& itemPath) const { return {}; };
@@ -532,9 +549,7 @@ void AbstractFileSystem::moveAndRenameItem(const AbstractPath& pathFrom, const A
using namespace zen;
if (typeid(pathFrom.afsDevice.ref()) != typeid(pathTo.afsDevice.ref()))
- throw ErrorMoveUnsupported(replaceCpy(replaceCpy(_("Cannot move file %x to %y."),
- L"%x", L'\n' + fmtPath(getDisplayPath(pathFrom))),
- L"%y", L'\n' + fmtPath(getDisplayPath(pathTo))), _("Operation not supported between different devices."));
+ throw ErrorMoveUnsupported(generateMoveErrorMsg(pathFrom, pathTo), _("Operation not supported between different devices."));
//already existing: undefined behavior! (e.g. fail/overwrite)
pathFrom.afsDevice.ref().moveAndRenameItemForSameAfsType(pathFrom.afsPath, pathTo); //throw FileError, ErrorMoveUnsupported
diff --git a/FreeFileSync/Source/afs/ftp.cpp b/FreeFileSync/Source/afs/ftp.cpp
index 283ca3ec..d72daf92 100644
--- a/FreeFileSync/Source/afs/ftp.cpp
+++ b/FreeFileSync/Source/afs/ftp.cpp
@@ -2119,7 +2119,7 @@ struct OutputStreamFtp : public AFS::OutputStreamImpl
/* is setting modtime after closing the file handle a pessimization?
FTP: no: could set modtime via CURLOPT_POSTQUOTE (but this would internally trigger an extra round-trip anyway!) */
}
- catch (const FileError& e) { result.errorModTime = FileError(e.toString()); /*avoid slicing*/ }
+ catch (const FileError& e) { result.errorModTime = e; /*might slice derived class?*/ }
return result;
}
@@ -2463,13 +2463,8 @@ private:
// FileZilla Server: CURLE_QUOTE_ERROR: QUOT command failed with 553 file exists
void moveAndRenameItemForSameAfsType(const AfsPath& pathFrom, const AbstractPath& pathTo) const override //throw FileError, ErrorMoveUnsupported
{
- auto generateErrorMsg = [&] { return replaceCpy(replaceCpy(_("Cannot move file %x to %y."),
- L"%x", L'\n' + fmtPath(getDisplayPath(pathFrom))),
- L"%y", L'\n' + fmtPath(AFS::getDisplayPath(pathTo)));
- };
-
if (compareDeviceSameAfsType(pathTo.afsDevice.ref()) != std::weak_ordering::equivalent)
- throw ErrorMoveUnsupported(generateErrorMsg(), _("Operation not supported between different devices."));
+ throw ErrorMoveUnsupported(generateMoveErrorMsg(pathFrom, pathTo), _("Operation not supported between different devices."));
try
{
@@ -2489,7 +2484,7 @@ private:
}
catch (const SysError& e)
{
- throw FileError(generateErrorMsg(), e.toString());
+ throw FileError(generateMoveErrorMsg(pathFrom, pathTo), e.toString());
}
}
diff --git a/FreeFileSync/Source/afs/gdrive.cpp b/FreeFileSync/Source/afs/gdrive.cpp
index 25c0255b..e6215b63 100644
--- a/FreeFileSync/Source/afs/gdrive.cpp
+++ b/FreeFileSync/Source/afs/gdrive.cpp
@@ -3774,13 +3774,8 @@ private:
//=> actual behavior: 1. fails or 2. creates duplicate (unlikely)
void moveAndRenameItemForSameAfsType(const AfsPath& pathFrom, const AbstractPath& pathTo) const override //throw FileError, ErrorMoveUnsupported
{
- auto generateErrorMsg = [&] { return replaceCpy(replaceCpy(_("Cannot move file %x to %y."),
- L"%x", L'\n' + fmtPath(getDisplayPath(pathFrom))),
- L"%y", L'\n' + fmtPath(AFS::getDisplayPath(pathTo)));
- };
-
if (compareDeviceSameAfsType(pathTo.afsDevice.ref()) != std::weak_ordering::equivalent)
- throw ErrorMoveUnsupported(generateErrorMsg(), _("Operation not supported between different devices."));
+ throw ErrorMoveUnsupported(generateMoveErrorMsg(pathFrom, pathTo), _("Operation not supported between different devices."));
//note: moving files within account works, e.g. between My Drive <-> shared drives
// BUT: not supported by our model with separate GdriveFileStates; e.g. how to handle complexity of a moved folder (tree)?
try
@@ -3837,7 +3832,7 @@ private:
fileState.all().notifyMoveAndRename(aai.stateDelta, itemId, parentIdFrom, parentIdTo, itemNameNew);
});
}
- catch (const SysError& e) { throw FileError(generateErrorMsg(), e.toString()); }
+ catch (const SysError& e) { throw FileError(generateMoveErrorMsg(pathFrom, pathTo), e.toString()); }
}
bool supportsPermissions(const AfsPath& folderPath) const override { return false; } //throw FileError
diff --git a/FreeFileSync/Source/afs/native.cpp b/FreeFileSync/Source/afs/native.cpp
index 46b2bb4d..cbcce134 100644
--- a/FreeFileSync/Source/afs/native.cpp
+++ b/FreeFileSync/Source/afs/native.cpp
@@ -375,20 +375,18 @@ struct OutputStreamNative : public AFS::OutputStreamImpl
AFS::FinalizeResult finalize(const IoCallback& notifyUnbufferedIO /*throw X*/) override //throw FileError, X
{
AFS::FinalizeResult result;
- if (modTime_)
- result.filePrint = getNativeFileInfo(fileOut_).filePrint; //throw FileError
- fileOut_.close(); //throw FileError
+ result.filePrint = getNativeFileInfo(fileOut_).filePrint; //throw FileError
+ fileOut_.close(); //throw FileError
+ /* is setting modtime after closing the file handle a pessimization?
+ no, needed for functional correctness, see file_access.cpp::copyNewFile() for macOS/Linux */
try
{
if (modTime_)
setFileTime(fileOut_.getFilePath(), *modTime_, ProcSymlink::follow); //throw FileError
- /* is setting modtime after closing the file handle a pessimization?
- no, needed for functional correctness, see file_access.cpp */
}
- catch (const FileError& e) { result.errorModTime = FileError(e.toString()); /*avoid slicing*/ }
-
+ catch (const FileError& e) { result.errorModTime = e; /*might slice derived class?*/ }
return result;
}
@@ -626,10 +624,8 @@ private:
//perf test: detecting different volumes by path is ~30 times faster than having ::MoveFileEx() fail with ERROR_NOT_SAME_DEVICE (6µs vs 190µs)
//=> maybe we can even save some actual I/O in some cases?
if (compareDeviceSameAfsType(pathTo.afsDevice.ref()) != std::weak_ordering::equivalent)
- throw ErrorMoveUnsupported(replaceCpy(replaceCpy(_("Cannot move file %x to %y."),
- L"%x", L'\n' + fmtPath(getDisplayPath(pathFrom))),
- L"%y", L'\n' + fmtPath(AFS::getDisplayPath(pathTo))),
- _("Operation not supported between different devices."));
+ throw ErrorMoveUnsupported(generateMoveErrorMsg(pathFrom, pathTo), _("Operation not supported between different devices."));
+
initComForThread(); //throw FileError
const Zstring nativePathTarget = static_cast<const NativeFileSystem&>(pathTo.afsDevice.ref()).getNativePath(pathTo.afsPath);
diff --git a/FreeFileSync/Source/afs/sftp.cpp b/FreeFileSync/Source/afs/sftp.cpp
index 4787b668..795b5a01 100644
--- a/FreeFileSync/Source/afs/sftp.cpp
+++ b/FreeFileSync/Source/afs/sftp.cpp
@@ -1820,13 +1820,8 @@ private:
//=> actual behavior: fail with obscure LIBSSH2_FX_FAILURE error
void moveAndRenameItemForSameAfsType(const AfsPath& pathFrom, const AbstractPath& pathTo) const override //throw FileError, ErrorMoveUnsupported
{
- auto generateErrorMsg = [&] { return replaceCpy(replaceCpy(_("Cannot move file %x to %y."),
- L"%x", L'\n' + fmtPath(getDisplayPath(pathFrom))),
- L"%y", L'\n' + fmtPath(AFS::getDisplayPath(pathTo)));
- };
-
if (compareDeviceSameAfsType(pathTo.afsDevice.ref()) != std::weak_ordering::equivalent)
- throw ErrorMoveUnsupported(generateErrorMsg(), _("Operation not supported between different devices."));
+ throw ErrorMoveUnsupported(generateMoveErrorMsg(pathFrom, pathTo), _("Operation not supported between different devices."));
try
{
@@ -1849,7 +1844,7 @@ private:
}
catch (const SysError& e) //libssh2_sftp_rename_ex reports generic LIBSSH2_FX_FAILURE if target is already existing!
{
- throw FileError(generateErrorMsg(), e.toString());
+ throw FileError(generateMoveErrorMsg(pathFrom, pathTo), e.toString());
}
}
diff --git a/FreeFileSync/Source/application.cpp b/FreeFileSync/Source/application.cpp
index 272fed83..baaf660f 100644
--- a/FreeFileSync/Source/application.cpp
+++ b/FreeFileSync/Source/application.cpp
@@ -418,7 +418,7 @@ void Application::onEnterEventLoop()
else
{
XmlGuiConfig guiCfg;
- guiCfg.mainCfg.syncCfg.directionCfg.var = SyncVariant::mirror;
+ guiCfg.mainCfg.syncCfg.directionCfg = getDefaultSyncCfg(SyncVariant::mirror);
replaceDirectories(guiCfg.mainCfg); //throw FileError
diff --git a/FreeFileSync/Source/base/algorithm.cpp b/FreeFileSync/Source/base/algorithm.cpp
index 5ff1cfbc..6bd2d203 100644
--- a/FreeFileSync/Source/base/algorithm.cpp
+++ b/FreeFileSync/Source/base/algorithm.cpp
@@ -34,21 +34,21 @@ void fff::swapGrids(const MainConfiguration& mainCfg, FolderComparison& folderCm
namespace
{
//visitFSObjectRecursively? nope, see premature end of traversal in processFolder()
-class SetSyncDirectionByConfig
+class SetSyncDirViaDifferences
{
public:
- static void execute(const DirectionSet& dirCfgIn, ContainerObject& hierObj) { SetSyncDirectionByConfig(dirCfgIn).recurse(hierObj); }
+ static void execute(const DirectionByDiff& dirs, ContainerObject& conObj) { SetSyncDirViaDifferences(dirs).recurse(conObj); }
private:
- SetSyncDirectionByConfig(const DirectionSet& dirCfgIn) : dirCfg_(dirCfgIn) {}
+ SetSyncDirViaDifferences(const DirectionByDiff& dirs) : dirs_(dirs) {}
- void recurse(ContainerObject& hierObj) const
+ void recurse(ContainerObject& conObj) const
{
- for (FilePair& file : hierObj.refSubFiles())
+ for (FilePair& file : conObj.refSubFiles())
processFile(file);
- for (SymlinkPair& link : hierObj.refSubLinks())
+ for (SymlinkPair& link : conObj.refSubLinks())
processLink(link);
- for (FolderPair& folder : hierObj.refSubFolders())
+ for (FolderPair& folder : conObj.refSubFolders())
processFolder(folder);
}
@@ -57,38 +57,49 @@ private:
const CompareFileResult cat = file.getCategory();
//##################### schedule old temporary files for deletion ####################
- if (cat == FILE_LEFT_SIDE_ONLY && endsWith(file.getItemName<SelectSide::left>(), AFS::TEMP_FILE_ENDING))
+ if (cat == FILE_LEFT_ONLY && endsWith(file.getItemName<SelectSide::left>(), AFS::TEMP_FILE_ENDING))
return file.setSyncDir(SyncDirection::left);
- else if (cat == FILE_RIGHT_SIDE_ONLY && endsWith(file.getItemName<SelectSide::right>(), AFS::TEMP_FILE_ENDING))
+ else if (cat == FILE_RIGHT_ONLY && endsWith(file.getItemName<SelectSide::right>(), AFS::TEMP_FILE_ENDING))
return file.setSyncDir(SyncDirection::right);
//####################################################################################
switch (cat)
{
- case FILE_LEFT_SIDE_ONLY:
- file.setSyncDir(dirCfg_.exLeftSideOnly);
+ case FILE_EQUAL:
+ //file.setSyncDir(SyncDirection::none);
break;
- case FILE_RIGHT_SIDE_ONLY:
- file.setSyncDir(dirCfg_.exRightSideOnly);
+ case FILE_RENAMED:
+ if (dirs_.leftNewer == dirs_.rightNewer)
+ file.setSyncDir(dirs_.leftNewer); //treat "rename" like a "file update"
+ else
+ file.setSyncDirConflict(txtDiffName_);
break;
- case FILE_RIGHT_NEWER:
- file.setSyncDir(dirCfg_.rightNewer);
+ case FILE_LEFT_ONLY:
+ file.setSyncDir(dirs_.leftOnly);
+ break;
+ case FILE_RIGHT_ONLY:
+ file.setSyncDir(dirs_.rightOnly);
break;
case FILE_LEFT_NEWER:
- file.setSyncDir(dirCfg_.leftNewer);
+ file.setSyncDir(dirs_.leftNewer);
break;
- case FILE_DIFFERENT_CONTENT:
- file.setSyncDir(dirCfg_.different);
+ case FILE_RIGHT_NEWER:
+ file.setSyncDir(dirs_.rightNewer);
break;
- case FILE_CONFLICT:
- case FILE_DIFFERENT_METADATA: //use setting from "conflict/cannot categorize"
- if (dirCfg_.conflict == SyncDirection::none)
- file.setSyncDirConflict(file.getCatExtraDescription()); //take over category conflict
+ case FILE_TIME_INVALID:
+ if (dirs_.leftNewer == dirs_.rightNewer) //e.g. "Mirror" sync variant
+ file.setSyncDir(dirs_.leftNewer);
else
- file.setSyncDir(dirCfg_.conflict);
+ file.setSyncDirConflict(file.getCategoryCustomDescription());
break;
- case FILE_EQUAL:
- file.setSyncDir(SyncDirection::none);
+ case FILE_DIFFERENT_CONTENT:
+ if (dirs_.leftNewer == dirs_.rightNewer)
+ file.setSyncDir(dirs_.leftNewer);
+ else
+ file.setSyncDirConflict(txtDiffContent_);
+ break;
+ case FILE_CONFLICT:
+ file.setSyncDirConflict(file.getCategoryCustomDescription()); //take over category conflict: allow *manual* resolution only!
break;
}
}
@@ -97,30 +108,41 @@ private:
{
switch (symlink.getLinkCategory())
{
- case SYMLINK_LEFT_SIDE_ONLY:
- symlink.setSyncDir(dirCfg_.exLeftSideOnly);
+ case SYMLINK_EQUAL:
+ //symlink.setSyncDir(SyncDirection::none);
+ break;
+ case SYMLINK_RENAMED:
+ if (dirs_.leftNewer == dirs_.rightNewer)
+ symlink.setSyncDir(dirs_.leftNewer);
+ else
+ symlink.setSyncDirConflict(txtDiffName_);
break;
- case SYMLINK_RIGHT_SIDE_ONLY:
- symlink.setSyncDir(dirCfg_.exRightSideOnly);
+ case SYMLINK_LEFT_ONLY:
+ symlink.setSyncDir(dirs_.leftOnly);
+ break;
+ case SYMLINK_RIGHT_ONLY:
+ symlink.setSyncDir(dirs_.rightOnly);
break;
case SYMLINK_LEFT_NEWER:
- symlink.setSyncDir(dirCfg_.leftNewer);
+ symlink.setSyncDir(dirs_.leftNewer);
break;
case SYMLINK_RIGHT_NEWER:
- symlink.setSyncDir(dirCfg_.rightNewer);
+ symlink.setSyncDir(dirs_.rightNewer);
break;
- case SYMLINK_CONFLICT:
- case SYMLINK_DIFFERENT_METADATA: //use setting from "conflict/cannot categorize"
- if (dirCfg_.conflict == SyncDirection::none)
- symlink.setSyncDirConflict(symlink.getCatExtraDescription()); //take over category conflict
+ case SYMLINK_TIME_INVALID:
+ if (dirs_.leftNewer == dirs_.rightNewer)
+ symlink.setSyncDir(dirs_.leftNewer);
else
- symlink.setSyncDir(dirCfg_.conflict);
+ symlink.setSyncDirConflict(symlink.getCategoryCustomDescription());
break;
case SYMLINK_DIFFERENT_CONTENT:
- symlink.setSyncDir(dirCfg_.different);
+ if (dirs_.leftNewer == dirs_.rightNewer)
+ symlink.setSyncDir(dirs_.leftNewer);
+ else
+ symlink.setSyncDirConflict(txtDiffContent_);
break;
- case SYMLINK_EQUAL:
- symlink.setSyncDir(SyncDirection::none);
+ case SYMLINK_CONFLICT:
+ symlink.setSyncDirConflict(symlink.getCategoryCustomDescription()); //take over category conflict: allow *manual* resolution only!
break;
}
}
@@ -130,50 +152,56 @@ private:
const CompareDirResult cat = folder.getDirCategory();
//########### schedule abandoned temporary recycle bin directory for deletion ##########
- if (cat == DIR_LEFT_SIDE_ONLY && endsWith(folder.getItemName<SelectSide::left>(), AFS::TEMP_FILE_ENDING))
+ if (cat == DIR_LEFT_ONLY && endsWith(folder.getItemName<SelectSide::left>(), AFS::TEMP_FILE_ENDING))
return setSyncDirectionRec(SyncDirection::left, folder); //
- else if (cat == DIR_RIGHT_SIDE_ONLY && endsWith(folder.getItemName<SelectSide::right>(), AFS::TEMP_FILE_ENDING))
+ else if (cat == DIR_RIGHT_ONLY && endsWith(folder.getItemName<SelectSide::right>(), AFS::TEMP_FILE_ENDING))
return setSyncDirectionRec(SyncDirection::right, folder); //don't recurse below!
//#######################################################################################
switch (cat)
{
- case DIR_LEFT_SIDE_ONLY:
- folder.setSyncDir(dirCfg_.exLeftSideOnly);
+ case DIR_EQUAL:
+ //folder.setSyncDir(SyncDirection::none);
break;
- case DIR_RIGHT_SIDE_ONLY:
- folder.setSyncDir(dirCfg_.exRightSideOnly);
+ case DIR_RENAMED:
+ if (dirs_.leftNewer == dirs_.rightNewer)
+ folder.setSyncDir(dirs_.leftNewer);
+ else
+ folder.setSyncDirConflict(txtDiffName_);
break;
- case DIR_EQUAL:
- folder.setSyncDir(SyncDirection::none);
+ case DIR_LEFT_ONLY:
+ folder.setSyncDir(dirs_.leftOnly);
+ break;
+ case DIR_RIGHT_ONLY:
+ folder.setSyncDir(dirs_.rightOnly);
break;
case DIR_CONFLICT:
- case DIR_DIFFERENT_METADATA: //use setting from "conflict/cannot categorize"
- if (dirCfg_.conflict == SyncDirection::none)
- folder.setSyncDirConflict(folder.getCatExtraDescription()); //take over category conflict
- else
- folder.setSyncDir(dirCfg_.conflict);
+ folder.setSyncDirConflict(folder.getCategoryCustomDescription()); //take over category conflict: allow *manual* resolution only!
break;
}
recurse(folder);
}
- const DirectionSet dirCfg_;
+ const DirectionByDiff dirs_;
+ const Zstringc txtDiffName_ = utfTo<Zstringc>(_("Cannot determine sync-direction:") + L'\n' + TAB_SPACE +
+ _("The items have different names, but it's unknown which side was renamed."));
+ const Zstringc txtDiffContent_ = utfTo<Zstringc>(_("Cannot determine sync-direction:") + L'\n' + TAB_SPACE +
+ _("The items have different content, but it's unknown which side has changed."));
};
//---------------------------------------------------------------------------------------------------------------
//test if non-equal items exist in scanned data
-bool allItemsCategoryEqual(const ContainerObject& hierObj)
+bool allItemsCategoryEqual(const ContainerObject& conObj)
{
- return std::all_of(hierObj.refSubFiles().begin(), hierObj.refSubFiles().end(),
+ return std::all_of(conObj.refSubFiles().begin(), conObj.refSubFiles().end(),
[](const FilePair& file) { return file.getCategory() == FILE_EQUAL; })&&
- std::all_of(hierObj.refSubLinks().begin(), hierObj.refSubLinks().end(),
+ std::all_of(conObj.refSubLinks().begin(), conObj.refSubLinks().end(),
[](const SymlinkPair& symlink) { return symlink.getLinkCategory() == SYMLINK_EQUAL; })&&
- std::all_of(hierObj.refSubFolders().begin(), hierObj.refSubFolders().end(), [](const FolderPair& folder)
+ std::all_of(conObj.refSubFolders().begin(), conObj.refSubFolders().end(), [](const FolderPair& folder)
{
return folder.getDirCategory() == DIR_EQUAL && allItemsCategoryEqual(folder); //short-circuit behavior!
});
@@ -190,19 +218,20 @@ bool fff::allElementsEqual(const FolderComparison& folderCmp)
namespace
{
template <SelectSide side> inline
-bool matchesDbEntry(const FilePair& file, const InSyncFile* dbFile, const std::vector<unsigned int>& ignoreTimeShiftMinutes)
+CudAction compareDbEntry(const FilePair& file, const InSyncFile* dbFile, const std::vector<unsigned int>& ignoreTimeShiftMinutes, bool renamedOrMoved)
{
if (file.isEmpty<side>())
- return !dbFile;
+ return dbFile ? (renamedOrMoved ? CudAction::update: CudAction::delete_) : CudAction::noChange;
else if (!dbFile)
- return false;
+ return (renamedOrMoved ? CudAction::update : CudAction::create);
const InSyncDescrFile& descrDb = selectParam<side>(dbFile->left, dbFile->right);
- return //we're not interested in "fileTimeTolerance" here!
- sameFileTime(file.getLastWriteTime<side>(), descrDb.modTime, FAT_FILE_TIME_PRECISION_SEC, ignoreTimeShiftMinutes) &&
- file.getFileSize<side>() == dbFile->fileSize;
- //note: we do *not* consider file ID here, but are only interested in *visual* changes. Consider user moving data to some other medium, this is not a change!
+ return sameFileTime(file.getLastWriteTime<side>(), descrDb.modTime, FAT_FILE_TIME_PRECISION_SEC, ignoreTimeShiftMinutes) &&
+ //- we're not interested in "fileTimeTolerance"!
+ //- we do *not* consider file ID, but only *user-visual* changes. E.g. user moving data to some other medium should not be considered a change!
+ file.getFileSize<side>() == dbFile->fileSize ?
+ CudAction::noChange : CudAction::update;
}
@@ -215,15 +244,15 @@ bool stillInSync(const InSyncFile& dbFile, CompareVariant compareVar, int fileTi
case CompareVariant::timeSize:
if (dbFile.cmpVar == CompareVariant::content) return true; //special rule: this is certainly "good enough" for CompareVariant::timeSize!
- //case-sensitive short name match is a database invariant!
+ //case-sensitive file name match is a database invariant!
return sameFileTime(dbFile.left.modTime, dbFile.right.modTime, fileTimeTolerance, ignoreTimeShiftMinutes);
case CompareVariant::content:
- //case-sensitive short name match is a database invariant!
+ //case-sensitive file name match is a database invariant!
return dbFile.cmpVar == CompareVariant::content;
//in contrast to comparison, we don't care about modification time here!
- case CompareVariant::size: //file size/case-sensitive short name always matches on both sides for an "in-sync" database entry
+ case CompareVariant::size: //file size/case-sensitive file name always matches on both sides for an "in-sync" database entry
return true;
}
assert(false);
@@ -234,16 +263,17 @@ bool stillInSync(const InSyncFile& dbFile, CompareVariant compareVar, int fileTi
//check whether database entry and current item match: *irrespective* of current comparison settings
template <SelectSide side> inline
-bool matchesDbEntry(const SymlinkPair& symlink, const InSyncSymlink* dbSymlink, const std::vector<unsigned int>& ignoreTimeShiftMinutes)
+CudAction compareDbEntry(const SymlinkPair& symlink, const InSyncSymlink* dbSymlink, const std::vector<unsigned int>& ignoreTimeShiftMinutes, bool renamedOrMoved)
{
if (symlink.isEmpty<side>())
- return !dbSymlink;
+ return dbSymlink ? (renamedOrMoved ? CudAction::update: CudAction::delete_) : CudAction::noChange;
else if (!dbSymlink)
- return false;
+ return (renamedOrMoved ? CudAction::update : CudAction::create);
const InSyncDescrLink& descrDb = selectParam<side>(dbSymlink->left, dbSymlink->right);
- return sameFileTime(symlink.getLastWriteTime<side>(), descrDb.modTime, FAT_FILE_TIME_PRECISION_SEC, ignoreTimeShiftMinutes);
+ return sameFileTime(symlink.getLastWriteTime<side>(), descrDb.modTime, FAT_FILE_TIME_PRECISION_SEC, ignoreTimeShiftMinutes) ?
+ CudAction::noChange : CudAction::update;
}
@@ -257,12 +287,12 @@ bool stillInSync(const InSyncSymlink& dbLink, CompareVariant compareVar, int fil
if (dbLink.cmpVar == CompareVariant::content || dbLink.cmpVar == CompareVariant::size)
return true; //special rule: this is already "good enough" for CompareVariant::timeSize!
- //case-sensitive short name match is a database invariant!
+ //case-sensitive symlink name match is a database invariant!
return sameFileTime(dbLink.left.modTime, dbLink.right.modTime, fileTimeTolerance, ignoreTimeShiftMinutes);
case CompareVariant::content:
case CompareVariant::size: //== categorized by content! see comparison.cpp, ComparisonBuffer::compareBySize()
- //case-sensitive short name match is a database invariant!
+ //case-sensitive symlink name match is a database invariant!
return dbLink.cmpVar == CompareVariant::content || dbLink.cmpVar == CompareVariant::size;
}
assert(false);
@@ -273,18 +303,21 @@ bool stillInSync(const InSyncSymlink& dbLink, CompareVariant compareVar, int fil
//check whether database entry and current item match: *irrespective* of current comparison settings
template <SelectSide side> inline
-bool matchesDbEntry(const FolderPair& folder, const InSyncFolder* dbFolder)
+CudAction compareDbEntry(const FolderPair& folder, const InSyncFolder* dbFolder, bool renamedOrMoved)
{
- const bool haveDbEntry = dbFolder && dbFolder->status != InSyncFolder::DIR_STATUS_STRAW_MAN;
- return haveDbEntry == !folder.isEmpty<side>();
+ if (folder.isEmpty<side>())
+ return dbFolder ? (renamedOrMoved ? CudAction::update: CudAction::delete_) : CudAction::noChange;
+ else if (!dbFolder)
+ return (renamedOrMoved ? CudAction::update : CudAction::create);
+
+ return CudAction::noChange;
}
inline
bool stillInSync(const InSyncFolder& dbFolder)
{
- //case-sensitive short name match is a database invariant!
- //InSyncFolder::DIR_STATUS_STRAW_MAN considered
+ //case-sensitive folder name match is a database invariant!
return true;
}
@@ -293,7 +326,11 @@ bool stillInSync(const InSyncFolder& dbFolder)
class DetectMovedFiles
{
public:
- static void execute(BaseFolderPair& baseFolder, const InSyncFolder& dbFolder) { DetectMovedFiles(baseFolder, dbFolder); }
+ static void execute(BaseFolderPair& baseFolder, const InSyncFolder& dbFolder)
+ {
+ DetectMovedFiles(baseFolder, dbFolder);
+ baseFolder.removeDoubleEmpty(); //see findAndSetMovePair()
+ }
private:
DetectMovedFiles(BaseFolderPair& baseFolder, const InSyncFolder& dbFolder) :
@@ -311,12 +348,14 @@ private:
detectMovePairs(dbFolder);
}
- void recurse(ContainerObject& hierObj, const InSyncFolder* dbFolderL, const InSyncFolder* dbFolderR)
+ void recurse(ContainerObject& conObj, const InSyncFolder* dbFolderL, const InSyncFolder* dbFolderR)
{
- for (FilePair& file : hierObj.refSubFiles())
+ for (FilePair& file : conObj.refSubFiles())
{
- const AFS::FingerPrint filePrintL = file.getFilePrint<SelectSide::left >();
- const AFS::FingerPrint filePrintR = file.getFilePrint<SelectSide::right>();
+ file.setMoveRef(nullptr); //discard remnants from previous move detection and start fresh (e.g. consider manual folder rename)
+
+ const AFS::FingerPrint filePrintL = file.isEmpty<SelectSide::left >() ? 0 : file.getFilePrint<SelectSide::left >();
+ const AFS::FingerPrint filePrintR = file.isEmpty<SelectSide::right>() ? 0 : file.getFilePrint<SelectSide::right>();
if (filePrintL != 0) filesL_.push_back(&file); //collect *all* prints for uniqueness check!
if (filePrintR != 0) filesR_.push_back(&file); //
@@ -331,19 +370,19 @@ private:
};
if (const CompareFileResult cat = file.getCategory();
- cat == FILE_LEFT_SIDE_ONLY)
+ cat == FILE_LEFT_ONLY)
{
if (const InSyncFile* dbEntry = getDbEntry(dbFolderL, file.getItemName<SelectSide::left>()))
exLeftOnlyByPath_.emplace(dbEntry, &file);
}
- else if (cat == FILE_RIGHT_SIDE_ONLY)
+ else if (cat == FILE_RIGHT_ONLY)
{
if (const InSyncFile* dbEntry = getDbEntry(dbFolderR, file.getItemName<SelectSide::right>()))
exRightOnlyByPath_.emplace(dbEntry, &file);
}
}
- for (FolderPair& folder : hierObj.refSubFolders())
+ for (FolderPair& folder : conObj.refSubFolders())
{
auto getDbEntry = [](const InSyncFolder* dbFolder, const ZstringNorm& folderName) -> const InSyncFolder*
{
@@ -391,7 +430,7 @@ private:
}
//collect unique file prints for files existing on one side only:
- constexpr CompareFileResult oneSideOnlyTag = side == SelectSide::left ? FILE_LEFT_SIDE_ONLY : FILE_RIGHT_SIDE_ONLY;
+ constexpr CompareFileResult oneSideOnlyTag = side == SelectSide::left ? FILE_LEFT_ONLY : FILE_RIGHT_ONLY;
for (FilePair* file : files)
if (file->getCategory() == oneSideOnlyTag)
@@ -455,17 +494,42 @@ private:
if (FilePair* fileRightOnly = getAssocFilePair<SelectSide::right>(dbFile))
if (sameSizeAndDate<SelectSide::right>(*fileRightOnly, dbFile))
{
- assert((!fileLeftOnly ->getMoveRef() &&
- !fileRightOnly->getMoveRef()) ||
- (fileLeftOnly ->getMoveRef() == fileRightOnly->getId() &&
- fileRightOnly->getMoveRef() == fileLeftOnly ->getId()));
-
- if (fileLeftOnly ->getMoveRef() == nullptr && //needless check!? file prints are unique in this context!
- fileRightOnly->getMoveRef() == nullptr) //
+ if (fileLeftOnly ->getMoveRef() == nullptr && //needless checks? (file prints are unique in this context)
+ fileRightOnly->getMoveRef() == nullptr && //
+ fileLeftOnly ->getCategory() == FILE_LEFT_ONLY && //is it possible we could get conflicting matches!?
+ fileRightOnly->getCategory() == FILE_RIGHT_ONLY) //=> likely 'yes', but only in obscure cases
+ //--------------- found a match ---------------
{
- fileLeftOnly ->setMoveRef(fileRightOnly->getId()); //found a pair, mark it!
- fileRightOnly->setMoveRef(fileLeftOnly ->getId()); //
+ //move pair is just a 'rename' => combine:
+ if (&fileLeftOnly->parent() == &fileRightOnly->parent())
+ {
+ fileLeftOnly->setSyncedTo<SelectSide::right>(fileLeftOnly->getFileSize<SelectSide::left>(),
+ fileRightOnly->getLastWriteTime<SelectSide::right>(), //lastWriteTimeTrg
+ fileLeftOnly ->getLastWriteTime<SelectSide::left >(), //lastWriteTimeSrc
+
+ fileRightOnly->getFilePrint<SelectSide::right>(), //filePrintTrg
+ fileLeftOnly ->getFilePrint<SelectSide::left >(), //filePrintSrc
+
+ fileRightOnly->isFollowedSymlink<SelectSide::right>(), //isSymlinkTrg
+ fileLeftOnly ->isFollowedSymlink<SelectSide::left >()); //isSymlinkSrc
+
+ fileLeftOnly->setItemName<SelectSide::right>(fileRightOnly->getItemName<SelectSide::right>());
+
+ assert(fileLeftOnly->isActive() && fileRightOnly->isActive()); //can this fail? excluded files are not added during comparison...
+ if (fileLeftOnly->isActive() != fileRightOnly->isActive()) //just in case
+ fileLeftOnly->setActive(false);
+
+ fileRightOnly->removeObject<SelectSide::right>(); //=> call ContainerObject::removeDoubleEmpty() later!
+ }
+ else //regular move pair: mark it!
+ {
+ fileLeftOnly ->setMoveRef(fileRightOnly->getId());
+ fileRightOnly->setMoveRef(fileLeftOnly ->getId());
+ }
}
+ else
+ assert(fileLeftOnly ->getMoveRef() == fileRightOnly->getId() &&
+ fileRightOnly->getMoveRef() == fileLeftOnly ->getId());
}
}
@@ -476,10 +540,10 @@ private:
std::vector<FilePair*> filesL_; //collection of *all* file items (with non-null filePrint)
std::vector<FilePair*> filesR_; // => detect duplicate file IDs
- std::unordered_map<AFS::FingerPrint, FilePair*> exLeftOnlyById_; //MSVC: twice as fast as std::map for 1 million items!
+ std::unordered_map<AFS::FingerPrint, FilePair*> exLeftOnlyById_;
std::unordered_map<AFS::FingerPrint, FilePair*> exRightOnlyById_;
- std::unordered_map<const InSyncFile*, FilePair*> exLeftOnlyByPath_; //MSVC: only 4% faster than std::map for 1 million items!
+ std::unordered_map<const InSyncFile*, FilePair*> exLeftOnlyByPath_;
std::unordered_map<const InSyncFile*, FilePair*> exRightOnlyByPath_;
/* Detect Renamed Files:
@@ -487,7 +551,7 @@ private:
X -> |_| Create right
|_| -> Y Delete right
- resolve as: Rename Y to X on right
+ resolve as: Move/Rename Y to X on right
Algorithm:
----------
@@ -507,13 +571,15 @@ private:
//----------------------------------------------------------------------------------------------
-class SetSyncDirectionsTwoWay
+class SetSyncDirViaChanges
{
public:
- static void execute(BaseFolderPair& baseFolder, const InSyncFolder& dbFolder) { SetSyncDirectionsTwoWay(baseFolder, dbFolder); }
+ static void execute(BaseFolderPair& baseFolder, const InSyncFolder& dbFolder, const DirectionByChange& dirs)
+ { SetSyncDirViaChanges(baseFolder, dbFolder, dirs); }
private:
- SetSyncDirectionsTwoWay(BaseFolderPair& baseFolder, const InSyncFolder& dbFolder) :
+ SetSyncDirViaChanges(BaseFolderPair& baseFolder, const InSyncFolder& dbFolder, const DirectionByChange& dirs) :
+ dirs_(dirs),
cmpVar_ (baseFolder.getCompVariant()),
fileTimeTolerance_ (baseFolder.getFileTimeTolerance()),
ignoreTimeShiftMinutes_(baseFolder.getIgnoredTimeShift())
@@ -524,13 +590,13 @@ private:
recurse(baseFolder, &dbFolder);
}
- void recurse(ContainerObject& hierObj, const InSyncFolder* dbFolder) const
+ void recurse(ContainerObject& conObj, const InSyncFolder* dbFolder) const
{
- for (FilePair& file : hierObj.refSubFiles())
+ for (FilePair& file : conObj.refSubFiles())
processFile(file, dbFolder);
- for (SymlinkPair& symlink : hierObj.refSubLinks())
+ for (SymlinkPair& symlink : conObj.refSubLinks())
processSymlink(symlink, dbFolder);
- for (FolderPair& folder : hierObj.refSubFolders())
+ for (FolderPair& folder : conObj.refSubFolders())
processDir(folder, dbFolder);
}
@@ -539,11 +605,13 @@ private:
const CompareFileResult cat = file.getCategory();
if (cat == FILE_EQUAL)
return;
+ else if (cat == FILE_CONFLICT) //take over category conflict: allow *manual* resolution only!
+ return file.setSyncDirConflict(file.getCategoryCustomDescription());
//##################### schedule old temporary files for deletion ####################
- if (cat == FILE_LEFT_SIDE_ONLY && endsWith(file.getItemName<SelectSide::left>(), AFS::TEMP_FILE_ENDING))
+ if (cat == FILE_LEFT_ONLY && endsWith(file.getItemName<SelectSide::left>(), AFS::TEMP_FILE_ENDING))
return file.setSyncDir(SyncDirection::left);
- else if (cat == FILE_RIGHT_SIDE_ONLY && endsWith(file.getItemName<SelectSide::right>(), AFS::TEMP_FILE_ENDING))
+ else if (cat == FILE_RIGHT_ONLY && endsWith(file.getItemName<SelectSide::right>(), AFS::TEMP_FILE_ENDING))
return file.setSyncDir(SyncDirection::right);
//####################################################################################
@@ -569,13 +637,22 @@ private:
dbEntry && !stillInSync(*dbEntry, cmpVar_, fileTimeTolerance_, ignoreTimeShiftMinutes_)) //check *before* misleadingly reporting txtNoSideChanged_
return file.setSyncDirConflict(txtDbNotInSync_);
- const bool changeOnLeft = !matchesDbEntry<SelectSide::left >(file, dbEntryL, ignoreTimeShiftMinutes_);
- const bool changeOnRight = !matchesDbEntry<SelectSide::right>(file, dbEntryR, ignoreTimeShiftMinutes_);
+ //consider renamed/moved files as "updated" with regards to "changes"-based sync settings: https://freefilesync.org/forum/viewtopic.php?t=10594
+ const bool renamedOrMoved = cat == FILE_RENAMED || [&file]
+ {
+ if (const FileSystemObject::ObjectId moveFileRef = file.getMoveRef())
+ if (auto refFile = dynamic_cast<const FilePair*>(FileSystemObject::retrieve(moveFileRef)))
+ {
+ if (refFile->getMoveRef() == file.getId()) //both ends should agree...
+ return true;
+ else assert(false); //...and why shouldn't they?
+ }
+ return false;
+ }();
+ const CudAction changeL = compareDbEntry<SelectSide::left >(file, dbEntryL, ignoreTimeShiftMinutes_, renamedOrMoved);
+ const CudAction changeR = compareDbEntry<SelectSide::right>(file, dbEntryR, ignoreTimeShiftMinutes_, renamedOrMoved);
- if (changeOnLeft == changeOnRight)
- file.setSyncDirConflict(changeOnLeft ? txtBothSidesChanged_ : txtNoSideChanged_);
- else
- file.setSyncDir(changeOnLeft ? SyncDirection::right : SyncDirection::left);
+ setSyncDirForChange(file, changeL, changeR);
}
void processSymlink(SymlinkPair& symlink, const InSyncFolder* dbFolder) const
@@ -583,6 +660,8 @@ private:
const CompareSymlinkResult cat = symlink.getLinkCategory();
if (cat == SYMLINK_EQUAL)
return;
+ else if (cat == SYMLINK_CONFLICT) //take over category conflict: allow *manual* resolution only!
+ return symlink.setSyncDirConflict(symlink.getCategoryCustomDescription());
//try to find corresponding database entry
auto getDbEntry = [dbFolder](const ZstringNorm& linkName) -> const InSyncSymlink*
@@ -606,13 +685,11 @@ private:
dbEntry && !stillInSync(*dbEntry, cmpVar_, fileTimeTolerance_, ignoreTimeShiftMinutes_))
return symlink.setSyncDirConflict(txtDbNotInSync_);
- const bool changeOnLeft = !matchesDbEntry<SelectSide::left >(symlink, dbEntryL, ignoreTimeShiftMinutes_);
- const bool changeOnRight = !matchesDbEntry<SelectSide::right>(symlink, dbEntryR, ignoreTimeShiftMinutes_);
+ const bool renamedOrMoved = cat == SYMLINK_RENAMED;
+ const CudAction changeL = compareDbEntry<SelectSide::left >(symlink, dbEntryL, ignoreTimeShiftMinutes_, renamedOrMoved);
+ const CudAction changeR = compareDbEntry<SelectSide::right>(symlink, dbEntryR, ignoreTimeShiftMinutes_, renamedOrMoved);
- if (changeOnLeft == changeOnRight)
- symlink.setSyncDirConflict(changeOnLeft ? txtBothSidesChanged_ : txtNoSideChanged_);
- else
- symlink.setSyncDir(changeOnLeft ? SyncDirection::right : SyncDirection::left);
+ setSyncDirForChange(symlink, changeL, changeR);
}
void processDir(FolderPair& folder, const InSyncFolder* dbFolder) const
@@ -620,9 +697,9 @@ private:
const CompareDirResult cat = folder.getDirCategory();
//########### schedule abandoned temporary recycle bin directory for deletion ##########
- if (cat == DIR_LEFT_SIDE_ONLY && endsWith(folder.getItemName<SelectSide::left>(), AFS::TEMP_FILE_ENDING))
+ if (cat == DIR_LEFT_ONLY && endsWith(folder.getItemName<SelectSide::left>(), AFS::TEMP_FILE_ENDING))
return setSyncDirectionRec(SyncDirection::left, folder); //
- else if (cat == DIR_RIGHT_SIDE_ONLY && endsWith(folder.getItemName<SelectSide::right>(), AFS::TEMP_FILE_ENDING))
+ else if (cat == DIR_RIGHT_ONLY && endsWith(folder.getItemName<SelectSide::right>(), AFS::TEMP_FILE_ENDING))
return setSyncDirectionRec(SyncDirection::right, folder); //don't recurse below!
//#######################################################################################
@@ -653,31 +730,75 @@ private:
}
const InSyncFolder* dbEntry = dbEntryL ? dbEntryL : dbEntryR; //exactly one side nullptr? => change in upper/lower case!
- if (cat != DIR_EQUAL)
+ if (cat == DIR_EQUAL)
+ ;
+ else if (cat == DIR_CONFLICT) //take over category conflict: allow *manual* resolution only!
+ folder.setSyncDirConflict(folder.getCategoryCustomDescription());
+ else
{
if (dbEntry && !stillInSync(*dbEntry))
folder.setSyncDirConflict(txtDbNotInSync_);
else
{
- const bool changeOnLeft = !matchesDbEntry<SelectSide::left >(folder, dbEntryL);
- const bool changeOnRight = !matchesDbEntry<SelectSide::right>(folder, dbEntryR);
+ const bool renamedOrMoved = cat == DIR_RENAMED;
+ const CudAction changeL = compareDbEntry<SelectSide::left >(folder, dbEntryL, renamedOrMoved);
+ const CudAction changeR = compareDbEntry<SelectSide::right>(folder, dbEntryR, renamedOrMoved);
- if (changeOnLeft == changeOnRight)
- folder.setSyncDirConflict(changeOnLeft ? txtBothSidesChanged_ : txtNoSideChanged_);
- else
- folder.setSyncDir(changeOnLeft ? SyncDirection::right : SyncDirection::left);
+ setSyncDirForChange(folder, changeL, changeR);
}
}
recurse(folder, dbEntry);
}
+ template <SelectSide side>
+ SyncDirection getSyncDirForChange(CudAction change) const
+ {
+ const auto& changedirs = selectParam<side>(dirs_.left, dirs_.right);
+ switch (change)
+ {
+ //*INDENT-OFF*
+ case CudAction::noChange: return SyncDirection::none;
+ case CudAction::create: return changedirs.create;
+ case CudAction::update: return changedirs.update;
+ case CudAction::delete_: return changedirs.delete_;
+ //*INDENT-ON*
+ }
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
+ }
+
+ void setSyncDirForChange(FileSystemObject& fsObj, CudAction changeL, CudAction changeR) const
+ {
+ const SyncDirection dirL = getSyncDirForChange<SelectSide::left >(changeL);
+ const SyncDirection dirR = getSyncDirForChange<SelectSide::right>(changeR);
+ if (changeL != CudAction::noChange)
+ {
+ if (changeR != CudAction::noChange) //both sides changed
+ {
+ if (dirL == dirR) //but luckily agree on direction
+ fsObj.setSyncDir(dirL);
+ else
+ fsObj.setSyncDirConflict(txtBothSidesChanged_);
+ }
+ else //change on left
+ fsObj.setSyncDir(dirL);
+ }
+ else
+ {
+ if (changeR != CudAction::noChange) //change on right
+ fsObj.setSyncDir(dirR);
+ else //no change on either side
+ fsObj.setSyncDirConflict(txtNoSideChanged_); //obscure, but possible if user widens "fileTimeTolerance"
+ }
+ }
+
//need ref-counted strings! see FileSystemObject::syncDirectionConflict_
const Zstringc txtBothSidesChanged_ = utfTo<Zstringc>(_("Both sides have changed since last synchronization."));
const Zstringc txtNoSideChanged_ = utfTo<Zstringc>(_("Cannot determine sync-direction:") + L'\n' + TAB_SPACE + _("No change since last synchronization."));
const Zstringc txtDbNotInSync_ = utfTo<Zstringc>(_("Cannot determine sync-direction:") + L'\n' + TAB_SPACE + _("The database entry is not in sync, considering current settings."));
const Zstringc txtDbAmbiguous_ = utfTo<Zstringc>(_("Cannot determine sync-direction:") + L'\n' + TAB_SPACE + _("The database entry is ambiguous."));
+ const DirectionByChange dirs_;
const CompareVariant cmpVar_;
const int fileTimeTolerance_;
const std::vector<unsigned int> ignoreTimeShiftMinutes_;
@@ -729,39 +850,38 @@ void fff::redetermineSyncDirection(const std::vector<std::pair<BaseFolderPair*,
for (const auto& [baseFolder, dirCfg] : directCfgs)
if (!allEqualPairs.contains(baseFolder))
{
- auto it = lastSyncStates.find(baseFolder);
- const InSyncFolder* lastSyncState = it != lastSyncStates.end() ? &it->second.ref() : nullptr;
-
- //set sync directions
- if (dirCfg.var == SyncVariant::twoWay)
+ if (const DirectionByDiff* diffDirs = std::get_if<DirectionByDiff>(&dirCfg.dirs))
+ SetSyncDirViaDifferences::execute(*diffDirs, *baseFolder);
+ else
{
- if (lastSyncState)
- SetSyncDirectionsTwoWay::execute(*baseFolder, *lastSyncState);
- else //default fallback
+ const DirectionByChange& changeDirs = std::get<DirectionByChange>(dirCfg.dirs);
+
+ auto it = lastSyncStates.find(baseFolder);
+ if (const InSyncFolder* lastSyncState = it != lastSyncStates.end() ? &it->second.ref() : nullptr)
{
- std::wstring msg = _("Setting directions for first synchronization: Old files will be overwritten with newer files.");
- if (directCfgs.size() > 1)
- msg += L'\n' + AFS::getDisplayPath(baseFolder->getAbstractPath<SelectSide::left >()) + L' ' + getVariantNameWithSymbol(dirCfg.var) + L' ' +
- AFS::getDisplayPath(baseFolder->getAbstractPath<SelectSide::right>());
+ //detect moved files (*before* setting sync directions: might combine moved files into single file pairs, wich changes category!)
+ DetectMovedFiles::execute(*baseFolder, *lastSyncState);
+ SetSyncDirViaChanges::execute(*baseFolder, *lastSyncState, changeDirs);
+ }
+ else //fallback:
+ {
+ std::wstring msg = _("Database file is not available: Setting default directions for synchronization.");
+ if (directCfgs.size() > 1)
+ msg += SPACED_DASH + getShortDisplayNameForFolderPair(baseFolder->getAbstractPath<SelectSide::left >(),
+ baseFolder->getAbstractPath<SelectSide::right>());
try { callback.logMessage(msg, PhaseCallback::MsgType::warning); /*throw X*/} catch (...) {};
- SetSyncDirectionByConfig::execute(getTwoWayUpdateSet(), *baseFolder);
+ SetSyncDirViaDifferences::execute(getDiffDirDefault(changeDirs), *baseFolder);
}
}
- else
- SetSyncDirectionByConfig::execute(extractDirections(dirCfg), *baseFolder);
-
- //detect renamed files
- if (lastSyncState)
- DetectMovedFiles::execute(*baseFolder, *lastSyncState);
}
//*INDENT-ON*
);
std::vector<const BaseFolderPair*> baseFoldersForDbLoad;
for (const auto& [baseFolder, dirCfg] : directCfgs)
- if (dirCfg.var == SyncVariant::twoWay || detectMovedFilesEnabled(dirCfg))
+ if (std::get_if<DirectionByChange>(&dirCfg.dirs))
{
if (allItemsCategoryEqual(*baseFolder)) //nothing to do: don't even try to open DB files
allEqualPairs.insert(baseFolder);
@@ -837,45 +957,45 @@ struct Eval<STRATEGY_AND>
template <FilterStrategy strategy>
-class ApplyHardFilter
+class ApplyPathFilter
{
public:
- static void execute(ContainerObject& hierObj, const PathFilter& filterProcIn) { ApplyHardFilter(hierObj, filterProcIn); }
+ static void execute(ContainerObject& conObj, const PathFilter& filter) { ApplyPathFilter(conObj, filter); }
private:
- ApplyHardFilter(ContainerObject& hierObj, const PathFilter& filterProcIn) : filterProc(filterProcIn) { recurse(hierObj); }
+ ApplyPathFilter(ContainerObject& conObj, const PathFilter& filter) : filter_(filter) { recurse(conObj); }
- void recurse(ContainerObject& hierObj) const
+ void recurse(ContainerObject& conObj) const
{
- for (FilePair& file : hierObj.refSubFiles())
+ for (FilePair& file : conObj.refSubFiles())
processFile(file);
- for (SymlinkPair& symlink : hierObj.refSubLinks())
+ for (SymlinkPair& symlink : conObj.refSubLinks())
processLink(symlink);
- for (FolderPair& folder : hierObj.refSubFolders())
+ for (FolderPair& folder : conObj.refSubFolders())
processDir(folder);
}
void processFile(FilePair& file) const
{
if (Eval<strategy>::process(file))
- file.setActive(filterProc.passFileFilter(file.getRelativePathAny()));
+ file.setActive(file.passFileFilter(filter_));
}
void processLink(SymlinkPair& symlink) const
{
if (Eval<strategy>::process(symlink))
- symlink.setActive(filterProc.passFileFilter(symlink.getRelativePathAny()));
+ symlink.setActive(symlink.passFileFilter(filter_));
}
void processDir(FolderPair& folder) const
{
bool childItemMightMatch = true;
- const bool filterPassed = filterProc.passDirFilter(folder.getRelativePathAny(), &childItemMightMatch);
+ const bool filterPassed = folder.passDirFilter(filter_, &childItemMightMatch);
if (Eval<strategy>::process(folder))
folder.setActive(filterPassed);
- if (!childItemMightMatch) //use same logic like directory traversing here: evaluate filter in subdirs only if objects could match
+ if (!childItemMightMatch) //use same logic like directory traversing: evaluate filter in subdirs only if objects *could* match
{
//exclude all files dirs in subfolders => incompatible with STRATEGY_OR!
auto onFsItem = [](FileSystemObject& fsObj) { fsObj.setActive(false); };
@@ -886,7 +1006,7 @@ private:
recurse(folder);
}
- const PathFilter& filterProc;
+ const PathFilter& filter_;
};
@@ -894,18 +1014,18 @@ template <FilterStrategy strategy>
class ApplySoftFilter //falsify only! -> can run directly after "hard/base filter"
{
public:
- static void execute(ContainerObject& hierObj, const SoftFilter& timeSizeFilter) { ApplySoftFilter(hierObj, timeSizeFilter); }
+ static void execute(ContainerObject& conObj, const SoftFilter& timeSizeFilter) { ApplySoftFilter(conObj, timeSizeFilter); }
private:
- ApplySoftFilter(ContainerObject& hierObj, const SoftFilter& timeSizeFilter) : timeSizeFilter_(timeSizeFilter) { recurse(hierObj); }
+ ApplySoftFilter(ContainerObject& conObj, const SoftFilter& timeSizeFilter) : timeSizeFilter_(timeSizeFilter) { recurse(conObj); }
- void recurse(fff::ContainerObject& hierObj) const
+ void recurse(fff::ContainerObject& conObj) const
{
- for (FilePair& file : hierObj.refSubFiles())
+ for (FilePair& file : conObj.refSubFiles())
processFile(file);
- for (SymlinkPair& symlink : hierObj.refSubLinks())
+ for (SymlinkPair& symlink : conObj.refSubLinks())
processLink(symlink);
- for (FolderPair& folder : hierObj.refSubFolders())
+ for (FolderPair& folder : conObj.refSubFolders())
processDir(folder);
}
@@ -982,7 +1102,7 @@ private:
void fff::addHardFiltering(BaseFolderPair& baseFolder, const Zstring& excludeFilter)
{
- ApplyHardFilter<STRATEGY_AND>::execute(baseFolder, NameFilter(FilterConfig().includeFilter, excludeFilter));
+ ApplyPathFilter<STRATEGY_AND>::execute(baseFolder, NameFilter(FilterConfig().includeFilter, excludeFilter));
}
@@ -1014,7 +1134,7 @@ void fff::applyFiltering(FolderComparison& folderCmp, const MainConfiguration& m
const NormalizedFilter normFilter = normalizeFilters(mainCfg.globalFilter, it->localFilter);
//"set" hard filter
- ApplyHardFilter<STRATEGY_SET>::execute(baseFolder, normFilter.nameFilter.ref());
+ ApplyPathFilter<STRATEGY_SET>::execute(baseFolder, normFilter.nameFilter.ref());
//"and" soft filter
addSoftFiltering(baseFolder, normFilter.timeSizeFilter);
@@ -1103,31 +1223,6 @@ std::optional<PathDependency> fff::getPathDependency(const AbstractPath& folderP
//############################################################################################################
-std::pair<std::wstring, int> fff::getSelectedItemsAsString(std::span<const FileSystemObject* const> selectionLeft,
- std::span<const FileSystemObject* const> selectionRight)
-{
- //don't use wxString! its dumb linear allocation strategy brings perf down to a crawl!
- std::wstring fileList; //
- int totalDelCount = 0;
-
- for (const FileSystemObject* fsObj : selectionLeft)
- if (!fsObj->isEmpty<SelectSide::left>())
- {
- fileList += AFS::getDisplayPath(fsObj->getAbstractPath<SelectSide::left>()) + L'\n';
- ++totalDelCount;
- }
-
- for (const FileSystemObject* fsObj : selectionRight)
- if (!fsObj->isEmpty<SelectSide::right>())
- {
- fileList += AFS::getDisplayPath(fsObj->getAbstractPath<SelectSide::right>()) + L'\n';
- ++totalDelCount;
- }
-
- return {fileList, totalDelCount};
-}
-
-
namespace
{
template <SelectSide side>
@@ -1254,38 +1349,34 @@ void copyToAlternateFolderFrom(const std::vector<const FileSystemObject*>& rowsT
}
-void fff::copyToAlternateFolder(std::span<const FileSystemObject* const> rowsToCopyOnLeft,
- std::span<const FileSystemObject* const> rowsToCopyOnRight,
+void fff::copyToAlternateFolder(const std::vector<const FileSystemObject*>& selectionL,
+ const std::vector<const FileSystemObject*>& selectionR,
const Zstring& targetFolderPathPhrase,
- bool keepRelPaths,
- bool overwriteIfExists,
+ bool keepRelPaths, bool overwriteIfExists,
WarningDialogs& warnings,
ProcessCallback& callback /*throw X*/) //throw X
{
- std::vector<const FileSystemObject*> itemSelectionLeft (rowsToCopyOnLeft .begin(), rowsToCopyOnLeft .end());
- std::vector<const FileSystemObject*> itemSelectionRight(rowsToCopyOnRight.begin(), rowsToCopyOnRight.end());
- std::erase_if(itemSelectionLeft, [](const FileSystemObject* fsObj) { return fsObj->isEmpty<SelectSide::left >(); }); //needed for correct stats!
- std::erase_if(itemSelectionRight, [](const FileSystemObject* fsObj) { return fsObj->isEmpty<SelectSide::right>(); }); //
+ assert(std::all_of(selectionL.begin(), selectionL.end(), [](const FileSystemObject* fsObj) { return !fsObj->isEmpty<SelectSide::left >(); }));
+ assert(std::all_of(selectionR.begin(), selectionR.end(), [](const FileSystemObject* fsObj) { return !fsObj->isEmpty<SelectSide::right>(); }));
- const int itemTotal = static_cast<int>(itemSelectionLeft.size() + itemSelectionRight.size());
+ const int itemTotal = static_cast<int>(selectionL.size() + selectionR.size());
int64_t bytesTotal = 0;
- for (const FileSystemObject* fsObj : itemSelectionLeft)
+ for (const FileSystemObject* fsObj : selectionL)
visitFSObject(*fsObj, [](const FolderPair& folder) {},
[&](const FilePair& file) { bytesTotal += static_cast<int64_t>(file.getFileSize<SelectSide::left>()); }, [](const SymlinkPair& symlink) {});
- for (const FileSystemObject* fsObj : itemSelectionRight)
+ for (const FileSystemObject* fsObj : selectionR)
visitFSObject(*fsObj, [](const FolderPair& folder) {},
[&](const FilePair& file) { bytesTotal += static_cast<int64_t>(file.getFileSize<SelectSide::right>()); }, [](const SymlinkPair& symlink) {});
callback.initNewPhase(itemTotal, bytesTotal, ProcessPhase::none); //throw X
-
//------------------------------------------------------------------------------
const AbstractPath targetFolderPath = createAbstractPath(targetFolderPathPhrase);
- copyToAlternateFolderFrom<SelectSide::left >(itemSelectionLeft, targetFolderPath, keepRelPaths, overwriteIfExists, callback);
- copyToAlternateFolderFrom<SelectSide::right>(itemSelectionRight, targetFolderPath, keepRelPaths, overwriteIfExists, callback);
+ copyToAlternateFolderFrom<SelectSide::left >(selectionL, targetFolderPath, keepRelPaths, overwriteIfExists, callback);
+ copyToAlternateFolderFrom<SelectSide::right>(selectionR, targetFolderPath, keepRelPaths, overwriteIfExists, callback);
}
//############################################################################################################
@@ -1293,11 +1384,12 @@ void fff::copyToAlternateFolder(std::span<const FileSystemObject* const> rowsToC
namespace
{
template <SelectSide side>
-void deleteFromGridAndHdOneSide(std::vector<FileSystemObject*>& rowsToDelete,
- bool moveToRecycler,
- bool& recyclerMissingReportOnce,
- bool& warnRecyclerMissing, //WarningDialogs::warnRecyclerMissing
- PhaseCallback& callback /*throw X*/) //throw X
+void deleteFilesOneSide(const std::vector<FileSystemObject*>& rowsToDelete,
+ bool moveToRecycler,
+ bool& recyclerMissingReportOnce,
+ bool& warnRecyclerMissing, //WarningDialogs::warnRecyclerMissing
+ const std::unordered_map<const BaseFolderPair*, SyncDirectionConfig>& baseFolderCfgs,
+ PhaseCallback& callback /*throw X*/) //throw X
{
const std::wstring txtDelFilePermanent_ = _("Deleting file %x");
const std::wstring txtDelFileRecycler_ = _("Moving file %x to the recycle bin");
@@ -1431,86 +1523,197 @@ void deleteFromGridAndHdOneSide(std::vector<FileSystemObject*>& rowsToDelete,
{
removeSymlink(symlink.getAbstractPath<side>(), statReporter); //throw FileError, X
});
+ //------- no-throw from here on -------
+ const CompareFileResult catOld = fsObj->getCategory();
fsObj->removeObject<side>(); //if directory: removes recursively!
+
+ //update sync direction: don't call redetermineSyncDirection() because user may have manually changed directions
+ if (catOld == CompareFileResult::FILE_EQUAL)
+ {
+ const SyncDirection newDir = [&]
+ {
+ const SyncDirectionConfig& dirCfg = baseFolderCfgs.find(&fsObj->base())->second; //not found? let it crash!
+
+ if (const DirectionByDiff* diffDirs = std::get_if<DirectionByDiff>(&dirCfg.dirs))
+ return side == SelectSide::left ? diffDirs->rightOnly : diffDirs->leftOnly;
+ else
+ {
+ const DirectionByChange& changeDirs = std::get<DirectionByChange>(dirCfg.dirs);
+ return side == SelectSide::left ? changeDirs.left.delete_ : changeDirs.right.delete_;
+ }
+ }();
+
+ setSyncDirectionRec(newDir, *fsObj); //set new direction (recursively)
+ }
+ //else: keep old syncDir_
}
}, callback); //throw X
}
}
-void fff::deleteFromGridAndHD(const std::vector<FileSystemObject*>& rowsToDeleteOnLeft, //refresh GUI grid after deletion to remove invalid rows
- const std::vector<FileSystemObject*>& rowsToDeleteOnRight, //all pointers need to be bound!
- const std::vector<std::pair<BaseFolderPair*, SyncDirectionConfig>>& directCfgs, //attention: rows will be physically deleted!
- bool moveToRecycler,
- bool& warnRecyclerMissing,
- ProcessCallback& callback /*throw X*/) //throw X
+void fff::deleteFiles(const std::vector<FileSystemObject*>& selectionL,
+ const std::vector<FileSystemObject*>& selectionR,
+ const std::vector<std::pair<BaseFolderPair*, SyncDirectionConfig>>& directCfgs,
+ bool moveToRecycler,
+ bool& warnRecyclerMissing,
+ ProcessCallback& callback /*throw X*/) //throw X
{
- if (directCfgs.empty())
- return;
+ assert(std::all_of(selectionL.begin(), selectionL.end(), [](const FileSystemObject* fsObj) { return !fsObj->isEmpty<SelectSide::left >(); }));
+ assert(std::all_of(selectionR.begin(), selectionR.end(), [](const FileSystemObject* fsObj) { return !fsObj->isEmpty<SelectSide::right>(); }));
+
+ const int itemCount = static_cast<int>(selectionL.size() + selectionR.size());
+ callback.initNewPhase(itemCount, 0, ProcessPhase::none); //throw X
+ //------------------------------------------------------------------------------
+
+ ZEN_ON_SCOPE_EXIT
+ (
+ //*INDENT-OFF*
+ for (const auto& [baseFolder, dirCfg] : directCfgs)
+ baseFolder->removeDoubleEmpty();
+ //*INDENT-ON*
+ );
//build up mapping from base directory to corresponding direction config
std::unordered_map<const BaseFolderPair*, SyncDirectionConfig> baseFolderCfgs;
for (const auto& [baseFolder, dirCfg] : directCfgs)
baseFolderCfgs[baseFolder] = dirCfg;
- std::vector<FileSystemObject*> deleteLeft = rowsToDeleteOnLeft;
- std::vector<FileSystemObject*> deleteRight = rowsToDeleteOnRight;
+ bool recyclerMissingReportOnce = false;
+ deleteFilesOneSide<SelectSide::left >(selectionL, moveToRecycler, recyclerMissingReportOnce, warnRecyclerMissing, baseFolderCfgs, callback); //throw X
+ deleteFilesOneSide<SelectSide::right>(selectionR, moveToRecycler, recyclerMissingReportOnce, warnRecyclerMissing, baseFolderCfgs, callback); //
+}
- std::erase_if(deleteLeft, [](const FileSystemObject* fsObj) { return fsObj->isEmpty<SelectSide::left >(); }); //needed?
- std::erase_if(deleteRight, [](const FileSystemObject* fsObj) { return fsObj->isEmpty<SelectSide::right>(); }); //yes, for correct stats:
+//############################################################################################################
- const int itemCount = static_cast<int>(deleteLeft.size() + deleteRight.size());
- callback.initNewPhase(itemCount, 0, ProcessPhase::none); //throw X
+namespace
+{
+template <SelectSide side>
+void renameItemsOneSide(const std::vector<FileSystemObject*>& selection,
+ const std::span<const Zstring> newNames,
+ const std::unordered_map<const BaseFolderPair*, SyncDirectionConfig>& baseFolderCfgs,
+ PhaseCallback& callback /*throw X*/) //throw X
+{
+ assert(selection.size() == newNames.size());
- //------------------------------------------------------------------------------
+ const std::wstring txtRenamingFileXtoY_ {_("Renaming file %x to %y")};
+ const std::wstring txtRenamingLinkXtoY_ {_("Renaming symbolic link %x to %y")};
+ const std::wstring txtRenamingFolderXtoY_{_("Renaming folder %x to %y")};
- //ensure cleanup: redetermination of sync-directions and removal of invalid rows
- auto updateDirection = [&]
+ for (size_t i = 0; i < selection.size(); ++i)
+ tryReportingError([&]
{
- //update sync direction: we cannot do a full redetermination since the user may have manual changes applied already
- std::vector<FileSystemObject*> rowsToDelete;
- append(rowsToDelete, deleteLeft);
- append(rowsToDelete, deleteRight);
- removeDuplicates(rowsToDelete);
+ FileSystemObject& fsObj = *selection[i];
+ const Zstring& newName = newNames[i];
+
+ assert(!fsObj.isEmpty<side>());
- for (auto it = rowsToDelete.begin(); it != rowsToDelete.end(); ++it)
+ auto haveNameClash = [newNameNorm = getUnicodeNormalForm(newName)](const FileSystemObject& fsObj2)
{
- FileSystemObject& fsObj = **it; //all pointers are required(!) to be bound
+ return !fsObj2.isEmpty<side>() && getUnicodeNormalForm(fsObj2.getItemName<side>()) == newNameNorm;
+ };
- if (fsObj.isEmpty<SelectSide::left>() != fsObj.isEmpty<SelectSide::right>()) //make sure objects exists on one side only
- {
- auto cfgIter = baseFolderCfgs.find(&fsObj.base());
- assert(cfgIter != baseFolderCfgs.end());
- if (cfgIter != baseFolderCfgs.end())
- {
- SyncDirection newDir = SyncDirection::none;
+ const bool nameAlreadyExisting = [&]
+ {
+ for (const FilePair& file : fsObj.parent().refSubFiles())
+ if (haveNameClash(file))
+ return true;
- if (cfgIter->second.var == SyncVariant::twoWay)
- newDir = fsObj.isEmpty<SelectSide::left>() ? SyncDirection::right : SyncDirection::left;
- else
- {
- const DirectionSet& dirCfg = extractDirections(cfgIter->second);
- newDir = fsObj.isEmpty<SelectSide::left>() ? dirCfg.exRightSideOnly : dirCfg.exLeftSideOnly;
- }
+ for (const SymlinkPair& symlink : fsObj.parent().refSubLinks())
+ if (haveNameClash(symlink))
+ return true;
+
+ for (const FolderPair& folder : fsObj.parent().refSubFolders())
+ if (haveNameClash(folder))
+ return true;
+ return false;
+ }();
+
+ //---------------------------------------------------------------
+ ItemStatReporter statReporter(1, 0, callback);
+
+ const std::wstring* txtRenamingXtoY_ = nullptr;
+ visitFSObject(fsObj,
+ [&](const FolderPair& folder) { txtRenamingXtoY_ = &txtRenamingFolderXtoY_; },
+ [&](const FilePair& file) { txtRenamingXtoY_ = &txtRenamingFileXtoY_; },
+ [&](const SymlinkPair& symlink) { txtRenamingXtoY_ = &txtRenamingLinkXtoY_; });
+
+ reportInfo(replaceCpy(replaceCpy(*txtRenamingXtoY_, L"%x", fmtPath(AFS::getDisplayPath(fsObj.getAbstractPath<side>()))),
+ L"%y", fmtPath(newName)), statReporter); //throw X
+
+ if (haveNameClash(fsObj))
+ return assert(false); //theoretically possible, but practically showRenameDialog() won't return until there is an actual name change
+
+ if (nameAlreadyExisting) //avoid inconsistent file model: expecting moveAndRenameItem() to fail (ERROR_ALREADY_EXISTS) is not good enough
+ return callback.reportFatalError(replaceCpy(replaceCpy(_("Cannot rename %x to %y."),
+ L"%x", fmtPath(AFS::getDisplayPath(fsObj.getAbstractPath<side>()))),
+ L"%y", fmtPath(newName)) + L"\n\n" +
+ replaceCpy(_("The name %x is already used by another item."), L"%x", fmtPath(newName))); //throw X
+
+ AFS::moveAndRenameItem(fsObj.getAbstractPath<side>(),
+ AFS::appendRelPath(fsObj.parent().getAbstractPath<side>(), newName)); //throw FileError, (ErrorMoveUnsupported)
+ //------- no-throw from here on -------
+ statReporter.reportDelta(1, 0);
- setSyncDirectionRec(newDir, fsObj); //set new direction (recursively)
+ const CompareFileResult catOld = fsObj.getCategory();
+
+ fsObj.setItemName<side>(newName);
+
+ warn_static("TODO: some users want to manually fix renamed folders/files: combine them here, don't require a re-compare!")
+
+ //update sync direction: don't call redetermineSyncDirection() because user may have manually changed directions
+ if (catOld == CompareFileResult::FILE_EQUAL)
+ {
+ const SyncDirection newDir = [&]
+ {
+ const SyncDirectionConfig& dirCfg = baseFolderCfgs.find(&fsObj.base())->second; //not found? let it crash!
+
+ if (const DirectionByDiff* diffDirs = std::get_if<DirectionByDiff>(&dirCfg.dirs))
+ return side == SelectSide::left ? diffDirs->leftNewer : diffDirs->rightNewer;
+ else
+ {
+ const DirectionByChange& changeDirs = std::get<DirectionByChange>(dirCfg.dirs);
+ return side == SelectSide::left ? changeDirs.left.update : changeDirs.right.update;
}
- }
+ }();
+
+ fsObj.setSyncDir(newDir); //folder? => do not recurse!
}
+ //else: keep old syncDir_
+ else if (fsObj.getCategory() == FILE_EQUAL) //edge-case, but possible
+ fsObj.setSyncDir(SyncDirection::none); //shouldn't matter, but avoids hitting some asserts
- //last step: cleanup empty rows: this one invalidates all pointers!
- for (const auto& [baseFolder, dirCfg] : directCfgs)
- BaseFolderPair::removeEmpty(*baseFolder);
- };
- ZEN_ON_SCOPE_EXIT(updateDirection()); //MSVC: assert is a macro and it doesn't play nice with ZEN_ON_SCOPE_EXIT, surprise... wasn't there something about macros being "evil"?
+ }, callback); //throw X
+}
+}
- bool recyclerMissingReportOnce = false;
- deleteFromGridAndHdOneSide<SelectSide::left >(deleteLeft, moveToRecycler, recyclerMissingReportOnce, warnRecyclerMissing, callback); //throw X
- deleteFromGridAndHdOneSide<SelectSide::right>(deleteRight, moveToRecycler, recyclerMissingReportOnce, warnRecyclerMissing, callback); //
+
+void fff::renameItems(const std::vector<FileSystemObject*>& selectionL,
+ const std::span<const Zstring> newNamesL,
+ const std::vector<FileSystemObject*>& selectionR,
+ const std::span<const Zstring> newNamesR,
+ const std::vector<std::pair<BaseFolderPair*, SyncDirectionConfig>>& directCfgs,
+ ProcessCallback& callback /*throw X*/) //throw X
+{
+ assert(std::all_of(selectionL.begin(), selectionL.end(), [](const FileSystemObject* fsObj) { return !fsObj->isEmpty<SelectSide::left >(); }));
+ assert(std::all_of(selectionR.begin(), selectionR.end(), [](const FileSystemObject* fsObj) { return !fsObj->isEmpty<SelectSide::right>(); }));
+
+ const int itemCount = static_cast<int>(selectionL.size() + selectionR.size());
+ callback.initNewPhase(itemCount, 0, ProcessPhase::none); //throw X
+ //------------------------------------------------------------------------------
+
+ //build up mapping from base directory to corresponding direction config
+ std::unordered_map<const BaseFolderPair*, SyncDirectionConfig> baseFolderCfgs;
+ for (const auto& [baseFolder, dirCfg] : directCfgs)
+ baseFolderCfgs[baseFolder] = dirCfg;
+
+ renameItemsOneSide<SelectSide::left >(selectionL, newNamesL, baseFolderCfgs, callback); //throw X
+ renameItemsOneSide<SelectSide::right>(selectionR, newNamesR, baseFolderCfgs, callback); //
}
//############################################################################################################
+
void fff::deleteListOfFiles(const std::vector<Zstring>& filesToDeletePaths,
std::vector<Zstring>& deletedPaths,
bool moveToRecycler,
diff --git a/FreeFileSync/Source/base/algorithm.h b/FreeFileSync/Source/base/algorithm.h
index 8cc8a87b..11aaad95 100644
--- a/FreeFileSync/Source/base/algorithm.h
+++ b/FreeFileSync/Source/base/algorithm.h
@@ -47,27 +47,28 @@ struct PathDependency
std::optional<PathDependency> getPathDependency(const AbstractPath& folderPathL, const PathFilter& filterL,
const AbstractPath& folderPathR, const PathFilter& filterR);
-std::pair<std::wstring, int> getSelectedItemsAsString( //returns string with item names and total count of selected(!) items, NOT total files/dirs!
- std::span<const FileSystemObject* const> selectionLeft, //all pointers need to be bound!
- std::span<const FileSystemObject* const> selectionRight); //
-
//manual copy to alternate folder:
-void copyToAlternateFolder(std::span<const FileSystemObject* const> rowsToCopyOnLeft, //all pointers need to be bound!
- std::span<const FileSystemObject* const> rowsToCopyOnRight, //
+void copyToAlternateFolder(const std::vector<const FileSystemObject*>& selectionL, //all pointers need to be bound and !isEmpty<side>!
+ const std::vector<const FileSystemObject*>& selectionR, //
const Zstring& targetFolderPathPhrase,
- bool keepRelPaths,
- bool overwriteIfExists,
+ bool keepRelPaths, bool overwriteIfExists,
WarningDialogs& warnings,
ProcessCallback& callback /*throw X*/); //throw X
//manual deletion of files on main grid
-void deleteFromGridAndHD(const std::vector<FileSystemObject*>& rowsToDeleteOnLeft, //refresh GUI grid after deletion to remove invalid rows
- const std::vector<FileSystemObject*>& rowsToDeleteOnRight, //all pointers need to be bound!
- const std::vector<std::pair<BaseFolderPair*, SyncDirectionConfig>>& directCfgs, //attention: rows will be physically deleted!
- bool moveToRecycler,
- //global warnings:
- bool& warnRecyclerMissing,
- ProcessCallback& callback /*throw X*/); //throw X
+void deleteFiles(const std::vector<FileSystemObject*>& selectionL, //all pointers need to be bound and !isEmpty<side>!
+ const std::vector<FileSystemObject*>& selectionR, //refresh GUI grid after deletion to remove invalid rows
+ const std::vector<std::pair<BaseFolderPair*, SyncDirectionConfig>>& directCfgs, //attention: rows will be physically deleted!
+ bool moveToRecycler,
+ bool& warnRecyclerMissing,
+ ProcessCallback& callback /*throw X*/); //throw X
+
+void renameItems(const std::vector<FileSystemObject*>& selectionL, //all pointers need to be bound and !isEmpty<side>!
+ const std::span<const Zstring> newNamesL,
+ const std::vector<FileSystemObject*>& selectionR, //refresh GUI grid after deletion to remove invalid rows
+ const std::span<const Zstring> newNamesR,
+ const std::vector<std::pair<BaseFolderPair*, SyncDirectionConfig>>& directCfgs,
+ ProcessCallback& callback /*throw X*/); //throw X
void deleteListOfFiles(const std::vector<Zstring>& filesToDeletePaths,
std::vector<Zstring>& deletedPaths,
diff --git a/FreeFileSync/Source/base/cmp_filetime.h b/FreeFileSync/Source/base/cmp_filetime.h
index 04633df7..c148c6b0 100644
--- a/FreeFileSync/Source/base/cmp_filetime.h
+++ b/FreeFileSync/Source/base/cmp_filetime.h
@@ -66,12 +66,14 @@ enum class TimeResult
};
+//number of seconds since Jan 1st 1970 + 1 year (needn't be too precise)
+inline const time_t oneYearFromNow = std::time(nullptr) + 365 * 24 * 3600;
+
+
inline
TimeResult compareFileTime(time_t lhs, time_t rhs, int tolerance, const std::vector<unsigned int>& ignoreTimeShiftMinutes)
{
- //number of seconds since Jan 1st 1970 + 1 year (needn't be too precise)
- static const time_t oneYearFromNow = std::time(nullptr) + 365 * 24 * 3600;
-
+ assert(oneYearFromNow != 0);
if (sameFileTime(lhs, rhs, tolerance, ignoreTimeShiftMinutes)) //last write time may differ by up to 2 seconds (NTFS vs FAT32)
return TimeResult::equal;
diff --git a/FreeFileSync/Source/base/comparison.cpp b/FreeFileSync/Source/base/comparison.cpp
index 8c1c9544..5b764ca2 100644
--- a/FreeFileSync/Source/base/comparison.cpp
+++ b/FreeFileSync/Source/base/comparison.cpp
@@ -237,9 +237,10 @@ FolderComparison ComparisonBuffer::execute(const std::vector<std::pair<ResolvedF
{
//+ only traverse *existing* folders
if (getBaseFolderStatus(folderPair.folderPathLeft) == BaseFolderStatus::existing)
- foldersToRead.emplace(DirectoryKey({folderPair.folderPathLeft, fpCfg.filter.nameFilter, fpCfg.handleSymlinks}));
+ foldersToRead.emplace(DirectoryKey{folderPair.folderPathLeft, fpCfg.filter.nameFilter, fpCfg.handleSymlinks});
if (getBaseFolderStatus(folderPair.folderPathRight) == BaseFolderStatus::existing)
- foldersToRead.emplace(DirectoryKey({folderPair.folderPathRight, fpCfg.filter.nameFilter, fpCfg.handleSymlinks}));
+ foldersToRead.emplace(DirectoryKey{folderPair.folderPathRight, fpCfg.filter.nameFilter, fpCfg.handleSymlinks});
+ warn_static("remove DirectoryKey{} prefix once mac supports it")
}
//------------------------------------------------------------------
@@ -301,8 +302,8 @@ FolderComparison ComparisonBuffer::execute(const std::vector<std::pair<ResolvedF
//--------------------assemble conflict descriptions---------------------------
-//const wchar_t arrowLeft [] = L"\u2190";
-//const wchar_t arrowRight[] = L"\u2192"; unicode arrows -> too small
+//const wchar_t arrowLeft [] = L"\u2190"; unicode arrows -> too small
+//const wchar_t arrowRight[] = L"\u2192";
const wchar_t arrowLeft [] = L"<-";
const wchar_t arrowRight[] = L"->";
@@ -313,15 +314,15 @@ template <SelectSide side, class FileOrLinkPair> inline
Zstringc getConflictInvalidDate(const FileOrLinkPair& file)
{
return utfTo<Zstringc>(replaceCpy(_("File %x has an invalid date."), L"%x", fmtPath(AFS::getDisplayPath(file.template getAbstractPath<side>()))) + L'\n' +
- TAB_SPACE + _("Date:") + L' ' + formatUtcToLocalTime(file.template getLastWriteTime<side>()));
+ _("Date:") + L' ' + formatUtcToLocalTime(file.template getLastWriteTime<side>()));
}
Zstringc getConflictSameDateDiffSize(const FilePair& file)
{
return utfTo<Zstringc>(_("Files have the same date but a different size.") + L'\n' +
- TAB_SPACE + arrowLeft + L' ' + _("Date:") + L' ' + formatUtcToLocalTime(file.getLastWriteTime<SelectSide::left >()) + TAB_SPACE + _("Size:") + L' ' + formatNumber(file.getFileSize<SelectSide::left>()) + L'\n' +
- TAB_SPACE + arrowRight + L' ' + _("Date:") + L' ' + formatUtcToLocalTime(file.getLastWriteTime<SelectSide::right>()) + TAB_SPACE + _("Size:") + L' ' + formatNumber(file.getFileSize<SelectSide::right>()));
+ _("Date:") + L' ' + formatUtcToLocalTime(file.getLastWriteTime<SelectSide::left >()) + TAB_SPACE + _("Size:") + L' ' + formatNumber(file.getFileSize<SelectSide::left >()) + L' ' + arrowLeft + L'\n' +
+ _("Date:") + L' ' + formatUtcToLocalTime(file.getLastWriteTime<SelectSide::right>()) + TAB_SPACE + _("Size:") + L' ' + formatNumber(file.getFileSize<SelectSide::right>()) + L' ' + arrowRight);
}
@@ -331,25 +332,6 @@ Zstringc getConflictSkippedBinaryComparison()
}
-Zstringc getDescrDiffMetaShortnameCase(const FileSystemObject& fsObj)
-{
- return utfTo<Zstringc>(_("Items differ in attributes only") + L'\n' +
- TAB_SPACE + arrowLeft + L' ' + fmtPath(fsObj.getItemName<SelectSide::left >()) + L'\n' +
- TAB_SPACE + arrowRight + L' ' + fmtPath(fsObj.getItemName<SelectSide::right>()));
-}
-
-
-#if 0
-template <class FileOrLinkPair>
-Zstringc getDescrDiffMetaData(const FileOrLinkPair& file)
-{
- return utfTo<Zstringc>(_("Items differ in attributes only") + L'\n' +
- TAB_SPACE + arrowLeft + L' ' + _("Date:") + L' ' + formatUtcToLocalTime(file.template getLastWriteTime<SelectSide::left >()) + L'\n' +
- TAB_SPACE + arrowRight + L' ' + _("Date:") + L' ' + formatUtcToLocalTime(file.template getLastWriteTime<SelectSide::right>()));
-}
-#endif
-
-
Zstringc getConflictAmbiguousItemName(const Zstring& itemName)
{
return utfTo<Zstringc>(replaceCpy(_("The name %x is used by more than one item in the folder."), L"%x", fmtPath(itemName)));
@@ -364,31 +346,23 @@ void categorizeSymlinkByTime(SymlinkPair& symlink)
symlink.getLastWriteTime<SelectSide::right>(), symlink.base().getFileTimeTolerance(), symlink.base().getIgnoredTimeShift()))
{
case TimeResult::equal:
- //Caveat:
- //1. SYMLINK_EQUAL may only be set if short names match in case: InSyncFolder's mapping tables use short name as a key! see db_file.cpp
- //2. harmonize with "bool stillInSync()" in algorithm.cpp
-
- if (getUnicodeNormalForm(symlink.getItemName<SelectSide::left >()) ==
- getUnicodeNormalForm(symlink.getItemName<SelectSide::right>()))
- symlink.setCategory<FILE_EQUAL>();
- else
- symlink.setCategoryDiffMetadata(getDescrDiffMetaShortnameCase(symlink));
+ symlink.setContentCategory(FileContentCategory::equal);
break;
case TimeResult::leftNewer:
- symlink.setCategory<FILE_LEFT_NEWER>();
+ symlink.setContentCategory(FileContentCategory::leftNewer);
break;
case TimeResult::rightNewer:
- symlink.setCategory<FILE_RIGHT_NEWER>();
+ symlink.setContentCategory(FileContentCategory::rightNewer);
break;
case TimeResult::leftInvalid:
- symlink.setCategoryConflict(getConflictInvalidDate<SelectSide::left>(symlink));
+ symlink.setCategoryInvalidTime(getConflictInvalidDate<SelectSide::left>(symlink));
break;
case TimeResult::rightInvalid:
- symlink.setCategoryConflict(getConflictInvalidDate<SelectSide::right>(symlink));
+ symlink.setCategoryInvalidTime(getConflictInvalidDate<SelectSide::right>(symlink));
break;
}
}
@@ -412,36 +386,26 @@ SharedRef<BaseFolderPair> ComparisonBuffer::compareByTimeSize(const ResolvedFold
file->getLastWriteTime<SelectSide::right>(), fileTimeTolerance_, fpConfig.ignoreTimeShiftMinutes))
{
case TimeResult::equal:
- //Caveat:
- //1. FILE_EQUAL may only be set if short names match in case: InSyncFolder's mapping tables use short name as a key! see db_file.cpp
- //2. FILE_EQUAL is expected to mean identical file sizes! See InSyncFile
- //3. harmonize with "bool stillInSync()" in algorithm.cpp, FilePair::setSyncedTo() in file_hierarchy.h
if (file->getFileSize<SelectSide::left>() == file->getFileSize<SelectSide::right>())
- {
- if (getUnicodeNormalForm(file->getItemName<SelectSide::left >()) ==
- getUnicodeNormalForm(file->getItemName<SelectSide::right>()))
- file->setCategory<FILE_EQUAL>();
- else
- file->setCategoryDiffMetadata(getDescrDiffMetaShortnameCase(*file));
- }
+ file->setContentCategory(FileContentCategory::equal);
else
- file->setCategoryConflict(getConflictSameDateDiffSize(*file)); //same date, different filesize
+ file->setCategoryInvalidTime(getConflictSameDateDiffSize(*file));
break;
case TimeResult::leftNewer:
- file->setCategory<FILE_LEFT_NEWER>();
+ file->setContentCategory(FileContentCategory::leftNewer);
break;
case TimeResult::rightNewer:
- file->setCategory<FILE_RIGHT_NEWER>();
+ file->setContentCategory(FileContentCategory::rightNewer);
break;
case TimeResult::leftInvalid:
- file->setCategoryConflict(getConflictInvalidDate<SelectSide::left>(*file));
+ file->setCategoryInvalidTime(getConflictInvalidDate<SelectSide::left>(*file));
break;
case TimeResult::rightInvalid:
- file->setCategoryConflict(getConflictInvalidDate<SelectSide::right>(*file));
+ file->setCategoryInvalidTime(getConflictInvalidDate<SelectSide::right>(*file));
break;
}
}
@@ -467,25 +431,7 @@ void categorizeSymlinkByContent(SymlinkPair& symlink, PhaseCallback& callback)
if (!errMsg.empty())
symlink.setCategoryConflict(utfTo<Zstringc>(errMsg));
else
- {
- if (equalContent)
- {
- //Caveat:
- //1. SYMLINK_EQUAL may only be set if short names match in case: InSyncFolder's mapping tables use short name as a key! see db_file.cpp
- //2. harmonize with "bool stillInSync()" in algorithm.cpp, FilePair::setSyncedTo() in file_hierarchy.h
-
- if (getUnicodeNormalForm(symlink.getItemName<SelectSide::left >()) !=
- getUnicodeNormalForm(symlink.getItemName<SelectSide::right>()))
- symlink.setCategoryDiffMetadata(getDescrDiffMetaShortnameCase(symlink));
- //else if (!sameFileTime(symlink.getLastWriteTime<SelectSide::left>(),
- // symlink.getLastWriteTime<SelectSide::right>(), symlink.base().getFileTimeTolerance(), symlink.base().getIgnoredTimeShift()))
- // symlink.setCategoryDiffMetadata(getDescrDiffMetaData(symlink));
- else
- symlink.setCategory<FILE_EQUAL>();
- }
- else
- symlink.setCategory<FILE_DIFFERENT_CONTENT>();
- }
+ symlink.setContentCategory(equalContent ? FileContentCategory::equal : FileContentCategory::different);
}
}
@@ -506,19 +452,13 @@ SharedRef<BaseFolderPair> ComparisonBuffer::compareBySize(const ResolvedFolderPa
for (FilePair* file : uncategorizedFiles)
{
//Caveat:
- //1. FILE_EQUAL may only be set if short names match in case: InSyncFolder's mapping tables use short name as a key! see db_file.cpp
+ //1. FILE_EQUAL may only be set if file names match in case: InSyncFolder's mapping tables use file name as a key! see db_file.cpp
//2. FILE_EQUAL is expected to mean identical file sizes! See InSyncFile
//3. harmonize with "bool stillInSync()" in algorithm.cpp, FilePair::setSyncedTo() in file_hierarchy.h
if (file->getFileSize<SelectSide::left>() == file->getFileSize<SelectSide::right>())
- {
- if (getUnicodeNormalForm(file->getItemName<SelectSide::left >()) ==
- getUnicodeNormalForm(file->getItemName<SelectSide::right>()))
- file->setCategory<FILE_EQUAL>();
- else
- file->setCategoryDiffMetadata(getDescrDiffMetaShortnameCase(*file));
- }
+ file->setContentCategory(FileContentCategory::equal);
else
- file->setCategory<FILE_DIFFERENT_CONTENT>();
+ file->setContentCategory(FileContentCategory::different);
}
return output;
}
@@ -544,7 +484,8 @@ void categorizeFileByContent(FilePair& file, const std::wstring& txtComparingCon
bool haveSameContent = false;
const std::wstring errMsg = tryReportingError([&]
{
- std::wstring statusMsg = replaceCpy(txtComparingContentOfFiles, L"%x", fmtPath(file.getRelativePathAny()));
+ std::wstring statusMsg = replaceCpy(txtComparingContentOfFiles, L"%x", fmtPath(file.getRelativePath<SelectSide::left>()));
+ //is it possible that right side has a different relPath? maybe, but who cares, it's a short-lived status message
ItemStatReporter statReporter(1, file.getFileSize<SelectSide::left>(), acb);
PercentStatReporter percentReporter(statusMsg, file.getFileSize<SelectSide::left>(), statReporter);
@@ -566,27 +507,7 @@ void categorizeFileByContent(FilePair& file, const std::wstring& txtComparingCon
if (!errMsg.empty())
file.setCategoryConflict(utfTo<Zstringc>(errMsg));
else
- {
- if (haveSameContent)
- {
- //Caveat:
- //1. FILE_EQUAL may only be set if short names match in case: InSyncFolder's mapping tables use short name as a key! see db_file.cpp
- //2. FILE_EQUAL is expected to mean identical file sizes! See InSyncFile
- //3. harmonize with "bool stillInSync()" in algorithm.cpp, FilePair::setSyncedTo() in file_hierarchy.h
- if (getUnicodeNormalForm(file.getItemName<SelectSide::left >()) !=
- getUnicodeNormalForm(file.getItemName<SelectSide::right>()))
- file.setCategoryDiffMetadata(getDescrDiffMetaShortnameCase(file));
-#if 0 //don't synchronize modtime only see FolderPairSyncer::synchronizeFileInt(), SO_COPY_METADATA_TO_*
- else if (!sameFileTime(file.getLastWriteTime<SelectSide::left>(),
- file.getLastWriteTime<SelectSide::right>(), file.base().getFileTimeTolerance(), file.base().getIgnoredTimeShift()))
- file.setCategoryDiffMetadata(getDescrDiffMetaData(file));
-#endif
- else
- file.setCategory<FILE_EQUAL>();
- }
- else
- file.setCategory<FILE_DIFFERENT_CONTENT>();
- }
+ file.setContentCategory(haveSameContent ? FileContentCategory::equal : FileContentCategory::different);
}
}
@@ -631,11 +552,12 @@ std::vector<SharedRef<BaseFolderPair>> ComparisonBuffer::compareByContent(const
for (FilePair* file : undefinedFiles)
//pre-check: files have different content if they have a different file size (must not be FILE_EQUAL: see InSyncFile)
if (file->getFileSize<SelectSide::left>() != file->getFileSize<SelectSide::right>())
- file->setCategory<FILE_DIFFERENT_CONTENT>();
+ file->setContentCategory(FileContentCategory::different);
else
{
//perf: skip binary comparison for excluded rows (e.g. via time span and size filter)!
//both soft and hard filter were already applied in ComparisonBuffer::performComparison()!
+ assert(file->getContentCategory() == FileContentCategory::unknown); //=default
if (!file->isActive())
file->setCategoryConflict(txtConflictSkippedBinaryComparison);
else
@@ -728,44 +650,65 @@ std::vector<SharedRef<BaseFolderPair>> ComparisonBuffer::compareByContent(const
class MergeSides
{
public:
- MergeSides(const std::unordered_map<ZstringNoCase, Zstringc>& errorsByRelPath,
- std::vector<FilePair*>& undefinedFilesOut,
- std::vector<SymlinkPair*>& undefinedSymlinksOut) :
- errorsByRelPath_(errorsByRelPath),
- undefinedFiles_(undefinedFilesOut),
- undefinedSymlinks_(undefinedSymlinksOut) {}
-
- void execute(const FolderContainer& lhs, const FolderContainer& rhs, ContainerObject& output)
+ static void execute(const FolderContainer& lhs, const FolderContainer& rhs,
+ const std::unordered_map<Zstring, Zstringc>& errorsByRelPathL,
+ const std::unordered_map<Zstring, Zstringc>& errorsByRelPathR,
+ ContainerObject& output,
+ std::vector<FilePair*>& undefinedFilesOut,
+ std::vector<SymlinkPair*>& undefinedSymlinksOut)
{
- auto it = errorsByRelPath_.find(Zstring()); //empty path if read-error for whole base directory
+ MergeSides inst(errorsByRelPathL, errorsByRelPathR, undefinedFilesOut, undefinedSymlinksOut);
+
+ const Zstringc* errorMsg = nullptr;
+ if (auto it = inst.errorsByRelPathL_.find(Zstring()); //empty path if read-error for whole base directory
+ it != inst.errorsByRelPathL_.end())
+ errorMsg = &it->second;
+ else if (auto it2 = inst.errorsByRelPathR_.find(Zstring());
+ it2 != inst.errorsByRelPathR_.end())
+ errorMsg = &it2->second;
- mergeTwoSides(lhs, rhs,
- it != errorsByRelPath_.end() ? &it->second : nullptr,
- output);
+ inst.mergeFolders(lhs, rhs, errorMsg, output);
}
private:
- void mergeTwoSides(const FolderContainer& lhs, const FolderContainer& rhs, const Zstringc* errorMsg, ContainerObject& output);
+ MergeSides(const std::unordered_map<Zstring, Zstringc>& errorsByRelPathL,
+ const std::unordered_map<Zstring, Zstringc>& errorsByRelPathR,
+ std::vector<FilePair*>& undefinedFilesOut,
+ std::vector<SymlinkPair*>& undefinedSymlinksOut) :
+ errorsByRelPathL_(errorsByRelPathL),
+ errorsByRelPathR_(errorsByRelPathR),
+ undefinedFiles_(undefinedFilesOut),
+ undefinedSymlinks_(undefinedSymlinksOut) {}
+
+ void mergeFolders(const FolderContainer& lhs, const FolderContainer& rhs, const Zstringc* errorMsg, ContainerObject& output);
template <SelectSide side>
void fillOneSide(const FolderContainer& folderCont, const Zstringc* errorMsg, ContainerObject& output);
+ template <SelectSide side>
+ const Zstringc* checkFailedRead(FileSystemObject& fsObj, const Zstringc* errorMsg);
+
const Zstringc* checkFailedRead(FileSystemObject& fsObj, const Zstringc* errorMsg);
- const std::unordered_map<ZstringNoCase, Zstringc>& errorsByRelPath_; //base-relative paths or empty if read-error for whole base directory
+ const std::unordered_map<Zstring, Zstringc>& errorsByRelPathL_; //base-relative paths or empty if read-error for whole base directory
+ const std::unordered_map<Zstring, Zstringc>& errorsByRelPathR_; //
std::vector<FilePair*>& undefinedFiles_;
std::vector<SymlinkPair*>& undefinedSymlinks_;
};
-inline
+template <SelectSide side> inline
const Zstringc* MergeSides::checkFailedRead(FileSystemObject& fsObj, const Zstringc* errorMsg)
{
if (!errorMsg)
- if (!errorsByRelPath_.empty()) //only pay for ZstringNoCase construction when needed
- if (const auto it = errorsByRelPath_.find(fsObj.getRelativePathAny());
- it != errorsByRelPath_.end())
+ {
+ const std::unordered_map<Zstring, Zstringc>& errorsByRelPath = selectParam<side>(errorsByRelPathL_, errorsByRelPathR_);
+
+ if (!errorsByRelPath.empty()) //only pay for relPath construction when needed
+ if (const auto it = errorsByRelPath.find(fsObj.getRelativePath<side>());
+ it != errorsByRelPath.end())
errorMsg = &it->second;
+ }
if (errorMsg) //make sure all items are disabled => avoid user panicking: https://freefilesync.org/forum/viewtopic.php?t=7582
{
@@ -777,6 +720,15 @@ const Zstringc* MergeSides::checkFailedRead(FileSystemObject& fsObj, const Zstri
}
+const Zstringc* MergeSides::checkFailedRead(FileSystemObject& fsObj, const Zstringc* errorMsg)
+{
+ if (const Zstringc* errorMsgNew = checkFailedRead<SelectSide::left>(fsObj, errorMsg))
+ return errorMsgNew;
+
+ return checkFailedRead<SelectSide::right>(fsObj, errorMsg);
+}
+
+
template <class MapType, class Function>
void forEachSorted(const MapType& fileMap, Function fun)
{
@@ -802,20 +754,19 @@ void MergeSides::fillOneSide(const FolderContainer& folderCont, const Zstringc*
forEachSorted(folderCont.files, [&](const Zstring& fileName, const FileAttributes& attrib)
{
FilePair& newItem = output.addFile<side>(fileName, attrib);
- checkFailedRead(newItem, errorMsg);
+ checkFailedRead<side>(newItem, errorMsg);
});
forEachSorted(folderCont.symlinks, [&](const Zstring& linkName, const LinkAttributes& attrib)
{
SymlinkPair& newItem = output.addLink<side>(linkName, attrib);
- checkFailedRead(newItem, errorMsg);
+ checkFailedRead<side>(newItem, errorMsg);
});
forEachSorted(folderCont.folders, [&](const Zstring& folderName, const std::pair<FolderAttributes, FolderContainer>& attrib)
{
FolderPair& newFolder = output.addFolder<side>(folderName, attrib.first);
- const Zstringc* errorMsgNew = checkFailedRead(newFolder, errorMsg);
-
+ const Zstringc* errorMsgNew = checkFailedRead<side>(newFolder, errorMsg);
fillOneSide<side>(attrib.second, errorMsgNew, newFolder); //recurse
});
}
@@ -894,13 +845,13 @@ void matchFolders(const MapType& mapLeft, const MapType& mapRight, ProcessLeftOn
}
-void MergeSides::mergeTwoSides(const FolderContainer& lhs, const FolderContainer& rhs, const Zstringc* errorMsg, ContainerObject& output)
+void MergeSides::mergeFolders(const FolderContainer& lhs, const FolderContainer& rhs, const Zstringc* errorMsg, ContainerObject& output)
{
using FileData = FolderContainer::FileList::value_type;
matchFolders(lhs.files, rhs.files, [&](const FileData& fileLeft, const Zstringc* conflictMsg)
{
- FilePair& newItem = output.addFile<SelectSide::left >(fileLeft.first, fileLeft.second);
+ FilePair& newItem = output.addFile<SelectSide::left>(fileLeft.first, fileLeft.second);
checkFailedRead(newItem, conflictMsg ? conflictMsg : errorMsg);
},
[&](const FileData& fileRight, const Zstringc* conflictMsg)
@@ -912,7 +863,6 @@ void MergeSides::mergeTwoSides(const FolderContainer& lhs, const FolderContainer
{
FilePair& newItem = output.addFile(fileLeft.first,
fileLeft.second,
- FILE_CONFLICT, //dummy-value until categorization is finished later
fileRight.first,
fileRight.second);
if (!checkFailedRead(newItem, errorMsg))
@@ -925,7 +875,7 @@ void MergeSides::mergeTwoSides(const FolderContainer& lhs, const FolderContainer
matchFolders(lhs.symlinks, rhs.symlinks, [&](const SymlinkData& symlinkLeft, const Zstringc* conflictMsg)
{
- SymlinkPair& newItem = output.addLink<SelectSide::left >(symlinkLeft.first, symlinkLeft.second);
+ SymlinkPair& newItem = output.addLink<SelectSide::left>(symlinkLeft.first, symlinkLeft.second);
checkFailedRead(newItem, conflictMsg ? conflictMsg : errorMsg);
},
[&](const SymlinkData& symlinkRight, const Zstringc* conflictMsg)
@@ -937,7 +887,6 @@ void MergeSides::mergeTwoSides(const FolderContainer& lhs, const FolderContainer
{
SymlinkPair& newItem = output.addLink(symlinkLeft.first,
symlinkLeft.second,
- SYMLINK_CONFLICT, //dummy-value until categorization is finished later
symlinkRight.first,
symlinkRight.second);
if (!checkFailedRead(newItem, errorMsg))
@@ -961,34 +910,28 @@ void MergeSides::mergeTwoSides(const FolderContainer& lhs, const FolderContainer
},
[&](const FolderData& dirLeft, const FolderData& dirRight)
{
- FolderPair& newFolder = output.addFolder(dirLeft.first, dirLeft.second.first, DIR_EQUAL, dirRight.first, dirRight.second.first);
+ FolderPair& newFolder = output.addFolder(dirLeft.first, dirLeft.second.first, dirRight.first, dirRight.second.first);
const Zstringc* errorMsgNew = checkFailedRead(newFolder, errorMsg);
-
- if (!errorMsgNew)
- if (getUnicodeNormalForm(dirLeft.first) !=
- getUnicodeNormalForm(dirRight.first))
- newFolder.setCategoryDiffMetadata(getDescrDiffMetaShortnameCase(newFolder));
-
- mergeTwoSides(dirLeft.second.second, dirRight.second.second, errorMsgNew, newFolder); //recurse
+ mergeFolders(dirLeft.second.second, dirRight.second.second, errorMsgNew, newFolder); //recurse
});
}
//-----------------------------------------------------------------------------------------------
//uncheck excluded directories (see parallelDeviceTraversal()) + remove superfluous excluded subdirectories
-void stripExcludedDirectories(ContainerObject& hierObj, const PathFilter& filterProc)
+void stripExcludedDirectories(ContainerObject& conObj, const PathFilter& filter)
{
- for (FolderPair& folder : hierObj.refSubFolders())
- stripExcludedDirectories(folder, filterProc);
+ for (FolderPair& folder : conObj.refSubFolders())
+ stripExcludedDirectories(folder, filter);
- //remove superfluous directories:
- // this does not invalidate "std::vector<FilePair*>& undefinedFiles", since we delete folders only
- // and there is no side-effect for memory positions of FilePair and SymlinkPair thanks to std::list!
+ /* remove superfluous directories:
+ this does not invalidate "std::vector<FilePair*>& undefinedFiles", since we delete folders only
+ and there is no side-effect for memory positions of FilePair and SymlinkPair thanks to std::list! */
static_assert(std::is_same_v<std::list<FolderPair>, ContainerObject::FolderList>);
- hierObj.refSubFolders().remove_if([&](FolderPair& folder)
+ conObj.refSubFolders().remove_if([&](FolderPair& folder)
{
- const bool included = filterProc.passDirFilter(folder.getRelativePathAny(), nullptr); //childItemMightMatch is false, child items were already excluded during scanning
+ const bool included = folder.passDirFilter(filter, nullptr /*childItemMightMatch*/); //child items were already excluded during scanning
if (!included) //falsify only! (e.g. might already be inactive due to read error!)
folder.setActive(false);
@@ -1014,7 +957,8 @@ SharedRef<BaseFolderPair> ComparisonBuffer::performComparison(const ResolvedFold
const BaseFolderStatus folderStatusR = getBaseFolderStatus(fp.folderPathRight);
- std::unordered_map<ZstringNoCase, Zstringc> failedReads; //base-relative paths or empty if read-error for whole base directory
+ std::unordered_map<Zstring, Zstringc> failedReadsL; //base-relative paths or empty if read-error for whole base directory
+ std::unordered_map<Zstring, Zstringc> failedReadsR; //
const FolderContainer* folderContL = nullptr;
const FolderContainer* folderContR = nullptr;
@@ -1027,14 +971,14 @@ SharedRef<BaseFolderPair> ComparisonBuffer::performComparison(const ResolvedFold
if (it == folderStatus_.failedChecks.end())
it = folderStatus_.failedChecks.find(fp.folderPathRight);
- failedReads[Zstring() /*empty string for root*/] = utfTo<Zstringc>(it->second.toString());
+ failedReadsL[Zstring() /*empty string for root*/] = failedReadsR[Zstring()] = utfTo<Zstringc>(it->second.toString());
folderContL = &empty; //no need to list or display one-sided results if
- folderContR = &empty; //*any* folder existence check fails (even if other side would have been in folderBuffer_!)
+ folderContR = &empty; //*any* folder existence check failed (even if other side exists in folderBuffer_!)
}
else
{
- auto evalBuffer = [&](const AbstractPath& folderPath, const FolderContainer*& folderCont)
+ auto evalBuffer = [&](const AbstractPath& folderPath, const FolderContainer*& folderCont, std::unordered_map<Zstring, Zstringc>& failedReads)
{
auto it = folderBuffer_.find({folderPath, fpCfg.filter.nameFilter, fpCfg.handleSymlinks});
if (it != folderBuffer_.end())
@@ -1043,9 +987,9 @@ SharedRef<BaseFolderPair> ComparisonBuffer::performComparison(const ResolvedFold
//mix failedFolderReads with failedItemReads:
//associate folder traversing errors with folder (instead of child items only) to show on GUI! See "MergeSides"
- //=> minor pessimization for "excludefilterFailedRead" which needlessly excludes parent folders, too
- failedReads.insert(dirVal.failedFolderReads.begin(), dirVal.failedFolderReads.end());
- failedReads.insert(dirVal.failedItemReads .begin(), dirVal.failedItemReads .end());
+ //=> minor pessimization for "excludeFilterFailedRead" which needlessly excludes parent folders, too
+ failedReads = dirVal.failedFolderReads; //failedReads.insert(dirVal.failedFolderReads.begin(), dirVal.failedFolderReads.end());
+ failedReads.insert(dirVal.failedItemReads.begin(), dirVal.failedItemReads.end());
assert(getBaseFolderStatus(folderPath) == BaseFolderStatus::existing);
folderCont = &dirVal.folderCont;
@@ -1056,34 +1000,41 @@ SharedRef<BaseFolderPair> ComparisonBuffer::performComparison(const ResolvedFold
folderCont = &empty;
}
};
- evalBuffer(fp.folderPathLeft, folderContL);
- evalBuffer(fp.folderPathRight, folderContR);
+ evalBuffer(fp.folderPathLeft, folderContL, failedReadsL);
+ evalBuffer(fp.folderPathRight, folderContR, failedReadsR);
}
- Zstring excludefilterFailedRead;
- if (failedReads.contains(Zstring())) //empty path if read-error for whole base directory
- excludefilterFailedRead += Zstr("*\n");
+ Zstring excludeFilterFailedRead;
+ if (failedReadsL.contains(Zstring()) ||
+ failedReadsR.contains(Zstring())) //empty path if read-error for whole base directory
+ excludeFilterFailedRead += Zstr("*\n");
else
- for (const auto& [relPath, errorMsg] : failedReads)
- excludefilterFailedRead += relPath.upperCase + Zstr('\n'); //exclude item AND (potential) child items!
+ {
+ for (const auto& [relPath, errorMsg] : failedReadsL)
+ excludeFilterFailedRead += relPath + Zstr('\n'); //exclude item AND (potential) child items!
+
+ for (const auto& [relPath, errorMsg] : failedReadsR)
+ excludeFilterFailedRead += relPath + Zstr('\n');
+ }
//somewhat obscure, but it's possible on Linux file systems to have a backslash as part of a file name
//=> avoid misinterpretation when parsing the filter phrase in PathFilter (see path_filter.cpp::parseFilterPhrase())
- if constexpr (FILE_NAME_SEPARATOR != Zstr('/' )) replace(excludefilterFailedRead, Zstr('/'), Zstr('?'));
- if constexpr (FILE_NAME_SEPARATOR != Zstr('\\')) replace(excludefilterFailedRead, Zstr('\\'), Zstr('?'));
+ if constexpr (FILE_NAME_SEPARATOR != Zstr('/' )) replace(excludeFilterFailedRead, Zstr('/'), Zstr('?'));
+ if constexpr (FILE_NAME_SEPARATOR != Zstr('\\')) replace(excludeFilterFailedRead, Zstr('\\'), Zstr('?'));
SharedRef<BaseFolderPair> output = makeSharedRef<BaseFolderPair>(fp.folderPathLeft,
folderStatusL, //check folder existence only once!
fp.folderPathRight,
folderStatusR, //
- fpCfg.filter.nameFilter.ref().copyFilterAddingExclusion(excludefilterFailedRead),
+ fpCfg.filter.nameFilter.ref().copyFilterAddingExclusion(excludeFilterFailedRead),
fpCfg.compareVar,
fileTimeTolerance_,
fpCfg.ignoreTimeShiftMinutes);
//PERF_START;
- MergeSides(failedReads, undefinedFiles, undefinedSymlinks).execute(*folderContL, *folderContR, output.ref());
+ MergeSides::execute(*folderContL, *folderContR, failedReadsL, failedReadsR,
+ output.ref(), undefinedFiles, undefinedSymlinks);
//PERF_STOP;
//##################### in/exclude rows according to filtering #####################
diff --git a/FreeFileSync/Source/base/db_file.cpp b/FreeFileSync/Source/base/db_file.cpp
index 193abe7a..e5dac708 100644
--- a/FreeFileSync/Source/base/db_file.cpp
+++ b/FreeFileSync/Source/base/db_file.cpp
@@ -24,7 +24,7 @@ namespace
//-------------------------------------------------------------------------------------------------------------------------------
const char DB_FILE_DESCR[] = "FreeFileSync";
const int DB_FILE_VERSION = 11; //2020-02-07
-const int DB_STREAM_VERSION = 4; //2021-02-14
+const int DB_STREAM_VERSION = 5; //2023-07-29
//-------------------------------------------------------------------------------------------------------------------------------
struct SessionData
@@ -294,7 +294,6 @@ private:
for (const auto& [itemName, inSyncData] : container.folders)
{
writeItemName(itemName.normStr);
- writeNumber<int32_t>(streamOutSmallNum_, inSyncData.status);
recurse(inSyncData);
}
@@ -368,7 +367,7 @@ public:
const std::string tmpL = readContainer<std::string>(streamInL);
const std::string tmpR = readContainer<std::string>(streamInR);
- auto output = makeSharedRef<InSyncFolder>(InSyncFolder::DIR_STATUS_IN_SYNC);
+ auto output = makeSharedRef<InSyncFolder>();
StreamParserV2 parser(decompress(tmpL), //
decompress(tmpR), //throw SysError
decompress(tmpB)); //
@@ -376,6 +375,7 @@ public:
return output;
}
else if (streamVersion == 3 || //TODO: remove migration code at some time! 2021-02-14
+ streamVersion == 4 || //TODO: remove migration code at some time! 2023-07-29
streamVersion == DB_STREAM_VERSION)
{
MemoryStreamIn& streamInPart1 = leadStreamLeft ? streamInL : streamInR;
@@ -393,7 +393,7 @@ public:
const std::string bufSmallNum = readContainer<std::string>(streamIn); //throw SysErrorUnexpectedEos
const std::string bufBigNum = readContainer<std::string>(streamIn); //
- auto output = makeSharedRef<InSyncFolder>(InSyncFolder::DIR_STATUS_IN_SYNC);
+ auto output = makeSharedRef<InSyncFolder>();
StreamParser parser(streamVersion,
decompress(bufText), //
decompress(bufSmallNum), //throw SysError
@@ -433,12 +433,12 @@ private:
const auto cmpVar = static_cast<CompareVariant>(readNumber<int32_t>(streamInSmallNum_)); //
const uint64_t fileSize = readNumber<uint64_t>(streamInSmallNum_); //
- const InSyncDescrFile dataL = readFileDescr(); //throw SysErrorUnexpectedEos
- const InSyncDescrFile dataT = readFileDescr(); //
+ const InSyncDescrFile descrL = readFileDescr(); //throw SysErrorUnexpectedEos
+ const InSyncDescrFile descrT = readFileDescr(); //
container.addFile(itemName,
- selectParam<leadSide>(dataL, dataT),
- selectParam<leadSide>(dataT, dataL), cmpVar, fileSize);
+ selectParam<leadSide>(descrL, descrT),
+ selectParam<leadSide>(descrT, descrL), cmpVar, fileSize);
}
size_t linkCount = readNumber<uint32_t>(streamInSmallNum_);
@@ -447,21 +447,23 @@ private:
const Zstring itemName = readItemName(); //
const auto cmpVar = static_cast<CompareVariant>(readNumber<int32_t>(streamInSmallNum_)); //
- const InSyncDescrLink dataL{static_cast<time_t>(readNumber<int64_t>(streamInBigNum_))}; //throw SysErrorUnexpectedEos
- const InSyncDescrLink dataT{static_cast<time_t>(readNumber<int64_t>(streamInBigNum_))}; //
+ const InSyncDescrLink descrL{static_cast<time_t>(readNumber<int64_t>(streamInBigNum_))}; //throw SysErrorUnexpectedEos
+ const InSyncDescrLink descrT{static_cast<time_t>(readNumber<int64_t>(streamInBigNum_))}; //
container.addSymlink(itemName,
- selectParam<leadSide>(dataL, dataT),
- selectParam<leadSide>(dataT, dataL), cmpVar);
+ selectParam<leadSide>(descrL, descrT),
+ selectParam<leadSide>(descrT, descrL), cmpVar);
}
size_t dirCount = readNumber<uint32_t>(streamInSmallNum_); //
while (dirCount-- != 0)
{
const Zstring itemName = readItemName(); //
- const auto status = static_cast<InSyncFolder::InSyncStatus>(readNumber<int32_t>(streamInSmallNum_)); //
- InSyncFolder& dbFolder = container.addFolder(itemName, status);
+ if (streamVersion_ <= 4) //TODO: remove migration code at some time! 2023-07-29
+ /*const auto status = static_cast<InSyncFolder::InSyncStatus>(*/ readNumber<int32_t>(streamInSmallNum_);
+
+ InSyncFolder& dbFolder = container.addFolder(itemName);
recurse<leadSide>(dbFolder);
}
}
@@ -530,9 +532,9 @@ private:
while (dirCount-- != 0)
{
const Zstring itemName = utfTo<Zstring>(readContainer<std::string>(inputBoth_));
- const auto status = static_cast<InSyncFolder::InSyncStatus>(readNumber<int32_t>(inputBoth_));
+ /*const auto status = static_cast<InSyncFolder::InSyncStatus>(*/ readNumber<int32_t>(inputBoth_);
- InSyncFolder& dbFolder = container.addFolder(itemName, status);
+ InSyncFolder& dbFolder = container.addFolder(itemName);
recurse(dbFolder);
}
}
@@ -567,7 +569,7 @@ public:
static void execute(const BaseFolderPair& baseFolder, InSyncFolder& dbFolder)
{
LastSynchronousStateUpdater updater(baseFolder.getCompVariant(), baseFolder.getFilter());
- updater.recurse(baseFolder, dbFolder);
+ updater.recurse(baseFolder, Zstring(), dbFolder);
}
private:
@@ -575,11 +577,11 @@ private:
filter_(filter),
activeCmpVar_(activeCmpVar) {}
- void recurse(const ContainerObject& hierObj, InSyncFolder& dbFolder)
+ void recurse(const ContainerObject& conObj, const Zstring& relPath, InSyncFolder& dbFolder)
{
- process(hierObj.refSubFiles (), hierObj.getRelativePathAny(), dbFolder.files);
- process(hierObj.refSubLinks (), hierObj.getRelativePathAny(), dbFolder.symlinks);
- process(hierObj.refSubFolders(), hierObj.getRelativePathAny(), dbFolder.folders);
+ process(conObj.refSubFiles (), relPath, dbFolder.files);
+ process(conObj.refSubLinks (), relPath, dbFolder.symlinks);
+ process(conObj.refSubFolders(), relPath, dbFolder.folders);
}
void process(const ContainerObject::FileList& currentFiles, const Zstring& parentRelPath, InSyncFolder::FileList& dbFiles)
@@ -591,20 +593,22 @@ private:
{
if (file.getCategory() == FILE_EQUAL) //data in sync: write current state
{
- //Caveat: If FILE_EQUAL, we *implicitly* assume equal left and right short names matching case: InSyncFolder's mapping tables use short name as a key!
+ //Caveat: If FILE_EQUAL, we *implicitly* assume equal left and right file names matching case: InSyncFolder's mapping tables use file name as a key!
//This makes us silently dependent from code in algorithm.h!!!
- assert(getUnicodeNormalForm(file.getItemName<SelectSide::left>()) == getUnicodeNormalForm(file.getItemName<SelectSide::right>()));
+ assert(file.hasEquivalentItemNames());
+ const Zstring& fileName = file.getItemName<SelectSide::left>();
assert(file.getFileSize<SelectSide::left>() == file.getFileSize<SelectSide::right>());
//create or update new "in-sync" state
- dbFiles.insert_or_assign(file.getItemNameAny(),
- InSyncFile(InSyncDescrFile{file.getLastWriteTime<SelectSide::left >(),
- file.getFilePrint <SelectSide::left >()},
- InSyncDescrFile{file.getLastWriteTime<SelectSide::right>(),
- file.getFilePrint <SelectSide::right>()},
- activeCmpVar_,
- file.getFileSize<SelectSide::left>()));
- toPreserve.insert(file.getItemNameAny());
+ dbFiles.insert_or_assign(fileName, InSyncFile
+ {
+ .left = InSyncDescrFile{file.getLastWriteTime<SelectSide::left >(), file.getFilePrint<SelectSide::left >()},
+ .right = InSyncDescrFile{file.getLastWriteTime<SelectSide::right>(), file.getFilePrint<SelectSide::right>()},
+ .cmpVar = activeCmpVar_,
+ .fileSize = file.getFileSize<SelectSide::left>(),
+ }
+ );
+ toPreserve.insert(fileName);
}
else //not in sync: preserve last synchronous state
{
@@ -634,14 +638,17 @@ private:
{
if (symlink.getLinkCategory() == SYMLINK_EQUAL) //data in sync: write current state
{
- assert(getUnicodeNormalForm(symlink.getItemName<SelectSide::left>()) == getUnicodeNormalForm(symlink.getItemName<SelectSide::right>()));
+ assert(symlink.hasEquivalentItemNames());
+ const Zstring& linkName = symlink.getItemName<SelectSide::left>();
//create or update new "in-sync" state
- dbSymlinks.insert_or_assign(symlink.getItemNameAny(),
- InSyncSymlink(InSyncDescrLink{symlink.getLastWriteTime<SelectSide::left >()},
- InSyncDescrLink{symlink.getLastWriteTime<SelectSide::right>()},
- activeCmpVar_));
- toPreserve.insert(symlink.getItemNameAny());
+ dbSymlinks.insert_or_assign(linkName, InSyncSymlink
+ {
+ .left = InSyncDescrLink{symlink.getLastWriteTime<SelectSide::left >()},
+ .right = InSyncDescrLink{symlink.getLastWriteTime<SelectSide::right>()},
+ .cmpVar = activeCmpVar_,
+ });
+ toPreserve.insert(linkName);
}
else //not in sync: preserve last synchronous state
{
@@ -670,35 +677,36 @@ private:
{
if (folder.getDirCategory() == DIR_EQUAL)
{
- assert(getUnicodeNormalForm(folder.getItemName<SelectSide::left>()) == getUnicodeNormalForm(folder.getItemName<SelectSide::right>()));
+ assert(folder.hasEquivalentItemNames());
+ const Zstring& folderName = folder.getItemName<SelectSide::left>();
- //update directory entry only (shallow), but do *not touch* existing child elements!!!
- InSyncFolder& dbFolder = dbFolders.emplace(folder.getItemNameAny(), InSyncFolder(InSyncFolder::DIR_STATUS_IN_SYNC)).first->second; //get or create
- dbFolder.status = InSyncFolder::DIR_STATUS_IN_SYNC;
+ //create directory entry if not existing (but do *not touch* existing child elements!!!)
+ dbFolders.try_emplace(folderName);
- toPreserve.emplace(folder.getItemNameAny(), &folder);
+ toPreserve.emplace(folderName, &folder);
}
else //not in sync: preserve last synchronous state
{
- toPreserve.emplace(folder.getItemName<SelectSide::left >(), &folder); //names differing in case? => treat like any other folder rename
+ toPreserve.emplace(folder.getItemName<SelectSide::left >(), &folder); //names differing (in case)? => treat like any other folder rename
toPreserve.emplace(folder.getItemName<SelectSide::right>(), &folder); //=> no *new* database entries even if child items are in sync
+ //BUT: update existing one: there should be only *one* DB entry after a folder rename (matching either folder name on left or right)
}
}
//delete removed items (= "in-sync") from database
eraseIf(dbFolders, [&](InSyncFolder::FolderList::value_type& v)
{
+ const Zstring& itemRelPath = appendPath(parentRelPath, v.first.normStr);
+
if (auto it = toPreserve.find(v.first); it != toPreserve.end())
{
- recurse(*(it->second), v.second); //required even if e.g. DIR_LEFT_SIDE_ONLY:
+ recurse(*(it->second), itemRelPath, v.second); //required even if e.g. DIR_LEFT_ONLY:
//existing child-items may not be in sync, but items deleted on both sides *are* in-sync!!!
return false;
}
- const Zstring& itemRelPath = appendPath(parentRelPath, v.first.normStr);
//if folder is not included in "current folders", it is either not existing anymore, in which case it should be deleted from database
//or it was excluded via filter and the database entry should be preserved
-
bool childItemMightMatch = true;
const bool passFilter = filter_.passDirFilter(itemRelPath, &childItemMightMatch);
if (!passFilter && childItemMightMatch)
@@ -917,7 +925,7 @@ void fff::saveLastSynchronousState(const BaseFolderPair& baseFolder, bool transa
//load last synchrounous state
auto itStreamOldL = streamsL.cend();
auto itStreamOldR = streamsR.cend();
- InSyncFolder lastSyncState(InSyncFolder::DIR_STATUS_IN_SYNC);
+ InSyncFolder lastSyncState;
try
{
//find associated session: there can be at most one session within intersection of left and right IDs
diff --git a/FreeFileSync/Source/base/db_file.h b/FreeFileSync/Source/base/db_file.h
index 67be33fa..d8f9c82a 100644
--- a/FreeFileSync/Source/base/db_file.h
+++ b/FreeFileSync/Source/base/db_file.h
@@ -32,7 +32,6 @@ struct InSyncDescrLink
//artificial hierarchy of last synchronous state:
struct InSyncFile
{
- InSyncFile(const InSyncDescrFile& l, const InSyncDescrFile& r, CompareVariant cv, uint64_t fileSizeIn) : left(l), right(r), cmpVar(cv), fileSize(fileSizeIn) {}
InSyncDescrFile left; //support flip()!
InSyncDescrFile right; //
CompareVariant cmpVar = CompareVariant::timeSize; //the one active while finding "file in sync"
@@ -41,7 +40,6 @@ struct InSyncFile
struct InSyncSymlink
{
- InSyncSymlink(const InSyncDescrLink& l, const InSyncDescrLink& r, CompareVariant cv) : left(l), right(r), cmpVar(cv) {}
InSyncDescrLink left;
InSyncDescrLink right;
CompareVariant cmpVar = CompareVariant::timeSize;
@@ -49,17 +47,6 @@ struct InSyncSymlink
struct InSyncFolder
{
- //for directories we have a logical problem: we cannot have "not existent" as an indicator for
- //"no last synchronous state" since this precludes child elements that may be in sync!
- enum InSyncStatus
- {
- DIR_STATUS_IN_SYNC,
- DIR_STATUS_STRAW_MAN //no last synchronous state, but used as container only
- };
- InSyncFolder(InSyncStatus statusIn) : status(statusIn) {}
-
- InSyncStatus status = DIR_STATUS_STRAW_MAN;
-
//------------------------------------------------------------------
using FolderList = std::unordered_map<ZstringNorm, InSyncFolder >; //
using FileList = std::unordered_map<ZstringNorm, InSyncFile >; // key: file name (ignoring Unicode normal forms)
@@ -71,19 +58,26 @@ struct InSyncFolder
SymlinkList symlinks; //non-followed symlinks
//convenience
- InSyncFolder& addFolder(const Zstring& folderName, InSyncStatus st)
+ InSyncFolder& addFolder(const Zstring& folderName)
{
- return folders.emplace(folderName, InSyncFolder(st)).first->second;
+ const auto [it, inserted] = folders.try_emplace(folderName);
+ assert(inserted);
+ return it->second;
}
- void addFile(const Zstring& fileName, const InSyncDescrFile& dataL, const InSyncDescrFile& dataR, CompareVariant cmpVar, uint64_t fileSize)
+ void addFile(const Zstring& fileName, const InSyncDescrFile& descrL, const InSyncDescrFile& descrR, CompareVariant cmpVar, uint64_t fileSize)
{
- files.emplace(fileName, InSyncFile(dataL, dataR, cmpVar, fileSize));
+ files.emplace(fileName, InSyncFile {descrL, descrR, cmpVar, fileSize});
+ assert(inserted);
+ warn_static("use try_emplace once mac is up to the task!!!")
+ //"Parenthesized initialization of aggregates" https://en.cppreference.com/w/cpp/compiler_support/20
}
- void addSymlink(const Zstring& linkName, const InSyncDescrLink& dataL, const InSyncDescrLink& dataR, CompareVariant cmpVar)
+ void addSymlink(const Zstring& linkName, const InSyncDescrLink& descrL, const InSyncDescrLink& descrR, CompareVariant cmpVar)
{
- symlinks.emplace(linkName, InSyncSymlink(dataL, dataR, cmpVar));
+ symlinks.emplace(linkName, InSyncSymlink {descrL, descrR, cmpVar});
+ assert(inserted);
+ warn_static("use try_emplace once mac is up to the task!!!")
}
};
diff --git a/FreeFileSync/Source/base/dir_exist_async.h b/FreeFileSync/Source/base/dir_exist_async.h
index f6af51fa..640479b9 100644
--- a/FreeFileSync/Source/base/dir_exist_async.h
+++ b/FreeFileSync/Source/base/dir_exist_async.h
@@ -53,6 +53,8 @@ FolderStatus getFolderStatusParallel(const std::set<AbstractPath>& folderPaths,
//----------------------------------------------------------------------
std::vector<ThreadGroup<std::packaged_task<bool()>>> deviceThreadGroups;
+ //----------------------------------------------------------------------
+
for (const auto& [device, deviceFolderPaths] : perDevicePaths)
{
deviceThreadGroups.emplace_back(1, Zstr("DirExist: ") + utfTo<Zstring>(AFS::getDisplayPath(AbstractPath(device, AfsPath()))));
diff --git a/FreeFileSync/Source/base/dir_lock.cpp b/FreeFileSync/Source/base/dir_lock.cpp
index 0486ccd3..cf0ff18a 100644
--- a/FreeFileSync/Source/base/dir_lock.cpp
+++ b/FreeFileSync/Source/base/dir_lock.cpp
@@ -296,8 +296,6 @@ ProcessStatus getProcessStatus(const LockInformation& lockInfo) //throw FileErro
DEFINE_NEW_FILE_ERROR(ErrorFileNotExisting)
uint64_t getLockFileSize(const Zstring& filePath) //throw FileError, ErrorFileNotExisting
{
- //yes, checking error ERROR_FILE_NOT_FOUND, ENOENT is not 100% reliable (e.g. might indicate missing parent folder)
- //but good enough for our lock file!
struct stat fileInfo = {};
if (::stat(filePath.c_str(), &fileInfo) == 0)
return fileInfo.st_size;
@@ -401,7 +399,7 @@ void waitOnDirLock(const Zstring& lockFilePath, const DirLockCallback& notifySta
if (lastCheckTime >= lastLifeSign + EMIT_LIFE_SIGN_INTERVAL + std::chrono::seconds(1))
{
const int remainingSeconds = std::max(0, static_cast<int>(std::chrono::duration_cast<std::chrono::seconds>(DETECT_ABANDONED_INTERVAL - (now - lastLifeSign)).count()));
- notifyStatus(infoMsg + SPACED_DASH + _("Detecting abandoned lock...") + L' ' + _P("1 sec", "%x sec", remainingSeconds)); //throw X
+ notifyStatus(infoMsg + SPACED_DASH + _("Lock file apparently abandoned...") + L' ' + _P("1 sec", "%x sec", remainingSeconds)); //throw X
}
else
notifyStatus(std::wstring(infoMsg)); //throw X; emit a message in any case (might clear other one)
diff --git a/FreeFileSync/Source/base/file_hierarchy.cpp b/FreeFileSync/Source/base/file_hierarchy.cpp
index e3ed5c88..8945d705 100644
--- a/FreeFileSync/Source/base/file_hierarchy.cpp
+++ b/FreeFileSync/Source/base/file_hierarchy.cpp
@@ -50,156 +50,214 @@ std::wstring fff::getShortDisplayNameForFolderPair(const AbstractPath& itemPathL
else if (AFS::isNullPath(itemPathR))
return getLastComponent(itemPathL);
else
- return getLastComponent(itemPathL) + SPACED_DASH +
+ return getLastComponent(itemPathL) + L" | " +
getLastComponent(itemPathR);
}
-void ContainerObject::removeEmptyRec()
+void ContainerObject::removeDoubleEmpty()
{
- bool emptyExisting = false;
- auto isEmpty = [&](const FileSystemObject& fsObj) -> bool
- {
- const bool objEmpty = fsObj.isPairEmpty();
- if (objEmpty)
- emptyExisting = true;
- return objEmpty;
- };
+ auto isEmpty = [](const FileSystemObject& fsObj) { return fsObj.isPairEmpty(); };
refSubFiles ().remove_if(isEmpty);
refSubLinks ().remove_if(isEmpty);
refSubFolders().remove_if(isEmpty);
- if (emptyExisting) //notify if actual deletion happened
- notifySyncCfgChanged(); //mustn't call this in ~FileSystemObject(), since parent, usually a FolderPair, may already be partially destroyed and existing as a pure ContainerObject!
-
for (FolderPair& folder : refSubFolders())
- folder.removeEmptyRec(); //recurse
+ folder.removeDoubleEmpty();
}
namespace
{
-SyncOperation getIsolatedSyncOperation(bool itemExistsLeft,
- bool itemExistsRight,
- CompareFileResult cmpResult,
+SyncOperation getIsolatedSyncOperation(const FileSystemObject& fsObj,
bool selectedForSync,
SyncDirection syncDir,
- bool hasDirectionConflict) //perf: std::wstring was wasteful here
+ bool hasDirectionConflict)
{
- assert(( itemExistsLeft && itemExistsRight && cmpResult != FILE_LEFT_SIDE_ONLY && cmpResult != FILE_RIGHT_SIDE_ONLY) ||
- ( itemExistsLeft && !itemExistsRight && cmpResult == FILE_LEFT_SIDE_ONLY ) ||
- (!itemExistsLeft && itemExistsRight && cmpResult == FILE_RIGHT_SIDE_ONLY) ||
- (!itemExistsLeft && !itemExistsRight && cmpResult == FILE_EQUAL && syncDir == SyncDirection::none && !hasDirectionConflict) ||
- cmpResult == FILE_CONFLICT);
-
assert(!hasDirectionConflict || syncDir == SyncDirection::none);
- if (!selectedForSync)
- return cmpResult == FILE_EQUAL ?
- SO_EQUAL :
- SO_DO_NOTHING;
-
- switch (cmpResult)
+ if (fsObj.isEmpty<SelectSide::left>() || fsObj.isEmpty<SelectSide::right>())
{
- case FILE_EQUAL:
- assert(syncDir == SyncDirection::none);
- return SO_EQUAL;
+ if (!selectedForSync)
+ return SO_DO_NOTHING;
- case FILE_LEFT_SIDE_ONLY:
- switch (syncDir)
- {
- case SyncDirection::left:
- return SO_DELETE_LEFT; //delete files on left
- case SyncDirection::right:
- return SO_CREATE_NEW_RIGHT; //copy files to right
- case SyncDirection::none:
- return hasDirectionConflict ? SO_UNRESOLVED_CONFLICT : SO_DO_NOTHING;
- }
- break;
+ if (hasDirectionConflict)
+ return SO_UNRESOLVED_CONFLICT;
- case FILE_RIGHT_SIDE_ONLY:
+ if (fsObj.isEmpty<SelectSide::left>())
+ {
+ if (fsObj.isEmpty<SelectSide::right>()) //both sides empty: should only occur temporarily, if ever
+ return SO_EQUAL;
+ else //right-only
+ switch (syncDir)
+ {
+ //*INDENT-OFF*
+ case SyncDirection::left: return SO_CREATE_LEFT;
+ case SyncDirection::right: return SO_DELETE_RIGHT;
+ case SyncDirection::none: return SO_DO_NOTHING;
+ //*INDENT-ON*
+ }
+ }
+ else //left-only
switch (syncDir)
{
- case SyncDirection::left:
- return SO_CREATE_NEW_LEFT; //copy files to left
- case SyncDirection::right:
- return SO_DELETE_RIGHT; //delete files on right
- case SyncDirection::none:
- return hasDirectionConflict ? SO_UNRESOLVED_CONFLICT : SO_DO_NOTHING;
+ //*INDENT-OFF*
+ case SyncDirection::left: return SO_DELETE_LEFT;
+ case SyncDirection::right: return SO_CREATE_RIGHT;
+ case SyncDirection::none: return SO_DO_NOTHING;
+ //*INDENT-ON*
}
- break;
+ }
+ //--------------------------------------------------------------
+ std::optional<SyncOperation> result;
- case FILE_LEFT_NEWER:
- case FILE_RIGHT_NEWER:
- case FILE_DIFFERENT_CONTENT:
- switch (syncDir)
- {
- case SyncDirection::left:
- return SO_OVERWRITE_LEFT; //copy from right to left
- case SyncDirection::right:
- return SO_OVERWRITE_RIGHT; //copy from left to right
- case SyncDirection::none:
- return hasDirectionConflict ? SO_UNRESOLVED_CONFLICT : SO_DO_NOTHING;
- }
- break;
+ visitFSObject(fsObj,
+ [&](const FolderPair& folder) //see FolderPair::getCategory()
+ {
+ if (folder.hasEquivalentItemNames()) //a.k.a. DIR_EQUAL
+ {
+ assert(syncDir == SyncDirection::none);
+ return result = SO_EQUAL; //no matter if "conflict" (e.g. traversal error) or "not selected"
+ }
- case FILE_DIFFERENT_METADATA:
- switch (syncDir)
- {
- case SyncDirection::left:
- return SO_COPY_METADATA_TO_LEFT;
- case SyncDirection::right:
- return SO_COPY_METADATA_TO_RIGHT;
- case SyncDirection::none:
- return hasDirectionConflict ? SO_UNRESOLVED_CONFLICT : SO_DO_NOTHING;
- }
- break;
+ if (!selectedForSync)
+ return result = SO_DO_NOTHING;
- case FILE_CONFLICT:
- switch (syncDir)
- {
- case SyncDirection::left:
- return itemExistsLeft && itemExistsRight ? SO_OVERWRITE_LEFT : itemExistsLeft ? SO_DELETE_LEFT: SO_CREATE_NEW_LEFT;
- case SyncDirection::right:
- return itemExistsLeft && itemExistsRight ? SO_OVERWRITE_RIGHT : itemExistsLeft ? SO_CREATE_NEW_RIGHT : SO_DELETE_RIGHT;
- case SyncDirection::none:
- return hasDirectionConflict ? SO_UNRESOLVED_CONFLICT : SO_DO_NOTHING;
- }
- break;
- }
+ if (hasDirectionConflict)
+ return result = SO_UNRESOLVED_CONFLICT;
- assert(false);
- return SO_DO_NOTHING;
+ switch (syncDir)
+ {
+ //*INDENT-OFF*
+ case SyncDirection::left: return result = SO_RENAME_LEFT;
+ case SyncDirection::right: return result = SO_RENAME_RIGHT;
+ case SyncDirection::none: return result = SO_DO_NOTHING;
+ //*INDENT-ON*
+ }
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
+ },
+ //--------------------------------------------------------------
+ [&](const FilePair& file) //see FilePair::getCategory()
+ {
+ if (file.getContentCategory() == FileContentCategory::equal && file.hasEquivalentItemNames()) //a.k.a. FILE_EQUAL
+ {
+ assert(syncDir == SyncDirection::none);
+ return result = SO_EQUAL; //no matter if "conflict" (e.g. traversal error) or "not selected"
+ }
+
+ if (!selectedForSync)
+ return result = SO_DO_NOTHING;
+
+ if (hasDirectionConflict)
+ return result = SO_UNRESOLVED_CONFLICT;
+
+ switch (file.getContentCategory())
+ {
+ case FileContentCategory::unknown:
+ case FileContentCategory::leftNewer:
+ case FileContentCategory::rightNewer:
+ case FileContentCategory::invalidTime:
+ case FileContentCategory::different:
+ case FileContentCategory::conflict:
+ switch (syncDir)
+ {
+ //*INDENT-OFF*
+ case SyncDirection::left: return result = SO_OVERWRITE_LEFT;
+ case SyncDirection::right: return result = SO_OVERWRITE_RIGHT;
+ case SyncDirection::none: return result = SO_DO_NOTHING;
+ //*INDENT-ON*
+ }
+ break;
+
+ case FileContentCategory::equal:
+ switch (syncDir)
+ {
+ //*INDENT-OFF*
+ case SyncDirection::left: return result = SO_RENAME_LEFT;
+ case SyncDirection::right: return result = SO_RENAME_RIGHT;
+ case SyncDirection::none: return result = SO_DO_NOTHING;
+ //*INDENT-ON*
+ }
+ break;
+ }
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
+ },
+ //--------------------------------------------------------------
+ [&](const SymlinkPair& symlink) //see SymlinkPair::getCategory()
+ {
+ if (symlink.getContentCategory() == FileContentCategory::equal && symlink.hasEquivalentItemNames()) //a.k.a. SYMLINK_EQUAL
+ {
+ assert(syncDir == SyncDirection::none);
+ return result = SO_EQUAL; //no matter if "conflict" (e.g. traversal error) or "not selected"
+ }
+
+ if (!selectedForSync)
+ return result = SO_DO_NOTHING;
+
+ if (hasDirectionConflict)
+ return result = SO_UNRESOLVED_CONFLICT;
+
+ switch (symlink.getContentCategory())
+ {
+ case FileContentCategory::unknown:
+ case FileContentCategory::leftNewer:
+ case FileContentCategory::rightNewer:
+ case FileContentCategory::invalidTime:
+ case FileContentCategory::different:
+ case FileContentCategory::conflict:
+ switch (syncDir)
+ {
+ //*INDENT-OFF*
+ case SyncDirection::left: return result = SO_OVERWRITE_LEFT;
+ case SyncDirection::right: return result = SO_OVERWRITE_RIGHT;
+ case SyncDirection::none: return result = SO_DO_NOTHING;
+ //*INDENT-ON*
+ }
+ break;
+
+ case FileContentCategory::equal:
+ switch (syncDir)
+ {
+ //*INDENT-OFF*
+ case SyncDirection::left: return result = SO_RENAME_LEFT;
+ case SyncDirection::right: return result = SO_RENAME_RIGHT;
+ case SyncDirection::none: return result = SO_DO_NOTHING;
+ //*INDENT-ON*
+ }
+ break;
+ }
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
+ });
+ return *result;
}
template <class Predicate> inline
-bool hasDirectChild(const ContainerObject& hierObj, Predicate p)
+bool hasDirectChild(const ContainerObject& conObj, Predicate p)
{
- return std::any_of(hierObj.refSubFiles ().begin(), hierObj.refSubFiles ().end(), p) ||
- std::any_of(hierObj.refSubLinks ().begin(), hierObj.refSubLinks ().end(), p) ||
- std::any_of(hierObj.refSubFolders().begin(), hierObj.refSubFolders().end(), p);
+ return std::any_of(conObj.refSubFiles ().begin(), conObj.refSubFiles ().end(), p) ||
+ std::any_of(conObj.refSubLinks ().begin(), conObj.refSubLinks ().end(), p) ||
+ std::any_of(conObj.refSubFolders().begin(), conObj.refSubFolders().end(), p);
}
}
SyncOperation FileSystemObject::testSyncOperation(SyncDirection testSyncDir) const //semantics: "what if"! assumes "active, no conflict, no recursion (directory)!
{
- return getIsolatedSyncOperation(!isEmpty<SelectSide::left>(), !isEmpty<SelectSide::right>(), getCategory(), true, testSyncDir, false);
+ return getIsolatedSyncOperation(*this, true, testSyncDir, false);
}
+//SyncOperation FolderPair::testSyncOperation() const -> no recursion: we do NOT consider child elements when testing!
+
SyncOperation FileSystemObject::getSyncOperation() const
{
- return getIsolatedSyncOperation(!isEmpty<SelectSide::left>(), !isEmpty<SelectSide::right>(), getCategory(), selectedForSync_, getSyncDir(), !syncDirectionConflict_.empty());
+ return getIsolatedSyncOperation(*this, selectedForSync_, syncDir_, !syncDirectionConflict_.empty());
//do *not* make a virtual call to testSyncOperation()! See FilePair::testSyncOperation()! <- better not implement one in terms of the other!!!
}
-//SyncOperation FolderPair::testSyncOperation() const -> no recursion: we do NOT want to consider child elements when testing!
-
-
SyncOperation FolderPair::getSyncOperation() const
{
if (!syncOpBuffered_) //redetermine...
@@ -210,18 +268,18 @@ SyncOperation FolderPair::getSyncOperation() const
//action for child elements may occassionally have to overwrite parent task:
switch (*syncOpBuffered_)
{
+ case SO_OVERWRITE_LEFT:
+ case SO_OVERWRITE_RIGHT:
case SO_MOVE_LEFT_FROM:
case SO_MOVE_LEFT_TO:
case SO_MOVE_RIGHT_FROM:
case SO_MOVE_RIGHT_TO:
assert(false);
[[fallthrough]];
- case SO_CREATE_NEW_LEFT:
- case SO_CREATE_NEW_RIGHT:
- case SO_OVERWRITE_LEFT:
- case SO_OVERWRITE_RIGHT:
- case SO_COPY_METADATA_TO_LEFT:
- case SO_COPY_METADATA_TO_RIGHT:
+ case SO_CREATE_LEFT:
+ case SO_CREATE_RIGHT:
+ case SO_RENAME_LEFT:
+ case SO_RENAME_RIGHT:
case SO_EQUAL:
break; //take over suggestion, no problem for child-elements
case SO_DELETE_LEFT:
@@ -232,18 +290,17 @@ SyncOperation FolderPair::getSyncOperation() const
{
//1. if at least one child-element is to be created, make sure parent folder is created also
//note: this automatically fulfills "create parent folders even if excluded"
- if (hasDirectChild(*this,
- [](const FileSystemObject& fsObj)
+ if (hasDirectChild(*this, [](const FileSystemObject& fsObj)
{
- const SyncOperation op = fsObj.getSyncOperation();
- return op == SO_CREATE_NEW_LEFT ||
+ assert(!fsObj.isPairEmpty() || fsObj.getSyncOperation() == SO_DO_NOTHING);
+ const SyncOperation op = fsObj.getSyncOperation();
+ return op == SO_CREATE_LEFT ||
op == SO_MOVE_LEFT_TO;
}))
- syncOpBuffered_ = SO_CREATE_NEW_LEFT;
+ syncOpBuffered_ = SO_CREATE_LEFT;
//2. cancel parent deletion if only a single child is not also scheduled for deletion
else if (*syncOpBuffered_ == SO_DELETE_RIGHT &&
- hasDirectChild(*this,
- [](const FileSystemObject& fsObj)
+ hasDirectChild(*this, [](const FileSystemObject& fsObj)
{
if (fsObj.isPairEmpty())
return false; //fsObj may already be empty because it once contained a "move source"
@@ -255,17 +312,16 @@ SyncOperation FolderPair::getSyncOperation() const
}
else if (isEmpty<SelectSide::right>())
{
- if (hasDirectChild(*this,
- [](const FileSystemObject& fsObj)
+ if (hasDirectChild(*this, [](const FileSystemObject& fsObj)
{
- const SyncOperation op = fsObj.getSyncOperation();
- return op == SO_CREATE_NEW_RIGHT ||
+ assert(!fsObj.isPairEmpty() || fsObj.getSyncOperation() == SO_DO_NOTHING);
+ const SyncOperation op = fsObj.getSyncOperation();
+ return op == SO_CREATE_RIGHT ||
op == SO_MOVE_RIGHT_TO;
}))
- syncOpBuffered_ = SO_CREATE_NEW_RIGHT;
+ syncOpBuffered_ = SO_CREATE_RIGHT;
else if (*syncOpBuffered_ == SO_DELETE_LEFT &&
- hasDirectChild(*this,
- [](const FileSystemObject& fsObj)
+ hasDirectChild(*this, [](const FileSystemObject& fsObj)
{
if (fsObj.isPairEmpty())
return false;
@@ -288,24 +344,26 @@ SyncOperation FilePair::applyMoveOptimization(SyncOperation op) const
/* check whether we can optimize "create + delete" via "move":
note: as long as we consider "create + delete" cases only, detection of renamed files, should be fine even for "binary" comparison variant! */
if (moveFileRef_)
- if (auto refFile = dynamic_cast<const FilePair*>(FileSystemObject::retrieve(moveFileRef_))) //we expect a "FilePair", but only need a "FileSystemObject" here
+ if (auto refFile = dynamic_cast<const FilePair*>(FileSystemObject::retrieve(moveFileRef_)))
+ {
if (refFile->moveFileRef_ == getId()) //both ends should agree...
{
const SyncOperation opRef = refFile->FileSystemObject::getSyncOperation(); //do *not* make a virtual call!
-
- if (op == SO_CREATE_NEW_LEFT &&
+ if (op == SO_CREATE_LEFT &&
opRef == SO_DELETE_LEFT)
op = SO_MOVE_LEFT_TO;
else if (op == SO_DELETE_LEFT &&
- opRef == SO_CREATE_NEW_LEFT)
+ opRef == SO_CREATE_LEFT)
op = SO_MOVE_LEFT_FROM;
- else if (op == SO_CREATE_NEW_RIGHT &&
+ else if (op == SO_CREATE_RIGHT &&
opRef == SO_DELETE_RIGHT)
op = SO_MOVE_RIGHT_TO;
else if (op == SO_DELETE_RIGHT &&
- opRef == SO_CREATE_NEW_RIGHT)
+ opRef == SO_CREATE_RIGHT)
op = SO_MOVE_RIGHT_FROM;
}
+ else assert(false); //...and why shouldn't they?
+ }
return op;
}
@@ -326,9 +384,13 @@ std::wstring fff::getCategoryDescription(CompareFileResult cmpRes)
{
switch (cmpRes)
{
- case FILE_LEFT_SIDE_ONLY:
+ case FILE_EQUAL:
+ return _("Both sides are equal");
+ case FILE_RENAMED:
+ return _("Items differ in name only");
+ case FILE_LEFT_ONLY:
return _("Item exists on left side only");
- case FILE_RIGHT_SIDE_ONLY:
+ case FILE_RIGHT_ONLY:
return _("Item exists on right side only");
case FILE_LEFT_NEWER:
return _("Left side is newer");
@@ -336,10 +398,7 @@ std::wstring fff::getCategoryDescription(CompareFileResult cmpRes)
return _("Right side is newer");
case FILE_DIFFERENT_CONTENT:
return _("Items have different content");
- case FILE_EQUAL:
- return _("Both sides are equal");
- case FILE_DIFFERENT_METADATA:
- return _("Items differ in attributes only");
+ case FILE_TIME_INVALID:
case FILE_CONFLICT:
return _("Conflict/item cannot be categorized");
}
@@ -352,20 +411,34 @@ namespace
{
const wchar_t arrowLeft [] = L"<-";
const wchar_t arrowRight[] = L"->";
+//const wchar_t arrowRight[] = L"\u2192"; unicode arrows -> too small
}
std::wstring fff::getCategoryDescription(const FileSystemObject& fsObj)
{
- const std::wstring footer = L"\n[" + utfTo<std::wstring>(fsObj. getItemNameAny()) + L']';
+ const std::wstring footer = [&]
+ {
+ if (fsObj.hasEquivalentItemNames())
+ return L'\n' + fmtPath(fsObj.getItemName<SelectSide::left>());
+ else
+ return std::wstring(L"\n") +
+ fmtPath(fsObj.getItemName<SelectSide::left >()) + L' ' + arrowLeft + L'\n' +
+ fmtPath(fsObj.getItemName<SelectSide::right>()) + L' ' + arrowRight;
+ }();
+
+ if (const Zstringc descr = fsObj.getCategoryCustomDescription();
+ !descr.empty())
+ return utfTo<std::wstring>(descr) + footer;
const CompareFileResult cmpRes = fsObj.getCategory();
switch (cmpRes)
{
- case FILE_LEFT_SIDE_ONLY:
- case FILE_RIGHT_SIDE_ONLY:
- case FILE_DIFFERENT_CONTENT:
case FILE_EQUAL:
+ case FILE_RENAMED:
+ case FILE_LEFT_ONLY:
+ case FILE_RIGHT_ONLY:
+ case FILE_DIFFERENT_CONTENT:
return getCategoryDescription(cmpRes) + footer; //use generic description
case FILE_LEFT_NEWER:
@@ -377,21 +450,22 @@ std::wstring fff::getCategoryDescription(const FileSystemObject& fsObj)
[&](const FilePair& file)
{
descr += std::wstring(L"\n") +
- arrowLeft + L' ' + formatUtcToLocalTime(file.getLastWriteTime<SelectSide::left >()) + L'\n' +
- arrowRight + L' ' + formatUtcToLocalTime(file.getLastWriteTime<SelectSide::right>());
+ formatUtcToLocalTime(file.getLastWriteTime<SelectSide::left >()) + L' ' + arrowLeft + L'\n' +
+ formatUtcToLocalTime(file.getLastWriteTime<SelectSide::right>()) + L' ' + arrowRight ;
},
[&](const SymlinkPair& symlink)
{
descr += std::wstring(L"\n") +
- arrowLeft + L' ' + formatUtcToLocalTime(symlink.getLastWriteTime<SelectSide::left >()) + L'\n' +
- arrowRight + L' ' + formatUtcToLocalTime(symlink.getLastWriteTime<SelectSide::right>());
+ formatUtcToLocalTime(symlink.getLastWriteTime<SelectSide::left >()) + L' ' + arrowLeft + L'\n' +
+ formatUtcToLocalTime(symlink.getLastWriteTime<SelectSide::right>()) + L' ' + arrowRight ;
});
return descr + footer;
}
- case FILE_DIFFERENT_METADATA:
+ case FILE_TIME_INVALID:
case FILE_CONFLICT:
- return utfTo<std::wstring>(fsObj.getCatExtraDescription()) + footer;
+ assert(false); //should have getCategoryDescription()!
+ return _("Error") + footer;
}
assert(false);
return std::wstring();
@@ -402,9 +476,9 @@ std::wstring fff::getSyncOpDescription(SyncOperation op)
{
switch (op)
{
- case SO_CREATE_NEW_LEFT:
+ case SO_CREATE_LEFT:
return _("Copy new item to left");
- case SO_CREATE_NEW_RIGHT:
+ case SO_CREATE_RIGHT:
return _("Copy new item to right");
case SO_DELETE_LEFT:
return _("Delete left item");
@@ -412,10 +486,10 @@ std::wstring fff::getSyncOpDescription(SyncOperation op)
return _("Delete right item");
case SO_MOVE_LEFT_FROM:
case SO_MOVE_LEFT_TO:
- return _("Move file on left"); //move only supported for files
+ return _("Move left file"); //move only supported for files
case SO_MOVE_RIGHT_FROM:
case SO_MOVE_RIGHT_TO:
- return _("Move file on right");
+ return _("Move right file");
case SO_OVERWRITE_LEFT:
return _("Update left item");
case SO_OVERWRITE_RIGHT:
@@ -424,10 +498,10 @@ std::wstring fff::getSyncOpDescription(SyncOperation op)
return _("Do nothing");
case SO_EQUAL:
return _("Both sides are equal");
- case SO_COPY_METADATA_TO_LEFT:
- return _("Update attributes on left");
- case SO_COPY_METADATA_TO_RIGHT:
- return _("Update attributes on right");
+ case SO_RENAME_LEFT:
+ return _("Rename left item");
+ case SO_RENAME_RIGHT:
+ return _("Rename right item");
case SO_UNRESOLVED_CONFLICT: //not used on GUI, but in .csv
return _("Conflict/item cannot be categorized");
}
@@ -438,37 +512,43 @@ std::wstring fff::getSyncOpDescription(SyncOperation op)
std::wstring fff::getSyncOpDescription(const FileSystemObject& fsObj)
{
- const std::wstring footer = L"\n[" + utfTo<std::wstring>(fsObj. getItemNameAny()) + L']';
-
const SyncOperation op = fsObj.getSyncOperation();
+
+ auto generateFooter = [&]
+ {
+ if (fsObj.hasEquivalentItemNames())
+ return L'\n' + fmtPath(fsObj.getItemName<SelectSide::left>());
+
+ Zstring itemNameNew = fsObj.getItemName<SelectSide::left >();
+ Zstring itemNameOld = fsObj.getItemName<SelectSide::right>();
+
+ if (const SyncDirection dir = getEffectiveSyncDir(op);
+ dir != SyncDirection::none)
+ {
+ if (dir == SyncDirection::left)
+ std::swap(itemNameNew, itemNameOld);
+
+ return L'\n' + fmtPath(itemNameOld) + L' ' + RIGHT_ARROW_CURV_DOWN + L'\n' + fmtPath(itemNameNew);
+ }
+ else
+ return L'\n' +
+ fmtPath(itemNameNew) + L' ' + arrowLeft + L'\n' +
+ fmtPath(itemNameOld) + L' ' + arrowRight;
+ };
+
switch (op)
{
- case SO_CREATE_NEW_LEFT:
- case SO_CREATE_NEW_RIGHT:
+ case SO_CREATE_LEFT:
+ case SO_CREATE_RIGHT:
case SO_DELETE_LEFT:
case SO_DELETE_RIGHT:
case SO_OVERWRITE_LEFT:
case SO_OVERWRITE_RIGHT:
case SO_DO_NOTHING:
case SO_EQUAL:
- return getSyncOpDescription(op) + footer; //use generic description
-
- case SO_COPY_METADATA_TO_LEFT:
- case SO_COPY_METADATA_TO_RIGHT:
- //harmonize with synchronization.cpp::FolderPairSyncer::synchronizeFileInt, ect!!
- {
- Zstring itemNameOld = fsObj.getItemName<SelectSide::right>();
- Zstring itemNameNew = fsObj.getItemName<SelectSide::left >();
- if (op == SO_COPY_METADATA_TO_LEFT)
- std::swap(itemNameOld, itemNameNew);
-
- if (getUnicodeNormalForm(itemNameOld) !=
- getUnicodeNormalForm(itemNameNew)) //detected change in case
- return getSyncOpDescription(op) + L'\n' +
- fmtPath(itemNameOld) + L' ' + arrowRight + L'\n' + //show short name only
- fmtPath(itemNameNew) /*+ footer -> redundant */;
- }
- return getSyncOpDescription(op) + footer; //fallback
+ case SO_RENAME_LEFT:
+ case SO_RENAME_RIGHT:
+ return getSyncOpDescription(op) + generateFooter();
case SO_MOVE_LEFT_FROM:
case SO_MOVE_LEFT_TO:
@@ -484,26 +564,26 @@ std::wstring fff::getSyncOpDescription(const FileSystemObject& fsObj)
if (!isMoveSource)
std::swap(fileFrom, fileTo);
- auto getRelName = [&](const FileSystemObject& fso, bool leftSide) { return leftSide ? fso.getRelativePath<SelectSide::left>() : fso.getRelativePath<SelectSide::right>(); };
+ auto getRelPath = [&](const FileSystemObject& fso) { return onLeft ? fso.getRelativePath<SelectSide::left>() : fso.getRelativePath<SelectSide::right>(); };
- const Zstring relPathFrom = getRelName(*fileFrom, onLeft);
- const Zstring relPathTo = getRelName(*fileTo, onLeft);
+ const Zstring relPathFrom = getRelPath(*fileFrom);
+ const Zstring relPathTo = getRelPath(*fileTo);
- //attention: ::SetWindowText() doesn't handle tab characters correctly in combination with certain file names, so don't use them
+ //attention: ::SetWindowText() doesn't handle tab characters correctly in combination with certain file names, so don't use
return getSyncOpDescription(op) + L'\n' +
(beforeLast(relPathFrom, FILE_NAME_SEPARATOR, IfNotFoundReturn::none) ==
beforeLast(relPathTo, FILE_NAME_SEPARATOR, IfNotFoundReturn::none) ?
//detected pure "rename"
- fmtPath(getItemName(relPathFrom)) + L' ' + arrowRight + L'\n' + //show short name only
+ fmtPath(getItemName(relPathFrom)) + L' ' + RIGHT_ARROW_CURV_DOWN + L'\n' + //show file name only
fmtPath(getItemName(relPathTo)) :
//"move" or "move + rename"
- fmtPath(relPathFrom) + L' ' + arrowRight + L'\n' +
- fmtPath(relPathTo)) /*+ footer -> redundant */;
+ fmtPath(relPathFrom) + L' ' + RIGHT_ARROW_CURV_DOWN + L'\n' +
+ fmtPath(relPathTo));
}
break;
case SO_UNRESOLVED_CONFLICT:
- return fsObj.getSyncOpConflict() + footer;
+ return fsObj.getSyncOpConflict() + generateFooter();
}
assert(false);
diff --git a/FreeFileSync/Source/base/file_hierarchy.h b/FreeFileSync/Source/base/file_hierarchy.h
index cb28b137..040cc0a4 100644
--- a/FreeFileSync/Source/base/file_hierarchy.h
+++ b/FreeFileSync/Source/base/file_hierarchy.h
@@ -22,73 +22,29 @@ namespace fff
{
struct FileAttributes
{
- FileAttributes() {}
- FileAttributes(time_t modTimeIn,
- uint64_t fileSizeIn,
- AFS::FingerPrint filePrintIn,
- bool followedSymlink) :
- modTime(modTimeIn),
- fileSize(fileSizeIn),
- filePrint(filePrintIn),
- isFollowedSymlink(followedSymlink)
- {
- static_assert(std::is_signed_v<time_t>, "... and signed!");
- }
-
time_t modTime = 0; //number of seconds since Jan. 1st 1970 GMT
uint64_t fileSize = 0;
AFS::FingerPrint filePrint = 0; //optional
bool isFollowedSymlink = false;
+ static_assert(std::is_signed_v<time_t>, "we need time_t to be signed");
+
std::strong_ordering operator<=>(const FileAttributes&) const = default;
};
struct LinkAttributes
{
- LinkAttributes() {}
- explicit LinkAttributes(time_t modTimeIn) : modTime(modTimeIn) {}
-
time_t modTime = 0; //number of seconds since Jan. 1st 1970 GMT
};
struct FolderAttributes
{
- FolderAttributes() {}
- explicit FolderAttributes(bool isSymlink) :
- isFollowedSymlink(isSymlink) {}
-
bool isFollowedSymlink = false;
};
-enum class SelectSide
-{
- left,
- right
-};
-
-
-template <SelectSide side>
-constexpr SelectSide getOtherSide = side == SelectSide::left ? SelectSide::right : SelectSide::left;
-
-
-template <SelectSide side, class T> inline
-T& selectParam(T& left, T& right)
-{
- if constexpr (side == SelectSide::left)
- return left;
- else
- return right;
-}
-
-//------------------------------------------------------------------
-
-std::wstring getShortDisplayNameForFolderPair(const AbstractPath& itemPathL, const AbstractPath& itemPathR);
-
-//------------------------------------------------------------------
-
struct FolderContainer
{
//------------------------------------------------------------------
@@ -109,16 +65,12 @@ struct FolderContainer
void addFile(const Zstring& itemName, const FileAttributes& attr)
{
- const auto [it, inserted] = files.emplace(itemName, attr);
- if (!inserted) //update entry if already existing (e.g. during folder traverser "retry")
- it->second = attr;
+ files.insert_or_assign(itemName, attr); //update entry if already existing (e.g. during folder traverser "retry")
}
void addLink(const Zstring& itemName, const LinkAttributes& attr)
{
- const auto [it, inserted] = symlinks.emplace(itemName, attr);
- if (!inserted)
- it->second = attr;
+ symlinks.insert_or_assign(itemName, attr);
}
FolderContainer& addFolder(const Zstring& itemName, const FolderAttributes& attr)
@@ -126,19 +78,82 @@ struct FolderContainer
auto& p = folders[itemName]; //value default-construction is okay here
p.first = attr;
return p.second;
-
- //const auto [it, inserted] = folders.emplace(itemName, std::pair<FolderAttributes, FolderContainer>(attr, FolderContainer()));
- //if (!inserted)
- // it->second.first = attr;
- //return it->second.second;
}
};
-class BaseFolderPair;
-class FolderPair;
-class FilePair;
-class SymlinkPair;
+//------------------------------------------------------------------
+
+enum class SelectSide
+{
+ left,
+ right
+};
+
+
+template <SelectSide side>
+constexpr SelectSide getOtherSide = side == SelectSide::left ? SelectSide::right : SelectSide::left;
+
+
+template <SelectSide side, class T> inline
+T& selectParam(T& left, T& right)
+{
+ if constexpr (side == SelectSide::left)
+ return left;
+ else
+ return right;
+}
+
+
+enum class FileContentCategory : unsigned char
+{
+ unknown,
+ equal,
+ leftNewer,
+ rightNewer,
+ invalidTime,
+ different,
+ conflict,
+};
+
+
+inline
+SyncDirection getEffectiveSyncDir(SyncOperation syncOp)
+{
+ switch (syncOp)
+ {
+ case SO_CREATE_LEFT:
+ case SO_DELETE_LEFT:
+ case SO_OVERWRITE_LEFT:
+ case SO_RENAME_LEFT:
+ case SO_MOVE_LEFT_FROM:
+ case SO_MOVE_LEFT_TO:
+ return SyncDirection::left;
+
+ case SO_CREATE_RIGHT:
+ case SO_DELETE_RIGHT:
+ case SO_OVERWRITE_RIGHT:
+ case SO_RENAME_RIGHT:
+ case SO_MOVE_RIGHT_FROM:
+ case SO_MOVE_RIGHT_TO:
+ return SyncDirection::right;
+
+ case SO_DO_NOTHING:
+ case SO_EQUAL:
+ case SO_UNRESOLVED_CONFLICT:
+ break; //nothing to do
+ }
+ return SyncDirection::none;
+}
+
+
+std::wstring getShortDisplayNameForFolderPair(const AbstractPath& itemPathL, const AbstractPath& itemPathR);
+
+
class FileSystemObject;
+class SymlinkPair;
+class FilePair;
+class FolderPair;
+class BaseFolderPair;
/*------------------------------------------------------------------
inheritance diagram:
@@ -161,7 +176,6 @@ struct PathInformation //diamond-shaped inheritance!
template <SelectSide side> AbstractPath getAbstractPath() const;
template <SelectSide side> Zstring getRelativePath() const; //get path relative to base sync dir (without leading/trailing FILE_NAME_SEPARATOR)
- Zstring getRelativePathAny() const { return getRelativePathL(); } //side doesn't matter
private:
virtual AbstractPath getAbstractPathL() const = 0; //implemented by FileSystemObject + BaseFolderPair
@@ -181,8 +195,7 @@ template <> inline Zstring PathInformation::getRelativePath<SelectSide::right>()
class ContainerObject : public virtual PathInformation
{
- friend class FolderPair;
- friend class FileSystemObject;
+ friend class FileSystemObject; //access to updateRelPathsRecursion()
public:
using FileList = std::list<FilePair>; //MergeSides::execute() requires a structure that doesn't invalidate pointers after push_back()
@@ -191,7 +204,6 @@ public:
FolderPair& addFolder(const Zstring& itemNameL, //file exists on both sides
const FolderAttributes& left,
- CompareDirResult defaultCmpResult,
const Zstring& itemNameR,
const FolderAttributes& right);
@@ -201,7 +213,6 @@ public:
FilePair& addFile(const Zstring& itemNameL, //file exists on both sides
const FileAttributes& left,
- CompareFileResult defaultCmpResult,
const Zstring& itemNameR,
const FileAttributes& right);
@@ -211,7 +222,6 @@ public:
SymlinkPair& addLink(const Zstring& itemNameL, //link exists on both sides
const LinkAttributes& left,
- CompareSymlinkResult defaultCmpResult,
const Zstring& itemNameR,
const LinkAttributes& right);
@@ -231,17 +241,19 @@ public:
const BaseFolderPair& getBase() const { return base_; }
/**/ BaseFolderPair& getBase() { return base_; }
-protected:
- ContainerObject(BaseFolderPair& baseFolder) : //used during BaseFolderPair constructor
- base_(baseFolder) {} //take reference only: baseFolder *not yet* fully constructed at this point!
+ void removeDoubleEmpty(); //remove all invalid entries (where both sides are empty) recursively
- ContainerObject(const FileSystemObject& fsAlias); //used during FolderPair constructor
+ virtual void flip();
- virtual ~ContainerObject() {} //don't need polymorphic deletion, but we have a vtable anyway
+protected:
+ explicit ContainerObject(BaseFolderPair& baseFolder) : //used during BaseFolderPair constructor
+ base_(baseFolder) //take reference only: baseFolder *not yet* fully constructed at this point!
+ { assert(relPathL_.c_str() == relPathR_.c_str()); } //expected by the following contructor!
- virtual void flip();
+ explicit ContainerObject(const FileSystemObject& fsAlias); //used during FolderPair constructor
- void removeEmptyRec();
+ virtual ~ContainerObject() //don't need polymorphic deletion, but we have a vtable anyway
+ { assert(relPathL_.c_str() == relPathR_.c_str() || relPathL_ != relPathR_); }
template <SelectSide side>
void updateRelPathsRecursion(const FileSystemObject& fsAlias);
@@ -250,8 +262,6 @@ private:
ContainerObject (const ContainerObject&) = delete; //this class is referenced by its child elements => make it non-copyable/movable!
ContainerObject& operator=(const ContainerObject&) = delete;
- virtual void notifySyncCfgChanged() {}
-
Zstring getRelativePathL() const override { return relPathL_; }
Zstring getRelativePathR() const override { return relPathR_; }
@@ -260,7 +270,7 @@ private:
FolderList subFolders_;
Zstring relPathL_; //path relative to base sync dir (without leading/trailing FILE_NAME_SEPARATOR)
- Zstring relPathR_; //
+ Zstring relPathR_; //class invariant: shared Zstring iff equal!
BaseFolderPair& base_;
};
@@ -274,7 +284,7 @@ enum class BaseFolderStatus
failure,
};
-class BaseFolderPair : public ContainerObject //synchronization base directory
+class BaseFolderPair : public ContainerObject
{
public:
BaseFolderPair(const AbstractPath& folderPathLeft,
@@ -292,8 +302,6 @@ public:
folderPathLeft_(folderPathLeft),
folderPathRight_(folderPathRight) {}
- static void removeEmpty(BaseFolderPair& baseFolder) { baseFolder.removeEmptyRec(); } //physically remove all invalid entries (where both sides are empty) recursively
-
template <SelectSide side> BaseFolderStatus getFolderStatus() const; //base folder status at the time of comparison!
template <SelectSide side> void setFolderStatus(BaseFolderStatus value); //update after creating the directory in FFS
@@ -410,15 +418,18 @@ public:
template <SelectSide side> bool isEmpty() const;
//path getters always return valid values, even if isEmpty<side>()!
- Zstring getItemNameAny() const; //like getItemName() but without bias to which side is returned
template <SelectSide side> Zstring getItemName() const; //case sensitive!
+ bool hasEquivalentItemNames() const; //*quick* check if left/right names are equivalent when ignoring Unicode normalization forms
+
+ //for use during compare() only:
+ virtual void setCategoryConflict(const Zstringc& description) = 0;
+
//comparison result
- CompareFileResult getCategory() const { return cmpResult_; }
- Zstringc getCatExtraDescription() const; //only filled if getCategory() == FILE_CONFLICT or FILE_DIFFERENT_METADATA
+ virtual CompareFileResult getCategory() const = 0;
+ virtual Zstringc getCategoryCustomDescription() const = 0; //optional
//sync settings
- SyncDirection getSyncDir() const { return syncDir_; }
void setSyncDir(SyncDirection newDir);
void setSyncDirConflict(const Zstringc& description); //set syncDir = SyncDirection::none + fill conflict description
@@ -426,7 +437,7 @@ public:
void setActive(bool active);
//sync operation
- virtual SyncOperation testSyncOperation(SyncDirection testSyncDir) const; //semantics: "what if"! assumes "active, no conflict, no recursion (directory)!
+ virtual SyncOperation testSyncOperation(SyncDirection testSyncDir) const; //"what if" semantics! assumes "active, no conflict, no recursion (directory)!
virtual SyncOperation getSyncOperation() const;
std::wstring getSyncOpConflict() const; //return conflict when determining sync direction or (still unresolved) conflict during categorization
@@ -434,35 +445,36 @@ public:
const ContainerObject& parent() const { return parent_; }
/**/ ContainerObject& parent() { return parent_; }
- const BaseFolderPair& base() const { return parent_.getBase(); }
- /**/ BaseFolderPair& base() { return parent_.getBase(); }
+ const BaseFolderPair& base() const { return parent_.getBase(); }
+ /**/ BaseFolderPair& base() { return parent_.getBase(); }
- //for use during init in "CompareProcess" only:
- template <CompareFileResult res> void setCategory();
- void setCategoryConflict (const Zstringc& description);
- void setCategoryDiffMetadata(const Zstringc& description);
+ bool passFileFilter(const PathFilter& filter) const; //optimized for perf!
+
+ virtual void flip();
+
+ template <SelectSide side>
+ void setItemName(const Zstring& itemName);
protected:
FileSystemObject(const Zstring& itemNameL,
const Zstring& itemNameR,
- ContainerObject& parentObj,
- CompareFileResult defaultCmpResult) :
- cmpResult_(defaultCmpResult),
+ ContainerObject& parentObj) :
itemNameL_(itemNameL),
itemNameR_(itemNameL == itemNameR ? itemNameL : itemNameR), //perf: no measurable speed drawback; -3% peak memory => further needed by ContainerObject construction!
parent_(parentObj)
{
assert(itemNameL_.c_str() == itemNameR_.c_str() || itemNameL_ != itemNameR_); //also checks ref-counted string precondition
- parent_.notifySyncCfgChanged();
+ FileSystemObject::notifySyncCfgChanged(); //non-virtual call! (=> anyway in a constructor!)
}
- virtual ~FileSystemObject() {} //don't need polymorphic deletion, but we have a vtable anyway
- //must not call parent here, it is already partially destroyed and nothing more than a pure ContainerObject!
-
- virtual void flip();
- virtual void notifySyncCfgChanged() { parent().notifySyncCfgChanged(); /*propagate!*/ }
+ virtual ~FileSystemObject() //don't need polymorphic deletion, but we have a vtable anyway
+ { assert(itemNameL_.c_str() == itemNameR_.c_str() || itemNameL_ != itemNameR_); }
- void setSynced(const Zstring& itemName);
+ virtual void notifySyncCfgChanged()
+ {
+ if (auto fsParent = dynamic_cast<FileSystemObject*>(&parent_))
+ fsParent->notifySyncCfgChanged(); //propagate!
+ }
private:
FileSystemObject (const FileSystemObject&) = delete;
@@ -475,22 +487,16 @@ private:
virtual void removeObjectR() = 0;
template <SelectSide side>
- void propagateChangedItemName(const Zstring& itemNameOld); //required after any itemName changes
-
- //categorization
- Zstringc cmpResultDescr_; //only filled if getCategory() == FILE_CONFLICT or FILE_DIFFERENT_METADATA
- //conserve memory (avoid std::string SSO overhead + allow ref-counting!)
- CompareFileResult cmpResult_; //although this uses 4 bytes there is currently *no* space wasted in class layout!
+ void propagateChangedItemName(); //required after any itemName changes
bool selectedForSync_ = true;
- //Note: we model *four* states with following two variables => "syncDirectionConflict is empty or syncDir == NONE" is a class invariant!!!
- SyncDirection syncDir_ = SyncDirection::none; //1 byte: optimize memory layout!
+ SyncDirection syncDir_ = SyncDirection::none;
Zstringc syncDirectionConflict_; //non-empty if we have a conflict setting sync-direction
//conserve memory (avoid std::string SSO overhead + allow ref-counting!)
- Zstring itemNameL_; //slightly redundant under Linux, but on Windows the "same" file paths can differ in case
- Zstring itemNameR_; //use as indicator: an empty name means: not existing on this side!
+ Zstring itemNameL_; //use as indicator: empty means "not existing on this side"
+ Zstring itemNameR_; //class invariant: shared Zstring iff equal!
ContainerObject& parent_;
};
@@ -500,20 +506,18 @@ private:
class FolderPair : public FileSystemObject, public ContainerObject
{
- friend class ContainerObject;
-
public:
void accept(FSObjectVisitor& visitor) const override;
- CompareDirResult getDirCategory() const; //returns actually used subset of CompareFileResult
+ CompareFileResult getCategory() const override;
+ CompareDirResult getDirCategory() const { return static_cast<CompareDirResult>(getCategory()); }
FolderPair(const Zstring& itemNameL, //use empty itemName if "not existing"
const FolderAttributes& attrL,
- CompareDirResult defaultCmpResult,
const Zstring& itemNameR,
const FolderAttributes& attrR,
ContainerObject& parentObj) :
- FileSystemObject(itemNameL, itemNameR, parentObj, static_cast<CompareFileResult>(defaultCmpResult)),
+ FileSystemObject(itemNameL, itemNameR, parentObj),
ContainerObject(static_cast<FileSystemObject&>(*this)), //FileSystemObject fully constructed at this point!
attrL_(attrL),
attrR_(attrR) {}
@@ -523,18 +527,26 @@ public:
SyncOperation getSyncOperation() const override;
template <SelectSide sideTrg>
- void setSyncedTo(const Zstring& itemName, bool isSymlinkTrg, bool isSymlinkSrc); //call after sync, sets DIR_EQUAL
+ void setSyncedTo(bool isSymlinkTrg, bool isSymlinkSrc); //call after successful sync
+
+ bool passDirFilter(const PathFilter& filter, bool* childItemMightMatch) const; //optimized for perf!
+
+ void flip() override;
+
+ void setCategoryConflict(const Zstringc& description) override;
+ Zstringc getCategoryCustomDescription() const override; //optional
private:
- void flip () override;
void removeObjectL() override;
void removeObjectR() override;
- void notifySyncCfgChanged() override { syncOpBuffered_ = {}; FileSystemObject::notifySyncCfgChanged(); ContainerObject::notifySyncCfgChanged(); }
+ void notifySyncCfgChanged() override { syncOpBuffered_ = {}; FileSystemObject::notifySyncCfgChanged(); }
mutable std::optional<SyncOperation> syncOpBuffered_; //determining sync-op for directory may be expensive as it depends on child-objects => buffer
FolderAttributes attrL_;
FolderAttributes attrR_;
+
+ Zstringc categoryConflict_; //conserve memory (avoid std::string SSO overhead + allow ref-counting!
};
@@ -542,21 +554,20 @@ private:
class FilePair : public FileSystemObject
{
- friend class ContainerObject; //construction
-
public:
void accept(FSObjectVisitor& visitor) const override;
FilePair(const Zstring& itemNameL, //use empty string if "not existing"
const FileAttributes& attrL,
- CompareFileResult defaultCmpResult,
const Zstring& itemNameR, //
const FileAttributes& attrR,
ContainerObject& parentObj) :
- FileSystemObject(itemNameL, itemNameR, parentObj, defaultCmpResult),
+ FileSystemObject(itemNameL, itemNameR, parentObj),
attrL_(attrL),
attrR_(attrR) {}
+ CompareFileResult getCategory() const override;
+
template <SelectSide side> time_t getLastWriteTime() const;
template <SelectSide side> uint64_t getFileSize() const;
template <SelectSide side> bool isFollowedSymlink() const;
@@ -565,22 +576,28 @@ public:
template <SelectSide side> void clearFilePrint();
void setMoveRef(ObjectId refId) { moveFileRef_ = refId; } //reference to corresponding renamed file
- ObjectId getMoveRef() const { return moveFileRef_; } //may be nullptr
-
- CompareFileResult getFileCategory() const;
+ ObjectId getMoveRef() const { assert(!moveFileRef_ || (isEmpty<SelectSide::left>() != isEmpty<SelectSide::right>())); return moveFileRef_; } //may be nullptr
SyncOperation testSyncOperation(SyncDirection testSyncDir) const override; //semantics: "what if"! assumes "active, no conflict, no recursion (directory)!
SyncOperation getSyncOperation() const override;
template <SelectSide sideTrg>
- void setSyncedTo(const Zstring& itemName, //call after sync, sets FILE_EQUAL
- uint64_t fileSize,
+ void setSyncedTo(uint64_t fileSize,
int64_t lastWriteTimeTrg,
int64_t lastWriteTimeSrc,
AFS::FingerPrint filePrintTrg,
AFS::FingerPrint filePrintSrc,
bool isSymlinkTrg,
- bool isSymlinkSrc);
+ bool isSymlinkSrc); //call after successful sync
+
+ void flip() override;
+
+ void setCategoryConflict(const Zstringc& description) override;
+ void setCategoryInvalidTime(const Zstringc& description);
+ Zstringc getCategoryCustomDescription() const override; //optional
+
+ void setContentCategory(FileContentCategory category);
+ FileContentCategory getContentCategory() const;
private:
Zstring getRelativePathL() const override { return appendPath(parent().getRelativePath<SelectSide::left >(), getItemName<SelectSide::left >()); }
@@ -588,65 +605,75 @@ private:
SyncOperation applyMoveOptimization(SyncOperation op) const;
- void flip () override;
- void removeObjectL() override { attrL_ = FileAttributes(); }
- void removeObjectR() override { attrR_ = FileAttributes(); }
+ void removeObjectL() override { attrL_ = FileAttributes(); contentCategory_ = FileContentCategory::unknown; }
+ void removeObjectR() override { attrR_ = FileAttributes(); contentCategory_ = FileContentCategory::unknown; }
FileAttributes attrL_;
FileAttributes attrR_;
ObjectId moveFileRef_ = nullptr; //optional, filled by redetermineSyncDirection()
+
+ FileContentCategory contentCategory_ = FileContentCategory::unknown;
+ Zstringc categoryDescr_; //optional: custom category description (e.g. FileContentCategory::conflict or invalidTime)
};
//------------------------------------------------------------------
class SymlinkPair : public FileSystemObject //this class models a TRUE symbolic link, i.e. one that is NEVER dereferenced: deref-links should be directly placed in class File/FolderPair
{
- friend class ContainerObject; //construction
-
public:
void accept(FSObjectVisitor& visitor) const override;
- template <SelectSide side> time_t getLastWriteTime() const; //write time of the link, NOT target!
-
- CompareSymlinkResult getLinkCategory() const; //returns actually used subset of CompareFileResult
-
- SymlinkPair(const Zstring& itemNameL, //use empty string if "not existing"
- const LinkAttributes& attrL,
- CompareSymlinkResult defaultCmpResult,
- const Zstring& itemNameR, //use empty string if "not existing"
- const LinkAttributes& attrR,
+ SymlinkPair(const Zstring& itemNameL, //use empty string if "not existing"
+ const LinkAttributes& attrL,
+ const Zstring& itemNameR, //use empty string if "not existing"
+ const LinkAttributes& attrR,
ContainerObject& parentObj) :
- FileSystemObject(itemNameL, itemNameR, parentObj, static_cast<CompareFileResult>(defaultCmpResult)),
+ FileSystemObject(itemNameL, itemNameR, parentObj),
attrL_(attrL),
attrR_(attrR) {}
+ CompareFileResult getCategory() const override;
+ CompareSymlinkResult getLinkCategory() const { return static_cast<CompareSymlinkResult>(getCategory()); }
+
+ template <SelectSide side> time_t getLastWriteTime() const; //write time of the link, NOT target!
+
template <SelectSide sideTrg>
- void setSyncedTo(const Zstring& itemName, //call after sync, sets SYMLINK_EQUAL
- int64_t lastWriteTimeTrg,
- int64_t lastWriteTimeSrc);
+ void setSyncedTo(int64_t lastWriteTimeTrg,
+ int64_t lastWriteTimeSrc); //call after successful sync
+
+ void flip() override;
+
+ void setCategoryConflict(const Zstringc& description) override;
+ void setCategoryInvalidTime(const Zstringc& description);
+ Zstringc getCategoryCustomDescription() const override; //optional
+
+ void setContentCategory(FileContentCategory category);
+ FileContentCategory getContentCategory() const;
private:
Zstring getRelativePathL() const override { return appendPath(parent().getRelativePath<SelectSide::left >(), getItemName<SelectSide::left >()); }
Zstring getRelativePathR() const override { return appendPath(parent().getRelativePath<SelectSide::right>(), getItemName<SelectSide::right>()); }
- void flip() override;
- void removeObjectL() override { attrL_ = LinkAttributes(); }
- void removeObjectR() override { attrR_ = LinkAttributes(); }
+ void removeObjectL() override { attrL_ = LinkAttributes(); contentCategory_ = FileContentCategory::unknown; }
+ void removeObjectR() override { attrR_ = LinkAttributes(); contentCategory_ = FileContentCategory::unknown; }
LinkAttributes attrL_;
LinkAttributes attrR_;
+
+ FileContentCategory contentCategory_ = FileContentCategory::unknown;
+ Zstringc categoryDescr_; //optional: custom category description (e.g. FileContentCategory::conflict or invalidTime)
};
//------------------------------------------------------------------
-//generic type descriptions (usecase CSV legend, sync config)
+//generic descriptions (usecase CSV legend, sync config)
std::wstring getCategoryDescription(CompareFileResult cmpRes);
-std::wstring getSyncOpDescription (SyncOperation op);
+std::wstring getSyncOpDescription(SyncOperation op);
-//item-specific type descriptions
+//item-specific descriptions
std::wstring getCategoryDescription(const FileSystemObject& fsObj);
-std::wstring getSyncOpDescription (const FileSystemObject& fsObj);
+std::wstring getSyncOpDescription(const FileSystemObject& fsObj);
//------------------------------------------------------------------
@@ -688,15 +715,17 @@ public:
RecursiveObjectVisitor(Function1 onFolder,
Function2 onFile,
Function3 onSymlink) : //unifying assignment
- onFolder_(std::move(onFolder)), onFile_(std::move(onFile)), onSymlink_(std::move(onSymlink)) {}
+ onFolder_ (std::move(onFolder)),
+ onFile_ (std::move(onFile)),
+ onSymlink_(std::move(onSymlink)) {}
- void execute(ContainerObject& hierObj)
+ void execute(ContainerObject& conObj)
{
- for (FilePair& file : hierObj.refSubFiles())
+ for (FilePair& file : conObj.refSubFiles())
onFile_(file);
- for (SymlinkPair& symlink : hierObj.refSubLinks())
+ for (SymlinkPair& symlink : conObj.refSubLinks())
onSymlink_(symlink);
- for (FolderPair& subFolder : hierObj.refSubFolders())
+ for (FolderPair& subFolder : conObj.refSubFolders())
{
onFolder_(subFolder);
execute(subFolder);
@@ -714,12 +743,12 @@ private:
}
template <class Function1, class Function2, class Function3> inline
-void visitFSObjectRecursively(ContainerObject& hierObj, //consider contained items only
+void visitFSObjectRecursively(ContainerObject& conObj, //consider contained items only
Function1 onFolder,
Function2 onFile,
Function3 onSymlink)
{
- impl::RecursiveObjectVisitor(onFolder, onFile, onSymlink).execute(hierObj);
+ impl::RecursiveObjectVisitor(onFolder, onFile, onSymlink).execute(conObj);
}
template <class Function1, class Function2, class Function3> inline
@@ -757,34 +786,12 @@ void visitFSObjectRecursively(FileSystemObject& fsObj, //consider item and conta
//--------------------- implementation ------------------------------------------
//inline virtual... admittedly its use may be limited
-inline void FilePair ::accept(FSObjectVisitor& visitor) const { visitor.visit(*this); }
inline void FolderPair ::accept(FSObjectVisitor& visitor) const { visitor.visit(*this); }
+inline void FilePair ::accept(FSObjectVisitor& visitor) const { visitor.visit(*this); }
inline void SymlinkPair::accept(FSObjectVisitor& visitor) const { visitor.visit(*this); }
inline
-CompareFileResult FilePair::getFileCategory() const
-{
- return getCategory();
-}
-
-
-inline
-CompareDirResult FolderPair::getDirCategory() const
-{
- return static_cast<CompareDirResult>(getCategory());
-}
-
-
-inline
-Zstringc FileSystemObject::getCatExtraDescription() const
-{
- assert(getCategory() == FILE_CONFLICT || getCategory() == FILE_DIFFERENT_METADATA);
- return cmpResultDescr_;
-}
-
-
-inline
void FileSystemObject::setSyncDir(SyncDirection newDir)
{
syncDir_ = newDir;
@@ -838,7 +845,7 @@ bool FileSystemObject::isPairEmpty() const
template <SelectSide side> inline
Zstring FileSystemObject::getItemName() const
{
- //assert(!itemNameL_.empty() || !itemNameR_.empty()); //-> file pair might be temporarily empty (until permanently removed after sync)
+ //assert(!itemNameL_.empty() || !itemNameR_.empty()); //-> file pair might be temporarily empty (until removed after sync)
const Zstring& itemName = selectParam<side>(itemNameL_, itemNameR_); //empty if not existing
if (!itemName.empty()) //avoid ternary-WTF! (implicit copy-constructor call!!!!!!)
@@ -848,130 +855,93 @@ Zstring FileSystemObject::getItemName() const
inline
-Zstring FileSystemObject::getItemNameAny() const
+bool FileSystemObject::hasEquivalentItemNames() const
{
- return getItemName<SelectSide::left>(); //side doesn't matter
-}
-
-
-template <> inline
-void FileSystemObject::removeObject<SelectSide::left>()
-{
- const Zstring itemNameOld = getItemName<SelectSide::left>();
-
- cmpResult_ = isEmpty<SelectSide::right>() ? FILE_EQUAL : FILE_RIGHT_SIDE_ONLY;
- itemNameL_.clear();
- removeObjectL();
+ if (itemNameL_.c_str() == itemNameR_.c_str() || //most likely case
+ itemNameL_.empty() || itemNameR_.empty()) //
+ return true;
- setSyncDir(SyncDirection::none); //calls notifySyncCfgChanged()
- propagateChangedItemName<SelectSide::left>(itemNameOld);
+ assert(itemNameL_ != itemNameR_); //class invariant
+ return getUnicodeNormalForm(itemNameL_) == getUnicodeNormalForm(itemNameR_);
}
-template <> inline
-void FileSystemObject::removeObject<SelectSide::right>()
+template <SelectSide side> inline
+void FileSystemObject::removeObject()
{
- const Zstring itemNameOld = getItemName<SelectSide::right>();
+ if (isEmpty<getOtherSide<side>>())
+ itemNameL_ = itemNameR_ = Zstring(); //ensure (c_str) class invariant!
+ else
+ selectParam<side>(itemNameL_, itemNameR_).clear();
+
+ if constexpr (side == SelectSide::left)
+ removeObjectL();
+ else
+ removeObjectR();
- cmpResult_ = isEmpty<SelectSide::left>() ? FILE_EQUAL : FILE_LEFT_SIDE_ONLY;
- itemNameR_.clear();
- removeObjectR();
+ if (isPairEmpty())
+ setSyncDir(SyncDirection::none); //calls notifySyncCfgChanged()
+ else //keep current syncDir_
+ notifySyncCfgChanged(); //needed!?
- setSyncDir(SyncDirection::none); //calls notifySyncCfgChanged()
- propagateChangedItemName<SelectSide::right>(itemNameOld);
+ propagateChangedItemName<side>();
}
-inline
-void FileSystemObject::setSynced(const Zstring& itemName)
+template <SelectSide side> inline
+void FileSystemObject::setItemName(const Zstring& itemName)
{
- const Zstring itemNameOldL = getItemName<SelectSide::left>();
- const Zstring itemNameOldR = getItemName<SelectSide::right>();
-
+ assert(!itemName.empty());
assert(!isPairEmpty());
- itemNameR_ = itemNameL_ = itemName;
- cmpResult_ = FILE_EQUAL;
- setSyncDir(SyncDirection::none);
- propagateChangedItemName<SelectSide::left >(itemNameOldL);
- propagateChangedItemName<SelectSide::right>(itemNameOldR);
-}
+ selectParam<side>(itemNameL_, itemNameR_) = itemName;
+ if (itemNameL_.c_str() != itemNameR_.c_str() &&
+ itemNameL_ == itemNameR_)
+ itemNameL_ = itemNameR_; //preserve class invariant
+ assert(itemNameL_.c_str() == itemNameR_.c_str() || itemNameL_ != itemNameR_);
-template <CompareFileResult res> inline
-void FileSystemObject::setCategory()
-{
- cmpResult_ = res;
+ propagateChangedItemName<side>();
}
-template <> void FileSystemObject::setCategory<FILE_CONFLICT> () = delete; //
-template <> void FileSystemObject::setCategory<FILE_DIFFERENT_METADATA>() = delete; //deny use
-template <> void FileSystemObject::setCategory<FILE_LEFT_SIDE_ONLY> () = delete; //
-template <> void FileSystemObject::setCategory<FILE_RIGHT_SIDE_ONLY> () = delete; //
-inline
-void FileSystemObject::setCategoryConflict(const Zstringc& description)
-{
- assert(!description.empty());
- cmpResult_ = FILE_CONFLICT;
- cmpResultDescr_ = description;
-}
-inline
-void FileSystemObject::setCategoryDiffMetadata(const Zstringc& description)
-{
- assert(!description.empty());
- cmpResult_ = FILE_DIFFERENT_METADATA;
- cmpResultDescr_ = description;
-}
-
-inline
-void FileSystemObject::flip()
+template <SelectSide side> inline
+void FileSystemObject::propagateChangedItemName()
{
- std::swap(itemNameL_, itemNameR_);
+ if (itemNameL_.empty() && itemNameR_.empty()) return; //both sides might just have been deleted by removeObject<>
- switch (cmpResult_)
+ if (auto conObj = dynamic_cast<ContainerObject*>(this))
{
- case FILE_LEFT_SIDE_ONLY:
- cmpResult_ = FILE_RIGHT_SIDE_ONLY;
- break;
- case FILE_RIGHT_SIDE_ONLY:
- cmpResult_ = FILE_LEFT_SIDE_ONLY;
- break;
- case FILE_LEFT_NEWER:
- cmpResult_ = FILE_RIGHT_NEWER;
- break;
- case FILE_RIGHT_NEWER:
- cmpResult_ = FILE_LEFT_NEWER;
- break;
- case FILE_DIFFERENT_CONTENT:
- case FILE_EQUAL:
- case FILE_DIFFERENT_METADATA:
- case FILE_CONFLICT:
- break;
+ const Zstring& itemNameOld = zen::getItemName(conObj->getRelativePath<side>());
+ if (itemNameOld != getItemName<side>()) //perf: premature optimization?
+ conObj->updateRelPathsRecursion<side>(*this);
}
-
- notifySyncCfgChanged();
}
template <SelectSide side> inline
-void FileSystemObject::propagateChangedItemName(const Zstring& itemNameOld)
+void ContainerObject::updateRelPathsRecursion(const FileSystemObject& fsAlias)
{
- if (itemNameL_.empty() && itemNameR_.empty()) return; //both sides might just have been deleted by removeObject<>
-
- if (itemNameOld != getItemName<side>()) //perf: premature optimization?
- if (auto hierObj = dynamic_cast<ContainerObject*>(this))
- hierObj->updateRelPathsRecursion<side>(*this);
-}
+ //perf: only call if actual item name changed:
+ assert(selectParam<side>(relPathL_, relPathR_) != appendPath(fsAlias.parent().getRelativePath<side>(), fsAlias.getItemName<side>()));
+ constexpr SelectSide otherSide = getOtherSide<side>;
-template <SelectSide side> inline
-void ContainerObject::updateRelPathsRecursion(const FileSystemObject& fsAlias)
-{
- assert(selectParam<side>(relPathL_, relPathR_) != //perf: only call if actual item name changed!
- appendPath(fsAlias.parent().getRelativePath<side>(), fsAlias.getItemName<side>()));
+ if (fsAlias.isEmpty<otherSide>()) //=> 1. other side's relPath also needs updating! 2. both sides have same name
+ selectParam<otherSide>(relPathL_, relPathR_) = appendPath(selectParam<otherSide>(fsAlias.parent().relPathL_,
+ fsAlias.parent().relPathR_), fsAlias.getItemName<otherSide>());
+ else //assume relPath on other side is up to date!
+ assert(selectParam<otherSide>(relPathL_, relPathR_) == appendPath(fsAlias.parent().getRelativePath<otherSide>(), fsAlias.getItemName<otherSide>()));
- selectParam<side>(relPathL_, relPathR_) = appendPath(fsAlias.parent().getRelativePath<side>(), fsAlias.getItemName<side>());
+ if (fsAlias.parent().relPathL_.c_str() == //
+ fsAlias.parent().relPathR_.c_str() && //see ContainerObject constructor and setItemName()
+ fsAlias.getItemName<SelectSide::left >().c_str() == //
+ fsAlias.getItemName<SelectSide::right>().c_str()) //
+ selectParam<side>(relPathL_, relPathR_) = selectParam<otherSide>(relPathL_, relPathR_);
+ else
+ selectParam<side>(relPathL_, relPathR_) = appendPath(selectParam<side>(fsAlias.parent().relPathL_,
+ fsAlias.parent().relPathR_), fsAlias.getItemName<side>());
+ assert(relPathL_.c_str() == relPathR_.c_str() || relPathL_ != relPathR_);
for (FolderPair& folder : subFolders_)
folder.updateRelPathsRecursion<side>(folder);
@@ -981,13 +951,12 @@ void ContainerObject::updateRelPathsRecursion(const FileSystemObject& fsAlias)
inline
ContainerObject::ContainerObject(const FileSystemObject& fsAlias) :
relPathL_(appendPath(fsAlias.parent().relPathL_, fsAlias.getItemName<SelectSide::left>())),
- relPathR_(
- fsAlias.parent().relPathL_.c_str() == //
- fsAlias.parent().relPathR_.c_str() && //take advantage of FileSystemObject's Zstring reuse:
- fsAlias.getItemName<SelectSide::left >().c_str() == //=> perf: 12% faster merge phase; -4% peak memory
- fsAlias.getItemName<SelectSide::right>().c_str() ? //
- relPathL_ : //ternary-WTF! (implicit copy-constructor call!!) => no big deal for a Zstring
- appendPath(fsAlias.parent().relPathR_, fsAlias.getItemName<SelectSide::right>())),
+ relPathR_(fsAlias.parent().relPathL_.c_str() == //
+ fsAlias.parent().relPathR_.c_str() && //take advantage of FileSystemObject's Zstring reuse:
+ fsAlias.getItemName<SelectSide::left >().c_str() == //=> perf: 12% faster merge phase; -4% peak memory
+ fsAlias.getItemName<SelectSide::right>().c_str() ? //
+ relPathL_ : //ternary-WTF! (implicit copy-constructor call!!) => no big deal for a Zstring
+ appendPath(fsAlias.parent().relPathR_, fsAlias.getItemName<SelectSide::right>())),
base_(fsAlias.parent().base_)
{
assert(relPathL_.c_str() == relPathR_.c_str() || relPathL_ != relPathR_);
@@ -995,27 +964,12 @@ ContainerObject::ContainerObject(const FileSystemObject& fsAlias) :
inline
-void ContainerObject::flip()
-{
- for (FilePair& file : refSubFiles())
- file.flip();
- for (SymlinkPair& symlink : refSubLinks())
- symlink.flip();
- for (FolderPair& folder : refSubFolders())
- folder.flip();
-
- std::swap(relPathL_, relPathR_);
-}
-
-
-inline
FolderPair& ContainerObject::addFolder(const Zstring& itemNameL,
const FolderAttributes& left,
- CompareDirResult defaultCmpResult,
const Zstring& itemNameR,
const FolderAttributes& right)
{
- subFolders_.emplace_back(itemNameL, left, defaultCmpResult, itemNameR, right, *this);
+ subFolders_.emplace_back(itemNameL, left, itemNameR, right, *this);
return subFolders_.back();
}
@@ -1023,7 +977,7 @@ FolderPair& ContainerObject::addFolder(const Zstring& itemNameL,
template <> inline
FolderPair& ContainerObject::addFolder<SelectSide::left>(const Zstring& itemName, const FolderAttributes& attr)
{
- subFolders_.emplace_back(itemName, attr, DIR_LEFT_SIDE_ONLY, Zstring(), FolderAttributes(), *this);
+ subFolders_.emplace_back(itemName, attr, Zstring(), FolderAttributes(), *this);
return subFolders_.back();
}
@@ -1031,19 +985,18 @@ FolderPair& ContainerObject::addFolder<SelectSide::left>(const Zstring& itemName
template <> inline
FolderPair& ContainerObject::addFolder<SelectSide::right>(const Zstring& itemName, const FolderAttributes& attr)
{
- subFolders_.emplace_back(Zstring(), FolderAttributes(), DIR_RIGHT_SIDE_ONLY, itemName, attr, *this);
+ subFolders_.emplace_back(Zstring(), FolderAttributes(), itemName, attr, *this);
return subFolders_.back();
}
inline
FilePair& ContainerObject::addFile(const Zstring& itemNameL,
- const FileAttributes& left, //file exists on both sides
- CompareFileResult defaultCmpResult,
+ const FileAttributes& left,
const Zstring& itemNameR,
- const FileAttributes& right)
+ const FileAttributes& right) //file exists on both sides
{
- subFiles_.emplace_back(itemNameL, left, defaultCmpResult, itemNameR, right, *this);
+ subFiles_.emplace_back(itemNameL, left, itemNameR, right, *this);
return subFiles_.back();
}
@@ -1051,7 +1004,7 @@ FilePair& ContainerObject::addFile(const Zstring& itemNameL,
template <> inline
FilePair& ContainerObject::addFile<SelectSide::left>(const Zstring& itemName, const FileAttributes& attr)
{
- subFiles_.emplace_back(itemName, attr, FILE_LEFT_SIDE_ONLY, Zstring(), FileAttributes(), *this);
+ subFiles_.emplace_back(itemName, attr, Zstring(), FileAttributes(), *this);
return subFiles_.back();
}
@@ -1059,19 +1012,18 @@ FilePair& ContainerObject::addFile<SelectSide::left>(const Zstring& itemName, co
template <> inline
FilePair& ContainerObject::addFile<SelectSide::right>(const Zstring& itemName, const FileAttributes& attr)
{
- subFiles_.emplace_back(Zstring(), FileAttributes(), FILE_RIGHT_SIDE_ONLY, itemName, attr, *this);
+ subFiles_.emplace_back(Zstring(), FileAttributes(), itemName, attr, *this);
return subFiles_.back();
}
inline
SymlinkPair& ContainerObject::addLink(const Zstring& itemNameL,
- const LinkAttributes& left, //link exists on both sides
- CompareSymlinkResult defaultCmpResult,
+ const LinkAttributes& left,
const Zstring& itemNameR,
- const LinkAttributes& right)
+ const LinkAttributes& right) //link exists on both sides
{
- subLinks_.emplace_back(itemNameL, left, defaultCmpResult, itemNameR, right, *this);
+ subLinks_.emplace_back(itemNameL, left, itemNameR, right, *this);
return subLinks_.back();
}
@@ -1079,7 +1031,7 @@ SymlinkPair& ContainerObject::addLink(const Zstring& itemNameL,
template <> inline
SymlinkPair& ContainerObject::addLink<SelectSide::left>(const Zstring& itemName, const LinkAttributes& attr)
{
- subLinks_.emplace_back(itemName, attr, SYMLINK_LEFT_SIDE_ONLY, Zstring(), LinkAttributes(), *this);
+ subLinks_.emplace_back(itemName, attr, Zstring(), LinkAttributes(), *this);
return subLinks_.back();
}
@@ -1087,12 +1039,34 @@ SymlinkPair& ContainerObject::addLink<SelectSide::left>(const Zstring& itemName,
template <> inline
SymlinkPair& ContainerObject::addLink<SelectSide::right>(const Zstring& itemName, const LinkAttributes& attr)
{
- subLinks_.emplace_back(Zstring(), LinkAttributes(), SYMLINK_RIGHT_SIDE_ONLY, itemName, attr, *this);
+ subLinks_.emplace_back(Zstring(), LinkAttributes(), itemName, attr, *this);
return subLinks_.back();
}
inline
+void FileSystemObject::flip()
+{
+ std::swap(itemNameL_, itemNameR_);
+ notifySyncCfgChanged();
+}
+
+
+inline
+void ContainerObject::flip()
+{
+ for (FilePair& file : refSubFiles())
+ file.flip();
+ for (SymlinkPair& symlink : refSubLinks())
+ symlink.flip();
+ for (FolderPair& folder : refSubFolders())
+ folder.flip();
+
+ std::swap(relPathL_, relPathR_);
+}
+
+
+inline
void BaseFolderPair::flip()
{
ContainerObject::flip();
@@ -1102,7 +1076,7 @@ void BaseFolderPair::flip()
inline
-void FolderPair::flip()
+void FolderPair::flip() //this overrides both ContainerObject/FileSystemObject::flip!
{
ContainerObject ::flip(); //call base class versions
FileSystemObject::flip(); //
@@ -1111,6 +1085,48 @@ void FolderPair::flip()
inline
+void FilePair::flip()
+{
+ FileSystemObject::flip(); //call base class version
+ std::swap(attrL_, attrR_);
+
+ switch (contentCategory_)
+ {
+ //*INDENT-OFF*
+ case FileContentCategory::unknown:
+ case FileContentCategory::equal:
+ case FileContentCategory::invalidTime:
+ case FileContentCategory::different:
+ case FileContentCategory::conflict: break;
+ case FileContentCategory::leftNewer: contentCategory_ = FileContentCategory::rightNewer; break;
+ case FileContentCategory::rightNewer: contentCategory_ = FileContentCategory::leftNewer; break;
+ //*INDENT-ON*
+ }
+}
+
+
+inline
+void SymlinkPair::flip()
+{
+ FileSystemObject::flip(); //call base class versions
+ std::swap(attrL_, attrR_);
+
+ switch (contentCategory_)
+ {
+ //*INDENT-OFF*
+ case FileContentCategory::unknown:
+ case FileContentCategory::equal:
+ case FileContentCategory::invalidTime:
+ case FileContentCategory::different:
+ case FileContentCategory::conflict: break;
+ case FileContentCategory::leftNewer: contentCategory_ = FileContentCategory::rightNewer; break;
+ case FileContentCategory::rightNewer: contentCategory_ = FileContentCategory::leftNewer; break;
+ //*INDENT-ON*
+ }
+}
+
+
+inline
void FolderPair::removeObjectL()
{
for (FilePair& file : refSubFiles())
@@ -1153,16 +1169,206 @@ void BaseFolderPair::setFolderStatus(BaseFolderStatus value)
inline
-void FilePair::flip()
+void FolderPair::setCategoryConflict(const Zstringc& description)
{
- FileSystemObject::flip(); //call base class version
- std::swap(attrL_, attrR_);
+ assert(!description.empty());
+ categoryConflict_ = description;
+}
+
+
+inline
+void FilePair::setCategoryConflict(const Zstringc& description)
+{
+ assert(!description.empty());
+ categoryDescr_ = description;
+ contentCategory_ = FileContentCategory::conflict;
+}
+
+
+inline
+void SymlinkPair::setCategoryConflict(const Zstringc& description)
+{
+ assert(!description.empty());
+ categoryDescr_ = description;
+ contentCategory_ = FileContentCategory::conflict;
+}
+
+
+inline
+void FilePair::setCategoryInvalidTime(const Zstringc& description)
+{
+ assert(!description.empty());
+ categoryDescr_ = description;
+ contentCategory_ = FileContentCategory::invalidTime;
+}
+
+
+inline
+void SymlinkPair::setCategoryInvalidTime(const Zstringc& description)
+{
+ assert(!description.empty());
+ categoryDescr_ = description;
+ contentCategory_ = FileContentCategory::invalidTime;
+}
+
+
+inline Zstringc FolderPair ::getCategoryCustomDescription() const { return categoryConflict_; }
+inline Zstringc FilePair ::getCategoryCustomDescription() const { return categoryDescr_; }
+inline Zstringc SymlinkPair::getCategoryCustomDescription() const { return categoryDescr_; }
+
+
+inline
+void FilePair::setContentCategory(FileContentCategory category)
+{
+ assert(!isEmpty<SelectSide::left>() &&!isEmpty<SelectSide::right>());
+ assert(category != FileContentCategory::unknown);
+ contentCategory_ = category;
+}
+
+
+inline
+void SymlinkPair::setContentCategory(FileContentCategory category)
+{
+ assert(!isEmpty<SelectSide::left>() &&!isEmpty<SelectSide::right>());
+ assert(category != FileContentCategory::unknown);
+ contentCategory_ = category;
+}
+
+
+inline
+FileContentCategory FilePair::getContentCategory() const
+{
+ assert(!isEmpty<SelectSide::left>() &&!isEmpty<SelectSide::right>());
+ return contentCategory_;
+}
+
+
+inline
+FileContentCategory SymlinkPair::getContentCategory() const
+{
+ assert(!isEmpty<SelectSide::left>() &&!isEmpty<SelectSide::right>());
+ return contentCategory_;
+}
+
+
+inline
+CompareFileResult FolderPair::getCategory() const
+{
+ if (!categoryConflict_.empty())
+ return FILE_CONFLICT;
+
+ if (isEmpty<SelectSide::left>())
+ {
+ if (isEmpty<SelectSide::right>())
+ return FILE_EQUAL;
+ else
+ return FILE_RIGHT_ONLY;
+ }
+ else
+ {
+ if (isEmpty<SelectSide::right>())
+ return FILE_LEFT_ONLY;
+ else
+ return hasEquivalentItemNames() ? FILE_EQUAL : FILE_RENAMED;
+ }
+}
+
+
+inline
+CompareFileResult FilePair::getCategory() const
+{
+ assert(contentCategory_ == FileContentCategory::conflict ||
+ (isEmpty<SelectSide::left>() || isEmpty<SelectSide::right>()) == (contentCategory_ == FileContentCategory::unknown));
+ assert(contentCategory_ != FileContentCategory::conflict || !categoryDescr_.empty());
+
+ if (contentCategory_ == FileContentCategory::conflict)
+ {
+ assert(!categoryDescr_.empty());
+ return FILE_CONFLICT;
+ }
+
+ if (isEmpty<SelectSide::left>())
+ {
+ if (isEmpty<SelectSide::right>())
+ return FILE_EQUAL;
+ else
+ return FILE_RIGHT_ONLY;
+ }
+ else
+ {
+ if (isEmpty<SelectSide::right>())
+ return FILE_LEFT_ONLY;
+ else
+ //Caveat:
+ //1. FILE_EQUAL may only be set if names match in case: InSyncFolder's mapping tables use file name as a key! see db_file.cpp
+ //2. harmonize with "bool stillInSync()" in algorithm.cpp, FilePair::setSyncedTo() in file_hierarchy.h
+ //3. FILE_EQUAL is expected to mean identical file sizes! See InSyncFile
+ switch (contentCategory_)
+ {
+ //*INDENT-OFF*
+ case FileContentCategory::unknown:
+ case FileContentCategory::conflict: assert(false); return FILE_CONFLICT;
+ case FileContentCategory::equal: return hasEquivalentItemNames() ? FILE_EQUAL : FILE_RENAMED;
+ case FileContentCategory::leftNewer: return FILE_LEFT_NEWER;
+ case FileContentCategory::rightNewer: return FILE_RIGHT_NEWER;
+ case FileContentCategory::invalidTime: return FILE_TIME_INVALID;
+ case FileContentCategory::different: return FILE_DIFFERENT_CONTENT;
+ //*INDENT-ON*
+ }
+ }
+ throw std::logic_error(std::string(__FILE__) + '[' + zen::numberTo<std::string>(__LINE__) + "] Contract violation!");
+}
+
+
+inline
+CompareFileResult SymlinkPair::getCategory() const
+{
+ assert(contentCategory_ == FileContentCategory::conflict ||
+ (isEmpty<SelectSide::left>() || isEmpty<SelectSide::right>()) == (contentCategory_ == FileContentCategory::unknown));
+ assert(contentCategory_ != FileContentCategory::conflict || !categoryDescr_.empty());
+
+ if (contentCategory_ == FileContentCategory::conflict)
+ {
+ assert(!categoryDescr_.empty());
+ return FILE_CONFLICT;
+ }
+
+ if (isEmpty<SelectSide::left>())
+ {
+ if (isEmpty<SelectSide::right>())
+ return FILE_EQUAL;
+ else
+ return FILE_RIGHT_ONLY;
+ }
+ else
+ {
+ if (isEmpty<SelectSide::right>())
+ return FILE_LEFT_ONLY;
+ else
+ //Caveat:
+ //1. SYMLINK_EQUAL may only be set if names match in case: InSyncFolder's mapping tables use link name as a key! see db_file.cpp
+ //2. harmonize with "bool stillInSync()" in algorithm.cpp, FilePair::setSyncedTo() in file_hierarchy.h
+ switch (contentCategory_)
+ {
+ //*INDENT-OFF*
+ case FileContentCategory::unknown:
+ case FileContentCategory::conflict: assert(false); return FILE_CONFLICT;
+ case FileContentCategory::equal: return hasEquivalentItemNames() ? FILE_EQUAL : FILE_RENAMED;
+ case FileContentCategory::leftNewer: return FILE_LEFT_NEWER;
+ case FileContentCategory::rightNewer: return FILE_RIGHT_NEWER;
+ case FileContentCategory::invalidTime: return FILE_TIME_INVALID;
+ case FileContentCategory::different: return FILE_DIFFERENT_CONTENT;
+ //*INDENT-ON*
+ }
+ }
+ throw std::logic_error(std::string(__FILE__) + '[' + zen::numberTo<std::string>(__LINE__) + "] Contract violation!");
}
template <SelectSide side> inline
FileAttributes FilePair::getAttributes() const
{
+ assert(!isEmpty<side>());
return selectParam<side>(attrL_, attrR_);
}
@@ -1170,6 +1376,7 @@ FileAttributes FilePair::getAttributes() const
template <SelectSide side> inline
time_t FilePair::getLastWriteTime() const
{
+ assert(!isEmpty<side>());
return selectParam<side>(attrL_, attrR_).modTime;
}
@@ -1177,20 +1384,23 @@ time_t FilePair::getLastWriteTime() const
template <SelectSide side> inline
uint64_t FilePair::getFileSize() const
{
+ assert(!isEmpty<side>());
return selectParam<side>(attrL_, attrR_).fileSize;
}
template <SelectSide side> inline
-bool FilePair::isFollowedSymlink() const
+bool FolderPair::isFollowedSymlink() const
{
+ assert(!isEmpty<side>());
return selectParam<side>(attrL_, attrR_).isFollowedSymlink;
}
template <SelectSide side> inline
-bool FolderPair::isFollowedSymlink() const
+bool FilePair::isFollowedSymlink() const
{
+ assert(!isEmpty<side>());
return selectParam<side>(attrL_, attrR_).isFollowedSymlink;
}
@@ -1198,6 +1408,7 @@ bool FolderPair::isFollowedSymlink() const
template <SelectSide side> inline
AFS::FingerPrint FilePair::getFilePrint() const
{
+ assert(!isEmpty<side>());
return selectParam<side>(attrL_, attrR_).filePrint;
}
@@ -1210,8 +1421,21 @@ void FilePair::clearFilePrint()
template <SelectSide sideTrg> inline
-void FilePair::setSyncedTo(const Zstring& itemName,
- uint64_t fileSize,
+void FolderPair::setSyncedTo(bool isSymlinkTrg,
+ bool isSymlinkSrc)
+{
+ selectParam< sideTrg >(attrL_, attrR_) = {.isFollowedSymlink = isSymlinkTrg};
+ selectParam<getOtherSide<sideTrg>>(attrL_, attrR_) = {.isFollowedSymlink = isSymlinkSrc};
+
+ setItemName<sideTrg>(getItemName<getOtherSide<sideTrg>>());
+
+ categoryConflict_.clear();
+ setSyncDir(SyncDirection::none);
+}
+
+
+template <SelectSide sideTrg> inline
+void FilePair::setSyncedTo(uint64_t fileSize,
int64_t lastWriteTimeTrg,
int64_t lastWriteTimeSrc,
AFS::FingerPrint filePrintTrg,
@@ -1219,58 +1443,100 @@ void FilePair::setSyncedTo(const Zstring& itemName,
bool isSymlinkTrg,
bool isSymlinkSrc)
{
- //FILE_EQUAL is only allowed for same short name and file size: enforced by this method!
- selectParam< sideTrg >(attrL_, attrR_) = FileAttributes(lastWriteTimeTrg, fileSize, filePrintTrg, isSymlinkTrg);
- selectParam<getOtherSide<sideTrg>>(attrL_, attrR_) = FileAttributes(lastWriteTimeSrc, fileSize, filePrintSrc, isSymlinkSrc);
+ selectParam< sideTrg >(attrL_, attrR_) = {lastWriteTimeTrg, fileSize, filePrintTrg, isSymlinkTrg};
+ selectParam<getOtherSide<sideTrg>>(attrL_, attrR_) = {lastWriteTimeSrc, fileSize, filePrintSrc, isSymlinkSrc};
+ warn_static("same thing for delete!?")
+ //cut ties between "move" pairs
+ if (moveFileRef_)
+ if (auto refFile = dynamic_cast<FilePair*>(FileSystemObject::retrieve(moveFileRef_)))
+ {
+ if (refFile->moveFileRef_ == getId()) //both ends should agree...
+ refFile->moveFileRef_ = nullptr;
+ else assert(false); //...and why shouldn't they?
+ }
moveFileRef_ = nullptr;
- FileSystemObject::setSynced(itemName); //set FileSystemObject specific part
+
+ setItemName<sideTrg>(getItemName<getOtherSide<sideTrg>>());
+
+ contentCategory_ = FileContentCategory::equal;
+ categoryDescr_.clear();
+ setSyncDir(SyncDirection::none);
}
template <SelectSide sideTrg> inline
-void SymlinkPair::setSyncedTo(const Zstring& itemName,
- int64_t lastWriteTimeTrg,
+void SymlinkPair::setSyncedTo(int64_t lastWriteTimeTrg,
int64_t lastWriteTimeSrc)
{
- selectParam< sideTrg >(attrL_, attrR_) = LinkAttributes(lastWriteTimeTrg);
- selectParam<getOtherSide<sideTrg>>(attrL_, attrR_) = LinkAttributes(lastWriteTimeSrc);
+ selectParam< sideTrg >(attrL_, attrR_) = {.modTime = lastWriteTimeTrg};
+ selectParam<getOtherSide<sideTrg>>(attrL_, attrR_) = {.modTime = lastWriteTimeSrc};
+
+ setItemName<sideTrg>(getItemName<getOtherSide<sideTrg>>());
- FileSystemObject::setSynced(itemName); //set FileSystemObject specific part
+ contentCategory_ = FileContentCategory::equal;
+ categoryDescr_.clear();
+ setSyncDir(SyncDirection::none);
}
-template <SelectSide sideTrg> inline
-void FolderPair::setSyncedTo(const Zstring& itemName,
- bool isSymlinkTrg,
- bool isSymlinkSrc)
+inline
+bool FolderPair::passDirFilter(const PathFilter& filter, bool* childItemMightMatch) const
{
- selectParam< sideTrg >(attrL_, attrR_) = FolderAttributes(isSymlinkTrg);
- selectParam<getOtherSide<sideTrg>>(attrL_, attrR_) = FolderAttributes(isSymlinkSrc);
+ const Zstring& relPathL = getRelativePath<SelectSide::left >();
+ const Zstring& relPathR = getRelativePath<SelectSide::right>();
+ assert(relPathL.c_str() == relPathR.c_str() || relPathL!= relPathR);
- FileSystemObject::setSynced(itemName); //set FileSystemObject specific part
+ if (filter.passDirFilter(relPathL, childItemMightMatch))
+ return relPathL.c_str() == relPathR.c_str() /*perf!*/ || equalNoCase(relPathL, relPathR) ||
+ filter.passDirFilter(relPathR, childItemMightMatch);
+ else
+ {
+ if (childItemMightMatch && *childItemMightMatch &&
+ relPathL.c_str() != relPathR.c_str() /*perf!*/ && !equalNoCase(relPathL, relPathR))
+ filter.passDirFilter(relPathR, childItemMightMatch);
+ return false;
+ }
}
-template <SelectSide side> inline
-time_t SymlinkPair::getLastWriteTime() const
+inline
+bool FileSystemObject::passFileFilter(const PathFilter& filter) const
{
- return selectParam<side>(attrL_, attrR_).modTime;
-}
+ assert(!dynamic_cast<const FolderPair*>(this));
+ assert(parent().getRelativePath<SelectSide::left >().c_str() ==
+ parent().getRelativePath<SelectSide::right>().c_str() ||
+ parent().getRelativePath<SelectSide::left >()!=
+ parent().getRelativePath<SelectSide::right>());
+ assert(getItemName<SelectSide::left >().c_str() ==
+ getItemName<SelectSide::right>().c_str() ||
+ getItemName<SelectSide::left >() !=
+ getItemName<SelectSide::right>());
+ const Zstring relPathL = getRelativePath<SelectSide::left>();
-inline
-CompareSymlinkResult SymlinkPair::getLinkCategory() const
-{
- return static_cast<CompareSymlinkResult>(getCategory());
+ if (!filter.passFileFilter(relPathL))
+ return false;
+
+ if (parent().getRelativePath<SelectSide::left >().c_str() == //
+ parent().getRelativePath<SelectSide::right>().c_str() && //perf! see ContainerObject constructor
+ getItemName<SelectSide::left >().c_str() == //
+ getItemName<SelectSide::right>().c_str()) //
+ return true;
+
+ const Zstring relPathR = getRelativePath<SelectSide::right>();
+
+ if (equalNoCase(relPathL, relPathR))
+ return true;
+
+ return filter.passFileFilter(relPathR);
}
-inline
-void SymlinkPair::flip()
+template <SelectSide side> inline
+time_t SymlinkPair::getLastWriteTime() const
{
- FileSystemObject::flip(); //call base class versions
- std::swap(attrL_, attrR_);
+ return selectParam<side>(attrL_, attrR_).modTime;
}
}
diff --git a/FreeFileSync/Source/base/multi_rename.cpp b/FreeFileSync/Source/base/multi_rename.cpp
index bc1e48ad..19467d5f 100644
--- a/FreeFileSync/Source/base/multi_rename.cpp
+++ b/FreeFileSync/Source/base/multi_rename.cpp
@@ -115,6 +115,10 @@ size_t getPlaceholderIndex(wchar_t c)
}
}
+
+bool fff::isRenamePlaceholderChar(wchar_t c) { return getPlaceholderIndex(c) < std::size(placeholders); }
+
+
struct fff::RenameBuf
{
explicit RenameBuf(const std::vector<std::wstring>& s) : strings(s) {}
@@ -127,7 +131,7 @@ struct fff::RenameBuf
//e.g. "Season ❶, Episode ❷ - ❸.avi"
std::pair<std::wstring /*phrase*/, SharedRef<const RenameBuf>> fff::getPlaceholderPhrase(const std::vector<std::wstring>& strings)
{
- auto renameBuf = makeSharedRef<const RenameBuf>(strings);
+ auto renameBuf = makeSharedRef<const RenameBuf>(strings);
std::wstring phrase;
size_t placeIdx = 0;
diff --git a/FreeFileSync/Source/base/multi_rename.h b/FreeFileSync/Source/base/multi_rename.h
index 40139049..ce44ddbb 100644
--- a/FreeFileSync/Source/base/multi_rename.h
+++ b/FreeFileSync/Source/base/multi_rename.h
@@ -7,9 +7,7 @@
#ifndef MULTI_RENAME_H_489572039485723453425
#define MULTI_RENAME_H_489572039485723453425
-//#include <span>
-//#include <string>
-//#include <vector>
+#include <string>
#include <zen/stl_tools.h>
namespace fff
@@ -18,6 +16,8 @@ struct RenameBuf;
std::pair<std::wstring /*phrase*/, zen::SharedRef<const RenameBuf>> getPlaceholderPhrase(const std::vector<std::wstring>& strings);
const std::vector<std::wstring> resolvePlaceholderPhrase(const std::wstring_view phrase, const RenameBuf& buf);
+
+bool isRenamePlaceholderChar(wchar_t c);
}
#endif //MULTI_RENAME_H_489572039485723453425
diff --git a/FreeFileSync/Source/base/parallel_scan.cpp b/FreeFileSync/Source/base/parallel_scan.cpp
index 626a9371..8ccb7e64 100644
--- a/FreeFileSync/Source/base/parallel_scan.cpp
+++ b/FreeFileSync/Source/base/parallel_scan.cpp
@@ -291,7 +291,13 @@ void DirCallback::onFile(const AFS::FileInfo& fi) //throw ThreadStopRequest
return;
//note: sync.ffs_db database and lock files are excluded via path filter!
- output_.addFile(fi.itemName, FileAttributes(fi.modTime, fi.fileSize, fi.filePrint, fi.isFollowedSymlink));
+ output_.addFile(fi.itemName,
+ {
+ .modTime = fi.modTime,
+ .fileSize = fi.fileSize,
+ .filePrint = fi.filePrint,
+ .isFollowedSymlink = fi.isFollowedSymlink,
+ });
cfg_.acb.incItemsScanned(); //add 1 element to the progress indicator
}
@@ -315,7 +321,7 @@ std::shared_ptr<AFS::TraverserCallback> DirCallback::onFolder(const AFS::FolderI
return nullptr; //do NOT traverse subdirs
//else: ensure directory filtering is applied later to exclude actually filtered directories!!!
- FolderContainer& subFolder = output_.addFolder(fi.itemName, FolderAttributes(fi.isFollowedSymlink));
+ FolderContainer& subFolder = output_.addFolder(fi.itemName, {.isFollowedSymlink = fi.isFollowedSymlink});
if (passFilter)
cfg_.acb.incItemsScanned(); //add 1 element to the progress indicator
@@ -354,7 +360,7 @@ DirCallback::HandleLink DirCallback::onSymlink(const AFS::SymlinkInfo& si) //thr
case SymLinkHandling::asLink:
if (cfg_.filter.ref().passFileFilter(relPath)) //always use file filter: Link type may not be "stable" on Linux!
{
- output_.addLink(si.itemName, LinkAttributes(si.modTime));
+ output_.addLink(si.itemName, {.modTime = si.modTime});
cfg_.acb.incItemsScanned(); //add 1 element to the progress indicator
}
return HandleLink::skip;
diff --git a/FreeFileSync/Source/base/soft_filter.h b/FreeFileSync/Source/base/soft_filter.h
index c3cd8d0f..44356f38 100644
--- a/FreeFileSync/Source/base/soft_filter.h
+++ b/FreeFileSync/Source/base/soft_filter.h
@@ -24,9 +24,9 @@ Semantics of SoftFilter:
class SoftFilter
{
public:
- SoftFilter(size_t timeSpan, UnitTime unitTimeSpan,
- size_t sizeMin, UnitSize unitSizeMin,
- size_t sizeMax, UnitSize unitSizeMax);
+ SoftFilter(size_t timeSpan, UnitTime unitTimeSpan,
+ uint64_t sizeMin, UnitSize unitSizeMin,
+ uint64_t sizeMax, UnitSize unitSizeMax);
bool matchTime(time_t writeTime) const { return timeFrom_ <= writeTime; }
bool matchSize(uint64_t fileSize) const { return sizeMin_ <= fileSize && fileSize <= sizeMax_; }
@@ -63,9 +63,9 @@ private:
// ----------------------- implementation -----------------------
inline
-SoftFilter::SoftFilter(size_t timeSpan, UnitTime unitTimeSpan,
- size_t sizeMin, UnitSize unitSizeMin,
- size_t sizeMax, UnitSize unitSizeMax) :
+SoftFilter::SoftFilter(size_t timeSpan, UnitTime unitTimeSpan,
+ uint64_t sizeMin, UnitSize unitSizeMin,
+ uint64_t sizeMax, UnitSize unitSizeMax) :
matchesFolder_(unitTimeSpan == UnitTime::none &&
unitSizeMin == UnitSize::none &&
unitSizeMax == UnitSize::none) //exclude folders if size or date filter is active: avoids creating empty folders if not needed!
diff --git a/FreeFileSync/Source/base/status_handler_impl.h b/FreeFileSync/Source/base/status_handler_impl.h
index e0bf44ad..fa794d6a 100644
--- a/FreeFileSync/Source/base/status_handler_impl.h
+++ b/FreeFileSync/Source/base/status_handler_impl.h
@@ -532,16 +532,16 @@ void massParallelExecute(const std::vector<std::pair<AbstractPath, ParallelWorkI
std::atomic<size_t> activeDeviceCount(perDeviceWorkload.size()); //
//---------------------------------------------------------------------------------------------------------
- std::map<AfsDevice, ThreadGroup<std::function<void()>>> deviceThreadGroups; //worker threads live here...
+ std::vector<ThreadGroup<std::function<void()>>> deviceThreadGroups; //worker threads live here...
//---------------------------------------------------------------------------------------------------------
for (const auto& [afsDevice, wl] : perDeviceWorkload)
{
const size_t statusPrio = deviceThreadGroups.size();
- auto& threadGroup = deviceThreadGroups.emplace(afsDevice, ThreadGroup<std::function<void()>>(
- 1,
- threadGroupName + Zstr(' ') + utfTo<Zstring>(AFS::getDisplayPath(AbstractPath(afsDevice, AfsPath()))))).first->second;
+ const Zstring& deviceGroupName = threadGroupName + Zstr(' ') + utfTo<Zstring>(AFS::getDisplayPath(AbstractPath(afsDevice, AfsPath())));
+ deviceThreadGroups.emplace_back(1, deviceGroupName);
+ auto& threadGroup = deviceThreadGroups.back();
for (const std::pair<AbstractPath, ParallelWorkItem>* item : wl)
threadGroup.run([&acb, statusPrio, &itemPath = item->first, &task = item->second]
diff --git a/FreeFileSync/Source/base/structures.cpp b/FreeFileSync/Source/base/structures.cpp
index b2e70d5e..c1da1bf5 100644
--- a/FreeFileSync/Source/base/structures.cpp
+++ b/FreeFileSync/Source/base/structures.cpp
@@ -69,69 +69,128 @@ std::wstring fff::getVariantNameWithSymbol(SyncVariant var)
}
-DirectionSet fff::extractDirections(const SyncDirectionConfig& cfg)
+DirectionByDiff fff::getDiffDirDefault(const DirectionByChange& changeDirs)
{
- DirectionSet output;
- switch (cfg.var)
+ return
{
- case SyncVariant::twoWay:
- throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] there are no predefined directions for automatic mode!");
-
- case SyncVariant::mirror:
- output.exLeftSideOnly = SyncDirection::right;
- output.exRightSideOnly = SyncDirection::right;
- output.leftNewer = SyncDirection::right;
- output.rightNewer = SyncDirection::right;
- output.different = SyncDirection::right;
- output.conflict = SyncDirection::right;
- break;
-
- case SyncVariant::update:
- output.exLeftSideOnly = SyncDirection::right;
- output.exRightSideOnly = SyncDirection::none;
- output.leftNewer = SyncDirection::right;
- output.rightNewer = SyncDirection::none;
- output.different = SyncDirection::right;
- output.conflict = SyncDirection::none;
- break;
-
- case SyncVariant::custom:
- output = cfg.custom;
- break;
- }
- return output;
+ .leftOnly = changeDirs.left.create,
+ .rightOnly = changeDirs.right.create,
+ .leftNewer = changeDirs.left.update,
+ .rightNewer = changeDirs.right.update,
+ };
+}
+
+
+DirectionByChange fff::getChangesDirDefault(const DirectionByDiff& diffDirs)
+{
+ return
+ {
+ .left
+ {
+ .create = diffDirs.leftOnly,
+ .update = diffDirs.leftNewer,
+ .delete_ = diffDirs.rightOnly,
+ },
+ .right
+ {
+ .create = diffDirs.rightOnly,
+ .update = diffDirs.rightNewer,
+ .delete_ = diffDirs.leftOnly,
+ }
+ };
+}
+
+
+namespace
+{
+DirectionByChange getTwoWayDirSet()
+{
+ return
+ {
+ .left
+ {
+ .create = SyncDirection::right,
+ .update = SyncDirection::right,
+ .delete_ = SyncDirection::right,
+ },
+ .right
+ {
+ .create = SyncDirection::left,
+ .update = SyncDirection::left,
+ .delete_ = SyncDirection::left,
+ }
+ };
}
-bool fff::detectMovedFilesSelectable(const SyncDirectionConfig& cfg)
+DirectionByDiff getMirrorDirSet()
{
- if (cfg.var == SyncVariant::twoWay)
- return false; //moved files are always detected since we have the database file anyway
-
- const DirectionSet tmp = fff::extractDirections(cfg);
- return (tmp.exLeftSideOnly == SyncDirection::right &&
- tmp.exRightSideOnly == SyncDirection::right) ||
- (tmp.exLeftSideOnly == SyncDirection::left &&
- tmp.exRightSideOnly == SyncDirection::left);
+ return
+ {
+ .leftOnly = SyncDirection::right,
+ .rightOnly = SyncDirection::right,
+ .leftNewer = SyncDirection::right,
+ .rightNewer = SyncDirection::right,
+ };
}
-bool fff::detectMovedFilesEnabled(const SyncDirectionConfig& cfg)
+DirectionByChange getUpdateDirSet()
{
- return detectMovedFilesSelectable(cfg) ? cfg.detectMovedFiles : cfg.var == SyncVariant::twoWay;
+ return
+ {
+ .left
+ {
+ .create = SyncDirection::right,
+ .update = SyncDirection::right,
+ .delete_ = SyncDirection::none,
+ },
+ .right
+ {
+ .create = SyncDirection::none,
+ .update = SyncDirection::none,
+ .delete_ = SyncDirection::none,
+ }
+ };
+}
}
-DirectionSet fff::getTwoWayUpdateSet()
+SyncVariant fff::getSyncVariant(const SyncDirectionConfig& cfg)
{
- DirectionSet output;
- output.exLeftSideOnly = SyncDirection::right;
- output.exRightSideOnly = SyncDirection::left;
- output.leftNewer = SyncDirection::right;
- output.rightNewer = SyncDirection::left;
- output.different = SyncDirection::none;
- output.conflict = SyncDirection::none;
- return output;
+ if (const DirectionByDiff* diffDirs = std::get_if<DirectionByDiff>(&cfg.dirs))
+ {
+ if (*diffDirs == getMirrorDirSet())
+ return SyncVariant::mirror;
+ if (*diffDirs == getDiffDirDefault(getUpdateDirSet())) //poor man's "update", still deserves name on GUI
+ return SyncVariant::update;
+ }
+ else
+ {
+ const DirectionByChange& changeDirs = std::get<DirectionByChange>(cfg.dirs);
+ if (changeDirs == getTwoWayDirSet())
+ return SyncVariant::twoWay;
+ if (changeDirs == getChangesDirDefault(getMirrorDirSet())) //equivalent: "mirror" defined in terms of "changes"
+ return SyncVariant::mirror;
+ if (changeDirs == getUpdateDirSet())
+ return SyncVariant::update;
+ }
+ return SyncVariant::custom;
+}
+
+
+SyncDirectionConfig fff::getDefaultSyncCfg(SyncVariant syncVar)
+{
+ switch (syncVar)
+ {
+ //*INDENT-OFF*
+ case SyncVariant::twoWay: return { .dirs = getTwoWayDirSet() };
+ case SyncVariant::mirror: return { .dirs = getMirrorDirSet() };
+ case SyncVariant::update: return { .dirs = getUpdateDirSet() };
+ case SyncVariant::custom: return { .dirs = getDiffDirDefault(getTwoWayDirSet()) };
+ //*INDENT-ON*
+ }
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
}
@@ -172,14 +231,15 @@ std::wstring fff::getSymbol(CompareFileResult cmpRes)
switch (cmpRes)
{
//*INDENT-OFF*
- case FILE_LEFT_SIDE_ONLY: return L"only <-";
- case FILE_RIGHT_SIDE_ONLY: return L"only ->";
+ case FILE_EQUAL: return L"'=="; //added quotation mark to avoid error in Excel cell when exporting to *.cvs
+ case FILE_RENAMED: return L"renamed";
+ case FILE_LEFT_ONLY: return L"only <-";
+ case FILE_RIGHT_ONLY: return L"only ->";
case FILE_LEFT_NEWER: return L"newer <-";
case FILE_RIGHT_NEWER: return L"newer ->";
case FILE_DIFFERENT_CONTENT: return L"!=";
- case FILE_EQUAL:
- case FILE_DIFFERENT_METADATA: /*= sub-category of equal!*/ return L"'=="; //added quotation mark to avoid error in Excel cell when exporting to *.cvs
- case FILE_CONFLICT: return L"conflict";
+ case FILE_TIME_INVALID:
+ case FILE_CONFLICT: return L"conflict";
//*INDENT-ON*
}
assert(false);
@@ -192,21 +252,21 @@ std::wstring fff::getSymbol(SyncOperation op)
switch (op)
{
//*INDENT-OFF*
- case SO_CREATE_NEW_LEFT: return L"create <-";
- case SO_CREATE_NEW_RIGHT: return L"create ->";
- case SO_DELETE_LEFT: return L"delete <-";
- case SO_DELETE_RIGHT: return L"delete ->";
- case SO_MOVE_LEFT_FROM: return L"move from <-";
- case SO_MOVE_LEFT_TO: return L"move to <-";
- case SO_MOVE_RIGHT_FROM: return L"move from ->";
- case SO_MOVE_RIGHT_TO: return L"move to ->";
- case SO_OVERWRITE_LEFT:
- case SO_COPY_METADATA_TO_LEFT: return L"update <-";
- case SO_OVERWRITE_RIGHT:
- case SO_COPY_METADATA_TO_RIGHT: return L"update ->";
- case SO_DO_NOTHING: return L" -";
- case SO_EQUAL: return L"'=="; //added quotation mark to avoid error in Excel cell when exporting to *.cvs
- case SO_UNRESOLVED_CONFLICT: return L"conflict"; //portable Unicode symbol: ⚡
+ case SO_CREATE_LEFT: return L"create <-";
+ case SO_CREATE_RIGHT: return L"create ->";
+ case SO_DELETE_LEFT: return L"delete <-";
+ case SO_DELETE_RIGHT: return L"delete ->";
+ case SO_MOVE_LEFT_FROM: return L"move from <-";
+ case SO_MOVE_LEFT_TO: return L"move to <-";
+ case SO_MOVE_RIGHT_FROM: return L"move from ->";
+ case SO_MOVE_RIGHT_TO: return L"move to ->";
+ case SO_OVERWRITE_LEFT: return L"update <-";
+ case SO_OVERWRITE_RIGHT: return L"update ->";
+ case SO_RENAME_LEFT: return L"rename <-";
+ case SO_RENAME_RIGHT: return L"rename ->";
+ case SO_DO_NOTHING: return L" -";
+ case SO_EQUAL: return L"'=="; //added quotation mark to avoid error in Excel cell when exporting to *.cvs
+ case SO_UNRESOLVED_CONFLICT: return L"conflict"; //portable Unicode symbol: ⚡
//*INDENT-ON*
};
assert(false);
@@ -261,7 +321,7 @@ time_t resolve(size_t value, UnitTime unit, time_t defaultVal)
}
-uint64_t resolve(size_t value, UnitSize unit, uint64_t defaultVal)
+uint64_t resolve(uint64_t value, UnitSize unit, uint64_t defaultVal)
{
constexpr uint64_t maxVal = std::numeric_limits<uint64_t>::max();
@@ -284,8 +344,8 @@ uint64_t resolve(size_t value, UnitSize unit, uint64_t defaultVal)
}
void fff::resolveUnits(size_t timeSpan, UnitTime unitTimeSpan,
- size_t sizeMin, UnitSize unitSizeMin,
- size_t sizeMax, UnitSize unitSizeMax,
+ uint64_t sizeMin, UnitSize unitSizeMin,
+ uint64_t sizeMax, UnitSize unitSizeMax,
time_t& timeFrom, //unit: UTC time, seconds
uint64_t& sizeMinBy, //unit: bytes
uint64_t& sizeMaxBy) //unit: bytes
@@ -296,7 +356,7 @@ void fff::resolveUnits(size_t timeSpan, UnitTime unitTimeSpan,
}
-std::optional<CompareVariant> fff::getCompVariant(const MainConfiguration& mainCfg)
+std::optional<CompareVariant> fff::getCommonCompVariant(const MainConfiguration& mainCfg)
{
const CompareVariant firstVar = mainCfg.firstPair.localCmpCfg ?
mainCfg.firstPair.localCmpCfg->compareVar :
@@ -315,18 +375,18 @@ std::optional<CompareVariant> fff::getCompVariant(const MainConfiguration& mainC
}
-std::optional<SyncVariant> fff::getSyncVariant(const MainConfiguration& mainCfg)
+std::optional<SyncVariant> fff::getCommonSyncVariant(const MainConfiguration& mainCfg)
{
- const SyncVariant firstVar = mainCfg.firstPair.localSyncCfg ?
- mainCfg.firstPair.localSyncCfg->directionCfg.var :
- mainCfg.syncCfg.directionCfg.var; //fallback to main sync cfg
+ const SyncVariant firstVar = getSyncVariant(mainCfg.firstPair.localSyncCfg ?
+ mainCfg.firstPair.localSyncCfg->directionCfg :
+ mainCfg.syncCfg.directionCfg); //fallback to main sync cfg
//test if there's a deviating variant within the additional folder pairs
for (const LocalPairConfig& lpc : mainCfg.additionalPairs)
{
- const SyncVariant localVariant = lpc.localSyncCfg ?
- lpc.localSyncCfg->directionCfg.var :
- mainCfg.syncCfg.directionCfg.var;
+ const SyncVariant localVariant = getSyncVariant(lpc.localSyncCfg ?
+ lpc.localSyncCfg->directionCfg:
+ mainCfg.syncCfg.directionCfg);
if (localVariant != firstVar)
return std::nullopt;
}
diff --git a/FreeFileSync/Source/base/structures.h b/FreeFileSync/Source/base/structures.h
index 4b8ed570..9b7ba360 100644
--- a/FreeFileSync/Source/base/structures.h
+++ b/FreeFileSync/Source/base/structures.h
@@ -7,6 +7,7 @@
#ifndef STRUCTURES_H_8210478915019450901745
#define STRUCTURES_H_8210478915019450901745
+#include <variant>
#include <vector>
#include <memory>
#include <chrono>
@@ -45,34 +46,36 @@ enum class SyncDirection : unsigned char //save space for use in FileSystemObjec
enum CompareFileResult
{
FILE_EQUAL,
- FILE_LEFT_SIDE_ONLY,
- FILE_RIGHT_SIDE_ONLY,
- FILE_LEFT_NEWER, //CompareVariant::timeSize only!
- FILE_RIGHT_NEWER, //
+ FILE_RENAMED, //both sides equal, except for different file name
+ FILE_LEFT_ONLY,
+ FILE_RIGHT_ONLY,
+ FILE_LEFT_NEWER, //
+ FILE_RIGHT_NEWER, //CompareVariant::timeSize only!
+ FILE_TIME_INVALID, // -> sync dirction can be determined (if leftNewer/rightNewer agree), unlike with FILE_CONFLICT
FILE_DIFFERENT_CONTENT, //CompareVariant::content, CompareVariant::size only!
- FILE_DIFFERENT_METADATA, //both sides equal, but different metadata only: short name case
FILE_CONFLICT
};
//attention make sure these /|\ \|/ three enums match!!!
enum CompareDirResult
{
- DIR_EQUAL = FILE_EQUAL,
- DIR_LEFT_SIDE_ONLY = FILE_LEFT_SIDE_ONLY,
- DIR_RIGHT_SIDE_ONLY = FILE_RIGHT_SIDE_ONLY,
- DIR_DIFFERENT_METADATA = FILE_DIFFERENT_METADATA, //both sides equal, but different metadata only: short name case
- DIR_CONFLICT = FILE_CONFLICT
+ DIR_EQUAL = FILE_EQUAL,
+ DIR_RENAMED = FILE_RENAMED,
+ DIR_LEFT_ONLY = FILE_LEFT_ONLY,
+ DIR_RIGHT_ONLY = FILE_RIGHT_ONLY,
+ DIR_CONFLICT = FILE_CONFLICT
};
enum CompareSymlinkResult
{
- SYMLINK_EQUAL = FILE_EQUAL,
- SYMLINK_LEFT_SIDE_ONLY = FILE_LEFT_SIDE_ONLY,
- SYMLINK_RIGHT_SIDE_ONLY = FILE_RIGHT_SIDE_ONLY,
- SYMLINK_LEFT_NEWER = FILE_LEFT_NEWER,
- SYMLINK_RIGHT_NEWER = FILE_RIGHT_NEWER,
- SYMLINK_DIFFERENT_CONTENT = FILE_DIFFERENT_CONTENT,
- SYMLINK_DIFFERENT_METADATA = FILE_DIFFERENT_METADATA, //both sides equal, but different metadata only: short name case
- SYMLINK_CONFLICT = FILE_CONFLICT
+ SYMLINK_EQUAL = FILE_EQUAL,
+ SYMLINK_RENAMED = FILE_RENAMED,
+ SYMLINK_LEFT_ONLY = FILE_LEFT_ONLY,
+ SYMLINK_RIGHT_ONLY = FILE_RIGHT_ONLY,
+ SYMLINK_LEFT_NEWER = FILE_LEFT_NEWER,
+ SYMLINK_RIGHT_NEWER = FILE_RIGHT_NEWER,
+ SYMLINK_TIME_INVALID = FILE_TIME_INVALID,
+ SYMLINK_DIFFERENT_CONTENT = FILE_DIFFERENT_CONTENT,
+ SYMLINK_CONFLICT = FILE_CONFLICT
};
@@ -81,21 +84,22 @@ std::wstring getSymbol(CompareFileResult cmpRes);
enum SyncOperation
{
- SO_CREATE_NEW_LEFT,
- SO_CREATE_NEW_RIGHT,
+ SO_CREATE_LEFT,
+ SO_CREATE_RIGHT,
SO_DELETE_LEFT,
SO_DELETE_RIGHT,
- SO_MOVE_LEFT_FROM, //SO_DELETE_LEFT - optimization!
- SO_MOVE_LEFT_TO, //SO_CREATE_NEW_LEFT
-
- SO_MOVE_RIGHT_FROM, //SO_DELETE_RIGHT - optimization!
- SO_MOVE_RIGHT_TO, //SO_CREATE_NEW_RIGHT
-
SO_OVERWRITE_LEFT,
SO_OVERWRITE_RIGHT,
- SO_COPY_METADATA_TO_LEFT, //objects are already equal: transfer metadata only - optimization
- SO_COPY_METADATA_TO_RIGHT, //
+
+ SO_MOVE_LEFT_FROM, //SO_DELETE_LEFT - optimization!
+ SO_MOVE_LEFT_TO, //SO_CREATE_LEFT
+
+ SO_MOVE_RIGHT_FROM, //SO_DELETE_RIGHT - optimization!
+ SO_MOVE_RIGHT_TO, //SO_CREATE_RIGHT
+
+ SO_RENAME_LEFT, //items are otherwise equal
+ SO_RENAME_RIGHT, //
SO_DO_NOTHING, //nothing will be synced: both sides differ
SO_EQUAL, //nothing will be synced: both sides are equal
@@ -105,62 +109,71 @@ enum SyncOperation
std::wstring getSymbol(SyncOperation op); //method used for exporting .csv file only!
-struct DirectionSet
+enum class CudAction
{
- SyncDirection exLeftSideOnly = SyncDirection::right;
- SyncDirection exRightSideOnly = SyncDirection::left;
- SyncDirection leftNewer = SyncDirection::right; //CompareVariant::timeSize only!
- SyncDirection rightNewer = SyncDirection::left; //
- SyncDirection different = SyncDirection::none; //CompareVariant::content, CompareVariant::size only!
- SyncDirection conflict = SyncDirection::none;
-
- bool operator==(const DirectionSet&) const = default;
+ noChange,
+ create,
+ update,
+ delete_, //"delete" is a reserved keyword :(
};
-DirectionSet getTwoWayUpdateSet();
+struct DirectionByDiff
+{
+ SyncDirection leftOnly = SyncDirection::none;
+ SyncDirection rightOnly = SyncDirection::none;
+ SyncDirection leftNewer = SyncDirection::none;
+ SyncDirection rightNewer = SyncDirection::none;
+
+ bool operator==(const DirectionByDiff&) const = default;
+};
-enum class SyncVariant
+struct DirectionByChange //=> requires sync.ffs_db
{
- twoWay, //use sync-database to determine directions
- mirror, //predefined
- update, //
- custom, //use custom directions
+ struct Changes
+ {
+ SyncDirection create = SyncDirection::none;
+ SyncDirection update = SyncDirection::none;
+ SyncDirection delete_ = SyncDirection::none; //"delete" is a reserved keyword :(
+
+ bool operator==(const Changes&) const = default;
+ } left, right;
+
+ bool operator==(const DirectionByChange&) const = default;
};
+
+
struct SyncDirectionConfig
{
- SyncVariant var = SyncVariant::twoWay;
- DirectionSet custom; //sync directions for SyncVariant::custom
- bool detectMovedFiles = false; //variant-dependent: e.g. always active for SyncVariant::twoWay! => use functions below for evaluation!
+ std::variant<DirectionByDiff, DirectionByChange> dirs;
+
+ bool operator==(const SyncDirectionConfig&) const = default;
};
-bool detectMovedFilesSelectable(const SyncDirectionConfig& cfg);
-bool detectMovedFilesEnabled (const SyncDirectionConfig& cfg);
-DirectionSet extractDirections(const SyncDirectionConfig& cfg); //get sync directions: DON'T call for SyncVariant::twoWay!
+inline
+bool effectivelyEqual(const SyncDirectionConfig& lhs, const SyncDirectionConfig& rhs) { return lhs == rhs; } //no change in behavior
+
+
+enum class SyncVariant
+{
+ twoWay,
+ mirror,
+ update,
+ custom,
+};
+SyncVariant getSyncVariant(const SyncDirectionConfig& cfg);
+
+SyncDirectionConfig getDefaultSyncCfg(SyncVariant syncVar);
+
+DirectionByDiff getDiffDirDefault(const DirectionByChange& changeDirs); //= when sync.ffs_db not yet available
+DirectionByChange getChangesDirDefault(const DirectionByDiff& diffDirs);
std::wstring getVariantName(std::optional<CompareVariant> var);
std::wstring getVariantName(std::optional<SyncVariant> var);
std::wstring getVariantNameWithSymbol(SyncVariant var);
-inline
-bool operator==(const SyncDirectionConfig& lhs, const SyncDirectionConfig& rhs)
-{
- return lhs.var == rhs.var &&
- (lhs.var != SyncVariant::custom || lhs.custom == rhs.custom) && //no need to consider custom directions if var != CUSTOM
- lhs.detectMovedFiles == rhs.detectMovedFiles; //useful to remember this setting even if the current sync variant does not need it
- //adapt effectivelyEqual() on changes, too!
-}
-
-inline
-bool effectivelyEqual(const SyncDirectionConfig& lhs, const SyncDirectionConfig& rhs)
-{
- return (lhs.var == SyncVariant::twoWay) == (rhs.var == SyncVariant::twoWay) && //either both two-way or none
- (lhs.var == SyncVariant::twoWay || extractDirections(lhs) == extractDirections(rhs)) &&
- detectMovedFilesEnabled(lhs) == detectMovedFilesEnabled(rhs);
-}
-
struct CompConfig
{
@@ -192,7 +205,7 @@ enum class VersioningStyle
struct SyncConfig
{
//sync direction settings
- SyncDirectionConfig directionCfg;
+ SyncDirectionConfig directionCfg = getDefaultSyncCfg(SyncVariant::twoWay);
DeletionVariant deletionVariant = DeletionVariant::recycler; //use Recycle Bin, delete permanently or move to user-defined location
@@ -212,7 +225,7 @@ bool operator==(const SyncConfig& lhs, const SyncConfig& rhs)
{
return lhs.directionCfg == rhs.directionCfg &&
lhs.deletionVariant == rhs.deletionVariant && //!= DeletionVariant::versioning => still consider versioningFolderPhrase: e.g. user temporarily
- lhs.versioningFolderPhrase == rhs.versioningFolderPhrase && //switched to "permanent" deletion and accidentally saved cfg => versioning folder is easily restored
+ lhs.versioningFolderPhrase == rhs.versioningFolderPhrase && //switched to "permanent" deletion and accidentally saved cfg => versioning folder can be restored
lhs.versioningStyle == rhs.versioningStyle &&
(lhs.versioningStyle == VersioningStyle::replace ||
(
@@ -264,24 +277,6 @@ enum class UnitTime
struct FilterConfig
{
- FilterConfig() {}
- FilterConfig(const Zstring& include,
- const Zstring& exclude,
- size_t timeSpanIn,
- UnitTime unitTimeSpanIn,
- size_t sizeMinIn,
- UnitSize unitSizeMinIn,
- size_t sizeMaxIn,
- UnitSize unitSizeMaxIn) :
- includeFilter(include),
- excludeFilter(exclude),
- timeSpan (timeSpanIn),
- unitTimeSpan (unitTimeSpanIn),
- sizeMin (sizeMinIn),
- unitSizeMin (unitSizeMinIn),
- sizeMax (sizeMaxIn),
- unitSizeMax (unitSizeMaxIn) {}
-
/* Semantics of PathFilter:
1. using it creates a NEW folder hierarchy! -> must be considered by <Two way> variant! (fortunately it turns out, doing nothing already has perfect semantics :)
2. it applies equally to both sides => it always matches either both sides or none! => can be used while traversing a single folder! */
@@ -292,13 +287,13 @@ struct FilterConfig
1. It potentially may match only one side => it MUST NOT be applied while traversing a single folder to avoid mismatches
2. => it is applied after traversing and just marks rows, (NO deletions after comparison are allowed)
3. => equivalent to a user temporarily (de-)selecting rows -> not relevant for <Two way> variant! ;) */
- size_t timeSpan = 0;
+ unsigned int timeSpan = 0;
UnitTime unitTimeSpan = UnitTime::none;
- size_t sizeMin = 0;
+ uint64_t sizeMin = 0;
UnitSize unitSizeMin = UnitSize::none;
- size_t sizeMax = 0;
+ uint64_t sizeMax = 0;
UnitSize unitSizeMax = UnitSize::none;
bool operator==(const FilterConfig&) const = default;
@@ -306,8 +301,8 @@ struct FilterConfig
void resolveUnits(size_t timeSpan, UnitTime unitTimeSpan,
- size_t sizeMin, UnitSize unitSizeMin,
- size_t sizeMax, UnitSize unitSizeMax,
+ uint64_t sizeMin, UnitSize unitSizeMin,
+ uint64_t sizeMax, UnitSize unitSizeMax,
time_t& timeFrom, //unit: UTC time, seconds
uint64_t& sizeMinBy, //unit: bytes
uint64_t& sizeMaxBy); //unit: bytes
@@ -315,19 +310,6 @@ void resolveUnits(size_t timeSpan, UnitTime unitTimeSpan,
struct LocalPairConfig //enhanced folder pairs with (optional) alternate configuration
{
- LocalPairConfig() {}
-
- LocalPairConfig(const Zstring& phraseLeft,
- const Zstring& phraseRight,
- const std::optional<CompConfig>& cmpCfg,
- const std::optional<SyncConfig>& syncCfg,
- const FilterConfig& filter) :
- folderPathPhraseLeft (phraseLeft),
- folderPathPhraseRight(phraseRight),
- localCmpCfg(cmpCfg),
- localSyncCfg(syncCfg),
- localFilter(filter) {}
-
Zstring folderPathPhraseLeft; //unresolved directory names as entered by user!
Zstring folderPathPhraseRight; //
@@ -390,8 +372,8 @@ size_t getDeviceParallelOps(const std::map<AfsDevice, size_t>& deviceParallelOps
void setDeviceParallelOps( std::map<AfsDevice, size_t>& deviceParallelOps, const Zstring& folderPathPhrase, size_t parallelOps);
-std::optional<CompareVariant> getCompVariant(const MainConfiguration& mainCfg);
-std::optional<SyncVariant> getSyncVariant(const MainConfiguration& mainCfg);
+std::optional<CompareVariant> getCommonCompVariant(const MainConfiguration& mainCfg);
+std::optional<SyncVariant> getCommonSyncVariant(const MainConfiguration& mainCfg);
struct WarningDialogs
diff --git a/FreeFileSync/Source/base/synchronization.cpp b/FreeFileSync/Source/base/synchronization.cpp
index 1d686900..94ef3918 100644
--- a/FreeFileSync/Source/base/synchronization.cpp
+++ b/FreeFileSync/Source/base/synchronization.cpp
@@ -39,9 +39,9 @@ SyncStatistics::SyncStatistics(const FolderComparison& folderCmp)
}
-SyncStatistics::SyncStatistics(const ContainerObject& hierObj)
+SyncStatistics::SyncStatistics(const ContainerObject& conObj)
{
- recurse(hierObj);
+ recurse(conObj);
}
@@ -53,18 +53,33 @@ SyncStatistics::SyncStatistics(const FilePair& file)
inline
-void SyncStatistics::recurse(const ContainerObject& hierObj)
+void SyncStatistics::recurse(const ContainerObject& conObj)
{
- for (const FilePair& file : hierObj.refSubFiles())
+ for (const FilePair& file : conObj.refSubFiles())
processFile(file);
- for (const SymlinkPair& symlink : hierObj.refSubLinks())
+ for (const SymlinkPair& symlink : conObj.refSubLinks())
processLink(symlink);
- for (const FolderPair& folder : hierObj.refSubFolders())
+ for (const FolderPair& folder : conObj.refSubFolders())
processFolder(folder);
- rowsTotal_ += hierObj.refSubFolders().size();
- rowsTotal_ += hierObj.refSubFiles ().size();
- rowsTotal_ += hierObj.refSubLinks ().size();
+ rowsTotal_ += conObj.refSubFolders().size();
+ rowsTotal_ += conObj.refSubFiles ().size();
+ rowsTotal_ += conObj.refSubLinks ().size();
+}
+
+
+inline
+void SyncStatistics::logConflict(const FileSystemObject& fsObj)
+{
+ if (conflictsPreview_.size() < CONFLICTS_PREVIEW_MAX)
+ {
+ const Zstring& relPathL = fsObj.getRelativePath<SelectSide::left >();
+ const Zstring& relPathR = fsObj.getRelativePath<SelectSide::right>();
+
+ conflictsPreview_.push_back((getUnicodeNormalForm(relPathL) == getUnicodeNormalForm(relPathR) ?
+ utfTo<std::wstring>(relPathL) :
+ utfTo<std::wstring>(relPathL + Zstr('\n') + relPathR)) + L": " + fsObj.getSyncOpConflict());
+ }
}
@@ -73,12 +88,12 @@ void SyncStatistics::processFile(const FilePair& file)
{
switch (file.getSyncOperation()) //evaluate comparison result and sync direction
{
- case SO_CREATE_NEW_LEFT:
+ case SO_CREATE_LEFT:
++createLeft_;
bytesToProcess_ += static_cast<int64_t>(file.getFileSize<SelectSide::right>());
break;
- case SO_CREATE_NEW_RIGHT:
+ case SO_CREATE_RIGHT:
++createRight_;
bytesToProcess_ += static_cast<int64_t>(file.getFileSize<SelectSide::left>());
break;
@@ -115,15 +130,14 @@ void SyncStatistics::processFile(const FilePair& file)
case SO_UNRESOLVED_CONFLICT:
++conflictCount_;
- if (conflictsPreview_.size() < CONFLICTS_PREVIEW_MAX)
- conflictsPreview_.push_back({file.getRelativePathAny(), file.getSyncOpConflict()});
+ logConflict(file);
break;
- case SO_COPY_METADATA_TO_LEFT:
+ case SO_RENAME_LEFT:
++updateLeft_;
break;
- case SO_COPY_METADATA_TO_RIGHT:
+ case SO_RENAME_RIGHT:
++updateRight_;
break;
@@ -139,11 +153,11 @@ void SyncStatistics::processLink(const SymlinkPair& symlink)
{
switch (symlink.getSyncOperation()) //evaluate comparison result and sync direction
{
- case SO_CREATE_NEW_LEFT:
+ case SO_CREATE_LEFT:
++createLeft_;
break;
- case SO_CREATE_NEW_RIGHT:
+ case SO_CREATE_RIGHT:
++createRight_;
break;
@@ -156,19 +170,18 @@ void SyncStatistics::processLink(const SymlinkPair& symlink)
break;
case SO_OVERWRITE_LEFT:
- case SO_COPY_METADATA_TO_LEFT:
+ case SO_RENAME_LEFT:
++updateLeft_;
break;
case SO_OVERWRITE_RIGHT:
- case SO_COPY_METADATA_TO_RIGHT:
+ case SO_RENAME_RIGHT:
++updateRight_;
break;
case SO_UNRESOLVED_CONFLICT:
++conflictCount_;
- if (conflictsPreview_.size() < CONFLICTS_PREVIEW_MAX)
- conflictsPreview_.push_back({symlink.getRelativePathAny(), symlink.getSyncOpConflict()});
+ logConflict(symlink);
break;
case SO_MOVE_LEFT_FROM:
@@ -189,11 +202,11 @@ void SyncStatistics::processFolder(const FolderPair& folder)
{
switch (folder.getSyncOperation()) //evaluate comparison result and sync direction
{
- case SO_CREATE_NEW_LEFT:
+ case SO_CREATE_LEFT:
++createLeft_;
break;
- case SO_CREATE_NEW_RIGHT:
+ case SO_CREATE_RIGHT:
++createRight_;
break;
@@ -207,20 +220,19 @@ void SyncStatistics::processFolder(const FolderPair& folder)
case SO_UNRESOLVED_CONFLICT:
++conflictCount_;
- if (conflictsPreview_.size() < CONFLICTS_PREVIEW_MAX)
- conflictsPreview_.push_back({folder.getRelativePathAny(), folder.getSyncOpConflict()});
+ logConflict(folder);
break;
- case SO_OVERWRITE_LEFT:
- case SO_COPY_METADATA_TO_LEFT:
+ case SO_RENAME_LEFT:
++updateLeft_;
break;
- case SO_OVERWRITE_RIGHT:
- case SO_COPY_METADATA_TO_RIGHT:
+ case SO_RENAME_RIGHT:
++updateRight_;
break;
+ case SO_OVERWRITE_LEFT:
+ case SO_OVERWRITE_RIGHT:
case SO_MOVE_LEFT_FROM:
case SO_MOVE_RIGHT_FROM:
case SO_MOVE_LEFT_TO:
@@ -254,17 +266,17 @@ public:
}
private:
- void recurse(const ContainerObject& hierObj)
+ void recurse(const ContainerObject& conObj)
{
//process files
- for (const FilePair& file : hierObj.refSubFiles())
+ for (const FilePair& file : conObj.refSubFiles())
switch (file.getSyncOperation()) //evaluate comparison result and sync direction
{
- case SO_CREATE_NEW_LEFT:
+ case SO_CREATE_LEFT:
spaceNeededLeft_ += static_cast<int64_t>(file.getFileSize<SelectSide::right>());
break;
- case SO_CREATE_NEW_RIGHT:
+ case SO_CREATE_RIGHT:
spaceNeededRight_ += static_cast<int64_t>(file.getFileSize<SelectSide::left>());
break;
@@ -293,8 +305,8 @@ private:
case SO_DO_NOTHING:
case SO_EQUAL:
case SO_UNRESOLVED_CONFLICT:
- case SO_COPY_METADATA_TO_LEFT:
- case SO_COPY_METADATA_TO_RIGHT:
+ case SO_RENAME_LEFT:
+ case SO_RENAME_RIGHT:
case SO_MOVE_LEFT_FROM:
case SO_MOVE_RIGHT_FROM:
case SO_MOVE_LEFT_TO:
@@ -306,7 +318,7 @@ private:
//[...]
//recurse into sub-dirs
- for (const FolderPair& folder : hierObj.refSubFolders())
+ for (const FolderPair& folder : conObj.refSubFolders())
switch (folder.getSyncOperation())
{
case SO_DELETE_LEFT:
@@ -318,18 +330,18 @@ private:
recurse(folder);
break;
+ case SO_OVERWRITE_LEFT:
+ case SO_OVERWRITE_RIGHT:
case SO_MOVE_LEFT_FROM:
case SO_MOVE_RIGHT_FROM:
case SO_MOVE_LEFT_TO:
case SO_MOVE_RIGHT_TO:
assert(false);
[[fallthrough]];
- case SO_CREATE_NEW_LEFT:
- case SO_CREATE_NEW_RIGHT:
- case SO_OVERWRITE_LEFT:
- case SO_OVERWRITE_RIGHT:
- case SO_COPY_METADATA_TO_LEFT:
- case SO_COPY_METADATA_TO_RIGHT:
+ case SO_CREATE_LEFT:
+ case SO_CREATE_RIGHT:
+ case SO_RENAME_LEFT:
+ case SO_RENAME_RIGHT:
case SO_DO_NOTHING:
case SO_EQUAL:
case SO_UNRESOLVED_CONFLICT:
@@ -359,9 +371,8 @@ std::vector<FolderPairSyncCfg> fff::extractSyncCfg(const MainConfiguration& main
output.push_back(
{
- syncCfg.directionCfg.var,
- syncCfg.directionCfg.var == SyncVariant::twoWay || detectMovedFilesEnabled(syncCfg.directionCfg),
-
+ getSyncVariant(syncCfg.directionCfg),
+ !!std::get_if<DirectionByChange>(&syncCfg.directionCfg.dirs),
syncCfg.deletionVariant,
syncCfg.versioningFolderPhrase,
syncCfg.versioningStyle,
@@ -377,36 +388,6 @@ std::vector<FolderPairSyncCfg> fff::extractSyncCfg(const MainConfiguration& main
namespace
{
-inline
-std::optional<SelectSide> getTargetDirection(SyncOperation syncOp)
-{
- switch (syncOp)
- {
- case SO_CREATE_NEW_LEFT:
- case SO_DELETE_LEFT:
- case SO_OVERWRITE_LEFT:
- case SO_COPY_METADATA_TO_LEFT:
- case SO_MOVE_LEFT_FROM:
- case SO_MOVE_LEFT_TO:
- return SelectSide::left;
-
- case SO_CREATE_NEW_RIGHT:
- case SO_DELETE_RIGHT:
- case SO_OVERWRITE_RIGHT:
- case SO_COPY_METADATA_TO_RIGHT:
- case SO_MOVE_RIGHT_FROM:
- case SO_MOVE_RIGHT_TO:
- return SelectSide::right;
-
- case SO_DO_NOTHING:
- case SO_EQUAL:
- case SO_UNRESOLVED_CONFLICT:
- break; //nothing to do
- }
- return {};
-}
-
-
//test if user accidentally selected the wrong folders to sync
bool significantDifferenceDetected(const SyncStatistics& folderPairStat)
{
@@ -431,10 +412,15 @@ bool significantDifferenceDetected(const SyncStatistics& folderPairStat)
template <SelectSide side>
bool plannedWriteAccess(const FileSystemObject& fsObj)
{
- if (std::optional<SelectSide> dir = getTargetDirection(fsObj.getSyncOperation()))
- return side == *dir;
- else
- return false;
+ switch (getEffectiveSyncDir(fsObj.getSyncOperation()))
+ {
+ //*INDENT-OFF*
+ case SyncDirection::none: return false;
+ case SyncDirection::left: return side == SelectSide::left;
+ case SyncDirection::right: return side == SelectSide::right;
+ //*INDENT-ON*
+ }
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
}
@@ -470,12 +456,15 @@ std::weak_ordering comparePathNoCase(const PathRaceItem& lhs, const PathRaceItem
std::wstring formatRaceItem(const PathRaceItem& item)
{
- const std::optional<SelectSide> syncDir = getTargetDirection(item.fsObj->getSyncOperation());
+ const SyncDirection syncDir = getEffectiveSyncDir(item.fsObj->getSyncOperation());
+
+ const bool writeAcess = (syncDir == SyncDirection::left && item.side == SelectSide::left) ||
+ (syncDir == SyncDirection::right && item.side == SelectSide::right);
return AFS::getDisplayPath(item.side == SelectSide::left ?
item.fsObj->base().getAbstractPath<SelectSide::left>() :
item.fsObj->base().getAbstractPath<SelectSide::right>()) +
- (syncDir && *syncDir == item.side ? L" 💾 " : L" 👓 ") +
+ (writeAcess ? L" 💾 " : L" 👓 ") +
utfTo<std::wstring>(item.side == SelectSide::left ?
item.fsObj->getRelativePath<SelectSide::left>() :
item.fsObj->getRelativePath<SelectSide::right>());
@@ -512,16 +501,16 @@ private:
GetChildItemsHashed() {}
- void recurse(const ContainerObject& hierObj, uint64_t parentPathHash)
+ void recurse(const ContainerObject& conObj, uint64_t parentPathHash)
{
- for (const FilePair& file : hierObj.refSubFiles())
+ for (const FilePair& file : conObj.refSubFiles())
childPathRefs_.push_back({&file, getPathHash(file, parentPathHash)});
//S1 -> T (update) is not a conflict (anymore) if S1, S2 contain different files
//S2 -> T (update) https://freefilesync.org/forum/viewtopic.php?t=9365#p36466
- for (const SymlinkPair& symlink : hierObj.refSubLinks())
+ for (const SymlinkPair& symlink : conObj.refSubLinks())
childPathRefs_.push_back({&symlink, getPathHash(symlink, parentPathHash)});
- for (const FolderPair& subFolder : hierObj.refSubFolders())
+ for (const FolderPair& subFolder : conObj.refSubFolders())
{
const uint64_t folderPathHash = getPathHash(subFolder, parentPathHash);
@@ -1208,11 +1197,11 @@ private:
};
-template <class List> inline
-bool haveNameClash(const Zstring& itemName, const List& m)
+template <SelectSide side, class List> inline
+bool haveNameClash(const FileSystemObject& fsObj, const List& m)
{
- return std::any_of(m.begin(), m.end(),
- [&](const typename List::value_type& obj) { return equalNoCase(obj.getItemNameAny(), itemName); }); //equalNoCase: when in doubt => assume name clash!
+ return std::any_of(m.begin(), m.end(), [itemName = fsObj.getItemName<side>()](const FileSystemObject& sibling)
+ { return equalNoCase(sibling.getItemName<side>(), itemName); }); //equalNoCase: when in doubt => assume name clash!
}
@@ -1310,7 +1299,9 @@ private:
const std::wstring txtUpdatingFile_ {_("Updating file %x" )};
const std::wstring txtUpdatingLink_ {_("Updating symbolic link %x")};
const std::wstring txtVerifyingFile_ {_("Verifying file %x" )};
- const std::wstring txtUpdatingAttributes_{_("Updating attributes of %x")};
+ const std::wstring txtRenamingFileXtoY_ {_("Renaming file %x to %y" )};
+ const std::wstring txtRenamingLinkXtoY_ {_("Renaming symbolic link %x to %y")};
+ const std::wstring txtRenamingFolderXtoY_{_("Renaming folder %x to %y" )};
const std::wstring txtMovingFileXtoY_ {_("Moving file %x to %y" )};
const std::wstring txtSourceItemNotExist_{_("Source item %x is not existing")};
};
@@ -1381,7 +1372,7 @@ RingBuffer<Workload::WorkItems> FolderPairSyncer::getFolderLevelWorkItems(PassNo
while (!foldersToInspect.empty())
{
- ContainerObject& hierObj = *foldersToInspect. front();
+ ContainerObject& conObj = *foldersToInspect. front();
/**/ foldersToInspect.pop_front();
RingBuffer<std::function<void()>> workItems;
@@ -1389,10 +1380,8 @@ RingBuffer<Workload::WorkItems> FolderPairSyncer::getFolderLevelWorkItems(PassNo
if (pass == PassNo::zero)
{
//create folders as required by file move targets:
- for (FolderPair& folder : hierObj.refSubFolders())
- if (needZeroPass(folder) &&
- !haveNameClash(folder.getItemNameAny(), folder.parent().refSubFiles()) && //name clash with files/symlinks? obscure => skip folder creation
- !haveNameClash(folder.getItemNameAny(), folder.parent().refSubLinks())) // => move: fall back to delete + copy
+ for (FolderPair& folder : conObj.refSubFolders())
+ if (needZeroPass(folder))
workItems.push_back([this, &folder, &workload, pass]
{
tryReportingError([&] { synchronizeFolder(folder); }, acb_); //throw ThreadStopRequest
@@ -1402,14 +1391,14 @@ RingBuffer<Workload::WorkItems> FolderPairSyncer::getFolderLevelWorkItems(PassNo
else
foldersToInspect.push_back(&folder);
- for (FilePair& file : hierObj.refSubFiles())
+ for (FilePair& file : conObj.refSubFiles())
if (needZeroPass(file))
workItems.push_back([this, &file] { executeFileMove(file); /*throw ThreadStopRequest*/ });
}
else
{
//synchronize folders *first* (see comment above "Multithreaded File Copy")
- for (FolderPair& folder : hierObj.refSubFolders())
+ for (FolderPair& folder : conObj.refSubFolders())
if (pass == getPass(folder))
workItems.push_back([this, &folder, &workload, pass]
{
@@ -1421,7 +1410,7 @@ RingBuffer<Workload::WorkItems> FolderPairSyncer::getFolderLevelWorkItems(PassNo
foldersToInspect.push_back(&folder);
//synchronize files:
- for (FilePair& file : hierObj.refSubFiles())
+ for (FilePair& file : conObj.refSubFiles())
if (pass == getPass(file))
workItems.push_back([this, &file]
{
@@ -1429,7 +1418,7 @@ RingBuffer<Workload::WorkItems> FolderPairSyncer::getFolderLevelWorkItems(PassNo
});
//synchronize symbolic links:
- for (SymlinkPair& symlink : hierObj.refSubLinks())
+ for (SymlinkPair& symlink : conObj.refSubLinks())
if (pass == getPass(symlink))
workItems.push_back([this, &symlink]
{
@@ -1484,21 +1473,17 @@ void FolderPairSyncer::executeFileMoveImpl(FilePair& fileFrom, FilePair& fileTo)
if (parentMissing)
{
- reportItemInfo(_("Cannot move file %x to %y.") + L"\n\n" +
- replaceCpy(_("Parent folder %x is not existing."), L"%x", fmtPath(AFS::getDisplayPath(parentMissing->getAbstractPath<side>()))),
- fileFrom.getAbstractPath<side>(),
- fileTo .getAbstractPath<side>()); //throw ThreadStopRequest
+ reportInfo(AFS::generateMoveErrorMsg(fileFrom.getAbstractPath<side>(), fileTo.getAbstractPath<side>()) + L"\n\n" +
+ replaceCpy(_("Parent folder %x is not existing."), L"%x", fmtPath(AFS::getDisplayPath(parentMissing->getAbstractPath<side>()))), acb_); //throw ThreadStopRequest
return true;
}
//name clash with folders/symlinks? obscure => fall back to delete + copy
- if (haveNameClash(fileTo.getItemNameAny(), fileTo.parent().refSubFolders()) ||
- haveNameClash(fileTo.getItemNameAny(), fileTo.parent().refSubLinks ()))
+ if (haveNameClash<side>(fileTo, fileTo.parent().refSubFolders()) ||
+ haveNameClash<side>(fileTo, fileTo.parent().refSubLinks ()))
{
- reportItemInfo(_("Cannot move file %x to %y.") + L"\n\n" +
- replaceCpy(_("The name %x is already used by another item."), L"%x", fmtPath(fileTo.getItemNameAny())),
- fileFrom.getAbstractPath<side>(),
- fileTo .getAbstractPath<side>()); //throw ThreadStopRequest
+ reportInfo(AFS::generateMoveErrorMsg(fileFrom.getAbstractPath<side>(), fileTo.getAbstractPath<side>()) + L"\n\n" +
+ replaceCpy(_("The name %x is already used by another item."), L"%x", fmtPath(fileTo.getItemName<side>())), acb_); //throw ThreadStopRequest
return true;
}
@@ -1557,16 +1542,16 @@ void FolderPairSyncer::executeFileMove(FilePair& file) //throw ThreadStopRequest
else assert(false);
break;
- case SO_CREATE_NEW_LEFT:
- case SO_CREATE_NEW_RIGHT:
+ case SO_CREATE_LEFT:
+ case SO_CREATE_RIGHT:
case SO_DELETE_LEFT:
case SO_DELETE_RIGHT:
case SO_MOVE_LEFT_FROM: //don't try to move more than *once* per pair
case SO_MOVE_RIGHT_FROM: //
case SO_OVERWRITE_LEFT:
case SO_OVERWRITE_RIGHT:
- case SO_COPY_METADATA_TO_LEFT:
- case SO_COPY_METADATA_TO_RIGHT:
+ case SO_RENAME_LEFT:
+ case SO_RENAME_RIGHT:
case SO_DO_NOTHING:
case SO_EQUAL:
case SO_UNRESOLVED_CONFLICT:
@@ -1595,24 +1580,30 @@ bool FolderPairSyncer::needZeroPass(const FolderPair& folder)
{
switch (folder.getSyncOperation())
{
- case SO_CREATE_NEW_LEFT:
- case SO_CREATE_NEW_RIGHT:
- return containsMoveTarget(folder); //recursive! watch perf!
+ case SO_CREATE_LEFT:
+ return containsMoveTarget(folder) && //recursive! watch perf!
+ !haveNameClash<SelectSide::left>(folder, folder.parent().refSubFiles()) && //name clash with files/symlinks? obscure => skip folder creation
+ !haveNameClash<SelectSide::left>(folder, folder.parent().refSubLinks()); // => move: fall back to delete + copy
+
+ case SO_CREATE_RIGHT:
+ return containsMoveTarget(folder) && //recursive! watch perf!
+ !haveNameClash<SelectSide::right>(folder, folder.parent().refSubFiles()) && //name clash with files/symlinks? obscure => skip folder creation
+ !haveNameClash<SelectSide::right>(folder, folder.parent().refSubLinks()); // => move: fall back to delete + copy
case SO_DO_NOTHING: //implies !isEmpty<side>(); see FolderPair::getSyncOperation()
case SO_UNRESOLVED_CONFLICT: //
case SO_EQUAL:
- case SO_OVERWRITE_LEFT: //possible: e.g. manually-resolved dir-traversal conflict
- case SO_OVERWRITE_RIGHT: //
- case SO_COPY_METADATA_TO_LEFT:
- case SO_COPY_METADATA_TO_RIGHT:
- assert((!folder.isEmpty<SelectSide::left>() && !folder.isEmpty<SelectSide::right>()) || !containsMoveTarget(folder));
+ case SO_RENAME_LEFT:
+ case SO_RENAME_RIGHT:
+ assert(!containsMoveTarget(folder) || (!folder.isEmpty<SelectSide::left>() && !folder.isEmpty<SelectSide::right>()));
//we're good to move contained items
break;
case SO_DELETE_LEFT: //not possible in the context of planning to move a child item, see FolderPair::getSyncOperation()
case SO_DELETE_RIGHT: //
assert(!containsMoveTarget(folder));
break;
+ case SO_OVERWRITE_LEFT: //
+ case SO_OVERWRITE_RIGHT: //
case SO_MOVE_LEFT_FROM: //
case SO_MOVE_RIGHT_FROM: //status not possible for folder
case SO_MOVE_LEFT_TO: //
@@ -1633,16 +1624,16 @@ bool FolderPairSyncer::needZeroPass(const FilePair& file)
case SO_MOVE_RIGHT_TO:
return true;
- case SO_CREATE_NEW_LEFT:
- case SO_CREATE_NEW_RIGHT:
+ case SO_CREATE_LEFT:
+ case SO_CREATE_RIGHT:
case SO_DELETE_LEFT:
case SO_DELETE_RIGHT:
case SO_MOVE_LEFT_FROM: //don't try to move more than *once* per pair
case SO_MOVE_RIGHT_FROM: //
case SO_OVERWRITE_LEFT:
case SO_OVERWRITE_RIGHT:
- case SO_COPY_METADATA_TO_LEFT:
- case SO_COPY_METADATA_TO_RIGHT:
+ case SO_RENAME_LEFT:
+ case SO_RENAME_RIGHT:
case SO_DO_NOTHING:
case SO_EQUAL:
case SO_UNRESOLVED_CONFLICT:
@@ -1678,10 +1669,10 @@ FolderPairSyncer::PassNo FolderPairSyncer::getPass(const FilePair& file)
case SO_MOVE_RIGHT_TO: //make sure 2-step move is processed in second pass, after move *target* parent directory was created!
return PassNo::two;
- case SO_CREATE_NEW_LEFT:
- case SO_CREATE_NEW_RIGHT:
- case SO_COPY_METADATA_TO_LEFT:
- case SO_COPY_METADATA_TO_RIGHT:
+ case SO_CREATE_LEFT:
+ case SO_CREATE_RIGHT:
+ case SO_RENAME_LEFT:
+ case SO_RENAME_RIGHT:
return PassNo::two;
case SO_DO_NOTHING:
@@ -1705,10 +1696,10 @@ FolderPairSyncer::PassNo FolderPairSyncer::getPass(const SymlinkPair& symlink)
case SO_OVERWRITE_LEFT:
case SO_OVERWRITE_RIGHT:
- case SO_CREATE_NEW_LEFT:
- case SO_CREATE_NEW_RIGHT:
- case SO_COPY_METADATA_TO_LEFT:
- case SO_COPY_METADATA_TO_RIGHT:
+ case SO_CREATE_LEFT:
+ case SO_CREATE_RIGHT:
+ case SO_RENAME_LEFT:
+ case SO_RENAME_RIGHT:
return PassNo::two;
case SO_MOVE_LEFT_FROM:
@@ -1736,14 +1727,14 @@ FolderPairSyncer::PassNo FolderPairSyncer::getPass(const FolderPair& folder)
case SO_DELETE_RIGHT:
return PassNo::one;
- case SO_CREATE_NEW_LEFT:
- case SO_CREATE_NEW_RIGHT:
- case SO_OVERWRITE_LEFT:
- case SO_OVERWRITE_RIGHT:
- case SO_COPY_METADATA_TO_LEFT:
- case SO_COPY_METADATA_TO_RIGHT:
+ case SO_CREATE_LEFT:
+ case SO_CREATE_RIGHT:
+ case SO_RENAME_LEFT:
+ case SO_RENAME_RIGHT:
return PassNo::two;
+ case SO_OVERWRITE_LEFT:
+ case SO_OVERWRITE_RIGHT:
case SO_MOVE_LEFT_FROM:
case SO_MOVE_RIGHT_FROM:
case SO_MOVE_LEFT_TO:
@@ -1767,9 +1758,10 @@ void FolderPairSyncer::synchronizeFile(FilePair& file) //throw FileError, ErrorM
assert(isLocked(singleThread_));
const SyncOperation syncOp = file.getSyncOperation();
- if (std::optional<SelectSide> sideTrg = getTargetDirection(syncOp))
+ if (const SyncDirection syncDir = getEffectiveSyncDir(syncOp);
+ syncDir != SyncDirection::none)
{
- if (*sideTrg == SelectSide::left)
+ if (syncDir == SyncDirection::left)
synchronizeFileInt<SelectSide::left>(file, syncOp);
else
synchronizeFileInt<SelectSide::right>(file, syncOp);
@@ -1785,8 +1777,8 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp)
switch (syncOp)
{
- case SO_CREATE_NEW_LEFT:
- case SO_CREATE_NEW_RIGHT:
+ case SO_CREATE_LEFT:
+ case SO_CREATE_RIGHT:
{
if (auto parentFolder = dynamic_cast<const FolderPair*>(&file.parent()))
if (parentFolder->isEmpty<sideTrg>()) //BaseFolderPair OTOH is always non-empty and existing in this context => else: fatal error in zen::synchronize()
@@ -1809,7 +1801,7 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp)
statReporter.reportDelta(1, 0);
//update FilePair
- file.setSyncedTo<sideTrg>(file.getItemName<sideSrc>(), result.fileSize,
+ file.setSyncedTo<sideTrg>(result.fileSize,
result.modTime, //target time set from source
result.modTime,
result.targetFilePrint,
@@ -1883,8 +1875,7 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp)
//update FilePair
assert(fileFrom->getFileSize<sideTrg>() == fileTo->getFileSize<sideSrc>());
- fileTo->setSyncedTo<sideTrg>(fileTo ->getItemName<sideSrc>(),
- fileTo ->getFileSize<sideSrc>(),
+ fileTo->setSyncedTo<sideTrg>(fileTo ->getFileSize<sideSrc>(),
fileFrom->getLastWriteTime<sideTrg>(),
fileTo ->getLastWriteTime<sideSrc>(),
fileFrom->getFilePrint<sideTrg>(),
@@ -1913,8 +1904,7 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp)
AsyncItemStatReporter statReporter(1, file.getFileSize<sideSrc>(), acb_);
if (file.isFollowedSymlink<sideTrg>()) //since we follow the link, we need to sync case sensitivity of the link manually!
- if (getUnicodeNormalForm(file.getItemName<sideTrg>()) !=
- getUnicodeNormalForm(file.getItemName<sideSrc>())) //have difference in case?
+ if (!file.hasEquivalentItemNames())
//already existing: undefined behavior! (e.g. fail/overwrite)
parallel::moveAndRenameItem(file.getAbstractPath<sideTrg>(), targetPathLogical, singleThread_); //throw FileError, (ErrorMoveUnsupported)
@@ -1943,7 +1933,7 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp)
//we model "delete + copy" as ONE logical operation
//update FilePair
- file.setSyncedTo<sideTrg>(file.getItemName<sideSrc>(), result.fileSize,
+ file.setSyncedTo<sideTrg>(result.fileSize,
result.modTime, //target time set from source
result.modTime,
result.targetFilePrint,
@@ -1956,15 +1946,15 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp)
}
break;
- case SO_COPY_METADATA_TO_LEFT:
- case SO_COPY_METADATA_TO_RIGHT:
+ case SO_RENAME_LEFT:
+ case SO_RENAME_RIGHT:
//harmonize with file_hierarchy.cpp::getSyncOpDescription!!
- reportItemInfo(txtUpdatingAttributes_, file.getAbstractPath<sideTrg>()); //throw ThreadStopRequest
+ reportInfo(replaceCpy(replaceCpy(txtRenamingFileXtoY_, L"%x", fmtPath(AFS::getDisplayPath(file.getAbstractPath<sideTrg>()))),
+ L"%y", fmtPath(file.getItemName<sideSrc>())), acb_); //throw ThreadStopRequest
{
AsyncItemStatReporter statReporter(1, 0, acb_);
- if (getUnicodeNormalForm(file.getItemName<sideTrg>()) !=
- getUnicodeNormalForm(file.getItemName<sideSrc>())) //have difference in case?
+ if (!file.hasEquivalentItemNames())
//already existing: undefined behavior! (e.g. fail/overwrite)
parallel::moveAndRenameItem(file.getAbstractPath<sideTrg>(), //throw FileError, (ErrorMoveUnsupported)
AFS::appendRelPath(file.parent().getAbstractPath<sideTrg>(), file.getItemName<sideSrc>()), singleThread_);
@@ -1982,7 +1972,7 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp)
//-> both sides *should* be completely equal now...
assert(file.getFileSize<sideTrg>() == file.getFileSize<sideSrc>());
- file.setSyncedTo<sideTrg>(file.getItemName<sideSrc>(), file.getFileSize<sideSrc>(),
+ file.setSyncedTo<sideTrg>(file.getFileSize<sideSrc>(),
file.getLastWriteTime <sideTrg>(),
file.getLastWriteTime <sideSrc>(),
file.getFilePrint <sideTrg>(),
@@ -2009,9 +1999,10 @@ void FolderPairSyncer::synchronizeLink(SymlinkPair& symlink) //throw FileError,
assert(isLocked(singleThread_));
const SyncOperation syncOp = symlink.getSyncOperation();
- if (std::optional<SelectSide> sideTrg = getTargetDirection(syncOp))
+ if (const SyncDirection syncDir = getEffectiveSyncDir(syncOp);
+ syncDir != SyncDirection::none)
{
- if (*sideTrg == SelectSide::left)
+ if (syncDir == SyncDirection::left)
synchronizeLinkInt<SelectSide::left>(symlink, syncOp);
else
synchronizeLinkInt<SelectSide::right>(symlink, syncOp);
@@ -2027,8 +2018,8 @@ void FolderPairSyncer::synchronizeLinkInt(SymlinkPair& symlink, SyncOperation sy
switch (syncOp)
{
- case SO_CREATE_NEW_LEFT:
- case SO_CREATE_NEW_RIGHT:
+ case SO_CREATE_LEFT:
+ case SO_CREATE_RIGHT:
{
if (auto parentFolder = dynamic_cast<const FolderPair*>(&symlink.parent()))
if (parentFolder->isEmpty<sideTrg>()) //BaseFolderPair OTOH is always non-empty and existing in this context => else: fatal error in zen::synchronize()
@@ -2045,8 +2036,7 @@ void FolderPairSyncer::synchronizeLinkInt(SymlinkPair& symlink, SyncOperation sy
statReporter.reportDelta(1, 0);
//update SymlinkPair
- symlink.setSyncedTo<sideTrg>(symlink.getItemName<sideSrc>(),
- symlink.getLastWriteTime<sideSrc>(), //target time set from source
+ symlink.setSyncedTo<sideTrg>(symlink.getLastWriteTime<sideSrc>(), //target time set from source
symlink.getLastWriteTime<sideSrc>());
}
@@ -2103,36 +2093,29 @@ void FolderPairSyncer::synchronizeLinkInt(SymlinkPair& symlink, SyncOperation sy
statReporter.reportDelta(1, 0); //we model "delete + copy" as ONE logical operation
//update SymlinkPair
- symlink.setSyncedTo<sideTrg>(symlink.getItemName<sideSrc>(),
- symlink.getLastWriteTime<sideSrc>(), //target time set from source
+ symlink.setSyncedTo<sideTrg>(symlink.getLastWriteTime<sideSrc>(), //target time set from source
symlink.getLastWriteTime<sideSrc>());
}
break;
- case SO_COPY_METADATA_TO_LEFT:
- case SO_COPY_METADATA_TO_RIGHT:
- reportItemInfo(txtUpdatingAttributes_, symlink.getAbstractPath<sideTrg>()); //throw ThreadStopRequest
+ case SO_RENAME_LEFT:
+ case SO_RENAME_RIGHT:
+ reportInfo(replaceCpy(replaceCpy(txtRenamingLinkXtoY_, L"%x", fmtPath(AFS::getDisplayPath(symlink.getAbstractPath<sideTrg>()))),
+ L"%y", fmtPath(symlink.getItemName<sideSrc>())), acb_); //throw ThreadStopRequest
{
AsyncItemStatReporter statReporter(1, 0, acb_);
- if (getUnicodeNormalForm(symlink.getItemName<sideTrg>()) !=
- getUnicodeNormalForm(symlink.getItemName<sideSrc>())) //have difference in case?
+ if (!symlink.hasEquivalentItemNames())
//already existing: undefined behavior! (e.g. fail/overwrite)
parallel::moveAndRenameItem(symlink.getAbstractPath<sideTrg>(), //throw FileError, (ErrorMoveUnsupported)
AFS::appendRelPath(symlink.parent().getAbstractPath<sideTrg>(), symlink.getItemName<sideSrc>()), singleThread_);
else
assert(false);
- //if (symlink.getLastWriteTime<sideTrg>() != symlink.getLastWriteTime<sideSrc>())
- // //- no need to call sameFileTime() or respect 2 second FAT/FAT32 precision in this comparison
- // //- do NOT read *current* source file time, but use buffered value which corresponds to time of comparison!
- // parallel::setModTimeSymlink(symlink.getAbstractPath<sideTrg>(), symlink.getLastWriteTime<sideSrc>()); //throw FileError
-
statReporter.reportDelta(1, 0);
//-> both sides *should* be completely equal now...
- symlink.setSyncedTo<sideTrg>(symlink.getItemName<sideSrc>(),
- symlink.getLastWriteTime<sideTrg>(), //target time set from source
+ symlink.setSyncedTo<sideTrg>(symlink.getLastWriteTime<sideTrg>(), //target time set from source
symlink.getLastWriteTime<sideSrc>());
}
break;
@@ -2156,9 +2139,10 @@ void FolderPairSyncer::synchronizeFolder(FolderPair& folder) //throw FileError,
assert(isLocked(singleThread_));
const SyncOperation syncOp = folder.getSyncOperation();
- if (std::optional<SelectSide> sideTrg = getTargetDirection(syncOp))
+ if (const SyncDirection syncDir = getEffectiveSyncDir(syncOp);
+ syncDir != SyncDirection::none)
{
- if (*sideTrg == SelectSide::left)
+ if (syncDir == SyncDirection::left)
synchronizeFolderInt<SelectSide::left>(folder, syncOp);
else
synchronizeFolderInt<SelectSide::right>(folder, syncOp);
@@ -2174,8 +2158,8 @@ void FolderPairSyncer::synchronizeFolderInt(FolderPair& folder, SyncOperation sy
switch (syncOp)
{
- case SO_CREATE_NEW_LEFT:
- case SO_CREATE_NEW_RIGHT:
+ case SO_CREATE_LEFT:
+ case SO_CREATE_RIGHT:
{
if (auto parentFolder = dynamic_cast<const FolderPair*>(&folder.parent()))
if (parentFolder->isEmpty<sideTrg>()) //BaseFolderPair OTOH is always non-empty and existing in this context => else: fatal error in zen::synchronize()
@@ -2209,8 +2193,7 @@ void FolderPairSyncer::synchronizeFolderInt(FolderPair& folder, SyncOperation sy
statReporter.reportDelta(1, 0);
//update FolderPair
- folder.setSyncedTo<sideTrg>(folder.getItemName<sideSrc>(),
- false, //isSymlinkTrg
+ folder.setSyncedTo<sideTrg>(false, //isSymlinkTrg
folder.isFollowedSymlink<sideSrc>());
}
else //source deleted meanwhile...
@@ -2253,32 +2236,30 @@ void FolderPairSyncer::synchronizeFolderInt(FolderPair& folder, SyncOperation sy
}
break;
- case SO_OVERWRITE_LEFT: //possible: e.g. manually-resolved dir-traversal conflict
- case SO_OVERWRITE_RIGHT: //
- case SO_COPY_METADATA_TO_LEFT:
- case SO_COPY_METADATA_TO_RIGHT:
- reportItemInfo(txtUpdatingAttributes_, folder.getAbstractPath<sideTrg>()); //throw ThreadStopRequest
+ case SO_RENAME_LEFT:
+ case SO_RENAME_RIGHT:
+ reportInfo(replaceCpy(replaceCpy(txtRenamingFolderXtoY_, L"%x", fmtPath(AFS::getDisplayPath(folder.getAbstractPath<sideTrg>()))),
+ L"%y", fmtPath(folder.getItemName<sideSrc>())), acb_); //throw ThreadStopRequest
{
AsyncItemStatReporter statReporter(1, 0, acb_);
- if (getUnicodeNormalForm(folder.getItemName<sideTrg>()) !=
- getUnicodeNormalForm(folder.getItemName<sideSrc>())) //have difference in case?
+ if (!folder.hasEquivalentItemNames())
//already existing: undefined behavior! (e.g. fail/overwrite)
parallel::moveAndRenameItem(folder.getAbstractPath<sideTrg>(), //throw FileError, (ErrorMoveUnsupported)
AFS::appendRelPath(folder.parent().getAbstractPath<sideTrg>(), folder.getItemName<sideSrc>()), singleThread_);
else
assert(false);
- //copyFileTimes -> useless: modification time changes with each child-object creation/deletion
statReporter.reportDelta(1, 0);
//-> both sides *should* be completely equal now...
- folder.setSyncedTo<sideTrg>(folder.getItemName<sideSrc>(),
- folder.isFollowedSymlink<sideTrg>(),
+ folder.setSyncedTo<sideTrg>(folder.isFollowedSymlink<sideTrg>(),
folder.isFollowedSymlink<sideSrc>());
}
break;
+ case SO_OVERWRITE_LEFT:
+ case SO_OVERWRITE_RIGHT:
case SO_MOVE_LEFT_FROM:
case SO_MOVE_RIGHT_FROM:
case SO_MOVE_LEFT_TO:
@@ -2415,7 +2396,7 @@ bool createBaseFolder(BaseFolderPair& baseFolder, bool copyFilePermissions, Phas
{
//create target directory: user presumably ignored warning "dir not yet existing" in order to have it created automatically
const AbstractPath folderPath = baseFolder.getAbstractPath<side>();
- static const SelectSide sideSrc = getOtherSide<side>;
+ constexpr SelectSide sideSrc = getOtherSide<side>;
const std::wstring errMsg = tryReportingError([&]
{
@@ -2509,7 +2490,7 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime
//-------------------execute basic checks all at once BEFORE starting sync--------------------------------------
std::vector<unsigned char /*we really want bool*/> skipFolderPair(folderCmp.size(), false); //folder pairs may be skipped after fatal errors were found
- std::vector<std::tuple<const BaseFolderPair*, int /*conflict count*/, std::vector<SyncStatistics::ConflictInfo>>> checkUnresolvedConflicts;
+ std::vector<std::tuple<const BaseFolderPair*, int /*conflict count*/, std::vector<std::wstring>>> checkUnresolvedConflicts;
std::vector<std::tuple<const BaseFolderPair*, SelectSide, bool /*write access*/>> checkBaseFolderRaceCondition;
@@ -2664,7 +2645,7 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime
if (!checkUnresolvedConflicts.empty())
{
//distribute CONFLICTS_PREVIEW_MAX over all pairs, not *per* pair, or else log size with many folder pairs can blow up!
- std::vector<std::vector<SyncStatistics::ConflictInfo>> conflictPreviewTrim(checkUnresolvedConflicts.size());
+ std::vector<std::vector<std::wstring>> conflictPreviewTrim(checkUnresolvedConflicts.size());
size_t previewRemain = CONFLICTS_PREVIEW_MAX;
for (size_t i = 0; ; ++i)
@@ -2696,8 +2677,8 @@ break2:
AFS::getDisplayPath(baseFolder->getAbstractPath<SelectSide::left >()) + L" <-> " +
AFS::getDisplayPath(baseFolder->getAbstractPath<SelectSide::right>());
- for (const SyncStatistics::ConflictInfo& item : *itPrevi)
- msg += L'\n' + utfTo<std::wstring>(item.relPath) + L": " + item.msg;
+ for (const std::wstring& conflictMsg : *itPrevi)
+ msg += L'\n' + conflictMsg;
if (makeUnsigned(conflictCount) > itPrevi->size())
msg += L"\n [...] " + replaceCpy(_P("Showing %y of 1 item", "Showing %y of %x items", conflictCount), //%x used as plural form placeholder!
@@ -2925,7 +2906,7 @@ break2:
});
//guarantee removal of invalid entries (where element is empty on both sides)
- ZEN_ON_SCOPE_EXIT(BaseFolderPair::removeEmpty(baseFolder));
+ ZEN_ON_SCOPE_EXIT(baseFolder.removeDoubleEmpty());
bool copyPermissionsFp = false;
tryReportingError([&]
diff --git a/FreeFileSync/Source/base/synchronization.h b/FreeFileSync/Source/base/synchronization.h
index 0e790f40..063563bc 100644
--- a/FreeFileSync/Source/base/synchronization.h
+++ b/FreeFileSync/Source/base/synchronization.h
@@ -20,7 +20,7 @@ class SyncStatistics //count *logical* operations, (create, update, delete + byt
//-> note the fundamental difference compared to counting disk accesses!
public:
explicit SyncStatistics(const FolderComparison& folderCmp);
- explicit SyncStatistics(const ContainerObject& hierObj);
+ explicit SyncStatistics(const ContainerObject& conObj);
explicit SyncStatistics(const FilePair& file);
template <SelectSide side>
@@ -38,16 +38,12 @@ public:
int64_t getBytesToProcess() const { return bytesToProcess_; }
size_t rowCount () const { return rowsTotal_; }
- struct ConflictInfo
- {
- Zstring relPath;
- std::wstring msg;
- };
- const std::vector<ConflictInfo>& getConflictsPreview() const { return conflictsPreview_; }
+ const std::vector<std::wstring>& getConflictsPreview() const { return conflictsPreview_; }
int conflictCount() const { return conflictCount_; }
private:
- void recurse(const ContainerObject& hierObj);
+ void recurse(const ContainerObject& conObj);
+ void logConflict(const FileSystemObject& fsObj);
void processFile (const FilePair& file);
void processLink (const SymlinkPair& symlink);
@@ -64,7 +60,7 @@ private:
size_t rowsTotal_ = 0;
int conflictCount_ = 0;
- std::vector<ConflictInfo> conflictsPreview_; //conflict texts to display as a warning message
+ std::vector<std::wstring> conflictsPreview_; //conflict texts to display as a warning message
//limit conflict count! e.g. there may be hundred thousands of "same date but a different size"
};
diff --git a/FreeFileSync/Source/base/versioning.cpp b/FreeFileSync/Source/base/versioning.cpp
index c3822d65..3417bebc 100644
--- a/FreeFileSync/Source/base/versioning.cpp
+++ b/FreeFileSync/Source/base/versioning.cpp
@@ -282,8 +282,11 @@ void FileVersioner::revisionFolderImpl(const AbstractPath& folderPath, const Zst
for (const AFS::FileInfo& fileInfo : files)
{
- const FileDescriptor fileDescr{AFS::appendRelPath(folderPath, fileInfo.itemName),
- FileAttributes(fileInfo.modTime, fileInfo.fileSize, fileInfo.filePrint, false /*isFollowedSymlink*/)};
+ const FileDescriptor fileDescr
+ {
+ .path = AFS::appendRelPath(folderPath, fileInfo.itemName),
+ .attr = {fileInfo.modTime, fileInfo.fileSize, fileInfo.filePrint, false /*isFollowedSymlink*/},
+ };
revisionFileImpl(fileDescr, appendPath(relPath, fileInfo.itemName), onBeforeFileMove, notifyUnbufferedIO); //throw FileError, X
}
diff --git a/FreeFileSync/Source/config.cpp b/FreeFileSync/Source/config.cpp
index 45c20daa..884be499 100644
--- a/FreeFileSync/Source/config.cpp
+++ b/FreeFileSync/Source/config.cpp
@@ -25,7 +25,7 @@ namespace
{
//-------------------------------------------------------------------------------------------------------------------------------
const int XML_FORMAT_GLOBAL_CFG = 27; //2023-05-13
-const int XML_FORMAT_SYNC_CFG = 19; //2023-06-09
+const int XML_FORMAT_SYNC_CFG = 23; //2023-08-24
//-------------------------------------------------------------------------------------------------------------------------------
}
@@ -703,44 +703,6 @@ bool readText(const std::string& input, VersioningStyle& value)
template <> inline
-void writeText(const SyncVariant& value, std::string& output)
-{
- switch (value)
- {
- case SyncVariant::twoWay:
- output = "TwoWay";
- break;
- case SyncVariant::mirror:
- output = "Mirror";
- break;
- case SyncVariant::update:
- output = "Update";
- break;
- case SyncVariant::custom:
- output = "Custom";
- break;
- }
-}
-
-template <> inline
-bool readText(const std::string& input, SyncVariant& value)
-{
- const std::string tmp = trimCpy(input);
- if (tmp == "TwoWay")
- value = SyncVariant::twoWay;
- else if (tmp == "Mirror")
- value = SyncVariant::mirror;
- else if (tmp == "Update")
- value = SyncVariant::update;
- else if (tmp == "Custom")
- value = SyncVariant::custom;
- else
- return false;
- return true;
-}
-
-
-template <> inline
void writeStruc(const ColAttributesRim& value, XmlElement& output)
{
output.setAttribute("Type", value.type);
@@ -1010,30 +972,100 @@ void readConfig(const XmlIn& in, CompConfig& cmpCfg)
}
-void readConfig(const XmlIn& in, SyncDirectionConfig& dirCfg)
+void readConfig(const XmlIn& in, SyncDirectionConfig& dirCfg, int formatVer)
{
- in["Variant"](dirCfg.var);
-
- if (dirCfg.var == SyncVariant::custom)
+ if (formatVer < 21) //TODO: remove if parameter migration after some time! 2023-08-09
{
- XmlIn inCustDir = in["CustomDirections"];
- inCustDir["LeftOnly" ](dirCfg.custom.exLeftSideOnly);
- inCustDir["RightOnly" ](dirCfg.custom.exRightSideOnly);
- inCustDir["LeftNewer" ](dirCfg.custom.leftNewer);
- inCustDir["RightNewer"](dirCfg.custom.rightNewer);
- inCustDir["Different" ](dirCfg.custom.different);
- inCustDir["Conflict" ](dirCfg.custom.conflict);
+ std::string varName;
+ in["Variant"](varName);
+ trim(varName);
+
+ if (varName == "TwoWay")
+ dirCfg = getDefaultSyncCfg(SyncVariant::twoWay);
+ else if (varName == "Mirror")
+ {
+ dirCfg = getDefaultSyncCfg(SyncVariant::mirror);
+
+ bool detectMovedFiles = false;
+ in["DetectMovedFiles"](detectMovedFiles);
+ if (detectMovedFiles)
+ {
+ if (const DirectionByDiff* diffDirs = std::get_if<DirectionByDiff>(&dirCfg.dirs))
+ dirCfg.dirs = getChangesDirDefault(*diffDirs); //convert to "changes"-based mirror, so that move detection is enabled
+ else assert(false);
+ }
+ }
+ else if (varName == "Update")
+ dirCfg.dirs = DirectionByDiff
+ {
+ .leftOnly = SyncDirection::right,
+ .rightOnly = SyncDirection::none,
+ .leftNewer = SyncDirection::right,
+ .rightNewer = SyncDirection::none, //note: will be fixed below for CompareVariant::content/size
+ };
+ else
+ {
+ assert(varName == "Custom");
+
+ dirCfg.dirs = DirectionByDiff();
+
+ XmlIn inCustDir = in["CustomDirections"];
+ inCustDir["LeftOnly" ](std::get<DirectionByDiff>(dirCfg.dirs).leftOnly);
+ inCustDir["RightOnly" ](std::get<DirectionByDiff>(dirCfg.dirs).rightOnly);
+ inCustDir["LeftNewer" ](std::get<DirectionByDiff>(dirCfg.dirs).leftNewer); //note: will be fixed below for CompareVariant::content/size
+ inCustDir["RightNewer"](std::get<DirectionByDiff>(dirCfg.dirs).rightNewer); //
+ }
}
- //else
- // dirCfg.custom = DirectionSet();
+ else if (formatVer < 22) //TODO: remove if parameter migration after some time! 2023-08-20
+ {
+ warn_static("formatVer == 21 was development-internal => get rid, once the betas are out of likely use")
- in["DetectMovedFiles"](dirCfg.detectMovedFiles);
+ if (XmlIn inDir = in["Differences"])
+ {
+ dirCfg.dirs = DirectionByDiff();
+ inDir["LeftOnly" ](std::get<DirectionByDiff>(dirCfg.dirs).leftOnly);
+ inDir["RightOnly" ](std::get<DirectionByDiff>(dirCfg.dirs).rightOnly);
+ inDir["LeftNewer" ](std::get<DirectionByDiff>(dirCfg.dirs).leftNewer);
+ inDir["RightNewer"](std::get<DirectionByDiff>(dirCfg.dirs).rightNewer);
+ }
+ else
+ {
+ assert(in["Changes"]);
+ dirCfg = getDefaultSyncCfg(SyncVariant::twoWay);
+ }
+ }
+ else
+ {
+ if (XmlIn inDirs = in["Differences"])
+ {
+ dirCfg.dirs = DirectionByDiff();
+ inDirs.attribute("LeftOnly", std::get<DirectionByDiff>(dirCfg.dirs).leftOnly);
+ inDirs.attribute("LeftNewer", std::get<DirectionByDiff>(dirCfg.dirs).leftNewer);
+ inDirs.attribute("RightNewer", std::get<DirectionByDiff>(dirCfg.dirs).rightNewer);
+ inDirs.attribute("RightOnly", std::get<DirectionByDiff>(dirCfg.dirs).rightOnly);
+ }
+ else
+ {
+ assert(in["Changes"]);
+ dirCfg.dirs = DirectionByChange();
+
+ XmlIn inDirsL = in["Changes"]["Left"];
+ inDirsL.attribute("Create", std::get<DirectionByChange>(dirCfg.dirs).left.create);
+ inDirsL.attribute("Update", std::get<DirectionByChange>(dirCfg.dirs).left.update);
+ inDirsL.attribute("Delete", std::get<DirectionByChange>(dirCfg.dirs).left.delete_);
+
+ XmlIn inDirsR = in["Changes"]["Right"];
+ inDirsR.attribute("Create", std::get<DirectionByChange>(dirCfg.dirs).right.create);
+ inDirsR.attribute("Update", std::get<DirectionByChange>(dirCfg.dirs).right.update);
+ inDirsR.attribute("Delete", std::get<DirectionByChange>(dirCfg.dirs).right.delete_);
+ }
+ }
}
void readConfig(const XmlIn& in, SyncConfig& syncCfg, std::map<AfsDevice, size_t>& deviceParallelOps, int formatVer)
{
- readConfig(in, syncCfg.directionCfg);
+ readConfig(in, syncCfg.directionCfg, formatVer);
in["DeletionPolicy" ](syncCfg.deletionVariant);
in["VersioningFolder"](syncCfg.versioningFolderPhrase);
@@ -1142,10 +1174,50 @@ void readConfig(const XmlIn& in, MainConfiguration& mainCfg, int formatVer)
else
in["Notes"](mainCfg.notes);
+
+
+ warn_static("wxwidgets: disable wxUSE_EXCEPTIONS so that we get proper exception stack locations")
+#if 0 //e.g.
+ std::get<DirectionByDiff>(mainCfg.syncCfg.directionCfg.dirs);
+ warn_static("why is std::bad_variant_access caught somewhere in wxEntry when thrown at this location!? ")
+#endif
+
+
readConfig(in["Compare"], mainCfg.cmpCfg);
readConfig(in["Synchronize"], mainCfg.syncCfg, mainCfg.deviceParallelOps, formatVer);
+ if (formatVer < 20) //TODO: remove if parameter migration after some time! 2023-08-09
+ if (mainCfg.cmpCfg.compareVar == CompareVariant::content ||
+ mainCfg.cmpCfg.compareVar == CompareVariant::size)
+ if (std::string varName;
+ in["Synchronize"]["Variant"](varName))
+ {
+ if (varName == "Update")
+ std::get<DirectionByDiff>(mainCfg.syncCfg.directionCfg.dirs).rightNewer = SyncDirection::right;
+ else if (varName == "Custom")
+ {
+ SyncDirection different = SyncDirection::none;
+ in["Synchronize"]["CustomDirections"]["Different"](different);
+
+ std::get<DirectionByDiff>(mainCfg.syncCfg.directionCfg.dirs).leftNewer =
+ std::get<DirectionByDiff>(mainCfg.syncCfg.directionCfg.dirs).rightNewer = different;
+ }
+ }
+
+ if (formatVer < 23) //TODO: remove if parameter migration after some time! 2023-08-24
+ {
+ bool detectMovedFiles = false;
+ in["Synchronize"]["DetectMovedFiles"](detectMovedFiles);
+ if (detectMovedFiles)
+ if (getSyncVariant(mainCfg.syncCfg.directionCfg) == SyncVariant::mirror)
+ {
+ if (const DirectionByDiff* diffDirs = std::get_if<DirectionByDiff>(&mainCfg.syncCfg.directionCfg.dirs))
+ mainCfg.syncCfg.directionCfg.dirs = getChangesDirDefault(*diffDirs); //convert to "changes"-based mirror, so that move detection is enabled
+ else assert(false);
+ }
+ }
+
readConfig(in["Filter"], mainCfg.globalFilter);
//###########################################################
@@ -1158,6 +1230,40 @@ void readConfig(const XmlIn& in, MainConfiguration& mainCfg, int formatVer)
LocalPairConfig lpc;
readConfig(inPair, lpc, mainCfg.deviceParallelOps, formatVer);
+ if (formatVer < 20) //TODO: remove if parameter migration after some time! 2023-08-09
+ if (lpc.localSyncCfg)
+ {
+ const CompConfig& cmpCfg = lpc.localCmpCfg ? *lpc.localCmpCfg : mainCfg.cmpCfg;
+ if (cmpCfg.compareVar == CompareVariant::content ||
+ cmpCfg.compareVar == CompareVariant::size)
+ if (std::string varName;
+ inPair["Synchronize"]["Variant"](varName))
+ if (varName == "Update")
+ std::get<DirectionByDiff>(lpc.localSyncCfg->directionCfg.dirs).rightNewer = SyncDirection::right;
+ else if (varName == "Custom")
+ if (inPair["Synchronize"]["CustomDirections"]["Different"])
+ {
+ SyncDirection different = SyncDirection::none;
+ inPair["Synchronize"]["CustomDirections"]["Different"](different);
+
+ std::get<DirectionByDiff>(lpc.localSyncCfg->directionCfg.dirs).leftNewer =
+ std::get<DirectionByDiff>(lpc.localSyncCfg->directionCfg.dirs).rightNewer = different;
+ }
+ }
+ if (formatVer < 23) //TODO: remove if parameter migration after some time! 2023-08-24
+ if (lpc.localSyncCfg)
+ {
+ bool detectMovedFiles = false;
+ inPair["Synchronize"]["DetectMovedFiles"](detectMovedFiles);
+ if (detectMovedFiles)
+ if (getSyncVariant(lpc.localSyncCfg->directionCfg) == SyncVariant::mirror)
+ {
+ if (const DirectionByDiff* diffDirs = std::get_if<DirectionByDiff>(&lpc.localSyncCfg->directionCfg.dirs))
+ lpc.localSyncCfg->directionCfg.dirs = getChangesDirDefault(*diffDirs); //convert to "changes"-based mirror, so that move detection is enabled
+ else assert(false);
+ }
+ }
+
if (firstItem)
{
firstItem = false;
@@ -1875,20 +1981,28 @@ void writeConfig(const CompConfig& cmpCfg, XmlOut& out)
void writeConfig(const SyncDirectionConfig& dirCfg, XmlOut& out)
{
- out["Variant"](dirCfg.var);
-
- if (dirCfg.var == SyncVariant::custom)
+ if (const DirectionByDiff* diffDirs = std::get_if<DirectionByDiff>(&dirCfg.dirs))
{
- XmlOut outCustDir = out["CustomDirections"];
- outCustDir["LeftOnly" ](dirCfg.custom.exLeftSideOnly);
- outCustDir["RightOnly" ](dirCfg.custom.exRightSideOnly);
- outCustDir["LeftNewer" ](dirCfg.custom.leftNewer);
- outCustDir["RightNewer"](dirCfg.custom.rightNewer);
- outCustDir["Different" ](dirCfg.custom.different);
- outCustDir["Conflict" ](dirCfg.custom.conflict);
+ XmlOut outDirs = out["Differences"];
+ outDirs.attribute("LeftOnly", diffDirs->leftOnly);
+ outDirs.attribute("LeftNewer", diffDirs->leftNewer);
+ outDirs.attribute("RightNewer", diffDirs->rightNewer);
+ outDirs.attribute("RightOnly", diffDirs->rightOnly);
}
+ else
+ {
+ const DirectionByChange& changeDirs = std::get<DirectionByChange>(dirCfg.dirs);
- out["DetectMovedFiles"](dirCfg.detectMovedFiles);
+ XmlOut outDirsL = out["Changes"]["Left"];
+ outDirsL.attribute("Create", changeDirs.left.create);
+ outDirsL.attribute("Update", changeDirs.left.update);
+ outDirsL.attribute("Delete", changeDirs.left.delete_);
+
+ XmlOut outDirsR = out["Changes"]["Right"];
+ outDirsR.attribute("Create", changeDirs.right.create);
+ outDirsR.attribute("Update", changeDirs.right.update);
+ outDirsR.attribute("Delete", changeDirs.right.delete_);
+ }
}
diff --git a/FreeFileSync/Source/icon_buffer.cpp b/FreeFileSync/Source/icon_buffer.cpp
index f4851b06..f8325a3b 100644
--- a/FreeFileSync/Source/icon_buffer.cpp
+++ b/FreeFileSync/Source/icon_buffer.cpp
@@ -165,12 +165,12 @@ public:
std::lock_guard dummy(lockIconList_);
//thread safety: moving ImageHolder is free from side effects, but ~wxImage() is NOT! => do NOT delete items from iconList here!
- auto rc = iconList.emplace(filePath, IconData());
- assert(rc.second); //insertion took place
- if (rc.second)
+ const auto [it, inserted] = iconList.try_emplace(filePath);
+ assert(inserted);
+ if (inserted)
{
- refData(rc.first).iconHolder = std::move(ih);
- priorityListPushBack(rc.first);
+ refData(it).iconHolder = std::move(ih);
+ priorityListPushBack(it);
}
}
diff --git a/FreeFileSync/Source/localization.cpp b/FreeFileSync/Source/localization.cpp
index 7c6fa18a..0ca0d841 100644
--- a/FreeFileSync/Source/localization.cpp
+++ b/FreeFileSync/Source/localization.cpp
@@ -306,9 +306,6 @@ wxLayoutDirection globalLayoutDir = wxLayout_Default;
void fff::localizationInit(const Zstring& zipPath) //throw FileError
{
- assert(globalTranslations.empty());
- globalTranslations = loadTranslations(zipPath); //throw FileError
-
/* wxLocale vs wxUILocale (since wxWidgets 3.1.6)
------------------------------------------|--------------------
calls setlocale() Windows, Linux, maCOS | Linux only
@@ -336,6 +333,11 @@ void fff::localizationInit(const Zstring& zipPath) //throw FileError
assert(!wxTranslations::Get());
wxTranslations::Set(new wxTranslations() /*pass ownership*/); //implicitly done by wxLocale, but *not* wxUILocale
+ //throw *after* mandatory initialization: setLanguage() requires wxTranslations::Get()!
+
+ assert(globalTranslations.empty());
+ globalTranslations = loadTranslations(zipPath); //throw FileError
+
setLanguage(getDefaultLanguage()); //throw FileError
}
diff --git a/FreeFileSync/Source/parse_lng.h b/FreeFileSync/Source/parse_lng.h
index 2c1a7adb..3614db2a 100644
--- a/FreeFileSync/Source/parse_lng.h
+++ b/FreeFileSync/Source/parse_lng.h
@@ -10,7 +10,6 @@
#include <unordered_map>
#include <unordered_set>
#include <zen/utf.h>
-#include <zen/ring_buffer.h>
#include "parse_plural.h"
@@ -80,34 +79,20 @@ template<> struct std::hash<lng::SingularPluralPair>
namespace lng
{
-enum class TranslationNewItemPos
-{
- rel,
- top
-};
-
class TranslationUnorderedList //unordered list of unique translation items
{
public:
- TranslationUnorderedList(TranslationNewItemPos newItemPos, TranslationMap&& transOld, TranslationPluralMap&& transPluralOld) :
- newItemPos_(newItemPos), transOld_(std::move(transOld)), transPluralOld_(std::move(transPluralOld)) {}
+ TranslationUnorderedList(TranslationMap&& transOld, TranslationPluralMap&& transPluralOld) :
+ transOld_(std::move(transOld)), transPluralOld_(std::move(transPluralOld)) {}
void addItem(const std::string& orig)
{
if (!transUnique_.insert(orig).second) return;
auto it = transOld_.find(orig);
if (it != transOld_.end() && !it->second.empty()) //preserve old translation from .lng file if existing
- sequence_.push_back(std::make_shared<RegularItem>(std::make_pair(orig, it->second)));
+ sequence_.push_back(std::make_shared<SingularItem>(std::make_pair(orig, it->second)));
else
- switch (newItemPos_)
- {
- case TranslationNewItemPos::rel:
- sequence_.push_back(std::make_shared<RegularItem>(std::make_pair(orig, std::string())));
- break;
- case TranslationNewItemPos::top:
- sequence_.push_front(std::make_shared<RegularItem>(std::make_pair(orig, std::string()))); //put untranslated items to the front of the .lng filebreak;
- break;
- }
+ sequence_.push_back(std::make_shared<SingularItem>(std::make_pair(orig, std::string())));
}
void addItem(const SingularPluralPair& orig)
@@ -117,15 +102,7 @@ public:
if (it != transPluralOld_.end() && !it->second.empty()) //preserve old translation from .lng file if existing
sequence_.push_back(std::make_shared<PluralItem>(std::make_pair(orig, it->second)));
else
- switch (newItemPos_)
- {
- case TranslationNewItemPos::rel:
- sequence_.push_back(std::make_shared<PluralItem>(std::make_pair(orig, PluralForms())));
- break;
- case TranslationNewItemPos::top:
- sequence_.push_front(std::make_shared<PluralItem>(std::make_pair(orig, PluralForms()))); //put untranslated items to the front of the .lng file
- break;
- }
+ sequence_.push_back(std::make_shared<PluralItem>(std::make_pair(orig, PluralForms())));
}
bool untranslatedTextExists() const { return std::any_of(sequence_.begin(), sequence_.end(), [](const std::shared_ptr<Item>& item) { return !item->hasTranslation(); }); }
@@ -134,7 +111,7 @@ public:
void visitItems(Function onTrans, Function2 onPluralTrans) const //onTrans takes (const TranslationMap::value_type&), onPluralTrans takes (const TranslationPluralMap::value_type&)
{
for (const std::shared_ptr<Item>& item : sequence_)
- if (auto regular = dynamic_cast<const RegularItem*>(item.get()))
+ if (auto regular = dynamic_cast<const SingularItem*>(item.get()))
onTrans(regular->value);
else if (auto plural = dynamic_cast<const PluralItem*>(item.get()))
onPluralTrans(plural->value);
@@ -143,11 +120,22 @@ public:
private:
struct Item { virtual ~Item() {} virtual bool hasTranslation() const = 0; };
- struct RegularItem : public Item { RegularItem(const TranslationMap ::value_type& val) : value(val) {} bool hasTranslation() const override { return !value.second.empty(); } TranslationMap ::value_type value; };
- struct PluralItem : public Item { PluralItem (const TranslationPluralMap::value_type& val) : value(val) {} bool hasTranslation() const override { return !value.second.empty(); } TranslationPluralMap::value_type value; };
+
+ struct SingularItem : public Item
+ {
+ explicit SingularItem(const TranslationMap::value_type& val) : value(val) {}
+ bool hasTranslation() const override { return !value.second.empty(); }
+ TranslationMap::value_type value;
+ };
- const TranslationNewItemPos newItemPos_;
- zen::RingBuffer<std::shared_ptr<Item>> sequence_; //ordered list of translation elements
+ struct PluralItem : public Item
+ {
+ explicit PluralItem(const TranslationPluralMap::value_type& val) : value(val) {}
+ bool hasTranslation() const override { return !value.second.empty(); }
+ TranslationPluralMap::value_type value;
+ };
+
+ std::vector<std::shared_ptr<Item>> sequence_; //ordered list of translation elements
std::unordered_set<TranslationMap ::key_type> transUnique_; //check uniqueness
std::unordered_set<TranslationPluralMap::key_type> pluralUnique_; //
@@ -375,7 +363,7 @@ public:
consumeToken(TokenType::localeBegin); //throw ParsingError
header.locale = token().text;
- consumeToken(TokenType::text); //throw ParsingError
+ consumeToken(TokenType::text); //throw ParsingError
consumeToken(TokenType::localeEnd); //
consumeToken(TokenType::flagFileBegin); //throw ParsingError
@@ -418,7 +406,7 @@ private:
validateTranslation(original, translation); //throw ParsingError
consumeToken(TokenType::trgEnd); //
- out.emplace(original, translation);
+ out.emplace(original, std::move(translation));
}
void parsePlural(TranslationPluralMap& pluralOut, const plural::PluralFormInfo& pluralInfo)
@@ -452,7 +440,7 @@ private:
validateTranslation(original, pluralList, pluralInfo);
consumeToken(TokenType::trgEnd); //throw ParsingError
- pluralOut.emplace(original, pluralList);
+ pluralOut.emplace(original, std::move(pluralList));
}
void validateTranslation(const std::string& original, const std::string& translation) //throw ParsingError
@@ -740,25 +728,30 @@ void formatMultiLineText(std::string& text)
inline
-std::string generateLng(const TranslationUnorderedList& in, const TransHeader& header)
+std::string generateLng(const TranslationUnorderedList& in, const TransHeader& header, bool untranslatedToTop)
{
const KnownTokens tokens; //no need for static non-POD!
- std::string out;
- //header
- out += tokens.text(TokenType::headerBegin) + '\n';
+ std::string headerLines;
+ headerLines += tokens.text(TokenType::headerBegin) + '\n';
- out += '\t' + tokens.text(TokenType::langNameBegin) + header.languageName + tokens.text(TokenType::langNameEnd) + '\n';
- out += '\t' + tokens.text(TokenType::transNameBegin) + header.translatorName + tokens.text(TokenType::transNameEnd) + '\n';
- out += '\t' + tokens.text(TokenType::localeBegin) + header.locale + tokens.text(TokenType::localeEnd) + '\n';
- out += '\t' + tokens.text(TokenType::flagFileBegin) + header.flagFile + tokens.text(TokenType::flagFileEnd) + '\n';
- out += '\t' + tokens.text(TokenType::pluralCountBegin) + zen::numberTo<std::string>(header.pluralCount) + tokens.text(TokenType::pluralCountEnd) + '\n';
- out += '\t' + tokens.text(TokenType::pluralDefBegin) + header.pluralDefinition + tokens.text(TokenType::pluralDefEnd) + '\n';
+ headerLines += '\t' + tokens.text(TokenType::langNameBegin) + header.languageName + tokens.text(TokenType::langNameEnd) + '\n';
+ headerLines += '\t' + tokens.text(TokenType::transNameBegin) + header.translatorName + tokens.text(TokenType::transNameEnd) + '\n';
+ headerLines += '\t' + tokens.text(TokenType::localeBegin) + header.locale + tokens.text(TokenType::localeEnd) + '\n';
+ headerLines += '\t' + tokens.text(TokenType::flagFileBegin) + header.flagFile + tokens.text(TokenType::flagFileEnd) + '\n';
+ headerLines += '\t' + tokens.text(TokenType::pluralCountBegin) + zen::numberTo<std::string>(header.pluralCount) + tokens.text(TokenType::pluralCountEnd) + '\n';
+ headerLines += '\t' + tokens.text(TokenType::pluralDefBegin) + header.pluralDefinition + tokens.text(TokenType::pluralDefEnd) + '\n';
- out += tokens.text(TokenType::headerEnd) + "\n\n";
+ headerLines += tokens.text(TokenType::headerEnd) + "\n\n";
+
+
+ std::string topLines; //untranslated items first?
+ std::string mainLines;
in.visitItems([&](const TranslationMap::value_type& trans)
{
+ std::string& out = untranslatedToTop && trans.second.empty() ? topLines : mainLines;
+
std::string original = trans.first;
std::string translation = trans.second;
@@ -770,6 +763,8 @@ std::string generateLng(const TranslationUnorderedList& in, const TransHeader& h
},
[&](const TranslationPluralMap::value_type& transPlural)
{
+ std::string& out = untranslatedToTop && transPlural.second.empty() ? topLines : mainLines;
+
std::string engSingular = transPlural.first.first;
std::string engPlural = transPlural.first.second;
const PluralForms& forms = transPlural.second;
@@ -793,8 +788,9 @@ std::string generateLng(const TranslationUnorderedList& in, const TransHeader& h
out += tokens.text(TokenType::trgEnd) + "\n\n";
});
- assert(!zen::contains(out, "\r\n") && !zen::contains(out, "\r"));
- return zen::replaceCpy(out, '\n', "\r\n"); //back to win line endings
+ std::string output = headerLines + topLines + mainLines;
+ assert(!zen::contains(output, "\r\n") && !zen::contains(output, "\r"));
+ return zen::replaceCpy(output, '\n', "\r\n"); //back to Windows line endings
}
}
diff --git a/FreeFileSync/Source/ui/cfg_grid.cpp b/FreeFileSync/Source/ui/cfg_grid.cpp
index 859c4044..4078a5c0 100644
--- a/FreeFileSync/Source/ui/cfg_grid.cpp
+++ b/FreeFileSync/Source/ui/cfg_grid.cpp
@@ -337,7 +337,7 @@ public:
static int getRowDefaultHeight(const Grid& grid)
{
- return std::max(getDefaultMenuIconSize(), grid.getMainWin().GetCharHeight()) + fastFromDIP(1) /*extra space*/;
+ return std::max(getDefaultMenuIconSize(), grid.getMainWin().GetCharHeight()) + fastFromDIP(1) /*extra space*/;
}
int getSyncOverdueDays() const { return syncOverdueDays_; }
diff --git a/FreeFileSync/Source/ui/file_grid.cpp b/FreeFileSync/Source/ui/file_grid.cpp
index ebadc892..50089617 100644
--- a/FreeFileSync/Source/ui/file_grid.cpp
+++ b/FreeFileSync/Source/ui/file_grid.cpp
@@ -102,43 +102,36 @@ wxColor getDefaultBackgroundColorAlternating(bool wantStandardColor)
}
-enum class CudAction
-{
- doNothing,
- create,
- update,
- destroy,
-};
std::pair<CudAction, SelectSide> getCudAction(SyncOperation so)
{
switch (so)
{
//*INDENT-OFF*
- case SO_CREATE_NEW_LEFT:
+ case SO_CREATE_LEFT:
case SO_MOVE_LEFT_TO: return {CudAction::create, SelectSide::left};
- case SO_CREATE_NEW_RIGHT:
+ case SO_CREATE_RIGHT:
case SO_MOVE_RIGHT_TO: return {CudAction::create, SelectSide::right};
case SO_DELETE_LEFT:
- case SO_MOVE_LEFT_FROM: return {CudAction::destroy, SelectSide::left};
+ case SO_MOVE_LEFT_FROM: return {CudAction::delete_, SelectSide::left};
case SO_DELETE_RIGHT:
- case SO_MOVE_RIGHT_FROM: return {CudAction::destroy, SelectSide::right};
+ case SO_MOVE_RIGHT_FROM: return {CudAction::delete_, SelectSide::right};
case SO_OVERWRITE_LEFT:
- case SO_COPY_METADATA_TO_LEFT: return {CudAction::update, SelectSide::left};
+ case SO_RENAME_LEFT: return {CudAction::update, SelectSide::left};
case SO_OVERWRITE_RIGHT:
- case SO_COPY_METADATA_TO_RIGHT: return {CudAction::update, SelectSide::right};
+ case SO_RENAME_RIGHT: return {CudAction::update, SelectSide::right};
case SO_DO_NOTHING:
case SO_EQUAL:
- case SO_UNRESOLVED_CONFLICT: return {CudAction::doNothing, SelectSide::left};
+ case SO_UNRESOLVED_CONFLICT: return {CudAction::noChange, SelectSide::left};
//*INDENT-ON*
}
assert(false);
- return {CudAction::doNothing, SelectSide::left};
+ return {CudAction::noChange, SelectSide::left};
}
@@ -146,20 +139,20 @@ wxColor getBackGroundColorSyncAction(SyncOperation so)
{
switch (so)
{
- case SO_CREATE_NEW_LEFT:
+ case SO_CREATE_LEFT:
case SO_OVERWRITE_LEFT:
case SO_DELETE_LEFT:
case SO_MOVE_LEFT_FROM:
case SO_MOVE_LEFT_TO:
- case SO_COPY_METADATA_TO_LEFT:
+ case SO_RENAME_LEFT:
return getColorSyncBlue(false /*faint*/);
- case SO_CREATE_NEW_RIGHT:
+ case SO_CREATE_RIGHT:
case SO_OVERWRITE_RIGHT:
case SO_DELETE_RIGHT:
case SO_MOVE_RIGHT_FROM:
case SO_MOVE_RIGHT_TO:
- case SO_COPY_METADATA_TO_RIGHT:
+ case SO_RENAME_RIGHT:
return getColorSyncGreen(false /*faint*/);
case SO_DO_NOTHING:
@@ -178,19 +171,19 @@ wxColor getBackGroundColorCmpDifference(CompareFileResult cmpResult)
switch (cmpResult)
{
//*INDENT-OFF*
- case FILE_LEFT_SIDE_ONLY: return getColorSyncBlue(false /*faint*/);
+ case FILE_EQUAL:
+ break; //usually white
+ case FILE_LEFT_ONLY: return getColorSyncBlue(false /*faint*/);
case FILE_LEFT_NEWER: return getColorSyncBlue(true /*faint*/);
- case FILE_RIGHT_SIDE_ONLY: return getColorSyncGreen(false /*faint*/);
+ case FILE_RIGHT_ONLY: return getColorSyncGreen(false /*faint*/);
case FILE_RIGHT_NEWER: return getColorSyncGreen(true /*faint*/);
case FILE_DIFFERENT_CONTENT:
return getColorDifferentBackground(false /*faint*/);
- case FILE_EQUAL:
- break; //usually white
-
+ case FILE_RENAMED: //similar to both "equal" and "conflict": give hint via background color
+ case FILE_TIME_INVALID:
case FILE_CONFLICT:
- case FILE_DIFFERENT_METADATA: //= sub-category of equal, but hint via background that sync direction follows conflict-setting
return getColorConflictBackground(false /*faint*/);
//*INDENT-ON*
}
@@ -348,8 +341,6 @@ public:
return it->second;
}
- int getGroupItemNamesWidth(wxDC& dc, const FileView::PathDrawInfo& pdi);
-
private:
size_t getRowCount() const override { return getDataView().rowsOnView(); }
@@ -580,6 +571,9 @@ private:
itemNamesWidth = std::max(itemNamesWidth, *itPercentile);
}
assert(itemNamesWidth >= 0);
+
+ //Note: A better/faster solution would be to get 80th percentile of all std::wstring::size(), then do a *single* getTextExtentBuffered()!
+ // However, we need all the getTextExtentBuffered(itemName) later anyway, so above is fine.
}
return itemNamesWidth;
}
@@ -784,7 +778,7 @@ private:
if (getViewType() == GridViewType::action)
if (!enabled || !selected)
if (const auto& [cudAction, cudSide] = getCudAction(syncOp);
- cudAction != CudAction::doNothing && side == cudSide)
+ cudAction != CudAction::noChange && side == cudSide)
{
rectCud.width = gapSize_ + IconBuffer::getSize(IconBuffer::IconSize::small);
//fixed-size looks fine for all icon sizes! use same width even if file icons are disabled!
@@ -856,10 +850,10 @@ private:
//too much clutter? => drawIcon(getIconManager().getPlusOverlayIcon(), rectIcon,
// true /*drawActive: [!] e.g. disabled folder, exists left only, where child item is copied*/);
break;
- case CudAction::destroy:
+ case CudAction::delete_:
drawIcon(getIconManager().getMinusOverlayIcon(), rectIcon, true /*drawActive: [!]*/);
break;
- case CudAction::doNothing:
+ case CudAction::noChange:
case CudAction::update:
break;
};
@@ -986,7 +980,7 @@ private:
rectGroupItems.width -= 2 * gapSize_;
wxDCPenChanger dummy(dc, wxPen(getColorGridLine(), fastFromDIP(1)));
- dc.DrawLine(rectGroupItems.GetTopLeft(), rectGroupItems.GetBottomLeft() + wxPoint(0, 1)); //doesn't draw last pixel!
+ dc.DrawLine(rectGroupItems.GetTopLeft(), rectGroupItems.GetBottomLeft() + wxPoint(0, 1)); //DrawLine() doesn't draw last pixel!
rectGroupItems.x += fastFromDIP(1);
rectGroupItems.width -= fastFromDIP(1);
@@ -1629,14 +1623,15 @@ private:
{
switch (fsObj->getCategory())
{
- case FILE_LEFT_SIDE_ONLY: return "cat_left_only";
- case FILE_RIGHT_SIDE_ONLY: return "cat_right_only";
- case FILE_LEFT_NEWER: return "cat_left_newer";
- case FILE_RIGHT_NEWER: return "cat_right_newer";
- case FILE_DIFFERENT_CONTENT: return "cat_different";
- case FILE_EQUAL:
- case FILE_DIFFERENT_METADATA: return "cat_equal"; //= sub-category of equal
- case FILE_CONFLICT: return "cat_conflict";
+ case FILE_RENAMED: //similar to both "equal" and "conflict"
+ case FILE_EQUAL: return "cat_equal";
+ case FILE_LEFT_ONLY: return "cat_left_only";
+ case FILE_RIGHT_ONLY: return "cat_right_only";
+ case FILE_LEFT_NEWER: return "cat_left_newer";
+ case FILE_RIGHT_NEWER: return "cat_right_newer";
+ case FILE_DIFFERENT_CONTENT: return "cat_different";
+ case FILE_TIME_INVALID:
+ case FILE_CONFLICT: return "cat_conflict";
}
assert(false);
return "";
@@ -1652,21 +1647,21 @@ private:
{
switch (fsObj->getSyncOperation())
{
- case SO_CREATE_NEW_LEFT: return "so_create_left";
- case SO_CREATE_NEW_RIGHT: return "so_create_right";
- case SO_DELETE_LEFT: return "so_delete_left";
- case SO_DELETE_RIGHT: return "so_delete_right";
- case SO_MOVE_LEFT_FROM: return "so_move_left_source";
- case SO_MOVE_LEFT_TO: return "so_move_left_target";
- case SO_MOVE_RIGHT_FROM: return "so_move_right_source";
- case SO_MOVE_RIGHT_TO: return "so_move_right_target";
- case SO_OVERWRITE_LEFT: return "so_update_left";
- case SO_OVERWRITE_RIGHT: return "so_update_right";
- case SO_COPY_METADATA_TO_LEFT: return "so_move_left";
- case SO_COPY_METADATA_TO_RIGHT: return "so_move_right";
- case SO_DO_NOTHING: return "so_none";
- case SO_EQUAL: return "cat_equal";
- case SO_UNRESOLVED_CONFLICT: return "cat_conflict";
+ case SO_CREATE_LEFT: return "so_create_left";
+ case SO_CREATE_RIGHT: return "so_create_right";
+ case SO_DELETE_LEFT: return "so_delete_left";
+ case SO_DELETE_RIGHT: return "so_delete_right";
+ case SO_MOVE_LEFT_FROM: return "so_move_left_source";
+ case SO_MOVE_LEFT_TO: return "so_move_left_target";
+ case SO_MOVE_RIGHT_FROM: return "so_move_right_source";
+ case SO_MOVE_RIGHT_TO: return "so_move_right_target";
+ case SO_OVERWRITE_LEFT: return "so_update_left";
+ case SO_OVERWRITE_RIGHT: return "so_update_right";
+ case SO_RENAME_LEFT: return "so_move_left";
+ case SO_RENAME_RIGHT: return "so_move_right";
+ case SO_DO_NOTHING: return "so_none";
+ case SO_EQUAL: return "cat_equal";
+ case SO_UNRESOLVED_CONFLICT: return "cat_conflict";
};
assert(false);
return "";
@@ -2191,21 +2186,21 @@ wxImage fff::getSyncOpImage(SyncOperation syncOp)
switch (syncOp) //evaluate comparison result and sync direction
{
//*INDENT-OFF*
- case SO_CREATE_NEW_LEFT: return loadImage("so_create_left_sicon");
- case SO_CREATE_NEW_RIGHT: return loadImage("so_create_right_sicon");
- case SO_DELETE_LEFT: return loadImage("so_delete_left_sicon");
- case SO_DELETE_RIGHT: return loadImage("so_delete_right_sicon");
- case SO_MOVE_LEFT_FROM: return loadImage("so_move_left_source_sicon");
- case SO_MOVE_LEFT_TO: return loadImage("so_move_left_target_sicon");
- case SO_MOVE_RIGHT_FROM: return loadImage("so_move_right_source_sicon");
- case SO_MOVE_RIGHT_TO: return loadImage("so_move_right_target_sicon");
- case SO_OVERWRITE_LEFT: return loadImage("so_update_left_sicon");
- case SO_OVERWRITE_RIGHT: return loadImage("so_update_right_sicon");
- case SO_COPY_METADATA_TO_LEFT: return loadImage("so_move_left_sicon");
- case SO_COPY_METADATA_TO_RIGHT: return loadImage("so_move_right_sicon");
- case SO_DO_NOTHING: return loadImage("so_none_sicon");
- case SO_EQUAL: return loadImage("cat_equal_sicon");
- case SO_UNRESOLVED_CONFLICT: return loadImage("cat_conflict_small");
+ case SO_CREATE_LEFT: return loadImage("so_create_left_sicon");
+ case SO_CREATE_RIGHT: return loadImage("so_create_right_sicon");
+ case SO_DELETE_LEFT: return loadImage("so_delete_left_sicon");
+ case SO_DELETE_RIGHT: return loadImage("so_delete_right_sicon");
+ case SO_MOVE_LEFT_FROM: return loadImage("so_move_left_source_sicon");
+ case SO_MOVE_LEFT_TO: return loadImage("so_move_left_target_sicon");
+ case SO_MOVE_RIGHT_FROM: return loadImage("so_move_right_source_sicon");
+ case SO_MOVE_RIGHT_TO: return loadImage("so_move_right_target_sicon");
+ case SO_OVERWRITE_LEFT: return loadImage("so_update_left_sicon");
+ case SO_OVERWRITE_RIGHT: return loadImage("so_update_right_sicon");
+ case SO_RENAME_LEFT: return loadImage("so_move_left_sicon");
+ case SO_RENAME_RIGHT: return loadImage("so_move_right_sicon");
+ case SO_DO_NOTHING: return loadImage("so_none_sicon");
+ case SO_EQUAL: return loadImage("cat_equal_sicon");
+ case SO_UNRESOLVED_CONFLICT: return loadImage("cat_conflict_small");
//*INDENT-ON*
}
assert(false);
@@ -2218,14 +2213,15 @@ wxImage fff::getCmpResultImage(CompareFileResult cmpResult)
switch (cmpResult)
{
//*INDENT-OFF*
- case FILE_LEFT_SIDE_ONLY: return loadImage("cat_left_only_sicon");
- case FILE_RIGHT_SIDE_ONLY: return loadImage("cat_right_only_sicon");
- case FILE_LEFT_NEWER: return loadImage("cat_left_newer_sicon");
- case FILE_RIGHT_NEWER: return loadImage("cat_right_newer_sicon");
- case FILE_DIFFERENT_CONTENT: return loadImage("cat_different_sicon");
- case FILE_EQUAL:
- case FILE_DIFFERENT_METADATA: return loadImage("cat_equal_sicon"); //= sub-category of equal
- case FILE_CONFLICT: return loadImage("cat_conflict_small");
+ case FILE_RENAMED: //similar to both "equal" and "conflict"
+ case FILE_EQUAL: return loadImage("cat_equal_sicon");
+ case FILE_LEFT_ONLY: return loadImage("cat_left_only_sicon");
+ case FILE_RIGHT_ONLY: return loadImage("cat_right_only_sicon");
+ case FILE_LEFT_NEWER: return loadImage("cat_left_newer_sicon");
+ case FILE_RIGHT_NEWER: return loadImage("cat_right_newer_sicon");
+ case FILE_DIFFERENT_CONTENT: return loadImage("cat_different_sicon");
+ case FILE_TIME_INVALID:
+ case FILE_CONFLICT: return loadImage("cat_conflict_small");
//*INDENT-ON*
}
assert(false);
diff --git a/FreeFileSync/Source/ui/file_view.cpp b/FreeFileSync/Source/ui/file_view.cpp
index 67544c68..6bcc4a2a 100644
--- a/FreeFileSync/Source/ui/file_view.cpp
+++ b/FreeFileSync/Source/ui/file_view.cpp
@@ -16,15 +16,15 @@ using namespace fff;
namespace
{
-void serializeHierarchy(ContainerObject& hierObj, std::vector<FileSystemObject::ObjectId>& output)
+void serializeHierarchy(ContainerObject& conObj, std::vector<FileSystemObject::ObjectId>& output)
{
- for (FilePair& file : hierObj.refSubFiles())
+ for (FilePair& file : conObj.refSubFiles())
output.push_back(file.getId());
- for (SymlinkPair& symlink : hierObj.refSubLinks())
+ for (SymlinkPair& symlink : conObj.refSubLinks())
output.push_back(symlink.getId());
- for (FolderPair& folder : hierObj.refSubFolders())
+ for (FolderPair& folder : conObj.refSubFolders())
{
output.push_back(folder.getId());
serializeHierarchy(folder, output); //add recursion here to list sub-objects directly below parent!
@@ -135,9 +135,9 @@ ptrdiff_t FileView::findRowDirect(FileSystemObject::ObjectIdConst objId) const
}
-ptrdiff_t FileView::findRowFirstChild(const ContainerObject* hierObj) const
+ptrdiff_t FileView::findRowFirstChild(const ContainerObject* conObj) const
{
- auto it = rowPositionsFirstChild_.find(hierObj);
+ auto it = rowPositionsFirstChild_.find(conObj);
return it != rowPositionsFirstChild_.end() ? it->second : -1;
}
@@ -213,9 +213,9 @@ FileView::DifferenceViewStats FileView::applyDifferenceFilter(bool showExcluded,
switch (fsObj.getCategory())
{
- case FILE_LEFT_SIDE_ONLY:
+ case FILE_LEFT_ONLY:
return categorize(showLeftOnly, stats.leftOnly);
- case FILE_RIGHT_SIDE_ONLY:
+ case FILE_RIGHT_ONLY:
return categorize(showRightOnly, stats.rightOnly);
case FILE_LEFT_NEWER:
return categorize(showLeftNewer, stats.leftNewer);
@@ -224,9 +224,10 @@ FileView::DifferenceViewStats FileView::applyDifferenceFilter(bool showExcluded,
case FILE_DIFFERENT_CONTENT:
return categorize(showDifferent, stats.different);
case FILE_EQUAL:
- case FILE_DIFFERENT_METADATA: //= sub-category of equal
return categorize(showEqual, stats.equal);
+ case FILE_RENAMED:
case FILE_CONFLICT:
+ case FILE_TIME_INVALID:
return categorize(showConflict, stats.conflict);
}
assert(false);
@@ -273,22 +274,22 @@ FileView::ActionViewStats FileView::applyActionFilter(bool showExcluded, //maps
switch (fsObj.getSyncOperation()) //evaluate comparison result and sync direction
{
- case SO_CREATE_NEW_LEFT:
+ case SO_CREATE_LEFT:
return categorize(showCreateLeft, stats.createLeft);
- case SO_CREATE_NEW_RIGHT:
+ case SO_CREATE_RIGHT:
return categorize(showCreateRight, stats.createRight);
case SO_DELETE_LEFT:
return categorize(showDeleteLeft, stats.deleteLeft);
case SO_DELETE_RIGHT:
return categorize(showDeleteRight, stats.deleteRight);
case SO_OVERWRITE_LEFT:
- case SO_COPY_METADATA_TO_LEFT: //no extra filter button
+ case SO_RENAME_LEFT:
return categorize(showUpdateLeft, stats.updateLeft);
case SO_MOVE_LEFT_FROM:
case SO_MOVE_LEFT_TO:
return categorize(showUpdateLeft, moveLeft);
case SO_OVERWRITE_RIGHT:
- case SO_COPY_METADATA_TO_RIGHT: //no extra filter button
+ case SO_RENAME_RIGHT:
return categorize(showUpdateRight, stats.updateRight);
case SO_MOVE_RIGHT_FROM:
case SO_MOVE_RIGHT_TO:
@@ -414,12 +415,11 @@ bool lessFileName(const FileSystemObject& lhs, const FileSystemObject& rhs)
else if (isDirectoryPair(rhs))
return true;
- //sort directories and files/symlinks by short name
return zen::makeSortDirection(LessNaturalSort() /*even on Linux*/, std::bool_constant<ascending>())(lhs.getItemName<side>(), rhs.getItemName<side>());
}
-template <bool ascending> inline //side currently unused!
+template <bool ascending, SelectSide side> inline
bool lessFilePath(const FileSystemObject::ObjectId& lhs, const FileSystemObject::ObjectId& rhs,
const std::unordered_map<const void* /*BaseFolderPair*/, size_t /*position*/>& sortedPos,
std::vector<const FolderPair*>& tempBuf)
@@ -490,7 +490,7 @@ bool lessFilePath(const FileSystemObject::ObjectId& lhs, const FileSystemObject:
else if (folderL)
return true;
- return zen::makeSortDirection(LessNaturalSort(), std::bool_constant<ascending>())(fsObjL->getItemNameAny(), fsObjR->getItemNameAny());
+ return zen::makeSortDirection(LessNaturalSort(), std::bool_constant<ascending>())(fsObjL->getItemName<side>(), fsObjR->getItemName<side>());
}
else
return true;
@@ -499,7 +499,7 @@ bool lessFilePath(const FileSystemObject::ObjectId& lhs, const FileSystemObject:
return false;
//different components...
- if (const std::weak_ordering cmp = compareNatural((*itL)->getItemNameAny(), (*itR)->getItemNameAny());
+ if (const std::weak_ordering cmp = compareNatural((*itL)->getItemName<side>(), (*itR)->getItemName<side>());
cmp != std::weak_ordering::equivalent)
{
if constexpr (ascending)
@@ -642,7 +642,7 @@ struct LessFullPath
bool operator()(const FileSystemObject::ObjectId& lhs, const FileSystemObject::ObjectId& rhs) const
{
- return lessFilePath<ascending>(lhs, rhs, sortedPos_.ref(), tempBuf_);
+ return lessFilePath<ascending, side>(lhs, rhs, sortedPos_.ref(), tempBuf_);
}
private:
@@ -651,7 +651,7 @@ private:
};
-template <bool ascending>
+template <bool ascending, SelectSide side>
struct LessRelativeFolder
{
LessRelativeFolder(const std::vector<std::tuple<const void* /*BaseFolderPair*/, AbstractPath, AbstractPath>>& folderPairs)
@@ -664,7 +664,7 @@ struct LessRelativeFolder
bool operator()(const FileSystemObject::ObjectId& lhs, const FileSystemObject::ObjectId& rhs) const
{
- return lessFilePath<ascending>(lhs, rhs, sortedPos_.ref(), tempBuf_);
+ return lessFilePath<ascending, side>(lhs, rhs, sortedPos_.ref(), tempBuf_);
}
private:
@@ -791,42 +791,44 @@ void FileView::sortView(ColumnTypeRim type, ItemPathFormat pathFmt, bool onLeft,
switch (pathFmt)
{
case ItemPathFormat::name:
- if ( ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFileName<true, SelectSide::left>());
+ if ( ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFileName<true, SelectSide::left >());
else if ( ascending && !onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFileName<true, SelectSide::right>());
- else if (!ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFileName<false, SelectSide::left>());
+ else if (!ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFileName<false, SelectSide::left >());
else if (!ascending && !onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFileName<false, SelectSide::right>());
break;
case ItemPathFormat::relative:
- if ( ascending) std::sort(sortedRef_.begin(), sortedRef_.end(), LessRelativeFolder<true >(folderPairs_));
- else if (!ascending) std::sort(sortedRef_.begin(), sortedRef_.end(), LessRelativeFolder<false>(folderPairs_));
+ if ( ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessRelativeFolder<true, SelectSide::left >(folderPairs_));
+ else if ( ascending && !onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessRelativeFolder<true, SelectSide::right>(folderPairs_));
+ else if (!ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessRelativeFolder<false, SelectSide::left >(folderPairs_));
+ else if (!ascending && !onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessRelativeFolder<false, SelectSide::right>(folderPairs_));
break;
case ItemPathFormat::full:
- if ( ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFullPath<true, SelectSide::left>(folderPairs_));
+ if ( ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFullPath<true, SelectSide::left >(folderPairs_));
else if ( ascending && !onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFullPath<true, SelectSide::right>(folderPairs_));
- else if (!ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFullPath<false, SelectSide::left>(folderPairs_));
+ else if (!ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFullPath<false, SelectSide::left >(folderPairs_));
else if (!ascending && !onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFullPath<false, SelectSide::right>(folderPairs_));
break;
}
break;
case ColumnTypeRim::size:
- if ( ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFilesize<true, SelectSide::left>());
+ if ( ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFilesize<true, SelectSide::left >());
else if ( ascending && !onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFilesize<true, SelectSide::right>());
- else if (!ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFilesize<false, SelectSide::left>());
+ else if (!ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFilesize<false, SelectSide::left >());
else if (!ascending && !onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFilesize<false, SelectSide::right>());
break;
case ColumnTypeRim::date:
- if ( ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFiletime<true, SelectSide::left>());
+ if ( ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFiletime<true, SelectSide::left >());
else if ( ascending && !onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFiletime<true, SelectSide::right>());
- else if (!ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFiletime<false, SelectSide::left>());
+ else if (!ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFiletime<false, SelectSide::left >());
else if (!ascending && !onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFiletime<false, SelectSide::right>());
break;
case ColumnTypeRim::extension:
- if ( ascending && onLeft) std::stable_sort(sortedRef_.begin(), sortedRef_.end(), LessExtension<true, SelectSide::left>());
+ if ( ascending && onLeft) std::stable_sort(sortedRef_.begin(), sortedRef_.end(), LessExtension<true, SelectSide::left >());
else if ( ascending && !onLeft) std::stable_sort(sortedRef_.begin(), sortedRef_.end(), LessExtension<true, SelectSide::right>());
- else if (!ascending && onLeft) std::stable_sort(sortedRef_.begin(), sortedRef_.end(), LessExtension<false, SelectSide::left>());
+ else if (!ascending && onLeft) std::stable_sort(sortedRef_.begin(), sortedRef_.end(), LessExtension<false, SelectSide::left >());
else if (!ascending && !onLeft) std::stable_sort(sortedRef_.begin(), sortedRef_.end(), LessExtension<false, SelectSide::right>());
break;
}
diff --git a/FreeFileSync/Source/ui/file_view.h b/FreeFileSync/Source/ui/file_view.h
index 45baf2d7..9891f9e9 100644
--- a/FreeFileSync/Source/ui/file_view.h
+++ b/FreeFileSync/Source/ui/file_view.h
@@ -119,8 +119,8 @@ public:
const SortInfo* getSortConfig() const { return zen::get(currentSort_); } //return nullptr if currently not sorted
ptrdiff_t findRowDirect(FileSystemObject::ObjectIdConst objId) const; //find an object's row position on view list directly, return < 0 if not found
- ptrdiff_t findRowFirstChild(const ContainerObject* hierObj) const; //find first child of FolderPair or BaseFolderPair *on sorted sub view*
- //"hierObj" may be invalid, it is NOT dereferenced, return < 0 if not found
+ ptrdiff_t findRowFirstChild(const ContainerObject* conObj) const; //find first child of FolderPair or BaseFolderPair *on sorted sub view*
+ //"conObj" may be invalid, it is NOT dereferenced, return < 0 if not found
//count non-empty pairs to distinguish single/multiple folder pair cases
size_t getEffectiveFolderPairCount() const;
diff --git a/FreeFileSync/Source/ui/folder_pair.h b/FreeFileSync/Source/ui/folder_pair.h
index eff628ec..c7e26164 100644
--- a/FreeFileSync/Source/ui/folder_pair.h
+++ b/FreeFileSync/Source/ui/folder_pair.h
@@ -66,7 +66,7 @@ private:
setImage(*basicPanel_.m_bpButtonLocalSyncCfg, greyScaleIfDisabled(imgSync_, !!localSyncCfg_));
basicPanel_.m_bpButtonLocalSyncCfg->SetToolTip(localSyncCfg_ ?
- _("Local synchronization settings") + L" (" + getVariantName(localSyncCfg_->directionCfg.var) + L')' :
+ _("Local synchronization settings") + L" (" + getVariantName(getSyncVariant(localSyncCfg_->directionCfg)) + L')' :
_("Local synchronization settings"));
setImage(*basicPanel_.m_bpButtonLocalFilter, greyScaleIfDisabled(imgFilter_, !isNullFilter(localFilter_)));
diff --git a/FreeFileSync/Source/ui/gui_generated.cpp b/FreeFileSync/Source/ui/gui_generated.cpp
index 02d078e2..a17154f0 100644
--- a/FreeFileSync/Source/ui/gui_generated.cpp
+++ b/FreeFileSync/Source/ui/gui_generated.cpp
@@ -113,10 +113,6 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const
m_menuItemCheckVersionNow = new wxMenuItem( m_menuHelp, wxID_ANY, wxString( _("&Check for updates now") ) , wxEmptyString, wxITEM_NORMAL );
m_menuHelp->Append( m_menuItemCheckVersionNow );
- m_menuItemCheckVersionAuto = new wxMenuItem( m_menuHelp, wxID_ANY, wxString( _("Check &automatically once a week") ) , wxEmptyString, wxITEM_CHECK );
- m_menuHelp->Append( m_menuItemCheckVersionAuto );
- m_menuItemCheckVersionAuto->Check( true );
-
m_menuHelp->AppendSeparator();
m_menuItemAbout = new wxMenuItem( m_menuHelp, wxID_ABOUT, wxString( _("&About") ) + wxT('\t') + wxT("Shift+F1"), wxEmptyString, wxITEM_NORMAL );
@@ -1130,7 +1126,6 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const
m_menuTools->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::onMenuResetLayout ), this, m_menuItemResetLayout->GetId());
m_menuHelp->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::onShowHelp ), this, m_menuItemHelp->GetId());
m_menuHelp->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::onMenuCheckVersion ), this, m_menuItemCheckVersionNow->GetId());
- m_menuHelp->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::onMenuCheckVersionAutomatically ), this, m_menuItemCheckVersionAuto->GetId());
m_menuHelp->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::onMenuAbout ), this, m_menuItemAbout->GetId());
m_buttonCompare->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onCompare ), NULL, this );
m_bpButtonCmpConfig->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onCmpSettings ), NULL, this );
@@ -1332,7 +1327,7 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w
bSizerHeaderCompSettings = new wxBoxSizer( wxVERTICAL );
- m_staticTextMainCompSettings = new wxStaticText( m_panelCompSettingsTab, wxID_ANY, _("Main settings:"), wxDefaultPosition, wxDefaultSize, 0 );
+ m_staticTextMainCompSettings = new wxStaticText( m_panelCompSettingsTab, wxID_ANY, _("Common settings:"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticTextMainCompSettings->Wrap( -1 );
bSizerHeaderCompSettings->Add( m_staticTextMainCompSettings, 0, wxALL, 10 );
@@ -1468,7 +1463,7 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w
wxBoxSizer* bSizer1733;
bSizer1733 = new wxBoxSizer( wxVERTICAL );
- m_staticText112 = new wxStaticText( m_panelComparisonSettings, wxID_ANY, _("&Ignore time shift [hh:mm]"), wxDefaultPosition, wxDefaultSize, 0 );
+ m_staticText112 = new wxStaticText( m_panelComparisonSettings, wxID_ANY, _("&Ignore exact time shift [hh:mm]"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticText112->Wrap( -1 );
bSizer1733->Add( m_staticText112, 0, wxALL, 5 );
@@ -1650,7 +1645,7 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w
m_panelCompSettingsTab->SetSizer( bSizer275 );
m_panelCompSettingsTab->Layout();
bSizer275->Fit( m_panelCompSettingsTab );
- m_notebook->AddPage( m_panelCompSettingsTab, _("dummy"), true );
+ m_notebook->AddPage( m_panelCompSettingsTab, _("dummy"), false );
m_panelFilterSettingsTab = new wxPanel( m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
m_panelFilterSettingsTab->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) );
@@ -1659,7 +1654,7 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w
bSizerHeaderFilterSettings = new wxBoxSizer( wxVERTICAL );
- m_staticTextMainFilterSettings = new wxStaticText( m_panelFilterSettingsTab, wxID_ANY, _("Main settings:"), wxDefaultPosition, wxDefaultSize, 0 );
+ m_staticTextMainFilterSettings = new wxStaticText( m_panelFilterSettingsTab, wxID_ANY, _("Common settings:"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticTextMainFilterSettings->Wrap( -1 );
bSizerHeaderFilterSettings->Add( m_staticTextMainFilterSettings, 0, wxALL, 10 );
@@ -1901,7 +1896,7 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w
bSizerHeaderSyncSettings = new wxBoxSizer( wxVERTICAL );
- m_staticTextMainSyncSettings = new wxStaticText( m_panelSyncSettingsTab, wxID_ANY, _("Main settings:"), wxDefaultPosition, wxDefaultSize, 0 );
+ m_staticTextMainSyncSettings = new wxStaticText( m_panelSyncSettingsTab, wxID_ANY, _("Common settings:"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticTextMainSyncSettings->Wrap( -1 );
bSizerHeaderSyncSettings->Add( m_staticTextMainSyncSettings, 0, wxALL, 10 );
@@ -1974,19 +1969,93 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w
bSizer237->Add( 10, 0, 0, 0, 5 );
- wxBoxSizer* bSizer238;
- bSizer238 = new wxBoxSizer( wxVERTICAL );
+ wxBoxSizer* bSizer312;
+ bSizer312 = new wxBoxSizer( wxVERTICAL );
+
+ wxBoxSizer* bSizer311;
+ bSizer311 = new wxBoxSizer( wxHORIZONTAL );
+
+ m_bitmapDatabase = new wxStaticBitmap( m_panelSyncSettings, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), 0 );
+ m_bitmapDatabase->SetToolTip( _("sync.ffs_db") );
+
+ bSizer311->Add( m_bitmapDatabase, 0, wxRIGHT|wxALIGN_CENTER_VERTICAL, 5 );
+
+ m_checkBoxUseDatabase = new wxCheckBox( m_panelSyncSettings, wxID_ANY, _("Use database file to detect changes"), wxDefaultPosition, wxDefaultSize, 0 );
+ bSizer311->Add( m_checkBoxUseDatabase, 0, wxALIGN_CENTER_VERTICAL, 5 );
+
+
+ bSizer312->Add( bSizer311, 0, wxTOP|wxRIGHT|wxLEFT, 10 );
+
+
+ bSizer312->Add( 0, 0, 1, wxEXPAND, 5 );
+
+ m_staticTextSyncVarDescription = new wxStaticText( m_panelSyncSettings, wxID_ANY, _("dummy"), wxDefaultPosition, wxSize( -1,-1 ), 0 );
+ m_staticTextSyncVarDescription->Wrap( -1 );
+ m_staticTextSyncVarDescription->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) );
+
+ bSizer312->Add( m_staticTextSyncVarDescription, 0, wxALL, 10 );
+
+
+ bSizer312->Add( 0, 0, 1, wxEXPAND, 5 );
+
+ wxBoxSizer* bSizer310;
+ bSizer310 = new wxBoxSizer( wxVERTICAL );
+
+ m_staticline431 = new wxStaticLine( m_panelSyncSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
+ bSizer310->Add( m_staticline431, 0, wxEXPAND, 5 );
+
+ wxBoxSizer* bSizer201;
+ bSizer201 = new wxBoxSizer( wxHORIZONTAL );
+
+ m_staticline72 = new wxStaticLine( m_panelSyncSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL );
+ bSizer201->Add( m_staticline72, 0, wxEXPAND, 5 );
+
+ wxBoxSizer* bSizer3121;
+ bSizer3121 = new wxBoxSizer( wxHORIZONTAL );
+
+ m_bitmapMoveLeft = new wxStaticBitmap( m_panelSyncSettings, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), 0 );
+ m_bitmapMoveLeft->SetToolTip( _("- Not supported by all file systems\n- Requires and creates database files\n- Detection not available for first sync") );
+
+ bSizer3121->Add( m_bitmapMoveLeft, 0, wxALIGN_CENTER_VERTICAL, 5 );
+
+ m_bitmapMoveRight = new wxStaticBitmap( m_panelSyncSettings, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), 0 );
+ m_bitmapMoveRight->SetToolTip( _("- Not supported by all file systems\n- Requires and creates database files\n- Detection not available for first sync") );
+
+ bSizer3121->Add( m_bitmapMoveRight, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 );
+
+ m_staticTextDetectMove = new wxStaticText( m_panelSyncSettings, wxID_ANY, _("Detect moved files"), wxDefaultPosition, wxDefaultSize, 0 );
+ m_staticTextDetectMove->Wrap( -1 );
+ m_staticTextDetectMove->SetToolTip( _("- Not supported by all file systems\n- Requires and creates database files\n- Detection not available for first sync") );
+
+ bSizer3121->Add( m_staticTextDetectMove, 0, wxALIGN_CENTER_VERTICAL, 5 );
- bSizer238->Add( 0, 0, 1, wxEXPAND, 5 );
+ bSizer201->Add( bSizer3121, 0, wxALL, 10 );
+
+ m_hyperlink242 = new wxHyperlinkCtrl( m_panelSyncSettings, wxID_ANY, _("More information"), wxT("https://freefilesync.org/manual.php?topic=synchronization-settings"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE );
+ m_hyperlink242->SetToolTip( _("https://freefilesync.org/manual.php?topic=synchronization-settings") );
+
+ bSizer201->Add( m_hyperlink242, 0, wxTOP|wxBOTTOM|wxRIGHT|wxALIGN_CENTER_VERTICAL, 10 );
+
+ m_staticline721 = new wxStaticLine( m_panelSyncSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL );
+ bSizer201->Add( m_staticline721, 0, wxEXPAND, 5 );
+
+
+ bSizer310->Add( bSizer201, 0, 0, 5 );
+
+
+ bSizer312->Add( bSizer310, 0, 0, 5 );
+
+
+ bSizer237->Add( bSizer312, 0, wxEXPAND, 5 );
bSizerSyncDirHolder = new wxBoxSizer( wxHORIZONTAL );
- bSizerSyncDirections = new wxBoxSizer( wxVERTICAL );
+ bSizerSyncDirsDiff = new wxBoxSizer( wxVERTICAL );
m_staticText184 = new wxStaticText( m_panelSyncSettings, wxID_ANY, _("Difference"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticText184->Wrap( -1 );
- bSizerSyncDirections->Add( m_staticText184, 0, wxALIGN_CENTER_HORIZONTAL, 5 );
+ bSizerSyncDirsDiff->Add( m_staticText184, 0, wxALIGN_CENTER_HORIZONTAL, 5 );
ffgSizer11 = new wxFlexGridSizer( 2, 0, 5, 5 );
ffgSizer11->SetFlexibleDirection( wxBOTH );
@@ -2007,11 +2076,6 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w
ffgSizer11->Add( m_bitmapDifferent, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 );
- m_bitmapConflict = new wxStaticBitmap( m_panelSyncSettings, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), 0 );
- m_bitmapConflict->SetToolTip( _("Conflict/item cannot be categorized") );
-
- ffgSizer11->Add( m_bitmapConflict, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 );
-
m_bitmapRightNewer = new wxStaticBitmap( m_panelSyncSettings, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), 0 );
m_bitmapRightNewer->SetToolTip( _("Right side is newer") );
@@ -2031,9 +2095,6 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w
m_bpButtonDifferent = new wxBitmapButton( m_panelSyncSettings, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), wxBU_AUTODRAW|0 );
ffgSizer11->Add( m_bpButtonDifferent, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 );
- m_bpButtonConflict = new wxBitmapButton( m_panelSyncSettings, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), wxBU_AUTODRAW|0 );
- ffgSizer11->Add( m_bpButtonConflict, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 );
-
m_bpButtonRightNewer = new wxBitmapButton( m_panelSyncSettings, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), wxBU_AUTODRAW|0 );
ffgSizer11->Add( m_bpButtonRightNewer, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 );
@@ -2041,75 +2102,73 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w
ffgSizer11->Add( m_bpButtonRightOnly, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 );
- bSizerSyncDirections->Add( ffgSizer11, 0, 0, 5 );
+ bSizerSyncDirsDiff->Add( ffgSizer11, 0, 0, 5 );
m_staticText120 = new wxStaticText( m_panelSyncSettings, wxID_ANY, _("Action"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticText120->Wrap( -1 );
- bSizerSyncDirections->Add( m_staticText120, 0, wxALIGN_CENTER_HORIZONTAL|wxTOP, 5 );
+ bSizerSyncDirsDiff->Add( m_staticText120, 0, wxALIGN_CENTER_HORIZONTAL|wxTOP, 5 );
- bSizerSyncDirHolder->Add( bSizerSyncDirections, 0, 0, 5 );
-
- bSizerDatabase = new wxBoxSizer( wxVERTICAL );
-
- m_bitmapDatabase = new wxStaticBitmap( m_panelSyncSettings, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), 0 );
- bSizerDatabase->Add( m_bitmapDatabase, 0, wxALIGN_CENTER_HORIZONTAL, 5 );
-
+ bSizerSyncDirHolder->Add( bSizerSyncDirsDiff, 0, wxALIGN_CENTER_VERTICAL, 5 );
- bSizerDatabase->Add( 0, 3, 0, 0, 5 );
+ bSizerSyncDirsChanges = new wxBoxSizer( wxVERTICAL );
- m_staticText145 = new wxStaticText( m_panelSyncSettings, wxID_ANY, _("sync.ffs_db"), wxDefaultPosition, wxDefaultSize, 0 );
- m_staticText145->Wrap( -1 );
- m_staticText145->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_ITALIC, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) );
- m_staticText145->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) );
+ ffgSizer111 = new wxFlexGridSizer( 0, 3, 5, 5 );
+ ffgSizer111->SetFlexibleDirection( wxBOTH );
+ ffgSizer111->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
- bSizerDatabase->Add( m_staticText145, 0, wxALIGN_CENTER_HORIZONTAL, 5 );
+ m_staticText12011 = new wxStaticText( m_panelSyncSettings, wxID_ANY, _("Create:"), wxDefaultPosition, wxDefaultSize, 0 );
+ m_staticText12011->Wrap( -1 );
+ ffgSizer111->Add( m_staticText12011, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT, 5 );
+ m_bpButtonLeftCreate = new wxBitmapButton( m_panelSyncSettings, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), wxBU_AUTODRAW|0 );
+ ffgSizer111->Add( m_bpButtonLeftCreate, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 );
- bSizerSyncDirHolder->Add( bSizerDatabase, 0, wxLEFT|wxALIGN_CENTER_VERTICAL, 5 );
+ m_bpButtonRightCreate = new wxBitmapButton( m_panelSyncSettings, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), wxBU_AUTODRAW|0 );
+ ffgSizer111->Add( m_bpButtonRightCreate, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 );
- m_staticTextSyncVarDescription = new wxStaticText( m_panelSyncSettings, wxID_ANY, _("dummy"), wxDefaultPosition, wxSize( -1,-1 ), 0 );
- m_staticTextSyncVarDescription->Wrap( -1 );
- m_staticTextSyncVarDescription->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) );
+ m_staticText12012 = new wxStaticText( m_panelSyncSettings, wxID_ANY, _("Update:"), wxDefaultPosition, wxDefaultSize, 0 );
+ m_staticText12012->Wrap( -1 );
+ ffgSizer111->Add( m_staticText12012, 0, wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL, 5 );
- bSizerSyncDirHolder->Add( m_staticTextSyncVarDescription, 0, wxALL|wxALIGN_CENTER_VERTICAL, 10 );
+ m_bpButtonLeftUpdate = new wxBitmapButton( m_panelSyncSettings, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), wxBU_AUTODRAW|0 );
+ ffgSizer111->Add( m_bpButtonLeftUpdate, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 );
+ m_bpButtonRightUpdate = new wxBitmapButton( m_panelSyncSettings, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), wxBU_AUTODRAW|0 );
+ ffgSizer111->Add( m_bpButtonRightUpdate, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 );
- bSizer238->Add( bSizerSyncDirHolder, 0, wxTOP|wxBOTTOM|wxRIGHT, 10 );
+ m_staticText12013 = new wxStaticText( m_panelSyncSettings, wxID_ANY, _("Delete:"), wxDefaultPosition, wxDefaultSize, 0 );
+ m_staticText12013->Wrap( -1 );
+ ffgSizer111->Add( m_staticText12013, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT, 5 );
+ m_bpButtonLeftDelete = new wxBitmapButton( m_panelSyncSettings, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), wxBU_AUTODRAW|0 );
+ ffgSizer111->Add( m_bpButtonLeftDelete, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 );
- bSizer238->Add( 0, 0, 1, wxEXPAND, 5 );
+ m_bpButtonRightDelete = new wxBitmapButton( m_panelSyncSettings, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), wxBU_AUTODRAW|0 );
+ ffgSizer111->Add( m_bpButtonRightDelete, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 );
- m_staticline431 = new wxStaticLine( m_panelSyncSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
- bSizer238->Add( m_staticline431, 0, wxEXPAND, 5 );
- wxBoxSizer* bSizer201;
- bSizer201 = new wxBoxSizer( wxHORIZONTAL );
+ ffgSizer111->Add( 0, 0, 0, 0, 5 );
- m_staticline72 = new wxStaticLine( m_panelSyncSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL );
- bSizer201->Add( m_staticline72, 0, wxEXPAND, 5 );
-
- wxBoxSizer* bSizer249;
- bSizer249 = new wxBoxSizer( wxHORIZONTAL );
+ m_staticText1201 = new wxStaticText( m_panelSyncSettings, wxID_ANY, _("Left"), wxDefaultPosition, wxDefaultSize, 0 );
+ m_staticText1201->Wrap( -1 );
+ ffgSizer111->Add( m_staticText1201, 0, wxALIGN_CENTER_HORIZONTAL, 5 );
- m_checkBoxDetectMove = new wxCheckBox( m_panelSyncSettings, wxID_ANY, _("Detect moved files"), wxDefaultPosition, wxDefaultSize, 0 );
- m_checkBoxDetectMove->SetToolTip( _("- Not supported by all file systems\n- Requires and creates database files\n- Detection not available for first sync") );
+ m_staticText1202 = new wxStaticText( m_panelSyncSettings, wxID_ANY, _("Right"), wxDefaultPosition, wxDefaultSize, 0 );
+ m_staticText1202->Wrap( -1 );
+ ffgSizer111->Add( m_staticText1202, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 );
- bSizer249->Add( m_checkBoxDetectMove, 0, wxALL|wxEXPAND, 5 );
-
- m_hyperlink242 = new wxHyperlinkCtrl( m_panelSyncSettings, wxID_ANY, _("More information"), wxT("https://freefilesync.org/manual.php?topic=synchronization-settings"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE );
- m_hyperlink242->SetToolTip( _("https://freefilesync.org/manual.php?topic=synchronization-settings") );
- bSizer249->Add( m_hyperlink242, 0, wxTOP|wxBOTTOM|wxRIGHT, 5 );
+ bSizerSyncDirsChanges->Add( ffgSizer111, 0, 0, 5 );
- bSizer201->Add( bSizer249, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 );
+ bSizerSyncDirHolder->Add( bSizerSyncDirsChanges, 0, wxALIGN_CENTER_VERTICAL, 5 );
- bSizer238->Add( bSizer201, 0, 0, 5 );
+ bSizer237->Add( bSizerSyncDirHolder, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM, 5 );
- bSizer237->Add( bSizer238, 1, wxEXPAND, 5 );
+ bSizer237->Add( 0, 0, 1, 0, 5 );
bSizer232->Add( bSizer237, 0, wxEXPAND, 5 );
@@ -2503,7 +2562,7 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w
m_panelSyncSettingsTab->SetSizer( bSizer276 );
m_panelSyncSettingsTab->Layout();
bSizer276->Fit( m_panelSyncSettingsTab );
- m_notebook->AddPage( m_panelSyncSettingsTab, _("dummy"), false );
+ m_notebook->AddPage( m_panelSyncSettingsTab, _("dummy"), true );
bSizer190->Add( m_notebook, 1, wxEXPAND, 5 );
@@ -2602,13 +2661,18 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w
m_buttonUpdate->Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( ConfigDlgGenerated::onSyncUpdateDouble ), NULL, this );
m_buttonCustom->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onSyncCustom ), NULL, this );
m_buttonCustom->Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( ConfigDlgGenerated::onSyncCustomDouble ), NULL, this );
- m_bpButtonLeftOnly->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onExLeftSideOnly ), NULL, this );
+ m_checkBoxUseDatabase->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onToggleUseDatabase ), NULL, this );
+ m_bpButtonLeftOnly->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onLeftOnly ), NULL, this );
m_bpButtonLeftNewer->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onLeftNewer ), NULL, this );
m_bpButtonDifferent->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onDifferent ), NULL, this );
- m_bpButtonConflict->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onConflict ), NULL, this );
m_bpButtonRightNewer->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onRightNewer ), NULL, this );
- m_bpButtonRightOnly->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onExRightSideOnly ), NULL, this );
- m_checkBoxDetectMove->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onToggleDetectMovedFiles ), NULL, this );
+ m_bpButtonRightOnly->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onRightOnly ), NULL, this );
+ m_bpButtonLeftCreate->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onLeftCreate ), NULL, this );
+ m_bpButtonRightCreate->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onRightCreate ), NULL, this );
+ m_bpButtonLeftUpdate->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onLeftUpdate ), NULL, this );
+ m_bpButtonRightUpdate->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onRightUpdate ), NULL, this );
+ m_bpButtonLeftDelete->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onLeftDelete ), NULL, this );
+ m_bpButtonRightDelete->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onRightDelete ), NULL, this );
m_buttonRecycler->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onDeletionRecycler ), NULL, this );
m_buttonPermanent->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onDeletionPermanent ), NULL, this );
m_buttonVersioning->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onDeletionVersioning ), NULL, this );
@@ -2901,7 +2965,7 @@ CloudSetupDlgGenerated::CloudSetupDlgGenerated( wxWindow* parent, wxWindowID id,
m_checkBoxShowPassword = new wxCheckBox( m_panelAuth, wxID_ANY, _("&Show password"), wxDefaultPosition, wxDefaultSize, 0 );
bSizerPassword->Add( m_checkBoxShowPassword, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 );
- m_checkBoxPasswordPrompt = new wxCheckBox( m_panelAuth, wxID_ANY, _("Enter at login"), wxDefaultPosition, wxDefaultSize, 0 );
+ m_checkBoxPasswordPrompt = new wxCheckBox( m_panelAuth, wxID_ANY, _("Prompt during login"), wxDefaultPosition, wxDefaultSize, 0 );
bSizerPassword->Add( m_checkBoxPasswordPrompt, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 );
@@ -4593,6 +4657,15 @@ RenameDlgGenerated::RenameDlgGenerated( wxWindow* parent, wxWindowID id, const w
m_gridRenamePreview->SetScrollRate( 5, 5 );
bSizer242->Add( m_gridRenamePreview, 1, wxEXPAND, 5 );
+ m_staticlinePreview = new wxStaticLine( m_panel31, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
+ bSizer242->Add( m_staticlinePreview, 0, wxEXPAND, 5 );
+
+ m_staticTextPlaceholderDescription = new wxStaticText( m_panel31, wxID_ANY, _("Placeholders represent differences between the names."), wxDefaultPosition, wxDefaultSize, 0 );
+ m_staticTextPlaceholderDescription->Wrap( -1 );
+ m_staticTextPlaceholderDescription->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) );
+
+ bSizer242->Add( m_staticTextPlaceholderDescription, 0, wxTOP|wxRIGHT|wxLEFT|wxALIGN_CENTER_HORIZONTAL, 10 );
+
m_textCtrlNewName = new wxTextCtrl( m_panel31, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
bSizer242->Add( m_textCtrlNewName, 0, wxEXPAND|wxALL, 10 );
@@ -4607,7 +4680,7 @@ RenameDlgGenerated::RenameDlgGenerated( wxWindow* parent, wxWindowID id, const w
bSizerStdButtons = new wxBoxSizer( wxHORIZONTAL );
- m_buttonOK = new wxButton( this, wxID_OK, _("Rename"), wxDefaultPosition, wxSize( -1,-1 ), 0 );
+ m_buttonOK = new wxButton( this, wxID_OK, _("&Rename"), wxDefaultPosition, wxSize( -1,-1 ), 0 );
m_buttonOK->SetDefault();
m_buttonOK->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) );
@@ -4629,7 +4702,6 @@ RenameDlgGenerated::RenameDlgGenerated( wxWindow* parent, wxWindowID id, const w
// Connect Events
this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( RenameDlgGenerated::onClose ) );
- m_textCtrlNewName->Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( RenameDlgGenerated::onTypingName ), NULL, this );
m_buttonOK->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( RenameDlgGenerated::onOkay ), NULL, this );
m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( RenameDlgGenerated::onCancel ), NULL, this );
}
@@ -5322,13 +5394,13 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS
m_staticFfsTextVersion = new wxStaticText( m_panel41, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticFfsTextVersion->Wrap( -1 );
- bSizer298->Add( m_staticFfsTextVersion, 0, wxALIGN_CENTER_VERTICAL, 5 );
+ bSizer298->Add( m_staticFfsTextVersion, 0, wxALIGN_BOTTOM, 5 );
m_staticTextFfsVariant = new wxStaticText( m_panel41, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticTextFfsVariant->Wrap( -1 );
m_staticTextFfsVariant->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) );
- bSizer298->Add( m_staticTextFfsVariant, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 10 );
+ bSizer298->Add( m_staticTextFfsVariant, 0, wxLEFT|wxALIGN_BOTTOM, 10 );
bSizerMainSection->Add( bSizer298, 0, wxALIGN_CENTER_HORIZONTAL|wxALL, 5 );
@@ -5457,8 +5529,8 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS
bSizerStdButtons = new wxBoxSizer( wxHORIZONTAL );
- m_buttonShowDonationDetails = new wxButton( this, wxID_ANY, _("Thank you, %x, for your support!"), wxDefaultPosition, wxDefaultSize, 0 );
- bSizerStdButtons->Add( m_buttonShowDonationDetails, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT, 5 );
+ m_buttonShowSupporterDetails = new wxButton( this, wxID_ANY, _("Thank you, %x, for your support!"), wxDefaultPosition, wxDefaultSize, 0 );
+ bSizerStdButtons->Add( m_buttonShowSupporterDetails, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT, 5 );
bSizerStdButtons->Add( 0, 0, 1, 0, 5 );
@@ -5489,7 +5561,7 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS
m_buttonDonate1->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AboutDlgGenerated::onDonate ), NULL, this );
m_bpButtonForum->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AboutDlgGenerated::onOpenForum ), NULL, this );
m_bpButtonEmail->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AboutDlgGenerated::onSendEmail ), NULL, this );
- m_buttonShowDonationDetails->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AboutDlgGenerated::onShowDonationDetails ), NULL, this );
+ m_buttonShowSupporterDetails->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AboutDlgGenerated::onShowSupporterDetails ), NULL, this );
m_buttonDonate2->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AboutDlgGenerated::onDonate ), NULL, this );
m_buttonClose->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AboutDlgGenerated::onOkay ), NULL, this );
}
@@ -5766,7 +5838,7 @@ ActivationDlgGenerated::ActivationDlgGenerated( wxWindow* parent, wxWindowID id,
m_staticline82 = new wxStaticLine( m_panel35, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
bSizer172->Add( m_staticline82, 0, wxEXPAND, 5 );
- m_staticTextMain = new wxStaticText( m_panel35, wxID_ANY, _("Activate the FreeFileSync Donation Edition by one of the following methods:"), wxDefaultPosition, wxDefaultSize, 0 );
+ m_staticTextMain = new wxStaticText( m_panel35, wxID_ANY, _("Activate FreeFileSync by one of the following methods:"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticTextMain->Wrap( -1 );
bSizer172->Add( m_staticTextMain, 0, wxALL, 10 );
diff --git a/FreeFileSync/Source/ui/gui_generated.h b/FreeFileSync/Source/ui/gui_generated.h
index 3378e36e..42b1f9d5 100644
--- a/FreeFileSync/Source/ui/gui_generated.h
+++ b/FreeFileSync/Source/ui/gui_generated.h
@@ -97,7 +97,6 @@ class MainDialogGenerated : public wxFrame
wxMenu* m_menuHelp;
wxMenuItem* m_menuItemHelp;
wxMenuItem* m_menuItemCheckVersionNow;
- wxMenuItem* m_menuItemCheckVersionAuto;
wxMenuItem* m_menuItemAbout;
wxBoxSizer* bSizerPanelHolder;
wxPanel* m_panelTopButtons;
@@ -235,7 +234,6 @@ class MainDialogGenerated : public wxFrame
virtual void onMenuResetLayout( wxCommandEvent& event ) { event.Skip(); }
virtual void onShowHelp( wxCommandEvent& event ) { event.Skip(); }
virtual void onMenuCheckVersion( wxCommandEvent& event ) { event.Skip(); }
- virtual void onMenuCheckVersionAutomatically( wxCommandEvent& event ) { event.Skip(); }
virtual void onMenuAbout( wxCommandEvent& event ) { event.Skip(); }
virtual void onCompSettingsContextMouse( wxMouseEvent& event ) { event.Skip(); }
virtual void onCompSettingsContext( wxCommandEvent& event ) { event.Skip(); }
@@ -420,31 +418,43 @@ class ConfigDlgGenerated : public wxDialog
zen::ToggleButton* m_buttonMirror;
zen::ToggleButton* m_buttonUpdate;
zen::ToggleButton* m_buttonCustom;
+ wxStaticBitmap* m_bitmapDatabase;
+ wxCheckBox* m_checkBoxUseDatabase;
+ wxStaticText* m_staticTextSyncVarDescription;
+ wxStaticLine* m_staticline431;
+ wxStaticLine* m_staticline72;
+ wxStaticBitmap* m_bitmapMoveLeft;
+ wxStaticBitmap* m_bitmapMoveRight;
+ wxStaticText* m_staticTextDetectMove;
+ wxHyperlinkCtrl* m_hyperlink242;
+ wxStaticLine* m_staticline721;
wxBoxSizer* bSizerSyncDirHolder;
- wxBoxSizer* bSizerSyncDirections;
+ wxBoxSizer* bSizerSyncDirsDiff;
wxStaticText* m_staticText184;
wxFlexGridSizer* ffgSizer11;
wxStaticBitmap* m_bitmapLeftOnly;
wxStaticBitmap* m_bitmapLeftNewer;
wxStaticBitmap* m_bitmapDifferent;
- wxStaticBitmap* m_bitmapConflict;
wxStaticBitmap* m_bitmapRightNewer;
wxStaticBitmap* m_bitmapRightOnly;
wxBitmapButton* m_bpButtonLeftOnly;
wxBitmapButton* m_bpButtonLeftNewer;
wxBitmapButton* m_bpButtonDifferent;
- wxBitmapButton* m_bpButtonConflict;
wxBitmapButton* m_bpButtonRightNewer;
wxBitmapButton* m_bpButtonRightOnly;
wxStaticText* m_staticText120;
- wxBoxSizer* bSizerDatabase;
- wxStaticBitmap* m_bitmapDatabase;
- wxStaticText* m_staticText145;
- wxStaticText* m_staticTextSyncVarDescription;
- wxStaticLine* m_staticline431;
- wxStaticLine* m_staticline72;
- wxCheckBox* m_checkBoxDetectMove;
- wxHyperlinkCtrl* m_hyperlink242;
+ wxFlexGridSizer* ffgSizer111;
+ wxStaticText* m_staticText12011;
+ wxBitmapButton* m_bpButtonLeftCreate;
+ wxBitmapButton* m_bpButtonRightCreate;
+ wxStaticText* m_staticText12012;
+ wxBitmapButton* m_bpButtonLeftUpdate;
+ wxBitmapButton* m_bpButtonRightUpdate;
+ wxStaticText* m_staticText12013;
+ wxBitmapButton* m_bpButtonLeftDelete;
+ wxBitmapButton* m_bpButtonRightDelete;
+ wxStaticText* m_staticText1201;
+ wxStaticText* m_staticText1202;
wxStaticLine* m_staticline54;
wxBoxSizer* bSizer2361;
wxStaticText* m_staticText87;
@@ -529,13 +539,18 @@ class ConfigDlgGenerated : public wxDialog
virtual void onSyncUpdateDouble( wxMouseEvent& event ) { event.Skip(); }
virtual void onSyncCustom( wxCommandEvent& event ) { event.Skip(); }
virtual void onSyncCustomDouble( wxMouseEvent& event ) { event.Skip(); }
- virtual void onExLeftSideOnly( wxCommandEvent& event ) { event.Skip(); }
+ virtual void onToggleUseDatabase( wxCommandEvent& event ) { event.Skip(); }
+ virtual void onLeftOnly( wxCommandEvent& event ) { event.Skip(); }
virtual void onLeftNewer( wxCommandEvent& event ) { event.Skip(); }
virtual void onDifferent( wxCommandEvent& event ) { event.Skip(); }
- virtual void onConflict( wxCommandEvent& event ) { event.Skip(); }
virtual void onRightNewer( wxCommandEvent& event ) { event.Skip(); }
- virtual void onExRightSideOnly( wxCommandEvent& event ) { event.Skip(); }
- virtual void onToggleDetectMovedFiles( wxCommandEvent& event ) { event.Skip(); }
+ virtual void onRightOnly( wxCommandEvent& event ) { event.Skip(); }
+ virtual void onLeftCreate( wxCommandEvent& event ) { event.Skip(); }
+ virtual void onRightCreate( wxCommandEvent& event ) { event.Skip(); }
+ virtual void onLeftUpdate( wxCommandEvent& event ) { event.Skip(); }
+ virtual void onRightUpdate( wxCommandEvent& event ) { event.Skip(); }
+ virtual void onLeftDelete( wxCommandEvent& event ) { event.Skip(); }
+ virtual void onRightDelete( wxCommandEvent& event ) { event.Skip(); }
virtual void onDeletionRecycler( wxCommandEvent& event ) { event.Skip(); }
virtual void onDeletionPermanent( wxCommandEvent& event ) { event.Skip(); }
virtual void onDeletionVersioning( wxCommandEvent& event ) { event.Skip(); }
@@ -555,6 +570,7 @@ class ConfigDlgGenerated : public wxDialog
public:
wxStaticBitmap* m_bitmapRetryErrors;
wxStaticText* m_staticTextFilterDescr;
+ wxBoxSizer* bSizerSyncDirsChanges;
wxBitmapButton* m_bpButtonSelectVersioningAltFolder;
wxBitmapButton* m_bpButtonShowLogFolder;
fff::FolderHistoryBox* m_logFolderPath;
@@ -1059,6 +1075,8 @@ class RenameDlgGenerated : public wxDialog
wxStaticLine* m_staticline91;
wxPanel* m_panel31;
zen::Grid* m_gridRenamePreview;
+ wxStaticLine* m_staticlinePreview;
+ wxStaticText* m_staticTextPlaceholderDescription;
wxTextCtrl* m_textCtrlNewName;
wxStaticLine* m_staticline9;
wxBoxSizer* bSizerStdButtons;
@@ -1067,7 +1085,6 @@ class RenameDlgGenerated : public wxDialog
// Virtual event handlers, override them in your derived class
virtual void onClose( wxCloseEvent& event ) { event.Skip(); }
- virtual void onTypingName( wxCommandEvent& event ) { event.Skip(); }
virtual void onOkay( wxCommandEvent& event ) { event.Skip(); }
virtual void onCancel( wxCommandEvent& event ) { event.Skip(); }
@@ -1264,7 +1281,7 @@ class AboutDlgGenerated : public wxDialog
wxFlexGridSizer* fgSizerTranslators;
wxStaticLine* m_staticline36;
wxBoxSizer* bSizerStdButtons;
- wxButton* m_buttonShowDonationDetails;
+ wxButton* m_buttonShowSupporterDetails;
zen::BitmapTextButton* m_buttonDonate2;
wxButton* m_buttonClose;
@@ -1273,7 +1290,7 @@ class AboutDlgGenerated : public wxDialog
virtual void onDonate( wxCommandEvent& event ) { event.Skip(); }
virtual void onOpenForum( wxCommandEvent& event ) { event.Skip(); }
virtual void onSendEmail( wxCommandEvent& event ) { event.Skip(); }
- virtual void onShowDonationDetails( wxCommandEvent& event ) { event.Skip(); }
+ virtual void onShowSupporterDetails( wxCommandEvent& event ) { event.Skip(); }
virtual void onOkay( wxCommandEvent& event ) { event.Skip(); }
diff --git a/FreeFileSync/Source/ui/gui_status_handler.cpp b/FreeFileSync/Source/ui/gui_status_handler.cpp
index 43d39981..60a836cc 100644
--- a/FreeFileSync/Source/ui/gui_status_handler.cpp
+++ b/FreeFileSync/Source/ui/gui_status_handler.cpp
@@ -514,7 +514,7 @@ StatusHandlerFloatingDialog::DlgOptions StatusHandlerFloatingDialog::showResult(
}
if (suspend) //*before* showing results dialog
- try
+ try
{
suspendSystem(); //throw FileError
}
diff --git a/FreeFileSync/Source/ui/log_panel.cpp b/FreeFileSync/Source/ui/log_panel.cpp
index bd291972..2e453b06 100644
--- a/FreeFileSync/Source/ui/log_panel.cpp
+++ b/FreeFileSync/Source/ui/log_panel.cpp
@@ -202,7 +202,7 @@ public:
}();
if (drawBottomLine)
- dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight() + wxPoint(1, 0));
+ dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight() + wxPoint(1, 0)); //DrawLine() doesn't draw last pixel!
//--------------------------------------------------------
}
diff --git a/FreeFileSync/Source/ui/main_dlg.cpp b/FreeFileSync/Source/ui/main_dlg.cpp
index 935d5d0f..54ba9497 100644
--- a/FreeFileSync/Source/ui/main_dlg.cpp
+++ b/FreeFileSync/Source/ui/main_dlg.cpp
@@ -200,7 +200,14 @@ public:
LocalPairConfig getValues() const
{
- return LocalPairConfig(folderSelectorLeft_.getPath(), folderSelectorRight_.getPath(), this->getCompConfig(), this->getSyncConfig(), this->getFilterConfig());
+ return
+ {
+ folderSelectorLeft_ .getPath(),
+ folderSelectorRight_.getPath(),
+ this->getCompConfig(),
+ this->getSyncConfig(),
+ this->getFilterConfig()
+ };
}
private:
@@ -1181,8 +1188,6 @@ void MainDialog::setGlobalCfgOnInit(const XmlGlobalSettings& globalSettings)
auiMgr_.GetPane(m_panelSearch).Hide(); //no need to show it on startup
auiMgr_.GetPane(m_panelLog ).Hide(); //
- m_menuItemCheckVersionAuto->Check(updateCheckActive(globalCfg_.lastUpdateCheck));
-
auiMgr_.Update();
}
@@ -1288,14 +1293,14 @@ std::vector<FileSystemObject*> expandSelectionForPartialSync(const std::vector<F
assert(dynamic_cast<FilePair*>(output.back())->getMoveRef() == file.getId());
break;
- case SO_CREATE_NEW_LEFT:
- case SO_CREATE_NEW_RIGHT:
+ case SO_CREATE_LEFT:
+ case SO_CREATE_RIGHT:
case SO_DELETE_LEFT:
case SO_DELETE_RIGHT:
case SO_OVERWRITE_LEFT:
case SO_OVERWRITE_RIGHT:
- case SO_COPY_METADATA_TO_LEFT:
- case SO_COPY_METADATA_TO_RIGHT:
+ case SO_RENAME_LEFT:
+ case SO_RENAME_RIGHT:
case SO_UNRESOLVED_CONFLICT:
case SO_DO_NOTHING:
case SO_EQUAL:
@@ -1465,14 +1470,34 @@ std::vector<FileSystemObject*> MainDialog::getTreeSelection() const
void MainDialog::copyToAlternateFolder(const std::vector<FileSystemObject*>& selectionL,
const std::vector<FileSystemObject*>& selectionR)
{
- if (std::all_of(selectionL.begin(), selectionL.end(), [](const FileSystemObject* fsObj) { return fsObj->isEmpty<SelectSide::left >(); }) &&
- /**/std::all_of(selectionR.begin(), selectionR.end(), [](const FileSystemObject* fsObj) { return fsObj->isEmpty<SelectSide::right>(); }))
- /**/return; //harmonize with onGridContextRim(): this function should be a no-op iff context menu option is disabled!
+ std::vector<const FileSystemObject*> copyLeft;
+ std::vector<const FileSystemObject*> copyRight;
+
+ for (const FileSystemObject* fsObj : selectionL)
+ if (!fsObj->isEmpty<SelectSide::left>())
+ copyLeft.push_back(fsObj);
+
+ for (const FileSystemObject* fsObj : selectionR)
+ if (!fsObj->isEmpty<SelectSide::right>())
+ copyRight.push_back(fsObj);
+
+ if (copyLeft.empty() && copyRight.empty())
+ return; //harmonize with onGridContextRim(): this function should be a no-op iff context menu option is disabled!
+
+ const int itemCount = static_cast<int>(copyLeft.size() + copyRight.size());
+ std::wstring itemList;
+
+ for (const FileSystemObject* fsObj : copyLeft)
+ itemList += AFS::getDisplayPath(fsObj->getAbstractPath<SelectSide::left>()) + L'\n';
+
+ for (const FileSystemObject* fsObj : copyRight)
+ itemList += AFS::getDisplayPath(fsObj->getAbstractPath<SelectSide::right>()) + L'\n';
+ //------------------------------------------------------------------
FocusPreserver fp;
if (showCopyToDialog(this,
- selectionL, selectionR,
+ itemList, itemCount,
globalCfg_.mainDlg.copyToCfg.targetFolderPath,
globalCfg_.mainDlg.copyToCfg.targetFolderLastSelected,
globalCfg_.mainDlg.copyToCfg.folderHistory, globalCfg_.folderHistoryMax,
@@ -1494,7 +1519,7 @@ void MainDialog::copyToAlternateFolder(const std::vector<FileSystemObject*>& sel
globalCfg_.soundFileAlertPending);
try
{
- fff::copyToAlternateFolder(selectionL, selectionR,
+ fff::copyToAlternateFolder(copyLeft, copyRight,
globalCfg_.mainDlg.copyToCfg.targetFolderPath,
globalCfg_.mainDlg.copyToCfg.keepRelPaths,
globalCfg_.mainDlg.copyToCfg.overwriteIfExists,
@@ -1515,13 +1540,26 @@ void MainDialog::copyToAlternateFolder(const std::vector<FileSystemObject*>& sel
void MainDialog::deleteSelectedFiles(const std::vector<FileSystemObject*>& selectionL,
const std::vector<FileSystemObject*>& selectionR, bool moveToRecycler)
{
- if (std::all_of(selectionL.begin(), selectionL.end(), [](const FileSystemObject* fsObj) { return fsObj->isEmpty<SelectSide::left >(); }) &&
- /**/std::all_of(selectionR.begin(), selectionR.end(), [](const FileSystemObject* fsObj) { return fsObj->isEmpty<SelectSide::right>(); }))
- /**/return; //harmonize with onGridContextRim(): this function should be a no-op iff context menu option is disabled!
+ std::vector<FileSystemObject*> deleteLeft = selectionL;
+ std::vector<FileSystemObject*> deleteRight = selectionR;
- FocusPreserver fp;
+ std::erase_if(deleteLeft, [](const FileSystemObject* fsObj) { return fsObj->isEmpty<SelectSide::left >(); });
+ std::erase_if(deleteRight, [](const FileSystemObject* fsObj) { return fsObj->isEmpty<SelectSide::right>(); });
+
+ if (deleteLeft.empty() && deleteRight.empty())
+ return; //harmonize with onGridContextRim(): this function should be a no-op iff context menu option is disabled!
+
+ const int itemCount = static_cast<int>(deleteLeft.size() + deleteRight.size());
+ std::wstring itemList;
- const auto& [itemList, itemCount] = getSelectedItemsAsString(selectionL, selectionR);
+ for (const FileSystemObject* fsObj : deleteLeft)
+ itemList += AFS::getDisplayPath(fsObj->getAbstractPath<SelectSide::left>()) + L'\n';
+
+ for (const FileSystemObject* fsObj : deleteRight)
+ itemList += AFS::getDisplayPath(fsObj->getAbstractPath<SelectSide::right>()) + L'\n';
+ //------------------------------------------------------------------
+
+ FocusPreserver fp;
if (showDeleteDialog(this, itemList, itemCount,
moveToRecycler) != ConfirmationButton::accept)
@@ -1541,11 +1579,11 @@ void MainDialog::deleteSelectedFiles(const std::vector<FileSystemObject*>& selec
globalCfg_.soundFileAlertPending);
try
{
- deleteFromGridAndHD(selectionL, selectionR,
- extractDirectionCfg(folderCmp_, getConfig().mainCfg),
- moveToRecycler,
- globalCfg_.warnDlgs.warnRecyclerMissing,
- statusHandler); //throw CancelProcess
+ deleteFiles(deleteLeft, deleteRight,
+ extractDirectionCfg(folderCmp_, getConfig().mainCfg),
+ moveToRecycler,
+ globalCfg_.warnDlgs.warnRecyclerMissing,
+ statusHandler); //throw CancelProcess
}
catch (CancelProcess&) {}
@@ -1565,27 +1603,26 @@ void MainDialog::deleteSelectedFiles(const std::vector<FileSystemObject*>& selec
void MainDialog::renameSelectedFiles(const std::vector<FileSystemObject*>& selectionL,
const std::vector<FileSystemObject*>& selectionR)
{
- warn_static("finish")
- if (std::all_of(selectionL.begin(), selectionL.end(), [](const FileSystemObject* fsObj) { return fsObj->isEmpty<SelectSide::left >(); }) &&
- /**/std::all_of(selectionR.begin(), selectionR.end(), [](const FileSystemObject* fsObj) { return fsObj->isEmpty<SelectSide::right>(); }))
- /**/return; //harmonize with onGridContextRim(): this function should be a no-op iff context menu option is disabled!
+ std::vector<FileSystemObject*> renameLeft = selectionL;
+ std::vector<FileSystemObject*> renameRight = selectionR;
+
+ std::erase_if(renameLeft, [](const FileSystemObject* fsObj) { return fsObj->isEmpty<SelectSide::left >(); });
+ std::erase_if(renameRight, [](const FileSystemObject* fsObj) { return fsObj->isEmpty<SelectSide::right>(); });
+
+ if (renameLeft.empty() && renameRight.empty())
+ return; //harmonize with onGridContextRim(): this function should be a no-op iff context menu option is disabled!
+ //------------------------------------------------------------------
FocusPreserver fp;
- std::vector<Zstring> fileNamesOld
- {
- Zstr("Season 1, Episode 21 - The Arsenal of Freedom.mkv"),
- Zstr("Season 1, Episode 22 - Symbiosis.mkv"),
- Zstr("Season 1, Episode 23 - Skin of Evil.mkv"),
- Zstr("Season 1, Episode 24 - We'll Always Have Paris.mkv"),
- Zstr("Season 1, Episode 25 - Conspiracy.mkv"),
- Zstr("Season 1, Episode 26 - The Neutral Zone.mkv"),
- Zstr("Season 2, Episode 01 - The Child.mkv"),
- Zstr("Season 2, Episode 02 - Where Silence Has Lease.mkv"),
- Zstr("Season 2, Episode 03 - Elementary, Dear Data.mkv"),
- };
- std::vector<Zstring> fileNamesNew;
+ std::vector<Zstring> fileNamesOld;
+ for (const FileSystemObject* fsObj : renameLeft)
+ fileNamesOld.push_back(fsObj->getItemName<SelectSide::left>());
+ for (const FileSystemObject* fsObj : renameRight)
+ fileNamesOld.push_back(fsObj->getItemName<SelectSide::right>());
+
+ std::vector<Zstring> fileNamesNew;
if (showRenameDialog(this, fileNamesOld, fileNamesNew) != ConfirmationButton::accept)
return;
@@ -1603,12 +1640,10 @@ void MainDialog::renameSelectedFiles(const std::vector<FileSystemObject*>& selec
globalCfg_.soundFileAlertPending);
try
{
-
- //deleteFromGridAndHD(selectionL, selectionR,
- // extractDirectionCfg(folderCmp_, getConfig().mainCfg),
- // moveToRecycler,
- // globalCfg_.warnDlgs.warnRecyclerMissing,
- // statusHandler); //throw CancelProcess
+ renameItems(renameLeft, {fileNamesNew.data(), renameLeft.size()},
+ renameRight, {fileNamesNew.data() + renameLeft.size(), fileNamesNew.size() - renameLeft.size()},
+ extractDirectionCfg(folderCmp_, getConfig().mainCfg),
+ statusHandler); //throw CancelProcess
}
catch (CancelProcess&) {}
@@ -1618,9 +1653,6 @@ void MainDialog::renameSelectedFiles(const std::vector<FileSystemObject*>& selec
append(fullSyncLog_->log, r.errorLog.ref());
fullSyncLog_->totalTime += r.summary.totalTime;
- ////remove rows that are empty: just a beautification, invalid rows shouldn't cause issues
- //filegrid::getDataView(*m_gridMainC).removeInvalidRows();
-
updateGui();
}
@@ -2222,6 +2254,11 @@ void MainDialog::onTreeKeyEvent(wxKeyEvent& event)
else
switch (keyCode)
{
+ case WXK_F2:
+ case WXK_NUMPAD_F2:
+ renameSelectedFiles(selection, selection);
+ return;
+
case WXK_RETURN:
case WXK_NUMPAD_ENTER:
startSyncForSelecction(selection);
@@ -2317,10 +2354,7 @@ void MainDialog::onGridKeyEvent(wxKeyEvent& event, Grid& grid, bool leftSide)
{
case WXK_F2:
case WXK_NUMPAD_F2:
- warn_static("finish")
-#if 0
renameSelectedFiles(selectionL, selectionR);
-#endif
return;
case WXK_RETURN:
@@ -2519,6 +2553,49 @@ void MainDialog::onTreeGridSelection(GridSelectEvent& event)
}
+namespace
+{
+template <SelectSide side>
+std::vector<Zstring> getFilterPhrasesRel(const std::vector<FileSystemObject*>& selection)
+{
+ std::vector<Zstring> output;
+ for (const FileSystemObject* fsObj : selection)
+ {
+ //#pragma warning(suppress: 6011) -> fsObj bound in this context!
+ Zstring phrase = FILE_NAME_SEPARATOR + fsObj->getRelativePath<side>();
+
+ const bool isFolder = dynamic_cast<const FolderPair*>(fsObj) != nullptr;
+ if (isFolder)
+ phrase += FILE_NAME_SEPARATOR;
+
+ output.push_back(std::move(phrase));
+ }
+ return output;
+}
+
+
+Zstring getFilterPhraseRel(const std::vector<FileSystemObject*>& selectionL,
+ const std::vector<FileSystemObject*>& selectionR)
+{
+ std::vector<Zstring> phrases;
+ append(phrases, getFilterPhrasesRel<SelectSide::left >(selectionL));
+ append(phrases, getFilterPhrasesRel<SelectSide::right>(selectionR));
+
+ removeDuplicatesStable(phrases, [](const Zstring& lhs, const Zstring& rhs) { return compareNoCase(lhs, rhs) < 0; });
+ //ignore case, just like path filter
+
+ Zstring relPathPhrase;
+ for (const Zstring& phrase : phrases)
+ {
+ relPathPhrase += phrase;
+ relPathPhrase += Zstr('\n');
+ }
+
+ return trimCpy(relPathPhrase);
+}
+}
+
+
void MainDialog::onTreeGridContext(GridContextMenuEvent& event)
{
const std::vector<FileSystemObject*>& selection = getTreeSelection(); //referenced by lambdas!
@@ -2549,32 +2626,62 @@ void MainDialog::onTreeGridContext(GridContextMenuEvent& event)
//----------------------------------------------------------------------------------------------------
auto addFilterMenu = [&](const std::wstring& label, const wxImage& img, bool include)
{
-
- if (selection.size() == 1)
+ if (selection.empty())
+ menu.addItem(label, nullptr, img, false /*enabled*/);
+ else if (selection.size() == 1)
{
ContextMenu submenu;
const bool isFolder = dynamic_cast<const FolderPair*>(selection[0]) != nullptr;
- //by short name
- Zstring labelShort = Zstring(Zstr("*")) + FILE_NAME_SEPARATOR + selection[0]->getItemNameAny();
+ const Zstring& relPathL = selection[0]->getRelativePath<SelectSide::left >();
+ const Zstring& relPathR = selection[0]->getRelativePath<SelectSide::right>();
+
+ //by extension
+ const Zstring extensionL = getFileExtension(relPathL);
+ const Zstring extensionR = getFileExtension(relPathR);
+ if (!extensionL.empty())
+ submenu.addItem(L"*." + utfTo<wxString>(extensionL),
+ [this, extensionL, include] { addFilterPhrase(Zstr("*.") + extensionL, include, false /*requireNewLine*/); });
+
+ if (!extensionR.empty() && !equalNoCase(extensionL, extensionR)) //rare, but possible (e.g. after manual rename)
+ submenu.addItem(L"*." + utfTo<wxString>(extensionR),
+ [this, extensionR, include] { addFilterPhrase(Zstr("*.") + extensionR, include, false /*requireNewLine*/); });
+
+ //by file name
+ Zstring filterPhraseNameL = Zstring(Zstr("*")) + FILE_NAME_SEPARATOR + getItemName(relPathL);
+ Zstring filterPhraseNameR = Zstring(Zstr("*")) + FILE_NAME_SEPARATOR + getItemName(relPathR);
if (isFolder)
- labelShort += FILE_NAME_SEPARATOR;
- submenu.addItem(utfTo<wxString>(labelShort), [this, &selection, include] { filterShortname(*selection[0], include); });
+ {
+ filterPhraseNameL += FILE_NAME_SEPARATOR;
+ filterPhraseNameR += FILE_NAME_SEPARATOR;
+ }
+
+ submenu.addItem(utfTo<wxString>(filterPhraseNameL),
+ [this, filterPhraseNameL, include] { addFilterPhrase(filterPhraseNameL, include, true /*requireNewLine*/); });
+
+ if (!equalNoCase(filterPhraseNameL, filterPhraseNameR)) //rare, but possible (ignore case, just like path filter)
+ submenu.addItem(utfTo<wxString>(filterPhraseNameR),
+ [this, filterPhraseNameR, include] { addFilterPhrase(filterPhraseNameR, include, true /*requireNewLine*/); });
//by relative path
- Zstring labelRel = FILE_NAME_SEPARATOR + selection[0]->getRelativePathAny();
+ Zstring filterPhraseRelL = FILE_NAME_SEPARATOR + relPathL;
+ Zstring filterPhraseRelR = FILE_NAME_SEPARATOR + relPathR;
if (isFolder)
- labelRel += FILE_NAME_SEPARATOR;
- submenu.addItem(utfTo<wxString>(labelRel), [this, &selection, include] { filterItems(selection, include); });
+ {
+ filterPhraseRelL += FILE_NAME_SEPARATOR;
+ filterPhraseRelR += FILE_NAME_SEPARATOR;
+ }
+ submenu.addItem(utfTo<wxString>(filterPhraseRelL), [this, filterPhraseRelL, include] { addFilterPhrase(filterPhraseRelL, include, true /*requireNewLine*/); });
+
+ if (!equalNoCase(filterPhraseRelL, filterPhraseRelR)) //rare, but possible
+ submenu.addItem(utfTo<wxString>(filterPhraseRelR), [this, filterPhraseRelR, include] { addFilterPhrase(filterPhraseRelR, include, true /*requireNewLine*/); });
menu.addSubmenu(label, submenu, img);
}
- else if (selection.size() > 1) //by relative path
+ else //by relative path
menu.addItem(label + L" <" + _("multiple selection") + L">",
- [this, &selection, include] { filterItems(selection, include); }, img);
- else
- menu.addItem(label, nullptr, img, false /*enabled*/);
+ [this, &selection, include] { addFilterPhrase(getFilterPhraseRel(selection, selection), include, true /*requireNewLine*/); }, img);
};
addFilterMenu(_("&Include via filter:"), loadImage("filter_include", getDefaultMenuIconSize()), true);
addFilterMenu(_("&Exclude via filter:"), loadImage("filter_exclude", getDefaultMenuIconSize()), false);
@@ -2587,38 +2694,26 @@ void MainDialog::onTreeGridContext(GridContextMenuEvent& event)
const bool selectionContainsItemsToSync = [&]
{
for (FileSystemObject* fsObj : expandSelectionForPartialSync(selection))
- switch (fsObj->getSyncOperation())
- {
- case SO_CREATE_NEW_LEFT:
- case SO_CREATE_NEW_RIGHT:
- case SO_DELETE_LEFT:
- case SO_DELETE_RIGHT:
- case SO_MOVE_LEFT_FROM:
- case SO_MOVE_LEFT_TO:
- case SO_MOVE_RIGHT_FROM:
- case SO_MOVE_RIGHT_TO:
- case SO_OVERWRITE_LEFT:
- case SO_OVERWRITE_RIGHT:
- case SO_COPY_METADATA_TO_LEFT:
- case SO_COPY_METADATA_TO_RIGHT:
- return true;
-
- case SO_UNRESOLVED_CONFLICT:
- case SO_DO_NOTHING:
- case SO_EQUAL:
- break;
- }
+ if (getEffectiveSyncDir(fsObj->getSyncOperation()) != SyncDirection::none)
+ return true;
return false;
}();
menu.addSeparator();
menu.addItem(_("&Synchronize selection") + L"\tEnter", [&] { startSyncForSelecction(selection); }, loadImage("start_sync_selection", getDefaultMenuIconSize()), selectionContainsItemsToSync);
//----------------------------------------------------------------------------------------------------
- const bool haveItemsSelected = std::any_of(selection.begin(), selection.end(), [](const FileSystemObject* fsObj) { return !fsObj->isEmpty<SelectSide::left>() || !fsObj->isEmpty<SelectSide::right>(); });
+ const ptrdiff_t itemsSelected =
+ std::count_if(selection.begin(), selection.end(), [](const FileSystemObject* fsObj) { return !fsObj->isEmpty<SelectSide::left >(); }) +
+ std::count_if(selection.begin(), selection.end(), [](const FileSystemObject* fsObj) { return !fsObj->isEmpty<SelectSide::right>(); });
+
//menu.addSeparator();
- //menu.addItem(_("&Copy to...") + L"\tCtrl+T", [&] { copyToAlternateFolder(selection, selection); }, wxNullImage, haveItemsSelected);
+ //menu.addItem(_("&Copy to...") + L"\tCtrl+T", [&] { copyToAlternateFolder(selection, selection); }, wxNullImage, itemsSelected > 0);
//----------------------------------------------------------------------------------------------------
menu.addSeparator();
- menu.addItem(_("&Delete") + L"\t(Shift+)Del", [&] { deleteSelectedFiles(selection, selection, true /*moveToRecycler*/); }, imgTrashSmall_, haveItemsSelected);
+
+ menu.addItem((itemsSelected > 1 ? _("Multi-&Rename") : _("&Rename")) + L"\tF2",
+ [&] { renameSelectedFiles(selection, selection); }, loadImage("rename", getDefaultMenuIconSize()), itemsSelected > 0);
+
+ menu.addItem(_("&Delete") + L"\t(Shift+)Del", [&] { deleteSelectedFiles(selection, selection, true /*moveToRecycler*/); }, imgTrashSmall_, itemsSelected > 0);
menu.popup(*m_gridOverview, event.mousePos_);
}
@@ -2690,40 +2785,46 @@ void MainDialog::onGridContextRim(const std::vector<FileSystemObject*>& selectio
//----------------------------------------------------------------------------------------------------
auto addFilterMenu = [&](const wxString& label, const wxImage& img, bool include)
{
- if (selection.size() == 1)
+ if (selectionL.empty() && selectionR.empty())
+ menu.addItem(label, nullptr, img, false /*enabled*/);
+ else if (selectionL.size() + selectionR.size() == 1)
{
ContextMenu submenu;
- const bool isFolder = dynamic_cast<const FolderPair*>(selection[0]) != nullptr;
+ const bool isFolder = dynamic_cast<const FolderPair*>((!selectionL.empty() ? selectionL : selectionR)[0]) != nullptr;
+ const Zstring& relPath = !selectionL.empty() ?
+ selectionL[0]->getRelativePath<SelectSide::left >() :
+ selectionR[0]->getRelativePath<SelectSide::right>();
//by extension
- if (!isFolder)
+ if (const Zstring extension = getFileExtension(relPath);
+ !extension.empty())
+ submenu.addItem(L"*." + utfTo<wxString>(extension), [this, extension, include]
{
- const Zstring extension = getFileExtension(selection[0]->getItemNameAny());
- if (!extension.empty())
- submenu.addItem(L"*." + utfTo<wxString>(extension),
- [this, extension, include] { filterExtension(extension, include); });
- }
+ addFilterPhrase(Zstr("*.") + extension, include, false /*requireNewLine*/);
+ });
- //by short name
- Zstring labelShort = Zstring(Zstr("*")) + FILE_NAME_SEPARATOR + selection[0]->getItemNameAny();
+ //by file name
+ Zstring filterPhraseName = Zstring(Zstr("*")) + FILE_NAME_SEPARATOR + getItemName(relPath);
if (isFolder)
- labelShort += FILE_NAME_SEPARATOR;
- submenu.addItem(utfTo<wxString>(labelShort), [this, &selection, include] { filterShortname(*selection[0], include); });
+ filterPhraseName += FILE_NAME_SEPARATOR;
+
+ submenu.addItem(utfTo<wxString>(filterPhraseName), [this, filterPhraseName, include]
+ {
+ addFilterPhrase(filterPhraseName, include, true /*requireNewLine*/);
+ });
//by relative path
- Zstring labelRel = FILE_NAME_SEPARATOR + selection[0]->getRelativePathAny();
+ Zstring filterPhraseRel = FILE_NAME_SEPARATOR + relPath;
if (isFolder)
- labelRel += FILE_NAME_SEPARATOR;
- submenu.addItem(utfTo<wxString>(labelRel), [this, &selection, include] { filterItems(selection, include); });
+ filterPhraseRel += FILE_NAME_SEPARATOR;
+ submenu.addItem(utfTo<wxString>(filterPhraseRel), [this, filterPhraseRel, include] { addFilterPhrase(filterPhraseRel, include, true /*requireNewLine*/); });
menu.addSubmenu(label, submenu, img);
}
- else if (selection.size() > 1) //by relative path
+ else //by relative path
menu.addItem(label + L" <" + _("multiple selection") + L">",
- [this, &selection, include] { filterItems(selection, include); }, img);
- else
- menu.addItem(label, nullptr, img, false /*enabled*/);
+ [this, &selectionL, &selectionR, include] { addFilterPhrase(getFilterPhraseRel(selectionL, selectionR), include, true /*requireNewLine*/); }, img);
};
addFilterMenu(_("&Include via filter:"), loadImage("filter_include", getDefaultMenuIconSize()), true);
addFilterMenu(_("&Exclude via filter:"), loadImage("filter_exclude", getDefaultMenuIconSize()), false);
@@ -2736,27 +2837,8 @@ void MainDialog::onGridContextRim(const std::vector<FileSystemObject*>& selectio
const bool selectionContainsItemsToSync = [&]
{
for (FileSystemObject* fsObj : expandSelectionForPartialSync(selection))
- switch (fsObj->getSyncOperation())
- {
- case SO_CREATE_NEW_LEFT:
- case SO_CREATE_NEW_RIGHT:
- case SO_DELETE_LEFT:
- case SO_DELETE_RIGHT:
- case SO_MOVE_LEFT_FROM:
- case SO_MOVE_LEFT_TO:
- case SO_MOVE_RIGHT_FROM:
- case SO_MOVE_RIGHT_TO:
- case SO_OVERWRITE_LEFT:
- case SO_OVERWRITE_RIGHT:
- case SO_COPY_METADATA_TO_LEFT:
- case SO_COPY_METADATA_TO_RIGHT:
- return true;
-
- case SO_UNRESOLVED_CONFLICT:
- case SO_DO_NOTHING:
- case SO_EQUAL:
- break;
- }
+ if (getEffectiveSyncDir(fsObj->getSyncOperation()) != SyncDirection::none)
+ return true;
return false;
}();
menu.addSeparator();
@@ -2788,21 +2870,19 @@ void MainDialog::onGridContextRim(const std::vector<FileSystemObject*>& selectio
}
}
//----------------------------------------------------------------------------------------------------
- const bool haveItemsSelected =
- std::any_of(selectionL.begin(), selectionL.end(), [](const FileSystemObject* fsObj) { return !fsObj->isEmpty<SelectSide::left >(); }) ||
- std::any_of(selectionR.begin(), selectionR.end(), [](const FileSystemObject* fsObj) { return !fsObj->isEmpty<SelectSide::right>(); });
+ const ptrdiff_t itemsSelected =
+ std::count_if(selectionL.begin(), selectionL.end(), [](const FileSystemObject* fsObj) { return !fsObj->isEmpty<SelectSide::left >(); }) +
+ std::count_if(selectionR.begin(), selectionR.end(), [](const FileSystemObject* fsObj) { return !fsObj->isEmpty<SelectSide::right>(); });
menu.addSeparator();
- menu.addItem(_("&Copy to...") + L"\tCtrl+T", [&] { copyToAlternateFolder(selectionL, selectionR); }, wxNullImage, haveItemsSelected);
+ menu.addItem(_("&Copy to...") + L"\tCtrl+T", [&] { copyToAlternateFolder(selectionL, selectionR); }, wxNullImage, itemsSelected > 0);
//----------------------------------------------------------------------------------------------------
menu.addSeparator();
- warn_static("finish")
-#if 0
- menu.addItem(_("&Rename") + L"\tF2", [&] { renameSelectedFiles(selectionL, selectionR); }, loadImage("rename", getDefaultMenuIconSize()), haveItemsSelected);
-#endif
+ menu.addItem((itemsSelected > 1 ? _("Multi-&Rename") : _("&Rename")) + L"\tF2",
+ [&] { renameSelectedFiles(selectionL, selectionR); }, loadImage("rename", getDefaultMenuIconSize()), itemsSelected > 0);
- menu.addItem(_("&Delete") + L"\t(Shift+)Del", [&] { deleteSelectedFiles(selectionL, selectionR, true /*moveToRecycler*/); }, imgTrashSmall_, haveItemsSelected);
+ menu.addItem(_("&Delete") + L"\t(Shift+)Del", [&] { deleteSelectedFiles(selectionL, selectionR, true /*moveToRecycler*/); }, imgTrashSmall_, itemsSelected > 0);
menu.popup(leftSide ? *m_gridMainL : *m_gridMainR, mousePos);
}
@@ -2861,48 +2941,6 @@ void MainDialog::addFilterPhrase(const Zstring& phrase, bool include, bool requi
}
-void MainDialog::filterExtension(const Zstring& extension, bool include)
-{
- assert(!extension.empty());
- addFilterPhrase(Zstr("*.") + extension, include, false);
-}
-
-
-void MainDialog::filterShortname(const FileSystemObject& fsObj, bool include)
-{
- Zstring phrase = Zstring(Zstr("*")) + FILE_NAME_SEPARATOR + fsObj.getItemNameAny();
- const bool isFolder = dynamic_cast<const FolderPair*>(&fsObj) != nullptr;
- if (isFolder)
- phrase += FILE_NAME_SEPARATOR;
-
- addFilterPhrase(phrase, include, true);
-}
-
-
-void MainDialog::filterItems(const std::vector<FileSystemObject*>& selection, bool include)
-{
- if (!selection.empty())
- {
- Zstring phrase;
- for (auto it = selection.begin(); it != selection.end(); ++it)
- {
- FileSystemObject* fsObj = *it;
-
- if (it != selection.begin())
- phrase += Zstr('\n');
-
- //#pragma warning(suppress: 6011) -> fsObj bound in this context!
- phrase += FILE_NAME_SEPARATOR + fsObj->getRelativePathAny();
-
- const bool isFolder = dynamic_cast<const FolderPair*>(fsObj) != nullptr;
- if (isFolder)
- phrase += FILE_NAME_SEPARATOR;
- }
- addFilterPhrase(phrase, include, true);
- }
-}
-
-
void MainDialog::onGridLabelContextC(GridLabelClickEvent& event)
{
ContextMenu menu;
@@ -3136,11 +3174,11 @@ void MainDialog::onSyncSettingsContext(wxEvent& event)
auto setVariant = [&](SyncVariant var)
{
- currentCfg_.mainCfg.syncCfg.directionCfg.var = var;
+ currentCfg_.mainCfg.syncCfg.directionCfg = getDefaultSyncCfg(var);
applySyncDirections();
};
- const auto activeSyncVar = getConfig().mainCfg.syncCfg.directionCfg.var;
+ const SyncVariant activeSyncVar = getSyncVariant(getConfig().mainCfg.syncCfg.directionCfg);
auto addVariantItem = [&](SyncVariant syncVar, const char* iconName)
{
@@ -3151,7 +3189,7 @@ void MainDialog::onSyncSettingsContext(wxEvent& event)
addVariantItem(SyncVariant::twoWay, "sync_twoway");
addVariantItem(SyncVariant::mirror, "sync_mirror");
addVariantItem(SyncVariant::update, "sync_update");
- addVariantItem(SyncVariant::custom, "sync_custom");
+ //addVariantItem(SyncVariant::custom, "sync_custom"); -> doesn't make sense, does it?
menu.popup(*m_bpButtonSyncContext, {m_bpButtonSyncContext->GetSize().x, 0});
}
@@ -3759,7 +3797,7 @@ void MainDialog::renameSelectedCfgHistoryItem()
wxTextEntryDialog cfgRenameDlg(this, _("New name:"), _("Rename Configuration"), utfTo<wxString>(cfgNameOld));
wxTextValidator inputValidator(wxFILTER_EXCLUDE_CHAR_LIST);
- inputValidator.SetCharExcludes(LR"(<>:"/\|?*)"); //forbidden chars for file names (at least on Windows)
+ inputValidator.SetCharExcludes(LR"(<>:"/\|?*)"); //chars forbidden for file names (at least on Windows)
//https://docs.microsoft.com/de-de/windows/win32/fileio/naming-a-file#naming-conventions
cfgRenameDlg.SetTextValidator(inputValidator);
@@ -3841,15 +3879,6 @@ void MainDialog::onCfgGridContext(GridContextMenuEvent& event)
assert(false);
//--------------------------------------------------------------------------------------------------------
- const bool renameEnabled = [&]
- {
- if (!selectedRows.empty())
- if (const ConfigView::Details* cfg = cfggrid::getDataView(*m_gridCfgHistory).getItem(selectedRows[0]))
- return !cfg->isLastRunCfg;
- return false;
- }();
- menu.addItem(_("&Rename...") + L"\tF2", [this] { renameSelectedCfgHistoryItem (); }, wxNullImage, renameEnabled);
- //--------------------------------------------------------------------------------------------------------
ContextMenu submenu;
auto applyBackColor = [this, &cfgFilePaths](const wxColor& col)
@@ -3981,6 +4010,16 @@ void MainDialog::onCfgGridContext(GridContextMenuEvent& event)
showInFileManager, imgFileManagerSmall_, !selectedRows.empty());
menu.addSeparator();
//--------------------------------------------------------------------------------------------------------
+ const bool renameEnabled = [&]
+ {
+ if (!selectedRows.empty())
+ if (const ConfigView::Details* cfg = cfggrid::getDataView(*m_gridCfgHistory).getItem(selectedRows[0]))
+ return !cfg->isLastRunCfg;
+ return false;
+ }();
+ menu.addItem(_("&Rename") + L"\tF2", [this] { renameSelectedCfgHistoryItem (); }, loadImage("rename", getDefaultMenuIconSize()), renameEnabled);
+
+ //--------------------------------------------------------------------------------------------------------
menu.addItem(_("&Hide") + L"\tDel", [this] { removeSelectedCfgHistoryItems(false /*deleteFromDisk*/); }, wxNullImage, !selectedRows.empty());
menu.addItem(_("&Delete") + L"\tShift+Del", [this] { removeSelectedCfgHistoryItems(true /*deleteFromDisk*/); }, imgTrashSmall_, !selectedRows.empty());
//--------------------------------------------------------------------------------------------------------
@@ -4456,6 +4495,7 @@ void MainDialog::onCompare(wxCommandEvent& event)
//wxBusyCursor dummy; -> redundant: progress already shown in progress dialog!
FocusPreserver fp; //e.g. keep focus on config panel after pressing F5
+
//give nice hint on what's next to do if user manually clicked on compare
assert(m_buttonCompare->GetId() != wxID_ANY);
if (fp.getFocusId() == m_buttonCompare->GetId())
@@ -4572,8 +4612,8 @@ void MainDialog::updateGui()
updateUnsavedCfgStatus();
const auto& mainCfg = getConfig().mainCfg;
- const std::optional<CompareVariant> cmpVar = getCompVariant(mainCfg);
- const std::optional<SyncVariant> syncVar = getSyncVariant(mainCfg);
+ const std::optional<CompareVariant> cmpVar = getCommonCompVariant(mainCfg);
+ const std::optional<SyncVariant> syncVar = getCommonSyncVariant(mainCfg);
const char* cmpVarIconName = nullptr;
if (cmpVar)
@@ -4705,7 +4745,7 @@ void MainDialog::onStartSync(wxCommandEvent& event)
bool dontShowAgain = false;
if (showSyncConfirmationDlg(this, false /*syncSelection*/,
- getSyncVariant(guiCfg.mainCfg),
+ getCommonSyncVariant(guiCfg.mainCfg),
SyncStatistics(folderCmp_),
dontShowAgain) != ConfirmationButton::accept)
return;
@@ -4913,6 +4953,12 @@ void MainDialog::onStartSync(wxCommandEvent& event)
updateGui();
+ warn_static("fix?")
+ //"The little statistics box in the lower right corner clears right after the sync
+ //completes. It used to clear after you close the progress window. I like to know
+ //the statistics, but usually I just start the sync and walk away. When I come back,
+ //the statistics are gone."
+
//---------------------------------------------------------------------------
const StatusHandlerFloatingDialog::DlgOptions dlgOpt = statusHandler.showResult();
@@ -4948,15 +4994,15 @@ void MainDialog::onStartSync(wxCommandEvent& event)
namespace
{
-void appendInactive(ContainerObject& hierObj, std::vector<FileSystemObject*>& inactiveItems)
+void appendInactive(ContainerObject& conObj, std::vector<FileSystemObject*>& inactiveItems)
{
- for (FilePair& file : hierObj.refSubFiles())
+ for (FilePair& file : conObj.refSubFiles())
if (!file.isActive())
inactiveItems.push_back(&file);
- for (SymlinkPair& symlink : hierObj.refSubLinks())
+ for (SymlinkPair& symlink : conObj.refSubLinks())
if (!symlink.isActive())
inactiveItems.push_back(&symlink);
- for (FolderPair& folder : hierObj.refSubFolders())
+ for (FolderPair& folder : conObj.refSubFolders())
{
if (!folder.isActive())
inactiveItems.push_back(&folder);
@@ -4976,8 +5022,8 @@ void MainDialog::startSyncForSelecction(const std::vector<FileSystemObject*>& se
{
switch (fsObj->getSyncOperation())
{
- case SO_CREATE_NEW_LEFT:
- case SO_CREATE_NEW_RIGHT:
+ case SO_CREATE_LEFT:
+ case SO_CREATE_RIGHT:
case SO_DELETE_LEFT:
case SO_DELETE_RIGHT:
case SO_MOVE_LEFT_FROM:
@@ -4986,8 +5032,8 @@ void MainDialog::startSyncForSelecction(const std::vector<FileSystemObject*>& se
case SO_MOVE_RIGHT_TO:
case SO_OVERWRITE_LEFT:
case SO_OVERWRITE_RIGHT:
- case SO_COPY_METADATA_TO_LEFT:
- case SO_COPY_METADATA_TO_RIGHT:
+ case SO_RENAME_LEFT:
+ case SO_RENAME_RIGHT:
basePairsSelect.insert(&fsObj->base());
break;
@@ -5051,7 +5097,7 @@ void MainDialog::startSyncForSelecction(const std::vector<FileSystemObject*>& se
if (showSyncConfirmationDlg(this,
true /*syncSelection*/,
- getSyncVariant(guiCfg.mainCfg),
+ getCommonSyncVariant(guiCfg.mainCfg),
SyncStatistics(folderCmpSelect),
dontShowAgain) != ConfirmationButton::accept)
return;
@@ -6198,25 +6244,6 @@ void MainDialog::onMenuCheckVersion(wxCommandEvent& event)
}
-void MainDialog::onMenuCheckVersionAutomatically(wxCommandEvent& event)
-{
- if (updateCheckActive(globalCfg_.lastUpdateCheck))
- disableUpdateCheck(globalCfg_.lastUpdateCheck);
- else
- globalCfg_.lastUpdateCheck = 0; //reset to GlobalSettings.xml default value!
-
- m_menuItemCheckVersionAuto->Check(updateCheckActive(globalCfg_.lastUpdateCheck));
-
- if (shouldRunAutomaticUpdateCheck(globalCfg_.lastUpdateCheck))
- {
- flashStatusInfo(_("Searching for program updates..."));
- //synchronous update check is sufficient here:
- automaticUpdateCheckEval(*this, globalCfg_.lastUpdateCheck, globalCfg_.lastOnlineVersion,
- automaticUpdateCheckRunAsync(automaticUpdateCheckPrepare(*this).ref()).ref());
- }
-}
-
-
void MainDialog::onStartupUpdateCheck(wxIdleEvent& event)
{
//execute just once per startup!
@@ -6225,12 +6252,12 @@ void MainDialog::onStartupUpdateCheck(wxIdleEvent& event)
auto showNewVersionReminder = [this]
{
- if (!globalCfg_.lastOnlineVersion.empty() && haveNewerVersionOnline(globalCfg_.lastOnlineVersion))
+ if (haveNewerVersionOnline(globalCfg_.lastOnlineVersion))
{
auto menu = new wxMenu();
wxMenuItem* newItem = new wxMenuItem(menu, wxID_ANY, _("&Show details"));
Bind(wxEVT_COMMAND_MENU_SELECTED, [this](wxCommandEvent&) { checkForUpdateNow(*this, globalCfg_.lastOnlineVersion); }, newItem->GetId());
- //show changelog + handle Donation Edition auto-updater (including expiration)
+ //show changelog + handle Supporter Edition auto-updater (including expiration)
menu->Append(newItem); //pass ownership
const std::wstring& blackStar = utfTo<std::wstring>("★");
@@ -6238,7 +6265,7 @@ void MainDialog::onStartupUpdateCheck(wxIdleEvent& event)
}
};
- if (shouldRunAutomaticUpdateCheck(globalCfg_.lastUpdateCheck))
+ if (automaticUpdateCheckDue(globalCfg_.lastUpdateCheck))
{
flashStatusInfo(_("Searching for program updates..."));
diff --git a/FreeFileSync/Source/ui/main_dlg.h b/FreeFileSync/Source/ui/main_dlg.h
index a8759173..d159106f 100644
--- a/FreeFileSync/Source/ui/main_dlg.h
+++ b/FreeFileSync/Source/ui/main_dlg.h
@@ -237,9 +237,6 @@ private:
void setLastOperationLog(const ProcessSummary& summary, const std::shared_ptr<const zen::ErrorLog>& errorLog);
void showLogPanel(bool show);
- void filterExtension(const Zstring& extension, bool include);
- void filterShortname(const FileSystemObject& fsObj, bool include);
- void filterItems(const std::vector<FileSystemObject*>& selection, bool include);
void addFilterPhrase(const Zstring& phrase, bool include, bool requireNewLine);
void onTopFolderPairAdd (wxCommandEvent& event) override;
@@ -277,7 +274,6 @@ private:
void onMenuResetLayout (wxCommandEvent& event) override { resetLayout(); }
void onMenuFindItem (wxCommandEvent& event) override { showFindPanel(true /*show*/); } //CTRL + F
void onMenuCheckVersion (wxCommandEvent& event) override;
- void onMenuCheckVersionAutomatically(wxCommandEvent& event) override;
void onMenuAbout (wxCommandEvent& event) override;
void onShowHelp (wxCommandEvent& event) override { wxLaunchDefaultBrowser(L"https://freefilesync.org/manual.php?topic=freefilesync"); }
void onMenuQuit (wxCommandEvent& event) override { Close(); }
diff --git a/FreeFileSync/Source/ui/rename_dlg.cpp b/FreeFileSync/Source/ui/rename_dlg.cpp
index 50798c69..d1c09cbb 100644
--- a/FreeFileSync/Source/ui/rename_dlg.cpp
+++ b/FreeFileSync/Source/ui/rename_dlg.cpp
@@ -5,9 +5,11 @@
// *****************************************************************************
#include "rename_dlg.h"
-#include "gui_generated.h"
+#include <chrono>
+#include <wx/valtext.h>
#include <wx+/window_layout.h>
#include <wx+/image_resources.h>
+#include "gui_generated.h"
#include "../base/multi_rename.h"
@@ -32,13 +34,50 @@ public:
fileNamesOld_(fileNamesOld),
renameBuf_(renameBuf) {}
- void updatePreview(const std::wstring& renamePhrase)
+ bool updatePreview(std::wstring_view renamePhrase, size_t selectBegin, size_t selectEnd) //support polling
{
- fileNamesNew_ = resolvePlaceholderPhrase(renamePhrase, renameBuf_.ref());
- assert(fileNamesNew_.size() == fileNamesOld_.size());
+ //normalize input: trim and adapt selection
+ {
+ const std::wstring_view renamePhraseTrm = trimCpy(renamePhrase);
+
+ if (selectBegin <= selectEnd && selectEnd <= renamePhrase.size())
+ {
+ selectBegin -= std::min(selectBegin, makeUnsigned(renamePhraseTrm.data() - renamePhrase.data())); //careful:
+ selectEnd -= std::min(selectEnd, makeUnsigned(renamePhraseTrm.data() - renamePhrase.data())); //avoid underflow
+
+ selectBegin = std::min(selectBegin, renamePhraseTrm.size());
+ selectEnd = std::min(selectEnd, renamePhraseTrm.size());
+ }
+ else
+ {
+ assert(false);
+ selectBegin = selectEnd = 0;
+ }
+
+ renamePhrase = renamePhraseTrm;
+ }
+
+ auto currentPhrase = std::make_tuple(renamePhrase, selectBegin, selectEnd);
+ if (currentPhrase != lastUsedPhrase_) //only update when needed
+ {
+ lastUsedPhrase_ = currentPhrase;
+
+ fileNamesNewSelectBefore_ = resolvePlaceholderPhrase(renamePhrase.substr(0, selectBegin), renameBuf_.ref());
+ fileNamesNewSelected_ = resolvePlaceholderPhrase(renamePhrase.substr(selectBegin, selectEnd - selectBegin), renameBuf_.ref());
+ fileNamesNewSelectAfter_ = resolvePlaceholderPhrase(renamePhrase.substr(selectEnd), renameBuf_.ref());
+
+ assert(fileNamesNewSelectBefore_.size() == fileNamesOld_.size());
+ assert(fileNamesNewSelected_ .size() == fileNamesOld_.size());
+ assert(fileNamesNewSelectAfter_ .size() == fileNamesOld_.size());
+
+ previewChangeTime_ = std::chrono::steady_clock::now();
+ return true;
+ }
+ else
+ return false;
}
- const std::vector<std::wstring>& getNewNames() const { return fileNamesNew_; }
+ std::vector<std::wstring> getNewNames() const { return resolvePlaceholderPhrase(std::get<std::wstring>(lastUsedPhrase_), renameBuf_.ref()); }
size_t getRowCount() const override { return fileNamesOld_.size(); }
@@ -51,7 +90,7 @@ public:
return fileNamesOld_[row];
case ColumnTypeRename::newName:
- return fileNamesNew_[row];
+ return fileNamesNewSelectBefore_[row] + fileNamesNewSelected_[row] + fileNamesNewSelectAfter_[row];
}
return std::wstring();
}
@@ -69,7 +108,42 @@ public:
rectTmp.x += getColumnGapLeft();
rectTmp.width -= getColumnGapLeft();
- drawCellText(dc, rectTmp, getValue(row, colType));
+
+ switch (static_cast<ColumnTypeRename>(colType))
+ {
+ case ColumnTypeRename::oldName:
+ drawCellText(dc, rectTmp, getValue(row, colType));
+ break;
+
+ case ColumnTypeRename::newName:
+ {
+ const wxSize extentBefore = dc.GetTextExtent(fileNamesNewSelectBefore_[row]);
+ drawCellText(dc, rectTmp, fileNamesNewSelectBefore_[row], wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, &extentBefore);
+ rectTmp.x += extentBefore.GetWidth();
+ rectTmp.width -= extentBefore.GetWidth();
+
+ if (!fileNamesNewSelected_[row].empty()) //highlight text selection:
+ {
+ const wxSize extentSelect = dc.GetTextExtent(fileNamesNewSelected_[row]);
+ clearArea(dc, {rectTmp.x, rectTmp.y, extentSelect.GetWidth(), rectTmp.height}, wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT));
+
+ wxDCTextColourChanger textColor(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT)); //accessibility: always set both foreground AND background colors!
+ drawCellText(dc, rectTmp, fileNamesNewSelected_[row], wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, &extentSelect);
+ rectTmp.x += extentSelect.GetWidth();
+ rectTmp.width -= extentSelect.GetWidth();
+ }
+
+ drawCellText(dc, rectTmp, fileNamesNewSelectAfter_[row]);
+
+ if (fileNamesNewSelected_[row].empty() && //draw input cursor
+ (showCursor_ || std::chrono::steady_clock::now() < previewChangeTime_ + std::chrono::milliseconds(400)))
+ {
+ wxDCPenChanger dummy(dc, wxPen(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT), fastFromDIP(1)));
+ dc.DrawLine(rectTmp.GetTopLeft(), rectTmp.GetBottomLeft() + wxPoint(0, 1)); //DrawLine() doesn't draw last pixel!
+ }
+ }
+ break;
+ }
}
int getBestSize(wxDC& dc, size_t row, ColumnType colType) override
@@ -93,9 +167,20 @@ public:
return std::wstring();
}
+ void setCursorShown(bool show) { showCursor_ = show; }
+
private:
const std::vector<std::wstring> fileNamesOld_;
- std::vector<std::wstring> fileNamesNew_;
+
+ std::tuple<std::wstring /*renamePhrase*/, size_t /*selectBegin*/, size_t /*selectEnd*/> lastUsedPhrase_;
+
+ std::vector<std::wstring> fileNamesNewSelectBefore_{fileNamesOld_.size()};
+ std::vector<std::wstring> fileNamesNewSelected_ {fileNamesOld_.size()};
+ std::vector<std::wstring> fileNamesNewSelectAfter_ {fileNamesOld_.size()};
+
+ bool showCursor_ = false;
+ std::chrono::steady_clock::time_point previewChangeTime_ = std::chrono::steady_clock::now();
+
const SharedRef<const RenameBuf> renameBuf_;
};
@@ -106,17 +191,24 @@ public:
RenameDialog(wxWindow* parent, const std::vector<std::wstring>& fileNamesOld, std::vector<Zstring>& fileNamesNew);
private:
- void onTypingName(wxCommandEvent& event) override { updatePreview(); }
- void onOkay (wxCommandEvent& event) override;
- void onCancel (wxCommandEvent& event) override { EndModal(static_cast<int>(ConfirmationButton::cancel)); }
- void onClose (wxCloseEvent& event) override { EndModal(static_cast<int>(ConfirmationButton::cancel)); }
+ void onOkay (wxCommandEvent& event) override;
+ void onCancel(wxCommandEvent& event) override { EndModal(static_cast<int>(ConfirmationButton::cancel)); }
+ void onClose (wxCloseEvent& event) override { EndModal(static_cast<int>(ConfirmationButton::cancel)); }
void onLocalKeyEvent(wxKeyEvent& event);
void updatePreview()
{
- getDataView().updatePreview(copyStringTo<std::wstring>(trimCpy(m_textCtrlNewName->GetValue())));
- m_gridRenamePreview->Refresh();
+ const std::wstring renamePhrase = copyStringTo<std::wstring>(m_textCtrlNewName->GetValue());
+
+ long selectBegin = 0;
+ long selectEnd = 0;
+ m_textCtrlNewName->GetSelection(&selectBegin, &selectEnd);
+
+ assert(selectBegin == m_textCtrlNewName->GetInsertionPoint()); //apparently this is true for all Win/macOS/Linux
+
+ if (getDataView().updatePreview(renamePhrase, selectBegin, selectEnd))
+ m_gridRenamePreview->Refresh();
}
GridDataRename& getDataView()
@@ -126,6 +218,9 @@ private:
throw std::runtime_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] m_gridRenamePreview was not initialized.");
}
+ wxTimer timer_; //poll for text selection changes
+ wxTimer timerCursor_; //second timer just for cursor blinking
+
//output-only parameters:
std::vector<Zstring>& fileNamesNewOut_;
};
@@ -146,33 +241,118 @@ RenameDialog::RenameDialog(wxWindow* parent,
m_staticTextHeader->SetLabelText(_P("Do you really want to rename the following item?",
"Do you really want to rename the following %x items?", fileNamesOld.size()));
- m_staticTextHeader->Wrap(fastFromDIP(460)); //needs to be reapplied after SetLabel()
-
m_buttonOK->SetLabelText(wxControl::RemoveMnemonics(_("&Rename"))); //no access key needed: use ENTER!
- const auto& [renamePhrase, renameBuf] = getPlaceholderPhrase(fileNamesOld);
+ auto [renamePhrase, renameBuf] = getPlaceholderPhrase(fileNamesOld);
+ trim(renamePhrase); //leading/trailing whitespace makes no sense for file names
+
+
+ std::wstring placeholders;
+ for (const wchar_t c : renamePhrase)
+ if (isRenamePlaceholderChar(c))
+ placeholders += c;
+
+ m_staticTextPlaceholderDescription->SetLabelText(placeholders + L": " + m_staticTextPlaceholderDescription->GetLabelText());
//-----------------------------------------------------------
+ m_gridRenamePreview->setDataProvider(std::make_shared<GridDataRename>(fileNamesOld, renameBuf));
m_gridRenamePreview->showRowLabel(false);
m_gridRenamePreview->setRowHeight(m_gridRenamePreview->getMainWin().GetCharHeight() + fastFromDIP(1) /*extra space*/);
- m_gridRenamePreview->setColumnConfig(
+
+ //-----------------------------------------------------------
+ if (fileNamesOld.size() > 1) //calculate reasonable default preview grid size
{
- {static_cast<ColumnType>(ColumnTypeRename::oldName), 0, 1, true},
- {static_cast<ColumnType>(ColumnTypeRename::newName), 0, 1, true},
- });
+ //quick and dirty: get (likely) maximum string width while avoiding excessive wxDC::GetTextExtent() calls
+ std::vector<std::wstring> names = fileNamesOld;
+ auto itMax10 = names.end() - std::min<size_t>(10, names.size()); //find the 10 longest strings according to std::wstring::size()
+ if (itMax10 != names.begin())
+ std::nth_element(names.begin(), itMax10, names.end(),
+ /**/[](const std::wstring& lhs, const std::wstring& rhs) { return lhs.size() < rhs.size(); }); //complexity: O(n)
+
+ wxMemoryDC dc; //the context used for bitmaps
+ setScaleFactor(dc, getDisplayScaleFactor());
+ dc.SetFont(m_gridRenamePreview->GetFont()); //the font parameter of GetTextExtent() is not evaluated on OS X, wxWidgets 2.9.5, so apply it to the DC directly!
+
+ int maxStringWidth = 0;
+ std::for_each(itMax10, names.end(), [&](const std::wstring& str)
+ {
+ maxStringWidth = std::max(maxStringWidth, dc.GetTextExtent(str).GetWidth());
+ });
- m_gridRenamePreview->setDataProvider(std::make_shared<GridDataRename>(fileNamesOld, renameBuf));
+ const int defaultColWidthOld = maxStringWidth + 2 * GridData::getColumnGapLeft() + fastFromDIP(1) /*border*/ + fastFromDIP(10) /*extra space: less cramped*/;
+ const int defaultColWidthNew = maxStringWidth + 2 * GridData::getColumnGapLeft() + fastFromDIP(1) /*border*/ + fastFromDIP(50) /*extra space: for longer new name*/;
+
+ m_gridRenamePreview->setColumnConfig(
+ {
+ {static_cast<ColumnType>(ColumnTypeRename::oldName), defaultColWidthOld, 0, true}, //"old name" is fixed =>
+ {static_cast<ColumnType>(ColumnTypeRename::newName), -defaultColWidthOld, 1, true}, //stretch "new name" only
+ });
- warn_static("make smarter!")
- m_gridRenamePreview->SetMinSize({fastFromDIP(500), fastFromDIP(200)});
+ const int previewDefaultWidth = std::min(defaultColWidthOld + defaultColWidthNew + fastFromDIP(25), //scroll bar width (guess!)
+ fastFromDIP(900));
+
+ const int previewDefaultHeight = std::min(m_gridRenamePreview->getColumnLabelHeight() +
+ static_cast<int>(fileNamesOld.size()) * m_gridRenamePreview->getRowHeight(),
+ fastFromDIP(400));
+
+ m_gridRenamePreview->SetMinSize({previewDefaultWidth, previewDefaultHeight});
+
+ m_staticTextHeader->Wrap(std::max(previewDefaultWidth, fastFromDIP(400))); //needs to be reapplied after SetLabel()
+ }
+ else //renaming single file
+ {
+ m_gridRenamePreview ->Hide();
+ m_staticlinePreview ->Hide();
+ m_staticTextPlaceholderDescription->Hide();
+
+ wxMemoryDC dc; //the context used for bitmaps
+ setScaleFactor(dc, getDisplayScaleFactor());
+ dc.SetFont(m_textCtrlNewName->GetFont()); //the font parameter of GetTextExtent() is not evaluated on OS X, wxWidgets 2.9.5, so apply it to the DC directly!
+
+ const int textCtrlDefaultWidth = std::min(dc.GetTextExtent(renamePhrase).GetWidth() + 20 /*borders (non-DIP!)*/ +
+ fastFromDIP(50) /*extra space: for longer new name*/,
+ fastFromDIP(900));
+ m_textCtrlNewName->SetMinSize({textCtrlDefaultWidth, -1});
+
+ m_staticTextHeader->Wrap(std::max(textCtrlDefaultWidth, fastFromDIP(400))); //needs to be reapplied after SetLabel()
+ }
//-----------------------------------------------------------
+ wxTextValidator inputValidator(wxFILTER_EXCLUDE_CHAR_LIST);
+ inputValidator.SetCharExcludes(LR"(<>:"/\|?*)"); //chars forbidden for file names (at least on Windows)
+ //https://docs.microsoft.com/de-de/windows/win32/fileio/naming-a-file#naming-conventions
+ m_textCtrlNewName->SetValidator(inputValidator);
m_textCtrlNewName->ChangeValue(renamePhrase);
- updatePreview();
+
+ if (fileNamesOld.size() > 1)
+ {
+ timer_.Bind(wxEVT_TIMER, [this](wxTimerEvent& event) { updatePreview(); }); //poll to detect text selection changes
+ timer_.Start(100 /*unit: [ms]*/);
+
+ timerCursor_.Bind(wxEVT_TIMER, [this, show = true](wxTimerEvent& event) mutable //trigger blinking cursor
+ {
+ getDataView().setCursorShown(show);
+ m_gridRenamePreview->Refresh();
+ show = !show;
+ });
+ timerCursor_.Start(wxCaret::GetBlinkTime() /*unit: [ms]*/);
+ }
+
+ m_textCtrlNewName->Bind(wxEVT_COMMAND_TEXT_UPDATED, [this, renamePhraseOld = renamePhrase, needPreview = fileNamesOld.size() > 1](wxCommandEvent& event)
+ {
+ if (needPreview)
+ updatePreview(); //(almost?) redundant, considering timer_ is doing the same!?
+
+ //disable OK button, until user changes input
+ const std::wstring renamePhraseNew = trimCpy(copyStringTo<std::wstring>(m_textCtrlNewName->GetValue()));
+ m_buttonOK->Enable(!renamePhraseNew.empty() && renamePhraseNew != renamePhraseOld); //supports polling
+ });
+ m_buttonOK->Disable();
Bind(wxEVT_CHAR_HOOK, [this](wxKeyEvent& event) { onLocalKeyEvent(event); }); //enable dialog-specific key events
+ //-----------------------------------------------------------
GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize()
#ifdef __WXGTK3__
Show(); //GTK3 size calculation requires visible window: https://github.com/wxWidgets/wxWidgets/issues/16088
@@ -181,16 +361,28 @@ RenameDialog::RenameDialog(wxWindow* parent,
Center(); //needs to be re-applied after a dialog size change!
m_textCtrlNewName->SetFocus(); //[!] required *before* SetSelection() on wxGTK
+ //-----------------------------------------------------------
+
+ //macOS issue: the *whole* text control is selected by default, unless we SetSelection() *after* wxDialog::Show()!
+ CallAfter([this, nameCount = fileNamesOld.size(), renamePhrase = renamePhrase]
+ {
+ //pre-select name part that user will most likely change
+ //assert(contains(renamePhrase, L'\u2776') == nameCount > 1); -> fails, if user selects same item on left and right grid
+ auto it = std::find_if(renamePhrase.begin(), renamePhrase.end(), isRenamePlaceholderChar); //❶
+ if (it == renamePhrase.end())
+ it = findLast(renamePhrase.begin(), renamePhrase.end(), L'.'); //select everything except file extension
+
+ if (it == renamePhrase.end())
+ m_textCtrlNewName->SelectAll();
+ else
+ {
+ const long selectEnd = static_cast<long>(it - renamePhrase.begin());
+ m_textCtrlNewName->SetSelection(0, selectEnd);
+ }
+
+ updatePreview(); //consider new selection
+ });
- //pre-select name part that user will most likely change
- assert(contains(renamePhrase, L'\u2776') == fileNamesOld.size() > 1);
- auto it = fileNamesOld.size() == 1 ?
- findLast (renamePhrase.begin(), renamePhrase.end(), L'.') : //select everything except file extension
- std::find(renamePhrase.begin(), renamePhrase.end(), L'\u2776'); //❶
- if (it == renamePhrase.end())
- m_textCtrlNewName->SelectAll();
- else
- m_textCtrlNewName->SetSelection(0, static_cast<long>(it - renamePhrase.begin()));
}
@@ -202,6 +394,8 @@ void RenameDialog::onLocalKeyEvent(wxKeyEvent& event)
void RenameDialog::onOkay(wxCommandEvent& event)
{
+ updatePreview(); //ensure GridDataRename::getNewNames() is current
+
fileNamesNewOut_.clear();
for (const std::wstring& newName : getDataView().getNewNames())
fileNamesNewOut_.push_back(utfTo<Zstring>(newName));
@@ -217,7 +411,7 @@ ConfirmationButton fff::showRenameDialog(wxWindow* parent,
{
std::vector<std::wstring> namesOld;
for (const Zstring& name : fileNamesOld)
- namesOld.push_back(utfTo<std::wstring>(name));
+ namesOld.push_back(utfTo<std::wstring>(getUnicodeNormalForm(name))); //[!] don't care about Normalization form differences!
RenameDialog dlg(parent, namesOld, fileNamesNew);
return static_cast<ConfirmationButton>(dlg.ShowModal());
diff --git a/FreeFileSync/Source/ui/small_dlgs.cpp b/FreeFileSync/Source/ui/small_dlgs.cpp
index 16140cca..2724de44 100644
--- a/FreeFileSync/Source/ui/small_dlgs.cpp
+++ b/FreeFileSync/Source/ui/small_dlgs.cpp
@@ -78,7 +78,6 @@ AboutDlg::AboutDlg(wxWindow* parent) : AboutDlgGenerated(parent)
setBitmapTextLabel(*m_bpButtonEmail, loadImage("ffs_email"), wxString() + L"zenju@" + /*don't leave full email in either source or binary*/ L"freefilesync.org");
m_bpButtonEmail->SetToolTip( wxString() + L"mailto:zenju@" + /*don't leave full email in either source or binary*/ L"freefilesync.org");
-
wxString build = utfTo<wxString>(ffsVersion);
const wchar_t* const SPACED_BULLET = L" \u2022 ";
@@ -108,7 +107,7 @@ AboutDlg::AboutDlg(wxWindow* parent) : AboutDlgGenerated(parent)
setRelativeFontSize(*m_buttonDonate1, 1.25);
setBitmapTextLabel(*m_buttonDonate1, loadImage("ffs_heart", fastFromDIP(28)), m_buttonDonate1->GetLabelText());
- m_buttonShowDonationDetails->Hide();
+ m_buttonShowSupporterDetails->Hide();
m_buttonDonate2->Hide();
}
@@ -896,8 +895,7 @@ class CopyToDialog : public CopyToDlgGenerated
{
public:
CopyToDialog(wxWindow* parent,
- std::span<const FileSystemObject* const> rowsOnLeft,
- std::span<const FileSystemObject* const> rowsOnRight,
+ const std::wstring& itemList, int itemCount,
Zstring& targetFolderPath, Zstring& targetFolderLastSelected,
std::vector<Zstring>& folderHistory, size_t folderHistoryMax,
Zstring& sftpKeyFileLastSelected,
@@ -922,8 +920,7 @@ private:
CopyToDialog::CopyToDialog(wxWindow* parent,
- std::span<const FileSystemObject* const> rowsOnLeft,
- std::span<const FileSystemObject* const> rowsOnRight,
+ const std::wstring& itemList, int itemCount,
Zstring& targetFolderPath, Zstring& targetFolderLastSelected,
std::vector<Zstring>& folderHistory, size_t folderHistoryMax,
Zstring& sftpKeyFileLastSelected,
@@ -955,8 +952,6 @@ CopyToDialog::CopyToDialog(wxWindow* parent,
=> another Unity problem like the following?
https://github.com/wxWidgets/wxWidgets/issues/14823 "Menu not disabled when showing modal dialogs in wxGTK under Unity" */
- const auto& [itemList, itemCount] = getSelectedItemsAsString(rowsOnLeft, rowsOnRight);
-
m_staticTextHeader->SetLabelText(_P("Copy the following item to another folder?",
"Copy the following %x items to another folder?", itemCount));
m_staticTextHeader->Wrap(fastFromDIP(460)); //needs to be reapplied after SetLabel()
@@ -1011,15 +1006,14 @@ void CopyToDialog::onOkay(wxCommandEvent& event)
}
ConfirmationButton fff::showCopyToDialog(wxWindow* parent,
- std::span<const FileSystemObject* const> rowsOnLeft,
- std::span<const FileSystemObject* const> rowsOnRight,
+ const std::wstring& itemList, int itemCount,
Zstring& targetFolderPath, Zstring& targetFolderLastSelected,
std::vector<Zstring>& folderHistory, size_t folderHistoryMax,
Zstring& sftpKeyFileLastSelected,
bool& keepRelPaths,
bool& overwriteIfExists)
{
- CopyToDialog dlg(parent, rowsOnLeft, rowsOnRight, targetFolderPath, targetFolderLastSelected, folderHistory, folderHistoryMax, sftpKeyFileLastSelected, keepRelPaths, overwriteIfExists);
+ CopyToDialog dlg(parent, itemList, itemCount, targetFolderPath, targetFolderLastSelected, folderHistory, folderHistoryMax, sftpKeyFileLastSelected, keepRelPaths, overwriteIfExists);
return static_cast<ConfirmationButton>(dlg.ShowModal());
}
@@ -2061,13 +2055,15 @@ ActivationDlg::ActivationDlg(wxWindow* parent,
{
setStandardButtonLayout(*bSizerStdButtons, StdButtons().setCancel(m_buttonCancel));
- SetTitle(L"FreeFileSync " + utfTo<std::wstring>(ffsVersion) + L" [" + _("Donation Edition") + L']');
+ std::wstring title = L"FreeFileSync " + utfTo<std::wstring>(ffsVersion);
+ SetTitle(title);
+
+ //setMainInstructionFont(*m_staticTextMain);
m_richTextLastError ->SetMinSize({-1, m_richTextLastError ->GetCharHeight() * 7});
m_richTextManualActivationUrl->SetMinSize({-1, m_richTextManualActivationUrl->GetCharHeight() * 4});
m_textCtrlOfflineActivationKey->SetMinSize({fastFromDIP(260), -1});
- //setMainInstructionFont(*m_staticTextMain);
-
+
setImage(*m_bitmapActivation, loadImage("internet"));
m_textCtrlOfflineActivationKey->ForceUpper();
diff --git a/FreeFileSync/Source/ui/small_dlgs.h b/FreeFileSync/Source/ui/small_dlgs.h
index 81a3dd0d..2ed304d6 100644
--- a/FreeFileSync/Source/ui/small_dlgs.h
+++ b/FreeFileSync/Source/ui/small_dlgs.h
@@ -20,8 +20,7 @@ namespace fff
void showAboutDialog(wxWindow* parent);
zen::ConfirmationButton showCopyToDialog(wxWindow* parent,
- std::span<const FileSystemObject* const> rowsOnLeft,
- std::span<const FileSystemObject* const> rowsOnRight,
+ const std::wstring& itemList, int itemCount,
Zstring& targetFolderPath, Zstring& targetFolderLastSelected,
std::vector<Zstring>& folderPathHistory, size_t folderPathHistoryMax,
Zstring& sftpKeyFileLastSelected,
diff --git a/FreeFileSync/Source/ui/sync_cfg.cpp b/FreeFileSync/Source/ui/sync_cfg.cpp
index f1ab6afd..0a2a7baa 100644
--- a/FreeFileSync/Source/ui/sync_cfg.cpp
+++ b/FreeFileSync/Source/ui/sync_cfg.cpp
@@ -281,13 +281,13 @@ private:
EnumDescrList<UnitSize> enumSizeDescr_;
//------------- synchronization panel -----------------
- void onSyncTwoWay(wxCommandEvent& event) override { directionCfg_.var = SyncVariant::twoWay; updateSyncGui(); }
- void onSyncMirror(wxCommandEvent& event) override { directionCfg_.var = SyncVariant::mirror; updateSyncGui(); }
- void onSyncUpdate(wxCommandEvent& event) override { directionCfg_.var = SyncVariant::update; updateSyncGui(); }
- void onSyncCustom(wxCommandEvent& event) override { directionCfg_.var = SyncVariant::custom; updateSyncGui(); }
+ void onSyncTwoWay(wxCommandEvent& event) override { directionsCfg_ = getDefaultSyncCfg(SyncVariant::twoWay); updateSyncGui(); }
+ void onSyncMirror(wxCommandEvent& event) override { directionsCfg_ = getDefaultSyncCfg(SyncVariant::mirror); updateSyncGui(); }
+ void onSyncUpdate(wxCommandEvent& event) override { directionsCfg_ = getDefaultSyncCfg(SyncVariant::update); updateSyncGui(); }
+ void onSyncCustom(wxCommandEvent& event) override { directionsCfg_ = getDefaultSyncCfg(SyncVariant::custom); updateSyncGui(); }
void onToggleLocalSyncSettings(wxCommandEvent& event) override { updateSyncGui(); }
- void onToggleDetectMovedFiles (wxCommandEvent& event) override { directionCfg_.detectMovedFiles = !directionCfg_.detectMovedFiles; updateSyncGui(); } //parameter NOT owned by checkbox!
+ void onToggleUseDatabase (wxCommandEvent& event) override;
void onChanegVersioningStyle (wxCommandEvent& event) override { updateSyncGui(); }
void onToggleVersioningLimit (wxCommandEvent& event) override { updateSyncGui(); }
@@ -296,16 +296,24 @@ private:
void onSyncUpdateDouble(wxMouseEvent& event) override;
void onSyncCustomDouble(wxMouseEvent& event) override;
- void onExLeftSideOnly (wxCommandEvent& event) override;
- void onExRightSideOnly(wxCommandEvent& event) override;
- void onLeftNewer (wxCommandEvent& event) override;
- void onRightNewer (wxCommandEvent& event) override;
- void onDifferent (wxCommandEvent& event) override;
- void onConflict (wxCommandEvent& event) override;
-
- void onDeletionPermanent (wxCommandEvent& event) override { deletionVariant_ = DeletionVariant::permanent; updateSyncGui(); }
- void onDeletionRecycler (wxCommandEvent& event) override { deletionVariant_ = DeletionVariant::recycler; updateSyncGui(); }
- void onDeletionVersioning (wxCommandEvent& event) override { deletionVariant_ = DeletionVariant::versioning; updateSyncGui(); }
+ void onLeftOnly (wxCommandEvent& event) override { toggleSyncDirButton(&DirectionByDiff::leftOnly); }
+ void onRightOnly (wxCommandEvent& event) override { toggleSyncDirButton(&DirectionByDiff::rightOnly); }
+ void onLeftNewer (wxCommandEvent& event) override;
+ void onRightNewer(wxCommandEvent& event) override;
+ void onDifferent (wxCommandEvent& event) override;
+ void toggleSyncDirButton(SyncDirection DirectionByDiff::* dir);
+
+ void onLeftCreate (wxCommandEvent& event) override { toggleSyncDirButton(&DirectionByChange::left, &DirectionByChange::Changes::create); }
+ void onLeftUpdate (wxCommandEvent& event) override { toggleSyncDirButton(&DirectionByChange::left, &DirectionByChange::Changes::update); }
+ void onLeftDelete (wxCommandEvent& event) override { toggleSyncDirButton(&DirectionByChange::left, &DirectionByChange::Changes::delete_); }
+ void onRightCreate(wxCommandEvent& event) override { toggleSyncDirButton(&DirectionByChange::right, &DirectionByChange::Changes::create); }
+ void onRightUpdate(wxCommandEvent& event) override { toggleSyncDirButton(&DirectionByChange::right, &DirectionByChange::Changes::update); }
+ void onRightDelete(wxCommandEvent& event) override { toggleSyncDirButton(&DirectionByChange::right, &DirectionByChange::Changes::delete_); }
+ void toggleSyncDirButton(DirectionByChange::Changes DirectionByChange::* side, SyncDirection DirectionByChange::Changes::* dir);
+
+ void onDeletionPermanent (wxCommandEvent& event) override { deletionVariant_ = DeletionVariant::permanent; updateSyncGui(); }
+ void onDeletionRecycler (wxCommandEvent& event) override { deletionVariant_ = DeletionVariant::recycler; updateSyncGui(); }
+ void onDeletionVersioning(wxCommandEvent& event) override { deletionVariant_ = DeletionVariant::versioning; updateSyncGui(); }
void onToggleMiscOption(wxCommandEvent& event) override { updateMiscGui(); }
void onToggleMiscEmail (wxCommandEvent& event) override
@@ -323,11 +331,13 @@ private:
std::optional<SyncConfig> getSyncConfig() const;
void setSyncConfig(const SyncConfig* syncCfg);
+ bool leftRightNewerCombined() const;
+
void updateSyncGui();
//-----------------------------------------------------
//parameters with ownership NOT within GUI controls!
- SyncDirectionConfig directionCfg_;
+ SyncDirectionConfig directionsCfg_;
DeletionVariant deletionVariant_ = DeletionVariant::recycler; //use Recycler, delete permanently or move to user-defined location
const std::function<size_t(const Zstring& folderPathPhrase)> getDeviceParallelOps_;
@@ -368,7 +378,7 @@ private:
std::vector<LocalPairConfig> localPairCfg_;
int selectedPairIndexToShow_ = EMPTY_PAIR_INDEX_SELECTED;
- static const int EMPTY_PAIR_INDEX_SELECTED = -2;
+ static constexpr int EMPTY_PAIR_INDEX_SELECTED = -2;
bool showNotesPanel_ = false;
@@ -581,12 +591,12 @@ globalLogFolderPhrase_(globalLogFolderPhrase)
m_buttonUpdate->SetToolTip(getSyncVariantDescription(SyncVariant::update));
m_buttonCustom->SetToolTip(getSyncVariantDescription(SyncVariant::custom));
- setImage(*m_bitmapLeftOnly, mirrorIfRtl(greyScale(loadImage("cat_left_only" ))));
- setImage(*m_bitmapRightOnly, mirrorIfRtl(greyScale(loadImage("cat_right_only" ))));
- setImage(*m_bitmapLeftNewer, mirrorIfRtl(greyScale(loadImage("cat_left_newer" ))));
- setImage(*m_bitmapRightNewer, mirrorIfRtl(greyScale(loadImage("cat_right_newer"))));
- setImage(*m_bitmapDifferent, mirrorIfRtl(greyScale(loadImage("cat_different" ))));
- setImage(*m_bitmapConflict, mirrorIfRtl(greyScale(loadImage("cat_conflict" ))));
+ const int catSizeMax = loadImage("cat_left_only").GetWidth() * 8 / 10;
+ setImage(*m_bitmapLeftOnly, mirrorIfRtl(greyScale(loadImage("cat_left_only", catSizeMax))));
+ setImage(*m_bitmapRightOnly, mirrorIfRtl(greyScale(loadImage("cat_right_only", catSizeMax))));
+ setImage(*m_bitmapLeftNewer, mirrorIfRtl(greyScale(loadImage("cat_left_newer", catSizeMax))));
+ setImage(*m_bitmapRightNewer, mirrorIfRtl(greyScale(loadImage("cat_right_newer", catSizeMax))));
+ setImage(*m_bitmapDifferent, mirrorIfRtl(greyScale(loadImage("cat_different", catSizeMax))));
setRelativeFontSize(*m_buttonTwoWay, 1.25);
setRelativeFontSize(*m_buttonMirror, 1.25);
@@ -684,7 +694,7 @@ globalLogFolderPhrase_(globalLogFolderPhrase)
//temporarily set main config as reference for window min size calculations:
globalPairCfg_ = GlobalPairConfig();
- globalPairCfg_.syncCfg.directionCfg.var = SyncVariant::mirror;
+ globalPairCfg_.syncCfg.directionCfg = getDefaultSyncCfg(SyncVariant::twoWay);
globalPairCfg_.syncCfg.deletionVariant = DeletionVariant::versioning;
globalPairCfg_.syncCfg.versioningFolderPhrase = Zstr("dummy");
globalPairCfg_.syncCfg.versioningStyle = VersioningStyle::timestampFile;
@@ -702,8 +712,8 @@ globalLogFolderPhrase_(globalLogFolderPhrase)
#endif
Center(); //needs to be re-applied after a dialog size change!
- //keep stable sizer height: "two way" description is smaller than grid of sync directions
- bSizerSyncDirHolder ->SetMinSize(-1, bSizerSyncDirections ->GetSize().y);
+ //keep stable sizer height: change-based directions are taller than difference-based ones => init with SyncVariant::twoWay
+ bSizerSyncDirHolder ->SetMinSize(-1, bSizerSyncDirsChanges ->GetSize().y);
bSizerVersioningHolder->SetMinSize(-1, bSizerVersioningHolder->GetSize().y);
unselectFolderPairConfig(false /*validateParams*/);
@@ -934,13 +944,15 @@ FilterConfig ConfigDialog::getFilterConfig() const
const Zstring& includeFilter = utfTo<Zstring>(m_textCtrlInclude->GetValue());
const Zstring& exludeFilter = utfTo<Zstring>(m_textCtrlExclude->GetValue());
- return FilterConfig(includeFilter, exludeFilter,
- m_spinCtrlTimespan->GetValue(),
- getEnumVal(enumTimeDescr_, *m_choiceUnitTimespan),
- m_spinCtrlMinSize->GetValue(),
- getEnumVal(enumSizeDescr_, *m_choiceUnitMinSize),
- m_spinCtrlMaxSize->GetValue(),
- getEnumVal(enumSizeDescr_, *m_choiceUnitMaxSize));
+ return
+ {
+ includeFilter, exludeFilter,
+ makeUnsigned(m_spinCtrlTimespan->GetValue()),
+ getEnumVal(enumTimeDescr_, *m_choiceUnitTimespan),
+ makeUnsigned(m_spinCtrlMinSize->GetValue()),
+ getEnumVal(enumSizeDescr_, *m_choiceUnitMinSize),
+ makeUnsigned(m_spinCtrlMaxSize->GetValue()),
+ getEnumVal(enumSizeDescr_, *m_choiceUnitMaxSize)};
}
@@ -982,6 +994,19 @@ void ConfigDialog::updateFilterGui()
}
+void ConfigDialog::onToggleUseDatabase(wxCommandEvent& event)
+{
+ if (const DirectionByDiff* diffDirs = std::get_if<DirectionByDiff>(&directionsCfg_.dirs))
+ directionsCfg_.dirs = getChangesDirDefault(*diffDirs);
+ else
+ {
+ const DirectionByChange& changeDirs = std::get<DirectionByChange>(directionsCfg_.dirs);
+ directionsCfg_.dirs = getDiffDirDefault(changeDirs);
+ }
+ updateSyncGui();
+}
+
+
void ConfigDialog::onSyncTwoWayDouble(wxMouseEvent& event)
{
wxCommandEvent dummy;
@@ -1031,145 +1056,116 @@ void toggleSyncDirection(SyncDirection& current)
}
-void toggleCustomSyncConfig(SyncDirectionConfig& directionCfg, SyncDirection& custSyncDir)
+void ConfigDialog::toggleSyncDirButton(SyncDirection DirectionByDiff::* dir)
{
- switch (directionCfg.var)
- {
- case SyncVariant::twoWay:
- assert(false);
- break;
- case SyncVariant::mirror:
- case SyncVariant::update:
- directionCfg.custom = extractDirections(directionCfg);
- break;
- case SyncVariant::custom:
- break;
- }
- SyncDirection syncDirOld = custSyncDir;
- toggleSyncDirection(custSyncDir);
-
- //some config optimization: if custom settings happen to match "mirror" or "update", just switch variant
- const DirectionSet mirrorSet = []
- {
- SyncDirectionConfig mirrorCfg;
- mirrorCfg.var = SyncVariant::mirror;
- return extractDirections(mirrorCfg);
- }();
-
- const DirectionSet updateSet = []
- {
- SyncDirectionConfig updateCfg;
- updateCfg.var = SyncVariant::update;
- return extractDirections(updateCfg);
- }();
-
- if (directionCfg.custom == mirrorSet)
- {
- directionCfg.var = SyncVariant::mirror;
- custSyncDir = syncDirOld;
- }
- else if (directionCfg.custom == updateSet)
+ if (DirectionByDiff* diffDirs = std::get_if<DirectionByDiff>(&directionsCfg_.dirs))
{
- directionCfg.var = SyncVariant::update;
- custSyncDir = syncDirOld;
+ toggleSyncDirection(diffDirs->*dir);
+ updateSyncGui();
}
- else
- directionCfg.var = SyncVariant::custom;
+ else assert(false);
}
-void ConfigDialog::onExLeftSideOnly(wxCommandEvent& event)
+void ConfigDialog::onLeftNewer(wxCommandEvent& event)
{
- toggleCustomSyncConfig(directionCfg_, directionCfg_.custom.exLeftSideOnly);
- updateSyncGui();
+ toggleSyncDirButton(&DirectionByDiff::leftNewer);
+ assert(!leftRightNewerCombined());
}
-void ConfigDialog::onExRightSideOnly(wxCommandEvent& event)
+void ConfigDialog::onRightNewer(wxCommandEvent& event)
{
- toggleCustomSyncConfig(directionCfg_, directionCfg_.custom.exRightSideOnly);
- updateSyncGui();
+ toggleSyncDirButton(&DirectionByDiff::rightNewer);
+ assert(!leftRightNewerCombined());
}
-void ConfigDialog::onLeftNewer(wxCommandEvent& event)
+void ConfigDialog::onDifferent(wxCommandEvent& event)
{
- toggleCustomSyncConfig(directionCfg_, directionCfg_.custom.leftNewer);
- updateSyncGui();
+ toggleSyncDirButton(&DirectionByDiff::leftNewer);
+
+ if (DirectionByDiff* diffDirs = std::get_if<DirectionByDiff>(&directionsCfg_.dirs))
+ //simulate category "different" as leftNewer/rightNewer combined:
+ diffDirs->rightNewer = diffDirs->leftNewer;
+ else assert(false);
+ assert(leftRightNewerCombined());
}
-void ConfigDialog::onRightNewer(wxCommandEvent& event)
+void ConfigDialog::toggleSyncDirButton(DirectionByChange::Changes DirectionByChange::* side, SyncDirection DirectionByChange::Changes::* dir)
{
- toggleCustomSyncConfig(directionCfg_, directionCfg_.custom.rightNewer);
- updateSyncGui();
+ if (DirectionByChange* changeDirs = std::get_if<DirectionByChange>(&directionsCfg_.dirs))
+ {
+ toggleSyncDirection(changeDirs->*side.*dir);
+ updateSyncGui();
+ }
+ else assert(false);
}
-void ConfigDialog::onDifferent(wxCommandEvent& event)
+namespace
{
- toggleCustomSyncConfig(directionCfg_, directionCfg_.custom.different);
- updateSyncGui();
+auto updateDirButton(wxBitmapButton& button, SyncDirection dir,
+ const char* imgNameLeft, const char* imgNameNone, const char* imgNameRight,
+ SyncOperation opLeft, SyncOperation opNone, SyncOperation opRight)
+{
+ const char* imgName = nullptr;
+ switch (dir)
+ {
+ case SyncDirection::left:
+ imgName = imgNameLeft;
+ button.SetToolTip(getSyncOpDescription(opLeft));
+ break;
+ case SyncDirection::none:
+ imgName = imgNameNone;
+ button.SetToolTip(getSyncOpDescription(opNone));
+ break;
+ case SyncDirection::right:
+ imgName = imgNameRight;
+ button.SetToolTip(getSyncOpDescription(opRight));
+ break;
+ }
+ wxImage img = mirrorIfRtl(loadImage(imgName));
+ button.SetBitmapLabel (toScaledBitmap( img));
+ button.SetBitmapDisabled(toScaledBitmap(greyScale(img))); //fix wxWidgets' all-too-clever multi-state!
+ //=> the disabled bitmap is generated during first SetBitmapLabel() call but never updated again by wxWidgets!
}
-void ConfigDialog::onConflict(wxCommandEvent& event)
+void updateDiffDirButtons(const DirectionByDiff& diffDirs,
+ wxBitmapButton& buttonLeftOnly,
+ wxBitmapButton& buttonRightOnly,
+ wxBitmapButton& buttonLeftNewer,
+ wxBitmapButton& buttonRightNewer,
+ wxBitmapButton& buttonDifferent)
{
- toggleCustomSyncConfig(directionCfg_, directionCfg_.custom.conflict);
- updateSyncGui();
+ updateDirButton(buttonLeftOnly, diffDirs.leftOnly, "so_delete_left", "so_none", "so_create_right", SO_DELETE_LEFT, SO_DO_NOTHING, SO_CREATE_RIGHT);
+ updateDirButton(buttonRightOnly, diffDirs.rightOnly, "so_create_left", "so_none", "so_delete_right", SO_CREATE_LEFT, SO_DO_NOTHING, SO_DELETE_RIGHT);
+ updateDirButton(buttonLeftNewer, diffDirs.leftNewer, "so_update_left", "so_none", "so_update_right", SO_OVERWRITE_LEFT, SO_DO_NOTHING, SO_OVERWRITE_RIGHT);
+ updateDirButton(buttonRightNewer, diffDirs.rightNewer, "so_update_left", "so_none", "so_update_right", SO_OVERWRITE_LEFT, SO_DO_NOTHING, SO_OVERWRITE_RIGHT);
+ //simulate category "different" as leftNewer/rightNewer combined:
+ updateDirButton(buttonDifferent, diffDirs.leftNewer, "so_update_left", "so_none", "so_update_right", SO_OVERWRITE_LEFT, SO_DO_NOTHING, SO_OVERWRITE_RIGHT);
}
-void updateSyncDirectionIcons(const SyncDirectionConfig& directionCfg,
- wxBitmapButton& buttonLeftOnly,
- wxBitmapButton& buttonRightOnly,
- wxBitmapButton& buttonLeftNewer,
- wxBitmapButton& buttonRightNewer,
- wxBitmapButton& buttonDifferent,
- wxBitmapButton& buttonConflict)
+void updateChangeDirButtons(const DirectionByChange& changeDirs,
+ wxBitmapButton& buttonLeftCreate,
+ wxBitmapButton& buttonLeftUpdate,
+ wxBitmapButton& buttonLeftDelete,
+ wxBitmapButton& buttonRightCreate,
+ wxBitmapButton& buttonRightUpdate,
+ wxBitmapButton& buttonRightDelete)
{
- if (directionCfg.var != SyncVariant::twoWay) //automatic mode needs no sync-directions
- {
- auto updateButton = [](wxBitmapButton& button, SyncDirection dir,
- const char* imgNameLeft, const char* imgNameNone, const char* imgNameRight,
- SyncOperation opLeft, SyncOperation opNone, SyncOperation opRight)
- {
- const char* imgName = nullptr;
- switch (dir)
- {
- case SyncDirection::left:
- imgName = imgNameLeft;
- button.SetToolTip(getSyncOpDescription(opLeft));
- break;
- case SyncDirection::none:
- imgName = imgNameNone;
- button.SetToolTip(getSyncOpDescription(opNone));
- break;
- case SyncDirection::right:
- imgName = imgNameRight;
- button.SetToolTip(getSyncOpDescription(opRight));
- break;
- }
- wxImage img = mirrorIfRtl(loadImage(imgName));
- button.SetBitmapLabel (toScaledBitmap( img));
- button.SetBitmapDisabled(toScaledBitmap(greyScale(img))); //fix wxWidgets' all-too-clever multi-state!
- //=> the disabled bitmap is generated during first SetBitmapLabel() call but never updated again by wxWidgets!
- };
-
- const DirectionSet dirCfg = extractDirections(directionCfg);
+ updateDirButton(buttonLeftCreate, changeDirs.left.create, "so_delete_left", "so_none", "so_create_right", SO_DELETE_LEFT, SO_DO_NOTHING, SO_CREATE_RIGHT);
+ updateDirButton(buttonLeftUpdate, changeDirs.left.update, "so_update_left", "so_none", "so_update_right", SO_OVERWRITE_LEFT, SO_DO_NOTHING, SO_OVERWRITE_RIGHT);
+ updateDirButton(buttonLeftDelete, changeDirs.left.delete_, "so_create_left", "so_none", "so_delete_right", SO_CREATE_LEFT, SO_DO_NOTHING, SO_DELETE_RIGHT);
- updateButton(buttonLeftOnly, dirCfg.exLeftSideOnly, "so_delete_left", "so_none", "so_create_right", SO_DELETE_LEFT, SO_DO_NOTHING, SO_CREATE_NEW_RIGHT);
- updateButton(buttonRightOnly, dirCfg.exRightSideOnly, "so_create_left", "so_none", "so_delete_right", SO_CREATE_NEW_LEFT, SO_DO_NOTHING, SO_DELETE_RIGHT );
- updateButton(buttonLeftNewer, dirCfg.leftNewer, "so_update_left", "so_none", "so_update_right", SO_OVERWRITE_LEFT, SO_DO_NOTHING, SO_OVERWRITE_RIGHT );
- updateButton(buttonRightNewer, dirCfg.rightNewer, "so_update_left", "so_none", "so_update_right", SO_OVERWRITE_LEFT, SO_DO_NOTHING, SO_OVERWRITE_RIGHT );
- updateButton(buttonDifferent, dirCfg.different, "so_update_left", "so_none", "so_update_right", SO_OVERWRITE_LEFT, SO_DO_NOTHING, SO_OVERWRITE_RIGHT );
-
- updateButton(buttonConflict, dirCfg.conflict, "so_update_left", "cat_conflict", "so_update_right", SO_OVERWRITE_LEFT, SO_DO_NOTHING, SO_OVERWRITE_RIGHT );
- if (dirCfg.conflict == SyncDirection::none)
- buttonConflict.SetToolTip(_("Leave as unresolved conflict")); //silent dependency from algorithm.cpp::Redetermine!!!
- }
+ updateDirButton(buttonRightCreate, changeDirs.right.create, "so_create_left", "so_none", "so_delete_right", SO_CREATE_LEFT, SO_DO_NOTHING, SO_DELETE_RIGHT);
+ updateDirButton(buttonRightUpdate, changeDirs.right.update, "so_update_left", "so_none", "so_update_right", SO_OVERWRITE_LEFT, SO_DO_NOTHING, SO_OVERWRITE_RIGHT);
+ updateDirButton(buttonRightDelete, changeDirs.right.delete_, "so_delete_left", "so_none", "so_create_right", SO_DELETE_LEFT, SO_DO_NOTHING, SO_CREATE_RIGHT);
+}
}
-
void ConfigDialog::onShowLogFolder(wxCommandEvent& event)
{
@@ -1187,13 +1183,21 @@ void ConfigDialog::onShowLogFolder(wxCommandEvent& event)
}
+bool ConfigDialog::leftRightNewerCombined() const
+{
+ assert(std::get_if<DirectionByDiff>(&directionsCfg_.dirs));
+ const CompareVariant activeCmpVar = m_checkBoxUseLocalCmpOptions->GetValue() ? localCmpVar_ : globalPairCfg_.cmpCfg.compareVar;
+ return activeCmpVar == CompareVariant::content || activeCmpVar == CompareVariant::size;
+}
+
+
std::optional<SyncConfig> ConfigDialog::getSyncConfig() const
{
if (!m_checkBoxUseLocalSyncOptions->GetValue())
return {};
SyncConfig syncCfg;
- syncCfg.directionCfg = directionCfg_;
+ syncCfg.directionCfg = directionsCfg_;
syncCfg.deletionVariant = deletionVariant_;
syncCfg.versioningFolderPhrase = versioningFolder_.getPath();
syncCfg.versioningStyle = getEnumVal(enumVersioningStyle_, *m_choiceVersioningStyle);
@@ -1203,6 +1207,12 @@ std::optional<SyncConfig> ConfigDialog::getSyncConfig() const
syncCfg.versionCountMin = m_checkBoxVersionCountMin->GetValue() && m_checkBoxVersionMaxDays->GetValue() ? m_spinCtrlVersionCountMin->GetValue() : 0;
syncCfg.versionCountMax = m_checkBoxVersionCountMax->GetValue() ? m_spinCtrlVersionCountMax->GetValue() : 0;
}
+
+ //simulate category "different" as leftNewer/rightNewer combined:
+ if (DirectionByDiff* diffDirs = std::get_if<DirectionByDiff>(&syncCfg.directionCfg.dirs))
+ if (leftRightNewerCombined())
+ diffDirs->rightNewer = diffDirs->leftNewer;
+
return syncCfg;
}
@@ -1214,7 +1224,7 @@ void ConfigDialog::setSyncConfig(const SyncConfig* syncCfg)
//when local settings are inactive, display (current) global settings instead:
const SyncConfig tmpCfg = syncCfg ? *syncCfg : globalPairCfg_.syncCfg;
- directionCfg_ = tmpCfg.directionCfg; //make working copy; ownership *not* on GUI
+ directionsCfg_ = tmpCfg.directionCfg; //make working copy; ownership *not* on GUI
deletionVariant_ = tmpCfg.deletionVariant;
versioningFolder_.setPath(tmpCfg.versioningFolderPhrase);
setEnumVal(enumVersioningStyle_, *m_choiceVersioningStyle, tmpCfg.versioningStyle);
@@ -1242,51 +1252,71 @@ void ConfigDialog::updateSyncGui()
m_notebook->SetPageImage(static_cast<size_t>(SyncConfigPanel::sync),
static_cast<int>(syncOptionsEnabled ? ConfigTypeImage::sync: ConfigTypeImage::syncGrey));
- updateSyncDirectionIcons(directionCfg_,
+ const bool setDirsByDifferences = std::get_if<DirectionByDiff>(&directionsCfg_.dirs);
+
+ m_checkBoxUseDatabase->SetValue(!setDirsByDifferences);
+
+ //display only relevant sync options
+ bSizerSyncDirsDiff ->Show( setDirsByDifferences);
+ bSizerSyncDirsChanges->Show(!setDirsByDifferences);
+
+ if (const DirectionByDiff* diffDirs = std::get_if<DirectionByDiff>(&directionsCfg_.dirs)) //sync directions by differences
+ {
+ updateDiffDirButtons(*diffDirs,
*m_bpButtonLeftOnly,
*m_bpButtonRightOnly,
*m_bpButtonLeftNewer,
*m_bpButtonRightNewer,
- *m_bpButtonDifferent,
- *m_bpButtonConflict);
-
- //selecting "detect move files" does not always make sense:
- m_checkBoxDetectMove->Enable(detectMovedFilesSelectable(directionCfg_));
- m_checkBoxDetectMove->SetValue(detectMovedFilesEnabled(directionCfg_)); //parameter NOT owned by checkbox!
+ *m_bpButtonDifferent);
- //display only relevant sync options
- bSizerDatabase ->Show(directionCfg_.var == SyncVariant::twoWay);
- bSizerSyncDirections->Show(directionCfg_.var != SyncVariant::twoWay);
+ //simulate category "different" as leftNewer/rightNewer combined:
+ const bool haveLeftRightNewerCombined = leftRightNewerCombined();
+ m_bitmapLeftNewer ->Show(!haveLeftRightNewerCombined);
+ m_bpButtonLeftNewer ->Show(!haveLeftRightNewerCombined);
+ m_bitmapRightNewer ->Show(!haveLeftRightNewerCombined);
+ m_bpButtonRightNewer->Show(!haveLeftRightNewerCombined);
- if (directionCfg_.var == SyncVariant::twoWay)
- setImage(*m_bitmapDatabase, greyScaleIfDisabled(loadImage("database"), syncOptionsEnabled));
- else
+ m_bitmapDifferent ->Show(haveLeftRightNewerCombined);
+ m_bpButtonDifferent->Show(haveLeftRightNewerCombined);
+ }
+ else //sync directions by changes
{
- const CompareVariant activeCmpVar = m_checkBoxUseLocalCmpOptions->GetValue() ? localCmpVar_ : globalPairCfg_.cmpCfg.compareVar;
+ const DirectionByChange& changeDirs = std::get<DirectionByChange>(directionsCfg_.dirs);
+
+ updateChangeDirButtons(changeDirs,
+ *m_bpButtonLeftCreate,
+ *m_bpButtonLeftUpdate,
+ *m_bpButtonLeftDelete,
+ *m_bpButtonRightCreate,
+ *m_bpButtonRightUpdate,
+ *m_bpButtonRightDelete);
+ }
- m_bitmapLeftNewer ->Show(activeCmpVar == CompareVariant::timeSize);
- m_bpButtonLeftNewer ->Show(activeCmpVar == CompareVariant::timeSize);
- m_bitmapRightNewer ->Show(activeCmpVar == CompareVariant::timeSize);
- m_bpButtonRightNewer->Show(activeCmpVar == CompareVariant::timeSize);
+ const bool useDatabaseFile = std::get_if<DirectionByChange>(&directionsCfg_.dirs);
- m_bitmapDifferent ->Show(activeCmpVar == CompareVariant::content || activeCmpVar == CompareVariant::size);
- m_bpButtonDifferent->Show(activeCmpVar == CompareVariant::content || activeCmpVar == CompareVariant::size);
- }
+ setImage(*m_bitmapDatabase, greyScaleIfDisabled(loadImage("database", fastFromDIP(22)), useDatabaseFile && syncOptionsEnabled));
+
+ //"detect move files" is always active iff database is used:
+ setImage(*m_bitmapMoveLeft, greyScaleIfDisabled(loadImage("so_move_left", fastFromDIP(20)), useDatabaseFile && syncOptionsEnabled));
+ setImage(*m_bitmapMoveRight, greyScaleIfDisabled(loadImage("so_move_right", fastFromDIP(20)), useDatabaseFile && syncOptionsEnabled));
+ m_staticTextDetectMove->Enable(useDatabaseFile);
+
+ const SyncVariant syncVar = getSyncVariant(directionsCfg_);
//active variant description:
- setText(*m_staticTextSyncVarDescription, getSyncVariantDescription(directionCfg_.var));
+ setText(*m_staticTextSyncVarDescription, getSyncVariantDescription(syncVar));
m_staticTextSyncVarDescription->Wrap(fastFromDIP(CFG_DESCRIPTION_WIDTH_DIP)); //needs to be reapplied after SetLabel()
//update toggle buttons -> they have no parameter-ownership at all!
- m_buttonTwoWay->setActive(SyncVariant::twoWay == directionCfg_.var && syncOptionsEnabled);
- m_buttonMirror->setActive(SyncVariant::mirror == directionCfg_.var && syncOptionsEnabled);
- m_buttonUpdate->setActive(SyncVariant::update == directionCfg_.var && syncOptionsEnabled);
- m_buttonCustom->setActive(SyncVariant::custom == directionCfg_.var && syncOptionsEnabled);
+ m_buttonTwoWay->setActive(SyncVariant::twoWay == syncVar && syncOptionsEnabled);
+ m_buttonMirror->setActive(SyncVariant::mirror == syncVar && syncOptionsEnabled);
+ m_buttonUpdate->setActive(SyncVariant::update == syncVar && syncOptionsEnabled);
+ m_buttonCustom->setActive(SyncVariant::custom == syncVar && syncOptionsEnabled);
//syncOptionsEnabled: nudge wxWidgets to render inactive config state (needed on Windows, NOT on Linux!)
- m_buttonRecycler ->setActive(DeletionVariant::recycler == deletionVariant_ && syncOptionsEnabled);
- m_buttonPermanent ->setActive(DeletionVariant::permanent == deletionVariant_ && syncOptionsEnabled);
- m_buttonVersioning->setActive(DeletionVariant::versioning == deletionVariant_ && syncOptionsEnabled);
+ m_buttonRecycler ->setActive(DeletionVariant::recycler == deletionVariant_ && syncOptionsEnabled);
+ m_buttonPermanent ->setActive(DeletionVariant::permanent == deletionVariant_ && syncOptionsEnabled);
+ m_buttonVersioning->setActive(DeletionVariant::versioning == deletionVariant_ && syncOptionsEnabled);
switch (deletionVariant_) //unconditionally update image, including "local options off"
{
@@ -1376,7 +1406,6 @@ void ConfigDialog::updateSyncGui()
MiscSyncConfig ConfigDialog::getMiscSyncOptions() const
{
- assert(selectedPairIndexToShow_ == -1);
MiscSyncConfig miscCfg;
// Avoid "fake" changed configs! =>
@@ -1392,8 +1421,8 @@ MiscSyncConfig ConfigDialog::getMiscSyncOptions() const
++i;
}
//----------------------------------------------------------------------------
- miscCfg.ignoreErrors = m_checkBoxIgnoreErrors ->GetValue();
- miscCfg.autoRetryCount = m_checkBoxAutoRetry ->GetValue() ? m_spinCtrlAutoRetryCount->GetValue() : 0;
+ miscCfg.ignoreErrors = m_checkBoxIgnoreErrors->GetValue();
+ miscCfg.autoRetryCount = m_checkBoxAutoRetry ->GetValue() ? m_spinCtrlAutoRetryCount->GetValue() : 0;
miscCfg.autoRetryDelay = std::chrono::seconds(m_spinCtrlAutoRetryDelay->GetValue());
//----------------------------------------------------------------------------
miscCfg.postSyncCommand = m_comboBoxPostSyncCommand->getValue();
@@ -1418,8 +1447,6 @@ MiscSyncConfig ConfigDialog::getMiscSyncOptions() const
void ConfigDialog::setMiscSyncOptions(const MiscSyncConfig& miscCfg)
{
- assert(selectedPairIndexToShow_ == -1);
-
// Avoid "fake" changed configs! =>
//- when editting, consider only the deviceParallelOps items corresponding to the currently-used folder paths
//- keep parallel ops == 1 only temporarily during edit
@@ -1487,70 +1514,72 @@ void ConfigDialog::setMiscSyncOptions(const MiscSyncConfig& miscCfg)
void ConfigDialog::updateMiscGui()
{
- const MiscSyncConfig miscCfg = getMiscSyncOptions();
-
- setImage(*m_bitmapIgnoreErrors, greyScaleIfDisabled(loadImage("error_ignore_active"), miscCfg.ignoreErrors));
- setImage(*m_bitmapRetryErrors, greyScaleIfDisabled(loadImage("error_retry"), miscCfg.autoRetryCount > 0 ));
+ if (selectedPairIndexToShow_ == -1)
+ {
+ const MiscSyncConfig miscCfg = getMiscSyncOptions();
- fgSizerAutoRetry->Show(miscCfg.autoRetryCount > 0);
+ setImage(*m_bitmapIgnoreErrors, greyScaleIfDisabled(loadImage("error_ignore_active"), miscCfg.ignoreErrors));
+ setImage(*m_bitmapRetryErrors, greyScaleIfDisabled(loadImage("error_retry"), miscCfg.autoRetryCount > 0 ));
- m_panelComparisonSettings->Layout(); //showing "retry count" can affect bSizerPerformance!
- //----------------------------------------------------------------------------
- const bool sendEmailEnabled = m_checkBoxSendEmail->GetValue();
- setImage(*m_bitmapEmail, greyScaleIfDisabled(loadImage("email"), sendEmailEnabled));
- m_comboBoxEmail->Show(sendEmailEnabled);
-
- auto updateButton = [successIcon = loadImage("msg_success", getDefaultMenuIconSize()),
- warningIcon = loadImage("msg_warning", getDefaultMenuIconSize()),
- errorIcon = loadImage("msg_error", getDefaultMenuIconSize()),
- sendEmailEnabled, this] (wxBitmapButton& button, ResultsNotification notifyCondition)
- {
- button.Show(sendEmailEnabled);
- if (sendEmailEnabled)
- {
- wxString tooltip = _("Error");
- wxImage label = errorIcon;
+ fgSizerAutoRetry->Show(miscCfg.autoRetryCount > 0);
- if (notifyCondition == ResultsNotification::always ||
- notifyCondition == ResultsNotification::errorWarning)
- {
- tooltip += (L" | ") + _("Warning");
- label = stackImages(label, warningIcon, ImageStackLayout::horizontal, ImageStackAlignment::center);
- }
- else
- label = resizeCanvas(label, {label.GetWidth() + warningIcon.GetWidth(), label.GetHeight()}, wxALIGN_LEFT);
+ m_panelComparisonSettings->Layout(); //showing "retry count" can affect bSizerPerformance!
+ //----------------------------------------------------------------------------
+ const bool sendEmailEnabled = m_checkBoxSendEmail->GetValue();
+ setImage(*m_bitmapEmail, greyScaleIfDisabled(loadImage("email"), sendEmailEnabled));
+ m_comboBoxEmail->Show(sendEmailEnabled);
- if (notifyCondition == ResultsNotification::always)
+ auto updateButton = [successIcon = loadImage("msg_success", getDefaultMenuIconSize()),
+ warningIcon = loadImage("msg_warning", getDefaultMenuIconSize()),
+ errorIcon = loadImage("msg_error", getDefaultMenuIconSize()),
+ sendEmailEnabled, this](wxBitmapButton& button, ResultsNotification notifyCondition)
+ {
+ button.Show(sendEmailEnabled);
+ if (sendEmailEnabled)
{
- tooltip += (L" | ") + _("Success");
- label = stackImages(label, successIcon, ImageStackLayout::horizontal, ImageStackAlignment::center);
+ wxString tooltip = _("Error");
+ wxImage label = errorIcon;
+
+ if (notifyCondition == ResultsNotification::always ||
+ notifyCondition == ResultsNotification::errorWarning)
+ {
+ tooltip += (L" | ") + _("Warning");
+ label = stackImages(label, warningIcon, ImageStackLayout::horizontal, ImageStackAlignment::center);
+ }
+ else
+ label = resizeCanvas(label, {label.GetWidth() + warningIcon.GetWidth(), label.GetHeight()}, wxALIGN_LEFT);
+
+ if (notifyCondition == ResultsNotification::always)
+ {
+ tooltip += (L" | ") + _("Success");
+ label = stackImages(label, successIcon, ImageStackLayout::horizontal, ImageStackAlignment::center);
+ }
+ else
+ label = resizeCanvas(label, {label.GetWidth() + successIcon.GetWidth(), label.GetHeight()}, wxALIGN_LEFT);
+
+ button.SetToolTip(tooltip);
+ button.SetBitmapLabel (toScaledBitmap(notifyCondition == emailNotifyCondition_ && sendEmailEnabled ? label : greyScale(label)));
+ button.SetBitmapDisabled(toScaledBitmap(greyScale(label))); //fix wxWidgets' all-too-clever multi-state!
+ //=> the disabled bitmap is generated during first SetBitmapLabel() call but never updated again by wxWidgets!
}
- else
- label = resizeCanvas(label, {label.GetWidth() + successIcon.GetWidth(), label.GetHeight()}, wxALIGN_LEFT);
-
- button.SetToolTip(tooltip);
- button.SetBitmapLabel (toScaledBitmap(notifyCondition == emailNotifyCondition_ && sendEmailEnabled ? label : greyScale(label)));
- button.SetBitmapDisabled(toScaledBitmap(greyScale(label))); //fix wxWidgets' all-too-clever multi-state!
- //=> the disabled bitmap is generated during first SetBitmapLabel() call but never updated again by wxWidgets!
- }
- };
- updateButton(*m_bpButtonEmailAlways, ResultsNotification::always);
- updateButton(*m_bpButtonEmailErrorWarning, ResultsNotification::errorWarning);
- updateButton(*m_bpButtonEmailErrorOnly, ResultsNotification::errorOnly);
-
- m_hyperlinkPerfDeRequired2->Show(!enableExtraFeatures_); //required after each bSizerSyncMisc->Show()
+ };
+ updateButton(*m_bpButtonEmailAlways, ResultsNotification::always);
+ updateButton(*m_bpButtonEmailErrorWarning, ResultsNotification::errorWarning);
+ updateButton(*m_bpButtonEmailErrorOnly, ResultsNotification::errorOnly);
- //----------------------------------------------------------------------------
- setImage(*m_bitmapLogFile, greyScaleIfDisabled(loadImage("log_file", fastFromDIP(20)), m_checkBoxOverrideLogPath->GetValue()));
- m_logFolderPath ->Enable(m_checkBoxOverrideLogPath->GetValue()); //
- m_buttonSelectLogFolder ->Show(m_checkBoxOverrideLogPath->GetValue()); //enabled status can't be derived from resolved config!
- m_bpButtonSelectAltLogFolder->Show(m_checkBoxOverrideLogPath->GetValue()); //
+ m_hyperlinkPerfDeRequired2->Show(!enableExtraFeatures_); //required after each bSizerSyncMisc->Show()
- m_panelSyncSettings->Layout(); //after showing/hiding m_buttonSelectLogFolder
+ //----------------------------------------------------------------------------
+ setImage(*m_bitmapLogFile, greyScaleIfDisabled(loadImage("log_file", fastFromDIP(20)), m_checkBoxOverrideLogPath->GetValue()));
+ m_logFolderPath ->Enable(m_checkBoxOverrideLogPath->GetValue()); //
+ m_buttonSelectLogFolder ->Show(m_checkBoxOverrideLogPath->GetValue()); //enabled status can't be derived from resolved config!
+ m_bpButtonSelectAltLogFolder->Show(m_checkBoxOverrideLogPath->GetValue()); //
- m_panelSyncSettings->Refresh(); //removes a few artifacts when toggling email notifications
- m_panelLogfile ->Refresh();//
+ m_panelSyncSettings->Layout(); //after showing/hiding m_buttonSelectLogFolder
+ m_panelSyncSettings->Refresh(); //removes a few artifacts when toggling email notifications
+ m_panelLogfile ->Refresh();//
+ }
//----------------------------------------------------------------------------
m_buttonAddNotes->Show(!showNotesPanel_);
m_panelNotes ->Show(showNotesPanel_);
@@ -1611,10 +1640,9 @@ void ConfigDialog::selectFolderPairConfig(int newPairIndexToShow)
addDevicePath(globalPairCfg_.syncCfg.versioningFolderPhrase);
//---------------------------------------------------------------------------------------------------------------
- setCompConfig (&globalPairCfg_.cmpCfg);
- setSyncConfig (&globalPairCfg_.syncCfg);
- setFilterConfig (globalPairCfg_.filter);
- setMiscSyncOptions(globalPairCfg_.miscCfg);
+ setCompConfig (&globalPairCfg_.cmpCfg);
+ setSyncConfig (&globalPairCfg_.syncCfg);
+ setFilterConfig(globalPairCfg_.filter);
}
else
{
@@ -1622,6 +1650,7 @@ void ConfigDialog::selectFolderPairConfig(int newPairIndexToShow)
setSyncConfig(get(localPairCfg_[selectedPairIndexToShow_].localSyncCfg));
setFilterConfig (localPairCfg_[selectedPairIndexToShow_].localFilter);
}
+ setMiscSyncOptions(globalPairCfg_.miscCfg);
m_panelCompSettingsTab ->Layout(); //fix comp panel glitch on Win 7 125% font size + perf panel
m_panelFilterSettingsTab->Layout();
@@ -1637,9 +1666,7 @@ bool ConfigDialog::unselectFolderPairConfig(bool validateParams)
std::optional<SyncConfig> syncCfg = getSyncConfig();
FilterConfig filterCfg = getFilterConfig();
- std::optional<MiscSyncConfig> miscCfg;
- if (selectedPairIndexToShow_ < 0)
- miscCfg = getMiscSyncOptions();
+ MiscSyncConfig miscCfg = getMiscSyncOptions(); //some "misc" options are always visible, e.g. "notes"
//------- parameter validation (BEFORE writing output!) -------
if (validateParams)
@@ -1692,18 +1719,18 @@ bool ConfigDialog::unselectFolderPairConfig(bool validateParams)
if (selectedPairIndexToShow_ < 0)
{
- if (AFS::isNullPath(createAbstractPath(miscCfg->altLogFolderPathPhrase)) &&
- !miscCfg->altLogFolderPathPhrase.empty())
+ if (AFS::isNullPath(createAbstractPath(miscCfg.altLogFolderPathPhrase)) &&
+ !miscCfg.altLogFolderPathPhrase.empty())
{
m_notebook->ChangeSelection(static_cast<size_t>(SyncConfigPanel::sync));
showNotificationDialog(this, DialogInfoType::info, PopupDialogCfg().setMainInstructions(_("Please enter a folder path.")));
m_logFolderPath->SetFocus();
return false;
}
- m_logFolderPath->getHistory()->addItem(miscCfg->altLogFolderPathPhrase);
+ m_logFolderPath->getHistory()->addItem(miscCfg.altLogFolderPathPhrase);
- if (!miscCfg->emailNotifyAddress.empty() &&
- !isValidEmail(trimCpy(miscCfg->emailNotifyAddress)))
+ if (!miscCfg.emailNotifyAddress.empty() &&
+ !isValidEmail(trimCpy(miscCfg.emailNotifyAddress)))
{
m_notebook->ChangeSelection(static_cast<size_t>(SyncConfigPanel::sync));
showNotificationDialog(this, DialogInfoType::info, PopupDialogCfg().setMainInstructions(_("Please enter a valid email address.")));
@@ -1721,7 +1748,6 @@ bool ConfigDialog::unselectFolderPairConfig(bool validateParams)
globalPairCfg_.cmpCfg = *compCfg;
globalPairCfg_.syncCfg = *syncCfg;
globalPairCfg_.filter = filterCfg;
- globalPairCfg_.miscCfg = *miscCfg;
}
else
{
@@ -1729,6 +1755,7 @@ bool ConfigDialog::unselectFolderPairConfig(bool validateParams)
localPairCfg_[selectedPairIndexToShow_].localSyncCfg = syncCfg;
localPairCfg_[selectedPairIndexToShow_].localFilter = filterCfg;
}
+ globalPairCfg_.miscCfg = miscCfg;
selectedPairIndexToShow_ = EMPTY_PAIR_INDEX_SELECTED;
//m_listBoxFolderPair->SetSelection(wxNOT_FOUND); not needed, selectedPairIndexToShow has parameter ownership
diff --git a/FreeFileSync/Source/ui/tree_grid.cpp b/FreeFileSync/Source/ui/tree_grid.cpp
index b6b2db66..6f32a746 100644
--- a/FreeFileSync/Source/ui/tree_grid.cpp
+++ b/FreeFileSync/Source/ui/tree_grid.cpp
@@ -31,6 +31,16 @@ const int TREE_GRID_GAP_SIZE_DIP = 2;
inline wxColor getColorPercentBorder () { return {198, 198, 198}; }
inline wxColor getColorPercentBackground() { return {0xf8, 0xf8, 0xf8}; }
+
+
+Zstring getFolderPairName(const FolderPair& folder)
+{
+ if (folder.hasEquivalentItemNames())
+ return folder.getItemName<SelectSide::left>();
+ else
+ return folder.getItemName<SelectSide::left >() + Zstr(" | ") +
+ folder.getItemName<SelectSide::right>();
+}
}
@@ -62,31 +72,30 @@ void TreeView::compressNode(Container& cont) //remove single-element sub-trees -
template <class Function> //(const FileSystemObject&) -> bool
-void TreeView::extractVisibleSubtree(ContainerObject& hierObj, //in
+void TreeView::extractVisibleSubtree(ContainerObject& conObj, //in
TreeView::Container& cont, //out
Function pred)
{
auto getBytes = [](const FilePair& file) //MSVC screws up miserably if we put this lambda into std::for_each
{
- ////give accumulated bytes the semantics of a sync preview!
- //if (file.isActive())
- // switch (file.getSyncDir())
- // {
- // case SyncDirection::left:
- // return file.getFileSize<SelectSide::right>();
- // case SyncDirection::right:
- // return file.getFileSize<SelectSide::left>();
- // case SyncDirection::none:
- // break;
- // }
-
+#if 0 //give accumulated bytes the semantics of a sync preview?
+ switch (getEffectiveSyncDir(file.getSyncOperation()))
+ {
+ //*INDENT-OFF*
+ case SyncDirection::none: break;
+ case SyncDirection::left: return file.getFileSize<SelectSide::right>();
+ case SyncDirection::right: return file.getFileSize<SelectSide::left>();
+ //*INDENT-ON*
+ }
+#endif
//prefer file-browser semantics over sync preview (=> always show useful numbers, even for SyncDirection::none)
//discussion: https://freefilesync.org/forum/viewtopic.php?t=1595
- return std::max(file.getFileSize<SelectSide::left>(), file.getFileSize<SelectSide::right>());
+ return std::max(file.isEmpty<SelectSide::left >() ? 0 : file.getFileSize<SelectSide::left>(),
+ file.isEmpty<SelectSide::right>() ? 0 : file.getFileSize<SelectSide::right>());
};
cont.firstFileId = nullptr;
- for (FilePair& file : hierObj.refSubFiles())
+ for (FilePair& file : conObj.refSubFiles())
if (pred(file))
{
cont.bytesNet += getBytes(file);
@@ -96,7 +105,7 @@ void TreeView::extractVisibleSubtree(ContainerObject& hierObj, //in
cont.firstFileId = file.getId();
}
- for (SymlinkPair& symlink : hierObj.refSubLinks())
+ for (SymlinkPair& symlink : conObj.refSubLinks())
if (pred(symlink))
{
++cont.itemCountNet;
@@ -108,9 +117,9 @@ void TreeView::extractVisibleSubtree(ContainerObject& hierObj, //in
cont.bytesGross += cont.bytesNet;
cont.itemCountGross += cont.itemCountNet;
- cont.subDirs.reserve(hierObj.refSubFolders().size()); //avoid expensive reallocations!
+ cont.subDirs.reserve(conObj.refSubFolders().size()); //avoid expensive reallocations!
- for (FolderPair& folder : hierObj.refSubFolders())
+ for (FolderPair& folder : conObj.refSubFolders())
{
const bool included = pred(folder);
@@ -195,18 +204,17 @@ struct TreeView::LessShortName
return makeSortDirection(LessNaturalSort() /*even on Linux*/,
std::bool_constant<ascending>())(utfTo<Zstring>(static_cast<const RootNodeImpl*>(lhs.node)->displayName),
utfTo<Zstring>(static_cast<const RootNodeImpl*>(rhs.node)->displayName));
-
case NodeType::folder:
{
const auto* folderL = dynamic_cast<const FolderPair*>(FileSystemObject::retrieve(static_cast<const DirNodeImpl*>(lhs.node)->objId));
const auto* folderR = dynamic_cast<const FolderPair*>(FileSystemObject::retrieve(static_cast<const DirNodeImpl*>(rhs.node)->objId));
- if (!folderL) //might be pathologic, but it's covered
+ if (!folderL) //might be pathologic, but it's covered
return false;
else if (!folderR)
return true;
- return makeSortDirection(LessNaturalSort(), std::bool_constant<ascending>())(folderL->getItemNameAny(), folderR->getItemNameAny());
+ return makeSortDirection(LessNaturalSort(), std::bool_constant<ascending>())(getFolderPairName(*folderL), getFolderPairName(*folderR));
}
case NodeType::files:
@@ -321,8 +329,8 @@ void TreeView::applySubView(std::vector<RootNodeImpl>&& newView)
auto it = flatTree_.begin();
for (auto itNext = flatTree_.begin() + 1; itNext != flatTree_.end(); ++itNext, ++it)
if (it->level < itNext->level)
- if (auto hierObj = getHierAlias(*it))
- expandedNodes.insert(hierObj);
+ if (auto conObj = getHierAlias(*it))
+ expandedNodes.insert(conObj);
}
//update view on full data
@@ -363,8 +371,8 @@ void TreeView::applySubView(std::vector<RootNodeImpl>&& newView)
{
const TreeLine& line = flatTree_[row];
- if (auto hierObj = getHierAlias(line))
- if (expandedNodes.contains(hierObj))
+ if (auto conObj = getHierAlias(line))
+ if (expandedNodes.contains(conObj))
{
std::vector<TreeLine> newLines;
getChildren(*line.node, line.level + 1, newLines);
@@ -525,9 +533,9 @@ void TreeView::applyDifferenceFilter(bool showExcluded,
switch (fsObj.getCategory())
{
- case FILE_LEFT_SIDE_ONLY:
+ case FILE_LEFT_ONLY:
return leftOnlyFilesActive;
- case FILE_RIGHT_SIDE_ONLY:
+ case FILE_RIGHT_ONLY:
return rightOnlyFilesActive;
case FILE_LEFT_NEWER:
return leftNewerFilesActive;
@@ -536,9 +544,10 @@ void TreeView::applyDifferenceFilter(bool showExcluded,
case FILE_DIFFERENT_CONTENT:
return differentFilesActive;
case FILE_EQUAL:
- case FILE_DIFFERENT_METADATA: //= sub-category of equal
return equalFilesActive;
+ case FILE_RENAMED:
case FILE_CONFLICT:
+ case FILE_TIME_INVALID:
return conflictFilesActive;
}
assert(false);
@@ -574,21 +583,21 @@ void TreeView::applyActionFilter(bool showExcluded,
switch (fsObj.getSyncOperation())
{
- case SO_CREATE_NEW_LEFT:
+ case SO_CREATE_LEFT:
return syncCreateLeftActive;
- case SO_CREATE_NEW_RIGHT:
+ case SO_CREATE_RIGHT:
return syncCreateRightActive;
case SO_DELETE_LEFT:
return syncDeleteLeftActive;
case SO_DELETE_RIGHT:
return syncDeleteRightActive;
case SO_OVERWRITE_RIGHT:
- case SO_COPY_METADATA_TO_RIGHT:
+ case SO_RENAME_RIGHT:
case SO_MOVE_RIGHT_FROM:
case SO_MOVE_RIGHT_TO:
return syncDirOverwRightActive;
case SO_OVERWRITE_LEFT:
- case SO_COPY_METADATA_TO_LEFT:
+ case SO_RENAME_LEFT:
case SO_MOVE_LEFT_FROM:
case SO_MOVE_LEFT_TO:
return syncDirOverwLeftActive;
@@ -751,7 +760,7 @@ private:
if (const TreeView::RootNode* root = dynamic_cast<const TreeView::RootNode*>(node.get()))
return root->displayName;
else if (const TreeView::DirNode* dir = dynamic_cast<const TreeView::DirNode*>(node.get()))
- return utfTo<std::wstring>(dir->folder.getItemNameAny());
+ return utfTo<std::wstring>(getFolderPairName(dir->folder));
else if (dynamic_cast<const TreeView::FilesNode*>(node.get()))
return _("Files");
break;
diff --git a/FreeFileSync/Source/ui/tree_grid.h b/FreeFileSync/Source/ui/tree_grid.h
index 472739d7..fb721f68 100644
--- a/FreeFileSync/Source/ui/tree_grid.h
+++ b/FreeFileSync/Source/ui/tree_grid.h
@@ -153,7 +153,7 @@ private:
static void compressNode(Container& cont);
template <class Function>
- static void extractVisibleSubtree(ContainerObject& hierObj, Container& cont, Function includeObject);
+ static void extractVisibleSubtree(ContainerObject& conObj, Container& cont, Function includeObject);
void getChildren(const Container& cont, unsigned int level, std::vector<TreeLine>& output);
template <class Predicate> void updateView(Predicate pred);
void applySubView(std::vector<RootNodeImpl>&& newView);
diff --git a/FreeFileSync/Source/ui/version_check.cpp b/FreeFileSync/Source/ui/version_check.cpp
index ca49d087..de71621d 100644
--- a/FreeFileSync/Source/ui/version_check.cpp
+++ b/FreeFileSync/Source/ui/version_check.cpp
@@ -5,22 +5,22 @@
// *****************************************************************************
#include "version_check.h"
-#include <ctime>
+//#include <ctime>
#include <zen/crc.h>
-#include <zen/string_tools.h>
-#include <zen/i18n.h>
-#include <zen/utf.h>
-#include <zen/scope_guard.h>
+//#include <zen/string_tools.h>
+//#include <zen/i18n.h>
+//#include <zen/utf.h>
+//#include <zen/scope_guard.h>
#include <zen/build_info.h>
-#include <zen/basic_math.h>
+//#include <zen/basic_math.h>
#include <zen/file_io.h>
-#include <zen/file_error.h>
+//#include <zen/file_error.h>
#include <zen/http.h>
#include <zen/process_exec.h>
#include <zen/sys_version.h>
#include <zen/sys_info.h>
-#include <zen/thread.h>
-#include <wx+/popup_dlg.h>
+//#include <zen/thread.h>
+//#include <wx+/popup_dlg.h>
#include <wx+/image_resources.h>
#include "../ffs_paths.h"
#include "../version/version.h"
@@ -39,29 +39,6 @@ namespace
const Zchar ffsUpdateCheckUserAgent[] = Zstr("FFS-Update-Check");
-time_t getVersionCheckInactiveId()
-{
- //use current version to calculate a changing number for the inactive state near UTC begin, in order to always check for updates after installing a new version
- //=> interpret version as 11-based *unique* number (this breaks lexicographical version ordering, but that's irrelevant!)
- int id = 0;
- const char* first = ffsVersion;
- const char* last = first + zen::strLength(ffsVersion);
- std::for_each(first, last, [&](char c)
- {
- id *= 11;
- if ('0' <= c && c <= '9')
- id += c - '0';
- else
- {
- assert(c == FFS_VERSION_SEPARATOR);
- id += 10;
- }
- });
- assert(0 < id && id < 3600 * 24 * 365); //as long as value is within a year after UTC begin (1970) there's no risk to clash with *current* time
- return id;
-}
-
-
time_t getVersionCheckCurrentTime()
@@ -73,21 +50,20 @@ time_t getVersionCheckCurrentTime()
void openBrowserForDownload(wxWindow* parent)
{
- wxLaunchDefaultBrowser(L"https://freefilesync.org/get_latest.php");
+ wxLaunchDefaultBrowser(L"https://freefilesync.org/get_latest.php");
}
}
-bool fff::shouldRunAutomaticUpdateCheck(time_t lastUpdateCheck)
+bool fff::automaticUpdateCheckDue(time_t lastUpdateCheck)
{
- if (lastUpdateCheck == getVersionCheckInactiveId())
- return false;
-
const time_t now = std::time(nullptr);
return std::abs(now - lastUpdateCheck) >= 7 * 24 * 3600; //check weekly
}
+namespace
+{
std::wstring getIso639Language()
{
assert(runningOnMainThread()); //this function is not thread-safe: consider wxWidgets usage
@@ -106,8 +82,6 @@ std::wstring getIso639Language()
}
-namespace
-{
std::wstring getIso3166Country()
{
assert(runningOnMainThread()); //this function is not thread-safe, consider wxWidgets usage
@@ -212,6 +186,12 @@ std::string getOnlineVersion(const std::vector<std::pair<std::string, std::strin
return response;
}
+
+
+std::string getUnknownVersionTag()
+{
+ return '<' + utfTo<std::string>(_("version unknown")) + '>';
+}
}
@@ -235,18 +215,6 @@ bool fff::haveNewerVersionOnline(const std::string& onlineVersion)
}
-bool fff::updateCheckActive(time_t lastUpdateCheck)
-{
- return lastUpdateCheck != getVersionCheckInactiveId();
-}
-
-
-void fff::disableUpdateCheck(time_t& lastUpdateCheck)
-{
- lastUpdateCheck = getVersionCheckInactiveId();
-}
-
-
void fff::checkForUpdateNow(wxWindow& parent, std::string& lastOnlineVersion)
{
try
@@ -266,7 +234,7 @@ void fff::checkForUpdateNow(wxWindow& parent, std::string& lastOnlineVersion)
{
if (internetIsAlive())
{
- lastOnlineVersion = "Unknown";
+ lastOnlineVersion = getUnknownVersionTag();
switch (showConfirmationDialog(&parent, DialogInfoType::error, PopupDialogCfg().
setTitle(_("Check for Program Updates")).
@@ -323,8 +291,8 @@ SharedRef<const UpdateCheckResultPrep> fff::automaticUpdateCheckPrepare(wxWindow
struct fff::UpdateCheckResult
{
std::string onlineVersion;
- bool internetIsAlive = false;
std::optional<SysError> error;
+ bool internetIsAlive = false;
};
SharedRef<const UpdateCheckResult> fff::automaticUpdateCheckRunAsync(const UpdateCheckResultPrep& resultPrep)
{
@@ -353,37 +321,46 @@ void fff::automaticUpdateCheckEval(wxWindow& parent, time_t& lastUpdateCheck, st
if (!result.error)
{
- lastUpdateCheck = getVersionCheckCurrentTime();
- lastOnlineVersion = result.onlineVersion;
+ lastUpdateCheck = getVersionCheckCurrentTime();
+
+ if (lastOnlineVersion != result.onlineVersion) //show new version popup only *once*
+ {
+ lastOnlineVersion = result.onlineVersion;
- if (haveNewerVersionOnline(result.onlineVersion))
- showUpdateAvailableDialog(&parent, result.onlineVersion);
+ if (haveNewerVersionOnline(result.onlineVersion)) //beta or development version is newer than online
+ showUpdateAvailableDialog(&parent, result.onlineVersion);
+ }
}
else
{
if (result.internetIsAlive)
{
- lastOnlineVersion = "Unknown";
-
- switch (showConfirmationDialog(&parent, DialogInfoType::error, PopupDialogCfg().
- setTitle(_("Check for Program Updates")).
- setMainInstructions(_("Cannot find current FreeFileSync version number online. A newer version is likely available. Check manually now?")).
- setDetailInstructions(result.error->toString()),
- _("&Check"), _("&Retry")))
+ if (lastOnlineVersion != getUnknownVersionTag())
{
- case ConfirmationButton2::accept:
- openBrowserForDownload(&parent);
- break;
- case ConfirmationButton2::accept2: //retry
- automaticUpdateCheckEval(parent, lastUpdateCheck, lastOnlineVersion,
- automaticUpdateCheckRunAsync(automaticUpdateCheckPrepare(parent).ref()).ref()); //note: retry via recursion!!!
- break;
- case ConfirmationButton2::cancel:
- break;
+ lastOnlineVersion = getUnknownVersionTag();
+
+ switch (showConfirmationDialog(&parent, DialogInfoType::error, PopupDialogCfg().
+ setTitle(_("Check for Program Updates")).
+ setMainInstructions(_("Cannot find current FreeFileSync version number online. A newer version is likely available. Check manually now?")).
+ setDetailInstructions(result.error->toString()),
+ _("&Check"), _("&Retry")))
+ {
+ case ConfirmationButton2::accept:
+ openBrowserForDownload(&parent);
+ break;
+ case ConfirmationButton2::accept2: //retry
+ automaticUpdateCheckEval(parent, lastUpdateCheck, lastOnlineVersion,
+ automaticUpdateCheckRunAsync(automaticUpdateCheckPrepare(parent).ref()).ref()); //retry via recursion!!!
+ break;
+ case ConfirmationButton2::cancel:
+ break;
+ }
}
}
- //else: ignore this error
+ else //no internet connection
+ {
+ if (lastOnlineVersion.empty())
+ lastOnlineVersion = getUnknownVersionTag();
+ }
}
}
-
-
diff --git a/FreeFileSync/Source/ui/version_check.h b/FreeFileSync/Source/ui/version_check.h
index 1a88997b..e7b75f13 100644
--- a/FreeFileSync/Source/ui/version_check.h
+++ b/FreeFileSync/Source/ui/version_check.h
@@ -7,20 +7,15 @@
#ifndef VERSION_CHECK_H_324872374893274983275
#define VERSION_CHECK_H_324872374893274983275
-//#include <functional>
-//#include <memory>
#include <wx/window.h>
#include <zen/stl_tools.h>
namespace fff
{
-bool updateCheckActive (time_t lastUpdateCheck);
-void disableUpdateCheck(time_t& lastUpdateCheck);
bool haveNewerVersionOnline(const std::string& onlineVersion);
//----------------------------------------------------------------------------
-//periodic update check:
-bool shouldRunAutomaticUpdateCheck(time_t lastUpdateCheck);
+bool automaticUpdateCheckDue(time_t lastUpdateCheck);
struct UpdateCheckResultPrep;
struct UpdateCheckResult;
diff --git a/FreeFileSync/Source/version/version.h b/FreeFileSync/Source/version/version.h
index 3b1d7c53..cb647a02 100644
--- a/FreeFileSync/Source/version/version.h
+++ b/FreeFileSync/Source/version/version.h
@@ -3,7 +3,7 @@
namespace fff
{
-const char ffsVersion[] = "12.5"; //internal linkage!
+const char ffsVersion[] = "13.0"; //internal linkage!
const char FFS_VERSION_SEPARATOR = '.';
}
diff --git a/wx+/grid.cpp b/wx+/grid.cpp
index 68f40fb6..8e9fe592 100644
--- a/wx+/grid.cpp
+++ b/wx+/grid.cpp
@@ -171,7 +171,7 @@ void GridData::drawCellText(wxDC& dc, const wxRect& rect, const std::wstring& te
if (extentTrunc.GetWidth() > rect.width)
{
//unlike Windows Explorer, we truncate UTF-16 correctly: e.g. CJK-Ideograph encodes to TWO wchar_t: utfTo<std::wstring>("\xf0\xa4\xbd\x9c");
- size_t low = 0; //number of unicode chars!
+ size_t low = 0; //number of Unicode chars!
size_t high = unicodeLength(text); //
if (high > 1)
for (;;)
@@ -1991,6 +1991,9 @@ void Grid::setRowHeight(int height)
}
+int Grid::getRowHeight() const { return rowLabelWin_->getRowHeight(); }
+
+
void Grid::setColumnConfig(const std::vector<Grid::ColAttributes>& attr)
{
//hold ownership of non-visible columns
diff --git a/wx+/grid.h b/wx+/grid.h
index 302abda8..471ba31b 100644
--- a/wx+/grid.h
+++ b/wx+/grid.h
@@ -151,6 +151,7 @@ public:
size_t getRowCount() const;
void setRowHeight(int height);
+ int getRowHeight() const;
struct ColAttributes
{
diff --git a/wx+/image_tools.cpp b/wx+/image_tools.cpp
index 04f7f7ff..7b6d943b 100644
--- a/wx+/image_tools.cpp
+++ b/wx+/image_tools.cpp
@@ -170,7 +170,7 @@ wxImage zen::createImageFromText(const wxString& text, const wxFont& font, const
{
wxMemoryDC dc; //the context used for bitmaps
setScaleFactor(dc, getDisplayScaleFactor());
- dc.SetFont(font); //the font parameter of GetMultiLineTextExtent() is not evaluated on OS X, wxWidgets 2.9.5, so apply it to the DC directly!
+ dc.SetFont(font); //the font parameter of GetTextExtent() is not evaluated on OS X, wxWidgets 2.9.5, so apply it to the DC directly!
std::vector<std::pair<wxString, wxSize>> lineInfo; //text + extent
for (const wxString& line : splitCpy(text, L'\n', SplitOnEmpty::allow))
diff --git a/wx+/popup_dlg_generated.cpp b/wx+/popup_dlg_generated.cpp
index b7e3d94d..aa5eb234 100644
--- a/wx+/popup_dlg_generated.cpp
+++ b/wx+/popup_dlg_generated.cpp
@@ -11,88 +11,88 @@
PopupDialogGenerated::PopupDialogGenerated( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style )
{
- this->SetSizeHints( wxSize( -1, -1 ), wxDefaultSize );
- this->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) );
+ this->SetSizeHints( wxSize( -1,-1 ), wxDefaultSize );
+ this->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) );
- wxBoxSizer* bSizer24;
- bSizer24 = new wxBoxSizer( wxVERTICAL );
+ wxBoxSizer* bSizer24;
+ bSizer24 = new wxBoxSizer( wxVERTICAL );
- m_panel33 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
- m_panel33->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) );
+ m_panel33 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
+ m_panel33->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) );
- wxBoxSizer* bSizer165;
- bSizer165 = new wxBoxSizer( wxHORIZONTAL );
+ wxBoxSizer* bSizer165;
+ bSizer165 = new wxBoxSizer( wxHORIZONTAL );
- m_bitmapMsgType = new wxStaticBitmap( m_panel33, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), 0 );
- bSizer165->Add( m_bitmapMsgType, 0, wxALL, 10 );
+ m_bitmapMsgType = new wxStaticBitmap( m_panel33, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), 0 );
+ bSizer165->Add( m_bitmapMsgType, 0, wxALL, 10 );
- wxBoxSizer* bSizer16;
- bSizer16 = new wxBoxSizer( wxVERTICAL );
+ wxBoxSizer* bSizer16;
+ bSizer16 = new wxBoxSizer( wxVERTICAL );
- bSizer16->Add( 0, 10, 0, 0, 5 );
+ bSizer16->Add( 0, 10, 0, 0, 5 );
- m_staticTextMain = new wxStaticText( m_panel33, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 );
- m_staticTextMain->Wrap( -1 );
- bSizer16->Add( m_staticTextMain, 0, wxBOTTOM|wxRIGHT, 10 );
+ m_staticTextMain = new wxStaticText( m_panel33, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 );
+ m_staticTextMain->Wrap( -1 );
+ bSizer16->Add( m_staticTextMain, 0, wxBOTTOM|wxRIGHT, 10 );
- m_richTextDetail = new wxRichTextCtrl( m_panel33, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY|wxBORDER_NONE|wxVSCROLL|wxWANTS_CHARS );
- bSizer16->Add( m_richTextDetail, 1, wxEXPAND, 5 );
+ m_richTextDetail = new wxRichTextCtrl( m_panel33, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY|wxBORDER_NONE|wxVSCROLL|wxWANTS_CHARS );
+ bSizer16->Add( m_richTextDetail, 1, wxEXPAND, 5 );
- bSizer165->Add( bSizer16, 1, wxEXPAND, 5 );
+ bSizer165->Add( bSizer16, 1, wxEXPAND, 5 );
- m_panel33->SetSizer( bSizer165 );
- m_panel33->Layout();
- bSizer165->Fit( m_panel33 );
- bSizer24->Add( m_panel33, 1, wxEXPAND, 5 );
+ m_panel33->SetSizer( bSizer165 );
+ m_panel33->Layout();
+ bSizer165->Fit( m_panel33 );
+ bSizer24->Add( m_panel33, 1, wxEXPAND, 5 );
- m_staticline6 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
- bSizer24->Add( m_staticline6, 0, wxEXPAND, 5 );
+ m_staticline6 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
+ bSizer24->Add( m_staticline6, 0, wxEXPAND, 5 );
- wxBoxSizer* bSizer25;
- bSizer25 = new wxBoxSizer( wxVERTICAL );
+ wxBoxSizer* bSizer25;
+ bSizer25 = new wxBoxSizer( wxVERTICAL );
- m_checkBoxCustom = new wxCheckBox( this, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 );
- bSizer25->Add( m_checkBoxCustom, 0, wxALIGN_CENTER_HORIZONTAL|wxALL, 5 );
+ m_checkBoxCustom = new wxCheckBox( this, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 );
+ bSizer25->Add( m_checkBoxCustom, 0, wxALIGN_CENTER_HORIZONTAL|wxALL, 5 );
- bSizerStdButtons = new wxBoxSizer( wxHORIZONTAL );
+ bSizerStdButtons = new wxBoxSizer( wxHORIZONTAL );
- m_buttonAccept = new wxButton( this, wxID_YES, _("dummy"), wxDefaultPosition, wxSize( -1, -1 ), 0 );
+ m_buttonAccept = new wxButton( this, wxID_YES, _("dummy"), wxDefaultPosition, wxSize( -1,-1 ), 0 );
- m_buttonAccept->SetDefault();
- bSizerStdButtons->Add( m_buttonAccept, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT|wxLEFT, 5 );
+ m_buttonAccept->SetDefault();
+ bSizerStdButtons->Add( m_buttonAccept, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT|wxLEFT, 5 );
- m_buttonAccept2 = new wxButton( this, wxID_YESTOALL, _("dummy"), wxDefaultPosition, wxSize( -1, -1 ), 0 );
- bSizerStdButtons->Add( m_buttonAccept2, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT, 5 );
+ m_buttonAccept2 = new wxButton( this, wxID_YESTOALL, _("dummy"), wxDefaultPosition, wxSize( -1,-1 ), 0 );
+ bSizerStdButtons->Add( m_buttonAccept2, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT, 5 );
- m_buttonDecline = new wxButton( this, wxID_NO, _("dummy"), wxDefaultPosition, wxSize( -1, -1 ), 0 );
- bSizerStdButtons->Add( m_buttonDecline, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT, 5 );
+ m_buttonDecline = new wxButton( this, wxID_NO, _("dummy"), wxDefaultPosition, wxSize( -1,-1 ), 0 );
+ bSizerStdButtons->Add( m_buttonDecline, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT, 5 );
- m_buttonCancel = new wxButton( this, wxID_CANCEL, _("Cancel"), wxDefaultPosition, wxSize( -1, -1 ), 0 );
- bSizerStdButtons->Add( m_buttonCancel, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT, 5 );
+ m_buttonCancel = new wxButton( this, wxID_CANCEL, _("Cancel"), wxDefaultPosition, wxSize( -1,-1 ), 0 );
+ bSizerStdButtons->Add( m_buttonCancel, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT, 5 );
- bSizer25->Add( bSizerStdButtons, 0, wxALIGN_RIGHT, 5 );
+ bSizer25->Add( bSizerStdButtons, 0, wxALIGN_RIGHT, 5 );
- bSizer24->Add( bSizer25, 0, wxEXPAND, 5 );
+ bSizer24->Add( bSizer25, 0, wxEXPAND, 5 );
- this->SetSizer( bSizer24 );
- this->Layout();
- bSizer24->Fit( this );
+ this->SetSizer( bSizer24 );
+ this->Layout();
+ bSizer24->Fit( this );
- this->Centre( wxBOTH );
+ this->Centre( wxBOTH );
- // Connect Events
- this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( PopupDialogGenerated::onClose ) );
- m_checkBoxCustom->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( PopupDialogGenerated::onCheckBoxClick ), NULL, this );
- m_buttonAccept->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PopupDialogGenerated::onButtonAccept ), NULL, this );
- m_buttonAccept2->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PopupDialogGenerated::onButtonAccept2 ), NULL, this );
- m_buttonDecline->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PopupDialogGenerated::onButtonDecline ), NULL, this );
- m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PopupDialogGenerated::onCancel ), NULL, this );
+ // Connect Events
+ this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( PopupDialogGenerated::onClose ) );
+ m_checkBoxCustom->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( PopupDialogGenerated::onCheckBoxClick ), NULL, this );
+ m_buttonAccept->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PopupDialogGenerated::onButtonAccept ), NULL, this );
+ m_buttonAccept2->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PopupDialogGenerated::onButtonAccept2 ), NULL, this );
+ m_buttonDecline->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PopupDialogGenerated::onButtonDecline ), NULL, this );
+ m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PopupDialogGenerated::onCancel ), NULL, this );
}
PopupDialogGenerated::~PopupDialogGenerated()
@@ -101,22 +101,22 @@ PopupDialogGenerated::~PopupDialogGenerated()
TooltipDlgGenerated::TooltipDlgGenerated( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style )
{
- this->SetSizeHints( wxDefaultSize, wxDefaultSize );
+ this->SetSizeHints( wxDefaultSize, wxDefaultSize );
- wxBoxSizer* bSizer158;
- bSizer158 = new wxBoxSizer( wxHORIZONTAL );
+ wxBoxSizer* bSizer158;
+ bSizer158 = new wxBoxSizer( wxHORIZONTAL );
- m_bitmapLeft = new wxStaticBitmap( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 );
- bSizer158->Add( m_bitmapLeft, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 );
+ m_bitmapLeft = new wxStaticBitmap( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 );
+ bSizer158->Add( m_bitmapLeft, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 );
- m_staticTextMain = new wxStaticText( this, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 );
- m_staticTextMain->Wrap( 600 );
- bSizer158->Add( m_staticTextMain, 0, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 );
+ m_staticTextMain = new wxStaticText( this, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 );
+ m_staticTextMain->Wrap( 600 );
+ bSizer158->Add( m_staticTextMain, 0, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 );
- this->SetSizer( bSizer158 );
- this->Layout();
- bSizer158->Fit( this );
+ this->SetSizer( bSizer158 );
+ this->Layout();
+ bSizer158->Fit( this );
}
TooltipDlgGenerated::~TooltipDlgGenerated()
diff --git a/wx+/popup_dlg_generated.h b/wx+/popup_dlg_generated.h
index ce4e2ac8..65c8c11b 100644
--- a/wx+/popup_dlg_generated.h
+++ b/wx+/popup_dlg_generated.h
@@ -38,35 +38,35 @@
///////////////////////////////////////////////////////////////////////////////
class PopupDialogGenerated : public wxDialog
{
-private:
+ private:
-protected:
- wxPanel* m_panel33;
- wxStaticBitmap* m_bitmapMsgType;
- wxStaticText* m_staticTextMain;
- wxRichTextCtrl* m_richTextDetail;
- wxStaticLine* m_staticline6;
- wxCheckBox* m_checkBoxCustom;
- wxBoxSizer* bSizerStdButtons;
- wxButton* m_buttonAccept;
- wxButton* m_buttonAccept2;
- wxButton* m_buttonDecline;
- wxButton* m_buttonCancel;
+ protected:
+ wxPanel* m_panel33;
+ wxStaticBitmap* m_bitmapMsgType;
+ wxStaticText* m_staticTextMain;
+ wxRichTextCtrl* m_richTextDetail;
+ wxStaticLine* m_staticline6;
+ wxCheckBox* m_checkBoxCustom;
+ wxBoxSizer* bSizerStdButtons;
+ wxButton* m_buttonAccept;
+ wxButton* m_buttonAccept2;
+ wxButton* m_buttonDecline;
+ wxButton* m_buttonCancel;
- // Virtual event handlers, override them in your derived class
- virtual void onClose( wxCloseEvent& event ) { event.Skip(); }
- virtual void onCheckBoxClick( wxCommandEvent& event ) { event.Skip(); }
- virtual void onButtonAccept( wxCommandEvent& event ) { event.Skip(); }
- virtual void onButtonAccept2( wxCommandEvent& event ) { event.Skip(); }
- virtual void onButtonDecline( wxCommandEvent& event ) { event.Skip(); }
- virtual void onCancel( wxCommandEvent& event ) { event.Skip(); }
+ // Virtual event handlers, override them in your derived class
+ virtual void onClose( wxCloseEvent& event ) { event.Skip(); }
+ virtual void onCheckBoxClick( wxCommandEvent& event ) { event.Skip(); }
+ virtual void onButtonAccept( wxCommandEvent& event ) { event.Skip(); }
+ virtual void onButtonAccept2( wxCommandEvent& event ) { event.Skip(); }
+ virtual void onButtonDecline( wxCommandEvent& event ) { event.Skip(); }
+ virtual void onCancel( wxCommandEvent& event ) { event.Skip(); }
-public:
+ public:
- PopupDialogGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("dummy"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER );
+ PopupDialogGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("dummy"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER );
- ~PopupDialogGenerated();
+ ~PopupDialogGenerated();
};
@@ -75,17 +75,17 @@ public:
///////////////////////////////////////////////////////////////////////////////
class TooltipDlgGenerated : public wxDialog
{
-private:
+ private:
-protected:
+ protected:
-public:
- wxStaticBitmap* m_bitmapLeft;
- wxStaticText* m_staticTextMain;
+ public:
+ wxStaticBitmap* m_bitmapLeft;
+ wxStaticText* m_staticTextMain;
- TooltipDlgGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxEmptyString, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE );
+ TooltipDlgGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxEmptyString, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE );
- ~TooltipDlgGenerated();
+ ~TooltipDlgGenerated();
};
diff --git a/wx+/tooltip.cpp b/wx+/tooltip.cpp
index 19b6ede7..79d4fa1a 100644
--- a/wx+/tooltip.cpp
+++ b/wx+/tooltip.cpp
@@ -4,6 +4,7 @@
// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
// *****************************************************************************
#include "tooltip.h"
+#include <zen/zstring.h>
#include <wx/dialog.h>
#include <wx/stattext.h>
#include <wx/sizer.h>
@@ -62,7 +63,7 @@ void Tooltip::show(const wxString& text, wxPoint mousePos, const wxImage* img)
const wxImage& newImg = img ? *img : wxNullImage;
const bool imgChanged = !newImg.IsSameAs(lastUsedImg_);
- const bool txtChanged = text != tipWindow_->staticTextMain_->GetLabelText();
+ const bool txtChanged = text != lastUsedText_;
if (imgChanged)
{
@@ -73,7 +74,8 @@ void Tooltip::show(const wxString& text, wxPoint mousePos, const wxImage* img)
if (txtChanged)
{
- tipWindow_->staticTextMain_->SetLabelText(text);
+ lastUsedText_ = text;
+ tipWindow_->staticTextMain_->SetLabelText(text);
tipWindow_->staticTextMain_->Wrap(fastFromDIP(600));
}
diff --git a/wx+/tooltip.h b/wx+/tooltip.h
index b496f36c..e20cb3da 100644
--- a/wx+/tooltip.h
+++ b/wx+/tooltip.h
@@ -28,6 +28,7 @@ private:
TooltipDlgGenerated* tipWindow_ = nullptr;
wxWindow& parent_;
wxImage lastUsedImg_;
+ wxString lastUsedText_; //needed, considering "SetLabelText(textFixed)"
};
}
diff --git a/zen/file_access.cpp b/zen/file_access.cpp
index 4ce66acf..af19df87 100644
--- a/zen/file_access.cpp
+++ b/zen/file_access.cpp
@@ -273,6 +273,18 @@ void zen::removeDirectoryPlainRecursion(const Zstring& dirPath) //throw FileErro
namespace
{
+std::wstring generateMoveErrorMsg(const Zstring& pathFrom, const Zstring& pathTo)
+{
+ if (getParentFolderPath(pathFrom) == getParentFolderPath(pathTo)) //pure "rename"
+ return replaceCpy(replaceCpy(_("Cannot rename %x to %y."),
+ L"%x", fmtPath(pathFrom)),
+ L"%y", fmtPath(getItemName(pathTo)));
+ else //"move" or "move + rename"
+ return trimCpy(replaceCpy(replaceCpy(_("Cannot move %x to %y."),
+ L"%x", L'\n' + fmtPath(pathFrom)),
+ L"%y", L'\n' + fmtPath(pathTo)));
+}
+
/* Usage overview: (avoid circular pattern!)
moveAndRenameItem() --> moveAndRenameFileSub()
@@ -283,7 +295,7 @@ namespace
//wrapper for file system rename function:
void moveAndRenameFileSub(const Zstring& pathFrom, const Zstring& pathTo, bool replaceExisting) //throw FileError, ErrorMoveUnsupported, ErrorTargetExisting
{
- auto getErrorMsg = [&] { return replaceCpy(replaceCpy(_("Cannot move file %x to %y."), L"%x", L'\n' + fmtPath(pathFrom)), L"%y", L'\n' + fmtPath(pathTo)); };
+ auto getErrorMsg = [&] { return generateMoveErrorMsg(pathFrom, pathTo); };
//rename() will never fail with EEXIST, but always (atomically) overwrite!
//=> equivalent to SetFileInformationByHandle() + FILE_RENAME_INFO::ReplaceIfExists or ::MoveFileEx() + MOVEFILE_REPLACE_EXISTING
@@ -338,6 +350,8 @@ void zen::moveAndRenameItem(const Zstring& pathFrom, const Zstring& pathTo, bool
}
+
+
namespace
{
void setWriteTimeNative(const Zstring& itemPath, const timespec& modTime, ProcSymlink procSl) //throw FileError
@@ -714,10 +728,7 @@ FileCopyResult zen::copyNewFile(const Zstring& sourceFile, const Zstring& target
macOS: https://freefilesync.org/forum/viewtopic.php?t=356 */
setWriteTimeNative(targetFile, sourceInfo.st_mtim, ProcSymlink::follow); //throw FileError
}
- catch (const FileError& e)
- {
- errorModTime = FileError(e.toString()); //avoid slicing
- }
+ catch (const FileError& e) { errorModTime = e; /*might slice derived class?*/ }
return
{
diff --git a/zen/http.cpp b/zen/http.cpp
index 8cdcd65c..fcf69fb3 100644
--- a/zen/http.cpp
+++ b/zen/http.cpp
@@ -265,7 +265,17 @@ std::unique_ptr<HttpInputStream::Impl> sendHttpRequestImpl(const Zstring& url,
else
{
if (httpStatus != 200) //HTTP_STATUS_OK
- throw SysError(formatHttpError(httpStatus)); //e.g. "HTTP status 404: Not found."
+ {
+#if 0 //beneficial to add error details?
+ std::wstring errorDetails;
+ try
+ {
+ errorDetails = utfTo<std::wstring>(HttpInputStream(std::move(response)).readAll(nullptr /*notifyUnbufferedIO*/)); //throw SysError
+ }
+ catch (const SysError& e) { errorDetails = e.toString(); }
+#endif
+ throw SysError(formatHttpError(httpStatus) /*+ L' ' + errorDetails*/); //e.g. "HTTP status 404: Not found."
+ }
return response;
}
diff --git a/zen/zstring.h b/zen/zstring.h
index 5bcc8b34..bf7ac526 100644
--- a/zen/zstring.h
+++ b/zen/zstring.h
@@ -84,9 +84,11 @@ const wchar_t EM_DASH = L'\u2014';
const wchar_t* const SPACED_DASH = L" \u2014 "; //using 'EM DASH'
const wchar_t* const ELLIPSIS = L"\u2026"; //"..."
const wchar_t MULT_SIGN = L'\u00D7'; //fancy "x"
-//const wchar_t NOBREAK_SPACE = L'\u00A0';
+const wchar_t NOBREAK_SPACE = L'\u00A0';
const wchar_t ZERO_WIDTH_SPACE = L'\u200B';
+const wchar_t EN_SPACE = L'\u2002';
+
const wchar_t LTR_MARK = L'\u200E'; //UTF-8: E2 80 8E
const wchar_t RTL_MARK = L'\u200F'; //UTF-8: E2 80 8F https://www.w3.org/International/questions/qa-bidi-unicode-controls
//const wchar_t BIDI_DIR_ISOLATE_RTL = L'\u2067'; //=> not working on Win 10
@@ -94,6 +96,8 @@ const wchar_t RTL_MARK = L'\u200F'; //UTF-8: E2 80 8F https://www.w3.org/Interna
//const wchar_t BIDI_DIR_EMBEDDING_RTL = L'\u202B'; //=> not working on Win 10
//const wchar_t BIDI_POP_DIR_FORMATTING = L'\u202C'; //=> not working on Win 10
+const wchar_t RIGHT_ARROW_CURV_DOWN = L'\u2935'; //Right Arrow Curving Down
+
const wchar_t* const TAB_SPACE = L" "; //4: the only sensible space count for tabs
#endif //ZSTRING_H_73425873425789
bgstack15