diff options
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*>(©FileCallback)) != 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) { |