summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Wilhelm <daniel@wili.li>2015-10-02 14:54:15 +0200
committerDaniel Wilhelm <daniel@wili.li>2015-10-02 14:54:15 +0200
commit7293645a25d10dc4d6b874d821f9ad802b30d3ed (patch)
tree48b058b2bddd4feb2318fc5b56fd580efca64b87
parent6.12 (diff)
downloadFreeFileSync-7293645a25d10dc4d6b874d821f9ad802b30d3ed.tar.gz
FreeFileSync-7293645a25d10dc4d6b874d821f9ad802b30d3ed.tar.bz2
FreeFileSync-7293645a25d10dc4d6b874d821f9ad802b30d3ed.zip
6.13
-rw-r--r--FreeFileSync/Build/Changelog.txt19
-rw-r--r--FreeFileSync/Build/Help/html/Exclude Items.html4
-rw-r--r--FreeFileSync/Build/Languages/ukrainian.lng (renamed from FreeFileSync/Build/Languages/outdated/ukrainian.lng)332
-rw-r--r--FreeFileSync/Source/RealtimeSync/Makefile1
-rw-r--r--FreeFileSync/Source/RealtimeSync/application.cpp4
-rw-r--r--FreeFileSync/Source/algorithm.cpp59
-rw-r--r--FreeFileSync/Source/algorithm.h4
-rw-r--r--FreeFileSync/Source/application.cpp8
-rw-r--r--FreeFileSync/Source/comparison.cpp207
-rw-r--r--FreeFileSync/Source/file_hierarchy.cpp61
-rw-r--r--FreeFileSync/Source/file_hierarchy.h40
-rw-r--r--FreeFileSync/Source/lib/db_file.cpp16
-rw-r--r--FreeFileSync/Source/lib/dir_lock.cpp4
-rw-r--r--FreeFileSync/Source/lib/hard_filter.cpp287
-rw-r--r--FreeFileSync/Source/lib/hard_filter.h190
-rw-r--r--FreeFileSync/Source/lib/norm_filter.h23
-rw-r--r--FreeFileSync/Source/lib/parallel_scan.cpp19
-rw-r--r--FreeFileSync/Source/lib/parallel_scan.h7
-rw-r--r--FreeFileSync/Source/lib/resolve_path.cpp56
-rw-r--r--FreeFileSync/Source/lib/status_handler_impl.h4
-rw-r--r--FreeFileSync/Source/structures.cpp5
-rw-r--r--FreeFileSync/Source/structures.h11
-rw-r--r--FreeFileSync/Source/synchronization.cpp36
-rw-r--r--FreeFileSync/Source/ui/batch_status_handler.cpp26
-rw-r--r--FreeFileSync/Source/ui/custom_grid.cpp6
-rw-r--r--FreeFileSync/Source/ui/folder_history_box.cpp15
-rw-r--r--FreeFileSync/Source/ui/folder_history_box.h2
-rw-r--r--FreeFileSync/Source/ui/grid_view.cpp2
-rw-r--r--FreeFileSync/Source/ui/gui_status_handler.cpp4
-rw-r--r--FreeFileSync/Source/ui/main_dlg.cpp64
-rw-r--r--FreeFileSync/Source/ui/on_completion_box.cpp11
-rw-r--r--FreeFileSync/Source/ui/progress_indicator.cpp15
-rw-r--r--FreeFileSync/Source/ui/sync_cfg.cpp2
-rw-r--r--FreeFileSync/Source/ui/tree_view.cpp2
-rw-r--r--FreeFileSync/Source/version/version.h2
-rw-r--r--FreeFileSync/Source/version/version.iss2
-rw-r--r--zen/file_access.cpp250
-rw-r--r--zen/stl_tools.h14
38 files changed, 1013 insertions, 801 deletions
diff --git a/FreeFileSync/Build/Changelog.txt b/FreeFileSync/Build/Changelog.txt
index b6a748c5..2d7d9f9e 100644
--- a/FreeFileSync/Build/Changelog.txt
+++ b/FreeFileSync/Build/Changelog.txt
@@ -1,3 +1,22 @@
+FreeFileSync 6.13
+-----------------
+Fixed crash when failing to create log file during batch run
+Show directory traversal errors as conflict category on grid
+Improved file filter behavior for certain edge cases when updating the database
+Fixed crash when task scheduler ends FreeFileSync after a certain time (Windows)
+Don't show alternative folder paths if volume name is empty
+Support silent installation for Inno Setup (Windows)
+Fixed recursive yield when minimized into notification area (Linux, OS X)
+Include ACLs when copying file and folder permissions (OS X)
+New file copy routine including extended attributes (OS X)
+Fixed failure to permanently delete directories containing symlinks
+Copy extended attributes when creating new folders and symlinks (OS X)
+Restore process umask after creating lock file (Linux, OS X)
+Copy directory permissions by default (Linux, OS X)
+Optimized construction of merged path filters
+Exclude items subject to traversal errors when updating the database
+
+
FreeFileSync 6.12 [2014-12-01]
------------------------------
New "Actions" menu bar entry with basic operations
diff --git a/FreeFileSync/Build/Help/html/Exclude Items.html b/FreeFileSync/Build/Help/html/Exclude Items.html
index c5c71088..7aeb099e 100644
--- a/FreeFileSync/Build/Help/html/Exclude Items.html
+++ b/FreeFileSync/Build/Help/html/Exclude Items.html
@@ -99,10 +99,10 @@ include list and <B>none</B> of the entries in the exclude list as presented in
<TR>
<TD WIDTH="65%" STYLE="border-bottom: 1px solid black">
- <P STYLE="margin-left: 0.8cm">Exclude all files and folders located in subdirectories of base directories</P>
+ <P STYLE="margin-left: 0.8cm">Exclude all subdirectories of the base directories</P>
</TD>
<TD WIDTH="35%" STYLE="border-bottom: 1px solid black; border-left: 1px solid black">
- <P STYLE="margin-left: 0.8cm"><FONT FACE="Courier New, monospace">\*\*</FONT></P>
+ <P STYLE="margin-left: 0.8cm"><FONT FACE="Courier New, monospace">*\</FONT></P>
</TD>
</TR>
<TR>
diff --git a/FreeFileSync/Build/Languages/outdated/ukrainian.lng b/FreeFileSync/Build/Languages/ukrainian.lng
index 65091b8f..71573e54 100644
--- a/FreeFileSync/Build/Languages/outdated/ukrainian.lng
+++ b/FreeFileSync/Build/Languages/ukrainian.lng
@@ -1,12 +1,18 @@
<header>
<language>Українська</language>
- <translator>Roman Ardan</translator>
+ <translator>Mykola Pavluchynskyi</translator>
<locale>uk_UA</locale>
<image>flag_ukraine.png</image>
<plural_count>3</plural_count>
<plural_definition>n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2</plural_definition>
</header>
+<source>Cannot copy attributes from %x to %y.</source>
+<target></target>
+
+<source>Cannot copy permissions from %x to %y.</source>
+<target></target>
+
<source>Both sides have changed since last synchronization.</source>
<target>З моменту останньої синхронізації з обох сторін відбулися зміни.</target>
@@ -20,10 +26,7 @@
<target>Запис бази даних не синхронізований з урахуванням поточних налаштувань.</target>
<source>Setting default synchronization directions: Old files will be overwritten with newer files.</source>
-<target>
-Налаштування напряму синхронізації за замовчуванням:
-Старі файли будуть замінені новішими файлами.
-</target>
+<target>Налаштування напрямку синхронізації за замовчуванням: Старі файли будуть замінені новішими файлами.</target>
<source>Checking recycle bin availability for folder %x...</source>
<target>Перевірка доступності Корзини для папки %x...</target>
@@ -35,7 +38,7 @@
<target>Переміщення папки %x до Корзини</target>
<source>Moving symbolic link %x to the recycle bin</source>
-<target>Переміщення символічного посилання %x до Корзини</target>
+<target>Переміщення символьного посилання %x до Корзини</target>
<source>Deleting file %x</source>
<target>Вилучення файлу %x</target>
@@ -53,25 +56,25 @@
<target>Відбулось виключення</target>
<source>A directory path is expected after %x.</source>
-<target>Після %x очікується шлях до каталогу.</target>
+<target>Після %x очікується шлях до папки.</target>
<source>Syntax error</source>
<target>Синтаксична помилка</target>
<source>Cannot open file %x.</source>
-<target>Не вдається фідкрити файл %x.</target>
+<target>Не вдається відкрити файл %x.</target>
<source>File %x does not contain a valid configuration.</source>
<target>Файл %x не містить правильної конфігурації.</target>
<source>Unequal number of left and right directories specified.</source>
-<target>Вказано різну кількість лівих і правих каталогів</target>
+<target>Вказано різну кількість лівих і правих папок</target>
<source>The config file must not contain settings at directory pair level when directories are set via command line.</source>
-<target>Конфігураційний файл не повинен містити налаштувань на рівні пар каталогів, якщо каталоги задаються командним рядком.</target>
+<target>Конфігураційний файл не повинен містити налаштувань на рівні пар папок, якщо папки задаються командним рядком.</target>
<source>Directories cannot be set for more than one configuration file.</source>
-<target>Каталоги не можуть бути призначені більш ніж одному файлу конфігурації.</target>
+<target>Папки не можуть бути призначені більш ніж одному файлу конфігурації.</target>
<source>Command line</source>
<target>Командний рядок</target>
@@ -80,13 +83,13 @@
<target>Синтаксис:</target>
<source>global config file:</source>
-<target>глобальні файли конфігурації:</target>
+<target>глобальний конфігураційний файл:</target>
<source>config files:</source>
<target>файли конфігурації:</target>
<source>directory</source>
-<target>каталог</target>
+<target>папка</target>
<source>Path to an alternate GlobalSettings.xml file.</source>
<target>Шлях до альтернативного файлу GlobalSettings.xml.</target>
@@ -95,10 +98,10 @@
<target>Будь-яка кількість FreeFileSync .ffs_gui та/або .ffs_batch файлів конфігурації.</target>
<source>Any number of alternative directory pairs for at most one config file.</source>
-<target>Будь-яка кількість альтернативних пар каталогів для не більше одного конфігураційного файлу.</target>
+<target>Будь-яка кількість альтернативних пар папок для не більше одного конфігураційного файлу.</target>
<source>Open configuration for edit without executing.</source>
-<target></target>
+<target>Відкрити конфігурацію для редагування без виконання.</target>
<source>Cannot find the following folders:</source>
<target>Не вдається знайти такі папки:</target>
@@ -113,7 +116,7 @@
<target>Відповідна папка буде вважатися порожньою.</target>
<source>The following folder paths are dependent from each other:</source>
-<target></target>
+<target>Наступні шляхи папок залежать один від одного:</target>
<source>File %x has an invalid date.</source>
<target>Файл %x має неіснуючу дату.</target>
@@ -149,7 +152,7 @@
<target>Встановлення напрямку синхронізації...</target>
<source>Out of memory.</source>
-<target>Бракує пам'яті.</target>
+<target>Недостатньо пам'яті.</target>
<source>Item exists on left side only</source>
<target>Елемент існує тільки ліворуч</target>
@@ -191,10 +194,10 @@
<target>Перемістити файли праворуч</target>
<source>Update left item</source>
-<target></target>
+<target>Оновити елемент ліворуч</target>
<source>Update right item</source>
-<target></target>
+<target>Оновити елемент праворуч</target>
<source>Do nothing</source>
<target>Нічого не робити</target>
@@ -231,7 +234,7 @@
<target>Несумісний файл бази даних %x.</target>
<source>Initial synchronization:</source>
-<target>Вступна синхронізація:</target>
+<target>Початкова синхронізація:</target>
<source>Database file %x does not yet exist.</source>
<target>Файл бази даних %x ще не існує.</target>
@@ -246,19 +249,19 @@
<target>Не вдається прочитати файл %x.</target>
<source>Database files do not share a common session.</source>
-<target>Файли баз даних не поділяють спільну сесію.</target>
+<target>Файли баз даних не мають спільної сесії.</target>
<source>Searching for folder %x...</source>
-<target>Пошук каталогу %x...</target>
+<target>Пошук папки %x...</target>
<source>Cannot read file attributes of %x.</source>
-<target>Не вдається прочитати атрибути файла %x.</target>
+<target>Не вдається прочитати атрибути файлу %x.</target>
<source>Cannot get process information.</source>
<target>Не вдається отримати інформацію процесу</target>
<source>Waiting while directory is locked:</source>
-<target>Очікування на заблокований каталог:</target>
+<target>Очікування поки папка заблокована:</target>
<source>Lock owner:</source>
<target>Власник блокування:</target>
@@ -280,7 +283,7 @@
<target>Створення файлу %x</target>
<source>Saving file %x...</source>
-<target></target>
+<target>Збереження файлу %x...</target>
<source>Items processed:</source>
<target>Елементів оброблено:</target>
@@ -292,22 +295,22 @@
<target>Загальний час:</target>
<source>Error parsing file %x, row %y, column %z.</source>
-<target>Помилка розбору файла %x, рядок %y, колонка %z.</target>
+<target>Помилка розбору файлу %x, рядок %y, колонка %z.</target>
<source>Cannot set directory lock for %x.</source>
-<target>Не вдається замкнути каталога %x.</target>
+<target>Не вдається встановити блокування папки %x.</target>
<source>Scanning:</source>
-<target>Сканую:</target>
+<target>Сканування:</target>
<source>
<pluralform>1 thread</pluralform>
<pluralform>%x threads</pluralform>
</source>
<target>
-<pluralform>%x нить виконання</pluralform>
-<pluralform>%x ниті виконання</pluralform>
-<pluralform>%x нитей виконання</pluralform>
+<pluralform>%x потік виконання</pluralform>
+<pluralform>%x потоки виконання</pluralform>
+<pluralform>%x потоків виконання</pluralform>
</target>
<source>/sec</source>
@@ -323,7 +326,7 @@
<target>Відкрити за допомогою програми за замовчуванням</target>
<source>Browse directory</source>
-<target>Переглянути каталог</target>
+<target>Переглянути папку</target>
<source>Cannot access the Volume Shadow Copy Service.</source>
<target>Не вдається отримати доступ до послуги Тіньового Копіювання Тому.</target>
@@ -335,7 +338,7 @@
<target>Не вдалося встановити ім'я тому для %x.</target>
<source>Volume name %x is not part of file path %y.</source>
-<target>Ім'я тому %x не є частиною файловго шляху %y.</target>
+<target>Ім'я тому %x не є частиною файлового шляху %y.</target>
<source>Stop requested: Waiting for current operation to finish...</source>
<target>Запит зупинки: очікування завершення поточної операції...</target>
@@ -350,19 +353,19 @@
<target>Зберегти &як...</target>
<source>E&xit</source>
-<target></target>
+<target>В&ихід</target>
<source>&File</source>
-<target></target>
+<target>&Файл</target>
<source>&View help</source>
-<target>&Перегляд довідки</target>
+<target>П&ерегляд довідки</target>
<source>&About</source>
-<target>&Про програму</target>
+<target>Пр&о програму</target>
<source>&Help</source>
-<target>&Допомога</target>
+<target>&Довідка</target>
<source>Usage:</source>
<target>Використання:</target>
@@ -380,7 +383,7 @@
<target>Щоб розпочати імпортуйте .ffs_batch файл.</target>
<source>Folders to watch:</source>
-<target>Папки для спостеження</target>
+<target>Папки для спостереження</target>
<source>Add folder</source>
<target>Додати папку</target>
@@ -398,7 +401,7 @@
<target>Час очікування (секунд):</target>
<source>Idle time between last detected change and execution of command</source>
-<target>Час простою між виявленням останньої зміни та виконанням команди</target>
+<target>Час очікування між виявленням останньої зміни та виконанням команди</target>
<source>Command line:</source>
<target>Командний рядок:</target>
@@ -410,8 +413,8 @@ The command is triggered if:
</source>
<target>
Команда спрацьовує, якщо:
-- змінилися файли або вкладені папки
-- появилися нові папки (наприклад, підключена USB-пам'ять)
+- змінилися файли або підпапки
+- з'явилися нові папки (наприклад, підключений USB флеш-носій)
</target>
<source>&Start</source>
@@ -421,7 +424,7 @@ The command is triggered if:
<target>Про</target>
<source>Build: %x</source>
-<target>компіляція %x</target>
+<target>Збірка: %x</target>
<source>All files</source>
<target>Всі файли</target>
@@ -430,22 +433,22 @@ The command is triggered if:
<target>Автоматична Синхронізація</target>
<source>Directory monitoring active</source>
-<target>Моніторинг каталогів активний</target>
+<target>Моніторинг папок активний</target>
<source>Waiting until all directories are available...</source>
-<target>Очікування доступності всіх каталогів...</target>
+<target>Очікування доступності всіх папок...</target>
<source>Error</source>
<target>Помилка</target>
<source>&Restore</source>
-<target>&Відновити</target>
+<target>Від&новити</target>
<source>&Show error</source>
<target>&Показати помилку</target>
<source>&Quit</source>
-<target>&Вихід</target>
+<target>В&ихід</target>
<source>Incorrect command line:</source>
<target>Неправильний командний рядок:</target>
@@ -493,10 +496,10 @@ The command is triggered if:
<target>Створення папки %x</target>
<source>Updating file %x</source>
-<target></target>
+<target>Оновлення файлу %x</target>
<source>Updating symbolic link %x</source>
-<target></target>
+<target>Оновлення символьних посилань %x</target>
<source>Verifying file %x</source>
<target>Перевірка файлу %x</target>
@@ -514,13 +517,13 @@ The command is triggered if:
<target>Цільова папка %x вже існує.</target>
<source>Cannot find folder %x.</source>
-<target></target>
+<target>Неможливо знайти папку %x.</target>
<source>Target folder input field must not be empty.</source>
<target>Поле цільової папки не повинно бути порожнім.</target>
<source>Source folder %x not found.</source>
-<target>Вихідний каталог %x не знайдено.</target>
+<target>Вихідну папку %x не знайдено.</target>
<source>Please enter a target folder for versioning.</source>
<target>Будь ласка, введіть цільову папку для версій.</target>
@@ -532,7 +535,7 @@ The command is triggered if:
<target>Ці папки істотно відрізняються. Переконайтеся, що ви вказали відповідні папки для синхронізації.</target>
<source>Not enough free disk space available in:</source>
-<target>Не достатньо вільного місця в:</target>
+<target>Не достатньо вільного місця на:</target>
<source>Required:</source>
<target>Потрібно:</target>
@@ -541,7 +544,7 @@ The command is triggered if:
<target>Доступно:</target>
<source>Multiple folder pairs write to a common subfolder. Please review your configuration.</source>
-<target></target>
+<target>Декілька пар папок пишуть до спільної підпапки. Будь ласка, перевірте конфігурацію.</target>
<source>Synchronizing folder pair:</source>
<target>Синхронізація пари папок:</target>
@@ -568,7 +571,7 @@ The command is triggered if:
<target>Синхронізація успішно завершена</target>
<source>Cleaning up old log files...</source>
-<target></target>
+<target>Очистка старих журналів...</target>
<source>Stopped</source>
<target>Зупинено</target>
@@ -583,7 +586,7 @@ The command is triggered if:
<target>&Ігнорувати</target>
<source>&Switch</source>
-<target>&Змінити</target>
+<target>&Перейти</target>
<source>Switching to FreeFileSync's main window</source>
<target>Перехід до головного вікна FreeFileSync</target>
@@ -623,10 +626,10 @@ The command is triggered if:
<target>У Вас найновіша версія FreeFileSync.</target>
<source>Unable to connect to FreeFileSync.org.</source>
-<target></target>
+<target>Не вдалось підключитись до FreeFileSync.org.</target>
<source>Cannot find current FreeFileSync version number online. Do you want to check manually?</source>
-<target>Не вдається знайти номер поточної версії FreeFileSync онлайн. Бажаєте перевірити вручну?</target>
+<target>Не вдається знайти номер поточної версії FreeFileSync он-лайн. Бажаєте перевірити вручну?</target>
<source>&Check</source>
<target>&Перевірити</target>
@@ -644,7 +647,7 @@ The command is triggered if:
<target>Назва</target>
<source>Relative folder</source>
-<target></target>
+<target>Відносна папка</target>
<source>Base folder</source>
<target>Базова папка</target>
@@ -677,28 +680,28 @@ The command is triggered if:
<target>Локальний фільтр</target>
<source>Active</source>
-<target>Активні</target>
+<target>Активний</target>
<source>None</source>
-<target>Відсутні</target>
+<target>Відсутній</target>
<source>Remove local settings</source>
<target>Вилучити локальні налаштування</target>
<source>Clear local filter</source>
-<target></target>
+<target>Очистити локальний фільтр</target>
<source>Copy</source>
<target>Копіювати</target>
<source>Paste</source>
-<target>Вклеїти</target>
+<target>Вставити</target>
<source>Local Synchronization Settings</source>
<target>Налаштування Локальної Синхронізації</target>
<source>&New</source>
-<target>&Нова</target>
+<target>&Створити</target>
<source>&Save</source>
<target>&Зберегти</target>
@@ -707,22 +710,22 @@ The command is triggered if:
<target>Зберегти як &пакетне завдання</target>
<source>Start &comparison</source>
-<target></target>
+<target>Запуск по&рівняння</target>
<source>C&omparison settings</source>
-<target></target>
+<target>Налаштування п&орівняння</target>
<source>&Filter settings</source>
-<target></target>
+<target>Налаштування &фільтру</target>
<source>S&ynchronization settings</source>
-<target></target>
+<target>Н&алаштування синхронізації</target>
<source>Start &synchronization</source>
-<target></target>
+<target>Запуск &синхронізації</target>
<source>&Actions</source>
-<target></target>
+<target>Ді&ї</target>
<source>&Options</source>
<target>&Опції</target>
@@ -731,10 +734,10 @@ The command is triggered if:
<target>&Мова</target>
<source>&Find...</source>
-<target>&Знайти...</target>
+<target>З&найти...</target>
<source>&Reset layout</source>
-<target></target>
+<target>&Скинути розташування</target>
<source>&Export file list...</source>
<target>&Експортувати список файлів...</target>
@@ -779,7 +782,7 @@ The command is triggered if:
<target>Враховувати регістр</target>
<source>New</source>
-<target></target>
+<target>Нова</target>
<source>Open...</source>
<target>Відкрити...</target>
@@ -791,7 +794,7 @@ The command is triggered if:
<target>Зберегти як...</target>
<source>View type:</source>
-<target>Тип перегляду</target>
+<target>Тип перегляду:</target>
<source>Select view:</source>
<target>Вибрати перегляд:</target>
@@ -803,13 +806,13 @@ The command is triggered if:
<target>Кількість файлів і папок, які будуть вилучені</target>
<source>Number of files that will be updated</source>
-<target></target>
+<target>Кількість файлів і папок, які будуть оновлені</target>
<source>Number of files and folders that will be created</source>
<target>Кількість файлів і папок, які будуть створені</target>
<source>Total bytes to copy</source>
-<target>Всього зкопіювати байтів</target>
+<target>Всього скопіювати байтів</target>
<source>Use local settings:</source>
<target>Використати локальні налаштування:</target>
@@ -821,19 +824,19 @@ The command is triggered if:
<target>Визначити однакові файли порівнюючи час модифікації та розмір.</target>
<source>Identify equal files by comparing the file content.</source>
-<target>Визначити однакові файли порівнюючи їх вміст.</target>
+<target>Визначати однакові файли порівнюючи їх вміст.</target>
<source>Ignore time shift (in hours)</source>
-<target></target>
+<target>Ігнорувати зсув у часі в годинах)</target>
<source>Consider file times with specified offset as equal</source>
-<target></target>
+<target>Враховувати час файлів зі вказаним зсувом рівним</target>
<source>Handle daylight saving time</source>
-<target></target>
+<target>Перехід на літній час вручну</target>
<source>Include symbolic links:</source>
-<target></target>
+<target>Включити символьні посилання:</target>
<source>Direct</source>
<target>Прямо</target>
@@ -851,16 +854,16 @@ The command is triggered if:
<target>Включити</target>
<source>Exclude:</source>
-<target>Виключити</target>
+<target>Виключити:</target>
<source>Show examples</source>
<target>Показати приклади</target>
<source>Time span:</source>
-<target>Часовий інтервал</target>
+<target>Відрізок часу:</target>
<source>File size:</source>
-<target>Розмір Файла</target>
+<target>Розмір файлу:</target>
<source>Minimum:</source>
<target>Мінімум:</target>
@@ -872,7 +875,7 @@ The command is triggered if:
<target>Виберіть правила фільтрації для виключення деяких файлів із синхронізації. Введіть шляхи файлів відносно відповідної пари папок.</target>
<source>C&lear</source>
-<target></target>
+<target>О&чистити</target>
<source>Detect moved files</source>
<target>Виявляти переміщені файли</target>
@@ -882,28 +885,32 @@ The command is triggered if:
- Requires and creates database files
- Not supported by all file systems
</source>
-<target></target>
+<target>
+- Виявлення активно після початкової синхронізації
+- Потребує і створює файли бази даних
+- Підтримується не всіма файловими системами
+</target>
<source>Detect synchronization directions with the help of database files</source>
<target>Визначити напрямок синхронізації за допомогою файлів баз даних</target>
<source>Delete files:</source>
-<target>Вилучити файли:</target>
+<target>Вилучати файли:</target>
<source>&Permanent</source>
-<target></target>
+<target>&Безповоротно</target>
<source>Delete or overwrite files permanently</source>
-<target>Вилучати чи перезаписати файли назавжди</target>
+<target>Вилучати чи перезаписувати файли безповоротно</target>
<source>&Recycle bin</source>
<target>&Корзина</target>
<source>Back up deleted and overwritten files in the recycle bin</source>
-<target>Резервно зберегти вилучені та перезаисані файли в Корзині</target>
+<target>Робити резервну копію вилучених та перезаписаних файлів в Корзині</target>
<source>&Versioning</source>
-<target></target>
+<target>&Управління версіями</target>
<source>Move files to a user-defined folder</source>
<target>Перемістити файли у визначену користувачем папку</target>
@@ -918,7 +925,7 @@ The command is triggered if:
<target>Приховати всі помилки і повідомлення з попередженнями</target>
<source>&Pop-up</source>
-<target></target>
+<target>&Спливаючі вікна</target>
<source>Show pop-up on errors or warnings</source>
<target>Показувати виринаючі вікна при помилках та попередженнях</target>
@@ -954,10 +961,10 @@ The command is triggered if:
<target>Згорнути в область повідомлень</target>
<source>Bytes copied:</source>
-<target></target>
+<target>Байт скопійовано:</target>
<source>Close</source>
-<target>Замкнути</target>
+<target>Закрити</target>
<source>&Pause</source>
<target>&Пауза</target>
@@ -969,13 +976,13 @@ The command is triggered if:
<target>Створити пакетний файл для автоматичної синхронізації. Щоб розпочати двічі клацніть цей файл або заплануйте в планувальнику завдань: %x</target>
<source>&Stop</source>
-<target></target>
+<target>&Зупинити</target>
<source>Stop synchronization at first error</source>
<target>Зупинити синхронізацію при першій помилці</target>
<source>Run minimized</source>
-<target>Запустити мінімізовано</target>
+<target>Запустити згорнутим</target>
<source>Save log:</source>
<target>Зберегти журнал:</target>
@@ -993,15 +1000,15 @@ The command is triggered if:
<target>Наступні налаштування використовуються для всіх завдань синхронізації.</target>
<source>Fail-safe file copy</source>
-<target>Безпечне копіювання файлів</target>
+<target>Відмовостійке копіювання файлів</target>
<source>
Copy to a temporary file (*.ffs_tmp) before overwriting target.
This guarantees a consistent state even in case of a serious error.
</source>
<target>
-Скопіювати в тимчасовий файл (*.ffs_tmp) перед перезаписом цілі.
-Це гарантує узгоджений стан навіть у випадку серйозної помилки.
+Скопіювати в тимчасовий файл (*.ffs_tmp) перед перезаписом цільового.
+Це гарантує цілісність навіть у випадку серйозної помилки.
</target>
<source>(recommended)</source>
@@ -1017,10 +1024,10 @@ This guarantees a consistent state even in case of a serious error.
<target>(потрібні права адміністратора)</target>
<source>Copy file access permissions</source>
-<target>Копіювати права доступу до файлу</target>
+<target>Копіювати права доступу до файлів</target>
<source>Transfer file and folder permissions.</source>
-<target>Перенести права файлів і папок.</target>
+<target>Перенести права доступу файлів і папок.</target>
<source>Automatic retry on error:</source>
<target>Автоматичний повтор при помилці:</target>
@@ -1038,10 +1045,10 @@ This guarantees a consistent state even in case of a serious error.
<target>Опис</target>
<source>Show hidden dialogs again</source>
-<target></target>
+<target>Показати сховані діалоги знову</target>
<source>Show all permanently hidden dialogs and warning messages again</source>
-<target></target>
+<target>Показати всі сховані діалоги і повідомлення з попередженнями знову</target>
<source>&Default</source>
<target>&За замовчуванням</target>
@@ -1059,10 +1066,10 @@ This guarantees a consistent state even in case of a serious error.
<target>Відгуки та пропозиції вітаються</target>
<source>Homepage</source>
-<target>Оф.сайт</target>
+<target>Домашня сторінка</target>
<source>Email</source>
-<target>Почта</target>
+<target>Пошта</target>
<source>Published under the GNU General Public License</source>
<target>Видано за ліцензією GNU General Public License</target>
@@ -1083,7 +1090,7 @@ This guarantees a consistent state even in case of a serious error.
<target>Виберіть Інтервал Часу</target>
<source>&Preferences...</source>
-<target>&Уподобання</target>
+<target>&Уподобання...</target>
<source>Folder Pairs</source>
<target>Пари Папок</target>
@@ -1095,7 +1102,7 @@ This guarantees a consistent state even in case of a serious error.
<target>Налаштування перегляду</target>
<source>Configuration</source>
-<target>Налаштування</target>
+<target>Конфігурація</target>
<source>Overview</source>
<target>Огляд</target>
@@ -1124,9 +1131,9 @@ This guarantees a consistent state even in case of a serious error.
<pluralform>%x directories</pluralform>
</source>
<target>
-<pluralform>%x каталог</pluralform>
-<pluralform>%x каталоги</pluralform>
-<pluralform>%x каталогів</pluralform>
+<pluralform>%x папка</pluralform>
+<pluralform>%x папки</pluralform>
+<pluralform>%x папок</pluralform>
</target>
<source>
@@ -1156,13 +1163,13 @@ This guarantees a consistent state even in case of a serious error.
<target>груповий вибір</target>
<source>Include via filter:</source>
-<target>Включити згідно фільтру:</target>
+<target>Включити за допомогою фільтру:</target>
<source>Exclude via filter:</source>
-<target>Виключити через фільтр:</target>
+<target>Виключити за допомогою фільтру:</target>
<source>Include temporarily</source>
-<target>Включити</target>
+<target>Включити тимчасово</target>
<source>Exclude temporarily</source>
<target>Виключити тимчасово</target>
@@ -1171,10 +1178,10 @@ This guarantees a consistent state even in case of a serious error.
<target>Видалити</target>
<source>Include all</source>
-<target>Включити все</target>
+<target>Включити всі</target>
<source>Exclude all</source>
-<target>Виключити все</target>
+<target>Виключити всі</target>
<source>Show icons:</source>
<target>Показати іконки:</target>
@@ -1183,10 +1190,10 @@ This guarantees a consistent state even in case of a serious error.
<target>Малий</target>
<source>Medium</source>
-<target>середній</target>
+<target>Середній</target>
<source>Large</source>
-<target>великий</target>
+<target>Великий</target>
<source>Select time span...</source>
<target>Виберіть інтервал часу...</target>
@@ -1222,7 +1229,7 @@ This guarantees a consistent state even in case of a serious error.
<target>Налаштування Синхронізації</target>
<source>Clear filter</source>
-<target></target>
+<target>Очистити фільтр</target>
<source>Show files that exist on left side only</source>
<target>Показати файли, які є тільки ліворуч</target>
@@ -1240,7 +1247,7 @@ This guarantees a consistent state even in case of a serious error.
<target>Показати однакові файли</target>
<source>Show files that are different</source>
-<target>Показати різні файли</target>
+<target>Показати файли що відрізняються</target>
<source>Show conflicts</source>
<target>Показати конфлікти</target>
@@ -1258,13 +1265,13 @@ This guarantees a consistent state even in case of a serious error.
<target>Показати файли, які будуть вилучені праворуч</target>
<source>Show files that will be updated on the left side</source>
-<target></target>
+<target>Показати файли, які будуть оновлені ліворуч</target>
<source>Show files that will be updated on the right side</source>
-<target></target>
+<target>Показати файли, які будуть оновлені праворуч</target>
<source>Show files that won't be copied</source>
-<target>Показати файли, які не будуть зкопійовані</target>
+<target>Показати файли, які не будуть скопійовані</target>
<source>Show filtered or temporarily excluded files</source>
<target>Показати відфільтровані чи тимчасово виключені елементи</target>
@@ -1279,7 +1286,7 @@ This guarantees a consistent state even in case of a serious error.
<target>Всі файли синхронні</target>
<source>Cannot find %x</source>
-<target>Не можна знайти %x</target>
+<target>Неможливо знайти %x</target>
<source>Comma-separated values</source>
<target>Значення розділені комою</target>
@@ -1297,14 +1304,11 @@ This guarantees a consistent state even in case of a serious error.
<target>Сплячий режим</target>
<source>Log off</source>
-<target>Вилогувати</target>
+<target>Вийти із системи</target>
<source>Shut down</source>
<target>Вимкнути комп'ютер</target>
-<source>Hibernate</source>
-<target>Гібернація</target>
-
<source>Scanning...</source>
<target>Сканування...</target>
@@ -1368,31 +1372,31 @@ This guarantees a consistent state even in case of a serious error.
<target>Копіювати права доступу NTFS</target>
<source>Integrate external applications into context menu. The following macros are available:</source>
-<target>Інтеграція зовнішніх додатків в контекстному меню. Доступні макроси:</target>
+<target>Інтеграція зовнішніх додатків до контекстного меню. Наступні макроси доступні:</target>
<source>- full file or folder name</source>
-<target>- повна назва файлу або папки</target>
+<target>- повна назва файлу чи папки</target>
<source>- folder part only</source>
-<target>- тільки папка</target>
+<target>- тільки частина з папкою</target>
<source>- Other side's counterpart to %item_path%</source>
-<target>- Елемент з протилежної сторони до %item_path%</target>
+<target>- Двійник з протилежної сторони до %item_path%</target>
<source>- Other side's counterpart to %item_folder%</source>
-<target>- Елемент з протилежної сторони до %item_folder%</target>
+<target>- Двійник з протилежної сторони до %item_folder%</target>
<source>Show hidden dialogs and warning messages again?</source>
-<target></target>
+<target>Показати сховані діалоги і попереджувальні повідомлення знову?</target>
<source>&Show</source>
-<target></target>
+<target>&Показати</target>
<source>Identify and propagate changes on both sides. Deletions, moves and conflicts are detected automatically using a database.</source>
<target>Виявити та поширити зміни на обидві сторони. Видалення, перейменування та конфлікти визначаються автоматично використовуючи базу даних.</target>
<source>Create a mirror backup of the left folder by adapting the right folder to match.</source>
-<target>Створення дзеркальної резервної копії лівої папки шляхом адаптації правої папки.</target>
+<target>Створення дзеркальної резервної копії лівої папки шляхом приведення правої папки у повну відповідність.</target>
<source>Copy new and updated files to the right folder.</source>
<target>Скопіювати нові та оновлені файли в праву папку.</target>
@@ -1431,10 +1435,10 @@ This guarantees a consistent state even in case of a serious error.
<target>Перемістити файли замінюючи існуючі</target>
<source>Time stamp</source>
-<target>Часова мітка</target>
+<target>Відмітка часу</target>
<source>Append a time stamp to each file name</source>
-<target>Приєднати часову мітку до кожної назви файлу</target>
+<target>Приєднати відмітку часу до кожної назви файлу</target>
<source>Comparison</source>
<target>Порівняння</target>
@@ -1461,7 +1465,7 @@ This guarantees a consistent state even in case of a serious error.
<target>Проценти</target>
<source>Cannot monitor directory %x.</source>
-<target>Не вдається контролювати каталог %x.</target>
+<target>Не вдається спостереження за папкою %x.</target>
<source>Cannot delete file %x.</source>
<target>Не вдається видалити файл %x.</target>
@@ -1473,37 +1477,37 @@ This guarantees a consistent state even in case of a serious error.
<target>Не вдається перемістити файл %x до %y.</target>
<source>Cannot delete directory %x.</source>
-<target>Не вдається видалити каталог %x.</target>
+<target>Не вдається видалити папку %x.</target>
<source>Cannot write file attributes of %x.</source>
-<target>Не вдається записати атрибути файла %x.</target>
+<target>Не вдається записати атрибути файлу %x.</target>
<source>Cannot write modification time of %x.</source>
-<target>Не вдається записати часу модифікації %x.</target>
+<target>Не вдається записати час модифікації %x.</target>
<source>Cannot read security context of %x.</source>
-<target>Не вдається прочитати контексту безпеки %x.</target>
+<target>Не вдається прочитати контекст безпеки %x.</target>
<source>Cannot write security context of %x.</source>
-<target>Не вдається записати контексту безпеки %x.</target>
+<target>Не вдається записати контекст безпеки %x.</target>
<source>Cannot read permissions of %x.</source>
-<target>Не вдається прочитати дозволів %x.</target>
+<target>Не вдається прочитати права доступу до %x.</target>
<source>Cannot write permissions of %x.</source>
-<target>Не вдається записати дозволів %x.</target>
+<target>Не вдається записати права доступу до %x.</target>
<source>Cannot create directory %x.</source>
-<target>Не вдається створити каталогу %x.</target>
+<target>Не вдається створити папку %x.</target>
<source>Cannot copy symbolic link %x to %y.</source>
-<target></target>
+<target>Не вдається скопіювати символьне посилання %x до %y.</target>
<source>Cannot find system function %x.</source>
-<target>Не вдається знайти системної функції %x.</target>
+<target>Не вдається знайти системну функцію %x.</target>
<source>Cannot copy file %x to %y.</source>
-<target>Не вдається зкопіювати файл %x до %y.</target>
+<target>Не вдається скопіювати файл %x до %y.</target>
<source>Type of item %x is not supported:</source>
<target>Тип елемента %x не підтримується:</target>
@@ -1512,10 +1516,10 @@ This guarantees a consistent state even in case of a serious error.
<target>Не вдається вирішити символьне посилання %x.</target>
<source>Cannot open directory %x.</source>
-<target>Не вдається відкрити каталогу %x.</target>
+<target>Не вдається відкрити папку %x.</target>
<source>Cannot enumerate directory %x.</source>
-<target>Не вдається вчитати каталог %x.</target>
+<target>Не вдається прочитати вміст папки %x.</target>
<source>%x TB</source>
<target>%x ТБ</target>
@@ -1560,7 +1564,7 @@ This guarantees a consistent state even in case of a serious error.
<target>Не вдається встановити привілеї %x.</target>
<source>Unable to suspend system sleep mode.</source>
-<target>Не вдається призупинbnb режим сну системи.</target>
+<target>Не вдається призупинити режим сну системи.</target>
<source>Cannot change process I/O priorities.</source>
<target>Не вдалося змінити пріоритетів Вх/Вих процесу.</target>
@@ -1584,13 +1588,13 @@ This guarantees a consistent state even in case of a serious error.
<target>Файл конфігурації %x завантажено лише частково.</target>
<source>Prepare installation</source>
-<target>Підготовка інсталяції</target>
+<target>Підготовка встановлення</target>
<source>Choose which components you want to install.</source>
<target>Виберіть які компоненти ви хочете встановити.</target>
<source>Select installation type:</source>
-<target></target>
+<target>Виберіть тип встановлення:</target>
<source>Local</source>
<target>Локальна</target>
@@ -1608,19 +1612,19 @@ This guarantees a consistent state even in case of a serious error.
<target>Зареєструвати розширення файлів FreeFileSync</target>
<source>Create Explorer context menu entries</source>
-<target></target>
+<target>Створити пункти контекстного меню Провідника</target>
<source>Save settings in installation directory</source>
-<target>Зберегти налаштування у каталозі встановлення</target>
+<target>Зберегти налаштування у папці встановлення</target>
<source>Do not write to Registry</source>
<target>Не записувати у Реєстр</target>
<source>Just copy the files</source>
-<target>Просто зкопіювати файли</target>
+<target>Просто скопіювати файли</target>
<source>Choose a directory for installation:</source>
-<target>Виберіть каталог для встановлення:</target>
+<target>Виберіть папку для встановлення:</target>
<source>Create shortcuts:</source>
<target>Створити ярлики:</target>
@@ -1632,23 +1636,23 @@ This guarantees a consistent state even in case of a serious error.
<target>Меню Пуск</target>
<source>Registering FreeFileSync file extensions</source>
-<target>Реєструю розширення файлів FreeFileSync</target>
+<target>Реєстрація розширень файлів FreeFileSync</target>
<source>Unregistering FreeFileSync file extensions</source>
-<target>Вилучаю реєстрацію розширення файлів FreeFileSync</target>
+<target>Вилучення реєстрації розширень файлів FreeFileSync</target>
<source>FreeFileSync Configuration</source>
-<target></target>
+<target>Конфігурація FreeFileSync</target>
<source>FreeFileSync Batch File</source>
-<target></target>
+<target>Файл пакетного завдання FreeFileSync</target>
<source>FreeFileSync Synchronization Database</source>
-<target></target>
+<target>База даних синхронізації FreeFileSync</target>
<source>RealtimeSync Configuration</source>
-<target></target>
+<target>Конфігурація RealtimeSync</target>
<source>Edit with FreeFileSync</source>
-<target></target>
+<target>Редагувати за допомогою FreeFileSync</target>
diff --git a/FreeFileSync/Source/RealtimeSync/Makefile b/FreeFileSync/Source/RealtimeSync/Makefile
index 4cdcb607..560edc9f 100644
--- a/FreeFileSync/Source/RealtimeSync/Makefile
+++ b/FreeFileSync/Source/RealtimeSync/Makefile
@@ -25,6 +25,7 @@ CPP_LIST+=../lib/localization.cpp
CPP_LIST+=../lib/process_xml.cpp
CPP_LIST+=../lib/resolve_path.cpp
CPP_LIST+=../lib/ffs_paths.cpp
+CPP_LIST+=../lib/hard_filter.cpp
CPP_LIST+=../../../zen/xml_io.cpp
CPP_LIST+=../../../zen/dir_watcher.cpp
CPP_LIST+=../../../zen/file_access.cpp
diff --git a/FreeFileSync/Source/RealtimeSync/application.cpp b/FreeFileSync/Source/RealtimeSync/application.cpp
index c8461724..28f09c5d 100644
--- a/FreeFileSync/Source/RealtimeSync/application.cpp
+++ b/FreeFileSync/Source/RealtimeSync/application.cpp
@@ -22,7 +22,7 @@
#ifdef ZEN_WIN
#include <zen/win_ver.h>
-#include <zen/dll.h>
+ #include <zen/dll.h>
#include "../lib/app_user_mode_id.h"
#elif defined ZEN_LINUX
@@ -136,7 +136,7 @@ void Application::onEnterEventLoop(wxEvent& event)
filepath += Zstr(".ffs_batch");
else
{
- showNotificationDialog(nullptr, DialogInfoType::ERROR2, PopupDialogCfg().setMainInstructions(replaceCpy(_("Cannot open file %x."), L"%x", fmtFileName(filepath))));
+ showNotificationDialog(nullptr, DialogInfoType::ERROR2, PopupDialogCfg().setMainInstructions(replaceCpy(_("Cannot find file %x."), L"%x", fmtFileName(filepath))));
return;
}
}
diff --git a/FreeFileSync/Source/algorithm.cpp b/FreeFileSync/Source/algorithm.cpp
index dae18933..b95f3101 100644
--- a/FreeFileSync/Source/algorithm.cpp
+++ b/FreeFileSync/Source/algorithm.cpp
@@ -75,7 +75,7 @@ private:
case FILE_LEFT_NEWER:
fileObj.setSyncDir(dirCfg.leftNewer);
break;
- case FILE_DIFFERENT:
+ case FILE_DIFFERENT_CONTENT:
fileObj.setSyncDir(dirCfg.different);
break;
case FILE_CONFLICT:
@@ -114,7 +114,7 @@ private:
else
linkObj.setSyncDir(dirCfg.conflict);
break;
- case SYMLINK_DIFFERENT:
+ case SYMLINK_DIFFERENT_CONTENT:
linkObj.setSyncDir(dirCfg.different);
break;
case SYMLINK_EQUAL:
@@ -145,6 +145,7 @@ private:
case DIR_EQUAL:
dirObj.setSyncDir(SyncDirection::NONE);
break;
+ case DIR_CONFLICT:
case DIR_DIFFERENT_METADATA: //use setting from "conflict/cannot categorize"
if (dirCfg.conflict == SyncDirection::NONE)
dirObj.setSyncDirConflict(dirObj.getCatExtraDescription()); //take over category conflict
@@ -801,8 +802,8 @@ namespace
enum FilterStrategy
{
STRATEGY_SET,
- STRATEGY_AND,
- STRATEGY_OR
+ STRATEGY_AND
+ //STRATEGY_OR -> usage of inOrExcludeAllRows doesn't allow for strategy "or"
};
template <FilterStrategy strategy> struct Eval;
@@ -811,21 +812,14 @@ template <>
struct Eval<STRATEGY_SET> //process all elements
{
template <class T>
- bool process(const T& obj) const { return true; }
+ static bool process(const T& obj) { return true; }
};
template <>
struct Eval<STRATEGY_AND>
{
template <class T>
- bool process(const T& obj) const { return obj.isActive(); }
-};
-
-template <>
-struct Eval<STRATEGY_OR>
-{
- template <class T>
- bool process(const T& obj) const { return !obj.isActive(); }
+ static bool process(const T& obj) { return obj.isActive(); }
};
@@ -850,13 +844,13 @@ private:
void processFile(FilePair& fileObj) const
{
- if (Eval<strategy>().process(fileObj))
+ if (Eval<strategy>::process(fileObj))
fileObj.setActive(filterProc.passFileFilter(fileObj.getPairRelativePath()));
}
void processLink(SymlinkPair& linkObj) const
{
- if (Eval<strategy>().process(linkObj))
+ if (Eval<strategy>::process(linkObj))
linkObj.setActive(filterProc.passFileFilter(linkObj.getPairRelativePath()));
}
@@ -865,12 +859,12 @@ private:
bool subObjMightMatch = true;
const bool filterPassed = filterProc.passDirFilter(dirObj.getPairRelativePath(), &subObjMightMatch);
- if (Eval<strategy>().process(dirObj))
+ if (Eval<strategy>::process(dirObj))
dirObj.setActive(filterPassed);
if (!subObjMightMatch) //use same logic like directory traversing here: evaluate filter in subdirs only if objects could match
{
- inOrExcludeAllRows<false>(dirObj); //exclude all files dirs in subfolders
+ inOrExcludeAllRows<false>(dirObj); //exclude all files dirs in subfolders => incompatible with STRATEGY_OR!
return;
}
@@ -880,9 +874,6 @@ private:
const HardFilter& filterProc;
};
-template <>
-class ApplyHardFilter<STRATEGY_OR>; //usage of InOrExcludeAllRows doesn't allow for strategy "or"
-
template <FilterStrategy strategy>
class ApplySoftFilter //falsify only! -> can run directly after "hard/base filter"
@@ -905,7 +896,7 @@ private:
void processFile(FilePair& fileObj) const
{
- if (Eval<strategy>().process(fileObj))
+ if (Eval<strategy>::process(fileObj))
{
if (fileObj.isEmpty<LEFT_SIDE>())
fileObj.setActive(matchSize<RIGHT_SIDE>(fileObj) &&
@@ -920,16 +911,16 @@ private:
/*
ST S T - ST := match size and time
--------- S := match size only
- ST |X|X|X|X| T := match time only
+ ST |I|I|I|I| T := match time only
------------ - := no match
- S |X|O|?|O|
- ------------ X := include row
- T |X|?|O|O| O := exclude row
+ S |I|E|?|E|
+ ------------ I := include row
+ T |I|?|E|E| E := exclude row
------------ ? := unclear
- - |X|O|O|O|
+ - |I|E|E|E|
------------
*/
- //let's set ? := O
+ //let's set ? := E
fileObj.setActive((matchSize<RIGHT_SIDE>(fileObj) &&
matchTime<RIGHT_SIDE>(fileObj)) ||
(matchSize<LEFT_SIDE>(fileObj) &&
@@ -940,7 +931,7 @@ private:
void processLink(SymlinkPair& linkObj) const
{
- if (Eval<strategy>().process(linkObj))
+ if (Eval<strategy>::process(linkObj))
{
if (linkObj.isEmpty<LEFT_SIDE>())
linkObj.setActive(matchTime<RIGHT_SIDE>(linkObj));
@@ -954,7 +945,7 @@ private:
void processDir(DirPair& dirObj) const
{
- if (Eval<strategy>().process(dirObj))
+ if (Eval<strategy>::process(dirObj))
dirObj.setActive(timeSizeFilter_.matchFolder()); //if date filter is active we deactivate all folders: effectively gets rid of empty folders!
recurse(dirObj);
@@ -1049,7 +1040,7 @@ private:
fileObj.setActive(matchTime<LEFT_SIDE>(fileObj));
else
fileObj.setActive(matchTime<RIGHT_SIDE>(fileObj) ||
- matchTime<LEFT_SIDE>(fileObj));
+ matchTime<LEFT_SIDE >(fileObj));
}
void processLink(SymlinkPair& linkObj) const
@@ -1116,7 +1107,7 @@ std::pair<Zstring, int> zen::deleteFromGridAndHDPreview(const std::vector<FileSy
namespace
{
template <typename Function> inline
-bool tryReportingError(Function cmd, DeleteFilesHandler& handler) //return "true" on success, "false" if error was ignored
+bool tryReportingError(Function cmd, DeleteFilesHandler& handler) //throw X?; return "true" on success, "false" if error was ignored
{
for (;;)
try
@@ -1126,7 +1117,7 @@ bool tryReportingError(Function cmd, DeleteFilesHandler& handler) //return "true
}
catch (FileError& error)
{
- switch (handler.reportError(error.toString())) //may throw!
+ switch (handler.reportError(error.toString())) //throw X?
{
case DeleteFilesHandler::IGNORE_ERROR:
return false;
@@ -1160,7 +1151,7 @@ void categorize(const std::set<FileSystemObject*>& rowsIn,
bool recExists = false;
tryReportingError([&]{
recExists = recycleBinExists(baseDirPf, [&] { callback.reportStatus(msg); /*may throw*/ }); //throw FileError
- }, callback);
+ }, callback); //throw X?
hasRecyclerBuffer.emplace(baseDirPf, recExists);
return recExists;
@@ -1273,7 +1264,7 @@ void deleteFromGridAndHDOneSide(std::vector<FileSystemObject*>& ptrList,
{
fsObj->accept(deleter); //throw FileError
fsObj->removeObject<side>(); //if directory: removes recursively!
- }, handler);
+ }, handler); //throw X?
}
}
diff --git a/FreeFileSync/Source/algorithm.h b/FreeFileSync/Source/algorithm.h
index 398d0def..5adaafbb 100644
--- a/FreeFileSync/Source/algorithm.h
+++ b/FreeFileSync/Source/algorithm.h
@@ -17,8 +17,8 @@ void swapGrids(const MainConfiguration& config, FolderComparison& folderCmp);
std::vector<DirectionConfig> extractDirectionCfg(const MainConfiguration& mainCfg);
-void redetermineSyncDirection(const DirectionConfig& directConfig, BaseDirPair& baseDirectory, std::function<void(const std::wstring& msg)> reportWarning);
-void redetermineSyncDirection(const MainConfiguration& mainCfg, FolderComparison& folderCmp, std::function<void(const std::wstring& msg)> reportWarning);
+void redetermineSyncDirection(const DirectionConfig& directConfig, BaseDirPair& baseDirectory, std::function<void(const std::wstring& msg)> reportWarning);
+void redetermineSyncDirection(const MainConfiguration& mainCfg, FolderComparison& folderCmp, std::function<void(const std::wstring& msg)> reportWarning);
void setSyncDirectionRec(SyncDirection newDirection, FileSystemObject& fsObj); //set new direction (recursively)
diff --git a/FreeFileSync/Source/application.cpp b/FreeFileSync/Source/application.cpp
index 12737071..ab7051e3 100644
--- a/FreeFileSync/Source/application.cpp
+++ b/FreeFileSync/Source/application.cpp
@@ -232,7 +232,7 @@ void Application::onEnterEventLoop(wxEvent& event)
int Application::OnRun()
-{
+{
auto processException = [](const std::wstring& msg)
{
//it's not always possible to display a message box, e.g. corrupted stack, however low-level file output works!
@@ -277,8 +277,8 @@ void showSyntaxHelp();
void Application::launch(const std::vector<Zstring>& commandArgs)
{
- //wxWidgets app exit handling is weird... we want the app to exit only if the logical main window is closed
- wxTheApp->SetExitOnFrameDelete(false); //avoid popup-windows from becoming temporary top windows leading to program exit after closure
+ //wxWidgets app exit handling is weird... we want to exit only if the logical main window is closed, not just *any* window!
+ wxTheApp->SetExitOnFrameDelete(false); //prevent popup-windows from becoming temporary top windows leading to program exit after closure
ZEN_ON_SCOPE_EXIT(if (!mainWindowWasSet()) wxTheApp->ExitMainLoop();); //quit application, if no main window was set (batch silent mode)
try
@@ -354,7 +354,7 @@ void Application::launch(const std::vector<Zstring>& commandArgs)
filepath += Zstr(".xml");
else
{
- notifyError(replaceCpy(_("Cannot open file %x."), L"%x", fmtFileName(filepath)), std::wstring());
+ notifyError(replaceCpy(_("Cannot find file %x."), L"%x", fmtFileName(filepath)), std::wstring());
return;
}
}
diff --git a/FreeFileSync/Source/comparison.cpp b/FreeFileSync/Source/comparison.cpp
index 30f97b05..8464ea28 100644
--- a/FreeFileSync/Source/comparison.cpp
+++ b/FreeFileSync/Source/comparison.cpp
@@ -12,6 +12,7 @@
#include <zen/process_priority.h>
#include <zen/symlink_target.h>
#include <zen/format_unit.h>
+#include <zen/stl_tools.h>
#include "algorithm.h"
#include "lib/parallel_scan.h"
#include "lib/resolve_path.h"
@@ -112,7 +113,7 @@ ResolutionInfo resolveFolderPairs(const std::vector<FolderPairCfg>& cfgList,
msg += std::wstring(L"\n") + dirpath;
throw FileError(msg, _("You can ignore this error to consider each folder as empty. The folders then will be created automatically during synchronization."));
}
- }, callback);
+ }, callback); //throw X?
return output;
}
@@ -387,7 +388,7 @@ void categorizeSymlinkByContent(SymlinkPair& linkObj, int fileTimeTolerance, uns
callback.reportStatus(replaceCpy(_("Resolving symbolic link %x"), L"%x", fmtFileName(linkObj.getFullPath<RIGHT_SIDE>())));
targetPathRawR = getSymlinkTargetRaw(linkObj.getFullPath<RIGHT_SIDE>()); //throw FileError
- }, callback);
+ }, callback); //throw X?
if (errMsg)
linkObj.setCategoryConflict(*errMsg);
@@ -415,7 +416,7 @@ void categorizeSymlinkByContent(SymlinkPair& linkObj, int fileTimeTolerance, uns
linkObj.setCategory<FILE_EQUAL>();
}
else
- linkObj.setCategory<FILE_DIFFERENT>();
+ linkObj.setCategory<FILE_DIFFERENT_CONTENT>();
}
}
@@ -443,7 +444,7 @@ std::list<std::shared_ptr<BaseDirPair>> ComparisonBuffer::compareByContent(const
for (FilePair* fileObj : undefinedFiles)
//pre-check: files have different content if they have a different filesize (must not be FILE_EQUAL: see InSyncFile)
if (fileObj->getFileSize<LEFT_SIDE>() != fileObj->getFileSize<RIGHT_SIDE>())
- fileObj->setCategory<FILE_DIFFERENT>();
+ fileObj->setCategory<FILE_DIFFERENT_CONTENT>();
else
{
//perf: skip binary comparison for excluded rows (e.g. via time span and size filter)!
@@ -491,7 +492,7 @@ std::list<std::shared_ptr<BaseDirPair>> ComparisonBuffer::compareByContent(const
statReporter.reportDelta(1, 0);
statReporter.reportFinished();
- }, callback_);
+ }, callback_); //throw X?
if (errMsg)
fileObj->setCategoryConflict(*errMsg);
@@ -512,45 +513,86 @@ std::list<std::shared_ptr<BaseDirPair>> ComparisonBuffer::compareByContent(const
fileObj->setCategory<FILE_EQUAL>();
}
else
- fileObj->setCategory<FILE_DIFFERENT>();
+ fileObj->setCategory<FILE_DIFFERENT_CONTENT>();
}
}
return output;
}
+//-----------------------------------------------------------------------------------------------
class MergeSides
{
public:
- MergeSides(std::vector<FilePair*>& appendUndefinedFileOut,
- std::vector<SymlinkPair*>& appendUndefinedLinkOut) :
- appendUndefinedFile(appendUndefinedFileOut),
- appendUndefinedLink(appendUndefinedLinkOut) {}
+ MergeSides(const std::map<Zstring, std::wstring, LessFilename>& failedItemReads,
+ std::vector<FilePair*>& undefinedFilesOut,
+ std::vector<SymlinkPair*>& undefinedLinksOut) :
+ failedItemReads_(failedItemReads),
+ undefinedFiles(undefinedFilesOut),
+ undefinedLinks(undefinedLinksOut) {}
+
+ void execute(const DirContainer& leftSide, const DirContainer& rightSide, HierarchyObject& output)
+ {
+ auto it = failedItemReads_.find(Zstring()); //empty path if read-error for whole base directory
- void execute(const DirContainer& leftSide, const DirContainer& rightSide, HierarchyObject& output);
+ mergeTwoSides(leftSide, rightSide,
+ it != failedItemReads_.end() ? &it->second : nullptr,
+ output);
+ }
private:
+ void mergeTwoSides(const DirContainer& leftSide, const DirContainer& rightSide, const std::wstring* errorMsg, HierarchyObject& output);
+
template <SelectedSide side>
- void fillOneSide(const DirContainer& dirCont, HierarchyObject& output);
+ void fillOneSide(const DirContainer& dirCont, const std::wstring* errorMsg, HierarchyObject& output);
+
+ const std::wstring* checkFailedRead(FileSystemObject& fsObj, const std::wstring* errorMsg);
- std::vector<FilePair*>& appendUndefinedFile;
- std::vector<SymlinkPair*>& appendUndefinedLink;
+ const std::map<Zstring, std::wstring, LessFilename>& failedItemReads_; //base-relative paths or empty if read-error for whole base directory
+ std::vector<FilePair*>& undefinedFiles;
+ std::vector<SymlinkPair*>& undefinedLinks;
};
+inline
+const std::wstring* MergeSides::checkFailedRead(FileSystemObject& fsObj, const std::wstring* errorMsg)
+{
+ if (!errorMsg)
+ {
+ auto it = failedItemReads_.find(fsObj.getPairRelativePath());
+ if (it != failedItemReads_.end())
+ errorMsg = &it->second;
+ }
+
+ if (errorMsg)
+ {
+ fsObj.setActive(false);
+ fsObj.setCategoryConflict(*errorMsg);
+ }
+ return errorMsg;
+}
+
+
template <SelectedSide side>
-void MergeSides::fillOneSide(const DirContainer& dirCont, HierarchyObject& output)
+void MergeSides::fillOneSide(const DirContainer& dirCont, const std::wstring* errorMsg, HierarchyObject& output)
{
for (const auto& file : dirCont.files)
- output.addSubFile<side>(file.first, file.second);
+ {
+ FilePair& newItem = output.addSubFile<side>(file.first, file.second);
+ checkFailedRead(newItem, errorMsg);
+ }
for (const auto& link : dirCont.links)
- output.addSubLink<side>(link.first, link.second);
+ {
+ SymlinkPair& newItem = output.addSubLink<side>(link.first, link.second);
+ checkFailedRead(newItem, errorMsg);
+ }
for (const auto& dir : dirCont.dirs)
{
- DirPair& newDirMap = output.addSubDir<side>(dir.first);
- fillOneSide<side>(dir.second, newDirMap); //recurse
+ DirPair& newDir = output.addSubDir<side>(dir.first);
+ const std::wstring* errorMsgNew = checkFailedRead(newDir, errorMsg);
+ fillOneSide<side>(dir.second, errorMsgNew, newDir); //recurse
}
}
@@ -594,41 +636,42 @@ void linearMerge(const MapType& mapLeft, const MapType& mapRight, ProcessLeftOnl
}
-void MergeSides::execute(const DirContainer& leftSide, const DirContainer& rightSide, HierarchyObject& output)
+void MergeSides::mergeTwoSides(const DirContainer& leftSide, const DirContainer& rightSide, const std::wstring* errorMsg, HierarchyObject& output)
{
- //HierarchyObject::addSubFile() must NOT invalidate references used in "appendUndefined"!
-
typedef const DirContainer::FileList::value_type FileData;
linearMerge(leftSide.files, rightSide.files,
- [&](const FileData& fileLeft ) { output.addSubFile<LEFT_SIDE >(fileLeft .first, fileLeft .second); }, //left only
- [&](const FileData& fileRight) { output.addSubFile<RIGHT_SIDE>(fileRight.first, fileRight.second); }, //right only
+ [&](const FileData& fileLeft ) { FilePair& newItem = output.addSubFile<LEFT_SIDE >(fileLeft .first, fileLeft .second); checkFailedRead(newItem, errorMsg); }, //left only
+ [&](const FileData& fileRight) { FilePair& newItem = output.addSubFile<RIGHT_SIDE>(fileRight.first, fileRight.second); checkFailedRead(newItem, errorMsg); }, //right only
[&](const FileData& fileLeft, const FileData& fileRight) //both sides
{
- FilePair& newEntry = output.addSubFile(fileLeft.first,
- fileLeft.second,
- FILE_EQUAL, //FILE_EQUAL is just a dummy-value here
- fileRight.first,
- fileRight.second);
- appendUndefinedFile.push_back(&newEntry);
+ FilePair& newItem = output.addSubFile(fileLeft.first,
+ fileLeft.second,
+ FILE_EQUAL, //dummy-value until categorization is finished later
+ fileRight.first,
+ fileRight.second);
+ if (!checkFailedRead(newItem, errorMsg))
+ undefinedFiles.push_back(&newItem);
+ static_assert(IsSameType<HierarchyObject::SubFileVec, FixedList<FilePair>>::value, ""); //HierarchyObject::addSubFile() must NOT invalidate references used in "undefinedFiles"!
});
//-----------------------------------------------------------------------------------------------
typedef const DirContainer::LinkList::value_type LinkData;
linearMerge(leftSide.links, rightSide.links,
- [&](const LinkData& linkLeft) { output.addSubLink<LEFT_SIDE >(linkLeft.first, linkLeft.second); }, //left only
- [&](const LinkData& linkRight) { output.addSubLink<RIGHT_SIDE>(linkRight.first, linkRight.second); }, //right only
+ [&](const LinkData& linkLeft) { SymlinkPair& newItem = output.addSubLink<LEFT_SIDE >(linkLeft .first, linkLeft .second); checkFailedRead(newItem, errorMsg); }, //left only
+ [&](const LinkData& linkRight) { SymlinkPair& newItem = output.addSubLink<RIGHT_SIDE>(linkRight.first, linkRight.second); checkFailedRead(newItem, errorMsg); }, //right only
[&](const LinkData& linkLeft, const LinkData& linkRight) //both sides
{
- SymlinkPair& newEntry = output.addSubLink(linkLeft.first,
- linkLeft.second,
- SYMLINK_EQUAL, //SYMLINK_EQUAL is just a dummy-value here
- linkRight.first,
- linkRight.second);
- appendUndefinedLink.push_back(&newEntry);
+ SymlinkPair& newItem = output.addSubLink(linkLeft.first,
+ linkLeft.second,
+ SYMLINK_EQUAL, //dummy-value until categorization is finished later
+ linkRight.first,
+ linkRight.second);
+ if (!checkFailedRead(newItem, errorMsg))
+ undefinedLinks.push_back(&newItem);
});
//-----------------------------------------------------------------------------------------------
@@ -637,40 +680,49 @@ void MergeSides::execute(const DirContainer& leftSide, const DirContainer& right
linearMerge(leftSide.dirs, rightSide.dirs,
[&](const DirData& dirLeft) //left only
{
- DirPair& newDirMap = output.addSubDir<LEFT_SIDE>(dirLeft.first);
- this->fillOneSide<LEFT_SIDE>(dirLeft.second, newDirMap); //recurse into subdirectories
+ DirPair& newDir = output.addSubDir<LEFT_SIDE>(dirLeft.first);
+ const std::wstring* errorMsgNew = checkFailedRead(newDir, errorMsg);
+ this->fillOneSide<LEFT_SIDE>(dirLeft.second, errorMsgNew, newDir); //recurse
},
[&](const DirData& dirRight) //right only
{
- DirPair& newDirMap = output.addSubDir<RIGHT_SIDE>(dirRight.first);
- this->fillOneSide<RIGHT_SIDE>(dirRight.second, newDirMap); //recurse into subdirectories
+ DirPair& newDir = output.addSubDir<RIGHT_SIDE>(dirRight.first);
+ const std::wstring* errorMsgNew = checkFailedRead(newDir, errorMsg);
+ this->fillOneSide<RIGHT_SIDE>(dirRight.second, errorMsgNew, newDir); //recurse
},
[&](const DirData& dirLeft, const DirData& dirRight) //both sides
{
- DirPair& newDirMap = output.addSubDir(dirLeft.first, dirRight.first, DIR_EQUAL);
- if (dirLeft.first != dirRight.first)
- newDirMap.setCategoryDiffMetadata(getDescrDiffMetaShortnameCase(newDirMap));
+ DirPair& newDir = output.addSubDir(dirLeft.first, dirRight.first, DIR_EQUAL);
+ const std::wstring* errorMsgNew = checkFailedRead(newDir, errorMsg);
+
+ if (!errorMsgNew)
+ if (dirLeft.first != dirRight.first)
+ newDir.setCategoryDiffMetadata(getDescrDiffMetaShortnameCase(newDir));
- execute(dirLeft.second, dirRight.second, newDirMap); //recurse into subdirectories
+ mergeTwoSides(dirLeft.second, dirRight.second, errorMsgNew, newDir); //recurse
});
}
+//-----------------------------------------------------------------------------------------------
+
//mark excluded directories (see fillBuffer()) + remove superfluous excluded subdirectories
void stripExcludedDirectories(HierarchyObject& hierObj, const HardFilter& filterProc)
{
- //process subdirs recursively
for (DirPair& dirObj : hierObj.refSubDirs())
- {
- dirObj.setActive(filterProc.passDirFilter(dirObj.getPairRelativePath(), nullptr)); //subObjMightMatch is always true in this context!
stripExcludedDirectories(dirObj, filterProc);
- }
- //remove superfluous directories -> note: 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 zen::FixedList!
- hierObj.refSubDirs().remove_if([](DirPair& dirObj)
+ //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 zen::FixedList!
+ hierObj.refSubDirs().remove_if([&](DirPair& dirObj)
{
- return !dirObj.isActive() &&
+ const bool included = filterProc.passDirFilter(dirObj.getPairRelativePath(), nullptr); //subObjMightMatch is false, child items were already excluded during scanning
+
+ if (!included) //falsify only! (e.g. might already be inactive due to read error!)
+ dirObj.setActive(false);
+
+ return !included & //don't check active status, but eval filter directly!
dirObj.refSubDirs ().empty() &&
dirObj.refSubLinks().empty() &&
dirObj.refSubFiles().empty();
@@ -696,43 +748,42 @@ std::shared_ptr<BaseDirPair> ComparisonBuffer::performComparison(const ResolvedF
const DirectoryValue* bufValueLeft = getDirValue(fp.dirpathLeft);
const DirectoryValue* bufValueRight = getDirValue(fp.dirpathRight);
- Zstring filterFailedRead;
- auto filterAddFailedDirReads = [&filterFailedRead](const std::set<Zstring>& failedDirReads) //exclude directory child items only!
- {
- //note: relDirPf is empty for base dir, otherwise postfixed! e.g. "subdir\"
- //an empty relDirPf is a pathological case at this point, since determineExistentDirs() already filtered missing base directories!
- for (const Zstring& relDirPf : failedDirReads)
- filterFailedRead += relDirPf + Zstr("?*\n");
- };
- auto filterAddFailedItemReads = [&filterFailedRead](const std::set<Zstring>& failedItemReads) //exclude item AND (potential) child items!
+ std::map<Zstring, std::wstring, LessFilename> failedReads; //base-relative paths or empty if read-error for whole base directory
{
- for (const Zstring& relItem : failedItemReads)
- filterFailedRead += relItem + Zstr("\n");
- };
-
- if (bufValueLeft ) filterAddFailedDirReads(bufValueLeft ->failedDirReads);
- if (bufValueRight) filterAddFailedDirReads(bufValueRight->failedDirReads);
+ //mix failedDirReads with failedItemReads:
+ //mark directory errors already at directory-level (instead for child items only) to show on GUI! See "MergeSides"
+ //=> minor pessimization for "excludefilterFailedRead" which needlessly excludes parent folders, too
+ if (bufValueLeft ) set_append(failedReads, bufValueLeft ->failedDirReads);
+ if (bufValueRight) set_append(failedReads, bufValueRight->failedDirReads);
+
+ if (bufValueLeft ) set_append(failedReads, bufValueLeft ->failedItemReads);
+ if (bufValueRight) set_append(failedReads, bufValueRight->failedItemReads);
+ }
- if (bufValueLeft ) filterAddFailedItemReads(bufValueLeft ->failedItemReads);
- if (bufValueRight) filterAddFailedItemReads(bufValueRight->failedItemReads);
+ Zstring excludefilterFailedRead;
+ if (failedReads.find(Zstring()) != failedReads.end()) //empty path if read-error for whole base directory
+ excludefilterFailedRead += Zstr("*\n");
+ else
+ for (const auto& item : failedReads)
+ excludefilterFailedRead += item.first + Zstr("\n"); //exclude item AND (potential) child items!
std::shared_ptr<BaseDirPair> output = std::make_shared<BaseDirPair>(fp.dirpathLeft,
bufValueLeft != nullptr, //dir existence must be checked only once: available iff buffer entry exists!
fp.dirpathRight,
bufValueRight != nullptr,
- fpCfg.filter.nameFilter,
+ fpCfg.filter.nameFilter->copyFilterAddingExclusion(excludefilterFailedRead),
fpCfg.compareVar,
fpCfg.fileTimeTolerance,
fpCfg.optTimeShiftHours);
//PERF_START;
DirContainer emptyDirCont; //WTF!!! => using a temporary in the ternary conditional would implicitly call the DirContainer copy-constructor!!!!!!
- MergeSides(undefinedFiles, undefinedLinks).execute(bufValueLeft ? bufValueLeft ->dirCont : emptyDirCont,
- bufValueRight ? bufValueRight->dirCont : emptyDirCont, *output);
+ MergeSides(failedReads, undefinedFiles, undefinedLinks).execute(bufValueLeft ? bufValueLeft ->dirCont : emptyDirCont,
+ bufValueRight ? bufValueRight->dirCont : emptyDirCont, *output);
//PERF_STOP;
//##################### in/exclude rows according to filtering #####################
- //NOTE: we need to finish excluding rows in this method, BEFORE binary comparison is applied on the non-excluded rows only!
+ //NOTE: we need to finish de-activating rows BEFORE binary comparison is run so that it can skip them!
//attention: some excluded directories are still in the comparison result! (see include filter handling!)
if (!fpCfg.filter.nameFilter->isNull())
@@ -741,10 +792,6 @@ std::shared_ptr<BaseDirPair> ComparisonBuffer::performComparison(const ResolvedF
//apply soft filtering (hard filter already applied during traversal!)
addSoftFiltering(*output, fpCfg.filter.timeSizeFilter);
- //handle (user-ignored) traversing errors: just uncheck them, no need to physically delete them from both sides
- if (!filterFailedRead.empty())
- addHardFiltering(*output, filterFailedRead);
-
//##################################################################################
return output;
}
@@ -833,7 +880,9 @@ void zen::compare(xmlAccess::OptionalDialogs& warnings,
//reduce peak memory by restricting lifetime of ComparisonBuffer to have ended when loading potentially huge InSyncDir instance in redetermineSyncDirection()
{
//------------ traverse/read folders -----------------------------------------------------
+ //PERF_START;
ComparisonBuffer cmpBuff(dirsToRead, callback);
+ //PERF_STOP;
//process binary comparison as one junk
std::vector<std::pair<ResolvedFolderPair, FolderPairCfg>> workLoadByContent;
diff --git a/FreeFileSync/Source/file_hierarchy.cpp b/FreeFileSync/Source/file_hierarchy.cpp
index 16b02a21..3777d5ed 100644
--- a/FreeFileSync/Source/file_hierarchy.cpp
+++ b/FreeFileSync/Source/file_hierarchy.cpp
@@ -28,20 +28,29 @@ void HierarchyObject::removeEmptyRec()
refSubDirs ().remove_if(isEmpty);
if (emptyExisting) //notify if actual deletion happened
- notifySyncCfgChanged(); //mustn't call this in ~FileSystemObject(), since parent, usually a DirPair, is already partially destroyed and existing as a pure HierarchyObject!
+ notifySyncCfgChanged(); //mustn't call this in ~FileSystemObject(), since parent, usually a DirPair, may already be partially destroyed and existing as a pure HierarchyObject!
for (DirPair& subDir : refSubDirs())
subDir.removeEmptyRec(); //recurse
}
+
namespace
{
-SyncOperation getIsolatedSyncOperation(CompareFilesResult cmpResult,
+SyncOperation getIsolatedSyncOperation(bool itemExistsLeft,
+ bool itemExistsRight,
+ CompareFilesResult cmpResult,
bool selectedForSynchronization,
SyncDirection syncDir,
- bool hasDirConflict) //perf: std::wstring was wasteful here
+ bool hasDirectionConflict) //perf: std::wstring was wasteful here
{
- assert(!hasDirConflict || syncDir == SyncDirection::NONE);
+ 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 (!selectedForSynchronization)
return cmpResult == FILE_EQUAL ?
@@ -50,6 +59,10 @@ SyncOperation getIsolatedSyncOperation(CompareFilesResult cmpResult,
switch (cmpResult)
{
+ case FILE_EQUAL:
+ assert(syncDir == SyncDirection::NONE);
+ return SO_EQUAL;
+
case FILE_LEFT_SIDE_ONLY:
switch (syncDir)
{
@@ -58,7 +71,7 @@ SyncOperation getIsolatedSyncOperation(CompareFilesResult cmpResult,
case SyncDirection::RIGHT:
return SO_CREATE_NEW_RIGHT; //copy files to right
case SyncDirection::NONE:
- return hasDirConflict ? SO_UNRESOLVED_CONFLICT : SO_DO_NOTHING;
+ return hasDirectionConflict ? SO_UNRESOLVED_CONFLICT : SO_DO_NOTHING;
}
break;
@@ -70,14 +83,13 @@ SyncOperation getIsolatedSyncOperation(CompareFilesResult cmpResult,
case SyncDirection::RIGHT:
return SO_DELETE_RIGHT; //delete files on right
case SyncDirection::NONE:
- return hasDirConflict ? SO_UNRESOLVED_CONFLICT : SO_DO_NOTHING;
+ return hasDirectionConflict ? SO_UNRESOLVED_CONFLICT : SO_DO_NOTHING;
}
break;
case FILE_LEFT_NEWER:
case FILE_RIGHT_NEWER:
- case FILE_DIFFERENT:
- case FILE_CONFLICT:
+ case FILE_DIFFERENT_CONTENT:
switch (syncDir)
{
case SyncDirection::LEFT:
@@ -85,7 +97,7 @@ SyncOperation getIsolatedSyncOperation(CompareFilesResult cmpResult,
case SyncDirection::RIGHT:
return SO_OVERWRITE_RIGHT; //copy from left to right
case SyncDirection::NONE:
- return hasDirConflict ? SO_UNRESOLVED_CONFLICT : SO_DO_NOTHING;
+ return hasDirectionConflict ? SO_UNRESOLVED_CONFLICT : SO_DO_NOTHING;
}
break;
@@ -97,13 +109,21 @@ SyncOperation getIsolatedSyncOperation(CompareFilesResult cmpResult,
case SyncDirection::RIGHT:
return SO_COPY_METADATA_TO_RIGHT;
case SyncDirection::NONE:
- return hasDirConflict ? SO_UNRESOLVED_CONFLICT : SO_DO_NOTHING;
+ return hasDirectionConflict ? SO_UNRESOLVED_CONFLICT : SO_DO_NOTHING;
}
break;
- case FILE_EQUAL:
- assert(syncDir == SyncDirection::NONE);
- return SO_EQUAL;
+ 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;
}
assert(false);
@@ -123,14 +143,14 @@ bool hasDirectChild(const HierarchyObject& hierObj, Predicate p)
SyncOperation FileSystemObject::testSyncOperation(SyncDirection testSyncDir) const //semantics: "what if"! assumes "active, no conflict, no recursion (directory)!
{
- return getIsolatedSyncOperation(getCategory(), true, testSyncDir, false);
+ return getIsolatedSyncOperation(!isEmpty<LEFT_SIDE>(), !isEmpty<RIGHT_SIDE>(), getCategory(), true, testSyncDir, false);
}
SyncOperation FileSystemObject::getSyncOperation() const
{
- return getIsolatedSyncOperation(getCategory(), selectedForSynchronization, getSyncDir(), syncDirConflict.get() != nullptr);
- //no *not* make a virtual call to testSyncOperation()! See FilePair::testSyncOperation()! <- better not implement one in terms of the other!!!
+ return getIsolatedSyncOperation(!isEmpty<LEFT_SIDE>(), !isEmpty<RIGHT_SIDE>(), getCategory(), selectedForSynchronization, getSyncDir(), syncDirectionConflict.get() != nullptr);
+ //do *not* make a virtual call to testSyncOperation()! See FilePair::testSyncOperation()! <- better not implement one in terms of the other!!!
}
@@ -150,8 +170,6 @@ SyncOperation DirPair::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_SOURCE:
case SO_MOVE_LEFT_TARGET:
case SO_MOVE_RIGHT_SOURCE:
@@ -159,6 +177,8 @@ SyncOperation DirPair::getSyncOperation() const
assert(false);
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_EQUAL:
@@ -170,8 +190,7 @@ SyncOperation DirPair::getSyncOperation() const
if (isEmpty<LEFT_SIDE>())
{
//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";
- //see http://sourceforge.net/tracker/index.php?func=detail&aid=2628943&group_id=234430&atid=1093080
+ //note: this automatically fulfills "create parent folders even if excluded"
if (hasDirectChild(*this,
[](const FileSystemObject& fsObj) -> bool
{
@@ -275,7 +294,7 @@ std::wstring zen::getCategoryDescription(CompareFilesResult cmpRes)
return _("Left side is newer");
case FILE_RIGHT_NEWER:
return _("Right side is newer");
- case FILE_DIFFERENT:
+ case FILE_DIFFERENT_CONTENT:
return _("Items have different content");
case FILE_EQUAL:
return _("Both sides are equal");
diff --git a/FreeFileSync/Source/file_hierarchy.h b/FreeFileSync/Source/file_hierarchy.h
index 5c473466..356020d1 100644
--- a/FreeFileSync/Source/file_hierarchy.h
+++ b/FreeFileSync/Source/file_hierarchy.h
@@ -396,7 +396,7 @@ public:
//for use during init in "CompareProcess" only:
template <CompareFilesResult res> void setCategory();
- void setCategoryConflict(const std::wstring& description);
+ void setCategoryConflict (const std::wstring& description);
void setCategoryDiffMetadata(const std::wstring& description);
protected:
@@ -436,12 +436,11 @@ private:
bool selectedForSynchronization;
+ //Note: we model *four* states with following two variables => "syncDirectionConflict is empty or syncDir == NONE" is a class invariant!!!
SyncDirection syncDir_; //1 byte: optimize memory layout!
- std::unique_ptr<std::wstring> syncDirConflict; //non-empty if we have a conflict setting sync-direction
+ std::unique_ptr<std::wstring> syncDirectionConflict; //non-empty if we have a conflict setting sync-direction
//get rid of std::wstring small string optimization (consumes 32/48 byte on VS2010 x86/x64!)
- //Note: we model *four* states with last two variables => "syncDirConflict is empty or syncDir == NONE" is a class invariant!!!
-
Zstring shortNameLeft_; //slightly redundant under linux, but on windows the "same" filepaths can differ in case
Zstring shortNameRight_; //use as indicator: an empty name means: not existing!
@@ -634,7 +633,9 @@ inline
std::wstring FileSystemObject::getCatExtraDescription() const
{
assert(getCategory() == FILE_CONFLICT || getCategory() == FILE_DIFFERENT_METADATA);
- return cmpResultDescr ? *cmpResultDescr : std::wstring();
+ if (cmpResultDescr) //avoid ternary-WTF!
+ return *cmpResultDescr;
+ return std::wstring();
}
@@ -649,7 +650,7 @@ inline
void FileSystemObject::setSyncDir(SyncDirection newDir)
{
syncDir_ = newDir;
- syncDirConflict.reset();
+ syncDirectionConflict.reset();
notifySyncCfgChanged();
}
@@ -659,7 +660,7 @@ inline
void FileSystemObject::setSyncDirConflict(const std::wstring& description)
{
syncDir_ = SyncDirection::NONE;
- syncDirConflict = zen::make_unique<std::wstring>(description);
+ syncDirectionConflict = zen::make_unique<std::wstring>(description);
notifySyncCfgChanged();
}
@@ -669,7 +670,9 @@ inline
std::wstring FileSystemObject::getSyncOpConflict() const
{
assert(getSyncOperation() == SO_UNRESOLVED_CONFLICT);
- return syncDirConflict ? *syncDirConflict : std::wstring();
+ if (syncDirectionConflict) //avoid ternary-WTF!
+ return *syncDirectionConflict;
+ return std::wstring();
}
@@ -712,7 +715,9 @@ const Zstring& FileSystemObject::getItemName() const
template <SelectedSide side> inline
Zstring FileSystemObject::getRelativePath() const
{
- return isEmpty<side>() ? Zstring() : parent_.getPairRelativePathPf() + getItemName<side>();
+ if (isEmpty<side>()) //avoid ternary-WTF!
+ return Zstring();
+ return parent_.getPairRelativePathPf() + getItemName<side>();
}
@@ -733,7 +738,9 @@ Zstring FileSystemObject::getPairShortName() const
template <SelectedSide side> inline
Zstring FileSystemObject::getFullPath() const
{
- return isEmpty<side>() ? Zstring() : getBaseDirPf<side>() + parent_.getPairRelativePathPf() + getItemName<side>();
+ if (isEmpty<side>()) //avoid ternary-WTF!
+ return Zstring();
+ return getBaseDirPf<side>() + parent_.getPairRelativePathPf() + getItemName<side>();
}
@@ -819,7 +826,7 @@ void FileSystemObject::flip()
case FILE_RIGHT_NEWER:
cmpResult = FILE_LEFT_NEWER;
break;
- case FILE_DIFFERENT:
+ case FILE_DIFFERENT_CONTENT:
case FILE_EQUAL:
case FILE_DIFFERENT_METADATA:
case FILE_CONFLICT:
@@ -897,12 +904,11 @@ FilePair& HierarchyObject::addSubFile<RIGHT_SIDE>(const Zstring& shortName, cons
inline
-SymlinkPair& HierarchyObject::addSubLink(
- const Zstring& shortNameLeft,
- const LinkDescriptor& left, //link exists on both sides
- CompareSymlinkResult defaultCmpResult,
- const Zstring& shortNameRight,
- const LinkDescriptor& right)
+SymlinkPair& HierarchyObject::addSubLink(const Zstring& shortNameLeft,
+ const LinkDescriptor& left, //link exists on both sides
+ CompareSymlinkResult defaultCmpResult,
+ const Zstring& shortNameRight,
+ const LinkDescriptor& right)
{
subLinks.emplace_back(shortNameLeft, left, defaultCmpResult, shortNameRight, right, *this);
return subLinks.back();
diff --git a/FreeFileSync/Source/lib/db_file.cpp b/FreeFileSync/Source/lib/db_file.cpp
index 22bd2424..6000ebf2 100644
--- a/FreeFileSync/Source/lib/db_file.cpp
+++ b/FreeFileSync/Source/lib/db_file.cpp
@@ -471,6 +471,7 @@ private:
void process(const HierarchyObject::SubFileVec& currentFiles, const Zstring& parentRelativeNamePf, InSyncDir::FileList& dbFiles)
{
std::unordered_set<const InSyncFile*> toPreserve; //referencing fixed-in-memory std::map elements
+
for (const FilePair& fileObj : currentFiles)
if (!fileObj.isEmpty())
{
@@ -500,7 +501,6 @@ private:
}
}
- warn_static("consider temporarily excluded items due to traveral error just like a fixed file filter here!?")
//delete removed items (= "in-sync") from database
map_remove_if(dbFiles, [&](const InSyncDir::FileList::value_type& v) -> bool
{
@@ -509,12 +509,14 @@ private:
//all items not existing in "currentFiles" have either been deleted meanwhile or been excluded via filter:
const Zstring& shortName = v.first;
return filter_.passFileFilter(parentRelativeNamePf + shortName);
+ //note: items subject to traveral errors are also excluded by this file filter here! see comparison.cpp, modified file filter for read errors
});
}
void process(const HierarchyObject::SubLinkVec& currentLinks, const Zstring& parentRelativeNamePf, InSyncDir::LinkList& dbLinks)
{
std::unordered_set<const InSyncSymlink*> toPreserve;
+
for (const SymlinkPair& linkObj : currentLinks)
if (!linkObj.isEmpty())
{
@@ -551,6 +553,7 @@ private:
void process(const HierarchyObject::SubDirVec& currentDirs, const Zstring& parentRelativeNamePf, InSyncDir::DirList& dbDirs)
{
std::unordered_set<const InSyncDir*> toPreserve;
+
for (const DirPair& dirObj : currentDirs)
if (!dirObj.isEmpty())
switch (dirObj.getDirCategory())
@@ -580,6 +583,7 @@ private:
}
break;
+ case DIR_CONFLICT:
case DIR_DIFFERENT_METADATA:
//if DIR_DIFFERENT_METADATA and no old database entry yet: we have to insert a placeholder database entry:
//we cannot simply skip the whole directory, since sub-items might be in sync!
@@ -588,7 +592,7 @@ private:
//reuse last "in-sync" if available or insert strawman entry (do not try to update and thereby remove child elements!!!)
InSyncDir& dir = dbDirs.emplace(dirObj.getPairShortName(), InSyncDir(InSyncDir::DIR_STATUS_STRAW_MAN)).first->second;
toPreserve.insert(&dir);
- recurse(dirObj, dir);
+ recurse(dirObj, dir); //unconditional recursion without filter check! => no problem since "subObjMightMatch" is optional!!!
}
break;
@@ -614,9 +618,11 @@ private:
const Zstring& shortName = v.first;
return filter_.passDirFilter(parentRelativeNamePf + shortName, nullptr);
//if directory is not included in "currentDirs", it is either not existing anymore, in which case it should be deleted from database
- //or it was excluded via filter, in which case the database entry should be preserved:
- //=> all child db elements are also preserved since they are not recursed in the loop above!!!
- //=> no problem with filter logic of excluding complete directory subtrees, if top folder is excluded directly!
+ //or it was excluded via filter and the database entry should be preserved
+
+ warn_static("insufficient for *.txt-include filters! -> e.g. 1. *.txt-include, both sides in sync, txt-fiels in subfolder")
+ warn_static("2. delete all subfolders externally ")
+ warn_static("3. sync => db should be updated == entries removed for .txt; mabye even for deleted subfolders!?!")
});
}
diff --git a/FreeFileSync/Source/lib/dir_lock.cpp b/FreeFileSync/Source/lib/dir_lock.cpp
index b486c730..703cb7b1 100644
--- a/FreeFileSync/Source/lib/dir_lock.cpp
+++ b/FreeFileSync/Source/lib/dir_lock.cpp
@@ -550,7 +550,9 @@ bool tryLock(const Zstring& lockfilepath) //throw FileError
//=> we don't need it that badly //::SetFileAttributes(applyLongPathPrefix(lockfilepath).c_str(), FILE_ATTRIBUTE_HIDDEN); //(try to) hide it
#elif defined ZEN_LINUX || defined ZEN_MAC
- ::umask(0); //important! -> why?
+ const mode_t oldMask = ::umask(0); //important: we want the lock file to have exactly the permissions specified
+ ZEN_ON_SCOPE_EXIT(::umask(oldMask));
+
//O_EXCL contains a race condition on NFS file systems: http://linux.die.net/man/2/open
const int fileHandle = ::open(lockfilepath.c_str(), O_CREAT | O_WRONLY | O_EXCL, S_IRWXU | S_IRWXG | S_IRWXO);
if (fileHandle == -1)
diff --git a/FreeFileSync/Source/lib/hard_filter.cpp b/FreeFileSync/Source/lib/hard_filter.cpp
index 3f153821..ad8398ae 100644
--- a/FreeFileSync/Source/lib/hard_filter.cpp
+++ b/FreeFileSync/Source/lib/hard_filter.cpp
@@ -13,47 +13,17 @@
using namespace zen;
-//inline bool operator<(const std::type_info& lhs, const std::type_info& rhs) { return lhs.before(rhs) != 0; } -> not working on GCC :(
-
-//--------------------------------------------------------------------------------------------------
bool zen::operator<(const HardFilter& lhs, const HardFilter& rhs)
{
if (typeid(lhs) != typeid(rhs))
return typeid(lhs).before(typeid(rhs)); //in worst case, order is guaranteed to be stable only during each program run
- //this and other are of same type:
+ //lhs, rhs have same type:
return lhs.cmpLessSameType(rhs);
}
-//void HardFilter::saveFilter(ZstreamOut& stream) const //serialize derived object
-//{
-// //save type information
-// writeString(stream, uniqueClassIdentifier());
-//
-// //save actual object
-// save(stream);
-//}
-
-
-//HardFilter::FilterRef HardFilter::loadFilter(ZstreamIn& stream) //throw UnexpectedEndOfStreamError
-//{
-// //read type information
-// const std::string uniqueClassId = readString<std::string>(stream); //throw UnexpectedEndOfStreamError
-//
-// //read actual object
-// if (uniqueClassId == "NullFilter")
-// return NullFilter::load(stream);
-// else if (uniqueClassId == "NameFilter")
-// return NameFilter::load(stream);
-// else if (uniqueClassId == "CombinedFilter")
-// return CombinedFilter::load(stream);
-// else
-// throw std::logic_error("Programming Error: Unknown filter!");
-//}
-
-
namespace
{
//constructing them in addFilterEntry becomes perf issue for large filter lists
@@ -61,17 +31,14 @@ const Zstring asterisk(Zstr("*"));
const Zstring sepAsterisk = FILE_NAME_SEPARATOR + asterisk;
const Zstring asteriskSep = asterisk + FILE_NAME_SEPARATOR;
const Zstring asteriskSepAsterisk = asteriskSep + asterisk;
-}
-void addFilterEntry(const Zstring& filterPhrase, std::vector<Zstring>& fileFilter, std::vector<Zstring>& directoryFilter)
+void addFilterEntry(const Zstring& filterPhrase, std::vector<Zstring>& masksFileFolder, std::vector<Zstring>& masksFolder)
{
#if defined ZEN_WIN || defined ZEN_MAC
- //Windows does NOT distinguish between upper/lower-case
- const Zstring& filterFmt= makeUpperCopy(filterPhrase);
+ const Zstring& filterFmt= makeUpperCopy(filterPhrase); //Windows does NOT distinguish between upper/lower-case
#elif defined ZEN_LINUX
- const Zstring& filterFmt = filterPhrase;
- //Linux DOES distinguish between upper/lower-case: nothing to do here
+ const Zstring& filterFmt = filterPhrase; //Linux DOES distinguish between upper/lower-case: nothing to do here
#endif
/*
phrase | action
@@ -85,34 +52,26 @@ void addFilterEntry(const Zstring& filterPhrase, std::vector<Zstring>& fileFilte
| *\blah | -> add blah
| *\*blah | -> add *blah
+---------+--------
- | blah\ | remove \; directory only
- | blah*\ | remove \; directory only
- | blah\*\ | remove \; directory only
+ | blah\ | remove \; folder only
+ | blah*\ | remove \; folder only
+ | blah\*\ | remove \; folder only
+---------+--------
| blah* |
- | blah\* | add blah for directory only
- | blah*\* | add blah* for directory only
+ | blah\* | remove \*; folder only
+ | blah*\* | remove \*; folder only
+---------+--------
*/
- auto processTail = [&fileFilter, &directoryFilter](const Zstring& phrase)
+ auto processTail = [&masksFileFolder, &masksFolder](const Zstring& phrase)
{
- if (endsWith(phrase, FILE_NAME_SEPARATOR)) //only relevant for directory filtering
+ if (endsWith(phrase, FILE_NAME_SEPARATOR) || //only relevant for folder filtering
+ endsWith(phrase, sepAsterisk)) // abc\*
{
const Zstring dirPhrase = beforeLast(phrase, FILE_NAME_SEPARATOR);
if (!dirPhrase.empty())
- directoryFilter.push_back(dirPhrase);
+ masksFolder.push_back(dirPhrase);
}
else if (!phrase.empty())
- {
- fileFilter .push_back(phrase);
- directoryFilter.push_back(phrase);
- if (endsWith(phrase, sepAsterisk)) // abc\*
- {
- const Zstring dirPhrase = beforeLast(phrase, sepAsterisk);
- if (!dirPhrase.empty())
- directoryFilter.push_back(dirPhrase);
- }
- }
+ masksFileFolder.push_back(phrase);
};
if (startsWith(filterFmt, FILE_NAME_SEPARATOR)) // \abc
@@ -126,10 +85,8 @@ void addFilterEntry(const Zstring& filterPhrase, std::vector<Zstring>& fileFilte
}
-namespace
-{
template <class Char> inline
-const Char* cStringFind(const Char* str, Char ch) //strchr()
+const Char* cStringFind(const Char* str, Char ch) //= strchr(), wcschr()
{
for (;;)
{
@@ -144,18 +101,38 @@ const Char* cStringFind(const Char* str, Char ch) //strchr()
}
-bool matchesMask(const Zchar* str, const Zchar* mask)
+struct FullMatch
{
- for (;; ++mask, ++str)
+ static bool matchesMaskEnd (const Zchar* path) { return *path == 0; }
+ static bool matchesMaskStar(const Zchar* path) { return true; }
+};
+
+struct ParentFolderMatch //strict match of parent folder path!
+{
+ static bool matchesMaskEnd (const Zchar* path) { return *path == FILE_NAME_SEPARATOR; }
+ static bool matchesMaskStar(const Zchar* path) { return cStringFind(path, FILE_NAME_SEPARATOR) != nullptr; }
+};
+
+struct AnyMatch
+{
+ static bool matchesMaskEnd (const Zchar* path) { return *path == 0 || *path == FILE_NAME_SEPARATOR; }
+ static bool matchesMaskStar(const Zchar* path) { return true; }
+};
+
+
+template <class PathEndMatcher>
+bool matchesMask(const Zchar* path, const Zchar* mask)
+{
+ for (;; ++mask, ++path)
{
Zchar m = *mask;
- if (m == 0)
- return *str == 0;
-
switch (m)
{
+ case 0:
+ return PathEndMatcher::matchesMaskEnd(path);
+
case Zstr('?'):
- if (*str == 0)
+ if (*path == 0)
return false;
break;
@@ -168,14 +145,14 @@ bool matchesMask(const Zchar* str, const Zchar* mask)
while (m == Zstr('*'));
if (m == 0) //mask ends with '*':
- return true;
+ return PathEndMatcher::matchesMaskStar(path);
//*? - pattern
if (m == Zstr('?'))
{
++mask;
- while (*str++ != 0)
- if (matchesMask(str, mask))
+ while (*path++ != 0)
+ if (matchesMask<PathEndMatcher>(path, mask))
return true;
return false;
}
@@ -184,17 +161,17 @@ bool matchesMask(const Zchar* str, const Zchar* mask)
++mask;
for (;;)
{
- str = cStringFind(str, m);
- if (!str)
+ path = cStringFind(path, m);
+ if (!path)
return false;
- ++str;
- if (matchesMask(str, mask))
+ ++path;
+ if (matchesMask<PathEndMatcher>(path, mask))
return true;
}
default:
- if (*str != m)
+ if (*path != m)
return false;
}
}
@@ -208,11 +185,11 @@ bool matchesMaskBegin(const Zchar* str, const Zchar* mask)
for (;; ++mask, ++str)
{
const Zchar m = *mask;
- if (m == 0)
- return *str == 0;
-
switch (m)
{
+ case 0:
+ return *str == 0;
+
case Zstr('?'):
if (*str == 0)
return true;
@@ -227,115 +204,128 @@ bool matchesMaskBegin(const Zchar* str, const Zchar* mask)
}
}
}
+
+
+template <class PathEndMatcher> inline
+bool matchesMask(const Zstring& name, const std::vector<Zstring>& masks)
+{
+ return std::any_of(masks.begin(), masks.end(), [&](const Zstring& mask) { return matchesMask<PathEndMatcher>(name.c_str(), mask.c_str()); });
}
inline
-bool matchesFilter(const Zstring& name, const std::vector<Zstring>& filter)
+bool matchesMaskBegin(const Zstring& name, const std::vector<Zstring>& masks)
{
- return std::any_of(filter.begin(), filter.end(), [&](const Zstring& mask) { return matchesMask(name.c_str(), mask.c_str()); });
+ return std::any_of(masks.begin(), masks.end(), [&](const Zstring& mask) { return matchesMaskBegin(name.c_str(), mask.c_str()); });
}
inline
-bool matchesFilterBegin(const Zstring& name, const std::vector<Zstring>& filter)
+void removeDuplicates(std::vector<Zstring>& v)
{
- return std::any_of(filter.begin(), filter.end(), [&](const Zstring& mask) { return matchesMaskBegin(name.c_str(), mask.c_str()); });
+ std::sort(v.begin(), v.end());
+ v.erase(std::unique(v.begin(), v.end()), v.end());
+}
}
-std::vector<Zstring> splitByDelimiter(const Zstring& filterString)
+std::vector<Zstring> zen::splitByDelimiter(const Zstring& filterString)
{
//delimiters may be ';' or '\n'
std::vector<Zstring> output;
- const std::vector<Zstring> blocks = split(filterString, Zchar(';')); //split by less common delimiter first
- std::for_each(blocks.begin(), blocks.end(),
- [&](const Zstring& item)
- {
- const std::vector<Zstring> blocks2 = split(item, Zchar('\n'));
-
- std::for_each(blocks2.begin(), blocks2.end(),
- [&](Zstring entry)
+ for (const Zstring& str : split(filterString, Zchar(';'))) //split by less common delimiter first
+ for (Zstring entry : split(str, Zchar('\n')))
{
trim(entry);
if (!entry.empty())
- output.push_back(entry);
- });
- });
+ output.push_back(std::move(entry));
+ }
return output;
}
//#################################################################################################
-NameFilter::NameFilter(const Zstring& includeFilter, const Zstring& excludeFilter) :
- includeFilterTmp(includeFilter), //save constructor arguments for serialization
- excludeFilterTmp(excludeFilter)
-{
- //no need for regular expressions: In tests wxRegex was by factor of 10 slower than wxString::Matches()
- //load filter into vectors of strings
- //delimiters may be ';' or '\n'
- const std::vector<Zstring>& includeList = splitByDelimiter(includeFilter);
- const std::vector<Zstring>& excludeList = splitByDelimiter(excludeFilter);
+NameFilter::NameFilter(const Zstring& includePhrase, const Zstring& excludePhrase)
+{
+ //no need for regular expressions: In tests wxRegex was slower than wxString::Matches() by a factor of 10
//setup include/exclude filters for files and directories
- std::for_each(includeList.begin(), includeList.end(), [&](const Zstring& entry) { addFilterEntry(entry, filterFileIn, filterFolderIn); });
- std::for_each(excludeList.begin(), excludeList.end(), [&](const Zstring& entry) { addFilterEntry(entry, filterFileEx, filterFolderEx); });
+ for (const Zstring& entry : splitByDelimiter(includePhrase)) addFilterEntry(entry, includeMasksFileFolder, includeMasksFolder);
+ for (const Zstring& entry : splitByDelimiter(excludePhrase)) addFilterEntry(entry, excludeMasksFileFolder, excludeMasksFolder);
- auto removeDuplicates = [](std::vector<Zstring>& cont)
- {
- std::vector<Zstring> output;
- std::set<Zstring> used;
- std::copy_if(cont.begin(), cont.end(), std::back_inserter(output), [&](const Zstring& item) { return used.insert(item).second; });
- output.swap(cont);
- };
+ removeDuplicates(includeMasksFileFolder);
+ removeDuplicates(includeMasksFolder);
+ removeDuplicates(excludeMasksFileFolder);
+ removeDuplicates(excludeMasksFolder);
+}
+
+
+void NameFilter::addExclusion(const Zstring& excludePhrase)
+{
+ for (const Zstring& entry : splitByDelimiter(excludePhrase)) addFilterEntry(entry, excludeMasksFileFolder, excludeMasksFolder);
- removeDuplicates(filterFileIn);
- removeDuplicates(filterFolderIn);
- removeDuplicates(filterFileEx);
- removeDuplicates(filterFolderEx);
+ removeDuplicates(excludeMasksFileFolder);
+ removeDuplicates(excludeMasksFolder);
}
-bool NameFilter::passFileFilter(const Zstring& relFilename) const
+bool NameFilter::passFileFilter(const Zstring& relFilePath) const
{
#if defined ZEN_WIN || defined ZEN_MAC //Windows does NOT distinguish between upper/lower-case
- const Zstring& nameFmt = makeUpperCopy(relFilename);
+ const Zstring& pathFmt = makeUpperCopy(relFilePath);
#elif defined ZEN_LINUX //Linux DOES distinguish between upper/lower-case
- const Zstring& nameFmt = relFilename; //nothing to do here
+ const Zstring& pathFmt = relFilePath; //nothing to do here
#endif
- return matchesFilter(nameFmt, filterFileIn) && //process include filters
- !matchesFilter(nameFmt, filterFileEx); //process exclude filters
+ if (matchesMask<AnyMatch >(pathFmt, excludeMasksFileFolder) || //either full match on file or partial match on any parent folder
+ matchesMask<ParentFolderMatch>(pathFmt, excludeMasksFolder)) //partial match on any parent folder only
+ return false;
+
+ return matchesMask<AnyMatch >(pathFmt, includeMasksFileFolder) ||
+ matchesMask<ParentFolderMatch>(pathFmt, includeMasksFolder);
}
-bool NameFilter::passDirFilter(const Zstring& relDirname, bool* subObjMightMatch) const
+bool NameFilter::passDirFilter(const Zstring& relDirPath, bool* subObjMightMatch) const
{
assert(!subObjMightMatch || *subObjMightMatch == true); //check correct usage
#if defined ZEN_WIN || defined ZEN_MAC //Windows does NOT distinguish between upper/lower-case
- const Zstring& nameFmt = makeUpperCopy(relDirname);
+ const Zstring& pathFmt = makeUpperCopy(relDirPath);
#elif defined ZEN_LINUX //Linux DOES distinguish between upper/lower-case
- const Zstring& nameFmt = relDirname; //nothing to do here
+ const Zstring& pathFmt = relDirPath; //nothing to do here
#endif
- if (matchesFilter(nameFmt, filterFolderEx)) //process exclude filters
+ if (matchesMask<AnyMatch>(pathFmt, excludeMasksFileFolder) ||
+ matchesMask<AnyMatch>(pathFmt, excludeMasksFolder))
{
if (subObjMightMatch)
- *subObjMightMatch = false; //exclude subfolders/subfiles as well
+ *subObjMightMatch = false; //perf: no need to traverse deeper; subfolders/subfiles would be excluded by filter anyway!
+ /*
+ Attention: the design choice that "subObjMightMatch" is optional implies that the filter must provide correct results no matter if this
+ value is considered by the client! In particular, if *subObjMightMatch == false, then any filter evaluations for child items must
+ also return "false"!
+ This is not a problem for folder traversal which stops at the first *subObjMightMatch == false anyway, but other code continues recursing further,
+ e.g. the database update code in db_file.cpp recurses unconditionally without filter check! It's possible to construct edge cases with incorrect
+ behavior if "subObjMightMatch" were not optional:
+ 1. two folders including a sub folder with some files are in sync with up-to-date database files
+ 2. deny access to this sub folder on both sides and start sync ignoring errors
+ 3. => database entries of this sub folder are incorrectly deleted! (if sub-folder is excluded, but child items are not!)
+ */
return false;
}
- if (!matchesFilter(nameFmt, filterFolderIn)) //process include filters
+ if (!matchesMask<AnyMatch>(pathFmt, includeMasksFileFolder) &&
+ !matchesMask<AnyMatch>(pathFmt, includeMasksFolder))
{
if (subObjMightMatch)
{
- const Zstring& subNameBegin = nameFmt + FILE_NAME_SEPARATOR; //const-ref optimization
+ const Zstring& childPathBegin = pathFmt + FILE_NAME_SEPARATOR;
- *subObjMightMatch = matchesFilterBegin(subNameBegin, filterFileIn) || //might match a file in subdirectory
- matchesFilterBegin(subNameBegin, filterFolderIn); //or another subdirectory
+ *subObjMightMatch = matchesMaskBegin(childPathBegin, includeMasksFileFolder) || //might match a file or folder in subdirectory
+ matchesMaskBegin(childPathBegin, includeMasksFolder); //
}
return false;
}
@@ -344,17 +334,18 @@ bool NameFilter::passDirFilter(const Zstring& relDirname, bool* subObjMightMatch
}
-bool NameFilter::isNull(const Zstring& includeFilter, const Zstring& excludeFilter)
+bool NameFilter::isNull(const Zstring& includePhrase, const Zstring& excludePhrase)
{
- Zstring include = includeFilter;
- Zstring exclude = excludeFilter;
+ Zstring include = includePhrase;
+ Zstring exclude = excludePhrase;
trim(include);
trim(exclude);
return include == Zstr("*") && exclude.empty();
- //return NameFilter(includeFilter, excludeFilter).isNull(); -> very expensive for huge lists
+ //return NameFilter(includePhrase, excludePhrase).isNull(); -> very expensive for huge lists
}
+
bool NameFilter::isNull() const
{
static NameFilter output(Zstr("*"), Zstring());
@@ -368,33 +359,17 @@ bool NameFilter::cmpLessSameType(const HardFilter& other) const
const NameFilter& otherNameFilt = static_cast<const NameFilter&>(other);
- if (filterFileIn != otherNameFilt.filterFileIn)
- return filterFileIn < otherNameFilt.filterFileIn;
+ if (includeMasksFileFolder != otherNameFilt.includeMasksFileFolder)
+ return includeMasksFileFolder < otherNameFilt.includeMasksFileFolder;
- if (filterFolderIn != otherNameFilt.filterFolderIn)
- return filterFolderIn < otherNameFilt.filterFolderIn;
+ if (includeMasksFolder != otherNameFilt.includeMasksFolder)
+ return includeMasksFolder < otherNameFilt.includeMasksFolder;
- if (filterFileEx != otherNameFilt.filterFileEx)
- return filterFileEx < otherNameFilt.filterFileEx;
+ if (excludeMasksFileFolder != otherNameFilt.excludeMasksFileFolder)
+ return excludeMasksFileFolder < otherNameFilt.excludeMasksFileFolder;
- if (filterFolderEx != otherNameFilt.filterFolderEx)
- return filterFolderEx < otherNameFilt.filterFolderEx;
+ if (excludeMasksFolder != otherNameFilt.excludeMasksFolder)
+ return excludeMasksFolder < otherNameFilt.excludeMasksFolder;
- return false; //vectors equal
+ return false; //all equal
}
-
-
-//void NameFilter::save(ZstreamOut& stream) const
-//{
-// writeString(stream, includeFilterTmp);
-// writeString(stream, excludeFilterTmp);
-//}
-//
-//
-//HardFilter::FilterRef NameFilter::load(ZstreamIn& stream) //throw UnexpectedEndOfStreamError
-//{
-// const Zstring include = readString<Zstring>(stream); //throw UnexpectedEndOfStreamError
-// const Zstring exclude = readString<Zstring>(stream); //
-//
-// return FilterRef(new NameFilter(include, exclude));
-//}
diff --git a/FreeFileSync/Source/lib/hard_filter.h b/FreeFileSync/Source/lib/hard_filter.h
index f86306ec..04c2f9f9 100644
--- a/FreeFileSync/Source/lib/hard_filter.h
+++ b/FreeFileSync/Source/lib/hard_filter.h
@@ -34,24 +34,20 @@ public:
virtual ~HardFilter() {}
//filtering
- virtual bool passFileFilter(const Zstring& relFilename) const = 0;
- virtual bool passDirFilter (const Zstring& relDirname, bool* subObjMightMatch) const = 0;
+ virtual bool passFileFilter(const Zstring& relFilePath) const = 0;
+ virtual bool passDirFilter (const Zstring& relDirPath, bool* subObjMightMatch) const = 0;
//subObjMightMatch: file/dir in subdirectories could(!) match
- //note: variable is only set if passDirFilter returns false!
+ //note: this hint is only set if passDirFilter returns false!
virtual bool isNull() const = 0; //filter is equivalent to NullFilter, but may be technically slower
typedef std::shared_ptr<const HardFilter> FilterRef; //always bound by design!
- //serialization
- // void saveFilter(ZstreamOut& stream) const; //serialize derived object
- // static FilterRef loadFilter(ZstreamIn& stream); //throw UnexpectedEndOfStreamError; CAVEAT!!! adapt this method for each new derivation!!!
+ virtual FilterRef copyFilterAddingExclusion(const Zstring& excludePhrase) const = 0;
private:
friend bool operator<(const HardFilter& lhs, const HardFilter& rhs);
- // virtual void save(ZstreamOut& stream) const = 0; //serialization
- virtual std::string uniqueClassIdentifier() const = 0; //get identifier, used for serialization
virtual bool cmpLessSameType(const HardFilter& other) const = 0; //typeid(*this) == typeid(other) in this context!
};
@@ -67,15 +63,12 @@ HardFilter::FilterRef combineFilters(const HardFilter::FilterRef& first,
class NullFilter : public HardFilter //no filtering at all
{
public:
- bool passFileFilter(const Zstring& relFilename) const override;
- bool passDirFilter(const Zstring& relDirname, bool* subObjMightMatch) const override;
- bool isNull() const override;
+ bool passFileFilter(const Zstring& relFilePath) const override { return true; }
+ bool passDirFilter(const Zstring& relDirPath, bool* subObjMightMatch) const override;
+ bool isNull() const override { return true; }
+ FilterRef copyFilterAddingExclusion(const Zstring& excludePhrase) const override;
private:
- friend class HardFilter;
- // void save(ZstreamOut& stream) const override {}
- std::string uniqueClassIdentifier() const override { return "NullFilter"; }
- // static FilterRef load(ZstreamIn& stream); //throw UnexpectedEndOfStreamError
bool cmpLessSameType(const HardFilter& other) const override;
};
@@ -83,49 +76,42 @@ private:
class NameFilter : public HardFilter //standard filter by filepath
{
public:
- NameFilter(const Zstring& includeFilter, const Zstring& excludeFilter);
+ NameFilter(const Zstring& includePhrase, const Zstring& excludePhrase);
+
+ void addExclusion(const Zstring& excludePhrase);
- bool passFileFilter(const Zstring& relFilename) const override;
- bool passDirFilter(const Zstring& relDirname, bool* subObjMightMatch) const override;
+ bool passFileFilter(const Zstring& relFilePath) const override;
+ bool passDirFilter(const Zstring& relDirPath, bool* subObjMightMatch) const override;
bool isNull() const override;
- static bool isNull(const Zstring& includeFilter, const Zstring& excludeFilter); //*fast* check without expensively constructing NameFilter instance!
+ static bool isNull(const Zstring& includePhrase, const Zstring& excludePhrase); //*fast* check without expensive NameFilter construction!
+ FilterRef copyFilterAddingExclusion(const Zstring& excludePhrase) const override;
private:
- friend class HardFilter;
- // void save(ZstreamOut& stream) const override;
- std::string uniqueClassIdentifier() const override { return "NameFilter"; }
- // static FilterRef load(ZstreamIn& stream); //throw UnexpectedEndOfStreamError
bool cmpLessSameType(const HardFilter& other) const override;
- std::vector<Zstring> filterFileIn; //
- std::vector<Zstring> filterFolderIn; //upper case (windows) + unique items by construction
- std::vector<Zstring> filterFileEx; //
- std::vector<Zstring> filterFolderEx; //
-
- const Zstring includeFilterTmp; //save constructor arguments for serialization
- const Zstring excludeFilterTmp; //
+ std::vector<Zstring> includeMasksFileFolder; //
+ std::vector<Zstring> includeMasksFolder; //upper case (windows) + unique items by construction
+ std::vector<Zstring> excludeMasksFileFolder; //
+ std::vector<Zstring> excludeMasksFolder; //
};
class CombinedFilter : public HardFilter //combine two filters to match if and only if both match
{
public:
- CombinedFilter(const FilterRef& first, const FilterRef& second) : first_(first), second_(second) {}
+ CombinedFilter(const NameFilter& first, const NameFilter& second) : first_(first), second_(second) { assert(!first.isNull() && !second.isNull()); } //if either is null, then wy use CombinedFilter?
- bool passFileFilter(const Zstring& relFilename) const override;
- bool passDirFilter(const Zstring& relDirname, bool* subObjMightMatch) const override;
+ bool passFileFilter(const Zstring& relFilePath) const override;
+ bool passDirFilter(const Zstring& relDirPath, bool* subObjMightMatch) const override;
bool isNull() const override;
+ FilterRef copyFilterAddingExclusion(const Zstring& excludePhrase) const override;
private:
- friend class HardFilter;
- // void save(ZstreamOut& stream) const override;
- std::string uniqueClassIdentifier() const override { return "CombinedFilter"; }
- // static FilterRef load(ZstreamIn& stream); //throw UnexpectedEndOfStreamError
bool cmpLessSameType(const HardFilter& other) const override;
- const FilterRef first_;
- const FilterRef second_;
+ const NameFilter first_;
+ const NameFilter second_;
};
@@ -133,73 +119,59 @@ private:
-
-
-
-
-
-
-
-
-
-
-
-
-//---------------Inline Implementation---------------------------------------------------
-//inline
-//HardFilter::FilterRef NullFilter::load(ZstreamIn& stream)
-//{
-// return FilterRef(new NullFilter);
-//}
-
-
+//--------------- inline implementation ---------------------------------------
inline
-bool NullFilter::passFileFilter(const Zstring& relFilename) const
+bool NullFilter::passDirFilter(const Zstring& relDirPath, bool* subObjMightMatch) const
{
+ assert(!subObjMightMatch || *subObjMightMatch == true); //check correct usage
return true;
}
inline
-bool NullFilter::passDirFilter(const Zstring& relDirname, bool* subObjMightMatch) const
+bool NullFilter::cmpLessSameType(const HardFilter& other) const
{
- assert(!subObjMightMatch || *subObjMightMatch == true); //check correct usage
- return true;
+ assert(typeid(*this) == typeid(other)); //always given in this context!
+ return false;
}
inline
-bool NullFilter::isNull() const
+HardFilter::FilterRef NullFilter::copyFilterAddingExclusion(const Zstring& excludePhrase) const
{
- return true;
+ auto filter = std::make_shared<NameFilter>(Zstr("*"), excludePhrase);
+ if (filter->isNull())
+ return std::make_shared<NullFilter>();
+ return filter;
}
inline
-bool NullFilter::cmpLessSameType(const HardFilter& other) const
+HardFilter::FilterRef NameFilter::copyFilterAddingExclusion(const Zstring& excludePhrase) const
{
- assert(typeid(*this) == typeid(other)); //always given in this context!
- return false;
+ auto tmp = std::make_shared<NameFilter>(*this);
+ tmp->addExclusion(excludePhrase);
+ return tmp;
}
inline
-bool CombinedFilter::passFileFilter(const Zstring& relFilename) const
+bool CombinedFilter::passFileFilter(const Zstring& relFilePath) const
{
- return first_ ->passFileFilter(relFilename) && //short-circuit behavior
- second_->passFileFilter(relFilename);
+ return first_ .passFileFilter(relFilePath) && //short-circuit behavior
+ second_.passFileFilter(relFilePath);
}
inline
-bool CombinedFilter::passDirFilter(const Zstring& relDirname, bool* subObjMightMatch) const
+bool CombinedFilter::passDirFilter(const Zstring& relDirPath, bool* subObjMightMatch) const
{
- if (first_->passDirFilter(relDirname, subObjMightMatch))
- return second_->passDirFilter(relDirname, subObjMightMatch);
+ if (first_.passDirFilter(relDirPath, subObjMightMatch))
+ return second_.passDirFilter(relDirPath, subObjMightMatch);
else
{
if (subObjMightMatch && *subObjMightMatch)
- second_->passDirFilter(relDirname, subObjMightMatch);
+ second_.passDirFilter(relDirPath, subObjMightMatch);
return false;
}
}
@@ -208,7 +180,17 @@ bool CombinedFilter::passDirFilter(const Zstring& relDirname, bool* subObjMightM
inline
bool CombinedFilter::isNull() const
{
- return first_->isNull() && second_->isNull();
+ return first_.isNull() && second_.isNull();
+}
+
+
+inline
+HardFilter::FilterRef CombinedFilter::copyFilterAddingExclusion(const Zstring& excludePhrase) const
+{
+ NameFilter tmp(first_);
+ tmp.addExclusion(excludePhrase);
+
+ return std::make_shared<CombinedFilter>(tmp, second_);
}
@@ -219,51 +201,39 @@ bool CombinedFilter::cmpLessSameType(const HardFilter& other) const
const CombinedFilter& otherCombFilt = static_cast<const CombinedFilter&>(other);
- if (*first_ != *otherCombFilt.first_)
- return *first_ < *otherCombFilt.first_;
+ if (first_ != otherCombFilt.first_)
+ return first_ < otherCombFilt.first_;
- return *second_ < *otherCombFilt.second_;
+ return second_ < otherCombFilt.second_;
}
-//inline
-//void CombinedFilter::save(ZstreamOut& stream) const
-//{
-// first_ ->saveFilter(stream);
-// second_->saveFilter(stream);
-//}
-
-
-//inline
-//HardFilter::FilterRef CombinedFilter::load(ZstreamIn& stream) //throw UnexpectedEndOfStreamError
-//{
-// FilterRef first = loadFilter(stream); //throw UnexpectedEndOfStreamError
-// FilterRef second = loadFilter(stream); //
-//
-// return combineFilters(first, second);
-//}
-
-
inline
-HardFilter::FilterRef combineFilters(const HardFilter::FilterRef& first,
- const HardFilter::FilterRef& second)
+HardFilter::FilterRef constructFilter(const Zstring& includePhrase,
+ const Zstring& excludePhrase,
+ const Zstring& includePhrase2,
+ const Zstring& excludePhrase2)
{
- if (first->isNull())
- {
- if (second->isNull())
- return std::make_shared<NullFilter>();
- else
- return second;
- }
+ std::shared_ptr<HardFilter> filterTmp;
+
+ if (NameFilter::isNull(includePhrase, Zstring()))
+ filterTmp = std::make_shared<NameFilter>(includePhrase2, excludePhrase + Zstr("\n") + excludePhrase2);
else
{
- if (second->isNull())
- return first;
+ if (NameFilter::isNull(includePhrase2, Zstring()))
+ filterTmp = std::make_shared<NameFilter>(includePhrase, excludePhrase + Zstr("\n") + excludePhrase2);
else
- return std::make_shared<CombinedFilter>(first, second);
+ return std::make_shared<CombinedFilter>(NameFilter(includePhrase, excludePhrase + Zstr("\n") + excludePhrase2), NameFilter(includePhrase2, Zstring()));
}
-}
+
+ if (filterTmp->isNull())
+ return std::make_shared<NullFilter>();
+
+ return filterTmp;
}
+std::vector<Zstring> splitByDelimiter(const Zstring& filterString); //keep external linkage for unit test
+}
+
#endif //HARD_FILTER_H_825780275842758345
diff --git a/FreeFileSync/Source/lib/norm_filter.h b/FreeFileSync/Source/lib/norm_filter.h
index e9ea0f5d..33efb1a5 100644
--- a/FreeFileSync/Source/lib/norm_filter.h
+++ b/FreeFileSync/Source/lib/norm_filter.h
@@ -46,29 +46,10 @@ bool isNullFilter(const FilterConfig& filterCfg)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
// ----------------------- implementation -----------------------
inline
NormalizedFilter normalizeFilters(const FilterConfig& global, const FilterConfig& local)
{
- HardFilter::FilterRef globalName = std::make_shared<NameFilter>(global.includeFilter, global.excludeFilter);
- HardFilter::FilterRef localName = std::make_shared<NameFilter>(local .includeFilter, local .excludeFilter);
-
SoftFilter globalTimeSize(global.timeSpan, global.unitTimeSpan,
global.sizeMin, global.unitSizeMin,
global.sizeMax, global.unitSizeMax);
@@ -77,7 +58,9 @@ NormalizedFilter normalizeFilters(const FilterConfig& global, const FilterConfig
local.sizeMin, local.unitSizeMin,
local.sizeMax, local.unitSizeMax);
- return NormalizedFilter(combineFilters(globalName, localName),
+
+ return NormalizedFilter(constructFilter(global.includeFilter, global.excludeFilter,
+ local .includeFilter, local .excludeFilter),
combineFilters(globalTimeSize, localTimeSize));
}
}
diff --git a/FreeFileSync/Source/lib/parallel_scan.cpp b/FreeFileSync/Source/lib/parallel_scan.cpp
index 2b76bf62..51c02214 100644
--- a/FreeFileSync/Source/lib/parallel_scan.cpp
+++ b/FreeFileSync/Source/lib/parallel_scan.cpp
@@ -158,8 +158,7 @@ typedef Zbase<wchar_t, StorageRefCountThreadSafe> BasicWString; //thread-safe st
class AsyncCallback //actor pattern
{
public:
- AsyncCallback() :
- notifyingThreadID(0),
+ AsyncCallback() : notifyingThreadID(0),
textScanning(_("Scanning:")),
itemsScanned(0),
activeWorker(0) {}
@@ -282,12 +281,12 @@ public:
TraverserShared(long threadID,
SymLinkHandling handleSymlinks,
const HardFilter::FilterRef& filter,
- std::set<Zstring>& failedDirReads,
- std::set<Zstring>& failedItemReads,
+ std::map<Zstring, std::wstring, LessFilename>& failedDirReads,
+ std::map<Zstring, std::wstring, LessFilename>& failedItemReads,
AsyncCallback& acb) :
handleSymlinks_(handleSymlinks),
filterInstance(filter),
- failedDirReads_(failedDirReads),
+ failedDirReads_ (failedDirReads),
failedItemReads_(failedItemReads),
acb_(acb),
threadID_(threadID) {}
@@ -295,8 +294,8 @@ public:
const SymLinkHandling handleSymlinks_;
const HardFilter::FilterRef filterInstance; //always bound!
- std::set<Zstring>& failedDirReads_;
- std::set<Zstring>& failedItemReads_;
+ std::map<Zstring, std::wstring, LessFilename>& failedDirReads_;
+ std::map<Zstring, std::wstring, LessFilename>& failedItemReads_;
AsyncCallback& acb_;
const long threadID_;
@@ -429,7 +428,7 @@ TraverseCallback* DirCallback::onDir(const Zchar* shortName, const Zstring& dirp
void DirCallback::releaseDirTraverser(TraverseCallback* trav)
{
- TraverseCallback::releaseDirTraverser(trav); //no-op, introduce compile-time coupling
+ TraverseCallback::releaseDirTraverser(trav); //no-op; introduce compile-time coupling
delete trav;
}
@@ -440,7 +439,7 @@ DirCallback::HandleError DirCallback::reportDirError(const std::wstring& msg, si
switch (cfg.acb_.reportError(msg, retryNumber))
{
case FillBufferCallback::ON_ERROR_IGNORE:
- cfg.failedDirReads_.insert(relNameParentPf_);
+ cfg.failedDirReads_[beforeLast(relNameParentPf_, FILE_NAME_SEPARATOR)] = msg;
return ON_ERROR_IGNORE;
case FillBufferCallback::ON_ERROR_RETRY:
@@ -457,7 +456,7 @@ DirCallback::HandleError DirCallback::reportItemError(const std::wstring& msg, s
switch (cfg.acb_.reportError(msg, retryNumber))
{
case FillBufferCallback::ON_ERROR_IGNORE:
- cfg.failedItemReads_.insert(relNameParentPf_ + shortName);
+ cfg.failedItemReads_[relNameParentPf_ + shortName] = msg;
return ON_ERROR_IGNORE;
case FillBufferCallback::ON_ERROR_RETRY:
diff --git a/FreeFileSync/Source/lib/parallel_scan.h b/FreeFileSync/Source/lib/parallel_scan.h
index 5557d10f..e0c8d15e 100644
--- a/FreeFileSync/Source/lib/parallel_scan.h
+++ b/FreeFileSync/Source/lib/parallel_scan.h
@@ -46,8 +46,11 @@ bool operator<(const DirectoryKey& lhs, const DirectoryKey& rhs)
struct DirectoryValue
{
DirContainer dirCont;
- std::set<Zstring> failedDirReads; //relative postfixed names (or empty string for root) for directories that could not be read (completely), e.g. access denied, or temporal network drop
- std::set<Zstring> failedItemReads; //relative postfixed names (never empty) for failure to read single file/dir/symlink
+ //relative names (or empty string for root) for directories that could not be read (completely), e.g. access denied, or temporal network drop
+ std::map<Zstring, std::wstring, LessFilename> failedDirReads; //with corresponding error message
+
+ //relative names (never empty) for failure to read single file/dir/symlink with corresponding error message
+ std::map<Zstring, std::wstring, LessFilename> failedItemReads;
};
diff --git a/FreeFileSync/Source/lib/resolve_path.cpp b/FreeFileSync/Source/lib/resolve_path.cpp
index 498ea39f..4faaa34f 100644
--- a/FreeFileSync/Source/lib/resolve_path.cpp
+++ b/FreeFileSync/Source/lib/resolve_path.cpp
@@ -28,10 +28,11 @@ using namespace zen;
namespace
{
-#ifdef ZEN_WIN
-Zstring resolveRelativePath(const Zstring& relativePath) //note: ::GetFullPathName() is documented not threadsafe!
+Zstring resolveRelativePath(const Zstring& relativePath) //note: ::GetFullPathName() is documented to be not thread-safe!
{
- //don't use long path prefix! does not work with relative paths "." and ".."
+#ifdef ZEN_WIN
+ //- don't use long path prefix here! does not work with relative paths "." and ".."
+ //- function also replaces "/" characters by "\"
const DWORD bufferSize = ::GetFullPathName(relativePath.c_str(), 0, nullptr, nullptr);
if (bufferSize > 0)
{
@@ -44,11 +45,8 @@ Zstring resolveRelativePath(const Zstring& relativePath) //note: ::GetFullPathNa
return Zstring(&buffer[0], charsWritten);
}
return relativePath; //ERROR! Don't do anything
-}
#elif defined ZEN_LINUX || defined ZEN_MAC
-Zstring resolveRelativePath(const Zstring& relativePath)
-{
//http://linux.die.net/man/2/path_resolution
if (!startsWith(relativePath, FILE_NAME_SEPARATOR)) //absolute names are exactly those starting with a '/'
{
@@ -80,8 +78,9 @@ Zstring resolveRelativePath(const Zstring& relativePath)
}
}
return relativePath;
-}
#endif
+}
+
#ifdef ZEN_WIN
class CsidlConstants
@@ -147,7 +146,7 @@ private:
addCsidl(CSIDL_COMMON_DESKTOPDIRECTORY, L"csidl_PublicDesktop"); // C:\Users\All Users\Desktop
addCsidl(CSIDL_FAVORITES, L"csidl_Favorites"); // C:\Users\<user>\Favorites
- addCsidl(CSIDL_COMMON_FAVORITES, L"csidl_PublicFavorites"); // C:\Users\<user>\Favorites; unused? -> http://blogs.msdn.com/b/oldnewthing/archive/2012/09/04/10346022.aspx
+ //addCsidl(CSIDL_COMMON_FAVORITES, L"csidl_PublicFavorites"); // C:\Users\<user>\Favorites; unused? -> http://blogs.msdn.com/b/oldnewthing/archive/2012/09/04/10346022.aspx
addCsidl(CSIDL_PERSONAL, L"csidl_MyDocuments"); // C:\Users\<user>\Documents
addCsidl(CSIDL_COMMON_DOCUMENTS, L"csidl_PublicDocuments"); // C:\Users\Public\Documents
@@ -209,11 +208,11 @@ auto& dummy = CsidlConstants::get();
#endif
-std::unique_ptr<Zstring> getEnvironmentVar(const Zstring& envName) //return nullptr if not found
+Opt<Zstring> getEnvironmentVar(const Zstring& envName) //return nullptr if not found
{
wxString value;
if (!wxGetEnv(utfCvrtTo<wxString>(envName), &value))
- return nullptr;
+ return NoValue();
//some postprocessing:
trim(value); //remove leading, trailing blanks
@@ -224,32 +223,32 @@ std::unique_ptr<Zstring> getEnvironmentVar(const Zstring& envName) //return null
value.length() >= 2)
value = wxString(value.c_str() + 1, value.length() - 2);
- return zen::make_unique<Zstring>(utfCvrtTo<Zstring>(value));
+ return utfCvrtTo<Zstring>(value);
}
-std::unique_ptr<Zstring> resolveMacro(const Zstring& macro, //macro without %-characters
- const std::vector<std::pair<Zstring, Zstring>>& ext) //return nullptr if not resolved
+Opt<Zstring> resolveMacro(const Zstring& macro, //macro without %-characters
+ const std::vector<std::pair<Zstring, Zstring>>& ext) //return nullptr if not resolved
{
auto equalNoCase = [](const Zstring& lhs, const Zstring& rhs) { return utfCvrtTo<wxString>(lhs).CmpNoCase(utfCvrtTo<wxString>(rhs)) == 0; };
//there exist environment variables named %TIME%, %DATE% so check for our internal macros first!
if (equalNoCase(macro, Zstr("time")))
- return zen::make_unique<Zstring>(formatTime<Zstring>(Zstr("%H%M%S")));
+ return formatTime<Zstring>(Zstr("%H%M%S"));
if (equalNoCase(macro, Zstr("date")))
- return zen::make_unique<Zstring>(formatTime<Zstring>(FORMAT_ISO_DATE));
+ return formatTime<Zstring>(FORMAT_ISO_DATE);
if (equalNoCase(macro, Zstr("timestamp")))
- return zen::make_unique<Zstring>(formatTime<Zstring>(Zstr("%Y-%m-%d %H%M%S"))); //e.g. "2012-05-15 131513"
+ return formatTime<Zstring>(Zstr("%Y-%m-%d %H%M%S")); //e.g. "2012-05-15 131513"
- std::unique_ptr<Zstring> cand;
+ Zstring cand;
auto processPhrase = [&](const Zchar* phrase, const Zchar* format) -> bool
{
if (!equalNoCase(macro, phrase))
return false;
- cand = zen::make_unique<Zstring>(formatTime<Zstring>(format));
+ cand = formatTime<Zstring>(format);
return true;
};
@@ -266,11 +265,11 @@ std::unique_ptr<Zstring> resolveMacro(const Zstring& macro, //macro without %-ch
{
auto it = std::find_if(ext.begin(), ext.end(), [&](const std::pair<Zstring, Zstring>& p) { return equalNoCase(macro, p.first); });
if (it != ext.end())
- return zen::make_unique<Zstring>(it->second);
+ return it->second;
}
//try to resolve as environment variable
- if (std::unique_ptr<Zstring> value = getEnvironmentVar(macro))
+ if (Opt<Zstring> value = getEnvironmentVar(macro))
return value;
#ifdef ZEN_WIN
@@ -279,11 +278,11 @@ std::unique_ptr<Zstring> resolveMacro(const Zstring& macro, //macro without %-ch
const auto& csidlMap = CsidlConstants::get();
auto it = csidlMap.find(macro);
if (it != csidlMap.end())
- return zen::make_unique<Zstring>(it->second);
+ return it->second;
}
#endif
- return nullptr;
+ return NoValue();
}
const Zchar MACRO_SEP = Zstr('%');
@@ -300,7 +299,7 @@ Zstring expandMacros(const Zstring& text, const std::vector<std::pair<Zstring, Z
Zstring potentialMacro = beforeFirst(rest, MACRO_SEP);
Zstring postfix = afterFirst (rest, MACRO_SEP); //text == prefix + MACRO_SEP + potentialMacro + MACRO_SEP + postfix
- if (std::unique_ptr<Zstring> value = resolveMacro(potentialMacro, ext))
+ if (Opt<Zstring> value = resolveMacro(potentialMacro, ext))
return prefix + *value + expandMacros(postfix, ext);
else
return prefix + MACRO_SEP + potentialMacro + expandMacros(MACRO_SEP + postfix, ext);
@@ -384,7 +383,7 @@ Zstring getVolumeName(const Zstring& volumePath) //return empty string on error
nullptr, //__out_opt LPDWORD lpFileSystemFlags,
nullptr, //__out LPTSTR lpFileSystemNameBuffer,
0)) //__in DWORD nFileSystemNameSize
- return &buffer[0];
+ return &buffer[0]; //can be empty!!!
}
return Zstring();
}
@@ -450,8 +449,9 @@ void getDirectoryAliasesRecursive(const Zstring& dirpath, std::set<Zstring, Less
dirpath[1] == L':' &&
dirpath[2] == L'\\')
{
- if (Opt<Zstring> volname = getVolumeName(Zstring(dirpath.c_str(), 3))) //should not block
- output.insert(L"[" + *volname + L"]" + Zstring(dirpath.c_str() + 2));
+ Zstring volname = getVolumeName(Zstring(dirpath.c_str(), 3)); //should not block
+ if (!volname.empty())
+ output.insert(L"[" + volname + L"]" + Zstring(dirpath.c_str() + 2));
}
//2. replace volume name by volume path: [SYSTEM]\dirpath -> c:\dirpath
@@ -470,7 +470,7 @@ void getDirectoryAliasesRecursive(const Zstring& dirpath, std::set<Zstring, Less
//get list of useful variables
auto addEnvVar = [&](const Zstring& envName)
{
- if (std::unique_ptr<Zstring> value = getEnvironmentVar(envName))
+ if (Opt<Zstring> value = getEnvironmentVar(envName))
envToDir.emplace(envName, *value);
};
#ifdef ZEN_WIN
@@ -556,7 +556,7 @@ Zstring zen::getFormattedDirectoryPath(const Zstring& dirpassPhrase) // throw()
need to resolve relative paths:
WINDOWS:
- \\?\-prefix which needs absolute names
- - Volume Shadow Copy: volume name needs to be part of each filepath
+ - Volume Shadow Copy: volume name needs to be part of each file path
- file icon buffer (at least for extensions that are actually read from disk, like "exe")
- ::SHFileOperation(): Using relative path names is not thread safe
WINDOWS/LINUX:
diff --git a/FreeFileSync/Source/lib/status_handler_impl.h b/FreeFileSync/Source/lib/status_handler_impl.h
index e96eb249..145d02f8 100644
--- a/FreeFileSync/Source/lib/status_handler_impl.h
+++ b/FreeFileSync/Source/lib/status_handler_impl.h
@@ -14,7 +14,7 @@
namespace zen
{
template <typename Function> inline
-zen::Opt<std::wstring> tryReportingError(Function cmd, ProcessCallback& handler) //return ignored error message if available
+zen::Opt<std::wstring> tryReportingError(Function cmd, ProcessCallback& handler) //throw X?; return ignored error message if available
{
for (size_t retryNumber = 0;; ++retryNumber)
try
@@ -24,7 +24,7 @@ zen::Opt<std::wstring> tryReportingError(Function cmd, ProcessCallback& handler)
}
catch (zen::FileError& error)
{
- switch (handler.reportError(error.toString(), retryNumber)) //may throw!
+ switch (handler.reportError(error.toString(), retryNumber)) //throw ?
{
case ProcessCallback::IGNORE_ERROR:
return error.toString();
diff --git a/FreeFileSync/Source/structures.cpp b/FreeFileSync/Source/structures.cpp
index c549e0df..da3d9a71 100644
--- a/FreeFileSync/Source/structures.cpp
+++ b/FreeFileSync/Source/structures.cpp
@@ -10,6 +10,7 @@
#include <ctime>
#include <zen/i18n.h>
#include <zen/time.h>
+#include "lib/hard_filter.h"
using namespace zen;
@@ -166,7 +167,7 @@ std::wstring zen::getSymbol(CompareFilesResult cmpRes)
return L"newer <-";
case FILE_RIGHT_NEWER:
return L"newer ->";
- case FILE_DIFFERENT:
+ case FILE_DIFFERENT_CONTENT:
return L"!=";
case FILE_EQUAL:
case FILE_DIFFERENT_METADATA: //= sub-category of equal!
@@ -338,7 +339,7 @@ FilterConfig mergeFilterConfig(const FilterConfig& global, const FilterConfig& l
FilterConfig out = local;
//hard filter
- if (out.includeFilter == FilterConfig().includeFilter)
+ if (NameFilter::isNull(out.includeFilter, Zstring())) //fancy way of checking for "*" include
out.includeFilter = global.includeFilter;
//else: if both global and local include filter contain data, only local filter is preserved
diff --git a/FreeFileSync/Source/structures.h b/FreeFileSync/Source/structures.h
index d7844697..8b183375 100644
--- a/FreeFileSync/Source/structures.h
+++ b/FreeFileSync/Source/structures.h
@@ -44,7 +44,7 @@ enum CompareFilesResult
FILE_RIGHT_SIDE_ONLY,
FILE_LEFT_NEWER, //CMP_BY_TIME_SIZE only!
FILE_RIGHT_NEWER, //
- FILE_DIFFERENT, //CMP_BY_CONTENT only!
+ FILE_DIFFERENT_CONTENT, //CMP_BY_CONTENT only!
FILE_DIFFERENT_METADATA, //both sides equal, but different metadata only: short name case, modification time
FILE_CONFLICT
};
@@ -54,7 +54,8 @@ 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_DIFFERENT_METADATA = FILE_DIFFERENT_METADATA, //both sides equal, but different metadata only: short name case
+ DIR_CONFLICT = FILE_CONFLICT
};
enum CompareSymlinkResult
@@ -64,7 +65,7 @@ enum CompareSymlinkResult
SYMLINK_RIGHT_SIDE_ONLY = FILE_RIGHT_SIDE_ONLY,
SYMLINK_LEFT_NEWER = FILE_LEFT_NEWER,
SYMLINK_RIGHT_NEWER = FILE_RIGHT_NEWER,
- SYMLINK_DIFFERENT = FILE_DIFFERENT,
+ 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
};
@@ -91,8 +92,8 @@ enum SyncOperation
SO_COPY_METADATA_TO_LEFT, //objects are already equal: transfer metadata only - optimization
SO_COPY_METADATA_TO_RIGHT, //
- SO_DO_NOTHING, //= both sides differ, but nothing will be synced
- SO_EQUAL, //= both sides are equal, so nothing will be synced
+ SO_DO_NOTHING, //nothing will be synced: both sides differ
+ SO_EQUAL, //nothing will be synced: both sides are equal
SO_UNRESOLVED_CONFLICT
};
diff --git a/FreeFileSync/Source/synchronization.cpp b/FreeFileSync/Source/synchronization.cpp
index c3abfa25..c6d8df33 100644
--- a/FreeFileSync/Source/synchronization.cpp
+++ b/FreeFileSync/Source/synchronization.cpp
@@ -232,16 +232,16 @@ void SyncStatistics::processDir(const DirPair& dirObj)
conflictMsgs.emplace_back(dirObj.getPairRelativePath(), dirObj.getSyncOpConflict());
break;
+ case SO_OVERWRITE_LEFT:
case SO_COPY_METADATA_TO_LEFT:
++updateLeft;
break;
+ case SO_OVERWRITE_RIGHT:
case SO_COPY_METADATA_TO_RIGHT:
++updateRight;
break;
- case SO_OVERWRITE_LEFT:
- case SO_OVERWRITE_RIGHT:
case SO_MOVE_LEFT_SOURCE:
case SO_MOVE_RIGHT_SOURCE:
case SO_MOVE_LEFT_TARGET:
@@ -1086,7 +1086,7 @@ void SynchronizeFolderPair::runZeroPass(HierarchyObject& hierObj)
this->manageFileMove<LEFT_SIDE>(*sourceObj, *targetObj); //throw FileError
else
this->manageFileMove<RIGHT_SIDE>(*sourceObj, *targetObj); //
- }, procCallback_);
+ }, procCallback_); //throw X?
if (errMsg)
{
@@ -1220,12 +1220,12 @@ SynchronizeFolderPair::PassId SynchronizeFolderPair::getPass(const DirPair& dirO
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:
return PASS_TWO;
- case SO_OVERWRITE_LEFT:
- case SO_OVERWRITE_RIGHT:
case SO_MOVE_LEFT_SOURCE:
case SO_MOVE_RIGHT_SOURCE:
case SO_MOVE_LEFT_TARGET:
@@ -1247,18 +1247,18 @@ void SynchronizeFolderPair::runPass(HierarchyObject& hierObj)
//synchronize files:
for (FilePair& fileObj : hierObj.refSubFiles())
if (pass == this->getPass(fileObj)) //"this->" required by two-pass lookup as enforced by GCC 4.7
- tryReportingError([&] { synchronizeFile(fileObj); }, procCallback_);
+ tryReportingError([&] { synchronizeFile(fileObj); }, procCallback_); //throw X?
//synchronize symbolic links:
for (SymlinkPair& linkObj : hierObj.refSubLinks())
if (pass == this->getPass(linkObj))
- tryReportingError([&] { synchronizeLink(linkObj); }, procCallback_);
+ tryReportingError([&] { synchronizeLink(linkObj); }, procCallback_); //throw X?
//synchronize folders:
for (DirPair& dirObj : hierObj.refSubDirs())
{
if (pass == this->getPass(dirObj))
- tryReportingError([&] { synchronizeFolder(dirObj); }, procCallback_);
+ tryReportingError([&] { synchronizeFolder(dirObj); }, procCallback_); //throw X?
this->runPass<pass>(dirObj); //recurse
}
@@ -1377,6 +1377,8 @@ void SynchronizeFolderPair::synchronizeFileInt(FilePair& fileObj, SyncOperation
getDelHandling<sideTrg>().removeFileWithCallback(fileObj.getFullPath<sideTrg>(), fileObj.getPairRelativePath(), onNotifyItemDeletion, onNotifyFileCopy); //throw FileError
+ warn_static("what if item not found? still an error if base dir is missing; externally deleted otherwise!")
+
fileObj.removeObject<sideTrg>(); //update FilePair
statReporter.reportFinished();
@@ -1730,6 +1732,8 @@ void SynchronizeFolderPair::synchronizeFolderInt(DirPair& dirObj, SyncOperation
}
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:
reportInfo(txtWritingAttributes, dirObj.getFullPath<sideTrg>());
@@ -1745,8 +1749,6 @@ void SynchronizeFolderPair::synchronizeFolderInt(DirPair& dirObj, SyncOperation
procCallback_.updateProcessedData(1, 0);
break;
- case SO_OVERWRITE_LEFT:
- case SO_OVERWRITE_RIGHT:
case SO_MOVE_LEFT_SOURCE:
case SO_MOVE_RIGHT_SOURCE:
case SO_MOVE_LEFT_TARGET:
@@ -1905,7 +1907,7 @@ bool createBaseDirectory(BaseDirPair& baseDirObj, ProcessCallback& callback) //n
// 2. deletion handling: versioning -> "
// 3. log file creates containing folder -> no, log only created in batch mode, and only *before* comparison
}
- }, callback); //may throw in error-callback!
+ }, callback); //throw X?
return !errMsg && !temporaryNetworkDrop;
}
@@ -1987,7 +1989,7 @@ void zen::synchronize(const TimeComp& timeStamp,
{
if (!dirExistsUpdating(baseDirPf, false, callback))
throw FileError(replaceCpy(_("Cannot find folder %x."), L"%x", fmtFileName(baseDirPf))); //should be logged as a "fatal error" if ignored by the user...
- }, callback)) //may throw in error-callback!
+ }, callback)) //throw X?
return true;
return false;
@@ -2181,7 +2183,7 @@ void zen::synchronize(const TimeComp& timeStamp,
tryReportingError([&]
{
recExists = recycleBinExists(baseDirPf, [&] { callback.requestUiRefresh(); /*may throw*/ }); //throw FileError
- }, callback); //show error dialog if necessary
+ }, callback); //throw X?
baseDirHasRecycler[baseDirPf] = recExists;
}
@@ -2328,7 +2330,7 @@ void zen::synchronize(const TimeComp& timeStamp,
!j->getBaseDirPf<RIGHT_SIDE>().empty() && //
supportsPermissions(beforeLast(j->getBaseDirPf<LEFT_SIDE >(), FILE_NAME_SEPARATOR)) && //throw FileError
supportsPermissions(beforeLast(j->getBaseDirPf<RIGHT_SIDE>(), FILE_NAME_SEPARATOR));
- }, callback); //show error dialog if necessary
+ }, callback); //throw X?
auto getEffectiveDeletionPolicy = [&](const Zstring& baseDirPf) -> DeletionPolicy
@@ -2369,8 +2371,8 @@ void zen::synchronize(const TimeComp& timeStamp,
syncFP.startSync(*j);
//(try to gracefully) cleanup temporary Recycle bin folders and versioning -> will be done in ~DeletionHandling anyway...
- tryReportingError([&] { delHandlerL.tryCleanup(); /*throw FileError*/}, callback); //show error dialog if necessary
- tryReportingError([&] { delHandlerR.tryCleanup(); /*throw FileError*/}, callback); //
+ tryReportingError([&] { delHandlerL.tryCleanup(); /*throw FileError*/}, callback); //throw X?
+ tryReportingError([&] { delHandlerR.tryCleanup(); /*throw FileError*/}, callback); //throw X?
}
//(try to gracefully) write database file
@@ -2379,7 +2381,7 @@ void zen::synchronize(const TimeComp& timeStamp,
callback.reportStatus(_("Generating database..."));
callback.forceUiRefresh();
- tryReportingError([&] { zen::saveLastSynchronousState(*j); /*throw FileError*/ }, callback);
+ tryReportingError([&] { zen::saveLastSynchronousState(*j); /*throw FileError*/ }, callback); //throw X?
guardUpdateDb.dismiss();
}
}
diff --git a/FreeFileSync/Source/ui/batch_status_handler.cpp b/FreeFileSync/Source/ui/batch_status_handler.cpp
index 84f996e1..e4726ead 100644
--- a/FreeFileSync/Source/ui/batch_status_handler.cpp
+++ b/FreeFileSync/Source/ui/batch_status_handler.cpp
@@ -63,10 +63,10 @@ private:
};
-void limitLogfileCount(const Zstring& logdir, const std::wstring& jobname, size_t maxCount, const std::function<void()>& onUpdateStatus) //throw()
+void limitLogfileCount(const Zstring& logdir, const std::wstring& jobname, size_t maxCount, const std::function<void()>& onUpdateStatus) //noexcept
{
std::vector<Zstring> logFiles;
- FindLogfiles traverseCallback(utfCvrtTo<Zstring>(jobname), logFiles, onUpdateStatus); //throw()!
+ FindLogfiles traverseCallback(utfCvrtTo<Zstring>(jobname), logFiles, onUpdateStatus); //noexcept
traverseFolder(logdir, traverseCallback);
if (logFiles.size() <= maxCount)
@@ -142,19 +142,21 @@ BatchStatusHandler::BatchStatusHandler(bool showProgress,
jobName_(jobName),
startTime_(wxGetUTCTimeMillis().GetValue())
{
+ //ATTENTION: "progressDlg" is an unmanaged resource!!! Anyway, at this point we already consider construction complete! =>
+ ScopeGuard guardConstructor = zen::makeGuard([&] { this->~BatchStatusHandler(); });
+
if (logfilesCountLimit != 0)
{
zen::Opt<std::wstring> errMsg = tryReportingError([&] { logFile = prepareNewLogfile(logfileDirectory, jobName, timeStamp); }, //throw FileError; return value always bound!
- *this);
+ *this); //throw X?
if (errMsg)
- {
- raiseReturnCode(returnCode_, FFS_RC_ABORTED);
- throw BatchAbortProcess();
- }
+ abortProcessNow(); //throw BatchAbortProcess
}
//if (logFile)
// ::wxSetEnv(L"logfile", utfCvrtTo<wxString>(logFile->getFilename()));
+
+ guardConstructor.dismiss();
}
@@ -188,9 +190,9 @@ BatchStatusHandler::~BatchStatusHandler()
else
try
{
- //use EXEC_TYPE_ASYNC until there is reason no to: https://sourceforge.net/p/freefilesync/discussion/help/thread/828dca52
- tryReportingError([&] { shellExecute(expandMacros(finalCommand), EXEC_TYPE_ASYNC); }, //throw FileError, throw X?
- *this);
+ //use EXEC_TYPE_ASYNC until there is reason not to: https://sourceforge.net/p/freefilesync/discussion/help/thread/828dca52
+ tryReportingError([&] { shellExecute(expandMacros(finalCommand), EXEC_TYPE_ASYNC); }, //throw FileError
+ *this); //throw X?
}
catch (...) {}
}
@@ -241,13 +243,13 @@ BatchStatusHandler::~BatchStatusHandler()
};
//----------------- write results into user-specified logfile ------------------------
- if (logFile.get())
+ if (logFile.get()) //can be null if BatchStatusHandler constructor throws!
{
if (logfilesCountLimit_ > 0)
{
try { reportStatus(_("Cleaning up old log files...")); }
catch (...) {}
- limitLogfileCount(beforeLast(logFile->getFilename(), FILE_NAME_SEPARATOR), jobName_, logfilesCountLimit_, [&] { try { requestUiRefresh(); } catch (...) {} }); //throw()
+ limitLogfileCount(beforeLast(logFile->getFilename(), FILE_NAME_SEPARATOR), jobName_, logfilesCountLimit_, [&] { try { requestUiRefresh(); } catch (...) {} }); //noexcept
}
try
diff --git a/FreeFileSync/Source/ui/custom_grid.cpp b/FreeFileSync/Source/ui/custom_grid.cpp
index f03659e9..a2130a82 100644
--- a/FreeFileSync/Source/ui/custom_grid.cpp
+++ b/FreeFileSync/Source/ui/custom_grid.cpp
@@ -1164,7 +1164,7 @@ private:
case FILE_RIGHT_NEWER:
return COLOR_SYNC_GREEN; //COLOR_CMP_GREEN;
- case FILE_DIFFERENT:
+ case FILE_DIFFERENT_CONTENT:
return COLOR_CMP_RED;
case FILE_EQUAL:
break; //usually white
@@ -1297,7 +1297,7 @@ private:
return L"cat_left_newer";
case FILE_RIGHT_NEWER:
return L"cat_right_newer";
- case FILE_DIFFERENT:
+ case FILE_DIFFERENT_CONTENT:
return L"cat_different";
case FILE_EQUAL:
case FILE_DIFFERENT_METADATA: //= sub-category of equal
@@ -1860,7 +1860,7 @@ wxBitmap zen::getCmpResultImage(CompareFilesResult cmpResult)
return getResourceImage(L"cat_left_newer_small");
case FILE_RIGHT_NEWER:
return getResourceImage(L"cat_right_newer_small");
- case FILE_DIFFERENT:
+ case FILE_DIFFERENT_CONTENT:
return getResourceImage(L"cat_different_small");
case FILE_EQUAL:
case FILE_DIFFERENT_METADATA: //= sub-category of equal
diff --git a/FreeFileSync/Source/ui/folder_history_box.cpp b/FreeFileSync/Source/ui/folder_history_box.cpp
index 95534a52..133dea86 100644
--- a/FreeFileSync/Source/ui/folder_history_box.cpp
+++ b/FreeFileSync/Source/ui/folder_history_box.cpp
@@ -46,7 +46,7 @@ FolderHistoryBox::FolderHistoryBox(wxWindow* parent,
we can't attach to wxEVT_COMMAND_TEXT_UPDATED, since setValueAndUpdateList() will implicitly emit wxEVT_COMMAND_TEXT_UPDATED again when calling Clear()!
=> Crash on Suse/X11/wxWidgets 2.9.4 on startup (setting a flag to guard against recursion does not work, still crash)
- On OS attaching to wxEVT_LEFT_DOWN leads to occasional crashes, especially when double-clicking
+ On OS X attaching to wxEVT_LEFT_DOWN leads to occasional crashes, especially when double-clicking
*/
#endif
@@ -110,10 +110,11 @@ void FolderHistoryBox::setValueAndUpdateList(const wxString& dirpath)
void FolderHistoryBox::OnKeyEvent(wxKeyEvent& event)
{
const int keyCode = event.GetKeyCode();
- if (keyCode == WXK_DELETE || keyCode == WXK_NUMPAD_DELETE)
+ if (keyCode == WXK_DELETE ||
+ keyCode == WXK_NUMPAD_DELETE)
{
//try to delete the currently selected config history item
- int pos = this->GetCurrentSelection();
+ const int pos = this->GetCurrentSelection();
if (0 <= pos && pos < static_cast<int>(this->GetCount()) &&
//what a mess...:
(GetValue() != GetString(pos) || //avoid problems when a character shall be deleted instead of list item
@@ -126,14 +127,10 @@ void FolderHistoryBox::OnKeyEvent(wxKeyEvent& event)
//delete selected row
if (sharedHistory_.get())
sharedHistory_->delItem(toZ(GetString(pos)));
- SetString(pos, wxString()); //in contrast to Delete(), this one does not kill the drop-down list and gives a nice visual feedback!
- //Delete(pos);
+ SetString(pos, wxString()); //in contrast to "Delete(pos)", this one does not kill the drop-down list and gives a nice visual feedback!
- //(re-)set value
this->SetValue(currentVal);
-
- //eat up key event
- return;
+ return; //eat up key event
}
}
event.Skip();
diff --git a/FreeFileSync/Source/ui/folder_history_box.h b/FreeFileSync/Source/ui/folder_history_box.h
index c008f724..806c83fa 100644
--- a/FreeFileSync/Source/ui/folder_history_box.h
+++ b/FreeFileSync/Source/ui/folder_history_box.h
@@ -50,7 +50,7 @@ public:
dirpaths_.resize(maxSize_);
}
- void delItem(const Zstring& dirpath) { zen::vector_remove_if(dirpaths_, [&](const Zstring& entry) { return ::EqualFilename()(entry, dirpath); }); }
+ void delItem(const Zstring& dirpath) { zen::vector_remove_if(dirpaths_, [&](const Zstring& item) { return ::EqualFilename()(item, dirpath); }); }
private:
size_t maxSize_;
diff --git a/FreeFileSync/Source/ui/grid_view.cpp b/FreeFileSync/Source/ui/grid_view.cpp
index 491808cb..2e0e6e52 100644
--- a/FreeFileSync/Source/ui/grid_view.cpp
+++ b/FreeFileSync/Source/ui/grid_view.cpp
@@ -162,7 +162,7 @@ GridView::StatusCmpResult GridView::updateCmpResult(bool showExcluded, //maps so
output.existsRightNewer = true;
if (!rightNewerFilesActive) return false;
break;
- case FILE_DIFFERENT:
+ case FILE_DIFFERENT_CONTENT:
output.existsDifferent = true;
if (!differentFilesActive) return false;
break;
diff --git a/FreeFileSync/Source/ui/gui_status_handler.cpp b/FreeFileSync/Source/ui/gui_status_handler.cpp
index ece926a3..f8fa0701 100644
--- a/FreeFileSync/Source/ui/gui_status_handler.cpp
+++ b/FreeFileSync/Source/ui/gui_status_handler.cpp
@@ -271,8 +271,8 @@ SyncStatusHandler::~SyncStatusHandler()
try
{
//use EXEC_TYPE_ASYNC until there is reason not to: https://sourceforge.net/p/freefilesync/discussion/help/thread/828dca52
- tryReportingError([&] { shellExecute(expandMacros(finalCommand), EXEC_TYPE_ASYNC); }, //throw FileError, throw X?
- *this);
+ tryReportingError([&] { shellExecute(expandMacros(finalCommand), EXEC_TYPE_ASYNC); }, //throw FileError
+ *this); //throw X?
}
catch (...) {}
}
diff --git a/FreeFileSync/Source/ui/main_dlg.cpp b/FreeFileSync/Source/ui/main_dlg.cpp
index 57470db7..ec1d0edf 100644
--- a/FreeFileSync/Source/ui/main_dlg.cpp
+++ b/FreeFileSync/Source/ui/main_dlg.cpp
@@ -537,21 +537,7 @@ MainDialog::MainDialog(const Zstring& globalConfigFile,
auiMgr.AddPane(m_panelCenter,
wxAuiPaneInfo().Name(L"PanelCenter").CenterPane().PaneBorder(false));
- auiMgr.AddPane(m_panelDirectoryPairs,
- wxAuiPaneInfo().Name(L"PanelFolders").Layer(2).Top().Caption(_("Folder Pairs")).CaptionVisible(false).PaneBorder(false).Gripper());
-
- auiMgr.AddPane(m_panelSearch,
- wxAuiPaneInfo().Name(L"PanelFind").Layer(2).Bottom().Row(2).Caption(_("Find")).CaptionVisible(false).PaneBorder(false).Gripper().MinSize(200, m_bpButtonHideSearch->GetSize().GetHeight()).Hide());
-
- auiMgr.AddPane(m_panelViewFilter,
- wxAuiPaneInfo().Name(L"PanelView").Layer(2).Bottom().Row(1).Caption(_("View Settings")).CaptionVisible(false).PaneBorder(false).Gripper().MinSize(m_bpButtonViewTypeSyncAction->GetSize().GetWidth(), m_panelViewFilter->GetSize().GetHeight()));
-
- auiMgr.AddPane(m_panelConfig,
- wxAuiPaneInfo().Name(L"PanelConfig").Layer(3).Left().Position(1).Caption(_("Configuration")).MinSize(m_listBoxHistory->GetSize().GetWidth(), m_panelConfig->GetSize().GetHeight()));
-
- auiMgr.AddPane(m_gridNavi,
- wxAuiPaneInfo().Name(L"PanelOverview").Layer(3).Left().Position(2).Caption(_("Overview")).MinSize(300, m_gridNavi->GetSize().GetHeight())); //MinSize(): just default size, see comment below
-
+ {
//set comparison button label tentatively for m_panelTopButtons to receive final height:
updateTopButton(*m_buttonCompare, getResourceImage(L"compare"), L"Dummy", false);
m_panelTopButtons->GetSizer()->SetSizeHints(m_panelTopButtons); //~=Fit() + SetMinSize()
@@ -561,11 +547,27 @@ MainDialog::MainDialog(const Zstring& globalConfigFile,
std::max(m_buttonCancel->GetSize().y, m_buttonCompare->GetSize().y)));
auiMgr.AddPane(m_panelTopButtons,
- wxAuiPaneInfo().Name(L"PanelTop").Layer(4).Top().Row(1).Caption(_("Main Bar")).CaptionVisible(false).PaneBorder(false).Gripper().MinSize(TOP_BUTTON_OPTIMAL_WIDTH, m_panelTopButtons->GetSize().GetHeight()));
+ wxAuiPaneInfo().Name(L"PanelTop").Layer(2).Top().Row(1).Caption(_("Main Bar")).CaptionVisible(false).PaneBorder(false).Gripper().MinSize(TOP_BUTTON_OPTIMAL_WIDTH, m_panelTopButtons->GetSize().GetHeight()));
//note: min height is calculated incorrectly by wxAuiManager if panes with and without caption are in the same row => use smaller min-size
auiMgr.AddPane(compareStatus->getAsWindow(),
- wxAuiPaneInfo().Name(L"PanelProgress").Layer(4).Top().Row(2).CaptionVisible(false).PaneBorder(false).Hide());
+ wxAuiPaneInfo().Name(L"PanelProgress").Layer(2).Top().Row(2).CaptionVisible(false).PaneBorder(false).Hide());
+ }
+
+ auiMgr.AddPane(m_panelDirectoryPairs,
+ wxAuiPaneInfo().Name(L"PanelFolders").Layer(2).Top().Row(3).Caption(_("Folder Pairs")).CaptionVisible(false).PaneBorder(false).Gripper());
+
+ auiMgr.AddPane(m_panelSearch,
+ wxAuiPaneInfo().Name(L"PanelFind").Layer(2).Bottom().Row(2).Caption(_("Find")).CaptionVisible(false).PaneBorder(false).Gripper().MinSize(200, m_bpButtonHideSearch->GetSize().GetHeight()).Hide());
+
+ auiMgr.AddPane(m_panelViewFilter,
+ wxAuiPaneInfo().Name(L"PanelView").Layer(2).Bottom().Row(1).Caption(_("View Settings")).CaptionVisible(false).PaneBorder(false).Gripper().MinSize(m_bpButtonViewTypeSyncAction->GetSize().GetWidth(), m_panelViewFilter->GetSize().GetHeight()));
+
+ auiMgr.AddPane(m_panelConfig,
+ wxAuiPaneInfo().Name(L"PanelConfig").Layer(3).Left().Position(1).Caption(_("Configuration")).MinSize(m_listBoxHistory->GetSize().GetWidth(), m_panelConfig->GetSize().GetHeight()));
+
+ auiMgr.AddPane(m_gridNavi,
+ wxAuiPaneInfo().Name(L"PanelOverview").Layer(3).Left().Position(2).Caption(_("Overview")).MinSize(300, m_gridNavi->GetSize().GetHeight())); //MinSize(): just default size, see comment below
auiMgr.Update();
@@ -1697,11 +1699,8 @@ void MainDialog::OnResizeLeftFolderWidth(wxEvent& event)
{
//adapt left-shift display distortion caused by scrollbars for multiple folder pairs
const int width = m_panelTopLeft->GetSize().GetWidth();
- std::for_each(additionalFolderPairs.begin(), additionalFolderPairs.end(),
- [&](FolderPairPanel* panel)
- {
+ for (FolderPairPanel* panel : additionalFolderPairs)
panel->m_panelLeft->SetMinSize(wxSize(width, -1));
- });
event.Skip();
}
@@ -1727,8 +1726,8 @@ void MainDialog::onTreeButtonEvent(wxKeyEvent& event)
{
case 'C':
case WXK_INSERT: //CTRL + C || CTRL + INS
- copySelectionToClipboard({ m_gridNavi });
- return;
+ copySelectionToClipboard({ m_gridNavi });
+ return;
}
else if (event.AltDown())
switch (keyCode)
@@ -1808,8 +1807,8 @@ void MainDialog::onGridButtonEvent(wxKeyEvent& event, Grid& grid, bool leftSide)
{
case 'C':
case WXK_INSERT: //CTRL + C || CTRL + INS
- copySelectionToClipboard({ m_gridMainL, m_gridMainR} );
- return; // -> swallow event! don't allow default grid commands!
+ copySelectionToClipboard({ m_gridMainL, m_gridMainR} );
+ return; // -> swallow event! don't allow default grid commands!
}
else if (event.AltDown())
@@ -2070,13 +2069,13 @@ void MainDialog::onNaviGridContext(GridClickEvent& event)
//by short name
Zstring labelShort = Zstring(Zstr("*")) + FILE_NAME_SEPARATOR + selection[0]->getPairShortName();
if (isDir)
- labelShort += FILE_NAME_SEPARATOR + Zstring(Zstr("*"));
+ labelShort += FILE_NAME_SEPARATOR;
submenu.addItem(utfCvrtTo<wxString>(labelShort), [this, &selection, include] { filterShortname(*selection[0], include); });
//by relative path
Zstring labelRel = FILE_NAME_SEPARATOR + selection[0]->getPairRelativePath();
if (isDir)
- labelRel += FILE_NAME_SEPARATOR + Zstring(Zstr("*"));
+ labelRel += FILE_NAME_SEPARATOR;
submenu.addItem(utfCvrtTo<wxString>(labelRel), [this, &selection, include] { filterItems(selection, include); });
menu.addSubmenu(label, submenu, &getResourceImage(iconName));
@@ -2198,13 +2197,13 @@ void MainDialog::onMainGridContextRim(bool leftSide)
//by short name
Zstring labelShort = Zstring(Zstr("*")) + FILE_NAME_SEPARATOR + selection[0]->getPairShortName();
if (isDir)
- labelShort += FILE_NAME_SEPARATOR + Zstring(Zstr("*"));
+ labelShort += FILE_NAME_SEPARATOR;
submenu.addItem(utfCvrtTo<wxString>(labelShort), [this, &selection, include] { filterShortname(*selection[0], include); });
//by relative path
Zstring labelRel = FILE_NAME_SEPARATOR + selection[0]->getPairRelativePath();
if (isDir)
- labelRel += FILE_NAME_SEPARATOR + Zstring(Zstr("*"));
+ labelRel += FILE_NAME_SEPARATOR;
submenu.addItem(utfCvrtTo<wxString>(labelRel), [this, &selection, include] { filterItems(selection, include); });
menu.addSubmenu(label, submenu, &getResourceImage(iconName));
@@ -2278,7 +2277,7 @@ void MainDialog::filterPhrase(const Zstring& phrase, bool include, bool addNewLi
if (include)
{
Zstring& includeFilter = currentCfg.mainCfg.globalFilter.includeFilter;
- if (NameFilter::isNull(includeFilter, FilterConfig().excludeFilter)) //fancy way of checking for "*" include
+ if (NameFilter::isNull(includeFilter, Zstring())) //fancy way of checking for "*" include
includeFilter.clear();
return includeFilter;
}
@@ -2322,7 +2321,7 @@ void MainDialog::filterShortname(const FileSystemObject& fsObj, bool include)
Zstring phrase = Zstring(Zstr("*")) + FILE_NAME_SEPARATOR + fsObj.getPairShortName();
const bool isDir = dynamic_cast<const DirPair*>(&fsObj) != nullptr;
if (isDir)
- phrase += FILE_NAME_SEPARATOR + Zstring(Zstr("*")); //include filter: * required; exclude filter: * optional, but let's still apply it!
+ phrase += FILE_NAME_SEPARATOR;
filterPhrase(phrase, include, true);
}
@@ -2345,7 +2344,7 @@ void MainDialog::filterItems(const std::vector<FileSystemObject*>& selection, bo
const bool isDir = dynamic_cast<const DirPair*>(fsObj) != nullptr;
if (isDir)
- phrase += FILE_NAME_SEPARATOR + Zstring(Zstr("*")); //include filter: * required; exclude filter: * optional, but let's still apply it!
+ phrase += FILE_NAME_SEPARATOR;
}
filterPhrase(phrase, include, true);
}
@@ -2699,7 +2698,6 @@ void MainDialog::updateUnsavedCfgStatus()
brighten(img, 80);
return img;
};
- //setImage(*m_bpButtonSave, greyScale(getResourceImage(L"save")));
setImage(*m_bpButtonSave, allowSave ? getResourceImage(L"save") : makeBrightGrey(getResourceImage(L"save")));
m_bpButtonSave->Enable(allowSave);
diff --git a/FreeFileSync/Source/ui/on_completion_box.cpp b/FreeFileSync/Source/ui/on_completion_box.cpp
index b489a051..b86727bc 100644
--- a/FreeFileSync/Source/ui/on_completion_box.cpp
+++ b/FreeFileSync/Source/ui/on_completion_box.cpp
@@ -33,30 +33,27 @@ std::vector<std::pair<std::wstring, Zstring>> getDefaultCommands() //(gui name/c
#ifdef ZEN_WIN
if (zen::vistaOrLater())
{
- addEntry(_("Standby" ), Zstr("rundll32.exe powrprof.dll,SetSuspendState Sleep")); //suspend/Suspend to RAM/sleep
addEntry(_("Log off" ), Zstr("shutdown /l"));
+ addEntry(_("Standby" ), Zstr("rundll32.exe powrprof.dll,SetSuspendState Sleep")); //suspend/Suspend to RAM/sleep
addEntry(_("Shut down"), Zstr("shutdown /s /t 60"));
- //addEntry(_"Hibernate", L"shutdown /h"); //Suspend to disk -> Standby is better anyway
}
else //XP
{
- addEntry(_("Standby" ), Zstr("rundll32.exe powrprof.dll,SetSuspendState")); //this triggers standby OR hibernate, depending on whether hibernate setting is active!
addEntry(_("Log off" ), Zstr("shutdown -l"));
+ addEntry(_("Standby" ), Zstr("rundll32.exe powrprof.dll,SetSuspendState")); //this triggers standby OR hibernate, depending on whether hibernate setting is active!
addEntry(_("Shut down"), Zstr("shutdown -s -t 60"));
//no suspend on XP?
}
#elif defined ZEN_LINUX
- addEntry(_("Standby" ), Zstr("sudo pm-suspend"));
addEntry(_("Log off" ), Zstr("gnome-session-quit")); //alternative requiring admin: sudo killall Xorg
+ addEntry(_("Standby" ), Zstr("sudo pm-suspend"));
addEntry(_("Shut down"), Zstr("dbus-send --print-reply --dest=org.gnome.SessionManager /org/gnome/SessionManager org.gnome.SessionManager.RequestShutdown"));
//alternative requiring admin: sudo shutdown -h 1
- //addEntry(_("Hibernate"), L"sudo pm-hibernate");
- //alternative: "pmi action suspend" and "pmi action hibernate", require "sudo apt-get install powermanagement-interaface"
#elif defined ZEN_MAC
- addEntry(_("Standby" ), Zstr("osascript -e \'tell application \"System Events\" to sleep\'"));
addEntry(_("Log off" ), Zstr("osascript -e \'tell application \"System Events\" to log out\'"));
+ addEntry(_("Standby" ), Zstr("osascript -e \'tell application \"System Events\" to sleep\'"));
addEntry(_("Shut down"), Zstr("osascript -e \'tell application \"System Events\" to shut down\'"));
#endif
return output;
diff --git a/FreeFileSync/Source/ui/progress_indicator.cpp b/FreeFileSync/Source/ui/progress_indicator.cpp
index e3595ccd..1d461050 100644
--- a/FreeFileSync/Source/ui/progress_indicator.cpp
+++ b/FreeFileSync/Source/ui/progress_indicator.cpp
@@ -1417,7 +1417,7 @@ void SyncProgressDialogImpl<TopLevelDialog>::notifyProgressChange() //noexcept!
switch (syncStat_->currentPhase())
{
case ProcessCallback::PHASE_NONE:
- assert(false);
+ //assert(false); -> can happen: e.g. batch run, log file creation failed, throw in BatchStatusHandler constructor
case ProcessCallback::PHASE_SCANNING:
break;
case ProcessCallback::PHASE_COMPARING_CONTENT:
@@ -1446,7 +1446,7 @@ enum Zorder
Zorder evaluateZorder(const wxWindow& top, const wxWindow& bottom)
{
- HWND hTop = static_cast<HWND>(top.GetHWND());
+ HWND hTop = static_cast<HWND>(top .GetHWND());
HWND hBottom = static_cast<HWND>(bottom.GetHWND());
assert(hTop && hBottom);
@@ -1655,7 +1655,7 @@ void SyncProgressDialogImpl<TopLevelDialog>::updateGuiInt(bool allowYield)
#ifdef ZEN_WIN
//workaround Windows 7 bug messing up z-order after temporary application hangs: https://sourceforge.net/tracker/index.php?func=detail&aid=3376523&group_id=234430&atid=1093080
- //This is still needed no matter if wxDialog or wxPanel is used! (2013-07)
+ //2013-07: This is still needed no matter if wxDialog or wxPanel is used!
if (parentFrame_)
if (evaluateZorder(*this, *parentFrame_) == ZORDER_WRONG)
{
@@ -1664,6 +1664,7 @@ void SyncProgressDialogImpl<TopLevelDialog>::updateGuiInt(bool allowYield)
{
::ShowWindow(hProgress, SW_HIDE); //make Windows recalculate z-order
::ShowWindow(hProgress, SW_SHOW); //
+ //::BringWindowToTop(hProgress); -> untested: better alternative?
}
}
#endif
@@ -1690,7 +1691,7 @@ void SyncProgressDialogImpl<TopLevelDialog>::updateGuiInt(bool allowYield)
//*first* refresh GUI (removing flicker) before sleeping!
boost::this_thread::sleep(boost::posix_time::milliseconds(UI_UPDATE_INTERVAL));
}
- //if SyncProgressDialogImpl::OnClose() already wxWindow::Destroy() on OS X then this instance is already toast!
+ //after SyncProgressDialogImpl::OnClose() called wxWindow::Destroy() on OS X this instance is instantly toast!
if (wereDead)
return; //GTFO and don't call this->resumeTimer()
@@ -2021,7 +2022,7 @@ void SyncProgressDialogImpl<TopLevelDialog>::OnClose(wxCloseEvent& event)
paused_ = false; //[!] we could be pausing here!
//now that we notified window termination prematurely, and since processHasFinished()/closeWindowDirectly() won't be called, make sure we don't call back, too!
- //e.g. the second notifyWindowTerminate_() in ~SyncProgressDialogImpl()!!!
+ //e.g. a second notifyWindowTerminate_() in ~SyncProgressDialogImpl()!!!
syncStat_ = nullptr;
abortCb_ = nullptr;
@@ -2083,8 +2084,8 @@ void SyncProgressDialogImpl<TopLevelDialog>::minimizeToTray()
//hide dock icon: else user is able to forcefully show the hidden main dialog by clicking on the icon!!
ProcessSerialNumber psn = { 0, kCurrentProcess };
::TransformProcessType(&psn, kProcessTransformToUIElementApplication);
- wxTheApp->Yield(); //required to complete TransformProcessType: else a subsequent modal dialog will be erroneously hidden!
- //-> Yield probably not needed here since we continue the event loop afterwards!
+ wxTheApp->Yield(true /*onlyIfNeeded -> avoid recursive yield*/); //required to complete TransformProcessType: else a subsequent modal dialog will be erroneously hidden!
+ //-> Yield not needed here since we continue the event loop afterwards!
#endif
}
}
diff --git a/FreeFileSync/Source/ui/sync_cfg.cpp b/FreeFileSync/Source/ui/sync_cfg.cpp
index fb758aaa..a1d030f7 100644
--- a/FreeFileSync/Source/ui/sync_cfg.cpp
+++ b/FreeFileSync/Source/ui/sync_cfg.cpp
@@ -514,7 +514,7 @@ void ConfigDialog::updateFilterGui()
else
staticBmp.SetBitmap(greyScale(getResourceImage(bmpName)));
};
- setStatusBitmap(*m_bitmapInclude, L"filter_include", !NameFilter::isNull(activeCfg.includeFilter, FilterConfig().excludeFilter));
+ setStatusBitmap(*m_bitmapInclude, L"filter_include", !NameFilter::isNull(activeCfg.includeFilter, Zstring()));
setStatusBitmap(*m_bitmapExclude, L"filter_exclude", !NameFilter::isNull(FilterConfig().includeFilter, activeCfg.excludeFilter));
setStatusBitmap(*m_bitmapFilterDate, L"clock", activeCfg.unitTimeSpan != UTIME_NONE);
setStatusBitmap(*m_bitmapFilterSize, L"size", activeCfg.unitSizeMin != USIZE_NONE || activeCfg.unitSizeMax != USIZE_NONE);
diff --git a/FreeFileSync/Source/ui/tree_view.cpp b/FreeFileSync/Source/ui/tree_view.cpp
index 735d4732..548e736f 100644
--- a/FreeFileSync/Source/ui/tree_view.cpp
+++ b/FreeFileSync/Source/ui/tree_view.cpp
@@ -584,7 +584,7 @@ void TreeView::updateCmpResult(bool showExcluded,
return leftNewerFilesActive;
case FILE_RIGHT_NEWER:
return rightNewerFilesActive;
- case FILE_DIFFERENT:
+ case FILE_DIFFERENT_CONTENT:
return differentFilesActive;
case FILE_EQUAL:
case FILE_DIFFERENT_METADATA: //= sub-category of equal
diff --git a/FreeFileSync/Source/version/version.h b/FreeFileSync/Source/version/version.h
index 9a490759..ab9ec7b2 100644
--- a/FreeFileSync/Source/version/version.h
+++ b/FreeFileSync/Source/version/version.h
@@ -3,7 +3,7 @@
namespace zen
{
-const wchar_t currentVersion[] = L"6.12"; //internal linkage!
+const wchar_t currentVersion[] = L"6.13"; //internal linkage!
}
#endif
diff --git a/FreeFileSync/Source/version/version.iss b/FreeFileSync/Source/version/version.iss
index da4f557e..79ec3c65 100644
--- a/FreeFileSync/Source/version/version.iss
+++ b/FreeFileSync/Source/version/version.iss
@@ -1 +1 @@
-#define FFS_Version "6.12"
+#define FFS_Version "6.13"
diff --git a/zen/file_access.cpp b/zen/file_access.cpp
index 7a77b998..e32a27a7 100644
--- a/zen/file_access.cpp
+++ b/zen/file_access.cpp
@@ -32,6 +32,7 @@
#elif defined ZEN_MAC
#include <sys/mount.h> //statfs
+ #include <copyfile.h>
#endif
#if defined ZEN_LINUX || defined ZEN_MAC
@@ -277,7 +278,7 @@ std::uint64_t zen::getFreeDiskSpace(const Zstring& path) //throw FileError
return get64BitUInt(bytesFree.LowPart, bytesFree.HighPart);
#elif defined ZEN_LINUX || defined ZEN_MAC
- struct statfs info = {};
+ struct ::statfs info = {};
if (::statfs(path.c_str(), &info) != 0)
throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(path)), L"statfs", getLastError());
@@ -561,9 +562,9 @@ public:
HandleLink onSymlink(const Zchar* shortName, const Zstring& linkpath, const SymlinkInfo& details) override
{
if (dirExists(linkpath)) //dir symlink
- dirs_.push_back(shortName);
+ dirs_.push_back(linkpath);
else //file symlink, broken symlink
- files_.push_back(shortName);
+ files_.push_back(linkpath);
return LINK_SKIP;
}
TraverseCallback* onDir(const Zchar* shortName, const Zstring& dirpath) override
@@ -1169,13 +1170,13 @@ void copyObjectPermissions(const Zstring& source, const Zstring& target, ProcSym
throw FileError
*/
-#elif defined ZEN_LINUX || defined ZEN_MAC
+#elif defined ZEN_LINUX
#ifdef HAVE_SELINUX //copy SELinux security context
copySecurityContext(source, target, procSl); //throw FileError
#endif
- struct stat fileInfo = {};
+ struct ::stat fileInfo = {};
if (procSl == ProcSymlink::FOLLOW)
{
if (::stat(source.c_str(), &fileInfo) != 0)
@@ -1195,9 +1196,38 @@ void copyObjectPermissions(const Zstring& source, const Zstring& target, ProcSym
if (::lchown(target.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights!
throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(target)), L"lchown", getLastError());
- if (!symlinkExists(target) && ::chmod(target.c_str(), fileInfo.st_mode) != 0) //setting access permissions doesn't make sense for symlinks on Linux: there is no lchmod()
+ if (!symlinkExists(target) && //setting access permissions doesn't make sense for symlinks on Linux: there is no lchmod()
+ ::chmod(target.c_str(), fileInfo.st_mode) != 0)
throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(target)), L"chmod", getLastError());
}
+
+#elif defined ZEN_MAC
+ copyfile_flags_t flags = COPYFILE_ACL | COPYFILE_STAT; //unfortunately COPYFILE_STAT copies modtime, too!
+ if (procSl == ProcSymlink::DIRECT)
+ flags |= COPYFILE_NOFOLLOW;
+
+ if (::copyfile(source.c_str(), target.c_str(), 0, flags) != 0)
+ throwFileError(replaceCpy(replaceCpy(_("Cannot copy permissions from %x to %y."), L"%x", L"\n" + fmtFileName(source)), L"%y", L"\n" + fmtFileName(target)), L"copyfile", getLastError());
+
+ //owner is *not* copied with ::copyfile():
+
+ struct ::stat fileInfo = {};
+ if (procSl == ProcSymlink::FOLLOW)
+ {
+ if (::stat(source.c_str(), &fileInfo) != 0)
+ throwFileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtFileName(source)), L"stat", getLastError());
+
+ if (::chown(target.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights!
+ throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(target)), L"chown", getLastError());
+ }
+ else
+ {
+ if (::lstat(source.c_str(), &fileInfo) != 0)
+ throwFileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtFileName(source)), L"lstat", getLastError());
+
+ if (::lchown(target.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights!
+ throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(target)), L"lchown", getLastError());
+ }
#endif
}
@@ -1329,7 +1359,18 @@ void zen::makeDirectoryPlain(const Zstring& directory, //throw FileError, ErrorT
}
#elif defined ZEN_LINUX || defined ZEN_MAC
- if (::mkdir(directory.c_str(), 0755) != 0) //mode: drwxr-xr-x
+ mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO; //= default for newly created directory
+
+ struct ::stat dirInfo = {};
+ if (!templateDir.empty())
+ if (::stat(templateDir.c_str(), &dirInfo) == 0)
+ {
+ mode = dirInfo.st_mode; //analog to "cp" which copies "mode" (considering umask) by default
+ mode |= S_IRWXU; //FFS only: we need full access to copy child items! "cp" seems to apply permissions *after* copying child items
+ }
+ //=> need copyObjectPermissions() only for "chown" and umask-agnostic permissions
+
+ if (::mkdir(directory.c_str(), mode) != 0)
{
const int lastError = errno; //copy before directly or indirectly making other system calls!
const std::wstring errorMsg = replaceCpy(_("Cannot create directory %x."), L"%x", fmtFileName(directory));
@@ -1407,6 +1448,9 @@ void zen::makeDirectoryPlain(const Zstring& directory, //throw FileError, ErrorT
}
}
}
+
+#elif defined ZEN_MAC
+ /*int rv =*/ ::copyfile(templateDir.c_str(), directory.c_str(), 0, COPYFILE_XATTR);
#endif
zen::ScopeGuard guardNewDir = zen::makeGuard([&] { try { removeDirectory(directory); } catch (FileError&) {} }); //ensure cleanup:
@@ -1481,6 +1525,11 @@ void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool
setFileTime(targetLink, srcInfo.st_mtime, ProcSymlink::DIRECT); //throw FileError
#endif
+#ifdef ZEN_MAC
+ if (::copyfile(sourceLink.c_str(), targetLink.c_str(), 0, COPYFILE_XATTR | COPYFILE_NOFOLLOW) != 0)
+ throwFileError(replaceCpy(replaceCpy(_("Cannot copy attributes from %x to %y."), L"%x", L"\n" + fmtFileName(sourceLink)), L"%y", L"\n" + fmtFileName(targetLink)), L"copyfile", getLastError());
+#endif
+
if (copyFilePermissions)
copyObjectPermissions(sourceLink, targetLink, ProcSymlink::DIRECT); //throw FileError
@@ -1950,7 +1999,7 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize,
//called after copy operation is finished - note: for 0-sized files this callback is invoked just ONCE!
//if (totalFileSize.QuadPart == totalBytesTransferred.QuadPart && dwStreamNumber == 1) {}
- if (cbd.onUpdateCopyStatus_ && totalBytesTransferred.QuadPart >= 0) //should be always true, but let's still check
+ if (cbd.onUpdateCopyStatus_ && totalBytesTransferred.QuadPart >= 0) //should always be true, but let's still check
try
{
cbd.onUpdateCopyStatus_(totalBytesTransferred.QuadPart - cbd.bytesReported); //throw X!
@@ -2088,10 +2137,10 @@ void copyFileWindowsSelectRoutine(const Zstring& sourceFile, const Zstring& targ
//another layer of indirection solving 8.3 name clashes
inline
-void copyFileWindows(const Zstring& sourceFile,
- const Zstring& targetFile,
- const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus,
- InSyncAttributes* sourceAttr)
+void copyFileOsSpecific(const Zstring& sourceFile,
+ const Zstring& targetFile,
+ const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus,
+ InSyncAttributes* sourceAttr)
{
try
{
@@ -2111,11 +2160,11 @@ void copyFileWindows(const Zstring& sourceFile,
}
-#elif defined ZEN_LINUX || defined ZEN_MAC
-void copyFileLinuxMac(const Zstring& sourceFile,
- const Zstring& targetFile,
- const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus,
- InSyncAttributes* newAttrib) //throw FileError, ErrorTargetExisting
+#elif defined ZEN_LINUX
+void copyFileOsSpecific(const Zstring& sourceFile,
+ const Zstring& targetFile,
+ const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus,
+ InSyncAttributes* newAttrib) //throw FileError, ErrorTargetExisting
{
FileInputUnbuffered fileIn(sourceFile); //throw FileError
@@ -2126,7 +2175,9 @@ void copyFileLinuxMac(const Zstring& sourceFile,
zen::ScopeGuard guardTarget = zen::makeGuard([&] { try { removeFile(targetFile); } catch (FileError&) {} }); //transactional behavior: place guard before lifetime of FileOutput
try
{
- FileOutputUnbuffered fileOut(targetFile, sourceInfo.st_mode); //throw FileError, ErrorTargetExisting
+ FileOutputUnbuffered fileOut(targetFile, //throw FileError, ErrorTargetExisting
+ sourceInfo.st_mode); //analog to "cp" which copies "mode" (considering umask) by default
+ //=> need copyObjectPermissions() only for "chown" and umask-agnostic permissions
std::vector<char> buffer(128 * 1024); //see comment in FileInputUnbuffered::read
do
@@ -2171,36 +2222,161 @@ void copyFileLinuxMac(const Zstring& sourceFile,
guardTarget.dismiss(); //target has been created successfully!
}
+
+
+#elif defined ZEN_MAC
+struct CallbackData
+{
+ CallbackData(const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus,
+ const Zstring& sourceFile,
+ const Zstring& targetFile) :
+ onUpdateCopyStatus_(onUpdateCopyStatus),
+ sourceFile_(sourceFile),
+ targetFile_(targetFile),
+ bytesReported() {}
+
+ const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus_; //in
+ const Zstring& sourceFile_;
+ const Zstring& targetFile_;
+
+ std::pair<std::wstring, std::wstring> errorMsg; //out; these are exclusive!
+ std::exception_ptr exception; //
+
+ std::int64_t bytesReported; //private to callback
+};
+
+
+int copyFileCallback(int what, int stage, copyfile_state_t state, const char* src, const char* dst, void* ctx)
+{
+ CallbackData& cbd = *static_cast<CallbackData*>(ctx);
+
+ off_t bytesCopied = 0;
+ if (::copyfile_state_get(state, COPYFILE_STATE_COPIED, &bytesCopied) != 0)
+ {
+ cbd.errorMsg = std::make_pair(replaceCpy(replaceCpy(_("Cannot copy file %x to %y."), L"%x", L"\n" + fmtFileName(cbd.sourceFile_)), L"%y", L"\n" + fmtFileName(cbd.targetFile_)),
+ formatSystemError(L"copyfile_state_get, COPYFILE_STATE_COPIED", getLastError()));
+ return COPYFILE_QUIT;
+ }
+
+ if (cbd.onUpdateCopyStatus_)
+ try
+ {
+ cbd.onUpdateCopyStatus_(bytesCopied - cbd.bytesReported); //throw X!
+ cbd.bytesReported = bytesCopied;
+ }
+ catch (...)
+ {
+ cbd.exception = std::current_exception();
+ return COPYFILE_QUIT;
+ }
+ return COPYFILE_CONTINUE;
+}
+
+
+void copyFileOsSpecific(const Zstring& sourceFile,
+ const Zstring& targetFile,
+ const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus,
+ InSyncAttributes* newAttrib) //throw FileError, ErrorTargetExisting
+{
+ //http://blog.plasticsfuture.org/2006/03/05/the-state-of-backup-and-cloning-tools-under-mac-os-x/
+
+ auto getCopyErrorMessage = [&] { return replaceCpy(replaceCpy(_("Cannot copy file %x to %y."), L"%x", L"\n" + fmtFileName(sourceFile)), L"%y", L"\n" + fmtFileName(targetFile)); };
+
+ copyfile_state_t copyState = ::copyfile_state_alloc();
+ ZEN_ON_SCOPE_EXIT(::copyfile_state_free(copyState));
+
+ CallbackData cbd(onUpdateCopyStatus, sourceFile, targetFile);
+
+ if (::copyfile_state_set(copyState, COPYFILE_STATE_STATUS_CTX, &cbd) != 0)
+ throwFileError(getCopyErrorMessage(), L"copyfile_state_set, COPYFILE_STATE_STATUS_CTX", getLastError());
+
+ if (::copyfile_state_set(copyState, COPYFILE_STATE_STATUS_CB, reinterpret_cast<const void*>(&copyFileCallback)) != 0)
+ throwFileError(getCopyErrorMessage(), L"copyfile_state_set, COPYFILE_STATE_STATUS_CB", getLastError());
+
+ zen::ScopeGuard guardTarget = zen::makeGuard([&] { try { removeFile(targetFile); } catch (FileError&) {} }); //transactional behavior: docs seem to indicate that copyfile does not clean up
+
+ //http://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/copyfile.3.html
+ if (::copyfile(sourceFile.c_str(), targetFile.c_str(),
+ copyState,
+ COPYFILE_XATTR | COPYFILE_DATA | COPYFILE_EXCL) != 0) //even though we don't use COPYFILE_STAT, "mode" (considering umask) is still copied! => harmonized with Linux file copy!
+ {
+ //evaluate first! errno is not set for COPYFILE_QUIT!
+ if (cbd.exception)
+ std::rethrow_exception(cbd.exception);
+
+ if (!cbd.errorMsg.first.empty())
+ throw FileError(cbd.errorMsg.first, cbd.errorMsg.second);
+
+ const int lastError = errno;
+ std::wstring errorDescr = formatSystemError(L"copyfile", lastError);
+
+ if (lastError == EEXIST)
+ {
+ guardTarget.dismiss(); //don't delete file that existed previously!
+ throw ErrorTargetExisting(getCopyErrorMessage(), errorDescr);
+ }
+
+ throw FileError(getCopyErrorMessage(), errorDescr);
+ }
+
+ int fdSource = 0;
+ if (::copyfile_state_get(copyState, COPYFILE_STATE_SRC_FD, &fdSource) != 0)
+ throwFileError(getCopyErrorMessage(), L"copyfile_state_get, COPYFILE_STATE_SRC_FD", getLastError());
+
+ int fdTarget = 0;
+ if (::copyfile_state_get(copyState, COPYFILE_STATE_DST_FD, &fdTarget) != 0)
+ throwFileError(getCopyErrorMessage(), L"copyfile_state_get, COPYFILE_STATE_DST_FD", getLastError());
+
+ struct ::stat sourceInfo = {};
+ if (::fstat(fdSource, &sourceInfo) != 0)
+ throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(sourceFile)), L"fstat", getLastError());
+
+ struct ::stat targetInfo = {};
+ if (::fstat(fdTarget, &targetInfo) != 0)
+ throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(targetFile)), L"fstat", getLastError());
+
+ struct ::timeval newTimes[2] = {};
+ newTimes[0].tv_sec = sourceInfo.st_atime; //access time (seconds)
+ newTimes[1].tv_sec = sourceInfo.st_mtime; //modification time (seconds)
+
+ if (::futimes(fdTarget, newTimes) != 0)
+ throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(targetFile)), L"futimes", getLastError());
+
+ if (newAttrib)
+ {
+ newAttrib->fileSize = sourceInfo.st_size;
+ newAttrib->modificationTime = sourceInfo.st_mtime;
+ newAttrib->sourceFileId = extractFileId(sourceInfo);
+ newAttrib->targetFileId = extractFileId(targetInfo);
+ }
+
+ guardTarget.dismiss();
+}
#endif
/*
------------------
|File Copy Layers|
------------------
- copyFile (setup transactional behavior)
- |
- copyFileSelectOs
- / \
-copyFileLinuxMac copyFileWindows (solve 8.3 issue)
+ copyFile (setup transactional behavior)
+ |
+ copyFileWithPermissions
|
+ copyFileOsSpecific (solve 8.3 issue)
+ |
copyFileWindowsSelectRoutine
/ \
copyFileWindowsDefault(::CopyFileEx) copyFileWindowsSparse(::BackupRead/::BackupWrite)
*/
inline
-void copyFileSelectOs(const Zstring& sourceFile,
- const Zstring& targetFile,
- bool copyFilePermissions,
- const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus,
- InSyncAttributes* sourceAttr)
+void copyFileWithPermissions(const Zstring& sourceFile,
+ const Zstring& targetFile,
+ bool copyFilePermissions,
+ const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus,
+ InSyncAttributes* sourceAttr)
{
-#ifdef ZEN_WIN
- copyFileWindows(sourceFile, targetFile, onUpdateCopyStatus, sourceAttr); //throw FileError, ErrorTargetExisting, ErrorFileLocked
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
- copyFileLinuxMac(sourceFile, targetFile, onUpdateCopyStatus, sourceAttr); //throw FileError, ErrorTargetExisting
-#endif
+ copyFileOsSpecific(sourceFile, targetFile, onUpdateCopyStatus, sourceAttr); //throw FileError, ErrorTargetExisting, ErrorFileLocked
if (copyFilePermissions)
{
@@ -2230,7 +2406,7 @@ void zen::copyFile(const Zstring& sourceFile, //throw FileError, ErrorFileLocked
for (int i = 0;; ++i)
try
{
- copyFileSelectOs(sourceFile, tmpTarget, copyFilePermissions, onUpdateCopyStatus, sourceAttr); //throw FileError, ErrorTargetExisting, ErrorFileLocked
+ copyFileWithPermissions(sourceFile, tmpTarget, copyFilePermissions, onUpdateCopyStatus, sourceAttr); //throw FileError, ErrorTargetExisting, ErrorFileLocked
break;
}
catch (const ErrorTargetExisting&) //optimistic strategy: assume everything goes well, but recover on error -> minimize file accesses
@@ -2239,7 +2415,7 @@ void zen::copyFile(const Zstring& sourceFile, //throw FileError, ErrorFileLocked
tmpTarget = targetFile + Zchar('_') + numberTo<Zstring>(i) + TEMP_FILE_ENDING;
}
- //transactional behavior: ensure cleanup; not needed before copyFileSelectOs() which is already transactional
+ //transactional behavior: ensure cleanup; not needed before copyFileWithPermissions() which is already transactional
zen::ScopeGuard guardTempFile = zen::makeGuard([&] { try { removeFile(tmpTarget); } catch (FileError&) {} });
//have target file deleted (after read access on source and target has been confirmed) => allow for almost transactional overwrite
@@ -2272,6 +2448,6 @@ void zen::copyFile(const Zstring& sourceFile, //throw FileError, ErrorFileLocked
if (onDeleteTargetFile)
onDeleteTargetFile();
- copyFileSelectOs(sourceFile, targetFile, copyFilePermissions, onUpdateCopyStatus, sourceAttr); //throw FileError, ErrorTargetExisting, ErrorFileLocked
+ copyFileWithPermissions(sourceFile, targetFile, copyFilePermissions, onUpdateCopyStatus, sourceAttr); //throw FileError, ErrorTargetExisting, ErrorFileLocked
}
}
diff --git a/zen/stl_tools.h b/zen/stl_tools.h
index a8f2a9b5..d00fc732 100644
--- a/zen/stl_tools.h
+++ b/zen/stl_tools.h
@@ -19,7 +19,10 @@ template <class V, class Predicate>
void vector_remove_if(V& vec, Predicate p);
template <class V, class W>
-void vector_append(V& vec, W& vec2);
+void vector_append(V& vec, const W& vec2);
+
+template <class V, class W>
+void set_append(V& s, const W& s2);
template <class S, class Predicate>
void set_remove_if(S& set, Predicate p);
@@ -68,12 +71,19 @@ void vector_remove_if(V& vec, Predicate p)
template <class V, class W> inline
-void vector_append(V& vec, W& vec2)
+void vector_append(V& vec, const W& vec2)
{
vec.insert(vec.end(), vec2.begin(), vec2.end());
}
+template <class V, class W> inline
+void set_append(V& s, const W& s2)
+{
+ s.insert(s2.begin(), s2.end());
+}
+
+
template <class S, class Predicate> inline
void set_remove_if(S& set, Predicate p)
{
bgstack15