diff options
author | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 17:00:50 +0200 |
---|---|---|
committer | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 17:00:50 +0200 |
commit | 4ecfd41e36533d858c98d051ef70cab80e69e972 (patch) | |
tree | ca07d8745967d2c6a7123a5d32269cfbfaa7bd6c | |
parent | 2.2 (diff) | |
download | FreeFileSync-4ecfd41e36533d858c98d051ef70cab80e69e972.tar.gz FreeFileSync-4ecfd41e36533d858c98d051ef70cab80e69e972.tar.bz2 FreeFileSync-4ecfd41e36533d858c98d051ef70cab80e69e972.zip |
2.3
106 files changed, 9073 insertions, 9046 deletions
diff --git a/Application.cpp b/Application.cpp index c6439665..eced6f8e 100644 --- a/Application.cpp +++ b/Application.cpp @@ -6,7 +6,6 @@ #include "application.h" #include "ui/mainDialog.h" -#include "shared/globalFunctions.h" #include <wx/msgdlg.h> #include "comparison.h" #include "algorithm.h" @@ -142,15 +141,6 @@ int Application::OnRun() { wxApp::OnRun(); } - catch (const RuntimeException& e) //catch runtime errors during main event loop; wxApp::OnUnhandledException could be used alternatively - { - //unfortunately it's not always possible to display a message box in this erroneous situation, however (non-stream) file output always works! - wxFile safeOutput(FreeFileSync::getLastErrorTxtFile(), wxFile::write); - safeOutput.Write(e.show()); - - wxMessageBox(e.show(), _("An exception occured!"), wxOK | wxICON_ERROR); - return -8; - } catch (const std::exception& e) //catch all STL exceptions { //unfortunately it's not always possible to display a message box in this erroneous situation, however (non-stream) file output always works! @@ -193,6 +183,8 @@ void Application::runGuiMode(const wxString& cfgFileName, xmlAccess::XmlGlobalSe void Application::runBatchMode(const wxString& filename, xmlAccess::XmlGlobalSettings& globSettings) { + using namespace xmlAccess; + //load XML settings xmlAccess::XmlBatchConfig batchCfg; //structure to receive gui settings try @@ -213,32 +205,33 @@ void Application::runBatchMode(const wxString& filename, xmlAccess::XmlGlobalSet try //begin of synchronization process (all in one try-catch block) { + //set error handling for comparison: DON'T allow ignoring errors! Aborted file traversing can lead to data-loss when synchronizing! + const OnError compareErrHandling = batchCfg.handleError == ON_ERROR_IGNORE ? + ON_ERROR_EXIT : //in GUI mode ON_ERROR_EXIT means "showPopups = true" which is okay + batchCfg.handleError; + //class handling status updates and error messages std::auto_ptr<BatchStatusHandler> statusHandler; //delete object automatically if (batchCfg.silent) - statusHandler = std::auto_ptr<BatchStatusHandler>(new BatchStatusHandlerSilent(batchCfg.handleError, batchCfg.logFileDirectory, returnValue)); + statusHandler.reset(new BatchStatusHandlerSilent(compareErrHandling, batchCfg.logFileDirectory, returnValue)); else - statusHandler = std::auto_ptr<BatchStatusHandler>(new BatchStatusHandlerGui(batchCfg.handleError, returnValue)); - - //PREPARE FILTER - std::auto_ptr<FreeFileSync::FilterProcess> filterInstance(NULL); - if (batchCfg.mainCfg.filterIsActive) - filterInstance.reset(new FreeFileSync::FilterProcess(batchCfg.mainCfg.includeFilter, batchCfg.mainCfg.excludeFilter)); + statusHandler.reset(new BatchStatusHandlerGui(compareErrHandling, returnValue)); //COMPARE DIRECTORIES FreeFileSync::FolderComparison folderCmp; - FreeFileSync::CompareProcess comparison(globSettings.traverseDirectorySymlinks, - globSettings.fileTimeTolerance, + FreeFileSync::CompareProcess comparison(batchCfg.mainCfg.hidden.traverseDirectorySymlinks, + batchCfg.mainCfg.hidden.fileTimeTolerance, globSettings.ignoreOneHourDiff, - globSettings.warnings, - filterInstance.get(), + globSettings.optDialogs, statusHandler.get()); - comparison.startCompareProcess(batchCfg.directoryPairs, + comparison.startCompareProcess(FreeFileSync::extractCompareCfg(batchCfg.mainCfg), batchCfg.mainCfg.compareVar, - batchCfg.mainCfg.syncConfiguration, folderCmp); + //set error handling for synchronization + statusHandler->setErrorStrategy(batchCfg.handleError); + //check if there are files/folders to be sync'ed at all if (!synchronizationNeeded(folderCmp)) { @@ -248,14 +241,16 @@ void Application::runBatchMode(const wxString& filename, xmlAccess::XmlGlobalSet //START SYNCHRONIZATION FreeFileSync::SyncProcess synchronization( - batchCfg.mainCfg.handleDeletion, - batchCfg.mainCfg.customDeletionDirectory, - globSettings.copyFileSymlinks, - globSettings.traverseDirectorySymlinks, - globSettings.warnings, + batchCfg.mainCfg.hidden.copyFileSymlinks, + batchCfg.mainCfg.hidden.traverseDirectorySymlinks, + globSettings.optDialogs, + batchCfg.mainCfg.hidden.verifyFileCopy, statusHandler.get()); - synchronization.startSynchronizationProcess(folderCmp); + const std::vector<FreeFileSync::FolderPairSyncCfg> syncProcessCfg = FreeFileSync::extractSyncCfg(batchCfg.mainCfg); + assert(syncProcessCfg.size() == folderCmp.size()); + + synchronization.startSynchronizationProcess(syncProcessCfg, folderCmp); } catch (FreeFileSync::AbortThisProcess&) //exit used by statusHandler { diff --git a/BUILD/Changelog.txt b/BUILD/Changelog.txt index fca7be5b..826950b0 100644 --- a/BUILD/Changelog.txt +++ b/BUILD/Changelog.txt @@ -2,8 +2,33 @@ |FreeFileSync| -------------- +Changelog v2.3 +-------------- +New filter and sync configuration at folder pair level +Improved sorting: sort across multiple folder pairs + stable sorting in middle grid + consolidated sorting of sync-direction +Open external applications via context menu(customizable) +Removed performance penalty when using include filters +Improved filter syntax for strings beginning with wildcards +Default handling for conflict files now configurable +New option to show all hidden dialogs again +Fixed issue with makros %nameCo, %dirCo +New option in *.ffs_gui/ffs_batch files: Verify copied files +Use Windows Volume Shadow Copy for shared and locked files(new) +More detailed information in *.cvs export +Use current working directory to save global configuration (portable version) +Respect subdirectories when manually changing sync-direction +Allow import of batch configuration into GUI mode +Some small GUI improvements +New shortcuts: SPACE: (de-)select rows; ENTER: start external application +Performance improvements: Reduced CPU time by 28%, (peak) memory consumption by 20% +Added Traditional Chinese translation +Updated translation files + + Changelog v2.2 ---------------- +-------------- New user-defined recycle bin directory Possibility to create synchronization directories automatically (if not existing) Support for relative directory names (e.g. \foo, ..\bar) respecting current working directory diff --git a/BUILD/Compress with UPX.cmd b/BUILD/Compress with UPX.cmd index 40180a1e..c5f7dda2 100644 --- a/BUILD/Compress with UPX.cmd +++ b/BUILD/Compress with UPX.cmd @@ -1,2 +1 @@ -C:\Programme\C++\UPX\upx -9 --compress-icons=0 %1 -pause
\ No newline at end of file +C:\Programme\C++\UPX\upx -9 --compress-icons=0 %1
\ No newline at end of file diff --git a/BUILD/Languages/chinese_simple.lng b/BUILD/Languages/chinese_simple.lng index a33f3904..cf9b3e11 100644 --- a/BUILD/Languages/chinese_simple.lng +++ b/BUILD/Languages/chinese_simple.lng @@ -68,6 +68,8 @@ 加载(&L) &Load configuration 加载配置(&L) +&New + &No 否 &OK @@ -84,8 +86,14 @@ 保存(&S) &Yes 是 +(Note that only FAT/FAT32 drives are affected by this problem!\nIn all other cases you can disable the setting \"ignore 1-hour difference\".) + , . +- Other side's counterpart to %dir + +- Other side's counterpart to %name + - conflict - 冲突 - conflict (same date, different size) @@ -110,10 +118,6 @@ - 右侧 - right newer - 右侧较新 -- sibling of %dir -- 与 %dir 同级 -- sibling of %name -- 与 %name 同级 -Open-Source file synchronization- -开源文件同步器- . @@ -154,8 +158,6 @@ Add folder 添加文件夹 Add folder pair 添加成对文件夹 -All items have been synchronized! -所有项目已经同步完成! An exception occured! 发生异常! As a result the files are separated into the following categories: @@ -208,6 +210,8 @@ Comparing content of files %x 正在比较文件内容的百分比 %x Comparing content... 正在比较文件内容... +Comparing files by content failed. + Comparison Result 比较结果 Comparison settings @@ -232,6 +236,8 @@ Confirm 确认 Conflict detected: 检测到冲突: +Conflicts/files that cannot be categorized + Continue 继续 Conversion error: @@ -270,16 +276,20 @@ Customize columns 自定义栏 Customize... 自定义... +D-Click + DECISION TREE 决策树 Data remaining: 剩余数据: +Data verification error: Source and target file have different content! + Date 日期 Delay 延时 -Delay between two invocations of the commandline -两个命令行调用之间的延时 +Delay between detection of changes and execution of commandline in seconds + Delete files/folders existing on left side only 删除仅在左侧存在的文件/文件夹 Delete files/folders existing on right side only @@ -300,6 +310,8 @@ Deleting folder %x 正删除文件夹 %x Deletion handling 删除处理 +Description + Directories are dependent! Be careful when setting up synchronization rules: 目录有依赖性!在设立同步规则时请小心: Directories to watch @@ -320,8 +332,6 @@ Do you really want to move the following objects(s) to the Recycle Bin? 你确定要移动下列对象到回收站吗? Do you want FreeFileSync to automatically check for updates every week? 要让 FreeFileSync 保持每周检查一次更新吗?> -Don't ask me again -不要再问我 Donate with PayPal 通过PayPal捐赠 Download now? @@ -392,14 +402,12 @@ Exit immediately and set returncode < 0 立即退出,并设置返回码 <0 Exit with RC < 0 退出并设置返回码 <0 +External applications + Feedback and suggestions are welcome at: 欢迎在下面提出反馈意见和建议: File %x has an invalid date! 文件 %x 的日期非法! -File Manager integration: -文件管理器集成: -File Time tolerance (seconds): -文件时间容错(秒): File already exists. Overwrite? 文件已经存在.覆盖? File content @@ -410,12 +418,10 @@ File list exported! 文件清单已经导出! File size and date 文件大小和日期 -File times that differ by up to the specified number of seconds are still handled as having same time. -文件相差最多指定秒数但仍然按相同时间的处理. Filename 文件名 -Files %x have a file time difference of less than 1 hour! It's not safe to decide which one is newer due to Daylight Saving Time issues. -文件 %x 的时间差异小于1小时!由于夏令时的问题因此不能安全地判断哪一个比较新. +Files %x have a file time difference of less than 1 hour!\n\nIt's not safe to decide which one is newer due to Daylight Saving Time issues. +文件 %x 的时间差异小于1小时!\n\n由于夏令时的问题因此不能安全地判断哪一个比较新. Files %x have the same date but a different size! 文件 %x 日期相同但大小不同! Files are found equal if\n - file content\nis the same. @@ -424,6 +430,8 @@ Files are found equal if\n - filesize\n - last write time and date\nare 如果文件大小和最后修改时间和日期相同则认为两者相同. Files remaining: 剩余文件: +Files that are equal on both sides + Files that exist on both sides and have different content 两侧都有并且内容不同的文件 Files that exist on both sides, left one is newer @@ -448,8 +456,8 @@ Filter view 过滤查看 Folder Comparison and Synchronization 文件夹比较与同步 -Folder pair -文件夹对 +Free disk space available: + FreeFileSync - Folder Comparison and Synchronization FreeFileSync - 文件夹比较与同步 FreeFileSync Batch Job @@ -470,6 +478,8 @@ Global settings 全局设置 Help 帮助 +Hidden dialogs: + Hide all error and warning messages 隐藏所有错误与警告信息 Hide conflicts @@ -486,10 +496,6 @@ Hide files that exist on left side only 隐藏仅在左侧的文件 Hide files that exist on right side only 隐藏仅在右侧的文件 -Hide files that will be copied to the left side -隐藏将被复制到左侧的文件 -Hide files that will be copied to the right side -隐藏将被复制到右侧的文件 Hide files that will be created on the left side 隐藏将在左侧被建立的文件 Hide files that will be created on the right side @@ -498,6 +504,10 @@ Hide files that will be deleted on the left side 隐藏将在左侧被删除的文件 Hide files that will be deleted on the right side 隐藏将在右侧被删除的文件 +Hide files that will be overwritten on left side + +Hide files that will be overwritten on right side + Hide files that won't be copied 隐藏将不会被复制的文件 Hide filtered items @@ -530,12 +540,16 @@ Info 信息 Information 信息 -Initialization of Recycle Bin failed! -初始化回收站失败! +Integrate external applications into context menu. The following macros are available: + It was not possible to initialize the Recycle Bin!\n\nIt's likely that you are not using Windows.\nIf you want this feature included, please contact the author. :) 初始化回收站是不大可能了!\n\n估计你使用的不是Windows系统.\n如果你想包含此特性,请联系作者. :) +Leave as unresolved conflict + Left 左侧 +Legend + Load configuration from file 从文件加载配置 Load configuration history (press DEL to delete items) @@ -554,16 +568,16 @@ Move column down 下移一行 Move column up 上移一行 -Move files to a custom directory. -将文件移动于自定义目录. -Move to custom directory -移动至自定义目录 +Move files to a user-defined directory. + Moving %x to Recycle Bin 移动 %x 到回收站 -Moving %x to custom directory -移动 %x 至自定义目录 -Not all items have been synchronized! Have a look at the list. -并非所有项目都已同步!请看此列表. +Moving file %x to user-defined directory %y + +Moving folder %x to user-defined directory %y + +Multiple... + Not enough free disk space available in: 没有足够的可用磁盘空间用于: Nothing to synchronize according to configuration! @@ -576,10 +590,8 @@ Number of files that will be overwritten 一些文件和目录将被覆盖 OK 确定 -Only files/directories that pass filtering will be selected for synchronization. The filter will be applied to the name relative(!) to the synchronization directories. -只有通过过滤的文件/文件夹会被选择用于同步.过滤中的路径将做为同步文件夹的相关路径. -Open with File Manager\tD-Click -在资源管理器中打开\t双击 +Only files/directories that pass filtering will be selected for synchronization. The filter will be applied to the name relative(!) to the base synchronization directories. + Operation aborted! 操作已取消! Operation: @@ -594,8 +606,6 @@ Please copy the appropriate \"Shadow.dll\" (located in \"Shadow.zip\" archive) i 请复制适当的\"Shadow.dll\"(位于\"Shadow.zip\"压缩包中)到FreeFileSync安装目录以启用此特性. Please fill all empty directory fields. 请填满所有空的目录区域. -Please specify alternate directory for deletion! -请指定用于删除的替换目录! Press button to activate filter 请按键以激活过滤 Published under the GNU General Public License: @@ -604,12 +614,16 @@ Question 问题 Quit 退出 +Re-enable all hidden dialogs? + RealtimeSync - Automated Synchronization 实时同步 - 自动同步 RealtimeSync configuration 实时同步配置 Relative path 相对路径 +Remove alternate settings + Remove folder 删除文件夹 Remove folder pair @@ -618,10 +632,6 @@ Report translation error 报告翻译错误 Reset 重置 -Reset all warning messages -重置所有警告信息 -Reset all warning messages? -重置所有警告信息? Result 结果 Right @@ -640,6 +650,10 @@ Scanning: 扫描中: Select a folder 选择一个文件夹 +Select alternate filter settings + +Select alternate synchronization settings + Select logfile directory: 选择日志保存位置: Select variant: @@ -660,10 +674,6 @@ Show files that exist on left side only 显示仅存在左侧的文件 Show files that exist on right side only 显示仅存在右侧的文件 -Show files that will be copied to the left side -显示将被复制到左侧的文件 -Show files that will be copied to the right side -显示将被复制到右侧的文件 Show files that will be created on the left side 显示将在左侧被建立的文件 Show files that will be created on the right side @@ -672,8 +682,14 @@ Show files that will be deleted on the left side 显示将在左侧被删除的文件 Show files that will be deleted on the right side 显示将在右侧被删除的文件 +Show files that will be overwritten on left side + +Show files that will be overwritten on right side + Show files that won't be copied 显示将不被复制的文件 +Show hidden dialogs + Show popup 显示弹出窗口 Show popup on errors or warnings @@ -684,8 +700,6 @@ Silent mode 静默模式 Size 大小 -Sorting file list... -排序文件列表... Source code written completely in C++ utilizing: 源代码完全使用C++工具编写: Speed: @@ -728,8 +742,6 @@ Target file already existing! 目标文件已经存在! The file does not contain a valid configuration: 该文件不包含有效的配置: -This commandline will be executed on each doubleclick. The following macros are available: -此命令行会在每次双击时被执行. 如下宏命令可用: This variant evaluates two equally named files as being equal when they have the same file size AND the same last write date and time. 此变化评估两个文件名相同的文件,只有当他们有同样的文件大小并且最后修改日期和时间也相同\n时才认为它们是相同的. Time @@ -766,16 +778,20 @@ Use Recycle Bin 使用回收站 Use Recycle Bin when deleting or overwriting files. 当删除或覆盖文件时使用回收站. +User-defined directory + +User-defined directory for deletion was not specified! + Variant 变化 +Verifying file %x + Volume name %x not part of filename %y! 卷名 %x 并非文件名 %y 的一部分! Warning 警告 Warning: Synchronization failed for %x item(s): 警告:同步失败 %x (某些)项目: -Warnings: -警告: When the comparison is started with this option set the following decision tree is processed: 当以此选项开始比较时以下决定树被处理: You can ignore the error to consider not existing directories as empty. diff --git a/BUILD/Languages/chinese_traditional.lng b/BUILD/Languages/chinese_traditional.lng new file mode 100644 index 00000000..08b20ee5 --- /dev/null +++ b/BUILD/Languages/chinese_traditional.lng @@ -0,0 +1,806 @@ + MinGW \t- Windows port of GNU Compiler Collection\n wxWidgets \t- Open-Source GUI framework\n wxFormBuilder\t- wxWidgets GUI-builder\n CodeBlocks \t- Open-Source IDE + MinGW \t- Windows port of GNU Compiler Collection\n wxWidgets \t- Open-Source GUI framework\n wxFormBuilder\t- wxWidgets GUI-builder\n CodeBlocks \t- Open-Source IDE + Byte + 位元組 + GB + GB + MB + MB + PB + PB + TB + TB + day(s) + 日 + hour(s) + 時 + kB + kB + min + 分 + sec + 秒 +%x / %y objects deleted successfully +%x / %y 個物件已删除成功 +%x Percent +百分之 %x +%x directories +%x 目錄 +%x files, +%x 檔案, +%x is not a valid FreeFileSync batch file! +%x 不是一個有效的 FreeFileSync 批次檔! +%x of %y rows in view +顯示 %x 之 %y 行 +%x of 1 row in view +顯示 %x 之 1 行 +&Abort +取消(&A) +&About... +關於(&A)... +&Advanced +進階(&A) +&Apply +套用(&Y) +&Cancel +取消(&C) +&Check for new version +檢查更新(&C) +&Create batch job +新建批次處理作業(&C) +&Default +預設(&D) +&Exit +結束(&E) +&Export file list +匯出檔案清單(&E) +&File +檔案(&F) +&Global settings +整體設定(&G) +&Help +說明(&H) +&Ignore +忽略(&I) +&Language +語言(&L) +&Load +載入(&L) +&Load configuration +載入配置(&L) +&New +新增專案(&N) +&No +否 +&OK +確定(&O) +&Pause +暫停(&P) +&Quit +離開(&Q) +&Restore +回復(&R) +&Retry +重試(&R) +&Save +儲存(&S) +&Yes +是 +(Note that only FAT/FAT32 drives are affected by this problem!\nIn all other cases you can disable the setting \"ignore 1-hour difference\".) +(請注意只有 FAT/FAT32 磁碟機會受到此問題的影響!\n在所有其他狀況下,可以關閉這個設定\"忽略1小時的誤差"\。) +, +. +- Other side's counterpart to %dir +- 另一邊對應到 %dir +- Other side's counterpart to %name +- 另一邊對應到 %name +- conflict +- 衝突 +- conflict (same date, different size) +- 衝突(相同日期,不同大小) +- different +- 不同 +- directory part only +- 只有目錄部份 +- equal +- 相同 +- exists left only +- 只存在於左邊 +- exists right only +- 只存在於右邊 +- full file or directory name +- 完整的檔名或目錄名稱 +- left +- 左邊 +- left newer +- 左邊較新 +- right +- 右邊 +- right newer +- 右邊較新 +-Open-Source file synchronization- +-開放原始碼檔案同步- +. +, +/sec +/秒 +1 directory +1 目錄 +1 file, +1 檔案, +1. &Compare +1. 比對(&C) +1. Enter relative file or directory names separated by ';' or a new line. +1. 輸入相對檔案或目錄名稱,使用';'或空行分隔。 +2. &Synchronize... +2. 同步(&S)... +2. Use wildcard characters '*' and '?'. +2. 使用萬用字元‘*’和‘?’。 +3. Exclude files directly on main grid via context menu. +3. 經由內容選單直接在主要網格排除檔案。 +<Directory> +<目錄> +<Last session> +<最後連線> +<multiple selection> +<多重選擇> +A newer version of FreeFileSync is available: +有較新版本的 FreeFileSync 可用: +Abort requested: Waiting for current operation to finish... +中止請求:正在等待目前操作完成... +Aborted +已中止 +About +關於 +Action +動作 +Add folder +新增資料夾 +Add folder pair +新增兩個資料夾 +An exception occured! +發生異常! +As a result the files are separated into the following categories: +結果是檔案分為以下幾類: +As the name suggests, two files which share the same name are marked as equal if and only if they have the same content. This option is useful for consistency checks rather than backup operations. Therefore the file times are not taken into account at all.\n\nWith this option enabled the decision tree is smaller: +顧名思義,兩個相同檔名的檔案,只有當他們具有同樣的内容時會被判斷是相同的。\n此選項對於一致性檢查比較有用,而不是備份操作。因此,檔案時間沒有列入考慮。\n\n啟用此選項使決策樹較小: +Assemble a batch file for automated synchronization. To start in batch mode simply pass the name of the file to the FreeFileSync executable: FreeFileSync.exe <batchfile>. This can also be scheduled in your operating system's task planner. +組合一個用於自動同步的批次檔。若要開始批次處理模式,只需簡單的將批次檔名傳送给 FreeFileSync 可執行檔:FreeFileSync.exe <batchfile>。這個也可以安排在你的作業系統的計畫任務中。 +Auto-adjust columns +自動調整欄寬 +Batch execution +批次處理執行 +Batch file created successfully! +批次檔新建成功! +Batch job +批次處理作業 +Big thanks for localizing FreeFileSync goes out to: +非常感謝 FreeFileSync 當地語系化的工作人員: +Browse +瀏覽 +Build: +建立: +Cancel +取消 +Change direction +改變方向 +Check all +全選 +Choose to hide filtered files/directories from list +從清單中選擇隱藏被篩選的檔案/目錄 +Comma separated list +逗號分隔清單 +Commandline +命令列 +Commandline is empty! +命令列是空的! +Compare +比對 +Compare both sides +比對兩邊 +Compare by \"File content\" +以檔案内容比對 +Compare by \"File size and date\" +以檔案大小和日期比對 +Compare by... +比對選項... +Comparing content +正在比對内容 +Comparing content of files %x +正在比對檔案内容的百分比 %x +Comparing content... +正在比對檔案内容... +Comparing files by content failed. +比對檔案內容失敗 +Comparison Result +比對結果 +Comparison settings +比對設定 +Completed +完成 +Configuration +配置 +Configuration loaded! +已載入配置! +Configuration overview: +配置概述: +Configuration saved! +配置已儲存! +Configure filter +配置篩選 +Configure filter... +配置篩選... +Configure your own synchronization rules. +配置你自己的同步規則。 +Confirm +確認 +Conflict detected: +檢測到衝突: +Conflicts/files that cannot be categorized +衝突/檔案不能被分類 +Continue +繼續 +Conversion error: +轉換錯誤: +Copy from left to right +從左邊複製到右邊 +Copy from left to right overwriting +從左邊複製到右邊覆蓋檔案 +Copy from right to left +從右邊複製到左邊 +Copy from right to left overwriting +從右邊複製到左邊覆蓋檔案 +Copy new or updated files to right folder. +將新的或更新過的檔案複製到右邊的資料夾中。 +Copy to clipboard\tCTRL+C +複製到剪貼簿\tCTRL+C +Copying file %x to %y +正在複製檔案 %x 到 %y +Copying file %x to %y overwriting target +正在複製檔案 %x 覆蓋到目標 %y +Could not determine volume name for file: +無法判定此檔案的卷標名稱: +Could not initialize directory monitoring: +無法初始化目錄監測: +Could not read values for the following XML nodes: +無法讀取 XML 之後節點的值: +Create a batch job +新建一個批次處理作業 +Creating folder %x +正在新建資料夾 %x +Current operation: +目前操作: +Custom +自訂 +Customize columns +自訂列 +Customize... +自訂... +D-Click +點兩下 +DECISION TREE +決策樹 +Data remaining: +剩餘資料: +Data verification error: Source and target file have different content! +資料驗證錯誤:來源和目的檔案內容不同! +Date +日期 +Delay +延遲 +Delay between detection of changes and execution of commandline in seconds +檢測更改和執行命令列的延遲時間,以秒為單位。 +Delete files/folders existing on left side only +刪除只存在於左邊的檔案/資料夾 +Delete files/folders existing on right side only +刪除只存在於右邊的檔案/資料夾 +Delete files\tDEL +刪除檔案\tDEL +Delete on both sides +兩邊都刪除 +Delete on both sides even if the file is selected on one side only +即使只在一邊中選好檔案,還是刪除兩邊檔案。 +Delete or overwrite files permanently. +永久刪除或覆蓋檔案。 +Delete permanently +永久刪除 +Deleting file %x +正在刪除檔案 %x +Deleting folder %x +正在刪除資料夾 %x +Deletion handling +刪除處理 +Description +描述 +Directories are dependent! Be careful when setting up synchronization rules: +目錄有依靠性!請小心設定同步規則: +Directories to watch +要監看的目錄 +Directory +目錄 +Directory does not exist: +目錄不存在: +Do not display visual status information but write to a logfile instead +不顯示視覺狀態資訊,使用寫入日誌檔代替。 +Do not show this dialog again +不要再顯示此對話框 +Do nothing +維持原狀 +Do you really want to delete the following objects(s)? +真的要刪除以下物件嗎? +Do you really want to move the following objects(s) to the Recycle Bin? +真的要將下列物件移動到資源回收筒嗎? +Do you want FreeFileSync to automatically check for updates every week? +要每週自動檢查更新 FreeFileSync 嗎? +Donate with PayPal +使用 PayPal 捐贈 +Download now? +要立即下載嗎? +Drag && drop +拖放 +Email +信箱 +Enable filter to exclude files from synchronization +啟用篩選器從同步中排除檔案 +Endless loop when traversing directory: +當遍歷目錄時無限循環: +Error +錯誤 +Error changing modification time: +變更修改時間錯誤: +Error copying file: +複製檔案錯誤: +Error copying locked file %x! +複製已鎖定檔案錯誤 %x! +Error creating directory: +新建目錄錯誤: +Error deleting directory: +刪除目錄錯誤: +Error deleting file: +刪除檔案錯誤: +Error handling +錯誤處理 +Error loading library function: +載入函數庫錯誤: +Error moving directory: +移動目錄錯誤: +Error moving file: +移動檔案錯誤: +Error moving to Recycle Bin: +移動到資源回收筒錯誤: +Error opening file: +開啟檔案錯誤: +Error parsing configuration file: +分析配置檔案錯誤: +Error reading file attributes: +讀取檔案屬性錯誤: +Error reading file: +讀取檔案錯誤: +Error resolving symbolic link: +解决符號連結錯誤: +Error starting Volume Shadow Copy Service! +啟動卷影複製服務錯誤! +Error traversing directory: +遍歷目錄錯誤: +Error when monitoring directories. +監測目錄錯誤: +Error writing file attributes: +寫入檔案屬性錯誤: +Error writing file: +寫入檔案錯誤: +Error: Source directory does not exist anymore: +錯誤:來源目錄不存在: +Example +例如 +Exclude +排除 +Exclude temporarily +暫時排除 +Exclude via filter: +使用篩選器排除: +Exit immediately and set returncode < 0 +立即結束,並設定返回碼 <0 +Exit with RC < 0 +結束並設定返回碼 <0 +External applications +外部應用程式 +Feedback and suggestions are welcome at: +歡迎在下面提出回報和建議: +File %x has an invalid date! +檔案 %x 的日期無效! +File already exists. Overwrite? +檔案已存在,要覆蓋嗎? +File content +檔案内容 +File does not exist: +檔案不存在: +File list exported! +檔案清單已匯出! +File size and date +檔案大小和日期 +Filename +檔案名稱 +Files %x have a file time difference of less than 1 hour!\n\nIt's not safe to decide which one is newer due to Daylight Saving Time issues. +檔案 %x 有一個檔案時差在1小時內!\n\n這不是安全的決定,其中之一是由於新的日光節約時間的問題。 +Files %x have the same date but a different size! +檔案 %x 日期相同但大小不同! +Files are found equal if\n - file content\nis the same. +如果檔案内容相同則判斷兩者相同。 +Files are found equal if\n - filesize\n - last write time and date\nare the same. +如果檔案大小和最後修改時間和日期相同則判斷兩者相同。 +Files remaining: +剩餘檔案: +Files that are equal on both sides +兩邊相同的檔案 +Files that exist on both sides and have different content +檔案存在於兩邊而且內容不同 +Files that exist on both sides, left one is newer +檔案存在於兩邊,左邊檔案較新 +Files that exist on both sides, right one is newer +檔案存在於兩邊,右邊檔案較新 +Files/folders remaining: +剩餘的檔案/資料夾: +Files/folders scanned: +已掃瞄的檔案/資料夾: +Files/folders that exist on left side only +只存在於左邊的檔案/資料夾 +Files/folders that exist on right side only +只存在於右邊的檔案/資料夾 +Filter +篩選器 +Filter active: Press again to deactivate +篩選器啟動:再按一次會停用 +Filter files +篩選檔案 +Filter view +篩選檢視 +Folder Comparison and Synchronization +資料夾比對和同步 +Free disk space available: +可用的磁碟空間: +FreeFileSync - Folder Comparison and Synchronization +FreeFileSync - 資料夾比對和同步 +FreeFileSync Batch Job +FreeFileSync 批次處理作業 +FreeFileSync at Sourceforge +FreeFileSync at Sourceforge +FreeFileSync batch file +FreeFileSync 批次檔 +FreeFileSync configuration +FreeFileSync 配置 +FreeFileSync is up to date! +FreeFileSync 是最新版本! +Full path +完整路徑 +Generating file list... +產生檔案清單... +Global settings +整體設定 +Help +說明 +Hidden dialogs: +隱藏對話框: +Hide all error and warning messages +隱藏所有錯誤和警告訊息 +Hide conflicts +隱藏衝突 +Hide files that are different +隱藏不同的檔案 +Hide files that are equal +隱藏相同的檔案 +Hide files that are newer on left +隱藏左邊較新的檔案 +Hide files that are newer on right +隱藏右邊較新的檔案 +Hide files that exist on left side only +隱藏只存在於左邊的檔案 +Hide files that exist on right side only +隱藏只存在於右邊的檔案 +Hide files that will be created on the left side +隱藏左邊將被新建的檔案 +Hide files that will be created on the right side +隱藏右邊將被新建的檔案 +Hide files that will be deleted on the left side +隱藏左邊將被刪除的檔案 +Hide files that will be deleted on the right side +隱藏右邊將被刪除的檔案 +Hide files that will be overwritten on left side +隱藏左邊將被覆蓋的檔案 +Hide files that will be overwritten on right side +隱藏右邊將被覆蓋的檔案 +Hide files that won't be copied +隱藏將不會被複製的檔案 +Hide filtered items +隱藏已篩選的項目 +Hide further error messages during the current process +在目前進程中隱藏進一步的錯誤訊息 +Hints: +提示: +Homepage +首頁 +If you like FFS +如果你喜歡 FFS +Ignore 1-hour file time difference +忽略1小時的檔案時差 +Ignore errors +忽略錯誤 +Ignore subsequent errors +忽略後續錯誤 +Ignore this error, retry or abort synchronization? +忽略此錯誤、重試或中止同步嗎? +Ignore this error, retry or abort? +忽略此錯誤、重試或中止嗎? +Include +包括 +Include temporarily +暫時包括 +Include: *.doc;*.zip;*.exe\nExclude: temp\\* +包括:*.doc;*.zip;*.exe\n排除temp\\* +Info +訊息 +Information +訊息 +Integrate external applications into context menu. The following macros are available: +整合上下文功能表中的外部應用程式。可以使用下面的巨集: +It was not possible to initialize the Recycle Bin!\n\nIt's likely that you are not using Windows.\nIf you want this feature included, please contact the author. :) +不能初始化資源回收筒!\n\n可能你使用的不是Windows系統。\n如果你想包括此功能,請聯繫作者。:) +Leave as unresolved conflict +保留給未解決的衝突 +Left +左邊 +Legend +圖例 +Load configuration from file +從檔案載入配置 +Load configuration history (press DEL to delete items) +載入配置的歷史記錄(按DEL鍵刪除項目) +Log-messages: +日誌訊息: +Logging +日誌記錄 +Mirror ->> +鏡像 ->> +Mirror backup of left folder: Right folder will be overwritten and exactly match left folder after synchronization. +左邊資料夾的鏡像備份:同步之後, 右邊資料夾將被覆蓋,完全和左邊資料夾一模一樣。 +More than 50% of the total number of files will be copied or deleted! +超過總數 50% 以上的檔案將被複製或刪除! +Move column down +下移一行 +Move column up +上移一行 +Move files to a user-defined directory. +將檔案移動到自定義目錄。 +Moving %x to Recycle Bin +移動 %x 到資源回收筒 +Moving file %x to user-defined directory %y +移動檔案 %x 到自定義目錄 %y +Moving folder %x to user-defined directory %y +移動資料夾 %x 到自定義資料夾 %y +Multiple... +多個... +Not enough free disk space available in: +沒有足夠的可用空間: +Nothing to synchronize according to configuration! +根據配置没有任何同步! +Number of files and directories that will be created +一些檔案和目錄將被新建 +Number of files and directories that will be deleted +一些檔案和目錄將被刪除 +Number of files that will be overwritten +一些檔案和目錄將被覆蓋 +OK +確定 +Only files/directories that pass filtering will be selected for synchronization. The filter will be applied to the name relative(!) to the base synchronization directories. +只有被選中篩選的檔案/目錄會進行同步。篩選器將套用到基本同步目錄的相對名稱。 +Operation aborted! +中止操作! +Operation: +操作: +Overview +摘要 +Pause +暫停 +Paused +已暫停 +Please copy the appropriate \"Shadow.dll\" (located in \"Shadow.zip\" archive) into the FreeFileSync installation directory to enable this feature. +請複製適當的\"Shadow.dll\"(位於\"Shadow.zip\"壓縮檔)到 FreeFileSync 安裝目錄以啟用此功能。 +Please fill all empty directory fields. +請填滿所有空白目錄欄位。 +Press button to activate filter +按下按鈕以啟動篩選器 +Published under the GNU General Public License: +在GNU通用公共許可證下發佈: +Question +問題 +Quit +離開 +Re-enable all hidden dialogs? +重新啟用所有隱藏的對話框嗎? +RealtimeSync - Automated Synchronization +即時同步 - 自動同步 +RealtimeSync configuration +即時同步配置 +Relative path +相對路徑 +Remove alternate settings +刪除備用設定 +Remove folder +刪除資料夾 +Remove folder pair +刪除兩個資料夾 +Report translation error +回報翻譯錯誤 +Reset +重置 +Result +結果 +Right +右邊 +S&ave configuration +儲存配置(&A) +S&witch view +切換檢視(&W) +Save changes to current configuration? +保存對目前配置的更改嗎? +Save current configuration to file +將目前配置儲存到檔案 +Scanning... +正在掃瞄... +Scanning: +掃瞄中: +Select a folder +選擇一個資料夾 +Select alternate filter settings +選擇備用篩選設定 +Select alternate synchronization settings +選擇備用同步設定 +Select logfile directory: +選擇日誌檔目錄: +Select variant: +選擇變數: +Show conflicts +顯示衝突 +Show file icons +顯示檔案圖示 +Show files that are different +顯示不同的檔案 +Show files that are equal +顯示相同的檔案 +Show files that are newer on left +顯示左邊較新的檔案 +Show files that are newer on right +顯示右邊較新的檔案 +Show files that exist on left side only +顯示只存在於左邊的檔案 +Show files that exist on right side only +顯示只存在於右邊的檔案 +Show files that will be created on the left side +顯示左邊將被新建的檔案 +Show files that will be created on the right side +顯示右邊將被新建的檔案 +Show files that will be deleted on the left side +顯示左邊將被刪除的檔案 +Show files that will be deleted on the right side +顯示右邊將被刪除的檔案 +Show files that will be overwritten on left side +顯示左邊將被覆蓋的檔案 +Show files that will be overwritten on right side +顯示右邊將被覆蓋的檔案 +Show files that won't be copied +顯示將不會被複製的檔案 +Show hidden dialogs +顯示隱藏的對話框 +Show popup +顯示快顯功能表 +Show popup on errors or warnings +在快顯功能表上顯示錯誤或警告 +Significant difference detected: +檢測到顯著的差異: +Silent mode +靜音模式 +Size +大小 +Source code written completely in C++ utilizing: +原始碼完全使用C++編寫: +Speed: +速度: +Start +開始 +Start synchronization +開始同步 +Statistics +統計 +Stop +停止 +Swap sides +兩邊交換 +Synchronization Preview +同步預覽 +Synchronization aborted! +同步已中止! +Synchronization completed successfully! +已成功的完成同步! +Synchronization completed with errors! +同步完成但有錯誤! +Synchronization settings +同步設定 +Synchronization status +同步狀態 +Synchronize all .doc, .zip and .exe files except everything in subfolder \"temp\". +同步所有 .doc, .zip 和 .exe 檔案, 除了\"temp\"中的一切。 +Synchronize both sides simultaneously: Copy new or updated files in both directions. +同時雙向同步:雙向複製新的或已更新的檔案。 +Synchronize... +同步... +Synchronizing... +同步中... +System out of memory! +系統記憶體不足! +Target directory already existing! +目標目錄已存在! +Target file already existing! +目標檔案已存在! +The file does not contain a valid configuration: +該檔案不包含有效的配置: +This variant evaluates two equally named files as being equal when they have the same file size AND the same last write date and time. +此變數計算結果為兩個檔名相同的檔案,只有當他們的檔案大小和最後修改日期時間也相同,才會判斷他們是相同的檔案。 +Time +時間 +Time elapsed: +經過時間: +Time remaining: +剩餘時間: +Total amount of data that will be transferred +將被傳輸的全部資料量 +Total required free disk space: +全部所需要的可用磁碟空間: +Total time: +總時間: +Treat file times that differ by exactly +/- 1 hour as equal, less than 1 hour as conflict in order to handle Daylight Saving Time changes. +對檔案時差在精確的+/-1小時的檔案判斷視為相同的,以小於1小時作為衝突,是為了處理日光節約時間的變化。 +Two way <-> +雙向 <-> +Unable to connect to sourceforge.net! +無法連接到 sourceforge.net! +Unable to create logfile! +無法新建日誌檔案! +Unable to initialize Recycle Bin! +無法初始化資源回收筒! +Uncheck all +取消全部選中 +Unresolved conflicts existing! \n\nYou can ignore conflicts and continue synchronization. +存在未解決的衝突!\n\n你可以忽略衝突,並繼續同步。 +Update -> +更新 -> +Usage: Select directories for monitoring and enter a commandline. Each time files are modified within these directories (or subdirectories) the commandline is executed. +用法:選擇監測的目錄並輸入命令行。每次目錄(或子目錄)中的檔案被修改時,此命令列就會執行。 +Use Recycle Bin +使用資源回收筒 +Use Recycle Bin when deleting or overwriting files. +當刪除或覆蓋檔案時使用資源回收筒。 +User-defined directory +自定義目錄 +User-defined directory for deletion was not specified! +未指定要刪除的自定義目錄! +Variant +變數 +Verifying file %x +驗證檔 %x +Volume name %x not part of filename %y! +卷名 %x 並非檔名 %y 的一部份! +Warning +警告 +Warning: Synchronization failed for %x item(s): +警告:同步失敗的名稱為 %x 項目: +When the comparison is started with this option set the following decision tree is processed: +當比對開始,使用此選項設定時,以下決策樹將被處理: +You can ignore the error to consider not existing directories as empty. +你可以忽略該錯誤而將不存在的目錄視為空目錄。 +You may try to synchronize remaining items again (WITHOUT having to re-compare)! +您可能會嘗試再次同步剩餘項目(不用重新比對)! +different +不同 +file exists on both sides +檔案在兩邊均已存在 +on one side only +只有一邊 diff --git a/BUILD/Languages/czech.lng b/BUILD/Languages/czech.lng index f1178b94..ee1bbc04 100644 --- a/BUILD/Languages/czech.lng +++ b/BUILD/Languages/czech.lng @@ -68,6 +68,8 @@ Zkontrolovat &aktualizace &Načíst &Load configuration &Načíst konfiguraci +&New +&Nový &No &Ne &OK @@ -84,8 +86,14 @@ U&končit &Uložit &Yes &Ano +(Note that only FAT/FAT32 drives are affected by this problem!\nIn all other cases you can disable the setting \"ignore 1-hour difference\".) +(Platí pouze pro souborové systémy FAT/FAT32. V ostatních případech můžete nastavení \"Ignorovat 1 hodinu rozdílu v čase mezi soubory\" nechat vypnuté.) , +- Other side's counterpart to %dir +- pouze cesta z opačného panelu (protistarna k %dir) +- Other side's counterpart to %name +- celá cesta nebo jméno souboru z opačného panelu (protistrana k %name) - conflict - konflikt - conflict (same date, different size) @@ -110,10 +118,6 @@ U&končit - vpravo - right newer - vpravo novější -- sibling of %dir -- pouze cesta z protistrany -- sibling of %name -- celá cesta nebo jméno souboru z protistrany -Open-Source file synchronization- -synchronizace souborů Open-Source- . @@ -154,8 +158,6 @@ Add folder Přidat adresář Add folder pair Přidat adresář pro porovnání -All items have been synchronized! -Všechny položky byly aktualizovány! An exception occured! Vyskytla se chyba! As a result the files are separated into the following categories: @@ -208,6 +210,8 @@ Comparing content of files %x Porovnávání obsahu souborů %x Comparing content... Porovnávání obsahu... +Comparing files by content failed. +Porovnání obsahu souborů se nezdařilo. Comparison Result Výsledek porovnání Comparison settings @@ -232,6 +236,8 @@ Confirm Potvrdit Conflict detected: Zaznamenán konflikt: +Conflicts/files that cannot be categorized +Konflikty/soubory které nelze zařadit Continue Pokračovat Conversion error: @@ -257,7 +263,7 @@ Není možné zjistit jméno jednotky souboru: Could not initialize directory monitoring: Nelze nastavit monitorování adresáře: Could not read values for the following XML nodes: - +Nelze načíst hodnoty následujících XML elementy: Create a batch job Vytvořit dávku Creating folder %x @@ -270,16 +276,20 @@ Customize columns Vlastní sloupce Customize... Vlastní.... +D-Click +Dvojklik DECISION TREE ROZHODOVÁNÍ Data remaining: Zbývá dat: +Data verification error: Source and target file have different content! +Chyba verifikace dat: Zdrojový a cílový soubor mají rozdílný obsah! Date Datum Delay Zpoždění -Delay between two invocations of the commandline -Zpoždění mezi dvěma příkazovými řádky +Delay between detection of changes and execution of commandline in seconds +Zpoždění detekce změn a suštění příkazu v sekundách Delete files/folders existing on left side only Smazat soubory/adresáře existující pouze na levé straně Delete files/folders existing on right side only @@ -300,6 +310,8 @@ Deleting folder %x Mazání adresáře %x Deletion handling Nastavení mazání +Description +Popis Directories are dependent! Be careful when setting up synchronization rules: Adresáře jsou závislé! Buďte opatrní s definicí synchronizačních pravidel: Directories to watch @@ -320,8 +332,6 @@ Do you really want to move the following objects(s) to the Recycle Bin? Opravdu chcete přesunout následující objekty do koše? Do you want FreeFileSync to automatically check for updates every week? Chcete aby FreeFileSync automaticky zjišťoval aktualizace každý týden? -Don't ask me again -Již se znovu neptat Donate with PayPal Přispět pomocí PayPal Download now? @@ -392,14 +402,12 @@ Exit immediately and set returncode < 0 Ukočit okamžitě a nastavit návratový kód < 0 Exit with RC < 0 Ukočit s RC < 0 +External applications +Externí aplikace Feedback and suggestions are welcome at: Komentáře a náměty zasílejte na: File %x has an invalid date! Soubor %x má chybné datum! -File Manager integration: -Integrace do průzkumníka: -File Time tolerance (seconds): -Tolerance času souboru (sekundy): File already exists. Overwrite? Soubor již existuje. Přepsat? File content @@ -410,12 +418,10 @@ File list exported! Seznam souborů exportován! File size and date Podle velikosti a data souboru -File times that differ by up to the specified number of seconds are still handled as having same time. -Soubory jejichž čas se neliší o více než zadaný počet sekund jsou brány jako se stejným časem. Filename Jméno -Files %x have a file time difference of less than 1 hour! It's not safe to decide which one is newer due to Daylight Saving Time issues. -Soubory %x se liší o méně než 1 hodinu. není možné bezpečně rozhodnout, který z nich je novější kvůli používání Letního času. +Files %x have a file time difference of less than 1 hour!\n\nIt's not safe to decide which one is newer due to Daylight Saving Time issues. +Soubory %x se liší o méně než 1 hodinu.\n\nnení možné bezpečně rozhodnout, který z nich je novější kvůli používání Letního času. Files %x have the same date but a different size! Soubory %x mají stejné datum a čas ale rozdílnou velikost! Files are found equal if\n - file content\nis the same. @@ -424,6 +430,8 @@ Files are found equal if\n - filesize\n - last write time and date\nare Soubory jsou shodné jestliže\n - velikost souboru\n - datum i čas poslední změny\njsou stejné. Files remaining: Zbývá souborů: +Files that are equal on both sides +Soubory shodné na obou stranách Files that exist on both sides and have different content Soubory, ketré existují na obou stranách a liší se obsahem Files that exist on both sides, left one is newer @@ -441,15 +449,15 @@ Soubory/adresáře, které existují pouze vpravo Filter Filtr Filter active: Press again to deactivate -Filtr aktivní: Klikni znovu pro deaktivaci +Filtr aktivní: Klikněte znovu pro deaktivaci Filter files Filtrovat soubory Filter view Filtrovat seznam Folder Comparison and Synchronization Porovnání a Synchronizace adresářů -Folder pair -Dvojice adresářů +Free disk space available: +Volné místo k dispozici: FreeFileSync - Folder Comparison and Synchronization FreeFileSync - Porovnání a Synchronizace adresářů FreeFileSync Batch Job @@ -470,6 +478,8 @@ Global settings Nastavení programu Help Nápověda +Hidden dialogs: +Skryté dialogy: Hide all error and warning messages Skrýt všechny chyby a varování Hide conflicts @@ -486,10 +496,6 @@ Hide files that exist on left side only Skrýt soubory existující pouze vlevo Hide files that exist on right side only Skrýt soubory existující pouze vpravo -Hide files that will be copied to the left side -Skrýt soubory, které budou kopírovány vlevo -Hide files that will be copied to the right side -Skrýt soubory, které budou kopírovány vpravo Hide files that will be created on the left side Skrýt soubory, které budou vlevo vytvořeny Hide files that will be created on the right side @@ -498,6 +504,10 @@ Hide files that will be deleted on the left side Skrýt soubory, které budou vlevo smazány Hide files that will be deleted on the right side Skrýt soubory, které budou vpravo smazány +Hide files that will be overwritten on left side +Skrýt soubory, které budou vlevo přepsány +Hide files that will be overwritten on right side +Skrýt soubory, které budou vpravo přepsány Hide files that won't be copied Skrýt soubory, které nebudou kopírovány Hide filtered items @@ -530,12 +540,16 @@ Info Info Information Informace -Initialization of Recycle Bin failed! -Chyba inicializace Koše! +Integrate external applications into context menu. The following macros are available: +Integrace externí apilkace do kontextového menu. K dispozici jsou následující makra: It was not possible to initialize the Recycle Bin!\n\nIt's likely that you are not using Windows.\nIf you want this feature included, please contact the author. :) Nebylo možné inicializovat Koš!\n\nPravděpodobně nepoužíváte Windows.\nPokud chcete tuto vlastnost využívat, kontaktujte autora. :) +Leave as unresolved conflict +Ponechat jako nevyřešený konflikt Left Levý +Legend +Legenda Load configuration from file Načíst konfiguraci ze souboru Load configuration history (press DEL to delete items) @@ -554,16 +568,16 @@ Move column down Přesunout sloupec dolů Move column up Přesunout sloupec nahoru -Move files to a custom directory. -Přesunout soubory do nastaveného adresáře. -Move to custom directory -Přesunout do adresáře +Move files to a user-defined directory. +Přesunout soubory do uživatelem definovaného adresáře. Moving %x to Recycle Bin Přesouvání %x do Koše. -Moving %x to custom directory -Přesouvání %x do nastaveného adresáře. -Not all items have been synchronized! Have a look at the list. -Ne všechny položky byly sesynchronizovány! Podívejte se na seznam. +Moving file %x to user-defined directory %y +Přesouvání souboru %x do uživatelského adresáře %y +Moving folder %x to user-defined directory %y +Přesouvání adrsáře %x do uživatelského adresáře %y +Multiple... +Několikanásobný... Not enough free disk space available in: Nedostatek místa na disku: Nothing to synchronize according to configuration! @@ -576,10 +590,8 @@ Number of files that will be overwritten Počet souborů a adresářů k přepsání OK OK -Only files/directories that pass filtering will be selected for synchronization. The filter will be applied to the name relative(!) to the synchronization directories. +Only files/directories that pass filtering will be selected for synchronization. The filter will be applied to the name relative(!) to the base synchronization directories. Pouze soubory/adresáře odpovídající nastavenému filtru budou vybrány pro synchronizaci. Filtr je aplikován relativně(!) k cestě synchronizovaných adresářů. -Open with File Manager\tD-Click -Otevřít\tDvojklikem Operation aborted! Operace zrušena! Operation: @@ -594,8 +606,6 @@ Please copy the appropriate \"Shadow.dll\" (located in \"Shadow.zip\" archive) i Prosím zkopírujte odpovídající \"Shadow.dll\" (umístěný v \"Shadow.zip\") do instalačního adresáře FreeFileSync, aby bylo možné použít tuto funkci. Please fill all empty directory fields. Zadejte adresáře pro synchronizaci. -Please specify alternate directory for deletion! -Zadejte cílový adresář pro mazání! Press button to activate filter Kliknutím aktivujete filtr Published under the GNU General Public License: @@ -604,12 +614,16 @@ Question Otázky Quit Konec +Re-enable all hidden dialogs? +Znovu povolit skryté dialogy? RealtimeSync - Automated Synchronization RealtimeSync - Automatická synchronizace RealtimeSync configuration Konfigurace RealtimeSync Relative path Relativní cesta +Remove alternate settings +Odstranit jiné nastavení Remove folder Odstranit adresář Remove folder pair @@ -618,10 +632,6 @@ Report translation error Hlásit chyby překladu Reset Resetovat -Reset all warning messages -Resetovat všechna varovná hlášení -Reset all warning messages? -Resetovat všechna varovná hlášení? Result Výsledek Right @@ -640,6 +650,10 @@ Scanning: Zpracováváno: Select a folder Vyberte adresář +Select alternate filter settings +Vyberte alternativní nastavení filtru +Select alternate synchronization settings +Vyberte alternativní nastavení synchronizace Select logfile directory: Vyberte adresář pro záznamy: Select variant: @@ -660,10 +674,6 @@ Show files that exist on left side only Zobrazit soubory existující pouze vlevo Show files that exist on right side only Zobrazit soubory existující pouze vpravo -Show files that will be copied to the left side -Zobrazit soubory pro kopírování doleva -Show files that will be copied to the right side -Zobrazit soubory pro kopírování doprava Show files that will be created on the left side Zobrazit soubory, které budou vlevo vytvořeny Show files that will be created on the right side @@ -672,8 +682,14 @@ Show files that will be deleted on the left side Zobrazit soubory, které budou vlevo smazány Show files that will be deleted on the right side Zobrazit soubory, které budou vpravo smazány +Show files that will be overwritten on left side +Zobrazit soubory, které budou vlevo přepsány +Show files that will be overwritten on right side +Zobrazit soubory, které budou vpravo přepsány Show files that won't be copied Zobrazit soubory, které nebudou kopírovány +Show hidden dialogs +Zobrazit skryté dialogy Show popup Zobrazit okno Show popup on errors or warnings @@ -684,8 +700,6 @@ Silent mode Tichý mód Size Velikost -Sorting file list... -Setřídění seznamu... Source code written completely in C++ utilizing: Zdrojový kód byl napsán kompletně v C++ s pomocí: Speed: @@ -723,13 +737,11 @@ Synchronizuji... System out of memory! Nedostatek paměti! Target directory already existing! - +Cílový adresář již existuje! Target file already existing! Cílový soubor již existuje! The file does not contain a valid configuration: Soubor neobsahuje platnou konfiguraci: -This commandline will be executed on each doubleclick. The following macros are available: -Tento příkaz bude spuštěn vždy, když poklikáte na název souboru. K dispozici jsou následující proměnné: This variant evaluates two equally named files as being equal when they have the same file size AND the same last write date and time. Tato variantra vyhodnotí dva stejně pojmenované soubory jako shodné pokud mají i stejnou velikost A ZÁROVEŇ i datum a čas poslední změny. Time @@ -766,16 +778,20 @@ Use Recycle Bin Použít Koš Use Recycle Bin when deleting or overwriting files. Použít Koš při mazání nebo přepisu souborů. +User-defined directory +Uživatelsky definovaný adresář +User-defined directory for deletion was not specified! +Uživatelsky definovaný adresář pro mazání nebyl zadán! Variant Varianta +Verifying file %x +Kontroluji soubor %x Volume name %x not part of filename %y! Disk %x není součástí jména souboru %y! Warning Varování Warning: Synchronization failed for %x item(s): Varování: Synchornizace se nepovedla pro \"%x\" položek: -Warnings: -Varování: When the comparison is started with this option set the following decision tree is processed: Když je zahájeno porovnávání s tímto nastavením, je použito následující schéma rozhodování: You can ignore the error to consider not existing directories as empty. diff --git a/BUILD/Languages/dutch.lng b/BUILD/Languages/dutch.lng index 3484395a..4f694323 100644 --- a/BUILD/Languages/dutch.lng +++ b/BUILD/Languages/dutch.lng @@ -68,6 +68,8 @@ &Laden &Load configuration &Laad de configuratie +&New +&Nieuw &No &Nee &OK @@ -84,8 +86,14 @@ &Opslaan &Yes &Ja +(Note that only FAT/FAT32 drives are affected by this problem!\nIn all other cases you can disable the setting \"ignore 1-hour difference\".) +(Alleen FAT/FAT32 schijven hebben last van dit probleem!\nIn alle andere gevallen kunt u deze instelling uitschakelen \"negeer 1-uur tijdsverschil\".) , . +- Other side's counterpart to %dir +- Tegenhander andere kant naar %dir +- Other side's counterpart to %name +- Tegenhanger andere kant naar %naam - conflict - conflict - conflict (same date, different size) @@ -110,10 +118,6 @@ - rechts - right newer - rechts is nieuwer -- sibling of %dir -- verwant aan %dir -- sibling of %name -- verwant aan &name -Open-Source file synchronization- -Open-Source bestandssynchronisatie- . @@ -154,8 +158,6 @@ Add folder Map toevoegen Add folder pair Voeg 1 paar gekoppelde mappen toe -All items have been synchronized! -Alle bestanden zijn gesynchroniseerd! An exception occured! Er heeft een uitzondering plaatsgevonden! As a result the files are separated into the following categories: @@ -201,13 +203,15 @@ Vergelijk met \"bestandsinhoud\" Compare by \"File size and date\" Vergelijk met \"bestandsgrootte en -datum\" Compare by... -Vergelelijk met... +Vergelijk met... Comparing content Vergelijken van inhoud Comparing content of files %x De inhoud van %x bestanden wordt vergeleken Comparing content... Inhoud vergelijken... +Comparing files by content failed. +Vergelijken van bestanden met de inhoud is mislukt. Comparison Result Resultaat vergelijken Comparison settings @@ -232,6 +236,8 @@ Confirm Bevestig Conflict detected: Conflict gedetecteerd: +Conflicts/files that cannot be categorized +Conflicten/bestanden die niet kunnen worden gecategoriseerd Continue Doorgaan Conversion error: @@ -257,7 +263,7 @@ Kon de schijfnaam niet vaststellen van bestand: Could not initialize directory monitoring: Initaliseren van locatie-controle niet mogelijk: Could not read values for the following XML nodes: - +Kon geen waarden inlezen voor de volgende XML punten: Create a batch job Creëer batchjob Creating folder %x @@ -270,16 +276,20 @@ Customize columns Aanpassen kolommen Customize... Aanpassen... +D-Click +Dubbele klik DECISION TREE BESLISSINGSBOOM Data remaining: Resterende data: +Data verification error: Source and target file have different content! +Dataverificatie-fout: Bron en doelbestand hebben verschillende inhoud! Date Datum Delay Vertraging -Delay between two invocations of the commandline -Vertraging tussen het aanroepen van twee opdrachten van de opdrachtregel +Delay between detection of changes and execution of commandline in seconds +Vertraging tussen detecteren van veranderingen en uitvoering van de opdrachtregel in seconden Delete files/folders existing on left side only Verwijder bestanden/folders die alleen links bestaan Delete files/folders existing on right side only @@ -300,6 +310,8 @@ Deleting folder %x Map %x wordt verwijderd Deletion handling Verwijder-afhandeling +Description +Beschrijving Directories are dependent! Be careful when setting up synchronization rules: Mappen zijn afhankelijk van elkaar! Wees voorzichtig met het maken van synchronisatieregels: Directories to watch @@ -320,8 +332,6 @@ Do you really want to move the following objects(s) to the Recycle Bin? Weet u zeker dat u de/het volgende bestand(en) wilt verplaatsen naar de prullenbak? Do you want FreeFileSync to automatically check for updates every week? Wilt u FreeFileSync automatisch elke week laten controleren of er een nieuwe versie is? -Don't ask me again -Niet meer vragen Donate with PayPal Doneer met PayPal Download now? @@ -392,14 +402,12 @@ Exit immediately and set returncode < 0 Onmiddelijk afsluiten en zet de returncode < 0 Exit with RC < 0 Afsluiten met RC < 0 +External applications +Externe applicaties Feedback and suggestions are welcome at: Tips en suggesties zijn welkom op: File %x has an invalid date! Bestand %x heeft een ongeldige datum! -File Manager integration: -Integratie bestandsbeheer: -File Time tolerance (seconds): -Bestandtijd tolerantie (seconden): File already exists. Overwrite? Het bestand bestaat al. Overschrijven? File content @@ -410,12 +418,10 @@ File list exported! Bestandslijst geëxporteerd! File size and date Bestandsgrootte en -datum -File times that differ by up to the specified number of seconds are still handled as having same time. -Bestandstijden die verschillen met maximaal de gespecificeerde tijd worden nog steeds behandeld alsof ze dezelfde tijd hebben. Filename Bestandsnaam -Files %x have a file time difference of less than 1 hour! It's not safe to decide which one is newer due to Daylight Saving Time issues. -De bestanden %x hebben een bestandstijd verschil van minder dan één uur! Het is niet veilig te bepalen welke nieuwer is vanwege zomertijd instellingen +Files %x have a file time difference of less than 1 hour!\n\nIt's not safe to decide which one is newer due to Daylight Saving Time issues. +De bestanden %x hebben een bestandstijd verschil van minder dan één uur!\n\nHet is niet veilig te bepalen welke nieuwer is vanwege zomertijd instellingen Files %x have the same date but a different size! De bestanden %x hebben dezelfde data maar een afwijkende grootte! Files are found equal if\n - file content\nis the same. @@ -424,6 +430,8 @@ Files are found equal if\n - filesize\n - last write time and date\nare Bestanden worden gelijk beschouwd als,\n - de grootte\n - datum en tijdstip van de laatste wijziging\novereenkomt. Files remaining: Resterende bestanden: +Files that are equal on both sides +Bestanden die aan beide kanten gelijk zijn Files that exist on both sides and have different content Bestanden die aan beide kanten bestaan maar een verschillende inhoud hebben Files that exist on both sides, left one is newer @@ -448,8 +456,8 @@ Filter view Bekijk het filter Folder Comparison and Synchronization Mappen vergelijken en synchroniseren -Folder pair -Gekoppelde mappen +Free disk space available: +Beschikbare vrije schijfruimte : FreeFileSync - Folder Comparison and Synchronization FreeFileSync - Mappen vergelijken en synchroniseren FreeFileSync Batch Job @@ -470,6 +478,8 @@ Global settings Algemene instellingen Help Help +Hidden dialogs: +Verborgen dialogen: Hide all error and warning messages Verberg alle foutmeldingen en waarschuwingen Hide conflicts @@ -486,10 +496,6 @@ Hide files that exist on left side only Verberg bestanden die alleen aan de linkerkant bestaan Hide files that exist on right side only Verberg bestanden die alleen aan de rechterkant bestaan -Hide files that will be copied to the left side -Verberg bestanden die naar de linkerkant zullen worden gekopieerd -Hide files that will be copied to the right side -Verberg bestanden die naar de rechterkant zullen worden gekopieerd Hide files that will be created on the left side Verberg bestanden die zullen worden aangemaakt aan de linkerkant Hide files that will be created on the right side @@ -498,6 +504,10 @@ Hide files that will be deleted on the left side Verberg bestanden die zullen worden verwijderd aan de linkerkant Hide files that will be deleted on the right side Verberg bestanden die zullen worden verwijderd aan de rechterkant +Hide files that will be overwritten on left side +Verberg bestanden die zullen worden overschreven aan de linkerkant +Hide files that will be overwritten on right side +Verberg bestanden die zullen worden overschreven aan de rechterkant Hide files that won't be copied Verberg bestanden die niet zullen worden gekopieerd Hide filtered items @@ -530,12 +540,16 @@ Info Info Information Informatie -Initialization of Recycle Bin failed! -Initialiseren van de prullenbak is mislukt. +Integrate external applications into context menu. The following macros are available: +Integreer externe applicaties in het context menu. De volgende macros zijn beschikbaar: It was not possible to initialize the Recycle Bin!\n\nIt's likely that you are not using Windows.\nIf you want this feature included, please contact the author. :) Het was niet mogelijk de prullenbak te initialiseren!\n\nHet is waarschijnlijk dat u niet Windows gebruikt.\nAls u deze optie wel wilt, neem dan alstublieft contact op met de auteur. :) +Leave as unresolved conflict +Beschouwen als onopgelost conflict Left Links +Legend +Legenda Load configuration from file Laad configuratie uit bestand Load configuration history (press DEL to delete items) @@ -554,16 +568,16 @@ Move column down Verplaats kolom naar beneden Move column up Verplaats kolom naar boven -Move files to a custom directory. -Verplaats bestanden naar een te kiezen locatie. -Move to custom directory -Verplaats naar een te kiezen locatie +Move files to a user-defined directory. +Verplaats bestanden naar een door de gebruiker te definiëren map. Moving %x to Recycle Bin %x aan het verplaatsen naar de Prullenbak -Moving %x to custom directory -Verplaatsen van %x naar gekozen locatie -Not all items have been synchronized! Have a look at the list. -Niet alle bestanden zijn gesynchroniseerd! Bekijk de lijst. +Moving file %x to user-defined directory %y +Verplaatsen bestand %x naar een door de gebruiker gedefinieerde locatie %y +Moving folder %x to user-defined directory %y +Verplaatsen map %x naar een door de gebruiker gedefinieerde locatie %y +Multiple... +Meerdere... Not enough free disk space available in: Niet genoeg schijfruimte beschikbaar op: Nothing to synchronize according to configuration! @@ -575,11 +589,9 @@ Aantal mappen en bestanden die zullen worden verwijderd Number of files that will be overwritten Aantal bestanden dat zal worden overschreven OK -OKE -Only files/directories that pass filtering will be selected for synchronization. The filter will be applied to the name relative(!) to the synchronization directories. -Alleen bestanden/mappen die niet zijn gefilterd worden geselecteerd voor synchronisatie. Het filter wordt toegepast op de naam relatief(!) ten opzichte van de synchronisatiemappen. -Open with File Manager\tD-Click -Openen met bestandsbeheerder\tD-klik +Oke +Only files/directories that pass filtering will be selected for synchronization. The filter will be applied to the name relative(!) to the base synchronization directories. +Alleen bestanden /locatie die niet worden gefilterd zullen worden gesynchroniseerd. Het filter word toegepast op de relatieve(!) naam van de basislocatie van de synchronisatie. Operation aborted! Operatie afgebroken! Operation: @@ -594,8 +606,6 @@ Please copy the appropriate \"Shadow.dll\" (located in \"Shadow.zip\" archive) i Kopiëer alstublieft \"Shadow.dll\" (locatie in \"Shadow.zip\" archief) naar de FreeFileSync installatiemap om het gebruik van deze optie mogelijk te maken Please fill all empty directory fields. Vul alstublieft aan beide kanten een pad in. -Please specify alternate directory for deletion! -Specificeer alstublieft een alternatieve locatie om te verwijderen! Press button to activate filter Druk op de knop om het filter te activeren. Published under the GNU General Public License: @@ -604,12 +614,16 @@ Question Vraag Quit Afsluiten +Re-enable all hidden dialogs? +Herstellen van alle verborgen dialogen? RealtimeSync - Automated Synchronization RealtimeSync - Automatische Synchronisatie RealtimeSync configuration RealtimeSync configuratie Relative path Relatieve pad +Remove alternate settings +Verwijder alternatieve instellingen Remove folder Verwijder map Remove folder pair @@ -618,10 +632,6 @@ Report translation error Rapporteer een fout in de vertaling Reset Reset -Reset all warning messages -Reset alle waarchuwingen -Reset all warning messages? -Reset alle waarschuwingen? Result Resultaat Right @@ -640,6 +650,10 @@ Scanning: Scannen: Select a folder Selecteer een map +Select alternate filter settings +Selecteer alternatieve filter instellingen +Select alternate synchronization settings +Selecteer alternatieve synchronisatie instellingen Select logfile directory: Selecteer een map voor het logbestand: Select variant: @@ -660,10 +674,6 @@ Show files that exist on left side only Geef bestanden weer die alleen bestaan aan de linkerkant Show files that exist on right side only Geef bestanden weer die alleen bestaan aan de rechterkant -Show files that will be copied to the left side -Geef bestanden weer die naar de linkerkant worden gekopieerd -Show files that will be copied to the right side -Geef bestanden weer die naar de rechterkant worden gekopieerd Show files that will be created on the left side Geef bestanden weer die zullen worden aangemaakt aan de linkerkant Show files that will be created on the right side @@ -672,8 +682,14 @@ Show files that will be deleted on the left side Geef bestanden weer die zullen worden verwijderd aan de linkerkant Show files that will be deleted on the right side Geef bestanden weer die zullen worden verwijderd aan de rechterkant +Show files that will be overwritten on left side +Geef bestanden weer die zullen worden overschreven aan de linkerkant +Show files that will be overwritten on right side +Geef bestanden weer die zullen worden overschreven aan de rechterkant Show files that won't be copied Geef bestanden weer die niet zullen worden gekopieerd +Show hidden dialogs +Geef verborgen dialogen weer Show popup Pop-up weergeven Show popup on errors or warnings @@ -684,8 +700,6 @@ Silent mode Stille modus Size Grootte -Sorting file list... -Bestandslijst sorteren... Source code written completely in C++ utilizing: Broncode compleet geschreven in C++ met behulp van: Speed: @@ -723,13 +737,11 @@ Aan het synchroniseren... System out of memory! Systeem heeft te weinig geheugen Target directory already existing! - +Doellocatie bestaal al! Target file already existing! Doelbestand bestaat al! The file does not contain a valid configuration: Het bestand bevat geen geldige configuratie: -This commandline will be executed on each doubleclick. The following macros are available: -Deze opdrachtregel zal worden uitgevoerd bij elke dubbele klik. De volgende macros zijn beschikbaar: This variant evaluates two equally named files as being equal when they have the same file size AND the same last write date and time. Deze variant ziet twee gelijknamige bestanden als gelijk wanneer ze dezelfde bestandsgrootte EN tijdstempel hebben. Time @@ -766,16 +778,20 @@ Use Recycle Bin Gebruik de prullenbak Use Recycle Bin when deleting or overwriting files. Gebruik de prullenbak bij verwijderen of overschrijven van bestanden +User-defined directory +Door de gebruiker te definiëren locatie +User-defined directory for deletion was not specified! +Door de gebruiker te definiëren locatie voor verwijderen was niet opgegeven! Variant Variant +Verifying file %x +Verifiëren bestand %x Volume name %x not part of filename %y! Volume naam %x maakt niet deel uit van bestandsnaam %y! Warning Attentie Warning: Synchronization failed for %x item(s): Let op: %x item(s) konden niet worden gesynchroniseerd: -Warnings: -Waarschuwingen: When the comparison is started with this option set the following decision tree is processed: Wanneer met deze optie aan de vergelijking wordt gestart zal de volgende vergelijkingsboom worden gebruikt: You can ignore the error to consider not existing directories as empty. diff --git a/BUILD/Languages/french.lng b/BUILD/Languages/french.lng index 47ce556d..ddfe21c2 100644 --- a/BUILD/Languages/french.lng +++ b/BUILD/Languages/french.lng @@ -68,6 +68,8 @@ &Charger &Load configuration &Charger la configuration +&New +&Nouveau &No &Non &OK @@ -84,8 +86,14 @@ &Sauvegarder &Yes &Oui +(Note that only FAT/FAT32 drives are affected by this problem!\nIn all other cases you can disable the setting \"ignore 1-hour difference\".) +(Notez que seuls les lecteurs FAT/FAT32 sont touchées par ce problème ! \ NDans les autres cas, vous pouvez désactiver le paramètre \ "ignorer la différence d'une heure \".) , +- Other side's counterpart to %dir +- lié de l'autre côté à %dir +- Other side's counterpart to %name +- lié de l'autre côté à %name - conflict - conflit - conflict (same date, different size) @@ -110,10 +118,6 @@ - à droite - right newer - fichier de droite plus récent -- sibling of %dir -- correspondance de %dir -- sibling of %name -- correspondance de %name -Open-Source file synchronization- -Synchronisation de fichiers Open-Source- . @@ -154,8 +158,6 @@ Add folder Ajout d'un dossier Add folder pair Ajout d'un couple de dossiers -All items have been synchronized! -Tous les éléments ont été synchronisés ! An exception occured! Une violation s'est produite ! As a result the files are separated into the following categories: @@ -208,6 +210,8 @@ Comparing content of files %x Comparaison du contenu des fichiers %x Comparing content... Comparaison du contenu... +Comparing files by content failed. +La comparaison des fichiers par leur contenu a échouée. Comparison Result Résultat de la comparaison Comparison settings @@ -232,6 +236,8 @@ Confirm Confirmation Conflict detected: Conflit détecté : +Conflicts/files that cannot be categorized +Conflits/fichiers ne pouvant être catégorisés Continue Continuer Conversion error: @@ -257,7 +263,7 @@ Impossible de trouver le nom de volume pour le fichier : Could not initialize directory monitoring: Impossible d'initialiser la surveillance des dossiers: Could not read values for the following XML nodes: - +Impossible de lire les valeurs des noeuds XML suivants : Create a batch job Création du fichier de commandes Creating folder %x @@ -270,16 +276,20 @@ Customize columns Personnaliser les colonnes Customize... Personnaliser... +D-Click +Clic Droit DECISION TREE ARBRE DE DECISION Data remaining: Données restantes : +Data verification error: Source and target file have different content! +Erreur lors du contrôle des données : Les fichiers source et destination ont des contenus différents ! Date Date Delay délai -Delay between two invocations of the commandline -délai entre deux requêtes de la ligne de commande +Delay between detection of changes and execution of commandline in seconds +Intervalle entre la détection des changements et l'exécution de la ligne de commande en secondes Delete files/folders existing on left side only Suppression des fichiers/répertoires n'existant que sur le côté gauche Delete files/folders existing on right side only @@ -299,7 +309,9 @@ Suppression du fichier %x Deleting folder %x Suppression du dossier %x Deletion handling -Festion des suppressions +Gestion des suppressions +Description +Description Directories are dependent! Be careful when setting up synchronization rules: Les dossiers sont imbriqués ! Attention à la mise à jour des règles de synchronisation : Directories to watch @@ -320,8 +332,6 @@ Do you really want to move the following objects(s) to the Recycle Bin? Voulez-vous vraiment déplacer les objets suivants dans la corbeille ? Do you want FreeFileSync to automatically check for updates every week? Voulez-vous que FreeFileSync recherche automatiquement de nouvelles versions chaque semaine ? -Don't ask me again -Ne plus me redemander Donate with PayPal Faites un don avec PayPal Download now? @@ -392,14 +402,12 @@ Exit immediately and set returncode < 0 Sortie immédiate avec le returncode < 0 Exit with RC < 0 Sortie avec RC < 0 +External applications +Applications externes Feedback and suggestions are welcome at: Commentaires et suggestions sont les bienvenus à : File %x has an invalid date! Le fichier %x a une date invalide ! -File Manager integration: -Choix du Gestionnaire de Fichiers : -File Time tolerance (seconds): -Tolérance sur l'heure de création du fichier (en secondes) : File already exists. Overwrite? Le fichier existe déjà. Voulez-vous le remplacer ? File content @@ -410,12 +418,10 @@ File list exported! Liste des fichiers exportée ! File size and date Taille et date du fichier -File times that differ by up to the specified number of seconds are still handled as having same time. -Les fichiers ayant des heures qui diffèrent d'un nombre de secondes inférieur à celui spécifié sont considérés comme ayant la même heure. Filename Nom du fichier -Files %x have a file time difference of less than 1 hour! It's not safe to decide which one is newer due to Daylight Saving Time issues. -Les fichiers %x ont une différence de date inférieure à 1 heure ! Il n'est pas sain de décider quelle est la plus récente à cause de l'heure d'été. +Files %x have a file time difference of less than 1 hour!\n\nIt's not safe to decide which one is newer due to Daylight Saving Time issues. +Les fichiers %x ont une différence de date inférieure à 1 heure !\n\nIl n'est pas sain de décider quelle est la plus récente à cause de l'heure d'été. Files %x have the same date but a different size! Les fichiers %x ont la même data mais une taille différente ! Files are found equal if\n - file content\nis the same. @@ -424,6 +430,8 @@ Files are found equal if\n - filesize\n - last write time and date\nare Les fichiers sont considérés comme identiques, si\n - leur taille\n - leur date et heure sont identiques. Files remaining: Fichiers restants : +Files that are equal on both sides +Fichiers identiques des deux côtés Files that exist on both sides and have different content Les fichiers existent des deux côtés et ont des contenus différents Files that exist on both sides, left one is newer @@ -448,8 +456,8 @@ Filter view Filtrage de la vue Folder Comparison and Synchronization Comparaison de Dossiers et Synchronisation -Folder pair -Couple de dossiers +Free disk space available: +Espace disque disponible : FreeFileSync - Folder Comparison and Synchronization FreeFileSync - Comparaison et synchronisation de répertoires FreeFileSync Batch Job @@ -470,6 +478,8 @@ Global settings Paramètres généraux Help Aide +Hidden dialogs: +Boîtes de dialogue masquées : Hide all error and warning messages Masquer tous les messages d'erreurs et les avertissements Hide conflicts @@ -486,10 +496,6 @@ Hide files that exist on left side only Masquer les fichiers existant des deux côtés Hide files that exist on right side only Masquer les fichiers n'existant qu'à droite -Hide files that will be copied to the left side -Masquer les fichiers qui seront copiés à gauche -Hide files that will be copied to the right side -Masquer les fichiers qui seront copiés à droite Hide files that will be created on the left side Masquer les fichiers qui seront créés à gauche Hide files that will be created on the right side @@ -498,6 +504,10 @@ Hide files that will be deleted on the left side Masquer les fichiers qui seront supprimés à gauche Hide files that will be deleted on the right side Masquer les fichiers qui seront supprimés à droite +Hide files that will be overwritten on left side +Fichiers masqués qui ont été écrasés à gauche +Hide files that will be overwritten on right side +Fichiers masqués qui ont été écrasés à droite Hide files that won't be copied Masquer les fichiers qui ne seront pas copiés Hide filtered items @@ -530,12 +540,16 @@ Info Info Information Information -Initialization of Recycle Bin failed! -Erreur lors de l'initialisation de la corbeille ! +Integrate external applications into context menu. The following macros are available: +Inclure les applications externes dans le menu contextuel. Les macros suivantes sont disponibles : It was not possible to initialize the Recycle Bin!\n\nIt's likely that you are not using Windows.\nIf you want this feature included, please contact the author. :) Impossible d'accéder à la corbeille !\n\nIl est probable que vous n'utilisez pas Windows.\nSi vous désirez utilisee cette fonctionnalité, veuillez contacter l'auteur. :) +Leave as unresolved conflict +Abandonner en tant que conflit non résolu Left Gauche +Legend +Légende Load configuration from file Charger la configuration à partir du fichier Load configuration history (press DEL to delete items) @@ -554,16 +568,16 @@ Move column down Déplacer la colonne vers le bas Move column up Déplacer la colonne vers le haut -Move files to a custom directory. -Déplacer les fichiers dans un dossier personnel -Move to custom directory -Déplacer dans un dossier personnel +Move files to a user-defined directory. +Déplacer les fichiers vers un répertoire défini par l'utilisateur. Moving %x to Recycle Bin Déplacement de %x vers la Corbeille -Moving %x to custom directory -Déplacement de %x dans un dossier personnel -Not all items have been synchronized! Have a look at the list. -Tout n'a pas été synchronisé ! Vérifiez la liste. +Moving file %x to user-defined directory %y +Déplacement du fichier %x vers le répertoire défini par l'utilisateur %y +Moving folder %x to user-defined directory %y +Déplacement du dossier %x vers le répertoire défini par l'utilisateur %y +Multiple... +Multiple... Not enough free disk space available in: Espace disque insuffisant sur : Nothing to synchronize according to configuration! @@ -576,10 +590,8 @@ Number of files that will be overwritten Nombre de fichiers qui seront remplacés OK OK -Only files/directories that pass filtering will be selected for synchronization. The filter will be applied to the name relative(!) to the synchronization directories. -Seuls les fichiers/répertoires filtrés seront sélectionnés pour la synchronisation. Le filtre sera appliqué aux noms relatifs(!) pour la synchronisation. -Open with File Manager\tD-Click -Ouvrir avec le Gestionnaire de Fichiers\tClick droit +Only files/directories that pass filtering will be selected for synchronization. The filter will be applied to the name relative(!) to the base synchronization directories. +Seuls les fichiers/répertoires filtrés seront sélectionnés pour la synchronisation. Le filtre sera appliqué au nom relatif (!) pour les répertoires de synchroniqation de base. Operation aborted! Opération abandonnée ! Operation: @@ -594,8 +606,6 @@ Please copy the appropriate \"Shadow.dll\" (located in \"Shadow.zip\" archive) i Veuillez copier le fichier \"Shadow.dll\" (situé dans l'archive \"Shadow.zip\") vers le répertoire d'installation de FreeFileSync pour utiliser cette fonctionnalité. Please fill all empty directory fields. Veuillez remplir tous les champs vides du répertoire. -Please specify alternate directory for deletion! -Spécifiez un autre dossier pour les suppressions ! Press button to activate filter Cliquez pour activer le filtrage Published under the GNU General Public License: @@ -604,12 +614,16 @@ Question Question Quit Quitter +Re-enable all hidden dialogs? +Ré-autoriser les boîtes de dialogues masquées ? RealtimeSync - Automated Synchronization RealtimeSync - Synchronisation Automatisée RealtimeSync configuration RealtimeSync configuration Relative path Chemin +Remove alternate settings +Supprimer les paramètres de rechange Remove folder Supprimer le dossier Remove folder pair @@ -618,10 +632,6 @@ Report translation error Etat d'erreurs de transfert Reset Réinitialiser -Reset all warning messages -Réinitialisation de tous les avertissements -Reset all warning messages? -Réinitialiser tous les avertissements ? Result Situation Right @@ -640,6 +650,10 @@ Scanning: Lecture en cours : Select a folder Choisissez un répertoire +Select alternate filter settings +Sélectionner une autre configuration du filtre +Select alternate synchronization settings +Sélectionner une autre configuration de la synchronisation Select logfile directory: Choisissez un dossier pour le fichier .log : Select variant: @@ -660,10 +674,6 @@ Show files that exist on left side only Afficher les fichiers existants seulement à gauche Show files that exist on right side only Afficher les fichiers existants seulement à droite -Show files that will be copied to the left side -Afficher les ficgier qui seront copiés à gauche -Show files that will be copied to the right side -Afficher les ficgier qui seront copiés à droite Show files that will be created on the left side Afficher les ficgier qui seront créés à gauche Show files that will be created on the right side @@ -672,8 +682,14 @@ Show files that will be deleted on the left side Afficher les ficgier qui seront supprimiés à gauche Show files that will be deleted on the right side Afficher les ficgier qui seront supprimiés à droite +Show files that will be overwritten on left side +Afficher les fichiers qui seront écrasés à gauche +Show files that will be overwritten on right side +Afficher les fichiers qui seront écrasés à droite Show files that won't be copied Afficher les ficgier qui ne seront pas copiés +Show hidden dialogs +Afficher les boîtes de dialogue masquées Show popup Afficher la boîte de dialogue Show popup on errors or warnings @@ -684,8 +700,6 @@ Silent mode Mode silencieux Size Taille -Sorting file list... -Tri de la liste des fichiers... Source code written completely in C++ utilizing: Code source écrit totalement en C++ et utilisant : Speed: @@ -723,13 +737,11 @@ Synchronisation en cours... System out of memory! Erreur mémoire système ! Target directory already existing! - +Le répertoire de destination existe déjà ! Target file already existing! Le fichier de destination existe déjà ! The file does not contain a valid configuration: Le fichier ne contient pas de configuration valide : -This commandline will be executed on each doubleclick. The following macros are available: -Cette ligne de commande sera éxécutée sur chaque double clic. Les macros suivnates sont valides : This variant evaluates two equally named files as being equal when they have the same file size AND the same last write date and time. Cette variante définit comme identiques deux fichiers de même nom lorsqu'ils ont la même taille et le même date et heure de modification. Time @@ -766,16 +778,20 @@ Use Recycle Bin Utilisation de la corbeille Use Recycle Bin when deleting or overwriting files. Utiliser la Corbeille lors de la suppression ou du remplacement d'un fichier. +User-defined directory +Réoertoire défini par l'utilisateur +User-defined directory for deletion was not specified! +Le réoertoire défini par l'utilisateur pour la suppression n'a pas été indiqué ! Variant Variante +Verifying file %x +Contrôle du fichier %x Volume name %x not part of filename %y! Le nom de volume %x ne fait pas partie du nom de fichier %y ! Warning Attention Warning: Synchronization failed for %x item(s): Attention : La synchronisation a échouée pour %x élément(s) : -Warnings: -Avertissements : When the comparison is started with this option set the following decision tree is processed: Lorsque la comparaison démarre avec cette option, l'arbre de décision suivant est exécuté : You can ignore the error to consider not existing directories as empty. diff --git a/BUILD/Languages/german.lng b/BUILD/Languages/german.lng index 3e801fc0..417923e4 100644 --- a/BUILD/Languages/german.lng +++ b/BUILD/Languages/german.lng @@ -68,6 +68,8 @@ Dateiliste e&xportieren &Laden &Load configuration Konfiguration &laden +&New +&Neu &No &Nein &OK @@ -84,8 +86,14 @@ Konfiguration &laden &Speichern &Yes &Ja +(Note that only FAT/FAT32 drives are affected by this problem!\nIn all other cases you can disable the setting \"ignore 1-hour difference\".) +(Achtung: Nur FAT/FAT32 Laufwerke sind von diesem Problem betroffen!\nIn allen anderen Fällen kann die Einstellung \"Zeitunterschied von einer Stunde ignorieren\" deaktiviert werden.) , . +- Other side's counterpart to %dir +- Entsprechung der anderen Seite zu %dir +- Other side's counterpart to %name +- Entsprechung der anderen Seite zu %name - conflict - Konflikt - conflict (same date, different size) @@ -110,10 +118,6 @@ Konfiguration &laden - rechts - right newer - rechts neuer -- sibling of %dir -- Gegenstück zu %dir -- sibling of %name -- Gegenstück zu %name -Open-Source file synchronization- -Open-Source Datei-Synchronisation- . @@ -154,8 +158,6 @@ Add folder Verzeichnis hinzufügen Add folder pair Verzeichnispaar hinzufügen -All items have been synchronized! -Alle Elemente wurden synchronisiert! An exception occured! Eine Ausnahme ist aufgetreten! As a result the files are separated into the following categories: @@ -189,7 +191,7 @@ Gefilterte Dateien bzw. Verzeichnisse ein-/ausblenden Comma separated list Kommagetrennte Liste Commandline -Kommandozeile +Befehlszeile Commandline is empty! Die Kommandozeile ist leer! Compare @@ -208,6 +210,8 @@ Comparing content of files %x Vergleiche Inhalt der Dateien %x Comparing content... Vergleiche Dateiinhalt... +Comparing files by content failed. +Vergleich nach Dateiinhalt ist fehlgeschlagen. Comparison Result Ergebnis des Vergleichs Comparison settings @@ -232,6 +236,8 @@ Confirm Bestätigen Conflict detected: Ein Konflikt wurde erkannt: +Conflicts/files that cannot be categorized +Konflikte/Dateien, die nicht eingeordnet werden können Continue Fortfahren Conversion error: @@ -270,16 +276,20 @@ Customize columns Spalten anpassen Customize... Anpassen... +D-Click +D-Klick DECISION TREE ENTSCHEIDUNGSBAUM Data remaining: Verbliebene Daten: +Data verification error: Source and target file have different content! +Verifizierungsfehler: Quell- und Zieldatei haben unterschiedlichen Inhalt! Date Datum Delay Verzögerung -Delay between two invocations of the commandline -Verzögerung zwischen zwei Aufrufen der Kommandozeile +Delay between detection of changes and execution of commandline in seconds +Verzögerung zwischen Erkennen einer Änderung und Aufrufen der Kommandozeile in Sekunden Delete files/folders existing on left side only Nur links existierende Dateien/Verzeichnisse löschen Delete files/folders existing on right side only @@ -300,6 +310,8 @@ Deleting folder %x Lösche Verzeichnis %x Deletion handling Behandlung von Löschungen +Description +Beschreibung Directories are dependent! Be careful when setting up synchronization rules: Die Verzeichnisse sind voneinander abhängig! Achtung beim Festlegen der Synchronisationseinstellungen: Directories to watch @@ -320,8 +332,6 @@ Do you really want to move the following objects(s) to the Recycle Bin? Sollen folgende Elemente wirklich in den Papierkorb verschoben werden? Do you want FreeFileSync to automatically check for updates every week? Soll FreeFileSync automatisch jede Woche nach Aktualisierungen suchen? -Don't ask me again -Nicht mehr nachfragen Donate with PayPal Mit PayPal spenden Download now? @@ -392,14 +402,12 @@ Exit immediately and set returncode < 0 Sofort beenden und Returncode < 0 setzen Exit with RC < 0 Beenden mit RC < 0 +External applications +Externe Anwendungen Feedback and suggestions are welcome at: Feedback und Vorschläge sind willkommen unter: File %x has an invalid date! Die Datei %x hat ein ungültiges Datum! -File Manager integration: -Einbinden des Dateimanagers: -File Time tolerance (seconds): -Abweichung der Dateizeit (Sekunden): File already exists. Overwrite? Die Datei existiert bereits. Überschreiben? File content @@ -410,12 +418,10 @@ File list exported! Dateiliste exportiert! File size and date Dateigröße und -datum -File times that differ by up to the specified number of seconds are still handled as having same time. -Dateiänderungszeiten, die um bis zu der angegebenen Anzahl an Sekunden abweichen, werden trotzdem als gleich angesehen. Filename Dateiname -Files %x have a file time difference of less than 1 hour! It's not safe to decide which one is newer due to Daylight Saving Time issues. -Die Dateien %x haben eine Abweichung der Dateizeit von unter einer Stunde! Aufgrund der Zeitumstellungsproblematik kann nicht zweifelsfrei festgestellt werden, welche die aktuellere ist. +Files %x have a file time difference of less than 1 hour!\n\nIt's not safe to decide which one is newer due to Daylight Saving Time issues. +Die Dateien %x haben eine Abweichung der Dateizeit von unter einer Stunde!\n\nAufgrund der Zeitumstellungsproblematik kann nicht zweifelsfrei festgestellt werden, welche die aktuellere ist. Files %x have the same date but a different size! Die Dateien %x haben dasselbe Datum, aber eine unterschiedliche Größe! Files are found equal if\n - file content\nis the same. @@ -424,6 +430,8 @@ Files are found equal if\n - filesize\n - last write time and date\nare Dateien gelten als gleich, wenn\n - die Größe\n - Datum und Uhrzeit der letzten Änderung\ngleich sind. Files remaining: Verbliebene Dateien: +Files that are equal on both sides +Auf beiden Seiten gleiche Dateien Files that exist on both sides and have different content Auf beiden Seiten existierende Dateien mit unterschiedlichem Inhalt Files that exist on both sides, left one is newer @@ -448,8 +456,8 @@ Filter view Ansicht filtern Folder Comparison and Synchronization Verzeichnisvergleich und Synchronisation -Folder pair -Verzeichnispaar +Free disk space available: +Verfügbarer freier Speicherplatz: FreeFileSync - Folder Comparison and Synchronization FreeFileSync - Verzeichnisvergleich und -synchronisation FreeFileSync Batch Job @@ -470,6 +478,8 @@ Global settings Globale Einstellungen Help Hilfe +Hidden dialogs: +Versteckte Dialoge: Hide all error and warning messages Alle Fehler- und Warnmeldungen werden unterdrückt Hide conflicts @@ -486,10 +496,6 @@ Hide files that exist on left side only Nur links existierende Dateien ausblenden Hide files that exist on right side only Nur rechts existierende Dateien ausblenden -Hide files that will be copied to the left side -Dateien die nach links kopiert werden ausblenden -Hide files that will be copied to the right side -Dateien die nach rechts kopiert werden ausblenden Hide files that will be created on the left side Dateien die links erstellt werden ausblenden Hide files that will be created on the right side @@ -498,6 +504,10 @@ Hide files that will be deleted on the left side Dateien die links gelöscht werden ausblenden Hide files that will be deleted on the right side Dateien die rechts gelöscht werden ausblenden +Hide files that will be overwritten on left side +Dateien die links überschrieben werden ausblenden +Hide files that will be overwritten on right side +Dateien die rechts überschrieben werden ausblenden Hide files that won't be copied Dateien die nicht kopiert werden ausblenden Hide filtered items @@ -530,12 +540,16 @@ Info Info Information Information -Initialization of Recycle Bin failed! -Die Initialisierung des Papierkorbs ist fehlgeschlagen! +Integrate external applications into context menu. The following macros are available: +Integriert externe Anwendungen in das Kontextmenu. Die folgenden Makros stehen zur Verfügung: It was not possible to initialize the Recycle Bin!\n\nIt's likely that you are not using Windows.\nIf you want this feature included, please contact the author. :) Die Papierkorbfunktion steht nicht zur Verfügung!\n\nWahrscheinlich benutzen Sie nicht Microsoft Windows.\nWenn Sie diese Funktion wirklich benötigen, kontaktieren Sie bitte den Autor. :) +Leave as unresolved conflict +Als unbehandelten Konflikt belassen Left Links +Legend +Legende Load configuration from file Konfiguration aus Datei laden Load configuration history (press DEL to delete items) @@ -554,16 +568,16 @@ Move column down Spalte nach unten verschieben Move column up Spalte nach oben verschieben -Move files to a custom directory. -Verschiebe Dateien in ein benutzerdefiniertes Verzeichnis. -Move to custom directory -In eigenes Verzeichnis verschieben +Move files to a user-defined directory. +Verschiebe Dateien in benutzerdefiniertes Verzeichnis Moving %x to Recycle Bin Verschiebe %x in den Papierkorb -Moving %x to custom directory -Verschiebe %x in eigenes Verzeichnis -Not all items have been synchronized! Have a look at the list. -Nicht alle Elemente wurden synchronisiert! Siehe verbliebene Elemente im Hauptfenster. +Moving file %x to user-defined directory %y +Verschiebe Datei %x in benutzerdefiniertes Verzeichnis %y +Moving folder %x to user-defined directory %y +Verschiebe Ordner %x in benutzerdefiniertes Verzeichnis %y +Multiple... +Verschiedene... Not enough free disk space available in: Nicht genügend freier Speicher verfügbar unter: Nothing to synchronize according to configuration! @@ -576,10 +590,8 @@ Number of files that will be overwritten Anzahl der zu überschreibenden Dateien OK OK -Only files/directories that pass filtering will be selected for synchronization. The filter will be applied to the name relative(!) to the synchronization directories. -Für die Synchronisation werden nur die Dateien/Verzeichnisse berücksichtigt, die den Filtereinstellungen genügen. Der Filter wird dabei auf den Dateinamen relativ(!) zu den zu synchronisierenden Verzeichnissen angewandt. -Open with File Manager\tD-Click -Mit Dateimanager öffnen\tD-Click +Only files/directories that pass filtering will be selected for synchronization. The filter will be applied to the name relative(!) to the base synchronization directories. +Für die Synchronisation werden nur die Dateien/Verzeichnisse berücksichtigt, die den Filtereinstellungen genügen. Der Filter wird dabei auf den Dateinamen relativ(!) zu den Basis-Synchronisationsverzeichnissen angewandt. Operation aborted! Vorgang abgebrochen! Operation: @@ -594,8 +606,6 @@ Please copy the appropriate \"Shadow.dll\" (located in \"Shadow.zip\" archive) i Um diese Funktionalität zu aktivieren, bitte die entsprechende Datei \"Shadow.dll\" (siehe Archiv \"Shadow.zip\") in das FreeFileSync Installationsverzeichnis kopieren. Please fill all empty directory fields. Bitte die leeren Verzeichnisfelder füllen. -Please specify alternate directory for deletion! -Bitte ein alternatives Löschverzeichnis angeben! Press button to activate filter Taste drücken, um Filter zu aktivieren Published under the GNU General Public License: @@ -604,12 +614,16 @@ Question Frage Quit Beenden +Re-enable all hidden dialogs? +Alle versteckten Dialoge wieder anzeigen? RealtimeSync - Automated Synchronization RealtimeSync - Automatisierte Synchronisation RealtimeSync configuration RealtimeSync Konfiguration Relative path Relativer Pfad +Remove alternate settings +Alternative Einstellungen entfernen Remove folder Verzeichnis entfernen Remove folder pair @@ -618,10 +632,6 @@ Report translation error Übersetzungsfehler melden Reset Zurücksetzen -Reset all warning messages -Alle Warnmeldungen zurücksetzen -Reset all warning messages? -Sollen alle Warnmeldungen zurückgesetzt werden? Result Ergebnis Right @@ -640,6 +650,10 @@ Scanning: Suche Dateien: Select a folder Verzeichnis auswählen +Select alternate filter settings +Alternative Filtereinstellungen auswählen +Select alternate synchronization settings +Alternative Synchronisationseinstellungen auswählen Select logfile directory: Verzeichnis für Logdatei wählen: Select variant: @@ -660,10 +674,6 @@ Show files that exist on left side only Nur links existierende Dateien anzeigen Show files that exist on right side only Nur rechts existierende Dateien anzeigen -Show files that will be copied to the left side -Dateien die nach links kopiert werden anzeigen -Show files that will be copied to the right side -Dateien die nach rechts kopiert werden anzeigen Show files that will be created on the left side Dateien die links erstellt werden anzeigen Show files that will be created on the right side @@ -672,8 +682,14 @@ Show files that will be deleted on the left side Dateien die links gelöscht werden anzeigen Show files that will be deleted on the right side Dateien die rechts gelöscht werden anzeigen +Show files that will be overwritten on left side +Dateien die links überschrieben werden anzeigen +Show files that will be overwritten on right side +Dateien die rechts überschrieben werden anzeigen Show files that won't be copied Dateien die nicht kopiert werden anzeigen +Show hidden dialogs +Zeige versteckte Dialoge Show popup Popup zeigen Show popup on errors or warnings @@ -684,8 +700,6 @@ Silent mode Stiller Modus Size Größe -Sorting file list... -Sortiere Dateiliste... Source code written completely in C++ utilizing: Sourcecode komplett in C++ geschrieben mit Hilfe von: Speed: @@ -728,8 +742,6 @@ Target file already existing! Die Zieldatei existiert bereits! The file does not contain a valid configuration: Die Datei enthält keine gültige Konfiguration: -This commandline will be executed on each doubleclick. The following macros are available: -Diese Befehlszeile wird bei jeden Doppelklick ausgeführt. Dabei stehen die folgenden Makros zur Verfügung: This variant evaluates two equally named files as being equal when they have the same file size AND the same last write date and time. Diese Variante identifiziert zwei gleichnamige Dateien als gleich, wenn sie die gleiche Dateigröße haben UND der Zeitpunkt der letzten Änderung derselbe ist. Time @@ -766,16 +778,20 @@ Use Recycle Bin Papierkorb verwenden Use Recycle Bin when deleting or overwriting files. Papierkorb für zu löschende oder zu überschreibende Dateien nutzen. +User-defined directory +Benutzerdefiniertes Verzeichnis +User-defined directory for deletion was not specified! +Kein benutzerdefiniertes Verzeichnis zum Löschen angegeben! Variant Variante +Verifying file %x +Verifiziere Datei %x Volume name %x not part of filename %y! Laufwerksname %x ist kein Teil des Dateinamens %y! Warning Warnung Warning: Synchronization failed for %x item(s): Warnung: Synchronisation fehlgeschlagen für %x Element(e): -Warnings: -Warnungen: When the comparison is started with this option set the following decision tree is processed: Wenn der Vergleich mit dieser Option gestartet wurde, wird folgender Entscheidungsbaum abgearbeitet: You can ignore the error to consider not existing directories as empty. diff --git a/BUILD/Languages/hungarian.lng b/BUILD/Languages/hungarian.lng index 0c1926c6..d12fbca8 100644 --- a/BUILD/Languages/hungarian.lng +++ b/BUILD/Languages/hungarian.lng @@ -68,6 +68,8 @@ A(z) %x nem megfelelő FreeFileSync kötegelt feladat fájl! &Betöltés &Load configuration &Beállítások betöltése +&New + &No &Nem &OK @@ -84,8 +86,14 @@ A(z) %x nem megfelelő FreeFileSync kötegelt feladat fájl! &Mestés &Yes &Igen +(Note that only FAT/FAT32 drives are affected by this problem!\nIn all other cases you can disable the setting \"ignore 1-hour difference\".) + , . +- Other side's counterpart to %dir + +- Other side's counterpart to %name + - conflict - ütközés - conflict (same date, different size) @@ -110,10 +118,6 @@ A(z) %x nem megfelelő FreeFileSync kötegelt feladat fájl! - jobb oldali - right newer - a jobb oldali újabb -- sibling of %dir -- a %dir gyermeke -- sibling of %name -- a %name gyermeke -Open-Source file synchronization- -Nyílt forráskódú fájlszinkronizálás- . @@ -154,8 +158,6 @@ Add folder Mappa hozzáadása Add folder pair Mappa pár megadása -All items have been synchronized! -Minden elem szinkronizálva lett! An exception occured! Kivétel keletkezett! As a result the files are separated into the following categories: @@ -208,6 +210,8 @@ Comparing content of files %x %x fájlok tartalmának összehasonlítása Comparing content... Tartalom összehasonlítása... +Comparing files by content failed. + Comparison Result Az összehasonlítás eredménye Comparison settings @@ -232,6 +236,8 @@ Confirm Megerősítés Conflict detected: Ütközés történt: +Conflicts/files that cannot be categorized + Continue Folytatás Conversion error: @@ -270,16 +276,20 @@ Customize columns Oszlopok testreszabása Customize... Testreszabás... +D-Click + DECISION TREE DÖNTÉSI FA Data remaining: Hátralévő adat: +Data verification error: Source and target file have different content! + Date Dátum Delay Várakozás -Delay between two invocations of the commandline -Két parancssor meghívása közötti várakozás +Delay between detection of changes and execution of commandline in seconds + Delete files/folders existing on left side only Csak a bal oldalon létező fájlok/mappák törlése Delete files/folders existing on right side only @@ -300,6 +310,8 @@ Deleting folder %x Mappa törlése %x Deletion handling Törlések kezelése +Description + Directories are dependent! Be careful when setting up synchronization rules: A mappák függenek egymástól! Legyen óvatos, amikor megadja a szinkronizálási szabályokat: Directories to watch @@ -320,8 +332,6 @@ Do you really want to move the following objects(s) to the Recycle Bin? Valóban a Lomtárba (Recycle Bin) akarja mozgatni az alábbi objektumo(ka)t? Do you want FreeFileSync to automatically check for updates every week? Akarod, hogy a FreeFileSync automatikusan minden héten keressen frissítést? -Don't ask me again -Ne kérdezd még egyszer Donate with PayPal Ha szereted a FreeFileSync-et, támogasd a PayPal segítségével. Download now? @@ -392,14 +402,12 @@ Exit immediately and set returncode < 0 Azonnali kilépés és a visszatérési érték < 0 Exit with RC < 0 Kilépés (visszatérési érték < 0) +External applications + Feedback and suggestions are welcome at: A visszajelzéseket és javaslatokat ide várjuk: File %x has an invalid date! A(z) %x fájlnak érvénytelen a dátuma! -File Manager integration: -Beépülés a Fájlkezelőbe: -File Time tolerance (seconds): -Fájl dátum tolerancia (másodpercek): File already exists. Overwrite? A fájl már létezik. Felülírjuk? File content @@ -410,12 +418,10 @@ File list exported! A fájllista exportálása befejeződött! File size and date fájlméret és dátum alapján -File times that differ by up to the specified number of seconds are still handled as having same time. -Két fájl, melyeknek dátuma legfeljebb az itt megadott másodperccel tér el egymástól, úgy lesz kezelve, mintha megegyezne a dátumuk. Filename Fájlnév -Files %x have a file time difference of less than 1 hour! It's not safe to decide which one is newer due to Daylight Saving Time issues. -A(z) %x fájlok dátuma kevesebb mint 1 órával eltér! A nyári időszámítás miatt nem biztonságos annak eldöntése, hogy melyik az újabb közülük. +Files %x have a file time difference of less than 1 hour!\n\nIt's not safe to decide which one is newer due to Daylight Saving Time issues. +A(z) %x fájlok dátuma kevesebb mint 1 órával eltér!\n\nA nyári időszámítás miatt nem biztonságos annak eldöntése, hogy melyik az újabb közülük. Files %x have the same date but a different size! A(z) %x fájlok dátuma megegyezik, de a mérete nem! Files are found equal if\n - file content\nis the same. @@ -424,6 +430,8 @@ Files are found equal if\n - filesize\n - last write time and date\nare A fájlok megegyező ha\n - a fájlméret\n - az utolsó módosítás dátuma\nmegegyezik. Files remaining: Hátralévő fájlok: +Files that are equal on both sides + Files that exist on both sides and have different content Mindkét oldalon létező fájlok különböző tartalommal Files that exist on both sides, left one is newer @@ -448,8 +456,8 @@ Filter view Szűrő nézet Folder Comparison and Synchronization Mappa összehasonlítás és szinkronizáció -Folder pair -Mappa pár +Free disk space available: + FreeFileSync - Folder Comparison and Synchronization FreeFileSync - Mappa összehasonlítás és szinkronizáció FreeFileSync Batch Job @@ -470,6 +478,8 @@ Global settings Globális beállítások Help Súgó +Hidden dialogs: + Hide all error and warning messages Összes hibaüzenet és figyelmeztetés elrejtése Hide conflicts @@ -486,10 +496,6 @@ Hide files that exist on left side only Csak a bal oldalon létező fájlok elrejtése Hide files that exist on right side only Csak a jobb oldalon létező fájlok elrejtése -Hide files that will be copied to the left side -A bal oldalra másolandó fájlok elrejtése -Hide files that will be copied to the right side -A jobb oldalra másolandó fájlok elrejtése Hide files that will be created on the left side A bal oldalon létrehozandó fájlok elrejtése. Hide files that will be created on the right side @@ -498,6 +504,10 @@ Hide files that will be deleted on the left side A bal oldalon törlendő fájlok elrejtése. Hide files that will be deleted on the right side A jobb oldalon törlendő fájlok elrejtése. +Hide files that will be overwritten on left side + +Hide files that will be overwritten on right side + Hide files that won't be copied A nem másolandó fájlok elrejtése Hide filtered items @@ -530,12 +540,16 @@ Info Információ Information Információ -Initialization of Recycle Bin failed! -A Lomtár (Recycle Bin) inicializálása sikertelen! +Integrate external applications into context menu. The following macros are available: + It was not possible to initialize the Recycle Bin!\n\nIt's likely that you are not using Windows.\nIf you want this feature included, please contact the author. :) Lehetetlen a Lomtár (Recycle Bin) inicializálása!\n\nValószínűleg azért, mert nem Windost használ.\nHa szeretné ezt a funkciót használni, kérjük, lépjen kapcsolatba a szerzővel. :) +Leave as unresolved conflict + Left Bal oldal +Legend + Load configuration from file Beállítások betöltése fájlból Load configuration history (press DEL to delete items) @@ -554,16 +568,16 @@ Move column down Oszlop mozgatása lefelé Move column up Oszlop mozgatása felfelé -Move files to a custom directory. -Fájlok mozgatása egyedi mappába. -Move to custom directory -Mozgatás egyedi mappába +Move files to a user-defined directory. + Moving %x to Recycle Bin %x mozgatása a Lomtárba -Moving %x to custom directory -%x mozgatása egyedi mappába -Not all items have been synchronized! Have a look at the list. -Nem minden elem szinkronizálódott! Tekintsd meg a listát. +Moving file %x to user-defined directory %y + +Moving folder %x to user-defined directory %y + +Multiple... + Not enough free disk space available in: Nincs elég szabad lemezterület: Nothing to synchronize according to configuration! @@ -576,10 +590,8 @@ Number of files that will be overwritten A felülírandó fájlok száma OK OK -Only files/directories that pass filtering will be selected for synchronization. The filter will be applied to the name relative(!) to the synchronization directories. -Csak azok a fájlok/mappák lesznek kijelölve a szinkronizációhoz, amelyek átmennek a szűrésen. A szűrő a szinkronizálás mappájához relatív(!) nevekre lesz alkalmazva. -Open with File Manager\tD-Click -Megnyitás a Fájlkezelőben\tD-Click +Only files/directories that pass filtering will be selected for synchronization. The filter will be applied to the name relative(!) to the base synchronization directories. + Operation aborted! Művelet megszakítva! Operation: @@ -594,8 +606,6 @@ Please copy the appropriate \"Shadow.dll\" (located in \"Shadow.zip\" archive) i Ennek a funkciónak a használatához, kérjük, másold a megfelelő \"Shadow.dll\" fájlt (megtalálható a \"Shadow.zip\" archívumban) abba a mappába, amelybe a FreeFileSync-et telepítetted. Please fill all empty directory fields. Kérjük, töltse ki az összes üres mappa mezőt. -Please specify alternate directory for deletion! -Kérjük, adjon meg egy alternatív mappát a törléshez! Press button to activate filter Nyomja meg a gombot a szűrő aktiválásához Published under the GNU General Public License: @@ -604,12 +614,16 @@ Question Kérdés Quit Kilépés +Re-enable all hidden dialogs? + RealtimeSync - Automated Synchronization RealtimeSync - Automatikus szinkronizálás RealtimeSync configuration RealtimeSync beállítások Relative path Relatív útvonal +Remove alternate settings + Remove folder Mappa eltávolítása Remove folder pair @@ -618,10 +632,6 @@ Report translation error Fordítói hiba bejelentése Reset Helyreállítás -Reset all warning messages -Összes figyelmeztető üzenet helyreállítása -Reset all warning messages? -Helyreállítja az összes figyelmeztető üzenetet? Result Eredmény Right @@ -640,6 +650,10 @@ Scanning: Vizsgálat: Select a folder Mappa kiválasztása +Select alternate filter settings + +Select alternate synchronization settings + Select logfile directory: Naplófájl mappájának kiválasztása: Select variant: @@ -660,10 +674,6 @@ Show files that exist on left side only Csak a bal oldalon létező fájlok mutatása Show files that exist on right side only Csak a jobb oldalon létező fájlok mutatása -Show files that will be copied to the left side -A bal oldalra másolandó fájlok mutatása -Show files that will be copied to the right side -A jobb oldalra másolandó fájlok mutatása Show files that will be created on the left side A bal oldalon létrehozandó fájlok mutatása. Show files that will be created on the right side @@ -672,8 +682,14 @@ Show files that will be deleted on the left side A bal oldalon törlendő fájlok mutatása. Show files that will be deleted on the right side A jobb oldalon törlendő fájlok mutatása. +Show files that will be overwritten on left side + +Show files that will be overwritten on right side + Show files that won't be copied A nem másolandó fájlok mutatása +Show hidden dialogs + Show popup Felbukkanó ablak mutatása Show popup on errors or warnings @@ -684,8 +700,6 @@ Silent mode Csendes mód Size Méret -Sorting file list... -Fájllista rendezése... Source code written completely in C++ utilizing: A forráskód teljes egészében C++-ban íródott\na következők felhasználásával: Speed: @@ -728,8 +742,6 @@ Target file already existing! A célként megadott fájl már létezik! The file does not contain a valid configuration: A következő fájl nem tartalmaz érvényes beállításokat: -This commandline will be executed on each doubleclick. The following macros are available: -Ez a parancssor fog végrehajtódni minden dupla kattintásnál. A következő makrók elérhetőek: This variant evaluates two equally named files as being equal when they have the same file size AND the same last write date and time. Ez a változat akkor tekint egyezőnek két azonos nevű fájlt, ha azok mérete ÉS az utolsó módosításuk ideje azonos. Time @@ -766,16 +778,20 @@ Use Recycle Bin Lomtár (Recycle Bin) használata Use Recycle Bin when deleting or overwriting files. A Lomtár használata fájlok törlésénél vagy felülírásánál. +User-defined directory + +User-defined directory for deletion was not specified! + Variant Variáns +Verifying file %x + Volume name %x not part of filename %y! A(z) %x kötetnevet nem tartalmazza a(z) %y fájlnév! Warning Figyelem Warning: Synchronization failed for %x item(s): Figyelem: A következő %x elem szinkronizálása sikertelen: -Warnings: -Figyelem: When the comparison is started with this option set the following decision tree is processed: Ha az összehasonlítás ezekkel a beállításokkal lesz elindítva, akkor a következő döntési fa érvényesül: You can ignore the error to consider not existing directories as empty. diff --git a/BUILD/Languages/italian.lng b/BUILD/Languages/italian.lng index 472a17cf..ef007fa8 100644 --- a/BUILD/Languages/italian.lng +++ b/BUILD/Languages/italian.lng @@ -68,6 +68,8 @@ &Carica &Load configuration &Carica la configurazione +&New +&Nuovo &No &No &OK @@ -84,8 +86,14 @@ &Salva &Yes &Si +(Note that only FAT/FAT32 drives are affected by this problem!\nIn all other cases you can disable the setting \"ignore 1-hour difference\".) +(Solo volumi con file-system FAT/FAT32 risentono di questo problema!\nIn tutti gli altri casi puoi disabilitare l'impostazione \"ignora differenze di 1 ora\".) , , +- Other side's counterpart to %dir +- L'altro lato e' equivalente a %dir +- Other side's counterpart to %name +- L'altro lato e' equivalente a %name - conflict - conflitto - conflict (same date, different size) @@ -110,10 +118,6 @@ - a destra - right newer - file di destra più recente -- sibling of %dir -- fratello di %dir -- sibling of %name -- fratello di %name -Open-Source file synchronization- -Sincronizzazione Open-Source- . @@ -154,8 +158,6 @@ Add folder Aggiungi cartella Add folder pair Aggiungi una coppia di cartelle -All items have been synchronized! -Tutti gli elementi sono stati sincronizzati! An exception occured! Si è verificato un problema! As a result the files are separated into the following categories: @@ -208,6 +210,8 @@ Comparing content of files %x Comparazione contenuto del file %x Comparing content... Comparazione contenuto... +Comparing files by content failed. +Comparazione file in base al contenuto fallita. Comparison Result Risultato della comparazione Comparison settings @@ -232,6 +236,8 @@ Confirm Conferma Conflict detected: Rilevato conflitto: +Conflicts/files that cannot be categorized +Conflitti/files senza categoria di appartenenza Continue Continua Conversion error: @@ -257,7 +263,7 @@ Impossibile determinare il nome volume per il file: Could not initialize directory monitoring: Monitoraggio directory non inizializzabile: Could not read values for the following XML nodes: - +Impossibile leggere i valori per i seguenti nodi XML: Create a batch job Creazione di un job batch Creating folder %x @@ -270,16 +276,20 @@ Customize columns Personalizza colonne Customize... Personalizza... +D-Click +D-Click DECISION TREE ALBERO DELLE DECISIONI Data remaining: Dati rimanenti: +Data verification error: Source and target file have different content! +Errore in verifica data: I file sorgente e destinazione hanno differente contenuto! Date Data Delay Ritardo -Delay between two invocations of the commandline -Ritardo tra due richieste nella lina di comando +Delay between detection of changes and execution of commandline in seconds +Ritardo in secondi tra rilevazione cambiamenti ed esecuzione della commandline Delete files/folders existing on left side only Elimina files/cartelle esistenti solo a sinistra Delete files/folders existing on right side only @@ -300,6 +310,8 @@ Deleting folder %x Eliminazione cartella %x Deletion handling Gestione cancellazione +Description +Descrizione Directories are dependent! Be careful when setting up synchronization rules: Le directory sono dipendenti! Fai attenzione quando configuri le regole di sincronizzazione: Directories to watch @@ -320,8 +332,6 @@ Do you really want to move the following objects(s) to the Recycle Bin? Vuoi veramente spostare i seguenti oggetti nel Cestino? Do you want FreeFileSync to automatically check for updates every week? Vuoi che FreeFileSync controlli automaticamente gli aggiornamenti ogni settimana? -Don't ask me again -Non chiederlo di nuovo Donate with PayPal Fai una donazione con PayPal Download now? @@ -392,14 +402,12 @@ Exit immediately and set returncode < 0 Esci immediatamente ed imposta returncode < 0 Exit with RC < 0 Esci con RC < 0 +External applications +Applicazioni esterne Feedback and suggestions are welcome at: Commenti e suggerimenti sono i benvenuti: File %x has an invalid date! Il file %x ha una data non valida! -File Manager integration: -Integrazione File Manager: -File Time tolerance (seconds): -Tolleranza di File Time (in secondi): File already exists. Overwrite? Il file esiste già. Lo vuoi sovrascrivere? File content @@ -410,12 +418,10 @@ File list exported! Lista dei file esportata! File size and date Dimensione e data del file -File times that differ by up to the specified number of seconds are still handled as having same time. -File time che differiscono per meno dello specificato numero di secondi sono considerati come aventi lo stesso File time. Filename Nome del file -Files %x have a file time difference of less than 1 hour! It's not safe to decide which one is newer due to Daylight Saving Time issues. -La data dei file %x differisce per meno di 1 ora! Non è sicuro decidere quale dei due è il più recente con la funzione Daylight Saving Time. +Files %x have a file time difference of less than 1 hour!\n\nIt's not safe to decide which one is newer due to Daylight Saving Time issues. +La data dei file %x differisce per meno di 1 ora!\n\nNon è sicuro decidere quale dei due è il più recente con la funzione Daylight Saving Time. Files %x have the same date but a different size! I file %x hanno la stessa data ma dimensione diversa! Files are found equal if\n - file content\nis the same. @@ -424,6 +430,8 @@ Files are found equal if\n - filesize\n - last write time and date\nare I file sono considerati identici se\n - dimensione\n - data e ora sono identici. Files remaining: File rimanenti: +Files that are equal on both sides +Files identici su entrambi i lati Files that exist on both sides and have different content Files esistenti su entrambi i lati e aventi differente contenuto Files that exist on both sides, left one is newer @@ -448,8 +456,8 @@ Filter view Filtro della vista Folder Comparison and Synchronization Comparazione di Cartelle e Sincronizzazione -Folder pair -Coppia di cartelle +Free disk space available: +Spazio libero su disco disponibile: FreeFileSync - Folder Comparison and Synchronization FreeFileSync - Comparazione e sincronizzazione di directory FreeFileSync Batch Job @@ -470,6 +478,8 @@ Global settings Preferenze Help Aiuto +Hidden dialogs: +Messaggi nascosti: Hide all error and warning messages Nascondi tutti gli errori e i messaggi d'avviso Hide conflicts @@ -486,10 +496,6 @@ Hide files that exist on left side only Nascondi i file esistenti solo a sinistra Hide files that exist on right side only Nascondi i file esistenti solo a destra -Hide files that will be copied to the left side -Nascondi i file da copiare sul lato sinistro -Hide files that will be copied to the right side -Nascondi i file da copiare sul lato destro Hide files that will be created on the left side Nascondi i file che verranno creati sul lato sinistro Hide files that will be created on the right side @@ -498,6 +504,10 @@ Hide files that will be deleted on the left side Nascondi i file che verranno cancellati sul lato sinistro Hide files that will be deleted on the right side Nascondi i file che verranno cancellati sul lato destro +Hide files that will be overwritten on left side +Nascondi i file che verranno sovrascritti sul lato sinistro +Hide files that will be overwritten on right side +Nascondi i file che verranno sovrascritti sul lato destro Hide files that won't be copied Nascondi i file che non saranno copiati Hide filtered items @@ -530,12 +540,16 @@ Info Info Information Informazioni -Initialization of Recycle Bin failed! -Inizializzazione del Cestino fallita! +Integrate external applications into context menu. The following macros are available: +Integra applicazioni esterne nel menu contestuale. Sono disponibili le seguenti macro: It was not possible to initialize the Recycle Bin!\n\nIt's likely that you are not using Windows.\nIf you want this feature included, please contact the author. :) Impossibile inizializzare il Cestino!\n\nE'probabile che non si stia utilizzando Windows.\nSe si vuole usare questa funzionalità, contattare l'autore. :) +Leave as unresolved conflict +Lascia come conflitti irrisolti Left Sinistra +Legend +Legenda Load configuration from file Carica configurazione da file Load configuration history (press DEL to delete items) @@ -554,18 +568,18 @@ Move column down Sposta colonna giu' Move column up Sposta colonna su' -Move files to a custom directory. +Move files to a user-defined directory. Sposta i file in una directory personalizzata. -Move to custom directory -Sposta in una directory personalizzata Moving %x to Recycle Bin Spostamento di %x nel Cestino -Moving %x to custom directory -Spostamento di %x in diretcory personalizzata -Not all items have been synchronized! Have a look at the list. -Non tutti gli oggetti sono stati sincronizzati! Controlla la lista. +Moving file %x to user-defined directory %y +Spostamento del file %x nella directory personalizzata %y +Moving folder %x to user-defined directory %y +Spostamento di cartella %x nella directory personalizzata %y +Multiple... +Multiplo... Not enough free disk space available in: -Spazio libero su disco non sufficiente in: +Spazio libero su disco insufficiente in: Nothing to synchronize according to configuration! Niente da sincronizzare in questa configurazione! Number of files and directories that will be created @@ -576,10 +590,8 @@ Number of files that will be overwritten Numero di file che verranno sovrascritti OK OK -Only files/directories that pass filtering will be selected for synchronization. The filter will be applied to the name relative(!) to the synchronization directories. -Solo file e/o directory non filtrati saranno selezionati per la sincronizzazione. Il filtro verrà applicato al nome relativo(!) nelle directory di sincronizzazione. -Open with File Manager\tD-Click -Apri con File Manager\tD-Click +Only files/directories that pass filtering will be selected for synchronization. The filter will be applied to the name relative(!) to the base synchronization directories. +Solo file e directory che passano il filtro saranno selezionati per la sincronizzazione. Il filtro verrà applicato al nome relativo(!) sulla base delle directory di sincronizzazione. Operation aborted! Operazione abortita! Operation: @@ -594,8 +606,6 @@ Please copy the appropriate \"Shadow.dll\" (located in \"Shadow.zip\" archive) i Per abilitare questa funzione è necessario copiare l'appropriata \"Shadow.dll\" (che si trova nell'archivio \"Shadow.zip\") nella directory di installazione di FreeFileSync. Please fill all empty directory fields. Compilare tutti i campi di directory vuoti. -Please specify alternate directory for deletion! -Prego specificare una directory alternativa per la cancellazione! Press button to activate filter Cliccare per attivare il filtro Published under the GNU General Public License: @@ -604,12 +614,16 @@ Question Domanda Quit Esci +Re-enable all hidden dialogs? +Ripristinare tutti i messaggi nascosti? RealtimeSync - Automated Synchronization RealtimeSync - Sincronizzazione Automatizzata RealtimeSync configuration Configurazione di RealtimeSync Relative path Percorso relativo +Remove alternate settings +Rimuovi impostazioni alternative Remove folder Rimuovi cartella Remove folder pair @@ -618,10 +632,6 @@ Report translation error Segnala errori di traduzione Reset Reset -Reset all warning messages -Resetta tutti i messaggi d'avviso -Reset all warning messages? -Resettare tutti i messaggi d'avviso? Result Risultato Right @@ -639,7 +649,11 @@ Analisi in corso... Scanning: Analisi in corso: Select a folder -Selezionare una cartella +Seleziona una cartella +Select alternate filter settings +Seleziona impostazioni di filtro alternative +Select alternate synchronization settings +Seleziona impostazioni di sincronizzazione alternative Select logfile directory: Seleziona cartella per il file di log: Select variant: @@ -660,10 +674,6 @@ Show files that exist on left side only Mostra file esistenti solo a sinistra Show files that exist on right side only Mostra file esistenti solo a destra -Show files that will be copied to the left side -Mostra file da copiare sul lato sinistro -Show files that will be copied to the right side -Mostra file da copiare sul lato destro Show files that will be created on the left side Mostra file che verranno creati sul lato sinistro Show files that will be created on the right side @@ -672,8 +682,14 @@ Show files that will be deleted on the left side Mostra file che verranno cancellati sul lato sinistro Show files that will be deleted on the right side Mostra file che verranno cancellati sul lato destro +Show files that will be overwritten on left side +Mostra file che verranno sovrascritti sul lato sinistro +Show files that will be overwritten on right side +Mostra file che verranno sovrascritti sul lato destro Show files that won't be copied Mostra file che non saranno copiati +Show hidden dialogs +Mostra messaggi nascosti Show popup Mostra popup Show popup on errors or warnings @@ -684,8 +700,6 @@ Silent mode Modalità Silenziosa Size Dimensione -Sorting file list... -Ordinamento lista file... Source code written completely in C++ utilizing: Codice sorgente scritto completamente in C++ \ne utilizzando: Speed: @@ -728,8 +742,6 @@ Target file already existing! File destinazione già esistente! The file does not contain a valid configuration: Il file non contiene una configurazione valida -This commandline will be executed on each doubleclick. The following macros are available: -Questa linea di comando verrà eseguita ad ogni doppio-click. Sono disponibili le seguenti macro: This variant evaluates two equally named files as being equal when they have the same file size AND the same last write date and time. Questa variante definisce identici due file con lo stesso nome quando hanno la stessa dimensione E la stessa data e ora. Time @@ -766,16 +778,20 @@ Use Recycle Bin Usa il Cestino Use Recycle Bin when deleting or overwriting files. Usa il Cestino quando si cancella o sovrascrive un file. +User-defined directory +Directory personalizzata +User-defined directory for deletion was not specified! +Directory personalizzata per la cancellazione non specificata! Variant Variante +Verifying file %x +Verifica di file %x Volume name %x not part of filename %y! Il nome volume %x non è parte del nome file %y! Warning Attenzione Warning: Synchronization failed for %x item(s): Attenzione: Sincronizzazione fallita per %x elementi: -Warnings: -Avvisi: When the comparison is started with this option set the following decision tree is processed: Quando questo set di opzioni viene selezionato per la comparazione viene processato il seguente albero di decisioni: You can ignore the error to consider not existing directories as empty. diff --git a/BUILD/Languages/japanese.lng b/BUILD/Languages/japanese.lng index 0d473b8c..ecd3ccf5 100644 --- a/BUILD/Languages/japanese.lng +++ b/BUILD/Languages/japanese.lng @@ -68,6 +68,8 @@ 読み込み(&L) &Load configuration 構成設定の読み込み(&L) +&New +新規(&N) &No いいえ(&N) &OK @@ -84,8 +86,14 @@ 保存(&S) &Yes はい(&Y) +(Note that only FAT/FAT32 drives are affected by this problem!\nIn all other cases you can disable the setting \"ignore 1-hour difference\".) +(注意: FAT/FAT32 ドライブのみこの影響を受けます! \n その他のケースでは設定を無効にすることができます。"1 時間の差異は無視" ) , . +- Other side's counterpart to %dir +- %dir の反対側の対象 +- Other side's counterpart to %name +- %name の反対側の対象 - conflict - 不一致 - conflict (same date, different size) @@ -110,10 +118,6 @@ - 右側 - right newer - 右側の方が新しい -- sibling of %dir -- %dir の兄弟 -- sibling of %name -- %name の兄弟 -Open-Source file synchronization- -Open-Source ファイル同期ツール- . @@ -154,8 +158,6 @@ Add folder フォルダを追加 Add folder pair フォルダのペアを追加 -All items have been synchronized! -すべてのアイテムは同期されました! An exception occured! 例外が発生しました! As a result the files are separated into the following categories: @@ -208,6 +210,8 @@ Comparing content of files %x ファイル %x の内容を比較中 Comparing content... 内容を比較中... +Comparing files by content failed. +ファイル内容の比較に失敗しました Comparison Result 比較結果 Comparison settings @@ -232,6 +236,8 @@ Confirm 確認 Conflict detected: 検出された競合: +Conflicts/files that cannot be categorized +ファイルに競合があるため分類できません Continue 続行 Conversion error: @@ -257,7 +263,7 @@ Could not determine volume name for file: Could not initialize directory monitoring: 監視するディレクトリを初期化できません: Could not read values for the following XML nodes: - +以下の XMLノードの値を読み込むことが出来ません: Create a batch job 一括ジョブを作成 Creating folder %x @@ -270,16 +276,20 @@ Customize columns 列の調整 Customize... カスタマイズ... +D-Click +D-クリック DECISION TREE [判定ツリー] Data remaining: 残りのデータ: +Data verification error: Source and target file have different content! +データ検証エラー: ソースと対象ファイルに異なる内容が含まれています! Date データ Delay 遅延時間 -Delay between two invocations of the commandline -2 つのコマンドラインを実行する時の遅延時間 +Delay between detection of changes and execution of commandline in seconds +変更の検出とコマンドライン実行間の遅延(秒) Delete files/folders existing on left side only 左側のみに存在するファイル/フォルダを削除 Delete files/folders existing on right side only @@ -300,6 +310,8 @@ Deleting folder %x フォルダ %x を削除中 Deletion handling 削除の取り扱い +Description +説明 Directories are dependent! Be careful when setting up synchronization rules: ディレクトリが依存関係にあります! 同期規則の設定時には注意してください: Directories to watch @@ -320,8 +332,6 @@ Do you really want to move the following objects(s) to the Recycle Bin? 本当に以下のオブジェクト(複)をゴミ箱に移動しますか? Do you want FreeFileSync to automatically check for updates every week? FreeFileSync のアップデートの有無の確認を自動的に毎週行いますか> -Don't ask me again -次回から確認しない Donate with PayPal PayPal から寄付する Download now? @@ -349,7 +359,7 @@ Error deleting directory: Error deleting file: ファイルの削除エラー: Error handling -ハンドリングのエラー +ハンドリングのエラー時: Error loading library function: ライブラリ読み込みエラー: Error moving directory: @@ -392,14 +402,12 @@ Exit immediately and set returncode < 0 すぐに終了する場合の戻り値設定 < 0 Exit with RC < 0 RC で終了 < 0 +External applications +外部アプリケーション Feedback and suggestions are welcome at: フィードバック、提案など: File %x has an invalid date! ファイル %x の日付は無効なものです! -File Manager integration: -ファイラとの統合: -File Time tolerance (seconds): -ファイル時間の許容範囲 (秒): File already exists. Overwrite? ファイルは存在します、上書きしますか? File content @@ -410,12 +418,10 @@ File list exported! ファイル一覧のエクスポートが完了! File size and date ファイルサイズと日付 -File times that differ by up to the specified number of seconds are still handled as having same time. -ファイル時刻の指定された秒以内の誤差は、同じものとして取り扱われます。 Filename ファイル名 -Files %x have a file time difference of less than 1 hour! It's not safe to decide which one is newer due to Daylight Saving Time issues. -ファイル %x の時間には、一時間未満の誤差があります! 夏時間の問題により、より新しい方を時間で決定するのは安全ではありません。 +Files %x have a file time difference of less than 1 hour!\n\nIt's not safe to decide which one is newer due to Daylight Saving Time issues. +ファイル %x の時間には、一時間未満の誤差があります!\n\n夏時間の問題により、より新しい方を時間で決定するのは安全ではありません。 Files %x have the same date but a different size! ファイル %x は、同じ時間ですがサイズが異なっています! Files are found equal if\n - file content\nis the same. @@ -424,6 +430,8 @@ Files are found equal if\n - filesize\n - last write time and date\nare ファイルが同様だった場合\n - ファイルサイズ\n - 最終書き込み時間と日付\nで判断する Files remaining: 残りのファイル: +Files that are equal on both sides +両側で同様のファイル Files that exist on both sides and have different content 両側に存在するが、内容が異なるファイル Files that exist on both sides, left one is newer @@ -448,8 +456,8 @@ Filter view 表示フィルター Folder Comparison and Synchronization フォルダの比較と同期 -Folder pair -フォルダペア +Free disk space available: +利用可能なディスク空き容量: FreeFileSync - Folder Comparison and Synchronization FreeFileSync - Folder Comparison and Synchronization FreeFileSync Batch Job @@ -470,6 +478,8 @@ Global settings 全般的な設定 Help ヘルプ +Hidden dialogs: +ダイアログを隠す Hide all error and warning messages すべてのエラーと警告メッセージを非表示 Hide conflicts @@ -486,10 +496,6 @@ Hide files that exist on left side only 左側のみに存在するファイルを非表示 Hide files that exist on right side only 右側のみに存在するファイルを非表示 -Hide files that will be copied to the left side -左側にコピーしたファイルを非表示にする -Hide files that will be copied to the right side -右側にコピーしたファイルを非表示にする Hide files that will be created on the left side 左側で作成されたファイルを非表示にする Hide files that will be created on the right side @@ -498,6 +504,10 @@ Hide files that will be deleted on the left side 左側で削除されたファイルを非表示にする Hide files that will be deleted on the right side 右側で削除されたファイルを非表示にする +Hide files that will be overwritten on left side +左側で上書きされたファイルを非表示にする +Hide files that will be overwritten on right side +右側で上書きされたファイルを非表示にする Hide files that won't be copied コピーしなかったファイルを隠す Hide filtered items @@ -530,12 +540,16 @@ Info 情報 Information インフォメーション -Initialization of Recycle Bin failed! -ゴミ箱の初期化に失敗しました! +Integrate external applications into context menu. The following macros are available: +外部のアプリケーションをコンテキストメニューに統合、以下のマクロが利用できます: It was not possible to initialize the Recycle Bin!\n\nIt's likely that you are not using Windows.\nIf you want this feature included, please contact the author. :) ゴミ箱を初期化することが出来ませんでした!\n\n使用している OS が、Windows 以外の OS です。\nこの機能を使用したい場合は、作者までご連絡ください :) +Leave as unresolved conflict +未解決の競合を残す Left 左側 +Legend +凡例 Load configuration from file 外部ファイルから構成設定を読み込みます Load configuration history (press DEL to delete items) @@ -554,16 +568,16 @@ Move column down 列を下に移動 Move column up 列を上に移動 -Move files to a custom directory. -ファイルを指定ディレクトリに移動 -Move to custom directory -指定ディレクトリに移動 +Move files to a user-defined directory. +ユーザ定義ディレクトリにファイルを移動 Moving %x to Recycle Bin %x をゴミ箱に移動中 -Moving %x to custom directory -%x を指定ディレクトリに移動中 -Not all items have been synchronized! Have a look at the list. -すべてのアイテムは同期されていません、リストを確認してください +Moving file %x to user-defined directory %y +ファイル %x をユーザ定義ディレクトリ %y に移動 +Moving folder %x to user-defined directory %y +フォルダ %x をユーザ定義ディレクトリ %y に移動 +Multiple... +複数処理... Not enough free disk space available in: 利用可能なディスク空き容量が足りません: Nothing to synchronize according to configuration! @@ -576,10 +590,8 @@ Number of files that will be overwritten 上書きされたファイル数 OK OK -Only files/directories that pass filtering will be selected for synchronization. The filter will be applied to the name relative(!) to the synchronization directories. -フィルタリングをパスしたファイル/ディレクトリのみが同期対象に選択されます。 フィルターは同期先ディレクトリの同類の名前(!)に対して適用されます。 -Open with File Manager\tD-Click -ファイラーから開く\tD-クリック +Only files/directories that pass filtering will be selected for synchronization. The filter will be applied to the name relative(!) to the base synchronization directories. +フィルターに適合するファイル/ディレクトリのみ同期対象に選択されます。このフィルターは、同期基準ディレクトリへの相対的(!)な名前に適用されます。 Operation aborted! 操作の中断! Operation: @@ -594,8 +606,6 @@ Please copy the appropriate \"Shadow.dll\" (located in \"Shadow.zip\" archive) i \"Shadow.dll\" (\"Shadow.zip\" アーカイブ内) を、FreeFileSync インストールフォルダにコピーすることで、この機能を利用できるようになります。 Please fill all empty directory fields. アイテムが選択されていません! -Please specify alternate directory for deletion! -削除に使用する代替ディレクトリを指定してください! Press button to activate filter ボタンをクリックで有効化 Published under the GNU General Public License: @@ -604,12 +614,16 @@ Question 質問 Quit 終了 +Re-enable all hidden dialogs? +すべての非表示ダイアログを有効にしますか? RealtimeSync - Automated Synchronization リアルタイム同期 - 自動同期 RealtimeSync configuration リアルタイム同期の構成設定 Relative path 相対パス +Remove alternate settings +代替設定を除去 Remove folder フォルダ除去 Remove folder pair @@ -618,10 +632,6 @@ Report translation error 翻訳エラーの詳細 Reset リセット -Reset all warning messages -すべての警告をリセット -Reset all warning messages? -すべての警告をリセットしますか? Result 結果 Right @@ -640,6 +650,10 @@ Scanning: スキャン: Select a folder フォルダを選択 +Select alternate filter settings +代替フィルター設定を選択 +Select alternate synchronization settings +代替同期設定を選択 Select logfile directory: ログファイルの保存先を選択: Select variant: @@ -660,10 +674,6 @@ Show files that exist on left side only 左側のみに存在するファイルを表示 Show files that exist on right side only 右側のみに存在するファイルを表示 -Show files that will be copied to the left side -左側にコピーされたファイルを表示 -Show files that will be copied to the right side -右側にコピーされたファイルを表示 Show files that will be created on the left side 左側で作成されたファイルを表示 Show files that will be created on the right side @@ -672,8 +682,14 @@ Show files that will be deleted on the left side 左側で削除されたファイルを表示 Show files that will be deleted on the right side 右側で削除されたファイルを表示 +Show files that will be overwritten on left side +左側で上書きされたファイルを表示 +Show files that will be overwritten on right side +右側で上書きされたファイルを表示 Show files that won't be copied コピーされなかったファイルを表示 +Show hidden dialogs +非表示ダイアログを表示 Show popup ポップアップ表示 Show popup on errors or warnings @@ -684,8 +700,6 @@ Silent mode サイレントモード Size サイズ -Sorting file list... -一覧のソート中... Source code written completely in C++ utilizing: ソースコードは C++ で書かれ、コンパイルされています: Speed: @@ -728,8 +742,6 @@ Target file already existing! 対象ファイルは既に存在します! The file does not contain a valid configuration: このファイルには有効な構成が含まれていません: -This commandline will be executed on each doubleclick. The following macros are available: -このコマンドラインはダブルクリックで実行されます。また、以下のマクロを利用可能です: This variant evaluates two equally named files as being equal when they have the same file size AND the same last write date and time. この変数では、ふたつの同名ファイルが存在した場合、 それぞれのファイルサイズと最終更新日付/時間を比較します。 Time @@ -766,16 +778,20 @@ Use Recycle Bin ゴミ箱を使用 Use Recycle Bin when deleting or overwriting files. ファイルの削除、上書き時にゴミ箱を使用する +User-defined directory +ユーで定義ディレクトリ +User-defined directory for deletion was not specified! +ユーザ定義ディレクトリが指定されていません! Variant 変化 +Verifying file %x +ファイル %x の検証中 Volume name %x not part of filename %y! ボリューム名 %x にファイル名 %y はありません! Warning 警告 Warning: Synchronization failed for %x item(s): 警告: %x アイテムの同期に失敗しました: -Warnings: -警告: When the comparison is started with this option set the following decision tree is processed: このオプションで比較を開始した場合は、以下のツリーに従って処理が行われます: You can ignore the error to consider not existing directories as empty. diff --git a/BUILD/Languages/polish.lng b/BUILD/Languages/polish.lng index 0a8b7f08..14474715 100644 --- a/BUILD/Languages/polish.lng +++ b/BUILD/Languages/polish.lng @@ -68,6 +68,8 @@ &Wczytaj &Load configuration &Wczytaj konfigurację +&New +&Nowy &No &Nie &OK @@ -84,8 +86,14 @@ &Zapisz &Yes &Tak +(Note that only FAT/FAT32 drives are affected by this problem!\nIn all other cases you can disable the setting \"ignore 1-hour difference\".) +(Pamiętaj, że ten problem dotyczy tylko FAT/FAT32!\nW każdym innym przypadku możesz wyłączyć opcję \"Ignoruj 1-godzinną różnicę\".) , . +- Other side's counterpart to %dir +- Odpowiednik %dir +- Other side's counterpart to %name +- Odpowiednik %name - conflict - konflikt - conflict (same date, different size) @@ -93,7 +101,7 @@ - different - różny - directory part only -- tylko struktura katalogu +- tylko część katalogu - equal - równy - exists left only @@ -110,10 +118,6 @@ - prawy - right newer - prawy jest nowszy -- sibling of %dir -- brat %dir -- sibling of %name -- brat %name -Open-Source file synchronization- -synchronizacja plików Open-Source- . @@ -154,8 +158,6 @@ Add folder Dodaj folder Add folder pair Dodaj foldery do porównania -All items have been synchronized! -Wszystkie elementy zsynchronizowane! An exception occured! Wystąpił wyjątek! As a result the files are separated into the following categories: @@ -208,6 +210,8 @@ Comparing content of files %x Porównywanie zawartości plików %x Comparing content... Porównywanie zawartości... +Comparing files by content failed. +Porównywanie przez zawartość zakończone niepowodzeniem. Comparison Result Rezultat porównywania Comparison settings @@ -232,6 +236,8 @@ Confirm Potwierdź Conflict detected: Wykryto konflikt: +Conflicts/files that cannot be categorized +Konflikty/pliki, które nie mogą być skategoryzowane Continue Kontynuuj Conversion error: @@ -257,7 +263,7 @@ Nie można określić nazwy dysku dla pliku: Could not initialize directory monitoring: Nie można uruchomić monitora katalogów: Could not read values for the following XML nodes: - +Nie można odczytać wartości dla danych gałęzi XML: Create a batch job Twórz plik wsadowy Creating folder %x @@ -270,16 +276,20 @@ Customize columns Dostosuj kolumny Customize... Dostosuj... +D-Click +Podw. kliknięcie DECISION TREE DRZEWO DECYZYJNE Data remaining: Pozostałe dane: +Data verification error: Source and target file have different content! +Błąd weryfikacji danych: Plik źródłowy i docelowy różnią się zawartością! Date Data Delay Opóźnienie -Delay between two invocations of the commandline -Opóźnienie pomiędzy wywołaniem dwóch komend +Delay between detection of changes and execution of commandline in seconds +Opóźnienie pomiędzy wykryciem zmian, a wykonaniem komendy w sekundach Delete files/folders existing on left side only Usuń pliki/foldery istniejące tylko po lewej stronie Delete files/folders existing on right side only @@ -300,6 +310,8 @@ Deleting folder %x Usuwanie folderu %x Deletion handling Obsługa usuwania +Description +Opis Directories are dependent! Be careful when setting up synchronization rules: Katalogi są zależne! Pamiętaj o tym podczas ustawiania zasad synchronizacji: Directories to watch @@ -320,8 +332,6 @@ Do you really want to move the following objects(s) to the Recycle Bin? Czy na pewno chcesz przenieść wybrane pliki do kosza? Do you want FreeFileSync to automatically check for updates every week? Czy chcesz aby FreeFileSync sprawdzał uaktualnienia co tydzień? -Don't ask me again -Nie pytaj mnie ponownie Donate with PayPal Wesprzyj z PayPal Download now? @@ -392,14 +402,12 @@ Exit immediately and set returncode < 0 Zakończ natychmiastowo i zwróć wartość < 0 Exit with RC < 0 Zakończ z RC < 0 +External applications +Aplikacje zewnętrzne Feedback and suggestions are welcome at: Komentarze i sugestie mile widziane na: File %x has an invalid date! Plik %x ma nieprawidłową datę! -File Manager integration: -Menadżer plików: -File Time tolerance (seconds): -Tolerancja czasu plików (sekundy): File already exists. Overwrite? Nadpisać istniejący już plik? File content @@ -410,12 +418,10 @@ File list exported! Lista plików wyeksportowana! File size and date Rozmiar i data pliku -File times that differ by up to the specified number of seconds are still handled as having same time. -Plik różniące się określoną liczbą sekund traktowane są jako równe. Filename Nazwa pliku -Files %x have a file time difference of less than 1 hour! It's not safe to decide which one is newer due to Daylight Saving Time issues. -Pliki %x różnią się mniej niż 1 godzinę. Nie można bezpiecznie określić, który plik jest nowszy zgodnie z przesunięciem czasu letniego. +Files %x have a file time difference of less than 1 hour!\n\nIt's not safe to decide which one is newer due to Daylight Saving Time issues. +Pliki %x różnią się mniej niż 1 godzinę.\n\nNie można bezpiecznie określić, który plik jest nowszy zgodnie z przesunięciem czasu letniego. Files %x have the same date but a different size! Pliki %x mają tą samą datę lecz różne rozmiary! Files are found equal if\n - file content\nis the same. @@ -424,6 +430,8 @@ Files are found equal if\n - filesize\n - last write time and date\nare Pliki są jednakowe jeżeli\n - rozmiar pliku\n - czas i data modyfikacji\nsą identyczne. Files remaining: Pozostałe pliki: +Files that are equal on both sides +Pliki równe po obu stronach Files that exist on both sides and have different content Pliki, które istnieją po obu stronach i różnią się zawartością Files that exist on both sides, left one is newer @@ -448,8 +456,8 @@ Filter view Filtr podglądu Folder Comparison and Synchronization Porównywanie i Synchronizacja folderów -Folder pair -Para folderów +Free disk space available: +Wolne miejsce: FreeFileSync - Folder Comparison and Synchronization FreeFileSync - Porównywanie i Synchronizacja folderów FreeFileSync Batch Job @@ -470,6 +478,8 @@ Global settings Ustawienia programu Help Pomoc +Hidden dialogs: +Ukryte dialogi: Hide all error and warning messages Ukryj wszystkie informacje błędach i ostrzeżeniach Hide conflicts @@ -486,10 +496,6 @@ Hide files that exist on left side only Ukryj pliki, które istnieją tylko po lewej stronie Hide files that exist on right side only Ukryj pliki, które istnieją tylko po prawej stronie -Hide files that will be copied to the left side -Ukryj pliki, które będą kopiowane na lewą stronę -Hide files that will be copied to the right side -Ukryj pliki, które będą kopiowane na prawą stronę Hide files that will be created on the left side Ukryj pliki, które będą utworzone po lewej stronie Hide files that will be created on the right side @@ -498,6 +504,10 @@ Hide files that will be deleted on the left side Ukryj pliki, które będą usunięte po lewej stronie Hide files that will be deleted on the right side Ukryj pliki, które będą usunięte po lewej stronie +Hide files that will be overwritten on left side +Ukryj pliki, które zostaną nadpisane po lewej stronie +Hide files that will be overwritten on right side +Ukryj pliki, które zostaną nadpisane po prawej stronie Hide files that won't be copied Ukryj pliki, które nie będą kopiowane Hide filtered items @@ -530,12 +540,16 @@ Info Info Information Informacja -Initialization of Recycle Bin failed! -Niepowodzenie inicjalizacji Kosza! +Integrate external applications into context menu. The following macros are available: +Dołącz zewnętrzną aplikację do menu kontekstowego. Dostępne macra: It was not possible to initialize the Recycle Bin!\n\nIt's likely that you are not using Windows.\nIf you want this feature included, please contact the author. :) Inicjalizacja Kosza nie była możliwa!\n\nPrawdopodobnie nie używasz Windowsa.\nJeżeli chcesz dołączyć tą cechę, skontaktuj się z autorem. :) +Leave as unresolved conflict +Zostaw jako nierozwiązany konflikt Left Lewy +Legend +Legenda Load configuration from file Wczytaj konfigurację z pliku Load configuration history (press DEL to delete items) @@ -554,16 +568,16 @@ Move column down Przesuń kolumnę w dół Move column up Przesuń kolumnę do góry -Move files to a custom directory. -Przenieś plik do niestandardowego katalogu. -Move to custom directory -Przenieś do niestandardowego katalogu +Move files to a user-defined directory. +Przenieś pliki do katalogu użytkownika. Moving %x to Recycle Bin Przenoszenie %x do kosza. -Moving %x to custom directory -Przenoszenie %x to niestandardowego katalogu -Not all items have been synchronized! Have a look at the list. -Nie wszystkie elementy zostały zsynchronizowane! Zobacz tą listę. +Moving file %x to user-defined directory %y +Przenoszenie pliku %x do katalogu użytkownia %y +Moving folder %x to user-defined directory %y +Przenoszenie folderu %x do katalogu użytkownika %y +Multiple... +Wiele... Not enough free disk space available in: Brak wystarczającej przestrzeni dyskowej na: Nothing to synchronize according to configuration! @@ -576,10 +590,8 @@ Number of files that will be overwritten Liczba plików, które zostaną nadpisane OK OK -Only files/directories that pass filtering will be selected for synchronization. The filter will be applied to the name relative(!) to the synchronization directories. -Tylko pliki/katalogi, które nie zostaną odrzucone przez filtr dodane będą do procesu synchronizacji. Filtr działa tylko dla nazw wprowadzonych relatywnie(!) -Open with File Manager\tD-Click -Exploruj +Only files/directories that pass filtering will be selected for synchronization. The filter will be applied to the name relative(!) to the base synchronization directories. +Tylko pliki/katalogi zaakceptowane przez filtr będą synchronizowane. Filtr działa dla nazw nazw relatywnych(!) względem katalogu bazowego. Operation aborted! Operacja przerwana! Operation: @@ -594,8 +606,6 @@ Please copy the appropriate \"Shadow.dll\" (located in \"Shadow.zip\" archive) i Skopiuj odpowiedni plik \"Shadow.dll\" (ulokowany w \"Shadow.zip\") do folderu z instalacją FreeFileSync aby uaktywnić tą opcję. Please fill all empty directory fields. Podaj foldery do synchronizacji. -Please specify alternate directory for deletion! -Proszę określić inny katalog jako kosz! Press button to activate filter Kliknij aby aktywować filtr Published under the GNU General Public License: @@ -604,12 +614,16 @@ Question Pytanie Quit Zakończ +Re-enable all hidden dialogs? +Odblokować wszystkie ukryte dialogi? RealtimeSync - Automated Synchronization RealtimeSync - Automatyczna Synchronizacja RealtimeSync configuration RealtimeSync konfiguracja Relative path Relatywna ścieżka +Remove alternate settings +Usuń alternatywne ustawienia Remove folder Usuń folder Remove folder pair @@ -618,10 +632,6 @@ Report translation error Zgłoś błąd w tłumaczeniu Reset Resetuj -Reset all warning messages -Resetuj wszystkie ostrzeżenia -Reset all warning messages? -Zresetować wszystkie ostrzeżenia? Result Rezultat Right @@ -640,6 +650,10 @@ Scanning: Skanowanie: Select a folder Wybierz folder +Select alternate filter settings +Stwórz alternatywne ustawienia filtra +Select alternate synchronization settings +Stwórz alternatywne reguły synchronizacji Select logfile directory: Wybierz katalog z logami: Select variant: @@ -660,10 +674,6 @@ Show files that exist on left side only Pokaż pliki istniejące tylko po lewej stronie Show files that exist on right side only Pokaż pliki istniejące tylko po prawej stronie -Show files that will be copied to the left side -Pokaż pliki, które będą kopiowane na lewą stronę -Show files that will be copied to the right side -Pokaż pliki, które będą kopiowane na prawą stronę Show files that will be created on the left side Pokaż pliki, które będą utworzone po lewej stronie Show files that will be created on the right side @@ -672,8 +682,14 @@ Show files that will be deleted on the left side Pokaż pliki, które będą usunięte po lewej stronie Show files that will be deleted on the right side Pokaż pliki, które będą usunięte po prawej stronie +Show files that will be overwritten on left side +Pokaż pliki, które zostaną nadpisane po lewej stronie +Show files that will be overwritten on right side +Pokaż pliki, które zostaną nadpisane po prawej stronie Show files that won't be copied Pokaż pliki, które nie będą kopiowane +Show hidden dialogs +Pokaż ukryte dialogi Show popup Pokaż okno popup Show popup on errors or warnings @@ -684,8 +700,6 @@ Silent mode Tryb Cichy Size Rozmiar -Sorting file list... -Sortowanie listy plików... Source code written completely in C++ utilizing: Kod źródłowy napisany całkowicie w C++ z wykorzystaniem: Speed: @@ -728,8 +742,6 @@ Target file already existing! Plik docelowy już istnieje! The file does not contain a valid configuration: Nieprawidłowy format pliku: -This commandline will be executed on each doubleclick. The following macros are available: -Ta komenda będzie wykonana przy każdym podwójnym kliknięciu. Dostępne są następujące makra: This variant evaluates two equally named files as being equal when they have the same file size AND the same last write date and time. Ten wariant traktuje dwa pliki jako równe w przypadku gdy mają jednakowy rozmiar oraz tą samą datę i czas ostatniej modyfikacji. Time @@ -766,16 +778,20 @@ Use Recycle Bin Użyj kosza Use Recycle Bin when deleting or overwriting files. Używaj Kosza podczas usuwania lub nadpisywania plików. +User-defined directory +Katalog użytkownika +User-defined directory for deletion was not specified! +Katalog użytkownika dla elementów usuniętych nie został określony! Variant Wariant +Verifying file %x +Weryfikowanie pliku %x Volume name %x not part of filename %y! Dysk %x nie jest częścią pliku %y! Warning Uwaga Warning: Synchronization failed for %x item(s): Uwaga: Błąd synchronizacji dla \"%x\" elementów: -Warnings: -Ostrzeżenia: When the comparison is started with this option set the following decision tree is processed: Gdy porównywanie z zaznaczoną opcją jest w toku, podejmowane są następujące dezyje: You can ignore the error to consider not existing directories as empty. diff --git a/BUILD/Languages/portuguese.lng b/BUILD/Languages/portuguese.lng index e250315d..97d6b758 100644 --- a/BUILD/Languages/portuguese.lng +++ b/BUILD/Languages/portuguese.lng @@ -68,6 +68,8 @@ &Carregar &Load configuration &Carregar configuração +&New +&Novo &No &Não &OK @@ -84,8 +86,14 @@ &Guardar &Yes &Sim +(Note that only FAT/FAT32 drives are affected by this problem!\nIn all other cases you can disable the setting \"ignore 1-hour difference\".) +(Apenas as drives FAT/FAT32 são afectadas por esta causa! \nPara todas as outras situações pode desactivar a opção \"ignorar diferença de 1 hora\".) , +- Other side's counterpart to %dir +- Contrapartida de %dir +- Other side's counterpart to %name +- Contrapartida de %name - conflict - conflito - conflict (same date, different size) @@ -110,12 +118,8 @@ - direita - right newer - mais novo à direita -- sibling of %dir -- irmão de %dir -- sibling of %name -- irmão de %name -Open-Source file synchronization- --Sincronização de ficheiros Open-Source- +-Sincronização open-source de ficheiros- . , /sec @@ -154,8 +158,6 @@ Add folder Adicionar pasta Add folder pair Adicionar um par de pastas -All items have been synchronized! -Todos os itens sincronizados! An exception occured! Ocorreu uma excepção! As a result the files are separated into the following categories: @@ -208,6 +210,8 @@ Comparing content of files %x A comparar o conteúdo do ficheiro %x Comparing content... A comparar... +Comparing files by content failed. +A comparação de ficheiros por conteúdo falhou. Comparison Result Resultados da Comparação Comparison settings @@ -232,6 +236,8 @@ Confirm Confirmar Conflict detected: Conflito detectado: +Conflicts/files that cannot be categorized +Conflitos/ficheiros que não podem ser categorizados Continue Continuar Conversion error: @@ -257,7 +263,7 @@ Não é possível determinar o nome do volume para o ficheiro: Could not initialize directory monitoring: Não é possível iniciar monitorização do directório: Could not read values for the following XML nodes: - +Não foi possível ler os valores dos seguintes nós XML: Create a batch job Criar ficheiro batch Creating folder %x @@ -270,16 +276,20 @@ Customize columns Personalizar colunas Customize... Personalizar... +D-Click +Duplo Clique DECISION TREE ÁRVORE DE DECISÃO Data remaining: Dados em falta: +Data verification error: Source and target file have different content! +Erro na verificação de dados: ficheiro fonte e de destino têm conteúdo diferente! Date Data Delay Atraso -Delay between two invocations of the commandline -Atraso entre duas chamadas da linha de comandos +Delay between detection of changes and execution of commandline in seconds +Atraso entre a detecção de diferenças e a execuçao dos comandos em segundos Delete files/folders existing on left side only Eliminar itens existentes apenas no lado esquerdo Delete files/folders existing on right side only @@ -300,6 +310,8 @@ Deleting folder %x Apagar pasta %x Deletion handling Controlo eliminação +Description +Descrição Directories are dependent! Be careful when setting up synchronization rules: Directórios são dependentes! Cuidado ao definir as regras de sincronização: Directories to watch @@ -320,8 +332,6 @@ Do you really want to move the following objects(s) to the Recycle Bin? Quer mesmo mover o(s) seguinte(s) objecto(s) para a Reciclagem? Do you want FreeFileSync to automatically check for updates every week? Deseja que o FreeFileSync procure automaticamente actualizações todas as semanas? -Don't ask me again -Não perguntar de novo Donate with PayPal Doar usando PayPal Download now? @@ -392,14 +402,12 @@ Exit immediately and set returncode < 0 Sair imediatamente e enviar o código < 0 Exit with RC < 0 Sair com RC < 0 +External applications +Aplicações externas Feedback and suggestions are welcome at: Comentários e sugestões são benvindos em: File %x has an invalid date! Ficheiro %x tem data inválida! -File Manager integration: -Integração c/ Gestor de Ficheiros: -File Time tolerance (seconds): -Tempo de tolerância do ficheiro (segundos): File already exists. Overwrite? O ficheiro já existe. Deseja substituir? File content @@ -410,12 +418,10 @@ File list exported! Lista dos ficheiros exportada! File size and date Data e tamanho do ficheiro -File times that differ by up to the specified number of seconds are still handled as having same time. -Ficheiro com diferença de tempo até ao número especificado de segundos são tratados como tendo o mesmo tempo. Filename Nome do ficheiro -Files %x have a file time difference of less than 1 hour! It's not safe to decide which one is newer due to Daylight Saving Time issues. -Os ficheiros %x têm uma diferença de tempo inferior a 1 hora! Não é seguro decidir qual o mais novo devido às mudanças de Hora de Verão. +Files %x have a file time difference of less than 1 hour!\n\nIt's not safe to decide which one is newer due to Daylight Saving Time issues. +Os ficheiros %x têm uma diferença de tempo inferior a 1 hora!\n\nNão é seguro decidir qual o mais novo devido às mudanças de Hora de Verão. Files %x have the same date but a different size! Os ficheiros %x têm a mesma data, mas tamanho diferente! Files are found equal if\n - file content\nis the same. @@ -424,6 +430,8 @@ Files are found equal if\n - filesize\n - last write time and date\nare Os ficheiros são considerados iguais se\n - o tamanho\n - data e hora são iguais. Files remaining: Ficheiros restantes: +Files that are equal on both sides +Ficheiros iguais dos dois lados Files that exist on both sides and have different content Ficheiros existentes dos dois lados e com conteúdo diferente Files that exist on both sides, left one is newer @@ -448,8 +456,8 @@ Filter view Filtrar vista Folder Comparison and Synchronization Comparação e Sincronização de pastas -Folder pair -Par de pastas +Free disk space available: +Espaço livre em disco: FreeFileSync - Folder Comparison and Synchronization FreeFileSync - Comparação e Sincronização de pastas FreeFileSync Batch Job @@ -470,6 +478,8 @@ Global settings Opções Help Ajuda +Hidden dialogs: +Diálogos ocultos: Hide all error and warning messages Ocultar todas as mensagens de erro ou aviso Hide conflicts @@ -486,10 +496,6 @@ Hide files that exist on left side only Ocultar ficheiros existentes somente à esquerda Hide files that exist on right side only Ocultar ficheiros existentes somente à direita -Hide files that will be copied to the left side -Ocultar ficheiros a copiar para a esquerda -Hide files that will be copied to the right side -Ocultar ficheiros a copiar para a direita Hide files that will be created on the left side Ocultar ficheiros a ser criados à esquerda Hide files that will be created on the right side @@ -498,6 +504,10 @@ Hide files that will be deleted on the left side Ocultar ficheiros a ser apagados à esquerda Hide files that will be deleted on the right side Ocultar ficheiros a ser apagados à direita +Hide files that will be overwritten on left side +Ocultar ficheiros a ser substituidos do lado esquerdo +Hide files that will be overwritten on right side +Ocultar ficheiros a ser substituidos do lado direito Hide files that won't be copied Ocultar ficheiros que não serão copiados Hide filtered items @@ -530,12 +540,16 @@ Info Info Information Informação -Initialization of Recycle Bin failed! -Início da Reciclagem falhou! +Integrate external applications into context menu. The following macros are available: +Integrar aplicações externas no menu de contexto. As seguintes macros estão disponíveis: It was not possible to initialize the Recycle Bin!\n\nIt's likely that you are not using Windows.\nIf you want this feature included, please contact the author. :) Não é possível aceder à Reciclagem!\n\nÉ possível que não esteja a utilizar Windows.\nSe desejar esta opção incluída, é favor contactar o autor. :) +Leave as unresolved conflict +Deixar como conflito Left Esquerda +Legend +Legenda Load configuration from file Carregar configuração do ficheiro Load configuration history (press DEL to delete items) @@ -554,16 +568,16 @@ Move column down Mover coluna para baixo Move column up Mover coluna para cima -Move files to a custom directory. -Mover ficheiros para uma pasta própria. -Move to custom directory -Mover para pasta própria +Move files to a user-defined directory. +Mover ficheiros para um directório definido pelo utilizador. Moving %x to Recycle Bin -Mover %x para a Reciclagem -Moving %x to custom directory -Mover %x para pasta própria -Not all items have been synchronized! Have a look at the list. -Nem todos os ficheiros foram sincronizados! Verifique a lista. +A mover %x para a Reciclagem +Moving file %x to user-defined directory %y +A mover ficheiro %x para o directório &y +Moving folder %x to user-defined directory %y +A mover pasta %x para o directório %y +Multiple... +Multiplo... Not enough free disk space available in: Não há espaço livre suficiente em: Nothing to synchronize according to configuration! @@ -576,10 +590,8 @@ Number of files that will be overwritten Número de ficheiros substituidos OK OK -Only files/directories that pass filtering will be selected for synchronization. The filter will be applied to the name relative(!) to the synchronization directories. -Apenas ficheiros/pastas que passem o filtro serão selecionados para sincronização. O filtro será aplicado no nome relativo às pastas em sincronização. -Open with File Manager\tD-Click -Abrir c/ Gestor de Ficheiros\tD-Click +Only files/directories that pass filtering will be selected for synchronization. The filter will be applied to the name relative(!) to the base synchronization directories. +Apenas ficheiros/directórios que passem o filtro serão seleccionados para sincronização. O filtro será aplicado ao nome relativo(!) ao directório base de sincronização. Operation aborted! Operação abortada! Operation: @@ -594,8 +606,6 @@ Please copy the appropriate \"Shadow.dll\" (located in \"Shadow.zip\" archive) i Por favor copie o \"Shadow.dll\" correcto (localizado no arquivo \"Shadow.zip\") para a pasta da instalação do FreeFileSync para activar esta opção. Please fill all empty directory fields. Por favor, preencha todos os campos vazios. -Please specify alternate directory for deletion! -Por favor especifique directório alternativo para eliminação! Press button to activate filter Pressione para activar o filtro Published under the GNU General Public License: @@ -604,12 +614,16 @@ Question Questão Quit Sair +Re-enable all hidden dialogs? +Reactivar todos os diálogos ocultos? RealtimeSync - Automated Synchronization RealtimeSync - Sincronização Automática RealtimeSync configuration Configuração do RealtimeSync Relative path Caminho +Remove alternate settings +Remover opções alternativas Remove folder Remover pasta(s) Remove folder pair @@ -618,10 +632,6 @@ Report translation error Informar um erro de tradução Reset Reiniciar -Reset all warning messages -Reiniciar todas mensagens de aviso -Reset all warning messages? -Reiniciar todas mensagens de aviso? Result Resultado Right @@ -640,6 +650,10 @@ Scanning: A pesquisar: Select a folder Seleccione uma pasta +Select alternate filter settings +Seleccionar opções alternativas de filtros +Select alternate synchronization settings +Seleccionar opções alternativas de sincronização Select logfile directory: Seleccione directório para ficheiro log: Select variant: @@ -660,10 +674,6 @@ Show files that exist on left side only Mostrar ficheiros existentes somente à esquerda Show files that exist on right side only Mostrar ficheiros existentes somente à direita -Show files that will be copied to the left side -Mostrar ficheiros a copiar para a esquerda -Show files that will be copied to the right side -Mostrar ficheiros a copiar para a direita Show files that will be created on the left side Mostrar ficheiros a ser criados à esquerda Show files that will be created on the right side @@ -672,8 +682,14 @@ Show files that will be deleted on the left side Mostrar ficheiros a ser apagados à esquerda Show files that will be deleted on the right side Mostrar ficheiros a ser apagados à direita +Show files that will be overwritten on left side +Mostrar ficheiros a ser substituidos do lado esquerdo +Show files that will be overwritten on right side +Mostrar ficheiros a ser substituidos do lado direito Show files that won't be copied Mostrar ficheiros que não serão copiados +Show hidden dialogs +Mostrar diálogos ocultos Show popup Mostrar popups Show popup on errors or warnings @@ -684,8 +700,6 @@ Silent mode Modo silencioso Size Tamanho -Sorting file list... -Ordenar lista de ficheiros... Source code written completely in C++ utilizing: Código fonte todo escrito em C++ utilizando: Speed: @@ -728,8 +742,6 @@ Target file already existing! Ficheiro de destino já existe! The file does not contain a valid configuration: O ficheiro não contém uma configuração válida: -This commandline will be executed on each doubleclick. The following macros are available: -Esta linha de comandos será executada a cada duplo clique. As seguintes macros estão disponíveis: This variant evaluates two equally named files as being equal when they have the same file size AND the same last write date and time. Esta variante avalia dois ficheiros de nome igual como iguais quando têm o mesmo tamanho e a mesma data e hora de modificação. Time @@ -766,16 +778,20 @@ Use Recycle Bin Utilizar Reciclagem Use Recycle Bin when deleting or overwriting files. Utilizar Reciclagem ao apagar ou substituir ficheiros. +User-defined directory +Directório definido pelo utilizador +User-defined directory for deletion was not specified! +Directório para eliminação não foi especificado! Variant Variável +Verifying file %x +A verificar ficheiro %x Volume name %x not part of filename %y! Nome do volume %x não faz parte do ficheiro %y! Warning Atenção Warning: Synchronization failed for %x item(s): Atenção: A sincronização falhou para %x item(s): -Warnings: -Avisos: When the comparison is started with this option set the following decision tree is processed: Usar a seguinte árvore de decisão quando inicia com estas opções de comparação: You can ignore the error to consider not existing directories as empty. diff --git a/BUILD/Languages/portuguese_br.lng b/BUILD/Languages/portuguese_br.lng index 5d6060b3..0839d747 100644 --- a/BUILD/Languages/portuguese_br.lng +++ b/BUILD/Languages/portuguese_br.lng @@ -68,6 +68,8 @@ &Carregar &Load configuration &Carregar configuração +&New +&Novo &No &Não &OK @@ -79,13 +81,19 @@ &Restore &Restaurar &Retry -&Tentar de Novo +&Tentar Novamente &Save &Salvar &Yes &Sim +(Note that only FAT/FAT32 drives are affected by this problem!\nIn all other cases you can disable the setting \"ignore 1-hour difference\".) +(Note que apenas discos FAT/FAT32 são afetados por este problema!\nEm todos os outros casos pode-se desabilitar esta opção \"ignorar diferença de 1 hora\".) , . +- Other side's counterpart to %dir +- Correspondente do outro lado a %dir +- Other side's counterpart to %name +- Correspondente do outro lado a %name - conflict - conflito - conflict (same date, different size) @@ -110,10 +118,6 @@ - direita - right newer - mais recente à direita -- sibling of %dir -- equivalente de %dir -- sibling of %name -- equivalente de %name -Open-Source file synchronization- -Sincronização de Arquivos Open-Source- . @@ -154,16 +158,14 @@ Add folder Adicionar pasta Add folder pair Adicionar par de pastas -All items have been synchronized! -Todos os itens foram sincronizados! An exception occured! Ocorreu uma exceção! As a result the files are separated into the following categories: Como resultado, os arquivos são separados nas seguintes categorias: As the name suggests, two files which share the same name are marked as equal if and only if they have the same content. This option is useful for consistency checks rather than backup operations. Therefore the file times are not taken into account at all.\n\nWith this option enabled the decision tree is smaller: -Como o nome sugere, dois arquivos com o mesmo nome são assinalados como iguais se e somente se o seu conteúdo for idêntico. Esta opção é útil para controles de consistência mais do que para efeitos de backup. Portanto, a data dos arquivos não é levada em consideração.\n\nCom esta opção, a árvore de decisão é menor: +Como o nome sugere, dois arquivos com o mesmo nome são assinalados como iguais se e somente se eles tiverem o mesmo conteúdo. Esta opção é útil para controles de consistência mais do que para efeitos de backup. Portanto, a data dos arquivos não é levada em consideração.\n\nCom esta opção, a árvore de decisão é menor: Assemble a batch file for automated synchronization. To start in batch mode simply pass the name of the file to the FreeFileSync executable: FreeFileSync.exe <batchfile>. This can also be scheduled in your operating system's task planner. -Criar um arquivo batch para sincronização automatizada. Para iniciar o modo batch, passar o nome do arquivo para o executável do FreeFileSync: FreeFileSync.exe <arquivo batch>. Também pode ser programado no Agendador de Tarefas do sistema operacional. +Monta um arquivo batch para sincronização automatizada. Para iniciar o modo batch, passar o nome do arquivo para o executável do FreeFileSync: FreeFileSync.exe <arquivo batch>. Também pode ser programado no Agendador de Tarefas do sistema operacional. Auto-adjust columns Autoajustar colunas Batch execution @@ -208,6 +210,8 @@ Comparing content of files %x Comparando conteúdo do arquivo %x Comparing content... Comparando conteúdo... +Comparing files by content failed. +Comparação de arquivos pelo conteúdo falhou. Comparison Result Resultado da Comparação Comparison settings @@ -232,6 +236,8 @@ Confirm Confirmar Conflict detected: Conflito detectado: +Conflicts/files that cannot be categorized +Conflitos/arquivos que não podem ser categorizados Continue Continuar Conversion error: @@ -257,7 +263,7 @@ Não foi possível determinar o nome do volume para o arquivo: Could not initialize directory monitoring: Não foi possível inicializar o monitoramento de diretórios: Could not read values for the following XML nodes: - +Não foi possível ler os valores para os seguintes nós XML: Create a batch job Criar arquivo batch Creating folder %x @@ -270,16 +276,20 @@ Customize columns Personalizar colunas Customize... Personalizar... +D-Click +Duplo Click DECISION TREE ÁRVORE DE DECISÃO Data remaining: Dados faltantes: +Data verification error: Source and target file have different content! +Erro de verificação de dados: Arquivo de origem e destino têm o mesmo conteúdo! Date Data Delay Atraso -Delay between two invocations of the commandline -Atraso entre duas chamadas de linhas de comando +Delay between detection of changes and execution of commandline in seconds +Atraso entre detecção de mudanças e execução de linha de comando em segundos Delete files/folders existing on left side only Apagar arquivos/pastas existentes apenas no lado esquerdo Delete files/folders existing on right side only @@ -300,6 +310,8 @@ Deleting folder %x Apagando pasta %x Deletion handling Tratamento da exclusão +Description +Descrição Directories are dependent! Be careful when setting up synchronization rules: Diretórios são dependentes! Cuidado ao definir as regras de sincronização: Directories to watch @@ -320,8 +332,6 @@ Do you really want to move the following objects(s) to the Recycle Bin? Quer mesmo mover o(s) seguinte(s) item(s) para a Lixeira? Do you want FreeFileSync to automatically check for updates every week? Deseja que o FreeFileSync procure automaticamente novas versões toda semana? -Don't ask me again -Não perguntar novamente Donate with PayPal Doar usando PayPal Download now? @@ -392,14 +402,12 @@ Exit immediately and set returncode < 0 Sair imediatamente e setar\nreturncode < 0 Exit with RC < 0 Sair com RC < 0 +External applications +Aplicações externas Feedback and suggestions are welcome at: Comentários e sugestões são bem-vindos em: File %x has an invalid date! Arquivo %x tem uma data inválida! -File Manager integration: -Integração com Gerenciador de Arquivos: -File Time tolerance (seconds): -Tolerância de Tempo do Arquivo (segundos): File already exists. Overwrite? O arquivo já existe. Deseja substituir? File content @@ -410,12 +418,10 @@ File list exported! Lista de arquivos exportada! File size and date Data e tamanho do arquivo -File times that differ by up to the specified number of seconds are still handled as having same time. -Arquivos com diferença de horário até o número especificado de segundos são tratados como tendo o mesmo horário. Filename Nome do arquivo -Files %x have a file time difference of less than 1 hour! It's not safe to decide which one is newer due to Daylight Saving Time issues. -Arquivos %x têm diferença no horário de criação menor do que 1 hora! Não é seguro decidir qual é o mais novo devido a problemas relacionados com o Horário de Verão. +Files %x have a file time difference of less than 1 hour!\n\nIt's not safe to decide which one is newer due to Daylight Saving Time issues. +Arquivos %x têm diferença no horário de criação menor do que 1 hora!\n\nNão é seguro decidir qual é o mais novo devido a problemas relacionados com o Horário de Verão. Files %x have the same date but a different size! Arquivos %x têm a mesma data mas tamanhos diferentes! Files are found equal if\n - file content\nis the same. @@ -424,6 +430,8 @@ Files are found equal if\n - filesize\n - last write time and date\nare Os arquivos são considerados iguais se\n - o tamanho\n - e a data e hora da última modificação\nsão iguais. Files remaining: Arquivos restantes: +Files that are equal on both sides +Arquivos que são iguais em ambos os lados Files that exist on both sides and have different content Arquivos que existem nos dois lados e têm conteúdo diferente Files that exist on both sides, left one is newer @@ -448,8 +456,8 @@ Filter view Filtrar vista Folder Comparison and Synchronization Comparação e Sincronização de Pastas -Folder pair -Par de pastas +Free disk space available: +Espaço livre em disco: FreeFileSync - Folder Comparison and Synchronization FreeFileSync - Comparação e Sincronização de Pastas FreeFileSync Batch Job @@ -470,6 +478,8 @@ Global settings Configurações Help Ajuda +Hidden dialogs: +Diálogos ocultos: Hide all error and warning messages Ocultar todas as mensagens de erro ou aviso Hide conflicts @@ -486,10 +496,6 @@ Hide files that exist on left side only Ocultar arquivos que existem somente à esquerda Hide files that exist on right side only Ocultar arquivos que existem somente à direita -Hide files that will be copied to the left side -Ocultar arquivos que serão copiados para o lado esquerdo -Hide files that will be copied to the right side -Ocultar arquivos que serão copiados para o lado direito Hide files that will be created on the left side Ocultar arquivos que serão criados no lado esquerdo Hide files that will be created on the right side @@ -498,6 +504,10 @@ Hide files that will be deleted on the left side Ocultar arquivos que serão apagados no lado esquerdo Hide files that will be deleted on the right side Ocultar arquivos que serão apagados no lado direito +Hide files that will be overwritten on left side +Ocultar arquivos que serão substituídos no lado esquerdo +Hide files that will be overwritten on right side +Ocultar arquivos que serão substituídos no lado direito Hide files that won't be copied Ocultar arquivos que não serão copiados Hide filtered items @@ -511,7 +521,7 @@ Homepage If you like FFS Se gosta do FFS Ignore 1-hour file time difference -Ignorar diferença de tempo do arquivo de 1 hora +Ignorar diferença de tempo de 1 hora Ignore errors Ignorar erros Ignore subsequent errors @@ -530,12 +540,16 @@ Info Info Information Informação -Initialization of Recycle Bin failed! -Inicialização da Lixeira falhou! +Integrate external applications into context menu. The following macros are available: +Integrar aplicações externas no menu de contexto. As seguintes macros estão disponíveis: It was not possible to initialize the Recycle Bin!\n\nIt's likely that you are not using Windows.\nIf you want this feature included, please contact the author. :) Não foi possível acessar a Lixeira!\n\nÉ possível que não esteja utilizando Windows.\nSe desejar esta opção incluída, favor contatar o autor. :) +Leave as unresolved conflict +Deixar como conflito não resolvido Left Esquerda +Legend +Legenda Load configuration from file Carregar configuração do arquivo Load configuration history (press DEL to delete items) @@ -554,16 +568,16 @@ Move column down Mover coluna para baixo Move column up Mover coluna para cima -Move files to a custom directory. -Mover arquivos para um diretório escolhido. -Move to custom directory -Mover para um diretório escolhido +Move files to a user-defined directory. +Mover arquivos para um diretório especificado. Moving %x to Recycle Bin Movendo %x para a Lixeira -Moving %x to custom directory -Movendo %x para o diretório escolhido -Not all items have been synchronized! Have a look at the list. -Nem todos os itens foram sincronizados! Olhe a lista. +Moving file %x to user-defined directory %y +Movendo arquivo %x para o diretório especificado +Moving folder %x to user-defined directory %y +Movendo pasta %x para o diretório especificado +Multiple... +Múltiplos... Not enough free disk space available in: Espaço em disco insuficiente em: Nothing to synchronize according to configuration! @@ -576,10 +590,8 @@ Number of files that will be overwritten Número de arquivos que serão substituídos OK OK -Only files/directories that pass filtering will be selected for synchronization. The filter will be applied to the name relative(!) to the synchronization directories. -Apenas arquivos/diretórios que passarem a filtragem serão selecionados para sincronização. O filtro será aplicado ao nome em relação(!) aos diretórios de sincronização. -Open with File Manager\tD-Click -Abrir com Gerenciador de Arquivos\tD-Click +Only files/directories that pass filtering will be selected for synchronization. The filter will be applied to the name relative(!) to the base synchronization directories. +Apenas arquivos/diretórios que passarem o filtro serão selecionados para sincronização. O filtro será aplicado ao nome relativo(!) aos diretórios base de sincronização. Operation aborted! Operação cancelada! Operation: @@ -594,8 +606,6 @@ Please copy the appropriate \"Shadow.dll\" (located in \"Shadow.zip\" archive) i Por favor, copie o \"Shadow.dll\" apropriado (localizado no arquivo \"Shadow.zip\") no diretório de instalação do FreeFileSync para habilitar essa funcionalidade. Please fill all empty directory fields. Por favor, preencha todos os campos de diretórios vazios. -Please specify alternate directory for deletion! -Por favor, especifique diretório alternativo para exclusão! Press button to activate filter Pressione para ativar o filtro Published under the GNU General Public License: @@ -604,12 +614,16 @@ Question Questão Quit Sair +Re-enable all hidden dialogs? +Reabilitar todos os diálogos ocultos? RealtimeSync - Automated Synchronization RealtimeSync - Sincronização Automátizada RealtimeSync configuration Configuração do RealtimeSync Relative path Caminho relativo +Remove alternate settings +Remover configurações alternativas Remove folder Remover pasta Remove folder pair @@ -618,10 +632,6 @@ Report translation error Reportar erro de tradução Reset Reiniciar -Reset all warning messages -Resetar todas as mensagens de aviso -Reset all warning messages? -Resetar todas as mensagens de aviso? Result Resultado Right @@ -640,6 +650,10 @@ Scanning: Pesquisando: Select a folder Selecione uma pasta +Select alternate filter settings +Selecionar configuração de filtro alternativa +Select alternate synchronization settings +Selecionar configuração de sincronização alternativa Select logfile directory: Escolha um diretório para salvar o arquivo log: Select variant: @@ -660,10 +674,6 @@ Show files that exist on left side only Mostrar arquivos que existem somente à esquerda Show files that exist on right side only Mostrar arquivos que existem somente à direita -Show files that will be copied to the left side -Mostrar arquivos que serão copiados para o lado esquerdo -Show files that will be copied to the right side -Mostrar arquivos que serão copiados para o lado direito Show files that will be created on the left side Mostrar arquivos que serão criados no lado esquerdo Show files that will be created on the right side @@ -672,8 +682,14 @@ Show files that will be deleted on the left side Mostrar arquivos que serão apagados no lado esquerdo Show files that will be deleted on the right side Mostrar arquivos que serão apagados no lado direito +Show files that will be overwritten on left side +Mostrar arquivos que serão substituídos no lado esquerdo +Show files that will be overwritten on right side +Mostrar arquivos que serão substituídos no lado direito Show files that won't be copied Mostrar arquivos que não serão copiados +Show hidden dialogs +Mostrar diálogos ocultos Show popup Mostrar popup Show popup on errors or warnings @@ -684,8 +700,6 @@ Silent mode Modo silencioso Size Tamanho -Sorting file list... -Ordenando lista de arquivos... Source code written completely in C++ utilizing: Código fonte todo escrito em C++ utilizando: Speed: @@ -728,8 +742,6 @@ Target file already existing! Arquivo de destino já existe! The file does not contain a valid configuration: O arquivo não contém uma configuração válida: -This commandline will be executed on each doubleclick. The following macros are available: -Esta linha de comando será executada em cada click-duplo. As seguintes macros estão disponíveis: This variant evaluates two equally named files as being equal when they have the same file size AND the same last write date and time. Esta variante avalia dois arquivos de nomes equivalentes como sendo iguais quando têm o mesmo tamanho E a mesma data e hora de modificação. Time @@ -753,33 +765,37 @@ Não foi possível conectar a sourceforge.net! Unable to create logfile! Não foi possível criar arquivo log! Unable to initialize Recycle Bin! -Não foi possível inicializar a Lixeira! +Não foi possível abrir a Lixeira! Uncheck all Desmarcar todos Unresolved conflicts existing! \n\nYou can ignore conflicts and continue synchronization. -Conflitos não resolvidos existentes! \n\nVocê pode ignorar conflitos e continuar a sincronização. +Conflitos não resolvidos existentes! \n\nVocê pode ignorar os conflitos e continuar a sincronização. Update -> Atualizar -> Usage: Select directories for monitoring and enter a commandline. Each time files are modified within these directories (or subdirectories) the commandline is executed. -Uso: Selecionar diretórios para monitoração e entre com uma linha de comando. Cada vez que os arquivos são modificados nesses diretórios (ou subdiretórios) a linha de comando é executada. +Uso: Selecionar diretórios para monitoração e entrar com linha de comando. Cada vez que os arquivos são modificados nesses diretórios (ou subdiretórios) a linha de comando é executada. Use Recycle Bin Utilizar Lixeira Use Recycle Bin when deleting or overwriting files. -Usar a Lixeira quando apagar ou sobrescrever arquivos. +Usar a Lixeira quando apagar ou substituir arquivos. +User-defined directory +Diretório especificado +User-defined directory for deletion was not specified! +Diretório especificado para arquivos apagados não foi definido! Variant Variante +Verifying file %x +Verificando arquivo %x Volume name %x not part of filename %y! Nome do volume %x não é parte do arquivo %y! Warning Atenção Warning: Synchronization failed for %x item(s): Atenção: A sincronização falhou para %x item(s): -Warnings: -Avisos: When the comparison is started with this option set the following decision tree is processed: Quando a comparação é iniciada com esta opção, a seguinte árvore de decisão é processada: You can ignore the error to consider not existing directories as empty. -Você pode ignorar o erro para considerar como diretórios não existente como vazios. +Você pode ignorar o erro para considerar diretórios não existente como vazios. You may try to synchronize remaining items again (WITHOUT having to re-compare)! Você pode tentar sincronizar os elementos restantes outra vez (SEM ter que comparar novamente)! different diff --git a/BUILD/Languages/russian.lng b/BUILD/Languages/russian.lng index 231757e3..c6300e78 100644 --- a/BUILD/Languages/russian.lng +++ b/BUILD/Languages/russian.lng @@ -68,6 +68,8 @@ &Загрузить &Load configuration &Загрузить конфигурацию... +&New +&Новая &No &Нет &OK @@ -84,8 +86,14 @@ &Сохранить &Yes &Да +(Note that only FAT/FAT32 drives are affected by this problem!\nIn all other cases you can disable the setting \"ignore 1-hour difference\".) +(Помните, что только диски FAT/FAT32 страдают от этой проблемы!\nВо всех остальных случаях вы можете отключить параметр \"игнорировать 1-часовую разницу\".) , . +- Other side's counterpart to %dir +- аналогичная папка с другой стороны +- Other side's counterpart to %name +- аналогичный файл с другой стороны - conflict - конфликт - conflict (same date, different size) @@ -110,10 +118,6 @@ - правые - right newer - правые новее -- sibling of %dir -- аналогичная папка с другой стороны -- sibling of %name -- аналогичный файл с другой стороны -Open-Source file synchronization- Синхронизатор файлов с открытым исходным кодом . @@ -154,8 +158,6 @@ Add folder Добавить папку Add folder pair Добавить папку -All items have been synchronized! -Все пункты были синхронизированы! An exception occured! Исключение произошло! As a result the files are separated into the following categories: @@ -208,6 +210,8 @@ Comparing content of files %x Сравнение содержания файлов %x Comparing content... Сравнение содержания... +Comparing files by content failed. +Сравнение файлов по содержимому провалено. Comparison Result Результаты сравнения Comparison settings @@ -232,6 +236,8 @@ Confirm Подтвердить Conflict detected: Обнаружен конфликт: +Conflicts/files that cannot be categorized +Конфликты/файлы, которые не могут быть отнесены к какой-либо категории Continue Продолжить Conversion error: @@ -270,16 +276,20 @@ Customize columns Выбор колонок Customize... Выбрать колонки... +D-Click +Двойной клик DECISION TREE Древо решений Data remaining: Осталось: +Data verification error: Source and target file have different content! +Ошибка проверки данных: исходный и конечный файлы имеют разное содержание! Date Дата Delay Задержка -Delay between two invocations of the commandline -Задержка между двумя обращениями к командной строке +Delay between detection of changes and execution of commandline in seconds +Задержка между обнаружением изменений и выполнением командной строки в секундах Delete files/folders existing on left side only Удалять файлы/папки, существующие только на левой стороне Delete files/folders existing on right side only @@ -291,7 +301,7 @@ Delete on both sides Delete on both sides even if the file is selected on one side only Удалить с обеих сторон, если даже файл выделен только на одной стороне Delete or overwrite files permanently. -Удалять или перезаписать файлы, не помещая в Корзину +Удалять или перезаписать файлы, не помещая в "Корзину" Delete permanently Удалять, не помещая в "Корзину" Deleting file %x @@ -300,6 +310,8 @@ Deleting folder %x Удаление папки %x Deletion handling Настройки удаления +Description +Описание Directories are dependent! Be careful when setting up synchronization rules: Зависимые папки! Будьте внимательны при настройке правил синхронизации: Directories to watch @@ -317,11 +329,9 @@ Do nothing Do you really want to delete the following objects(s)? Вы действительно хотите удалить следующие объекты? Do you really want to move the following objects(s) to the Recycle Bin? -Вы действительно хотите отправить следующие объекты в Корзину? +Вы действительно хотите отправить следующие объекты в "Корзину"? Do you want FreeFileSync to automatically check for updates every week? Вы хотите, чтобы FreeFileSync автоматически проверял наличие обновлений каждую неделю? -Don't ask me again -Не спрашивать меня больше Donate with PayPal Отправить деньги через PayPal Download now? @@ -357,7 +367,7 @@ Error moving directory: Error moving file: Ошибка перемещения файла: Error moving to Recycle Bin: -Ошибка при отправке в Корзину: +Ошибка при отправке в "Корзину": Error opening file: Ошибка при открытии файла: Error parsing configuration file: @@ -392,14 +402,12 @@ Exit immediately and set returncode < 0 Выйти немедленно и установить код возврата <0 Exit with RC < 0 Выход с RC <0 +External applications +Внешние приложения Feedback and suggestions are welcome at: Отзывы и предложения присылайте по адресу: File %x has an invalid date! Файл %x имеет недействительную дату! -File Manager integration: -Интеграция в Проводник: -File Time tolerance (seconds): -Допуск по времени файла (секунд): File already exists. Overwrite? Файл уже существует. Перезаписать? File content @@ -410,12 +418,10 @@ File list exported! Список файлов экспортирован! File size and date Размер и дата файла -File times that differ by up to the specified number of seconds are still handled as having same time. -Время файлов отличающееся на определенное количество секунд рассматривается как равное. Filename Имя файла -Files %x have a file time difference of less than 1 hour! It's not safe to decide which one is newer due to Daylight Saving Time issues. -Файлы %x имеют время отличающееся менее чем на 1 час! Решать какой новее небезопасно ввиду перехода на летнее время. +Files %x have a file time difference of less than 1 hour!\n\nIt's not safe to decide which one is newer due to Daylight Saving Time issues. +Файлы %x имеют время отличающееся менее чем на 1 час!\n\nРешать какой новее небезопасно ввиду перехода на летнее время. Files %x have the same date but a different size! Файлы %x имеют одинаковую дату, но различаются по размеру! Files are found equal if\n - file content\nis the same. @@ -424,6 +430,8 @@ Files are found equal if\n - filesize\n - last write time and date\nare Файлы считаются равными, если одинаковые\n - размер файла\n - дата и время последнего изменения Files remaining: Файлов осталось: +Files that are equal on both sides +Файлы, одинаковые с обеих сторон Files that exist on both sides and have different content Файлы, существующие на обоих сторонах и имеющие разное содержание Files that exist on both sides, left one is newer @@ -448,8 +456,8 @@ Filter view Вид фильтра Folder Comparison and Synchronization Сравнение и синхронизация -Folder pair -Дополнительная папка +Free disk space available: +Доступно свободного места на диске: FreeFileSync - Folder Comparison and Synchronization FreeFileSync - Сравнение и синхронизация FreeFileSync Batch Job @@ -470,6 +478,8 @@ Global settings Глобальные настройки Help Помощь +Hidden dialogs: +Скрытые диалоги Hide all error and warning messages Скрывать все ошибки и сообщения с предупреждениями Hide conflicts @@ -486,10 +496,6 @@ Hide files that exist on left side only Скрыть файлы, существующие только слева Hide files that exist on right side only Скрыть файлы, существующие только справа -Hide files that will be copied to the left side -Скрыть файлы, которые будут скопированы на левую сторону -Hide files that will be copied to the right side -Скрыть файлы, которые будут скопированы на правую сторону Hide files that will be created on the left side Скрыть файлы, которые будут созданы на левой стороне Hide files that will be created on the right side @@ -498,6 +504,10 @@ Hide files that will be deleted on the left side Скрыть файлы, которые будут удалены на левой стороне Hide files that will be deleted on the right side Скрыть файлы, которые будут удалены на правой стороне +Hide files that will be overwritten on left side +Скрыть файлы, которые будут перезаписаны на левой стороне +Hide files that will be overwritten on right side +Скрыть файлы, которые будут перезаписаны на правой стороне Hide files that won't be copied Скрыть файлы, которые не будут скопированы Hide filtered items @@ -530,12 +540,16 @@ Info Информация Information Информация -Initialization of Recycle Bin failed! -Инициализация Корзины провалена! +Integrate external applications into context menu. The following macros are available: +Интегрирует внешние приложения в контекстное меню.\nСледующие команды доступны: It was not possible to initialize the Recycle Bin!\n\nIt's likely that you are not using Windows.\nIf you want this feature included, please contact the author. :) -Невозможно инициализировать Корзину!\n\nВероятно, что Вы не используете Windows.\nЕсли Вы хотите, чтобы эта функция была, пожалуйста свяжитесь с автором.:) +Невозможно инициализировать "Корзину"!\n\nВероятно, что Вы не используете Windows.\nЕсли Вы хотите, чтобы эта функция была, пожалуйста свяжитесь с автором. :) +Leave as unresolved conflict +Оставить как нерешенный конфликт Left Слева +Legend +Легенда Load configuration from file Загрузить конфигурацию из файла Load configuration history (press DEL to delete items) @@ -554,16 +568,16 @@ Move column down Переместить вниз Move column up Переместить вверх -Move files to a custom directory. -Перемещать файлы в выбранную папку -Move to custom directory -Перемещать в выбранную папку +Move files to a user-defined directory. +Переместить файлы в заданную пользователем папку Moving %x to Recycle Bin -Отправка %x в Корзину -Moving %x to custom directory -Переместить %x в выбранную папку -Not all items have been synchronized! Have a look at the list. -Не все пункты были синхронизированы! Посмотрите список. +Отправка %x в "Корзину" +Moving file %x to user-defined directory %y +Перемещение файла %x в заданную пользователем папку %y +Moving folder %x to user-defined directory %y +Перемещение папки %x в заданную пользователем папку %y +Multiple... +Различные варианты синхронизации Not enough free disk space available in: Не достаточно свободного места в: Nothing to synchronize according to configuration! @@ -576,10 +590,8 @@ Number of files that will be overwritten Число файлов, которые будут перезаписаны OK OK -Only files/directories that pass filtering will be selected for synchronization. The filter will be applied to the name relative(!) to the synchronization directories. -Только файлы/папки, которые проходят фильтрацию, будут отобраны для синхронизации. Этот фильтр будет применяться к пути папки. -Open with File Manager\tD-Click -Открыть в Проводнике...\tD-Click +Only files/directories that pass filtering will be selected for synchronization. The filter will be applied to the name relative(!) to the base synchronization directories. +Только файлы/папки, которые проходят фильтрацию будут отобраны для синхронизации. Фильтр будет применяться ко всем именам, относящимся к основным синхронизируемым папкам. Operation aborted! Операция отменена! Operation: @@ -594,8 +606,6 @@ Please copy the appropriate \"Shadow.dll\" (located in \"Shadow.zip\" archive) i Пожалуйста, скопируйте соответствующий \"Shadow.dll\" (находящийся в архиве \"Shadow.zip\") в папку установки FreeFileSync для включения этой функции. Please fill all empty directory fields. Пожалуйста, заполните все пустые поля папок -Please specify alternate directory for deletion! -Просьба указать альтернативную папку для удаленых файлов! Press button to activate filter Включить фильтр Published under the GNU General Public License: @@ -604,12 +614,16 @@ Question Вопрос Quit Выход +Re-enable all hidden dialogs? +Показать все скрытые диалоги? RealtimeSync - Automated Synchronization RealtimeSync - Автоматическая синхронизация RealtimeSync configuration Настройка RealtimeSync Relative path Относительный путь +Remove alternate settings +Удалить альтернативные настройки Remove folder Удалить папку Remove folder pair @@ -618,10 +632,6 @@ Report translation error Сообщить об ошибке перевода Reset Сбросить -Reset all warning messages -Сбросить все сообщения с предупреждениями -Reset all warning messages? -Сбросить все сообщения с предупреждениями? Result Описание Right @@ -640,6 +650,10 @@ Scanning: Сканирую: Select a folder Выбрать папку +Select alternate filter settings +Выбрать альтернативные настройки фильтра +Select alternate synchronization settings +Выбрать альтернативные настройки синхронизации Select logfile directory: Выберите папку для лог-файлов: Select variant: @@ -660,10 +674,6 @@ Show files that exist on left side only Показать файлы, существующие только слева Show files that exist on right side only Показать файлы, существующие только справа -Show files that will be copied to the left side -Показать файлы, которые будут скопированы на левую сторону -Show files that will be copied to the right side -Показать файлы, которые будут скопированы на правую сторону Show files that will be created on the left side Показать файлы, которые будут созданы на левой стороне Show files that will be created on the right side @@ -672,8 +682,14 @@ Show files that will be deleted on the left side Показать файлы, которые будут удалены на левой стороне Show files that will be deleted on the right side Показать файлы, которые будут удалены на правой стороне +Show files that will be overwritten on left side +Показать файлы, которые будут перезаписаны на левой стороне +Show files that will be overwritten on right side +Показать файлы, которые будут перезаписаны на правой стороне Show files that won't be copied Показать файлы, которые не будут скопированы +Show hidden dialogs +Показать скрытые диалоги Show popup Показывать всплывающие окна Show popup on errors or warnings @@ -684,8 +700,6 @@ Silent mode Скрытый режим Size Размер -Sorting file list... -Формирование списка файлов... Source code written completely in C++ utilizing: Исходный код написан на С++ с использованием: Speed: @@ -728,8 +742,6 @@ Target file already existing! Конечный файл уже существует! The file does not contain a valid configuration: Файл не содержит действительную конфигурацию: -This commandline will be executed on each doubleclick. The following macros are available: -Эта командная строка будет выполняться при каждом двойном клике. Следующие макросы доступны: This variant evaluates two equally named files as being equal when they have the same file size AND the same last write date and time. Этот вариант сравнивает два файла с одинаковыми именами и считает их равными, если они имеют одинаковый размер файла и одинаковую дату и время последнего изменения. Time @@ -753,7 +765,7 @@ Unable to connect to sourceforge.net! Unable to create logfile! Невозможно создать лог! Unable to initialize Recycle Bin! -Невозможно инициализировать Корзину! +Невозможно инициализировать "Корзину"! Uncheck all Снять выделение со всех Unresolved conflicts existing! \n\nYou can ignore conflicts and continue synchronization. @@ -766,16 +778,20 @@ Use Recycle Bin Использовать "Корзину" Use Recycle Bin when deleting or overwriting files. Использовать "Корзину" при удалении или перезаписи файлов +User-defined directory +Использовать заданную пользователем папку +User-defined directory for deletion was not specified! +Пользовательская папка для удаления не была указана! Variant Вариант +Verifying file %x +Проверка файла %x Volume name %x not part of filename %y! Имя тома %x не является частью имени файла %y! Warning Внимание Warning: Synchronization failed for %x item(s): Внимание: Синхронизация провалена для %x -Warnings: -Предупреждения: When the comparison is started with this option set the following decision tree is processed: Когда сравнение запущено с этими критериями, алгоритм следующий: You can ignore the error to consider not existing directories as empty. diff --git a/BUILD/Languages/slovenian.lng b/BUILD/Languages/slovenian.lng index 5e77766b..93a7e4f8 100644 --- a/BUILD/Languages/slovenian.lng +++ b/BUILD/Languages/slovenian.lng @@ -1,5 +1,5 @@ MinGW \t- Windows port of GNU Compiler Collection\n wxWidgets \t- Open-Source GUI framework\n wxFormBuilder\t- wxWidgets GUI-builder\n CodeBlocks \t- Open-Source IDE - MinGW \t- Windows port od GNU Compiler Collection\n wxWidgets \t- Open-Source GUI framework\n wxFormBuilder\t- wxWidgets GUI-builder\n CodeBlocks \t- Open-Source IDE + MinGW \t- Windows port od GNU Compiler Collection\n wxWidgets \t- Open-Source GUI ogrodje\n wxFormBuilder\t- wxWidgets GUI-builder\n CodeBlocks \t- Open-Source IDE Byte Byte GB @@ -68,6 +68,8 @@ &Naloži &Load configuration Na&loži konfiguracijo +&New +&Novo &No &Ne &OK @@ -84,8 +86,14 @@ Na&loži konfiguracijo &Shrani &Yes &Da +(Note that only FAT/FAT32 drives are affected by this problem!\nIn all other cases you can disable the setting \"ignore 1-hour difference\".) +(Vedite, da samo na pogonih s FAT/FAT32 lahko pride do teh težav!\nV vseh drugih primerih lahko onemogočite nastavitev \"prezri 1-urno razliko\".) , , +- Other side's counterpart to %dir +Duplikat z druge strani od %dir +- Other side's counterpart to %name +Duplikat z druge strani od %name - conflict - spor - conflict (same date, different size) @@ -110,10 +118,6 @@ Na&loži konfiguracijo - desno - right newer - desna novejša -- sibling of %dir -- v sorodu z %dir -- sibling of %name -- v sorodu z %name -Open-Source file synchronization- -Odprto-kodna sinhronizacija datotek- . @@ -154,8 +158,6 @@ Add folder Dodaj mapo Add folder pair Dodaj par imenikov -All items have been synchronized! -Vsi predmeti so bili sinhronizirani! An exception occured! Zgodila se je napaka! As a result the files are separated into the following categories: @@ -208,6 +210,8 @@ Comparing content of files %x Primerjam vsebino datotek %x Comparing content... Primerjam vsebino... +Comparing files by content failed. +Primerjava datotek po vsebini ni uspela. Comparison Result Rezultati primerjave Comparison settings @@ -232,6 +236,8 @@ Confirm Potrdi Conflict detected: Zaznan spor: +Conflicts/files that cannot be categorized +Prisotni so spori/datoteke, ki ne morejo biti kategorizirani Continue Nadaljuj Conversion error: @@ -257,7 +263,7 @@ Ne morem določiti imena volumna za datoteko: Could not initialize directory monitoring: Ne morem začeti nadzorovanja imenikov: Could not read values for the following XML nodes: - +Ne morem brati vrednosti za naslednja XML vozlišča: Create a batch job Ustvari batch opravilo Creating folder %x @@ -270,16 +276,20 @@ Customize columns Stolpce prikroji po meri Customize... Prilagodi... +D-Click +D-Klik DECISION TREE DREVO ODLOČITEV Data remaining: Preostanek podatkov: +Data verification error: Source and target file have different content! +Napaka pri preverjanju podatkov: izvorna in tarčna datoteka imata različno vsebino! Date Datum Delay Zakasnitev -Delay between two invocations of the commandline -Zakasnitev med dvema pozivoma ukazne vrstice +Delay between detection of changes and execution of commandline in seconds +Zakasnitev med zaznavo sprememb in izvršitvijo ukazov v sekundah Delete files/folders existing on left side only Izbriši datoteke/mape, ki obstajajo samo na levi strani Delete files/folders existing on right side only @@ -300,6 +310,8 @@ Deleting folder %x Brisanje mape %x Deletion handling Postopanje pri brisanju +Description +Opis Directories are dependent! Be careful when setting up synchronization rules: Imeniki so v odvisnosti! Bodite pozorni, ko nastavljate sinhronizacijska pravila: Directories to watch @@ -320,8 +332,6 @@ Do you really want to move the following objects(s) to the Recycle Bin? Ali resnično želite premakniti naslednje objekte v Koš? Do you want FreeFileSync to automatically check for updates every week? Ali želite, da FreeFileSync samodejno preverja za posodobitve vsak teden? -Don't ask me again -Ne sprašuj me več Donate with PayPal Doniraj s PayPal Download now? @@ -392,14 +402,12 @@ Exit immediately and set returncode < 0 Takoj zapusti in vrni povratno kodo < 0 Exit with RC < 0 Zapusti z RC < 0 +External applications +Zunanje aplikacije Feedback and suggestions are welcome at: Povratne informacije in predlogi so dobrodošli na: File %x has an invalid date! Datoteka %x ima neveljaven datum! -File Manager integration: -Integracija z urejevalnikom datotek: -File Time tolerance (seconds): -Toleranca v času zadnje spremembe datoteke (sekunde): File already exists. Overwrite? Datoteka že obstaja. Prepišem? File content @@ -410,12 +418,10 @@ File list exported! Seznam datotek je bil izvožen! File size and date Velikosti in datumu datoteke -File times that differ by up to the specified number of seconds are still handled as having same time. -Časi sprememb datotek, ki se razlikujejo do navedenega števila sekund so smatrani kot da imajo isti čas spremembe. Filename Ime datoteke -Files %x have a file time difference of less than 1 hour! It's not safe to decide which one is newer due to Daylight Saving Time issues. -Datoteki %x imata razliko v času spremembe manj kot 1 uro! Ni se varno odločiti katera je novejša zaradi vprašanj v zvezi s Poletnim časom. +Files %x have a file time difference of less than 1 hour!\n\nIt's not safe to decide which one is newer due to Daylight Saving Time issues. +Datoteki %x imata razliko v času spremembe manj kot 1 uro!\n\nNi se varno odločiti katera je novejša zaradi vprašanj v zvezi s Poletnim časom. Files %x have the same date but a different size! Datoteki %x imata enak datum ampak različno velikost! Files are found equal if\n - file content\nis the same. @@ -424,6 +430,8 @@ Files are found equal if\n - filesize\n - last write time and date\nare Datoteki sta enaki, če so\n - velikost datoteke\n - zadnji čas spremembe in datum\nenaki. Files remaining: Preostale datoteke: +Files that are equal on both sides +Datoteki, ki sta enaki na obeh straneh Files that exist on both sides and have different content Datoteke, ki obstajajo na obeh straneh in imajo različno vesbino Files that exist on both sides, left one is newer @@ -448,8 +456,8 @@ Filter view Filtriran pogled Folder Comparison and Synchronization Primerjava in sinhronizacija imenika -Folder pair -Par map +Free disk space available: +Prosti disk, ki je na voljo: FreeFileSync - Folder Comparison and Synchronization FreeFileSync - Primerjava in sinhronizacija map FreeFileSync Batch Job @@ -470,6 +478,8 @@ Global settings Globalne nastavitve Help Pomoč +Hidden dialogs: +Skriti pogovori: Hide all error and warning messages Skrij vsa obvestila o napakah in opozorilih Hide conflicts @@ -486,10 +496,6 @@ Hide files that exist on left side only Skrij datoteke, ki obstajajo samo na levi strani Hide files that exist on right side only Skrij datoteke, ki obstajajo samo na desni strani -Hide files that will be copied to the left side -Skrij datoteke, ki bodo kopirane na levo stran -Hide files that will be copied to the right side -Skrij datoteke, ki bodo kopirane na desno stran Hide files that will be created on the left side Skrij datoteke, ki bodo ustvarjene na levi strani Hide files that will be created on the right side @@ -498,6 +504,10 @@ Hide files that will be deleted on the left side Skrij datoteke, ki bodo izbrisane na levi strani Hide files that will be deleted on the right side Skrij datoteke, ki bodo izbrisane na desni strani +Hide files that will be overwritten on left side +Skrij datoteke, ki bodo prepisane na levi strani +Hide files that will be overwritten on right side +Skrij datoteke, ki bodo prepisane na desni strani Hide files that won't be copied Skrij datoteke, ki ne bodo kopirane Hide filtered items @@ -530,12 +540,16 @@ Info Info Information Informacije -Initialization of Recycle Bin failed! -Inicializacija Koša ni uspela! +Integrate external applications into context menu. The following macros are available: +Integriraj zunanje aplikacije v kontekstni menu. Na voljo so naslednji makri: It was not possible to initialize the Recycle Bin!\n\nIt's likely that you are not using Windows.\nIf you want this feature included, please contact the author. :) Ni bilo mogoče inicializirati Koša!\n\nZgleda, da ne uporabljate Windowsov.\nČe želite imeti vključeno to lastnost, prosimo kontaktirajte avtorja. :) +Leave as unresolved conflict +Pusti kot nerešeni spor Left Levo +Legend +Legenda Load configuration from file Naloži konfiguracijo iz datoteke Load configuration history (press DEL to delete items) @@ -554,20 +568,20 @@ Move column down Premakni stolpec dol Move column up Premakni stolpec gor -Move files to a custom directory. -Premakni datoteke v imenik po meri. -Move to custom directory -Premakni v imenik po meri +Move files to a user-defined directory. +Premakne datoteke v uporabniško-določen imenik. Moving %x to Recycle Bin Premikam %x v Koš -Moving %x to custom directory -Premikam %x v imenik po meri -Not all items have been synchronized! Have a look at the list. -Vse stvari niso bile sinhronizirane! Preglejte seznam. +Moving file %x to user-defined directory %y +Premikam datoteko %x v uporabniško-določen imenik %y +Moving folder %x to user-defined directory %y +Premikam mapo %x v uporabniško-določen imenik %y +Multiple... +Večkratno... Not enough free disk space available in: Na voljo ni dovolj prostega prostora na disku v: Nothing to synchronize according to configuration! -Nič ni za sinhronizirati po trenutni konfiguraciji! +Po trenutni konfiguraciji ni nič za sinhronizirati! Number of files and directories that will be created Število datotek in imenikov, ki bodo ustvarjeni Number of files and directories that will be deleted @@ -576,10 +590,8 @@ Number of files that will be overwritten Število datotek, ki bodo prepisane OK V redu -Only files/directories that pass filtering will be selected for synchronization. The filter will be applied to the name relative(!) to the synchronization directories. -Samo datoteke/imeniki, ki preidejo skozi filtriranje bodo izbrane za sinhronizacijo. Filter bo uveljavljen na ime relativno(!) glede na sinhronizacijske imenike. -Open with File Manager\tD-Click -Odpri z urejevalnikom datotek\tD-Click +Only files/directories that pass filtering will be selected for synchronization. The filter will be applied to the name relative(!) to the base synchronization directories. +Samo datoteke/imeniki, ki preidejo filtriranje bodo izbrani za sinhronizacijo. Filter bo uveljavljnen k imenu relativno(!) na bazo sinhroniziranih imenikov. Operation aborted! Operacija prekinjena! Operation: @@ -594,8 +606,6 @@ Please copy the appropriate \"Shadow.dll\" (located in \"Shadow.zip\" archive) i Prosim prekopirajte ustrezno \"Shadow.dll\" (ki se nahaja v \"Shadow.zip\" arhivu) v FreeFileSync namestitveni imenik, da omogočite to lastnost. Please fill all empty directory fields. Prosim izpolnite vse imenike s praznimi polji. -Please specify alternate directory for deletion! -Prosim navedite alternativen imenik za brisanje! Press button to activate filter Kliknite za aktivacijo filtra Published under the GNU General Public License: @@ -604,12 +614,16 @@ Question Vprašanje Quit Zapusti +Re-enable all hidden dialogs? +Ponovno omogočim skrite pogovore? RealtimeSync - Automated Synchronization RealtimeSync - Avtomatizirana sinhronizacija RealtimeSync configuration RealtimeSync nastavitve Relative path Relativna pot +Remove alternate settings +Odstrani nadomestne nastavitve Remove folder Odstrani v mapo Remove folder pair @@ -618,10 +632,6 @@ Report translation error Poročaj o napaki prevoda Reset Ponastavi -Reset all warning messages -Ponastavi vsa obvestila z opozorili -Reset all warning messages? -Ponastavim vsa obvestila z opozorili? Result Rezultat Right @@ -640,6 +650,10 @@ Scanning: Pregledujem: Select a folder Izberite mapo +Select alternate filter settings +Izberite nadomestne nastavitve filtra +Select alternate synchronization settings +Izberite nadomestne nastavitve sinhronizacije Select logfile directory: Izberite imenik datoteke za beleženje: Select variant: @@ -660,10 +674,6 @@ Show files that exist on left side only Prikaži datoteke, ki obstajajo samo na levi Show files that exist on right side only Prikaži datoteke, ki obstajajo samo na desni -Show files that will be copied to the left side -Prikaži datoteke, ki bodo kopirane na levo stran -Show files that will be copied to the right side -Prikaži datoteke, ki bodo kopirane na desno stran Show files that will be created on the left side Prikaži datoteke, ki bodo ustvarjene na levi strani Show files that will be created on the right side @@ -672,8 +682,14 @@ Show files that will be deleted on the left side Prikaži datoteke, ki bodo izbrisane na levi strani Show files that will be deleted on the right side Prikaži datoteke, ki bodo izbrisane na desni strani +Show files that will be overwritten on left side +Prikaži datoteke, ki bodo prepisane na levi strani +Show files that will be overwritten on right side +Prikaži datoteke, ki bodo prepisane na desni strani Show files that won't be copied Prikaži datoteke, ki ne bodo kopirane +Show hidden dialogs +Prikaži skrite pogovore Show popup Prikaži pojavno okno Show popup on errors or warnings @@ -684,8 +700,6 @@ Silent mode Tihi način Size Velikost -Sorting file list... -Sortiram seznam datotek... Source code written completely in C++ utilizing: Izvorna koda napisana celotno v C++ z uporabo: Speed: @@ -723,13 +737,11 @@ Sinhroniziram... System out of memory! Sistemu je zmanjkalo pomnilnika! Target directory already existing! - +Ciljni imenik že obstaja! Target file already existing! Ciljna datoteka že obstaja! The file does not contain a valid configuration: Datoteka ne vsebuje veljavne konfiguracije: -This commandline will be executed on each doubleclick. The following macros are available: -Ta ukazna vrstica bo izvedena ob vsakem dvokliku. Na voljo so naslednji makri: This variant evaluates two equally named files as being equal when they have the same file size AND the same last write date and time. Ta varianta oceni dve datoteki z enakim imenom kot enaki, ko imata enako velikost IN enak datum ter čas zadnjega spreminjanja. Time @@ -766,16 +778,20 @@ Use Recycle Bin Uporabi Koš Use Recycle Bin when deleting or overwriting files. Uporabi Koš pri brisanju ali prepisovanju datotek. +User-defined directory +Uporabniško-določen imenik +User-defined directory for deletion was not specified! +Uporabniško-določen imenik za brisanje ni bil naveden! Variant Različica +Verifying file %x +Preverjam datoteko %x Volume name %x not part of filename %y! Ime volumna %x ni del imena datoteke %y! Warning Pozor Warning: Synchronization failed for %x item(s): Pozor: Sinhronizacija ni uspela za %x predmetov: -Warnings: -Opozorila: When the comparison is started with this option set the following decision tree is processed: Ko se primerjava zažene s tem setom možnosti, se obdela naslednje drevo odločitev: You can ignore the error to consider not existing directories as empty. diff --git a/BUILD/Languages/spanish.lng b/BUILD/Languages/spanish.lng index c3029efc..ff290214 100644 --- a/BUILD/Languages/spanish.lng +++ b/BUILD/Languages/spanish.lng @@ -68,6 +68,8 @@ &Cargar &Load configuration &Cargar configuración +&New + &No &OK @@ -84,8 +86,14 @@ &Guardar &Yes +(Note that only FAT/FAT32 drives are affected by this problem!\nIn all other cases you can disable the setting \"ignore 1-hour difference\".) + , , +- Other side's counterpart to %dir + +- Other side's counterpart to %name + - conflict - conflict (same date, different size) @@ -110,10 +118,6 @@ - derecha - right newer - el más nuevo en la derecha -- sibling of %dir - -- sibling of %name - -Open-Source file synchronization- -Sincronización de ficheros Open-Source- . @@ -154,8 +158,6 @@ Add folder Add folder pair Añadir un par de carpetas -All items have been synchronized! -¡Todos los elementos han sido sincronizados! An exception occured! ¡Ha ocurrido una excepción! As a result the files are separated into the following categories: @@ -208,6 +210,8 @@ Comparing content of files %x Comparación del contenido de %x ficheros Comparing content... +Comparing files by content failed. + Comparison Result Comparison settings @@ -232,6 +236,8 @@ Confirm Confirmar Conflict detected: +Conflicts/files that cannot be categorized + Continue Continuar Conversion error: @@ -270,15 +276,19 @@ Customize columns Personalizar columnas Customize... +D-Click + DECISION TREE ÁRBOL DE DECISIÓN Data remaining: Datos que faltan: +Data verification error: Source and target file have different content! + Date Fecha Delay -Delay between two invocations of the commandline +Delay between detection of changes and execution of commandline in seconds Delete files/folders existing on left side only Eliminar sólo ficheros/carpetas existentes en el lado izquierdo @@ -300,6 +310,8 @@ Deleting folder %x Borrar carpeta %x Deletion handling +Description + Directories are dependent! Be careful when setting up synchronization rules: ¡Los directorios son dependientes! Cuidado al establecer las reglas de sincronización: Directories to watch @@ -320,8 +332,6 @@ Do you really want to move the following objects(s) to the Recycle Bin? ¿Está seguro de querer mover el/los siguiente(s) objeto(s) a la Papelera? Do you want FreeFileSync to automatically check for updates every week? -Don't ask me again - Donate with PayPal Donar usando PayPal Download now? @@ -392,14 +402,12 @@ Exit immediately and set returncode < 0 Salir inmediatamente y enviar el código < 0 Exit with RC < 0 Salir com RC < 0 +External applications + Feedback and suggestions are welcome at: Los comentarios y sugerencias será bienvenidos en: File %x has an invalid date! -File Manager integration: -Integración con el Explorador: -File Time tolerance (seconds): - File already exists. Overwrite? El fichero ya existe. ¿Sustituir? File content @@ -410,11 +418,9 @@ File list exported! Lista de ficheros exportada! File size and date Fecha y tamaño del fichero -File times that differ by up to the specified number of seconds are still handled as having same time. -Las horas de los ficheros que difieren hasta el número de segundos especificados, son consideradas iguales. Filename Nombre del fichero -Files %x have a file time difference of less than 1 hour! It's not safe to decide which one is newer due to Daylight Saving Time issues. +Files %x have a file time difference of less than 1 hour!\n\nIt's not safe to decide which one is newer due to Daylight Saving Time issues. Files %x have the same date but a different size! @@ -424,6 +430,8 @@ Files are found equal if\n - filesize\n - last write time and date\nare Los ficheros serán considerados iguales si\n - la hora y fecha de la última escritura\nson iguales. Files remaining: Ficheros restantes: +Files that are equal on both sides + Files that exist on both sides and have different content Ficheros que existen en ambos lados y tienen contenidos diferentes Files that exist on both sides, left one is newer @@ -448,8 +456,8 @@ Filter view Vista de filtros Folder Comparison and Synchronization Carpeta de Comparación y Sincronización -Folder pair -Par de carpetas +Free disk space available: + FreeFileSync - Folder Comparison and Synchronization FreeFileSync - Comparación y Sincronización de carpetas FreeFileSync Batch Job @@ -470,6 +478,8 @@ Global settings Opciones globales Help Ayuda +Hidden dialogs: + Hide all error and warning messages Ocultar todos los mensajes de error y aviso Hide conflicts @@ -486,10 +496,6 @@ Hide files that exist on left side only Ocultar los ficheros que existen sólo en el lado izquierdo Hide files that exist on right side only Ocultar los ficheros que existen sólo en el lado derecho -Hide files that will be copied to the left side - -Hide files that will be copied to the right side - Hide files that will be created on the left side Hide files that will be created on the right side @@ -498,6 +504,10 @@ Hide files that will be deleted on the left side Hide files that will be deleted on the right side +Hide files that will be overwritten on left side + +Hide files that will be overwritten on right side + Hide files that won't be copied Hide filtered items @@ -530,12 +540,16 @@ Info Info Information Información -Initialization of Recycle Bin failed! -¡El inicio de la Papelera falló! +Integrate external applications into context menu. The following macros are available: + It was not possible to initialize the Recycle Bin!\n\nIt's likely that you are not using Windows.\nIf you want this feature included, please contact the author. :) ¡No fue posible iniciar la Papelera!\n\nEs probable que no esté usando Windows.\nSi quiere que este hecho sea considerado, por favor, contactate con el autor :) +Leave as unresolved conflict + Left +Legend + Load configuration from file Cargar configuración desde fichero Load configuration history (press DEL to delete items) @@ -554,15 +568,15 @@ Move column down Mover columna abajo Move column up Mover columna arriba -Move files to a custom directory. - -Move to custom directory +Move files to a user-defined directory. Moving %x to Recycle Bin -Moving %x to custom directory +Moving file %x to user-defined directory %y + +Moving folder %x to user-defined directory %y -Not all items have been synchronized! Have a look at the list. +Multiple... Not enough free disk space available in: @@ -576,10 +590,8 @@ Number of files that will be overwritten Número de ficheros que serán sustituídos OK OK -Only files/directories that pass filtering will be selected for synchronization. The filter will be applied to the name relative(!) to the synchronization directories. +Only files/directories that pass filtering will be selected for synchronization. The filter will be applied to the name relative(!) to the base synchronization directories. -Open with File Manager\tD-Click -Abrir con el Explorador\tD-Click Operation aborted! Operación abortada! Operation: @@ -594,8 +606,6 @@ Please copy the appropriate \"Shadow.dll\" (located in \"Shadow.zip\" archive) i Please fill all empty directory fields. Por favor, rellene todos los campos del directorio vacíos. -Please specify alternate directory for deletion! - Press button to activate filter Presione el botón para activar el filtro Published under the GNU General Public License: @@ -604,12 +614,16 @@ Question Quit Salir +Re-enable all hidden dialogs? + RealtimeSync - Automated Synchronization RealtimeSync configuration Relative path Camino relativo +Remove alternate settings + Remove folder Remove folder pair @@ -618,10 +632,6 @@ Report translation error Reset Reiniciar -Reset all warning messages - -Reset all warning messages? -¿Reiniciar todos los mensajes de aviso? Result Resultado Right @@ -640,6 +650,10 @@ Scanning: Analizar: Select a folder Seleccione una carpeta +Select alternate filter settings + +Select alternate synchronization settings + Select logfile directory: Select variant: @@ -660,10 +674,6 @@ Show files that exist on left side only Mostrar ficheros existentes en la izquierda sólamente Show files that exist on right side only Mostrar ficheros existentes en la derecha sólamente -Show files that will be copied to the left side - -Show files that will be copied to the right side - Show files that will be created on the left side Show files that will be created on the right side @@ -672,8 +682,14 @@ Show files that will be deleted on the left side Show files that will be deleted on the right side +Show files that will be overwritten on left side + +Show files that will be overwritten on right side + Show files that won't be copied +Show hidden dialogs + Show popup Mostrar "popups" Show popup on errors or warnings @@ -684,8 +700,6 @@ Silent mode Modo silencioso Size Tamaño -Sorting file list... -Ordenar lista de ficheros... Source code written completely in C++ utilizing: Código fuente escrito en C++ utilizando: Speed: @@ -728,8 +742,6 @@ Target file already existing! ¡El fichero objetivo existe ya! The file does not contain a valid configuration: El fichero no contiene una configuración válida: -This commandline will be executed on each doubleclick. The following macros are available: - This variant evaluates two equally named files as being equal when they have the same file size AND the same last write date and time. Esta variante evalúa dos ficheros con el mismo nombre como iguales cuando tienen el mismo tamaño Y la misma fecha de modificación. Time @@ -766,16 +778,20 @@ Use Recycle Bin Utilizar la Papelera Use Recycle Bin when deleting or overwriting files. +User-defined directory + +User-defined directory for deletion was not specified! + Variant +Verifying file %x + Volume name %x not part of filename %y! Warning Atención Warning: Synchronization failed for %x item(s): Atención: La sincronización falló para %x item(s): -Warnings: -Avisos: When the comparison is started with this option set the following decision tree is processed: Cuando la comparación se inicia con este conjunto de opciones, se procesa el siguiente árbol de decisiones: You can ignore the error to consider not existing directories as empty. diff --git a/BUILD/Readme.txt b/BUILD/Readme.txt index 0e359fc5..20576738 100644 --- a/BUILD/Readme.txt +++ b/BUILD/Readme.txt @@ -1,4 +1,4 @@ -FreeFileSync v2.2 +FreeFileSync v2.3 ----------------- --------- @@ -63,7 +63,7 @@ FreeFileSync v2.2 - Create a FreeFileSync batch file using "silent mode". - Set error handling to "Exit with Returncode < 0" or "ignore errors" to avoid having a popup stop the program flow. In case errors occur FreeFileSync will abort with a returncode < 0 which can be checked via the ERRORLEVEL command. -- Create a *.cmd or *.bat file and specify the location of FreeFileSync.exe and pass the name of the FreeFileSync batch file as %1 parameter; e.g.: +- Create a *.cmd or *.bat file and specify the location of FreeFileSync.exe and pass the name of the FreeFileSync batch file as first argument; e.g.: C:\Program Files\FreeFileSync\FreeFileSync.exe C:\SyncJob.ffs_batch IF NOT ERRORLEVEL 0 echo An error occurred! && pause @@ -93,12 +93,12 @@ You can: - drag & drop any directory onto the main window to set the directory f 4.) Exclude all subfolders from synchronization -Assuming you plan to synchronize two folders "C:\Source" and "D:\Target", simply set up a filter like this: +If you want to synchronize all files from the base synchronization directories only, simply set up a filter like this: Include: * - Exclude: *\* + Exclude: *\ -This will exclude all files and folders within the two directories that contain a "\" character. These are exactly the subfolders and -files you want to exclude. +This will exclude all objects within the two directories that end with a "\" character, which is interpreted as the end of a directory name, 5.) Synchronize with FTP @@ -108,15 +108,17 @@ FreeFileSync does not support FTP directly. But the FTP functionality can be eas Example: Use the free utility NetDrive (http://www.netdrive.net/) - Add a "New Site" and specify site name, site URL, drive letter, account and password. - Use the newly created drive as if it were a regular hard disk. +- Note: Most FTP drives set a file's timestamp to current time when synchronizing. As a workaround you can try a "compare by filesize", see below. -6.) Start associated program on double-click +6.) Start external application on double-click -FreeFileSync's default is to show files in the operating system's standard file browser on double-click e.g. by invoking "explorer /select, %name" on Windows. -If the file shall be started with its associated application instead, all you have to do is the following: -On main dialog navigate to: Menu -> Advanced -> Global settings: File Manager integration. Then replace the command string by +FreeFileSync's default is to show files in the operating system's standard file browser on each double-click e.g. by invoking "explorer /select, %name" on Windows. +If some other application shall be started instead, just navigate to "Menu -> Advanced -> Global settings: External Applications" and replace the command string: - cmd /c start "" "%name" +Examples: - Start associated application: cmd /c start "" "%name" + - Start visual difference tool: C:\Program Files\WinMerge\WinMergeU.exe "%name" "%nameCo" +(Don't forget to use quotation marks if file names contain spaces!) 7. Synchronize USB sticks with variable drive letter @@ -130,6 +132,22 @@ USB sticks often have different volume names assigned to them when plugged into => Working directory automatically is set to "E:\" by the operating system so that "\SyncDir" is interpreted as "E:\SyncDir". Now start synchronization as usual. +8. Start RealtimeSync via commandline + +RealtimeSync can be used to load a configuration file and start processing immediately without additional user intervention. Just pass the name of a configuration file as +first commandline argument. + +Example: C:\Program Files\FreeFileSync\RealtimeSync.exe MyConfig.ffs_real + + +9. Compare by filesize + +Sometimes you might want to compare both sides by filesize only, ignoring the last modification timestamp. Here is how you can do this: Open your *.ffs_gui configuration file +and change the XML node <FileTimeTolerance> to some sufficiently large value. Now changed files will be detected as a conflict (same date, different filesize) and a default +synchronization direction for conflics can be used. + + + --------- | Links | --------- diff --git a/BUILD/Resources.dat b/BUILD/Resources.dat Binary files differindex b3d3376a..4fc53ba5 100644 --- a/BUILD/Resources.dat +++ b/BUILD/Resources.dat diff --git a/BUILD/Shadow.zip b/BUILD/Shadow.zip Binary files differindex fbf0d585..24716c68 100644 --- a/BUILD/Shadow.zip +++ b/BUILD/Shadow.zip diff --git a/BUILD/mingwm10.dll b/BUILD/mingwm10.dll Binary files differindex 572f8886..cf2113a1 100644 --- a/BUILD/mingwm10.dll +++ b/BUILD/mingwm10.dll diff --git a/FreeFileSync.cbp b/FreeFileSync.cbp index aedebc95..3ca4ef97 100644 --- a/FreeFileSync.cbp +++ b/FreeFileSync.cbp @@ -100,6 +100,7 @@ <Add option="-DZSTRING_WIDE_CHAR" /> <Add directory="C:\Programme\C++\wxWidgets\include" /> <Add directory="C:\Programme\C++\wxWidgets\contrib\include" /> + <Add directory="shared\boost_1_40_0" /> </Compiler> <ResourceCompiler> <Add directory="C:\Programme\C++\wxWidgets\include" /> @@ -135,6 +136,8 @@ <Unit filename="comparison.h"> <Option target="<{~None~}>" /> </Unit> + <Unit filename="fileHierarchy.cpp" /> + <Unit filename="fileHierarchy.h" /> <Unit filename="library\customGrid.cpp"> <Option target="Debug" /> <Option target="Release" /> @@ -260,16 +263,24 @@ <Unit filename="shared\localization.h" /> <Unit filename="shared\shadow.cpp" /> <Unit filename="shared\shadow.h" /> - <Unit filename="shared\shared_ptr.h" /> <Unit filename="shared\standardPaths.cpp" /> <Unit filename="shared\standardPaths.h" /> <Unit filename="shared\staticAssert.h" /> + <Unit filename="shared\systemConstants.h" /> <Unit filename="shared\systemFunctions.cpp" /> <Unit filename="shared\systemFunctions.h" /> <Unit filename="shared\tinyxml\tinystr.cpp" /> <Unit filename="shared\tinyxml\tinyxml.cpp" /> <Unit filename="shared\tinyxml\tinyxmlerror.cpp" /> <Unit filename="shared\tinyxml\tinyxmlparser.cpp" /> + <Unit filename="shared\toggleButton.cpp"> + <Option target="Debug" /> + <Option target="Release" /> + </Unit> + <Unit filename="shared\toggleButton.h"> + <Option target="Debug" /> + <Option target="Release" /> + </Unit> <Unit filename="shared\xmlBase.cpp" /> <Unit filename="shared\xmlBase.h" /> <Unit filename="shared\zstring.cpp" /> @@ -296,7 +307,7 @@ <Unit filename="ui\checkVersion.h"> <Option target="<{~None~}>" /> </Unit> - <Unit filename="ui\dummyPanel.h"> + <Unit filename="ui\folderPair.h"> <Option target="Debug" /> <Option target="Release" /> </Unit> diff --git a/FreeFileSync.vcproj b/FreeFileSync.vcproj index 92118549..2027afb9 100644 --- a/FreeFileSync.vcproj +++ b/FreeFileSync.vcproj @@ -42,7 +42,7 @@ <Tool Name="VCCLCompilerTool" Optimization="0" - AdditionalIncludeDirectories=""C:\Programme\C++\wxWidgets\include";"C:\Programme\C++\wxWidgets\lib\vc_lib\mswud"" + AdditionalIncludeDirectories=""C:\Programme\C++\wxWidgets\include";"C:\Programme\C++\wxWidgets\lib\vc_lib\mswud";.\shared\boost_1_40_0" PreprocessorDefinitions="wxUSE_UNICODE;__WXMSW__;FFS_WIN;__WXDEBUG__;TIXML_USE_STL;ZSTRING_WIDE_CHAR" MinimalRebuild="true" BasicRuntimeChecks="3" @@ -50,6 +50,7 @@ UsePrecompiledHeader="0" WarningLevel="3" DebugInformationFormat="4" + DisableSpecificWarnings="4804" /> <Tool Name="VCManagedResourceCompilerTool" @@ -126,13 +127,14 @@ Optimization="2" EnableIntrinsicFunctions="true" FavorSizeOrSpeed="1" - AdditionalIncludeDirectories=""C:\Programme\C++\wxWidgets\include";"C:\Programme\C++\wxWidgets\lib\vc_lib\mswu"" + AdditionalIncludeDirectories=""C:\Programme\C++\wxWidgets\include";"C:\Programme\C++\wxWidgets\lib\vc_lib\mswu";.\shared\boost_1_40_0" PreprocessorDefinitions="wxUSE_UNICODE;__WXMSW__;FFS_WIN;NDEBUG;TIXML_USE_STL;ZSTRING_WIDE_CHAR" RuntimeLibrary="0" EnableFunctionLevelLinking="true" UsePrecompiledHeader="0" WarningLevel="3" DebugInformationFormat="3" + DisableSpecificWarnings="4804" /> <Tool Name="VCManagedResourceCompilerTool" @@ -234,6 +236,10 @@ > </File> <File + RelativePath=".\fileHierarchy.cpp" + > + </File> + <File RelativePath=".\shared\fileTraverser.cpp" > </File> @@ -330,6 +336,10 @@ > </File> <File + RelativePath=".\shared\toggleButton.cpp" + > + </File> + <File RelativePath=".\shared\xmlBase.cpp" > </File> @@ -1,4 +1,4 @@ -CPPFLAGS=-Wall -pipe -DNDEBUG `wx-config --cppflags` `pkg-config --cflags gtk+-2.0` -DFFS_LINUX -DTIXML_USE_STL -DZSTRING_CHAR -O3 -pthread -c +CPPFLAGS=-Wall -pipe -DNDEBUG `wx-config --cppflags` `pkg-config --cflags gtk+-2.0` -DFFS_LINUX -DTIXML_USE_STL -DZSTRING_CHAR -O3 -pthread -c -Ishared/boost_1_40_0 LINKFLAGS=`wx-config --libs` -O3 -pthread FILE_LIST= #internal list of all *.cpp files needed for compilation @@ -6,6 +6,7 @@ FILE_LIST+=structures.cpp FILE_LIST+=algorithm.cpp FILE_LIST+=comparison.cpp FILE_LIST+=synchronization.cpp +FILE_LIST+=fileHierarchy.cpp FILE_LIST+=application.cpp FILE_LIST+=ui/guiGenerated.cpp FILE_LIST+=ui/gridView.cpp @@ -37,6 +38,7 @@ FILE_LIST+=shared/standardPaths.cpp FILE_LIST+=shared/zstring.cpp FILE_LIST+=shared/xmlBase.cpp FILE_LIST+=shared/customButton.cpp +FILE_LIST+=shared/toggleButton.cpp #list of all *.o files OBJECT_LIST=$(foreach file, $(FILE_LIST), OBJ/$(subst .cpp,.o,$(notdir $(file)))) diff --git a/RealtimeSync/RealtimeSync.cbp b/RealtimeSync/RealtimeSync.cbp index 75c1d189..47afabf3 100644 --- a/RealtimeSync/RealtimeSync.cbp +++ b/RealtimeSync/RealtimeSync.cbp @@ -72,6 +72,7 @@ <Add option="-DTIXML_USE_STL" /> <Add directory="C:\Programme\C++\wxWidgets\include" /> <Add directory="C:\Programme\C++\wxWidgets\contrib\include" /> + <Add directory="..\shared\boost_1_40_0" /> </Compiler> <ResourceCompiler> <Add directory="C:\Programme\C++\wxWidgets\include" /> diff --git a/RealtimeSync/RealtimeSync.vcproj b/RealtimeSync/RealtimeSync.vcproj index 72bb7f61..7aebda01 100644 --- a/RealtimeSync/RealtimeSync.vcproj +++ b/RealtimeSync/RealtimeSync.vcproj @@ -42,7 +42,7 @@ <Tool Name="VCCLCompilerTool" Optimization="0" - AdditionalIncludeDirectories=""C:\Programme\C++\wxWidgets\include";"C:\Programme\C++\wxWidgets\lib\vc_lib\mswud"" + AdditionalIncludeDirectories=""C:\Programme\C++\wxWidgets\include";"C:\Programme\C++\wxWidgets\lib\vc_lib\mswud";..\shared\boost_1_40_0" PreprocessorDefinitions="wxUSE_UNICODE;__WXMSW__;FFS_WIN;__WXDEBUG__;TIXML_USE_STL;ZSTRING_WIDE_CHAR" MinimalRebuild="true" BasicRuntimeChecks="3" @@ -50,6 +50,7 @@ UsePrecompiledHeader="0" WarningLevel="3" DebugInformationFormat="4" + DisableSpecificWarnings="4804" /> <Tool Name="VCManagedResourceCompilerTool" @@ -126,13 +127,14 @@ Optimization="2" EnableIntrinsicFunctions="true" FavorSizeOrSpeed="1" - AdditionalIncludeDirectories=""C:\Programme\C++\wxWidgets\include";"C:\Programme\C++\wxWidgets\lib\vc_lib\mswu"" + AdditionalIncludeDirectories=""C:\Programme\C++\wxWidgets\include";"C:\Programme\C++\wxWidgets\lib\vc_lib\mswu";..\shared\boost_1_40_0" PreprocessorDefinitions="wxUSE_UNICODE;__WXMSW__;FFS_WIN;NDEBUG;TIXML_USE_STL;ZSTRING_WIDE_CHAR" RuntimeLibrary="0" EnableFunctionLevelLinking="true" UsePrecompiledHeader="0" WarningLevel="3" DebugInformationFormat="3" + DisableSpecificWarnings="4804" /> <Tool Name="VCManagedResourceCompilerTool" diff --git a/RealtimeSync/application.cpp b/RealtimeSync/application.cpp index 5515ded5..827a54c6 100644 --- a/RealtimeSync/application.cpp +++ b/RealtimeSync/application.cpp @@ -44,23 +44,7 @@ void Application::OnStartApplication(wxIdleEvent& event) #endif //set program language - try - { - FreeFileSync::CustomLocale::getInstance().setLanguage(RealtimeSync::getProgramLanguage()); - } - catch (const xmlAccess::XmlError& error) - { - if (wxFileExists(FreeFileSync::getGlobalConfigFile())) - { - SetExitOnFrameDelete(false); //prevent error messagebox from becoming top-level window - if (error.getSeverity() == xmlAccess::XmlError::WARNING) - wxMessageBox(error.show(), _("Warning"), wxOK | wxICON_WARNING); - else - wxMessageBox(error.show(), _("Error"), wxOK | wxICON_ERROR); - SetExitOnFrameDelete(true); - - } - } + FreeFileSync::CustomLocale::getInstance().setLanguage(RealtimeSync::getProgramLanguage()); //try to set config/batch-filename set by %1 parameter wxString cfgFilename; diff --git a/RealtimeSync/guiGenerated.cpp b/RealtimeSync/guiGenerated.cpp index 1150ae1b..6954f01c 100644 --- a/RealtimeSync/guiGenerated.cpp +++ b/RealtimeSync/guiGenerated.cpp @@ -13,198 +13,198 @@ MainDlgGenerated::MainDlgGenerated( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxFrame( parent, id, title, pos, size, style ) { - this->SetSizeHints( wxDefaultSize, wxDefaultSize ); - - m_menubar1 = new wxMenuBar( 0 ); - m_menuFile = new wxMenu(); - wxMenuItem* m_menuItem14; - m_menuItem14 = new wxMenuItem( m_menuFile, wxID_ANY, wxString( _("S&ave configuration") ) + wxT('\t') + wxT("CTRL-S"), wxEmptyString, wxITEM_NORMAL ); - m_menuFile->Append( m_menuItem14 ); - - wxMenuItem* m_menuItem13; - m_menuItem13 = new wxMenuItem( m_menuFile, wxID_ANY, wxString( _("&Load configuration") ) + wxT('\t') + wxT("CTRL-L"), wxEmptyString, wxITEM_NORMAL ); - m_menuFile->Append( m_menuItem13 ); - - m_menuFile->AppendSeparator(); - - wxMenuItem* m_menuItem4; - m_menuItem4 = new wxMenuItem( m_menuFile, wxID_EXIT, wxString( _("&Quit") ) + wxT('\t') + wxT("CTRL-Q"), wxEmptyString, wxITEM_NORMAL ); - m_menuFile->Append( m_menuItem4 ); - - m_menubar1->Append( m_menuFile, _("&File") ); - - m_menuHelp = new wxMenu(); - m_menuItemAbout = new wxMenuItem( m_menuHelp, wxID_ABOUT, wxString( _("&About...") ) + wxT('\t') + wxT("F1"), wxEmptyString, wxITEM_NORMAL ); - m_menuHelp->Append( m_menuItemAbout ); - - m_menubar1->Append( m_menuHelp, _("&Help") ); - - this->SetMenuBar( m_menubar1 ); - - bSizerMain = new wxBoxSizer( wxVERTICAL ); - - m_panelMain = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); - wxBoxSizer* bSizer1; - bSizer1 = new wxBoxSizer( wxVERTICAL ); - - - bSizer1->Add( 0, 10, 0, 0, 5 ); - - m_staticText2 = new wxStaticText( m_panelMain, wxID_ANY, _("Usage: Select directories for monitoring and enter a commandline. Each time files are modified within these directories (or subdirectories) the commandline is executed."), wxDefaultPosition, wxDefaultSize, 0|wxDOUBLE_BORDER ); - m_staticText2->Wrap( 350 ); - bSizer1->Add( m_staticText2, 0, wxALIGN_CENTER_HORIZONTAL|wxRIGHT|wxLEFT, 40 ); - - m_staticline2 = new wxStaticLine( m_panelMain, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); - bSizer1->Add( m_staticline2, 0, wxTOP|wxBOTTOM|wxEXPAND, 10 ); - - wxBoxSizer* bSizer8; - bSizer8 = new wxBoxSizer( wxVERTICAL ); - - wxStaticBoxSizer* sbSizer5; - sbSizer5 = new wxStaticBoxSizer( new wxStaticBox( m_panelMain, wxID_ANY, _("Directories to watch") ), wxVERTICAL ); - - m_panelMainFolder = new wxPanel( m_panelMain, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); - wxBoxSizer* bSizer114; - bSizer114 = new wxBoxSizer( wxHORIZONTAL ); - - wxBoxSizer* bSizer781; - bSizer781 = new wxBoxSizer( wxHORIZONTAL ); - - m_bpButtonAddFolder = new wxBitmapButton( m_panelMainFolder, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 19,21 ), wxBU_AUTODRAW ); - m_bpButtonAddFolder->SetToolTip( _("Add folder") ); - - m_bpButtonAddFolder->SetToolTip( _("Add folder") ); - - bSizer781->Add( m_bpButtonAddFolder, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); - - m_bpButtonRemoveTopFolder = new wxBitmapButton( m_panelMainFolder, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 19,21 ), wxBU_AUTODRAW ); - m_bpButtonRemoveTopFolder->SetToolTip( _("Remove folder") ); - - m_bpButtonRemoveTopFolder->SetToolTip( _("Remove folder") ); - - bSizer781->Add( m_bpButtonRemoveTopFolder, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); - - bSizer114->Add( bSizer781, 0, wxALIGN_CENTER_VERTICAL, 5 ); - - m_txtCtrlDirectoryMain = new wxTextCtrl( m_panelMainFolder, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 ); - bSizer114->Add( m_txtCtrlDirectoryMain, 1, wxALIGN_CENTER_VERTICAL, 5 ); - - m_dirPickerMain = new wxDirPickerCtrl( m_panelMainFolder, wxID_ANY, wxEmptyString, _("Select a folder"), wxDefaultPosition, wxDefaultSize, 0 ); - m_dirPickerMain->SetToolTip( _("Select a folder") ); - - bSizer114->Add( m_dirPickerMain, 0, wxALIGN_CENTER_VERTICAL, 5 ); - - m_panelMainFolder->SetSizer( bSizer114 ); - m_panelMainFolder->Layout(); - bSizer114->Fit( m_panelMainFolder ); - sbSizer5->Add( m_panelMainFolder, 0, wxEXPAND, 5 ); - - m_scrolledWinFolders = new wxScrolledWindow( m_panelMain, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxVSCROLL ); - m_scrolledWinFolders->SetScrollRate( 5, 5 ); - bSizerFolders = new wxBoxSizer( wxVERTICAL ); - - m_scrolledWinFolders->SetSizer( bSizerFolders ); - m_scrolledWinFolders->Layout(); - bSizerFolders->Fit( m_scrolledWinFolders ); - sbSizer5->Add( m_scrolledWinFolders, 0, wxEXPAND, 5 ); - - bSizer8->Add( sbSizer5, 0, wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); - - bSizer1->Add( bSizer8, 1, wxEXPAND, 5 ); - - wxStaticBoxSizer* sbSizer3; - sbSizer3 = new wxStaticBoxSizer( new wxStaticBox( m_panelMain, wxID_ANY, _("Commandline") ), wxVERTICAL ); - - m_textCtrlCommand = new wxTextCtrl( m_panelMain, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 ); - sbSizer3->Add( m_textCtrlCommand, 0, wxEXPAND|wxBOTTOM, 5 ); - - bSizer1->Add( sbSizer3, 0, wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 5 ); - - m_staticline1 = new wxStaticLine( m_panelMain, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); - bSizer1->Add( m_staticline1, 0, wxEXPAND|wxTOP|wxBOTTOM, 10 ); - - wxStaticBoxSizer* sbSizer4; - sbSizer4 = new wxStaticBoxSizer( new wxStaticBox( m_panelMain, wxID_ANY, _("Delay") ), wxVERTICAL ); - - m_spinCtrlDelay = new wxSpinCtrl( m_panelMain, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS|wxSP_WRAP, 0, 2000000000, 0 ); - m_spinCtrlDelay->SetToolTip( _("Delay between two invocations of the commandline") ); - - sbSizer4->Add( m_spinCtrlDelay, 0, wxBOTTOM|wxRIGHT|wxLEFT|wxALIGN_CENTER_HORIZONTAL, 5 ); - - bSizer1->Add( sbSizer4, 0, wxEXPAND|wxRIGHT|wxLEFT, 5 ); - - m_buttonStart = new wxButtonWithImage( m_panelMain, wxID_ANY, _("Start"), wxDefaultPosition, wxSize( -1,40 ), 0 ); - m_buttonStart->SetDefault(); - m_buttonStart->SetFont( wxFont( 14, 74, 90, 92, false, wxT("Arial Black") ) ); - - bSizer1->Add( m_buttonStart, 0, wxALL|wxALIGN_CENTER_HORIZONTAL, 5 ); - - m_buttonCancel = new wxButton( m_panelMain, wxID_CANCEL, _("dummy"), wxDefaultPosition, wxSize( 0,0 ), 0 ); - bSizer1->Add( m_buttonCancel, 0, 0, 5 ); - - m_panelMain->SetSizer( bSizer1 ); - m_panelMain->Layout(); - bSizer1->Fit( m_panelMain ); - bSizerMain->Add( m_panelMain, 1, wxEXPAND, 5 ); - - this->SetSizer( bSizerMain ); - this->Layout(); - bSizerMain->Fit( this ); - - // Connect Events - this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( MainDlgGenerated::OnClose ) ); - this->Connect( m_menuItem14->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDlgGenerated::OnSaveConfig ) ); - this->Connect( m_menuItem13->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDlgGenerated::OnLoadConfig ) ); - this->Connect( m_menuItem4->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDlgGenerated::OnQuit ) ); - this->Connect( m_menuItemAbout->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDlgGenerated::OnMenuAbout ) ); - m_bpButtonAddFolder->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDlgGenerated::OnAddFolder ), NULL, this ); - m_bpButtonRemoveTopFolder->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDlgGenerated::OnRemoveTopFolder ), NULL, this ); - m_buttonStart->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDlgGenerated::OnStart ), NULL, this ); - m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDlgGenerated::OnQuit ), NULL, this ); + this->SetSizeHints( wxDefaultSize, wxDefaultSize ); + + m_menubar1 = new wxMenuBar( 0 ); + m_menuFile = new wxMenu(); + wxMenuItem* m_menuItem14; + m_menuItem14 = new wxMenuItem( m_menuFile, wxID_ANY, wxString( _("S&ave configuration") ) + wxT('\t') + wxT("CTRL-S"), wxEmptyString, wxITEM_NORMAL ); + m_menuFile->Append( m_menuItem14 ); + + wxMenuItem* m_menuItem13; + m_menuItem13 = new wxMenuItem( m_menuFile, wxID_ANY, wxString( _("&Load configuration") ) + wxT('\t') + wxT("CTRL-L"), wxEmptyString, wxITEM_NORMAL ); + m_menuFile->Append( m_menuItem13 ); + + m_menuFile->AppendSeparator(); + + wxMenuItem* m_menuItem4; + m_menuItem4 = new wxMenuItem( m_menuFile, wxID_EXIT, wxString( _("&Quit") ) + wxT('\t') + wxT("CTRL-Q"), wxEmptyString, wxITEM_NORMAL ); + m_menuFile->Append( m_menuItem4 ); + + m_menubar1->Append( m_menuFile, _("&File") ); + + m_menuHelp = new wxMenu(); + m_menuItemAbout = new wxMenuItem( m_menuHelp, wxID_ABOUT, wxString( _("&About...") ) + wxT('\t') + wxT("F1"), wxEmptyString, wxITEM_NORMAL ); + m_menuHelp->Append( m_menuItemAbout ); + + m_menubar1->Append( m_menuHelp, _("&Help") ); + + this->SetMenuBar( m_menubar1 ); + + bSizerMain = new wxBoxSizer( wxVERTICAL ); + + m_panelMain = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); + wxBoxSizer* bSizer1; + bSizer1 = new wxBoxSizer( wxVERTICAL ); + + + bSizer1->Add( 0, 10, 0, 0, 5 ); + + m_staticText2 = new wxStaticText( m_panelMain, wxID_ANY, _("Usage: Select directories for monitoring and enter a commandline. Each time files are modified within these directories (or subdirectories) the commandline is executed."), wxDefaultPosition, wxDefaultSize, 0|wxDOUBLE_BORDER ); + m_staticText2->Wrap( 350 ); + bSizer1->Add( m_staticText2, 0, wxALIGN_CENTER_HORIZONTAL|wxRIGHT|wxLEFT, 40 ); + + m_staticline2 = new wxStaticLine( m_panelMain, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + bSizer1->Add( m_staticline2, 0, wxTOP|wxBOTTOM|wxEXPAND, 10 ); + + wxBoxSizer* bSizer8; + bSizer8 = new wxBoxSizer( wxVERTICAL ); + + wxStaticBoxSizer* sbSizer5; + sbSizer5 = new wxStaticBoxSizer( new wxStaticBox( m_panelMain, wxID_ANY, _("Directories to watch") ), wxVERTICAL ); + + m_panelMainFolder = new wxPanel( m_panelMain, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); + wxBoxSizer* bSizer114; + bSizer114 = new wxBoxSizer( wxHORIZONTAL ); + + wxBoxSizer* bSizer781; + bSizer781 = new wxBoxSizer( wxHORIZONTAL ); + + m_bpButtonAddFolder = new wxBitmapButton( m_panelMainFolder, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 19,21 ), wxBU_AUTODRAW ); + m_bpButtonAddFolder->SetToolTip( _("Add folder") ); + + m_bpButtonAddFolder->SetToolTip( _("Add folder") ); + + bSizer781->Add( m_bpButtonAddFolder, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); + + m_bpButtonRemoveTopFolder = new wxBitmapButton( m_panelMainFolder, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 19,21 ), wxBU_AUTODRAW ); + m_bpButtonRemoveTopFolder->SetToolTip( _("Remove folder") ); + + m_bpButtonRemoveTopFolder->SetToolTip( _("Remove folder") ); + + bSizer781->Add( m_bpButtonRemoveTopFolder, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); + + bSizer114->Add( bSizer781, 0, wxALIGN_CENTER_VERTICAL, 5 ); + + m_txtCtrlDirectoryMain = new wxTextCtrl( m_panelMainFolder, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 ); + bSizer114->Add( m_txtCtrlDirectoryMain, 1, wxALIGN_CENTER_VERTICAL, 5 ); + + m_dirPickerMain = new wxDirPickerCtrl( m_panelMainFolder, wxID_ANY, wxEmptyString, _("Select a folder"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dirPickerMain->SetToolTip( _("Select a folder") ); + + bSizer114->Add( m_dirPickerMain, 0, wxALIGN_CENTER_VERTICAL, 5 ); + + m_panelMainFolder->SetSizer( bSizer114 ); + m_panelMainFolder->Layout(); + bSizer114->Fit( m_panelMainFolder ); + sbSizer5->Add( m_panelMainFolder, 0, wxEXPAND, 5 ); + + m_scrolledWinFolders = new wxScrolledWindow( m_panelMain, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxVSCROLL ); + m_scrolledWinFolders->SetScrollRate( 5, 5 ); + bSizerFolders = new wxBoxSizer( wxVERTICAL ); + + m_scrolledWinFolders->SetSizer( bSizerFolders ); + m_scrolledWinFolders->Layout(); + bSizerFolders->Fit( m_scrolledWinFolders ); + sbSizer5->Add( m_scrolledWinFolders, 0, wxEXPAND, 5 ); + + bSizer8->Add( sbSizer5, 0, wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); + + bSizer1->Add( bSizer8, 1, wxEXPAND, 5 ); + + wxStaticBoxSizer* sbSizer3; + sbSizer3 = new wxStaticBoxSizer( new wxStaticBox( m_panelMain, wxID_ANY, _("Commandline") ), wxVERTICAL ); + + m_textCtrlCommand = new wxTextCtrl( m_panelMain, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 ); + sbSizer3->Add( m_textCtrlCommand, 0, wxEXPAND|wxBOTTOM, 5 ); + + bSizer1->Add( sbSizer3, 0, wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 5 ); + + m_staticline1 = new wxStaticLine( m_panelMain, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + bSizer1->Add( m_staticline1, 0, wxEXPAND|wxTOP|wxBOTTOM, 10 ); + + wxStaticBoxSizer* sbSizer4; + sbSizer4 = new wxStaticBoxSizer( new wxStaticBox( m_panelMain, wxID_ANY, _("Delay") ), wxVERTICAL ); + + m_spinCtrlDelay = new wxSpinCtrl( m_panelMain, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS|wxSP_WRAP, 0, 2000000000, 0 ); + m_spinCtrlDelay->SetToolTip( _("Delay between detection of changes and execution of commandline in seconds") ); + + sbSizer4->Add( m_spinCtrlDelay, 0, wxBOTTOM|wxRIGHT|wxLEFT|wxALIGN_CENTER_HORIZONTAL, 5 ); + + bSizer1->Add( sbSizer4, 0, wxEXPAND|wxRIGHT|wxLEFT, 5 ); + + m_buttonStart = new wxButtonWithImage( m_panelMain, wxID_ANY, _("Start"), wxDefaultPosition, wxSize( -1,40 ), 0 ); + m_buttonStart->SetDefault(); + m_buttonStart->SetFont( wxFont( 14, 74, 90, 92, false, wxT("Arial Black") ) ); + + bSizer1->Add( m_buttonStart, 0, wxALL|wxALIGN_CENTER_HORIZONTAL, 5 ); + + m_buttonCancel = new wxButton( m_panelMain, wxID_CANCEL, _("dummy"), wxDefaultPosition, wxSize( 0,0 ), 0 ); + bSizer1->Add( m_buttonCancel, 0, 0, 5 ); + + m_panelMain->SetSizer( bSizer1 ); + m_panelMain->Layout(); + bSizer1->Fit( m_panelMain ); + bSizerMain->Add( m_panelMain, 1, wxEXPAND, 5 ); + + this->SetSizer( bSizerMain ); + this->Layout(); + bSizerMain->Fit( this ); + + // Connect Events + this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( MainDlgGenerated::OnClose ) ); + this->Connect( m_menuItem14->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDlgGenerated::OnSaveConfig ) ); + this->Connect( m_menuItem13->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDlgGenerated::OnLoadConfig ) ); + this->Connect( m_menuItem4->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDlgGenerated::OnQuit ) ); + this->Connect( m_menuItemAbout->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDlgGenerated::OnMenuAbout ) ); + m_bpButtonAddFolder->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDlgGenerated::OnAddFolder ), NULL, this ); + m_bpButtonRemoveTopFolder->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDlgGenerated::OnRemoveTopFolder ), NULL, this ); + m_buttonStart->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDlgGenerated::OnStart ), NULL, this ); + m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDlgGenerated::OnQuit ), NULL, this ); } MainDlgGenerated::~MainDlgGenerated() { - // Disconnect Events - this->Disconnect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( MainDlgGenerated::OnClose ) ); - this->Disconnect( wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDlgGenerated::OnSaveConfig ) ); - this->Disconnect( wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDlgGenerated::OnLoadConfig ) ); - this->Disconnect( wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDlgGenerated::OnQuit ) ); - this->Disconnect( wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDlgGenerated::OnMenuAbout ) ); - m_bpButtonAddFolder->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDlgGenerated::OnAddFolder ), NULL, this ); - m_bpButtonRemoveTopFolder->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDlgGenerated::OnRemoveTopFolder ), NULL, this ); - m_buttonStart->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDlgGenerated::OnStart ), NULL, this ); - m_buttonCancel->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDlgGenerated::OnQuit ), NULL, this ); + // Disconnect Events + this->Disconnect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( MainDlgGenerated::OnClose ) ); + this->Disconnect( wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDlgGenerated::OnSaveConfig ) ); + this->Disconnect( wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDlgGenerated::OnLoadConfig ) ); + this->Disconnect( wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDlgGenerated::OnQuit ) ); + this->Disconnect( wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDlgGenerated::OnMenuAbout ) ); + m_bpButtonAddFolder->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDlgGenerated::OnAddFolder ), NULL, this ); + m_bpButtonRemoveTopFolder->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDlgGenerated::OnRemoveTopFolder ), NULL, this ); + m_buttonStart->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDlgGenerated::OnStart ), NULL, this ); + m_buttonCancel->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDlgGenerated::OnQuit ), NULL, this ); } FolderGenerated::FolderGenerated( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style ) : wxPanel( parent, id, pos, size, style ) { - wxBoxSizer* bSizer114; - bSizer114 = new wxBoxSizer( wxHORIZONTAL ); - - m_bpButtonRemoveFolder = new wxBitmapButton( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 19,21 ), wxBU_AUTODRAW ); - m_bpButtonRemoveFolder->SetToolTip( _("Remove folder") ); - - m_bpButtonRemoveFolder->SetToolTip( _("Remove folder") ); - - bSizer114->Add( m_bpButtonRemoveFolder, 0, wxALIGN_CENTER_VERTICAL, 5 ); - - wxBoxSizer* bSizer20; - bSizer20 = new wxBoxSizer( wxHORIZONTAL ); - - m_txtCtrlDirectory = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 ); - bSizer20->Add( m_txtCtrlDirectory, 1, wxALIGN_CENTER_VERTICAL, 5 ); - - m_dirPicker = new wxDirPickerCtrl( this, wxID_ANY, wxEmptyString, _("Select a folder"), wxDefaultPosition, wxDefaultSize, 0 ); - m_dirPicker->SetToolTip( _("Select a folder") ); - - bSizer20->Add( m_dirPicker, 0, wxALIGN_CENTER_VERTICAL, 5 ); - - bSizer114->Add( bSizer20, 1, 0, 5 ); - - this->SetSizer( bSizer114 ); - this->Layout(); - bSizer114->Fit( this ); + wxBoxSizer* bSizer114; + bSizer114 = new wxBoxSizer( wxHORIZONTAL ); + + m_bpButtonRemoveFolder = new wxBitmapButton( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 19,21 ), wxBU_AUTODRAW ); + m_bpButtonRemoveFolder->SetToolTip( _("Remove folder") ); + + m_bpButtonRemoveFolder->SetToolTip( _("Remove folder") ); + + bSizer114->Add( m_bpButtonRemoveFolder, 0, wxALIGN_CENTER_VERTICAL, 5 ); + + wxBoxSizer* bSizer20; + bSizer20 = new wxBoxSizer( wxHORIZONTAL ); + + m_txtCtrlDirectory = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 ); + bSizer20->Add( m_txtCtrlDirectory, 1, wxALIGN_CENTER_VERTICAL, 5 ); + + m_dirPicker = new wxDirPickerCtrl( this, wxID_ANY, wxEmptyString, _("Select a folder"), wxDefaultPosition, wxDefaultSize, 0 ); + m_dirPicker->SetToolTip( _("Select a folder") ); + + bSizer20->Add( m_dirPicker, 0, wxALIGN_CENTER_VERTICAL, 5 ); + + bSizer114->Add( bSizer20, 1, 0, 5 ); + + this->SetSizer( bSizer114 ); + this->Layout(); + bSizer114->Fit( this ); } FolderGenerated::~FolderGenerated() diff --git a/RealtimeSync/guiGenerated.h b/RealtimeSync/guiGenerated.h index f62410a1..17f28278 100644 --- a/RealtimeSync/guiGenerated.h +++ b/RealtimeSync/guiGenerated.h @@ -39,90 +39,66 @@ class wxButtonWithImage; /////////////////////////////////////////////////////////////////////////////// /// Class MainDlgGenerated /////////////////////////////////////////////////////////////////////////////// -class MainDlgGenerated : public wxFrame +class MainDlgGenerated : public wxFrame { -private: - -protected: - wxMenuBar* m_menubar1; - wxMenu* m_menuFile; - wxMenu* m_menuHelp; - wxMenuItem* m_menuItemAbout; - wxBoxSizer* bSizerMain; - wxPanel* m_panelMain; - - wxStaticText* m_staticText2; - wxStaticLine* m_staticline2; - wxPanel* m_panelMainFolder; - wxBitmapButton* m_bpButtonAddFolder; - wxBitmapButton* m_bpButtonRemoveTopFolder; - wxTextCtrl* m_txtCtrlDirectoryMain; - wxScrolledWindow* m_scrolledWinFolders; - wxBoxSizer* bSizerFolders; - wxTextCtrl* m_textCtrlCommand; - wxStaticLine* m_staticline1; - wxSpinCtrl* m_spinCtrlDelay; - wxButtonWithImage* m_buttonStart; - wxButton* m_buttonCancel; - - // Virtual event handlers, overide them in your derived class - virtual void OnClose( wxCloseEvent& event ) - { - event.Skip(); - } - virtual void OnSaveConfig( wxCommandEvent& event ) - { - event.Skip(); - } - virtual void OnLoadConfig( wxCommandEvent& event ) - { - event.Skip(); - } - virtual void OnQuit( wxCommandEvent& event ) - { - event.Skip(); - } - virtual void OnMenuAbout( wxCommandEvent& event ) - { - event.Skip(); - } - virtual void OnAddFolder( wxCommandEvent& event ) - { - event.Skip(); - } - virtual void OnRemoveTopFolder( wxCommandEvent& event ) - { - event.Skip(); - } - virtual void OnStart( wxCommandEvent& event ) - { - event.Skip(); - } - - -public: - wxDirPickerCtrl* m_dirPickerMain; - MainDlgGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("RealtimeSync - Automated Synchronization"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( -1,-1 ), long style = wxDEFAULT_FRAME_STYLE|wxTAB_TRAVERSAL ); - ~MainDlgGenerated(); - + private: + + protected: + wxMenuBar* m_menubar1; + wxMenu* m_menuFile; + wxMenu* m_menuHelp; + wxMenuItem* m_menuItemAbout; + wxBoxSizer* bSizerMain; + wxPanel* m_panelMain; + + wxStaticText* m_staticText2; + wxStaticLine* m_staticline2; + wxPanel* m_panelMainFolder; + wxBitmapButton* m_bpButtonAddFolder; + wxBitmapButton* m_bpButtonRemoveTopFolder; + wxTextCtrl* m_txtCtrlDirectoryMain; + wxScrolledWindow* m_scrolledWinFolders; + wxBoxSizer* bSizerFolders; + wxTextCtrl* m_textCtrlCommand; + wxStaticLine* m_staticline1; + wxSpinCtrl* m_spinCtrlDelay; + wxButtonWithImage* m_buttonStart; + wxButton* m_buttonCancel; + + // Virtual event handlers, overide them in your derived class + virtual void OnClose( wxCloseEvent& event ){ event.Skip(); } + virtual void OnSaveConfig( wxCommandEvent& event ){ event.Skip(); } + virtual void OnLoadConfig( wxCommandEvent& event ){ event.Skip(); } + virtual void OnQuit( wxCommandEvent& event ){ event.Skip(); } + virtual void OnMenuAbout( wxCommandEvent& event ){ event.Skip(); } + virtual void OnAddFolder( wxCommandEvent& event ){ event.Skip(); } + virtual void OnRemoveTopFolder( wxCommandEvent& event ){ event.Skip(); } + virtual void OnStart( wxCommandEvent& event ){ event.Skip(); } + + + public: + wxDirPickerCtrl* m_dirPickerMain; + MainDlgGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("RealtimeSync - Automated Synchronization"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( -1,-1 ), long style = wxDEFAULT_FRAME_STYLE|wxTAB_TRAVERSAL ); + ~MainDlgGenerated(); + }; /////////////////////////////////////////////////////////////////////////////// /// Class FolderGenerated /////////////////////////////////////////////////////////////////////////////// -class FolderGenerated : public wxPanel +class FolderGenerated : public wxPanel { -private: - -protected: - -public: - wxBitmapButton* m_bpButtonRemoveFolder; - wxTextCtrl* m_txtCtrlDirectory; - wxDirPickerCtrl* m_dirPicker; - FolderGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( -1,-1 ), long style = wxTAB_TRAVERSAL ); - ~FolderGenerated(); - + private: + + protected: + + public: + wxBitmapButton* m_bpButtonRemoveFolder; + wxTextCtrl* m_txtCtrlDirectory; + wxDirPickerCtrl* m_dirPicker; + FolderGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( -1,-1 ), long style = wxTAB_TRAVERSAL ); + ~FolderGenerated(); + }; #endif //__guiGenerated__ diff --git a/RealtimeSync/mainDialog.cpp b/RealtimeSync/mainDialog.cpp index a3496b35..cb1cbf86 100644 --- a/RealtimeSync/mainDialog.cpp +++ b/RealtimeSync/mainDialog.cpp @@ -2,7 +2,7 @@ #include "resources.h" #include "../shared/customButton.h" #include "../shared/standardPaths.h" -#include "../shared/globalFunctions.h" +//#include "../shared/globalFunctions.h" #include <wx/msgdlg.h> #include <wx/wupdlock.h> #include "watcher.h" @@ -11,6 +11,7 @@ #include "trayMenu.h" #include "../shared/fileHandling.h" #include "xmlFreeFileSync.h" +#include "../shared/systemConstants.h" MainDialog::MainDialog(wxDialog *dlg, const wxString& cfgFilename) diff --git a/RealtimeSync/makefile b/RealtimeSync/makefile index 02303ba9..81f41b16 100644 --- a/RealtimeSync/makefile +++ b/RealtimeSync/makefile @@ -1,4 +1,4 @@ -CPPFLAGS=-Wall -pipe -DNDEBUG `wx-config --cppflags` `pkg-config --cflags gtk+-2.0` -DFFS_LINUX -DTIXML_USE_STL -DZSTRING_CHAR -O3 -pthread -c +CPPFLAGS=-Wall -pipe -DNDEBUG `wx-config --cppflags` `pkg-config --cflags gtk+-2.0` -DFFS_LINUX -DTIXML_USE_STL -DZSTRING_CHAR -O3 -pthread -c -I../shared/boost_1_40_0 LINKFLAGS=`wx-config --libs` -O3 -pthread FILE_LIST= #internal list of all *.cpp files needed for compilation diff --git a/RealtimeSync/resources.cpp b/RealtimeSync/resources.cpp index 638df0f6..979b5ecb 100644 --- a/RealtimeSync/resources.cpp +++ b/RealtimeSync/resources.cpp @@ -3,8 +3,10 @@ #include <wx/zipstrm.h> #include <wx/image.h> #include <wx/icon.h> -#include "../shared/globalFunctions.h" +#include <memory> +//#include "../shared/globalFunctions.h" #include "../shared/standardPaths.h" +#include "../shared/systemConstants.h" const GlobalResources& GlobalResources::getInstance() diff --git a/RealtimeSync/xmlFreeFileSync.cpp b/RealtimeSync/xmlFreeFileSync.cpp index 36f0e2f8..b3c11d50 100644 --- a/RealtimeSync/xmlFreeFileSync.cpp +++ b/RealtimeSync/xmlFreeFileSync.cpp @@ -10,17 +10,47 @@ #ifdef FFS_WIN -class CmpNoCase +struct CmpNoCase { -public: - bool operator()(const wxString& a, const wxString& b) + bool operator()(const wxString& a, const wxString& b) const { - return FreeFileSync::compareStringsWin32(a.c_str(), b.c_str(), a.length(), b.length()) < 0; + return a.CmpNoCase(b) < 0; } }; #endif +xmlAccess::XmlRealConfig convertBatchToReal(const xmlAccess::XmlBatchConfig& batchCfg, const wxString& filename) +{ + xmlAccess::XmlRealConfig output; + +#ifdef FFS_WIN + std::set<wxString, CmpNoCase> uniqueFolders; +#elif defined FFS_LINUX + std::set<wxString> uniqueFolders; +#endif + + //add main folders + uniqueFolders.insert(batchCfg.mainCfg.mainFolderPair.leftDirectory.c_str()); + uniqueFolders.insert(batchCfg.mainCfg.mainFolderPair.rightDirectory.c_str()); + + //additional folders + for (std::vector<FreeFileSync::FolderPairEnh>::const_iterator i = batchCfg.mainCfg.additionalPairs.begin(); + i != batchCfg.mainCfg.additionalPairs.end(); ++i) + { + uniqueFolders.insert(i->leftDirectory.c_str()); + uniqueFolders.insert(i->rightDirectory.c_str()); + } + + output.directories.insert(output.directories.end(), uniqueFolders.begin(), uniqueFolders.end()); + + output.commandline = FreeFileSync::getInstallationDir() + globalFunctions::FILE_NAME_SEPARATOR + wxT("FreeFileSync.exe ") + + wxT("\"") + filename + wxT("\""); + + return output; +} + + void RealtimeSync::readRealOrBatchConfig(const wxString& filename, xmlAccess::XmlRealConfig& config) //throw (xmlAccess::XmlError); { if (xmlAccess::getXmlType(filename) != xmlAccess::XML_BATCH_CONFIG) @@ -37,32 +67,26 @@ void RealtimeSync::readRealOrBatchConfig(const wxString& filename, xmlAccess::Xm } catch (const xmlAccess::XmlError& e) { - if (e.getSeverity() != xmlAccess::XmlError::WARNING) //ignore parsing errors + if (e.getSeverity() != xmlAccess::XmlError::WARNING) throw; - } - -#ifdef FFS_WIN - std::set<wxString, CmpNoCase> uniqueFolders; -#elif defined FFS_LINUX - std::set<wxString> uniqueFolders; -#endif - for (std::vector<FreeFileSync::FolderPair>::const_iterator i = batchCfg.directoryPairs.begin(); i != batchCfg.directoryPairs.end(); ++i) - { - uniqueFolders.insert(i->leftDirectory.c_str()); - uniqueFolders.insert(i->rightDirectory.c_str()); + config = convertBatchToReal(batchCfg, filename); //do work despite parsing errors, then re-throw + throw; // } - - config.directories.insert(config.directories.end(), uniqueFolders.begin(), uniqueFolders.end()); - - config.commandline = FreeFileSync::getInstallationDir() + globalFunctions::FILE_NAME_SEPARATOR + wxT("FreeFileSync.exe ") + - wxT("\"") + filename + wxT("\""); + config = convertBatchToReal(batchCfg, filename); } int RealtimeSync::getProgramLanguage() { xmlAccess::XmlGlobalSettings settings; - xmlAccess::readGlobalSettings(settings); + + try + { + xmlAccess::readGlobalSettings(settings); + } + catch (const xmlAccess::XmlError&) + {} //user default language if error occured + return settings.programLanguage; } diff --git a/RealtimeSync/xmlFreeFileSync.h b/RealtimeSync/xmlFreeFileSync.h index e4e0be76..8de9af08 100644 --- a/RealtimeSync/xmlFreeFileSync.h +++ b/RealtimeSync/xmlFreeFileSync.h @@ -10,7 +10,7 @@ namespace RealtimeSync { void readRealOrBatchConfig(const wxString& filename, xmlAccess::XmlRealConfig& config); //throw (xmlAccess::XmlError); - int getProgramLanguage(); //throw (xmlAccess::XmlError); + int getProgramLanguage(); } #endif // XMLFREEFILESYNC_H_INCLUDED diff --git a/algorithm.cpp b/algorithm.cpp index 40636e83..94c4abfe 100644 --- a/algorithm.cpp +++ b/algorithm.cpp @@ -1,8 +1,8 @@ #include "algorithm.h" #include <wx/intl.h> +#include <stdexcept> #include <wx/log.h> #include "library/resources.h" -#include "shared/globalFunctions.h" #include "shared/systemFunctions.h" #include "shared/fileHandling.h" #include <wx/msgdlg.h> @@ -10,6 +10,10 @@ #include <wx/combobox.h> #include <wx/filepicker.h> #include "shared/localization.h" +#include "library/filter.h" +#include <boost/bind.hpp> +#include "shared/globalFunctions.h" +#include <wx/scrolwin.h> #ifdef FFS_WIN #include <wx/msw/wrapwin.h> //includes "windows.h" @@ -122,129 +126,183 @@ void FreeFileSync::setDirectoryName(const wxString& dirname, wxComboBox* txtCtrl } -void FreeFileSync::swapGrids(const SyncConfiguration& config, FolderComparison& folderCmp) +void FreeFileSync::scrollToBottom(wxScrolledWindow* scrWindow) { - for (FolderComparison::iterator j = folderCmp.begin(); j != folderCmp.end(); ++j) + int height = 0; + scrWindow->GetClientSize(NULL, &height); + + int pixelPerLine = 0; + scrWindow->GetScrollPixelsPerUnit(NULL, &pixelPerLine); + + if (height > 0 && pixelPerLine > 0) { - std::swap(j->syncPair.leftDirectory, j->syncPair.rightDirectory); + const int scrollLinesTotal = scrWindow->GetScrollLines(wxVERTICAL); + const int scrollLinesOnScreen = height / pixelPerLine; + const int scrollPosBottom = scrollLinesTotal - scrollLinesOnScreen; - FileComparison& fileCmp = j->fileCmp; - for (FileComparison::iterator i = fileCmp.begin(); i != fileCmp.end(); ++i) - { - //swap compare result - if (i->cmpResult == FILE_LEFT_SIDE_ONLY) - i->cmpResult = FILE_RIGHT_SIDE_ONLY; - else if (i->cmpResult == FILE_RIGHT_SIDE_ONLY) - i->cmpResult = FILE_LEFT_SIDE_ONLY; - else if (i->cmpResult == FILE_RIGHT_NEWER) - i->cmpResult = FILE_LEFT_NEWER; - else if (i->cmpResult == FILE_LEFT_NEWER) - i->cmpResult = FILE_RIGHT_NEWER; - - //swap file descriptors - std::swap(i->fileDescrLeft, i->fileDescrRight); - } + if (0 <= scrollPosBottom) + scrWindow->Scroll(0, scrollPosBottom); } +} + - //adjust sync direction +void swapGridsFP(HierarchyObject& hierObj) +{ + //swap directories + std::for_each(hierObj.subDirs.begin(), hierObj.subDirs.end(), std::mem_fun_ref(&FileSystemObject::swap)); + //swap files + std::for_each(hierObj.subFiles.begin(), hierObj.subFiles.end(), std::mem_fun_ref(&FileSystemObject::swap)); + //recurse into sub-dirs + std::for_each(hierObj.subDirs.begin(), hierObj.subDirs.end(), swapGridsFP); +} + + +void FreeFileSync::swapGrids2(const MainConfiguration& config, FolderComparison& folderCmp) +{ + std::for_each(folderCmp.begin(), folderCmp.end(), swapGridsFP); redetermineSyncDirection(config, folderCmp); } -void FreeFileSync::redetermineSyncDirection(const SyncConfiguration& config, FolderComparison& folderCmp) +class Redetermine { - //do not handle i->selectedForSynchronization in this method! handled in synchronizeFile(), synchronizeFolder()! +public: + Redetermine(const SyncConfiguration& configIn) : config(configIn) {} - for (FolderComparison::iterator j = folderCmp.begin(); j != folderCmp.end(); ++j) + void operator()(FileSystemObject& fsObj) const { - FileComparison& fileCmp = j->fileCmp; - for (FileComparison::iterator i = fileCmp.begin(); i != fileCmp.end(); ++i) + switch (fsObj.getCategory()) { - switch (i->cmpResult) - { - case FILE_LEFT_SIDE_ONLY: - i->syncDir = convertToSyncDirection(config.exLeftSideOnly); - break; + case FILE_LEFT_SIDE_ONLY: + fsObj.syncDir = convertToSyncDirection(config.exLeftSideOnly); + break; - case FILE_RIGHT_SIDE_ONLY: - i->syncDir = convertToSyncDirection(config.exRightSideOnly); - break; + case FILE_RIGHT_SIDE_ONLY: + fsObj.syncDir = convertToSyncDirection(config.exRightSideOnly); + break; - case FILE_RIGHT_NEWER: - i->syncDir = convertToSyncDirection(config.rightNewer); - break; + case FILE_RIGHT_NEWER: + fsObj.syncDir = convertToSyncDirection(config.rightNewer); + break; - case FILE_LEFT_NEWER: - i->syncDir = convertToSyncDirection(config.leftNewer); - break; + case FILE_LEFT_NEWER: + fsObj.syncDir = convertToSyncDirection(config.leftNewer); + break; - case FILE_DIFFERENT: - i->syncDir = convertToSyncDirection(config.different); - break; + case FILE_DIFFERENT: + fsObj.syncDir = convertToSyncDirection(config.different); + break; - case FILE_CONFLICT: - i->syncDir = SYNC_UNRESOLVED_CONFLICT; - break; + case FILE_CONFLICT: + fsObj.syncDir = convertToSyncDirection(config.conflict); + break; - case FILE_EQUAL: - i->syncDir = SYNC_DIR_NONE; - } + case FILE_EQUAL: + fsObj.syncDir = SYNC_DIR_NONE; } } +private: + const SyncConfiguration& config; +}; + + +void FreeFileSync::redetermineSyncDirection(const SyncConfiguration& config, HierarchyObject& baseDirectory) +{ + //do not handle i->selectedForSynchronization in this method! handled in synchronizeFile(), synchronizeFolder()! + + //swap directories + std::for_each(baseDirectory.subDirs.begin(), baseDirectory.subDirs.end(), Redetermine(config)); + //swap files + std::for_each(baseDirectory.subFiles.begin(), baseDirectory.subFiles.end(), Redetermine(config)); + //recurse into sub-dirs + std::for_each(baseDirectory.subDirs.begin(), baseDirectory.subDirs.end(), + boost::bind(static_cast<void (*)(const SyncConfiguration&, HierarchyObject&)>(redetermineSyncDirection), boost::cref(config), _1)); } -//add(!) all files and subfolder gridlines that are dependent from the directory -void FreeFileSync::addSubElements(const FileComparison& fileCmp, const FileCompareLine& relevantRow, std::set<int>& subElements) +void FreeFileSync::redetermineSyncDirection(const MainConfiguration& currentMainCfg, FolderComparison& folderCmp) { - const FileDescrLine& relFileDescrLeft = relevantRow.fileDescrLeft; - const FileDescrLine& relFileDescrRight = relevantRow.fileDescrRight; - - Zstring relevantDirectory; - if (relFileDescrLeft.objType == FileDescrLine::TYPE_DIRECTORY) - relevantDirectory = Zstring(relFileDescrLeft.relativeName.c_str(), relFileDescrLeft.relativeName.length()); - else if (relFileDescrRight.objType == FileDescrLine::TYPE_DIRECTORY) - relevantDirectory = Zstring(relFileDescrRight.relativeName.c_str(), relFileDescrRight.relativeName.length()); - else + if (folderCmp.size() == 0) return; + else if (folderCmp.size() != currentMainCfg.additionalPairs.size() + 1) + throw std::logic_error("Programming Error: Contract violation!"); - relevantDirectory += globalFunctions::FILE_NAME_SEPARATOR; //FILE_NAME_SEPARATOR needed to exclude subfile/dirs only + //main pair + redetermineSyncDirection(currentMainCfg.syncConfiguration, folderCmp[0]); - for (FileComparison::const_iterator i = fileCmp.begin(); i != fileCmp.end(); ++i) + //add. folder pairs + for (std::vector<FolderPairEnh>::const_iterator i = currentMainCfg.additionalPairs.begin(); i != currentMainCfg.additionalPairs.end(); ++i) { - if (i->fileDescrLeft.objType != FileDescrLine::TYPE_NOTHING) - { - if (i->fileDescrLeft.relativeName.StartsWith(relevantDirectory)) - subElements.insert(i - fileCmp.begin()); - } - //"else if": no need to do a redundant check on both sides: relative names should be the same! - else if (i->fileDescrRight.objType != FileDescrLine::TYPE_NOTHING) - { - if (i->fileDescrRight.relativeName.StartsWith(relevantDirectory)) - subElements.insert(i - fileCmp.begin()); - } + redetermineSyncDirection(i->altSyncConfig.get() ? i->altSyncConfig->syncConfiguration : currentMainCfg.syncConfiguration, + folderCmp[i - currentMainCfg.additionalPairs.begin() + 1]); } } -//############################################################################################################ -struct SortedFileName +class SetNewDirection { - unsigned position; - Zstring name; +public: + SetNewDirection(SyncDirection newDirection) : + newDirection_(newDirection) {} - bool operator < (const SortedFileName& b) const + void operator()(FileSystemObject& fsObj) const { - return position < b.position; + fsObj.syncDir = newDirection_; } + + void setSyncDirectionSub(FreeFileSync::HierarchyObject& hierObj) + { + //directories + std::for_each(hierObj.subDirs.begin(), hierObj.subDirs.end(), *this); + //files + std::for_each(hierObj.subFiles.begin(), hierObj.subFiles.end(), *this); + //recurse into sub-dirs + std::for_each(hierObj.subDirs.begin(), hierObj.subDirs.end(), boost::bind(&SetNewDirection::setSyncDirectionSub, this, _1)); + } + +private: + SyncDirection newDirection_; }; -//assemble message containing all files to be deleted -std::pair<wxString, int> FreeFileSync::deleteFromGridAndHDPreview(const FileComparison& fileCmp, - const std::set<int>& rowsToDeleteOnLeft, - const std::set<int>& rowsToDeleteOnRight, - const bool deleteOnBothSides) +void FreeFileSync::setSyncDirection(SyncDirection newDirection, FileSystemObject& fsObj) +{ + fsObj.syncDir = newDirection; + + DirMapping* dirObj = dynamic_cast<DirMapping*>(&fsObj); + if (dirObj) //process subdirectories also! + SetNewDirection(newDirection).setSyncDirectionSub(*dirObj); +} + + +void FreeFileSync::applyFiltering(const MainConfiguration& currentMainCfg, FolderComparison& folderCmp) +{ + assert (folderCmp.size() == 0 || folderCmp.size() == currentMainCfg.additionalPairs.size() + 1); + + if (folderCmp.size() != currentMainCfg.additionalPairs.size() + 1) + return; + + //main pair + FreeFileSync::FilterProcess(currentMainCfg.includeFilter, currentMainCfg.excludeFilter).filterAll(folderCmp[0]); + + //add. folder pairs + for (std::vector<FolderPairEnh>::const_iterator i = currentMainCfg.additionalPairs.begin(); i != currentMainCfg.additionalPairs.end(); ++i) + { + HierarchyObject& baseDirectory = folderCmp[i - currentMainCfg.additionalPairs.begin() + 1]; + + if (i->altFilter.get()) + FreeFileSync::FilterProcess(i->altFilter->includeFilter, i->altFilter->excludeFilter).filterAll(baseDirectory); + else + FreeFileSync::FilterProcess(currentMainCfg.includeFilter, currentMainCfg.excludeFilter).filterAll(baseDirectory); + } +} + + +//############################################################################################################ +std::pair<wxString, int> FreeFileSync::deleteFromGridAndHDPreview( //assemble message containing all files to be deleted + const std::vector<FileSystemObject*>& rowsToDeleteOnLeft, + const std::vector<FileSystemObject*>& rowsToDeleteOnRight, + const bool deleteOnBothSides) { wxString filesToDelete; int totalDelCount = 0; @@ -252,266 +310,197 @@ std::pair<wxString, int> FreeFileSync::deleteFromGridAndHDPreview(const FileComp if (deleteOnBothSides) { //mix selected rows from left and right - std::set<int> rowsToDelete = rowsToDeleteOnLeft; - for (std::set<int>::const_iterator i = rowsToDeleteOnRight.begin(); i != rowsToDeleteOnRight.end(); ++i) - rowsToDelete.insert(*i); + std::set<FileSystemObject*> rowsToDelete(rowsToDeleteOnLeft.begin(), rowsToDeleteOnLeft.end()); + rowsToDelete.insert(rowsToDeleteOnRight.begin(), rowsToDeleteOnRight.end()); - for (std::set<int>::const_iterator i = rowsToDelete.begin(); i != rowsToDelete.end(); ++i) + for (std::set<FileSystemObject*>::const_iterator i = rowsToDelete.begin(); i != rowsToDelete.end(); ++i) { - const FileCompareLine& currentCmpLine = fileCmp[*i]; + const FileSystemObject& currObj = *(*i); - if (currentCmpLine.fileDescrLeft.objType != FileDescrLine::TYPE_NOTHING) + if (!currObj.isEmpty<LEFT_SIDE>()) { - filesToDelete += (currentCmpLine.fileDescrLeft.fullName + wxT("\n")).c_str(); + filesToDelete += (currObj.getFullName<LEFT_SIDE>() + wxT("\n")).c_str(); ++totalDelCount; } - if (currentCmpLine.fileDescrRight.objType != FileDescrLine::TYPE_NOTHING) + if (!currObj.isEmpty<RIGHT_SIDE>()) { - filesToDelete += (currentCmpLine.fileDescrRight.fullName + wxT("\n")).c_str(); + filesToDelete += (currObj.getFullName<RIGHT_SIDE>() + wxT("\n")).c_str(); ++totalDelCount; } - filesToDelete+= wxT("\n"); + filesToDelete += wxT("\n"); } - - return std::pair<wxString, int>(filesToDelete, totalDelCount); } else //delete selected files only { - std::set<SortedFileName> outputTable; //sort selected files from left and right ascending by row number - - SortedFileName newEntry; - for (std::set<int>::const_iterator i = rowsToDeleteOnLeft.begin(); i != rowsToDeleteOnLeft.end(); ++i) + for (std::vector<FileSystemObject*>::const_iterator i = rowsToDeleteOnLeft.begin(); i != rowsToDeleteOnLeft.end(); ++i) { - const FileCompareLine& currentCmpLine = fileCmp[*i]; + const FileSystemObject& currObj = *(*i); - if (currentCmpLine.fileDescrLeft.objType != FileDescrLine::TYPE_NOTHING) + if (!currObj.isEmpty<LEFT_SIDE>()) { - newEntry.position = *i * 10; - newEntry.name = currentCmpLine.fileDescrLeft.fullName; - outputTable.insert(newEntry); + filesToDelete += (currObj.getFullName<LEFT_SIDE>() + wxT("\n")).c_str(); ++totalDelCount; } } - for (std::set<int>::const_iterator i = rowsToDeleteOnRight.begin(); i != rowsToDeleteOnRight.end(); ++i) + for (std::vector<FileSystemObject*>::const_iterator i = rowsToDeleteOnRight.begin(); i != rowsToDeleteOnRight.end(); ++i) { - const FileCompareLine& currentCmpLine = fileCmp[*i]; + const FileSystemObject& currObj = *(*i); - if (currentCmpLine.fileDescrRight.objType != FileDescrLine::TYPE_NOTHING) + if (!currObj.isEmpty<RIGHT_SIDE>()) { - newEntry.position = *i * 10 + 1; //ensure files on left are before files on right for the same row number - newEntry.name = currentCmpLine.fileDescrRight.fullName; - outputTable.insert(newEntry); + filesToDelete += (currObj.getFullName<RIGHT_SIDE>() + wxT("\n")).c_str(); ++totalDelCount; } } - - for (std::set<SortedFileName>::const_iterator i = outputTable.begin(); i != outputTable.end(); ++i) - filesToDelete += (i->name + wxT("\n")).c_str(); - - return std::pair<wxString, int>(filesToDelete, totalDelCount); - } -} - - -class RemoveAtExit //this class ensures, that the result of the method below is ALWAYS written on exit, even if exceptions are thrown! -{ -public: - RemoveAtExit(FreeFileSync::FileComparison& fileCmp) : - gridToWrite(fileCmp) {} - - ~RemoveAtExit() - { - globalFunctions::removeRowsFromVector(rowsProcessed, gridToWrite); } - void removeRow(int nr) - { - rowsProcessed.insert(nr); - } - -private: - FreeFileSync::FileComparison& gridToWrite; - std::set<int> rowsProcessed; -}; - - -template <bool leftSide> //update compareGrid row information after deletion from leftSide (or !leftSide) -inline -void updateCmpLineAfterDeletion(const int rowNr, const SyncConfiguration& syncConfig, FileComparison& fileCmp, RemoveAtExit& markForRemoval) -{ - //retrieve all files and subfolder gridlines that are dependent from this deleted entry - std::set<int> rowsToDelete; - rowsToDelete.insert(rowNr); - FreeFileSync::addSubElements(fileCmp, fileCmp[rowNr], rowsToDelete); - - //remove deleted entries from fileCmp (or adapt it if deleted from one side only) - for (std::set<int>::iterator j = rowsToDelete.begin(); j != rowsToDelete.end(); ++j) - { - FileCompareLine& currentLine = fileCmp[*j]; - - //file descriptor for "other side" - const FileDescrLine* const fileDescrPartner = leftSide ? ¤tLine.fileDescrRight : ¤tLine.fileDescrLeft; - - //remove deleted entries from grid - if (fileDescrPartner->objType == FileDescrLine::TYPE_NOTHING) - markForRemoval.removeRow(*j); - else - { - //get descriptor for file to be deleted; evaluated at compile time - FileDescrLine* const fileDescr = leftSide ? ¤tLine.fileDescrLeft : ¤tLine.fileDescrRight; - - //initialize fileDescr for deleted file/folder - *fileDescr = FileDescrLine(); - - //adapt the compare result and sync direction - if (leftSide) //again evaluated at compile time - { - currentLine.cmpResult = FILE_RIGHT_SIDE_ONLY; - currentLine.syncDir = convertToSyncDirection(syncConfig.exRightSideOnly); - } - else - { - currentLine.cmpResult = FILE_LEFT_SIDE_ONLY; - currentLine.syncDir = convertToSyncDirection(syncConfig.exLeftSideOnly); - } - } - } + return std::make_pair(filesToDelete, totalDelCount); } -template <bool leftSide> -void deleteFromGridAndHDOneSide(FileComparison& fileCmp, - const std::set<int>& rowsToDeleteOneSide, +template <SelectedSide side> +void deleteFromGridAndHDOneSide(std::vector<FileSystemObject*>& rowsToDeleteOneSide, const bool useRecycleBin, - RemoveAtExit& markForRemoval, - const SyncConfiguration& syncConfig, DeleteFilesHandler* statusHandler) { - for (std::set<int>::const_iterator i = rowsToDeleteOneSide.begin(); i != rowsToDeleteOneSide.end(); ++i) + for (std::vector<FileSystemObject*>::const_iterator i = rowsToDeleteOneSide.begin(); i != rowsToDeleteOneSide.end(); ++i) { - //get descriptor for file to be deleted; evaluated at compile time - const FileDescrLine* const fileDescr = leftSide ? &fileCmp[*i].fileDescrLeft : &fileCmp[*i].fileDescrRight; - - while (true) + if (!(*i)->isEmpty<side>()) { - try + while (true) { - switch (fileDescr->objType) + try { - case FileDescrLine::TYPE_FILE: - FreeFileSync::removeFile(fileDescr->fullName, useRecycleBin); - updateCmpLineAfterDeletion<leftSide>(*i, syncConfig, fileCmp, markForRemoval); //remove entries from fileCmp - statusHandler->deletionSuccessful(); //notify successful file/folder deletion - break; - case FileDescrLine::TYPE_DIRECTORY: - FreeFileSync::removeDirectory(fileDescr->fullName, useRecycleBin); - updateCmpLineAfterDeletion<leftSide>(*i, syncConfig, fileCmp, markForRemoval); //remove entries from fileCmp - statusHandler->deletionSuccessful(); //notify successful file/folder deletion - break; - case FileDescrLine::TYPE_NOTHING: + FileMapping* fileObj = dynamic_cast<FileMapping*>(*i); + if (fileObj != NULL) + { + FreeFileSync::removeFile(fileObj->getFullName<side>(), useRecycleBin); + fileObj->removeObject<side>(); + statusHandler->deletionSuccessful(); //notify successful file/folder deletion + } + else + { + DirMapping* dirObj = dynamic_cast<DirMapping*>(*i); + if (dirObj != NULL) + { + FreeFileSync::removeDirectory(dirObj->getFullName<side>(), useRecycleBin); + dirObj->removeObject<side>(); //directory: removes recursively! + statusHandler->deletionSuccessful(); //notify successful file/folder deletion + } + else + assert(!"It's no file, no dir, what is it then?"); + } + break; } + catch (const FileError& error) + { + DeleteFilesHandler::Response rv = statusHandler->reportError(error.show()); - break; - } - catch (const FileError& error) - { - DeleteFilesHandler::Response rv = statusHandler->reportError(error.show()); - - if (rv == DeleteFilesHandler::IGNORE_ERROR) - break; + if (rv == DeleteFilesHandler::IGNORE_ERROR) + break; - else if (rv == DeleteFilesHandler::RETRY) - ; //continue in loop - else - assert (false); + else if (rv == DeleteFilesHandler::RETRY) + ; //continue in loop + else + assert (false); + } } } } } -void FreeFileSync::deleteFromGridAndHD(FileComparison& fileCmp, - const std::set<int>& rowsToDeleteOnLeft, - const std::set<int>& rowsToDeleteOnRight, +class FinalizeDeletion +{ +public: + FinalizeDeletion(FolderComparison& folderCmp, const MainConfiguration& mainConfig) : + folderCmp_(folderCmp), + mainConfig_(mainConfig) {} + + ~FinalizeDeletion() + { + std::for_each(folderCmp_.begin(), folderCmp_.end(), FileSystemObject::removeEmpty); + redetermineSyncDirection(mainConfig_, folderCmp_); + } + +private: + FolderComparison& folderCmp_; + const MainConfiguration& mainConfig_; +}; + + +void FreeFileSync::deleteFromGridAndHD(FolderComparison& folderCmp, //attention: rows will be physically deleted! + std::vector<FileSystemObject*>& rowsToDeleteOnLeft, //refresh GUI grid after deletion to remove invalid rows + std::vector<FileSystemObject*>& rowsToDeleteOnRight, //all pointers need to be bound! const bool deleteOnBothSides, const bool useRecycleBin, - const SyncConfiguration& syncConfig, + const MainConfiguration& mainConfig, DeleteFilesHandler* statusHandler) { - //remove deleted rows from fileCmp (AFTER all rows to be deleted are known: consider row references! - RemoveAtExit markForRemoval(fileCmp); //ensure that fileCmp is always written to, even if method is exitted via exceptions + if (folderCmp.size() == 0) + return; + else if (folderCmp.size() != mainConfig.additionalPairs.size() + 1) + throw std::logic_error("Programming Error: Contract violation!"); + + FinalizeDeletion dummy(folderCmp, mainConfig); //ensure cleanup: redetermination of sync-directions and removal of invalid rows + if (deleteOnBothSides) { - //mix selected rows from left and right - std::set<int> rowsToDeleteBothSides = rowsToDeleteOnLeft; - for (std::set<int>::const_iterator i = rowsToDeleteOnRight.begin(); i != rowsToDeleteOnRight.end(); ++i) - rowsToDeleteBothSides.insert(*i); - - deleteFromGridAndHDOneSide<true>(fileCmp, - rowsToDeleteBothSides, - useRecycleBin, - markForRemoval, - syncConfig, - statusHandler); - - deleteFromGridAndHDOneSide<false>(fileCmp, - rowsToDeleteBothSides, - useRecycleBin, - markForRemoval, - syncConfig, - statusHandler); + //mix selected rows from left and right (and remove duplicates) + std::set<FileSystemObject*> temp(rowsToDeleteOnLeft.begin(), rowsToDeleteOnLeft.end()); + temp.insert(rowsToDeleteOnRight.begin(), rowsToDeleteOnRight.end()); + + std::vector<FileSystemObject*> rowsToDeleteBothSides(temp.begin(), temp.end()); + + deleteFromGridAndHDOneSide<LEFT_SIDE>(rowsToDeleteBothSides, + useRecycleBin, + statusHandler); + + deleteFromGridAndHDOneSide<RIGHT_SIDE>(rowsToDeleteBothSides, + useRecycleBin, + statusHandler); } else { - deleteFromGridAndHDOneSide<true>(fileCmp, - rowsToDeleteOnLeft, - useRecycleBin, - markForRemoval, - syncConfig, - statusHandler); - - deleteFromGridAndHDOneSide<false>(fileCmp, - rowsToDeleteOnRight, - useRecycleBin, - markForRemoval, - syncConfig, - statusHandler); + deleteFromGridAndHDOneSide<LEFT_SIDE>(rowsToDeleteOnLeft, + useRecycleBin, + statusHandler); + + deleteFromGridAndHDOneSide<RIGHT_SIDE>(rowsToDeleteOnRight, + useRecycleBin, + statusHandler); } } //############################################################################################################ inline -void writeTwoDigitNumber(unsigned int number, wxChar*& position) +void writeTwoDigitNumber(unsigned int number, wxString& string) { assert (number < 100); - *position = '0' + number / 10; - position[1] = '0' + number % 10; - - position += 2; + string += '0' + number / 10; + string += '0' + number % 10; } inline -void writeFourDigitNumber(unsigned int number, wxChar*& position) +void writeFourDigitNumber(unsigned int number, wxString& string) { assert (number < 10000); - *position = '0' + number / 1000; + string += '0' + number / 1000; number %= 1000; - position[1] = '0' + number / 100; + string += '0' + number / 100; number %= 100; - position[2] = '0' + number / 10; + string += '0' + number / 10; number %= 10; - position[3] = '0' + number; - - position += 4; + string += '0' + number; } @@ -534,9 +523,9 @@ wxString FreeFileSync::utcTimeToLocalString(const wxLongLong& utcTime, const Zst &lastWriteTimeUtc, //pointer to UTC file time to convert &localFileTime //pointer to converted file time ) == 0) - throw RuntimeException(wxString(_("Conversion error:")) + wxT(" FILETIME -> local FILETIME: ") + - wxT("(") + wxULongLong(lastWriteTimeUtc.dwHighDateTime, lastWriteTimeUtc.dwLowDateTime).ToString() + wxT(") ") + - filename.c_str() + wxT("\n\n") + getLastErrorFormatted().c_str()); + throw std::runtime_error(std::string((wxString(_("Conversion error:")) + wxT(" FILETIME -> local FILETIME: ") + + wxT("(") + wxULongLong(lastWriteTimeUtc.dwHighDateTime, lastWriteTimeUtc.dwLowDateTime).ToString() + wxT(") ") + + filename.c_str() + wxT("\n\n") + getLastErrorFormatted()).To8BitData())); if (localFileTime.dwHighDateTime > 0x7fffffff) return _("Error"); //this actually CAN happen if UTC time is just below this border and ::FileTimeToLocalFileTime() adds 2 hours due to DST or whatever! @@ -548,29 +537,28 @@ wxString FreeFileSync::utcTimeToLocalString(const wxLongLong& utcTime, const Zst &localFileTime, //pointer to file time to convert &time //pointer to structure to receive system time ) == 0) - throw RuntimeException(wxString(_("Conversion error:")) + wxT(" local FILETIME -> SYSTEMTIME: ") + - wxT("(") + wxULongLong(localFileTime.dwHighDateTime, localFileTime.dwLowDateTime).ToString() + wxT(") ") + - filename.c_str() + wxT("\n\n") + getLastErrorFormatted().c_str()); + throw std::runtime_error(std::string((wxString(_("Conversion error:")) + wxT(" local FILETIME -> SYSTEMTIME: ") + + wxT("(") + wxULongLong(localFileTime.dwHighDateTime, localFileTime.dwLowDateTime).ToString() + wxT(") ") + + filename.c_str() + wxT("\n\n") + getLastErrorFormatted()).To8BitData())); //assemble time string (performance optimized) - wxChar formattedTime[21]; - wxChar* p = formattedTime; - - writeFourDigitNumber(time.wYear, p); - *(p++) = wxChar('-'); - writeTwoDigitNumber(time.wMonth, p); - *(p++) = wxChar('-'); - writeTwoDigitNumber(time.wDay, p); - *(p++) = wxChar(' '); - *(p++) = wxChar(' '); - writeTwoDigitNumber(time.wHour, p); - *(p++) = wxChar(':'); - writeTwoDigitNumber(time.wMinute, p); - *(p++) = wxChar(':'); - writeTwoDigitNumber(time.wSecond, p); - *p = 0; - - return wxString(formattedTime); + wxString formattedTime; + formattedTime.reserve(20); + + writeFourDigitNumber(time.wYear, formattedTime); + formattedTime += wxChar('-'); + writeTwoDigitNumber(time.wMonth, formattedTime); + formattedTime += wxChar('-'); + writeTwoDigitNumber(time.wDay, formattedTime); + formattedTime += wxChar(' '); + formattedTime += wxChar(' '); + writeTwoDigitNumber(time.wHour, formattedTime); + formattedTime += wxChar(':'); + writeTwoDigitNumber(time.wMinute, formattedTime); + formattedTime += wxChar(':'); + writeTwoDigitNumber(time.wSecond, formattedTime); + + return formattedTime; #elif defined FFS_LINUX tm* timeinfo; diff --git a/algorithm.h b/algorithm.h index a61859bf..25fc6338 100644 --- a/algorithm.h +++ b/algorithm.h @@ -1,12 +1,13 @@ #ifndef ALGORITHM_H_INCLUDED #define ALGORITHM_H_INCLUDED -#include "structures.h" +#include "fileHierarchy.h" class ErrorHandler; class wxComboBox; class wxTextCtrl; class wxDirPickerCtrl; +class wxScrolledWindow; namespace FreeFileSync @@ -19,18 +20,22 @@ namespace FreeFileSync void setDirectoryName(const wxString& dirname, wxTextCtrl* txtCtrl, wxDirPickerCtrl* dirPicker); void setDirectoryName(const wxString& dirname, wxComboBox* txtCtrl, wxDirPickerCtrl* dirPicker); + void scrollToBottom(wxScrolledWindow* scrWindow); - void swapGrids(const SyncConfiguration& config, FolderComparison& folderCmp); + void swapGrids2(const MainConfiguration& config, FolderComparison& folderCmp); - void redetermineSyncDirection(const SyncConfiguration& config, FolderComparison& folderCmp); + void redetermineSyncDirection(const SyncConfiguration& config, HierarchyObject& baseDirectory); + void redetermineSyncDirection(const MainConfiguration& currentMainCfg, FolderComparison& folderCmp); - void addSubElements(const FileComparison& fileCmp, const FileCompareLine& relevantRow, std::set<int>& subElements); + void setSyncDirection(SyncDirection newDirection, FileSystemObject& fsObj); //set new direction (recursively) - //manual deletion of files on main grid: runs at individual directory pair level - std::pair<wxString, int> deleteFromGridAndHDPreview(const FileComparison& fileCmp, //returns wxString with elements to be deleted and total count - const std::set<int>& rowsToDeleteOnLeft, - const std::set<int>& rowsToDeleteOnRight, - const bool deleteOnBothSides); + void applyFiltering(const MainConfiguration& currentMainCfg, FolderComparison& folderCmp); + + //manual deletion of files on main grid + std::pair<wxString, int> deleteFromGridAndHDPreview( //returns wxString with elements to be deleted and total count + const std::vector<FileSystemObject*>& rowsToDeleteOnLeft, //all pointers need to be bound! + const std::vector<FileSystemObject*>& rowsToDeleteOnRight, // + const bool deleteOnBothSides); class DeleteFilesHandler { @@ -49,28 +54,16 @@ namespace FreeFileSync virtual void deletionSuccessful() = 0; //called for each file/folder that has been deleted }; - void deleteFromGridAndHD(FileComparison& fileCmp, - const std::set<int>& rowsToDeleteOnLeft, - const std::set<int>& rowsToDeleteOnRight, + void deleteFromGridAndHD(FolderComparison& folderCmp, //attention: rows will be physically deleted! + std::vector<FileSystemObject*>& rowsToDeleteOnLeft, //refresh GUI grid after deletion to remove invalid rows + std::vector<FileSystemObject*>& rowsToDeleteOnRight, //all pointers need to be bound! const bool deleteOnBothSides, const bool useRecycleBin, - const SyncConfiguration& syncConfig, + const MainConfiguration& mainConfig, DeleteFilesHandler* statusHandler); wxString utcTimeToLocalString(const wxLongLong& utcTime, const Zstring& filename); - - //enhanced binary search template: returns an iterator - template <class ForwardIterator, class T> - inline - ForwardIterator custom_binary_search (ForwardIterator first, ForwardIterator last, const T& value) - { - first = lower_bound(first, last, value); - if (first != last && !(value < *first)) - return first; - else - return last; - } } #endif // ALGORITHM_H_INCLUDED diff --git a/comparison.cpp b/comparison.cpp index 843990a8..a43a0c86 100644 --- a/comparison.cpp +++ b/comparison.cpp @@ -1,4 +1,5 @@ #include "comparison.h" +#include <stdexcept> #include "shared/globalFunctions.h" #include <wx/intl.h> #include <wx/timer.h> @@ -13,238 +14,326 @@ #include "shared/systemFunctions.h" #include "shared/fileTraverser.h" #include "library/filter.h" +#include <map> +#include "fileHierarchy.h" +#include <boost/bind.hpp> using namespace FreeFileSync; -class GetAllFilesFull : public FreeFileSync::TraverseCallback +std::vector<FreeFileSync::FolderPairCfg> FreeFileSync::extractCompareCfg(const MainConfiguration& mainCfg) { + std::vector<FolderPairCfg> output; + + //add main pair + output.push_back( + FolderPairCfg(mainCfg.mainFolderPair.leftDirectory, + mainCfg.mainFolderPair.rightDirectory, + mainCfg.filterIsActive, + mainCfg.includeFilter, + mainCfg.excludeFilter, + mainCfg.syncConfiguration)); + + //add additional pairs + for (std::vector<FolderPairEnh>::const_iterator i = mainCfg.additionalPairs.begin(); i != mainCfg.additionalPairs.end(); ++i) + output.push_back( + FolderPairCfg(i->leftDirectory, + i->rightDirectory, + mainCfg.filterIsActive, + i->altFilter.get() ? i->altFilter->includeFilter : mainCfg.includeFilter, + i->altFilter.get() ? i->altFilter->excludeFilter : mainCfg.excludeFilter, + i->altSyncConfig.get() ? i->altSyncConfig->syncConfiguration : mainCfg.syncConfiguration)); + + return output; +} + + + + + + + +template <bool filterActive> +class BaseDirCallback; + + +template <bool filterActive> +class DirCallback : public FreeFileSync::TraverseCallback +{ +public: + DirCallback(BaseDirCallback<filterActive>* baseCallback, + const Zstring& relNameParentPf, //postfixed with FILE_NAME_SEPARATOR! + DirContainer& output, + StatusHandler* handler) : + baseCallback_(baseCallback), + relNameParentPf_(relNameParentPf), + output_(output), + statusHandler(handler) {} + + virtual ~DirCallback() {} + + virtual ReturnValue onFile(const DefaultChar* shortName, const Zstring& fullName, const FileInfo& details); + virtual ReturnValDir onDir(const DefaultChar* shortName, const Zstring& fullName); + virtual ReturnValue onError(const wxString& errorText); + +private: + BaseDirCallback<filterActive>* const baseCallback_; + const Zstring relNameParentPf_; + DirContainer& output_; + StatusHandler* const statusHandler; +}; + + + +template <bool filterActive> +class BaseDirCallback : public DirCallback<filterActive> +{ + friend class DirCallback<filterActive>; public: - GetAllFilesFull(DirectoryDescrType& output, const Zstring& dirThatIsSearched, const FilterProcess* filter, StatusHandler* handler) : - m_output(output), - directory(dirThatIsSearched), + BaseDirCallback(DirContainer& output, const FilterProcess* filter, StatusHandler* handler) : + DirCallback<filterActive>(this, Zstring(), output, handler), textScanning(Zstring(_("Scanning:")) + wxT(" \n")), - filterInstance(filter), - statusHandler(handler) - { - prefixLength = directory.length(); - } + filterInstance(filter) {} + virtual TraverseCallback::ReturnValDir onDir(const DefaultChar* shortName, const Zstring& fullName); - inline - void writeText(const wxChar* text, const int length, wxChar*& currentPos) - { - memcpy(currentPos, text, length * sizeof(wxChar)); - currentPos += length; - } +private: + typedef boost::shared_ptr<const DirCallback<filterActive> > CallbackPointer; + const Zstring textScanning; + std::vector<CallbackPointer> callBackBox; //collection of callback pointers to handle ownership + const FilterProcess* const filterInstance; //must NOT be NULL if (filterActive) +}; - virtual ReturnValue onFile(const DefaultChar* shortName, const Zstring& fullName, const FileInfo& details) - { - //apply filter before processing (use relative name!) - if (filterInstance) - if ( !filterInstance->matchesFileFilterIncl(fullName.c_str() + prefixLength) || - filterInstance->matchesFileFilterExcl(fullName.c_str() + prefixLength)) - { - statusHandler->requestUiRefresh(); - return TRAVERSING_CONTINUE; - } - FileDescrLine fileDescr; - fileDescr.fullName = fullName; - fileDescr.relativeName = fullName.zsubstr(prefixLength); - fileDescr.lastWriteTimeRaw = details.lastWriteTimeRaw; - fileDescr.fileSize = details.fileSize; - fileDescr.objType = FileDescrLine::TYPE_FILE; - m_output.push_back(fileDescr); - - //assemble status message (performance optimized) = textScanning + wxT("\"") + fullName + wxT("\"") - const unsigned int statusTextMaxLen = 2000; - wxChar statusText[statusTextMaxLen]; - wxChar* position = statusText; - if (textScanning.length() + fullName.length() + 2 < statusTextMaxLen) //leave room for 0 terminating char! +template <bool filterActive> +TraverseCallback::ReturnValue DirCallback<filterActive>::onFile(const DefaultChar* shortName, const Zstring& fullName, const FileInfo& details) +{ + //apply filter before processing (use relative name!) + if (filterActive) + { + if (!baseCallback_->filterInstance->passFileFilter(relNameParentPf_ + shortName)) { - writeText(textScanning.c_str(), textScanning.length(), position); - writeText(wxT("\""), 1, position); - writeText(fullName.c_str(), fullName.length(), position); - writeText(wxT("\""), 1, position); + statusHandler->requestUiRefresh(); + return TRAVERSING_CONTINUE; } - *position = 0; + } - //update UI/commandline status information - statusHandler->updateStatusText(statusText); - //add 1 element to the progress indicator - statusHandler->updateProcessedData(1, 0); //NO performance issue at all - //trigger display refresh - statusHandler->requestUiRefresh(); + output_.addSubFile(FileDescriptor(shortName, details.lastWriteTimeRaw, details.fileSize), relNameParentPf_); - return TRAVERSING_CONTINUE; - } + //assemble status message (performance optimized) = textScanning + wxT("\"") + fullName + wxT("\"") + Zstring statusText = baseCallback_->textScanning; + statusText.reserve(statusText.length() + fullName.length() + 2); + statusText += DefaultChar('\"'); + statusText += fullName; + statusText += DefaultChar('\"'); + + //update UI/commandline status information + statusHandler->updateStatusText(statusText); + //add 1 element to the progress indicator + statusHandler->updateProcessedData(1, 0); //NO performance issue at all + //trigger display refresh + statusHandler->requestUiRefresh(); + return TRAVERSING_CONTINUE; +} - virtual ReturnValDir onDir(const DefaultChar* shortName, const Zstring& fullName) + +template <bool filterActive> +TraverseCallback::ReturnValDir DirCallback<filterActive>::onDir(const DefaultChar* shortName, const Zstring& fullName) +{ + using globalFunctions::FILE_NAME_SEPARATOR; + + Zstring relName = relNameParentPf_; + relName += shortName; + + //apply filter before processing (use relative name!) + if (filterActive) { - //apply filter before processing (use relative name!) - if (filterInstance) + bool subObjMightMatch = true; + if (!baseCallback_->filterInstance->passDirFilter(relName, &subObjMightMatch)) { - if (!filterInstance->matchesDirFilterIncl(fullName.c_str() + prefixLength)) - { - statusHandler->requestUiRefresh(); //if not included: CONTINUE traversing subdirs - return ReturnValDir(ReturnValDir::Continue(), this); - } - else if (filterInstance->matchesDirFilterExcl(fullName.c_str() + prefixLength)) + statusHandler->requestUiRefresh(); + + if (subObjMightMatch) { - statusHandler->requestUiRefresh(); //if excluded: do NOT traverse subdirs - return ReturnValDir::Ignore(); + DirContainer& subDir = output_.addSubDir(DirDescriptor(shortName), relNameParentPf_); + + DirCallback* subDirCallback = new DirCallback(baseCallback_, relName += FILE_NAME_SEPARATOR, subDir, statusHandler); + baseCallback_->callBackBox.push_back(typename BaseDirCallback<filterActive>::CallbackPointer(subDirCallback)); //handle ownership + + //attention: ensure directory filtering is applied to exclude actually filtered directories + return ReturnValDir(ReturnValDir::Continue(), subDirCallback); } + else + return ReturnValDir::Ignore(); //do NOT traverse subdirs } -#ifdef FFS_WIN - if ( fullName.EndsWith(wxT("\\RECYCLER")) || - fullName.EndsWith(wxT("\\System Volume Information"))) - { - statusHandler->requestUiRefresh(); - return ReturnValDir::Ignore(); - } -#endif // FFS_WIN - - FileDescrLine fileDescr; - fileDescr.fullName = fullName; - fileDescr.relativeName = fullName.zsubstr(prefixLength); - fileDescr.lastWriteTimeRaw = 0; //irrelevant for directories - fileDescr.fileSize = 0; //currently used by getBytesToTransfer - fileDescr.objType = FileDescrLine::TYPE_DIRECTORY; - m_output.push_back(fileDescr); - - //assemble status message (performance optimized) = textScanning + wxT("\"") + fullName + wxT("\"") - const unsigned int statusTextMaxLen = 2000; - wxChar statusText[statusTextMaxLen]; - wxChar* position = statusText; - if (textScanning.length() + fullName.length() + 2 < statusTextMaxLen) //leave room for 0 terminating char! - { - writeText(textScanning.c_str(), textScanning.length(), position); - writeText(wxT("\""), 1, position); - writeText(fullName.c_str(), fullName.length(), position); - writeText(wxT("\""), 1, position); - } - *position = 0; + } - //update UI/commandline status information - statusHandler->updateStatusText(statusText); - //add 1 element to the progress indicator - statusHandler->updateProcessedData(1, 0); //NO performance issue at all - //trigger display refresh - statusHandler->requestUiRefresh(); + DirContainer& subDir = output_.addSubDir(DirDescriptor(shortName), relNameParentPf_); - return ReturnValDir(ReturnValDir::Continue(), this); - } + //assemble status message (performance optimized) = textScanning + wxT("\"") + fullName + wxT("\"") + Zstring statusText = baseCallback_->textScanning; + statusText.reserve(statusText.length() + fullName.length() + 2); + statusText += DefaultChar('\"'); + statusText += fullName; + statusText += DefaultChar('\"'); + + //update UI/commandline status information + statusHandler->updateStatusText(statusText); + //add 1 element to the progress indicator + statusHandler->updateProcessedData(1, 0); //NO performance issue at all + //trigger display refresh + statusHandler->requestUiRefresh(); + + DirCallback* subDirCallback = new DirCallback(baseCallback_, relName+=FILE_NAME_SEPARATOR, subDir, statusHandler); + baseCallback_->callBackBox.push_back(typename BaseDirCallback<filterActive>::CallbackPointer(subDirCallback)); //handle ownership + + return ReturnValDir(ReturnValDir::Continue(), subDirCallback); +} - virtual ReturnValue onError(const wxString& errorText) + +template <bool filterActive> +TraverseCallback::ReturnValue DirCallback<filterActive>::onError(const wxString& errorText) +{ + while (true) { - while (true) + switch (statusHandler->reportError(errorText)) { - switch (statusHandler->reportError(errorText)) - { - case ErrorHandler::IGNORE_ERROR: - return TRAVERSING_CONTINUE; - case ErrorHandler::RETRY: - break; //I have to admit "retry" is a bit of a fake here... at least the user has opportunity to abort! - } + case ErrorHandler::IGNORE_ERROR: + return TRAVERSING_CONTINUE; + case ErrorHandler::RETRY: + break; //I have to admit "retry" is a bit of a fake here... at least the user has opportunity to abort! } - - return TRAVERSING_CONTINUE; } -private: - DirectoryDescrType& m_output; - Zstring directory; - int prefixLength; - const Zstring textScanning; - const FilterProcess* const filterInstance; //may be NULL! - StatusHandler* statusHandler; -}; + return TRAVERSING_CONTINUE; //dummy value +} -struct DescrBufferLine +template <bool filterActive> +TraverseCallback::ReturnValDir BaseDirCallback<filterActive>::onDir(const DefaultChar* shortName, const Zstring& fullName) { +//#ifdef FFS_WIN => transparency is more important: just scan every file +// if ( fullName.EndsWith(wxT("\\RECYCLER")) || +// fullName.EndsWith(wxT("\\System Volume Information"))) +// { +// DirCallback<filterActive>::statusHandler->requestUiRefresh(); +// return TraverseCallback::ReturnValDir::Ignore(); +// } +//#endif // FFS_WIN + return DirCallback<filterActive>::onDir(shortName, fullName); +} + + +//------------------------------------------------------------------------------------------ +struct DirBufferKey +{ + DirBufferKey(const Zstring& dirname, + boost::shared_ptr<const FilterProcess>& filterInst) : + directoryName(dirname), + filterInstance(filterInst) {} + Zstring directoryName; - DirectoryDescrType* directoryDesc; + boost::shared_ptr<const FilterProcess> filterInstance; //buffering has to consider filtering! - bool operator < (const DescrBufferLine& b) const + bool operator < (const DirBufferKey& b) const { #ifdef FFS_WIN //Windows does NOT distinguish between upper/lower-case - return (directoryName.CmpNoCase(b.directoryName) < 0); + const int rv = directoryName.CmpNoCase(b.directoryName); #elif defined FFS_LINUX //Linux DOES distinguish between upper/lower-case - return (directoryName.Cmp(b.directoryName) < 0); + const int rv = directoryName.Cmp(b.directoryName); #endif + if (rv != 0) + return rv < 0; + + return filterInstance.get() && b.filterInstance.get() ? + *filterInstance < *b.filterInstance : + filterInstance.get() < b.filterInstance.get(); //at least one of these is NULL } }; -class DirectoryDescrBuffer //buffer multiple scans of the same directories +//------------------------------------------------------------------------------------------ +class CompareProcess::DirectoryBuffer //buffer multiple scans of the same directories { public: - DirectoryDescrBuffer(const bool traverseDirectorySymlinks, const FilterProcess* filter, StatusHandler* statusUpdater) : + DirectoryBuffer(const bool traverseDirectorySymlinks, StatusHandler* statusUpdater) : m_traverseDirectorySymlinks(traverseDirectorySymlinks), - filterInstance(filter), m_statusUpdater(statusUpdater) {} - ~DirectoryDescrBuffer() - { - //clean up - for (std::set<DescrBufferLine>::iterator i = buffer.begin(); i != buffer.end(); ++i) - delete i->directoryDesc; - } + const DirContainer& getDirectoryDescription(const Zstring& directoryPostfixed, bool filterActive, const wxString& includeFilter, const wxString& excludeFilter); - DirectoryDescrType* getDirectoryDescription(const Zstring& directoryFormatted) - { - DescrBufferLine bufferEntry; - bufferEntry.directoryName = directoryFormatted; - - std::set<DescrBufferLine>::iterator entryFound = buffer.find(bufferEntry); - if (entryFound != buffer.end()) - { - //entry found in buffer; return - return entryFound->directoryDesc; - } - else - { - //entry not found; create new one - bufferEntry.directoryDesc = new DirectoryDescrType; - buffer.insert(bufferEntry); //exception safety: insert into buffer right after creation! - - if (FreeFileSync::dirExists(directoryFormatted)) //folder existence already checked in startCompareProcess(): do not treat as error when arriving here! - { - //get all files and folders from directoryFormatted (and subdirectories) - GetAllFilesFull traverser(*bufferEntry.directoryDesc, directoryFormatted, filterInstance, m_statusUpdater); //exceptions may be thrown! - traverseFolder(directoryFormatted, m_traverseDirectorySymlinks, &traverser); - } +private: + typedef boost::shared_ptr<DirContainer> DirBufferValue; //exception safety: avoid memory leak + typedef std::map<DirBufferKey, DirBufferValue> BufferType; - return bufferEntry.directoryDesc; - } - } + static DirBufferKey createKey(const Zstring& directoryPostfixed, bool filterActive, const wxString& includeFilter, const wxString& excludeFilter); + DirContainer& insertIntoBuffer(const DirBufferKey& newKey); -private: - std::set<DescrBufferLine> buffer; + BufferType buffer; const bool m_traverseDirectorySymlinks; - const FilterProcess* const filterInstance; //may be NULL! StatusHandler* m_statusUpdater; }; +//------------------------------------------------------------------------------------------ + + +DirBufferKey CompareProcess::DirectoryBuffer::createKey(const Zstring& directoryPostfixed, bool filterActive, const wxString& includeFilter, const wxString& excludeFilter) +{ + boost::shared_ptr<const FilterProcess> filterInstance( + filterActive && FilterProcess(includeFilter, excludeFilter) != FilterProcess::nullFilter() ? //nullfilter: in: '*', ex '' + new FilterProcess(includeFilter, excludeFilter) : NULL); + + return DirBufferKey(directoryPostfixed, filterInstance); +} -void foldersAreValidForComparison(const std::vector<FolderPair>& folderPairs, StatusHandler* statusUpdater) +DirContainer& CompareProcess::DirectoryBuffer::insertIntoBuffer(const DirBufferKey& newKey) +{ + DirBufferValue baseContainer(new DirContainer); + buffer.insert(std::make_pair(newKey, baseContainer)); + + if (FreeFileSync::dirExists(newKey.directoryName.c_str())) //folder existence already checked in startCompareProcess(): do not treat as error when arriving here! + { + std::auto_ptr<TraverseCallback> traverser(newKey.filterInstance.get() ? + static_cast<TraverseCallback*>(new BaseDirCallback<true>(*baseContainer.get(), newKey.filterInstance.get(), m_statusUpdater)) : + static_cast<TraverseCallback*>(new BaseDirCallback<false>(*baseContainer.get(), NULL, m_statusUpdater))); + + //get all files and folders from directoryPostfixed (and subdirectories) + traverseFolder(newKey.directoryName, m_traverseDirectorySymlinks, traverser.get()); //exceptions may be thrown! + } + return *baseContainer.get(); +} + + +const DirContainer& CompareProcess::DirectoryBuffer::getDirectoryDescription( + const Zstring& directoryPostfixed, + bool filterActive, + const wxString& includeFilter, + const wxString& excludeFilter) +{ + const DirBufferKey searchKey = createKey(directoryPostfixed, filterActive, includeFilter, excludeFilter); + + BufferType::const_iterator entryFound = buffer.find(searchKey); + if (entryFound != buffer.end()) + return *entryFound->second.get(); //entry found in buffer; return + else + return insertIntoBuffer(searchKey); //entry not found; create new one +} + + +//------------------------------------------------------------------------------------------ +void foldersAreValidForComparison(const std::vector<FolderPairCfg>& folderPairsForm, StatusHandler* statusUpdater) { bool checkEmptyDirnameActive = true; //check for empty dirs just once const wxString additionalInfo = _("You can ignore the error to consider not existing directories as empty."); - for (std::vector<FolderPair>::const_iterator i = folderPairs.begin(); i != folderPairs.end(); ++i) + for (std::vector<FolderPairCfg>::const_iterator i = folderPairsForm.begin(); i != folderPairsForm.end(); ++i) { - const Zstring leftFolderName = getFormattedDirectoryName(i->leftDirectory); - const Zstring rightFolderName = getFormattedDirectoryName(i->rightDirectory); - //check if folder name is empty - if (leftFolderName.empty() || rightFolderName.empty()) + if (i->leftDirectory.empty() || i->rightDirectory.empty()) { if (checkEmptyDirnameActive) { @@ -264,11 +353,11 @@ void foldersAreValidForComparison(const std::vector<FolderPair>& folderPairs, St } //check if folders exist - if (!leftFolderName.empty()) - while (!FreeFileSync::dirExists(leftFolderName)) + if (!i->leftDirectory.empty()) + while (!FreeFileSync::dirExists(i->leftDirectory.c_str())) { ErrorHandler::Response rv = statusUpdater->reportError(wxString(_("Directory does not exist:")) + wxT("\n") + - wxT("\"") + leftFolderName + wxT("\"") + wxT("\n\n") + + wxT("\"") + i->leftDirectory + wxT("\"") + wxT("\n\n") + FreeFileSync::getLastErrorFormatted() + wxT(" ") + additionalInfo); if (rv == ErrorHandler::IGNORE_ERROR) break; @@ -278,11 +367,11 @@ void foldersAreValidForComparison(const std::vector<FolderPair>& folderPairs, St assert (false); } - if (!rightFolderName.empty()) - while (!FreeFileSync::dirExists(rightFolderName)) + if (!i->rightDirectory.empty()) + while (!FreeFileSync::dirExists(i->rightDirectory.c_str())) { ErrorHandler::Response rv = statusUpdater->reportError(wxString(_("Directory does not exist:")) + wxT("\n") + - wxT("\"") + rightFolderName + wxT("\"") + wxT("\n\n") + + wxT("\"") + i->rightDirectory + wxT("\"") + wxT("\n\n") + FreeFileSync::getLastErrorFormatted() + wxT(" ") + additionalInfo); if (rv == ErrorHandler::IGNORE_ERROR) break; @@ -295,43 +384,40 @@ void foldersAreValidForComparison(const std::vector<FolderPair>& folderPairs, St } -bool dependencyExists(const std::vector<Zstring>& folders, const Zstring& newFolder, wxString& warningMessage) +bool dependencyExists(const std::set<Zstring>& folders, const Zstring& newFolder, wxString& warningMessage) { - if (!newFolder.empty()) //empty folders names might be accepted by user - { - warningMessage.Clear(); - - for (std::vector<Zstring>::const_iterator i = folders.begin(); i != folders.end(); ++i) - if (!i->empty()) //empty folders names might be accepted by user - if (newFolder.StartsWith(*i) || i->StartsWith(newFolder)) - { - warningMessage = wxString(_("Directories are dependent! Be careful when setting up synchronization rules:")) + wxT("\n") + - wxT("\"") + i->c_str() + wxT("\",\n") + - wxT("\"") + newFolder.c_str() + wxT("\""); - return true; - } - } + for (std::set<Zstring>::const_iterator i = folders.begin(); i != folders.end(); ++i) + if (newFolder.StartsWith(*i) || i->StartsWith(newFolder)) + { + warningMessage = wxString(_("Directories are dependent! Be careful when setting up synchronization rules:")) + wxT("\n") + + wxT("\"") + i->c_str() + wxT("\",\n") + + wxT("\"") + newFolder.c_str() + wxT("\""); + return true; + } return false; } -bool foldersHaveDependencies(const std::vector<FolderPair>& folderPairs, wxString& warningMessage) +bool foldersHaveDependencies(const std::vector<FolderPairCfg>& folderPairsFrom, wxString& warningMessage) { warningMessage.Clear(); - std::vector<Zstring> folders; - for (std::vector<FolderPair>::const_iterator i = folderPairs.begin(); i != folderPairs.end(); ++i) + std::set<Zstring> folders; + for (std::vector<FolderPairCfg>::const_iterator i = folderPairsFrom.begin(); i != folderPairsFrom.end(); ++i) { - const Zstring leftFolderName = getFormattedDirectoryName(i->leftDirectory); - const Zstring rightFolderName = getFormattedDirectoryName(i->rightDirectory); - - if (dependencyExists(folders, leftFolderName, warningMessage)) - return true; - folders.push_back(leftFolderName); + if (!i->leftDirectory.empty()) //empty folders names might be accepted by user + { + if (dependencyExists(folders, i->leftDirectory, warningMessage)) + return true; + folders.insert(i->leftDirectory); + } - if (dependencyExists(folders, rightFolderName, warningMessage)) - return true; - folders.push_back(rightFolderName); + if (!i->rightDirectory.empty()) //empty folders names might be accepted by user + { + if (dependencyExists(folders, i->rightDirectory, warningMessage)) + return true; + folders.insert(i->rightDirectory); + } } return false; @@ -341,8 +427,7 @@ bool foldersHaveDependencies(const std::vector<FolderPair>& folderPairs, wxStrin CompareProcess::CompareProcess(const bool traverseSymLinks, const unsigned int fileTimeTol, const bool ignoreOneHourDiff, - xmlAccess::WarningMessages& warnings, - const FilterProcess* filter, //may be NULL + xmlAccess::OptionalDialogs& warnings, StatusHandler* handler) : fileTimeTolerance(fileTimeTol), ignoreOneHourDifference(ignoreOneHourDiff), @@ -350,13 +435,7 @@ CompareProcess::CompareProcess(const bool traverseSymLinks, statusUpdater(handler), txtComparingContentOfFiles(Zstring(_("Comparing content of files %x")).Replace(wxT("%x"), wxT("\n\"%x\""), false)) { - descriptionBuffer = new DirectoryDescrBuffer(traverseSymLinks, filter, handler); -} - - -CompareProcess::~CompareProcess() -{ - delete descriptionBuffer; + directoryBuffer.reset(new DirectoryBuffer(traverseSymLinks, handler)); } @@ -364,19 +443,16 @@ struct MemoryAllocator { MemoryAllocator() { - buffer1 = new unsigned char[bufferSize]; - buffer2 = new unsigned char[bufferSize]; + buffer = new unsigned char[bufferSize]; } ~MemoryAllocator() { - delete [] buffer1; - delete [] buffer2; + delete [] buffer; } static const unsigned int bufferSize = 512 * 1024; //512 kb seems to be the perfect buffer size - unsigned char* buffer1; - unsigned char* buffer2; + unsigned char* buffer; }; @@ -391,26 +467,27 @@ public: bool filesHaveSameContent(const Zstring& filename1, const Zstring& filename2, CompareCallback* callback) { - static MemoryAllocator memory; + static MemoryAllocator memory1; + static MemoryAllocator memory2; wxFFile file1(filename1.c_str(), wxT("rb")); if (!file1.IsOpened()) - throw FileError(wxString(_("Error reading file:")) + wxT(" \"") + filename1.c_str() + wxT("\"")); + throw FileError(wxString(_("Error opening file:")) + wxT(" \"") + filename1.c_str() + wxT("\"")); wxFFile file2(filename2.c_str(), wxT("rb")); if (!file2.IsOpened()) //NO cleanup necessary for (wxFFile) file1 - throw FileError(wxString(_("Error reading file:")) + wxT(" \"") + filename2.c_str() + wxT("\"")); + throw FileError(wxString(_("Error opening file:")) + wxT(" \"") + filename2.c_str() + wxT("\"")); wxLongLong bytesCompared; do { - const size_t length1 = file1.Read(memory.buffer1, MemoryAllocator::bufferSize); + const size_t length1 = file1.Read(memory1.buffer, MemoryAllocator::bufferSize); if (file1.Error()) throw FileError(wxString(_("Error reading file:")) + wxT(" \"") + filename1.c_str() + wxT("\"")); - const size_t length2 = file2.Read(memory.buffer2, MemoryAllocator::bufferSize); + const size_t length2 = file2.Read(memory2.buffer, MemoryAllocator::bufferSize); if (file2.Error()) throw FileError(wxString(_("Error reading file:")) + wxT(" \"") + filename2.c_str() + wxT("\"")); - if (length1 != length2 || memcmp(memory.buffer1, memory.buffer2, length1) != 0) + if (length1 != length2 || ::memcmp(memory1.buffer, memory2.buffer, length1) != 0) return false; bytesCompared += length1 * 2; @@ -531,10 +608,63 @@ bool filesHaveSameContentUpdating(const Zstring& filename1, const Zstring& filen return cmpAndUpdate.sameContent; }*/ +void formatPair(FolderPairCfg& input) +{ + input.leftDirectory = FreeFileSync::getFormattedDirectoryName(input.leftDirectory); + input.rightDirectory = FreeFileSync::getFormattedDirectoryName(input.rightDirectory); +} + + +struct ToBeRemoved +{ + bool operator()(const DirMapping& dirObj) const + { + return !dirObj.selectedForSynchronization && dirObj.subDirs.size() == 0 && dirObj.subFiles.size() == 0; + } +}; + + +class RemoveFilteredDirs +{ +public: + RemoveFilteredDirs(const wxString& include, const wxString& exclude) : + filterProc(include, exclude) {} + + void operator()(HierarchyObject& hierObj) + { + //process subdirs recursively + std::for_each(hierObj.subDirs.begin(), hierObj.subDirs.end(), *this); + + //filter subdirectories + std::for_each(hierObj.subDirs.begin(), hierObj.subDirs.end(), boost::bind(&RemoveFilteredDirs::filterDir, this, _1)); + + //remove superfluous directories + hierObj.subDirs.erase(std::remove_if(hierObj.subDirs.begin(), hierObj.subDirs.end(), ::ToBeRemoved()), hierObj.subDirs.end()); + } + + void filterDir(FreeFileSync::DirMapping& dirObj) + { + const Zstring relName = dirObj.isEmpty<FreeFileSync::LEFT_SIDE>() ? + dirObj.getRelativeName<FreeFileSync::RIGHT_SIDE>() : + dirObj.getRelativeName<FreeFileSync::LEFT_SIDE>(); + + dirObj.selectedForSynchronization = filterProc.passDirFilter(relName, NULL); //subObjMightMatch is always true in this context! + } + +private: + const FilterProcess filterProc; +}; + + +//filters and removes all excluded directories (but keeps those serving as parent folders) +void filterAndRemoveDirs(BaseDirMapping& baseDir, const wxString& include, const wxString& exclude) +{ + RemoveFilteredDirs(include, exclude)(baseDir); +} + -void CompareProcess::startCompareProcess(const std::vector<FolderPair>& directoryPairs, +void CompareProcess::startCompareProcess(const std::vector<FolderPairCfg>& directoryPairs, const CompareVariant cmpVar, - const SyncConfiguration& config, FolderComparison& output) { #ifndef __WXDEBUG__ @@ -547,11 +677,8 @@ void CompareProcess::startCompareProcess(const std::vector<FolderPair>& director statusUpdater->initNewProcess(-1, 0, StatusHandler::PROCESS_SCANNING); //it's not known how many files will be scanned => -1 objects //format directory pairs: ensure they end with globalFunctions::FILE_NAME_SEPARATOR! - std::vector<FolderPair> directoryPairsFormatted; - for (std::vector<FolderPair>::const_iterator i = directoryPairs.begin(); i != directoryPairs.end(); ++i) - directoryPairsFormatted.push_back( - FolderPair(FreeFileSync::getFormattedDirectoryName(i->leftDirectory), - FreeFileSync::getFormattedDirectoryName(i->rightDirectory))); + std::vector<FolderPairCfg> directoryPairsFormatted = directoryPairs; + std::for_each(directoryPairsFormatted.begin(), directoryPairsFormatted.end(), formatPair); //-------------------some basic checks:------------------------------------------ @@ -581,26 +708,35 @@ void CompareProcess::startCompareProcess(const std::vector<FolderPair>& director break; } - //actually this is the initial determination - FreeFileSync::redetermineSyncDirection(config, output_tmp); + + assert (output_tmp.size() == directoryPairsFormatted.size()); + + for (FolderComparison::iterator j = output_tmp.begin(); j != output_tmp.end(); ++j) + { + const FolderPairCfg& fpCfg = directoryPairsFormatted[j - output_tmp.begin()]; + + //attention: some filtered directories are still in the comparison result! (see include filter handling!) + if (fpCfg.filterIsActive) //let's filter them now... (and remove those that contain excluded elements only) + filterAndRemoveDirs(*j, fpCfg.includeFilter, fpCfg.excludeFilter); + + //set sync-direction initially + FreeFileSync::redetermineSyncDirection(fpCfg.syncConfiguration, *j); + } //only if everything was processed correctly output is written to! //note: output mustn't change during this process to be in sync with GUI grid view!!! output_tmp.swap(output); } - catch (const RuntimeException& theException) - { - statusUpdater->reportFatalError(theException.show().c_str()); - return; //should be obsolete! - } - catch (std::bad_alloc& e) + catch (const std::exception& e) { - statusUpdater->reportFatalError((wxString(_("System out of memory!")) + wxT(" ") + wxString::From8BitData(e.what())).c_str()); + if (dynamic_cast<const std::bad_alloc*>(&e) != NULL) + statusUpdater->reportFatalError(wxString(_("System out of memory!")) + wxT(" ") + wxString::From8BitData(e.what())); + else + statusUpdater->reportFatalError(wxString::From8BitData(e.what())); return; //should be obsolete! } } - //--------------------assemble conflict descriptions--------------------------- //check for very old dates or dates in the future @@ -614,7 +750,7 @@ wxString getConflictInvalidDate(const Zstring& fileNameFull, const wxLongLong& u //check for changed files with same modification date -wxString getConflictSameDateDiffSize(const FileCompareLine& cmpLine) +wxString getConflictSameDateDiffSize(const FileMapping& fileObj) { //some beautification... wxString left = wxString(_("Left")) + wxT(": "); @@ -624,18 +760,18 @@ wxString getConflictSameDateDiffSize(const FileCompareLine& cmpLine) right.Pad(maxPref - right.length(), wxT(' '), true); wxString msg = _("Files %x have the same date but a different size!"); - msg.Replace(wxT("%x"), wxString(wxT("\"")) + cmpLine.fileDescrLeft.relativeName.c_str() + wxT("\"")); + msg.Replace(wxT("%x"), wxString(wxT("\"")) + fileObj.getRelativeName<LEFT_SIDE>() + wxT("\"")); msg += wxT("\n\n"); - msg += left + wxT("\t") + _("Date") + wxT(": ") + utcTimeToLocalString(cmpLine.fileDescrLeft.lastWriteTimeRaw, - cmpLine.fileDescrLeft.fullName) + wxT(" \t") + _("Size") + wxT(": ") + cmpLine.fileDescrLeft.fileSize.ToString() + wxT("\n"); - msg += right + wxT("\t") + _("Date") + wxT(": ") + utcTimeToLocalString(cmpLine.fileDescrRight.lastWriteTimeRaw, - cmpLine.fileDescrRight.fullName) + wxT(" \t") + _("Size") + wxT(": ") + cmpLine.fileDescrRight.fileSize.ToString(); + msg += left + wxT("\t") + _("Date") + wxT(": ") + utcTimeToLocalString(fileObj.getLastWriteTime<LEFT_SIDE>(), + fileObj.getFullName<LEFT_SIDE>()) + wxT(" \t") + _("Size") + wxT(": ") + fileObj.getFileSize<LEFT_SIDE>().ToString() + wxT("\n"); + msg += right + wxT("\t") + _("Date") + wxT(": ") + utcTimeToLocalString(fileObj.getLastWriteTime<RIGHT_SIDE>(), + fileObj.getFullName<RIGHT_SIDE>()) + wxT(" \t") + _("Size") + wxT(": ") + fileObj.getFileSize<RIGHT_SIDE>().ToString(); return wxString(_("Conflict detected:")) + wxT("\n") + msg; } //check for files that have a difference in file modification date below 1 hour when DST check is active -wxString getConflictChangeWithinHour(const FileCompareLine& cmpLine) +wxString getConflictChangeWithinHour(const FileMapping& fileObj) { //some beautification... wxString left = wxString(_("Left")) + wxT(": "); @@ -644,19 +780,16 @@ wxString getConflictChangeWithinHour(const FileCompareLine& cmpLine) left.Pad(maxPref - left.length(), wxT(' '), true); right.Pad(maxPref - right.length(), wxT(' '), true); - wxString msg = _("Files %x have a file time difference of less than 1 hour! It's not safe to decide which one is newer due to Daylight Saving Time issues."); - msg.Replace(wxT("%x"), wxString(wxT("\"")) + cmpLine.fileDescrLeft.relativeName.c_str() + wxT("\"")); + wxString msg = _("Files %x have a file time difference of less than 1 hour!\n\nIt's not safe to decide which one is newer due to Daylight Saving Time issues."); + msg += wxString(wxT("\n")) + _("(Note that only FAT/FAT32 drives are affected by this problem!\nIn all other cases you can disable the setting \"ignore 1-hour difference\".)"); + msg.Replace(wxT("%x"), wxString(wxT("\"")) + fileObj.getRelativeName<LEFT_SIDE>() + wxT("\"")); msg += wxT("\n\n"); - msg += left + wxT("\t") + _("Date") + wxT(": ") + utcTimeToLocalString(cmpLine.fileDescrLeft.lastWriteTimeRaw, cmpLine.fileDescrLeft.fullName) + wxT("\n"); - msg += right + wxT("\t") + _("Date") + wxT(": ") + utcTimeToLocalString(cmpLine.fileDescrRight.lastWriteTimeRaw, cmpLine.fileDescrRight.fullName); + msg += left + wxT("\t") + _("Date") + wxT(": ") + utcTimeToLocalString(fileObj.getLastWriteTime<LEFT_SIDE>(), fileObj.getFullName<LEFT_SIDE>()) + wxT("\n"); + msg += right + wxT("\t") + _("Date") + wxT(": ") + utcTimeToLocalString(fileObj.getLastWriteTime<RIGHT_SIDE>(), fileObj.getFullName<RIGHT_SIDE>()); return wxString(_("Conflict detected:")) + wxT("\n") + msg; } -//----------------------------------------------------------------------------- - - -const CompareFilesResult FILE_UNDEFINED = CompareFilesResult(42); - +//----------------------------------------------------------------------------- inline bool sameFileTime(const wxLongLong& a, const wxLongLong& b, const unsigned tolerance) { @@ -667,385 +800,344 @@ bool sameFileTime(const wxLongLong& a, const wxLongLong& b, const unsigned toler } -void CompareProcess::compareByTimeSize(const std::vector<FolderPair>& directoryPairsFormatted, FolderComparison& output) +void CompareProcess::compareByTimeSize(const std::vector<FolderPairCfg>& directoryPairsFormatted, FolderComparison& output) { //process one folder pair after each other - for (std::vector<FolderPair>::const_iterator pair = directoryPairsFormatted.begin(); pair != directoryPairsFormatted.end(); ++pair) + for (std::vector<FolderPairCfg>::const_iterator pair = directoryPairsFormatted.begin(); pair != directoryPairsFormatted.end(); ++pair) { - FolderCompareLine newEntry; - newEntry.syncPair = *pair; + BaseDirMapping newEntry(pair->leftDirectory, pair->rightDirectory); output.push_back(newEntry); //attention: push_back() copies by value!!! performance: append BEFORE writing values into fileCmp! - FileComparison& fileCmp = output.back().fileCmp; + //do basis scan and retrieve files existing on both sides + std::vector<FileMapping*> compareCandidates; + performBaseComparison(*pair, output.back(), compareCandidates); - //do basis scan: only result lines of type FILE_UNDEFINED (files that exist on both sides) need to be determined after this call - this->performBaseComparison(*pair, fileCmp); + //PERF_START; //categorize files that exist on both sides - for (FileComparison::iterator i = fileCmp.begin(); i != fileCmp.end(); ++i) - if (i->cmpResult == FILE_UNDEFINED) + for (std::vector<FileMapping*>::iterator i = compareCandidates.begin(); i != compareCandidates.end(); ++i) + { + FileMapping* const line = *i; + if (line->getLastWriteTime<LEFT_SIDE>() != line->getLastWriteTime<RIGHT_SIDE>()) { - if (i->fileDescrLeft.lastWriteTimeRaw != i->fileDescrRight.lastWriteTimeRaw) + //number of seconds since Jan 1st 1970 + 1 year (needn't be too precise) + static const long oneYearFromNow = wxGetUTCTime() + 365 * 24 * 3600; + + //check for erroneous dates (but only if dates are not (EXACTLY) the same) + if ( line->getLastWriteTime<LEFT_SIDE>() < 0 || //earlier than Jan 1st 1970 + line->getLastWriteTime<RIGHT_SIDE>() < 0 || //earlier than Jan 1st 1970 + line->getLastWriteTime<LEFT_SIDE>() > oneYearFromNow || //dated more than one year in future + line->getLastWriteTime<RIGHT_SIDE>() > oneYearFromNow) //dated more than one year in future + { + line->cmpResult = FILE_CONFLICT; + if (line->getLastWriteTime<LEFT_SIDE>() < 0 || line->getLastWriteTime<LEFT_SIDE>() > oneYearFromNow) + line->conflictDescription = getConflictInvalidDate(line->getFullName<LEFT_SIDE>(), line->getLastWriteTime<LEFT_SIDE>()); + else + line->conflictDescription = getConflictInvalidDate(line->getFullName<RIGHT_SIDE>(), line->getLastWriteTime<RIGHT_SIDE>()); + } + else //from this block on all dates are at least "valid" { - //number of seconds since Jan 1st 1970 + 1 year (needn't be too precise) - static const long oneYearFromNow = wxGetUTCTime() + 365 * 24 * 3600; - - //check for erroneous dates (but only if dates are not (EXACTLY) the same) - if ( i->fileDescrLeft.lastWriteTimeRaw < 0 || //earlier than Jan 1st 1970 - i->fileDescrRight.lastWriteTimeRaw < 0 || //earlier than Jan 1st 1970 - i->fileDescrLeft.lastWriteTimeRaw > oneYearFromNow || //dated more than one year in future - i->fileDescrRight.lastWriteTimeRaw > oneYearFromNow) //dated more than one year in future + //last write time may differ by up to 2 seconds (NTFS vs FAT32) + if (sameFileTime(line->getLastWriteTime<LEFT_SIDE>(), line->getLastWriteTime<RIGHT_SIDE>(), fileTimeTolerance)) { - i->cmpResult = FILE_CONFLICT; - if (i->fileDescrLeft.lastWriteTimeRaw < 0 || i->fileDescrLeft.lastWriteTimeRaw > oneYearFromNow) - i->conflictDescription = OptionalString(getConflictInvalidDate(i->fileDescrLeft.fullName, i->fileDescrLeft.lastWriteTimeRaw)); + if (line->getFileSize<LEFT_SIDE>() == line->getFileSize<RIGHT_SIDE>()) + line->cmpResult = FILE_EQUAL; else - i->conflictDescription = OptionalString(getConflictInvalidDate(i->fileDescrRight.fullName, i->fileDescrRight.lastWriteTimeRaw)); + { + line->cmpResult = FILE_CONFLICT; //same date, different filesize + line->conflictDescription = getConflictSameDateDiffSize(*line); + } } - else //from this block on all dates are at least "valid" + else { - //last write time may differ by up to 2 seconds (NTFS vs FAT32) - if (sameFileTime(i->fileDescrLeft.lastWriteTimeRaw, i->fileDescrRight.lastWriteTimeRaw, fileTimeTolerance)) + //finally: DST +/- 1-hour check: test if time diff is exactly +/- 1-hour (respecting 2 second FAT precision) + if (ignoreOneHourDifference && sameFileTime(line->getLastWriteTime<LEFT_SIDE>(), line->getLastWriteTime<RIGHT_SIDE>(), 3600 + 2)) { - if (i->fileDescrLeft.fileSize == i->fileDescrRight.fileSize) - i->cmpResult = FILE_EQUAL; - else + //date diff < 1 hour is a conflict: it's not safe to determine which file is newer + if (sameFileTime(line->getLastWriteTime<LEFT_SIDE>(), line->getLastWriteTime<RIGHT_SIDE>(), 3600 - 2 - 1)) { - i->cmpResult = FILE_CONFLICT; //same date, different filesize - i->conflictDescription = OptionalString(getConflictSameDateDiffSize(*i)); + line->cmpResult = FILE_CONFLICT; + line->conflictDescription = getConflictChangeWithinHour(*line); } - } - else - { - //finally: DST +/- 1-hour check: test if time diff is exactly +/- 1-hour (respecting 2 second FAT precision) - if (ignoreOneHourDifference && sameFileTime(i->fileDescrLeft.lastWriteTimeRaw, i->fileDescrRight.lastWriteTimeRaw, 3600 + 2)) + else //exact +/- 1-hour detected: treat as equal { - //date diff < 1 hour is a conflict: it's not safe to determine which file is newer - if (sameFileTime(i->fileDescrLeft.lastWriteTimeRaw, i->fileDescrRight.lastWriteTimeRaw, 3600 - 2 - 1)) - { - i->cmpResult = FILE_CONFLICT; - i->conflictDescription = OptionalString(getConflictChangeWithinHour(*i)); - } - else //exact +/- 1-hour detected: treat as equal + if (line->getFileSize<LEFT_SIDE>() == line->getFileSize<RIGHT_SIDE>()) + line->cmpResult = FILE_EQUAL; + else { - if (i->fileDescrLeft.fileSize == i->fileDescrRight.fileSize) - i->cmpResult = FILE_EQUAL; - else - { - i->cmpResult = FILE_CONFLICT; //same date, different filesize - i->conflictDescription = OptionalString(getConflictSameDateDiffSize(*i)); - } + line->cmpResult = FILE_CONFLICT; //same date, different filesize + line->conflictDescription = getConflictSameDateDiffSize(*line); } } + } + else + { + if (line->getLastWriteTime<LEFT_SIDE>() < line->getLastWriteTime<RIGHT_SIDE>()) + line->cmpResult = FILE_RIGHT_NEWER; else - { - if (i->fileDescrLeft.lastWriteTimeRaw < i->fileDescrRight.lastWriteTimeRaw) - i->cmpResult = FILE_RIGHT_NEWER; - else - i->cmpResult = FILE_LEFT_NEWER; - } + line->cmpResult = FILE_LEFT_NEWER; } } } - else //same write time + } + else //same write time + { + if (line->getFileSize<LEFT_SIDE>() == line->getFileSize<RIGHT_SIDE>()) + line->cmpResult = FILE_EQUAL; + else { - if (i->fileDescrLeft.fileSize == i->fileDescrRight.fileSize) - i->cmpResult = FILE_EQUAL; - else - { - i->cmpResult = FILE_CONFLICT; //same date, different filesize - i->conflictDescription = OptionalString(getConflictSameDateDiffSize(*i)); - } + line->cmpResult = FILE_CONFLICT; //same date, different filesize + line->conflictDescription = getConflictSameDateDiffSize(*line); } } + } } } -class RemoveAtExit //this class ensures, that the result of the method below is ALWAYS written on exit, even if exceptions are thrown! -{ -public: - RemoveAtExit(FileComparison& fileCmp) : - gridToWrite(fileCmp) {} - - ~RemoveAtExit() - { - globalFunctions::removeRowsFromVector(rowsToDelete, gridToWrite); - } - - void markRow(int nr) - { - rowsToDelete.insert(nr); - } - -private: - FileComparison& gridToWrite; - std::set<int> rowsToDelete; -}; - - -void getBytesToCompare(const FolderComparison& grid, const FolderCompRef& rowsToCompare, int& objectsTotal, wxULongLong& dataTotal) +wxULongLong getBytesToCompare(const std::vector<FileMapping*>& rowsToCompare) { - objectsTotal = 0; - dataTotal = 0; - - for (FolderComparison::const_iterator j = grid.begin(); j != grid.end(); ++j) - { - const FileComparison& fileCmp = j->fileCmp; + wxULongLong dataTotal; - const std::set<int>& index = rowsToCompare[j - grid.begin()]; - for (std::set<int>::const_iterator i = index.begin(); i != index.end(); ++i) - { - const FileCompareLine& line = fileCmp[*i]; - dataTotal += line.fileDescrLeft.fileSize; - dataTotal += line.fileDescrRight.fileSize; - } + for (std::vector<FileMapping*>::const_iterator j = rowsToCompare.begin(); j != rowsToCompare.end(); ++j) + dataTotal += (*j)->getFileSize<LEFT_SIDE>(); //left and right filesizes should be the same - objectsTotal += index.size() * 2; - } + return dataTotal * 2; } -void CompareProcess::compareByContent(const std::vector<FolderPair>& directoryPairsFormatted, FolderComparison& output) +void CompareProcess::compareByContent(const std::vector<FolderPairCfg>& directoryPairsFormatted, FolderComparison& output) { //PERF_START; + std::vector<FileMapping*> compareCandidates; //process one folder pair after each other - for (std::vector<FolderPair>::const_iterator pair = directoryPairsFormatted.begin(); pair != directoryPairsFormatted.end(); ++pair) + for (std::vector<FolderPairCfg>::const_iterator pair = directoryPairsFormatted.begin(); pair != directoryPairsFormatted.end(); ++pair) { - FolderCompareLine newEntry; - newEntry.syncPair = *pair; + BaseDirMapping newEntry(pair->leftDirectory, pair->rightDirectory); output.push_back(newEntry); //attention: push_back() copies by value!!! performance: append BEFORE writing values into fileCmp! - FileComparison& fileCmp = output.back().fileCmp; - - //do basis scan: only result lines of type FILE_UNDEFINED (files that exist on both sides) need to be determined after this call - this->performBaseComparison(*pair, fileCmp); + //do basis scan and retrieve candidates for binary comparison (files existing on both sides) + performBaseComparison(*pair, output.back(), compareCandidates); } //finish categorization... + std::vector<FileMapping*> filesToCompareBytewise; - FolderCompRef rowsToCompareBytewise; //content comparison of file content happens AFTER finding corresponding files + //content comparison of file content happens AFTER finding corresponding files //in order to separate into two processes (scanning and comparing) - for (FolderComparison::iterator j = output.begin(); j != output.end(); ++j) + for (std::vector<FileMapping*>::iterator i = compareCandidates.begin(); i != compareCandidates.end(); ++i) { - FileComparison& fileCmp = j->fileCmp; + //pre-check: files have different content if they have a different filesize + if ((*i)->getFileSize<LEFT_SIDE>() != (*i)->getFileSize<RIGHT_SIDE>()) + (*i)->cmpResult = FILE_DIFFERENT; + else + filesToCompareBytewise.push_back(*i); + } - std::set<int> newEntry; - for (FileComparison::iterator i = fileCmp.begin(); i != fileCmp.end(); ++i) - { - if (i->cmpResult == FILE_UNDEFINED) - { //pre-check: files have different content if they have a different filesize - if (i->fileDescrLeft.fileSize != i->fileDescrRight.fileSize) - i->cmpResult = FILE_DIFFERENT; - else - newEntry.insert(i - fileCmp.begin()); - } - } - rowsToCompareBytewise.push_back(newEntry); - } - int objectsTotal = 0; - wxULongLong dataTotal; - getBytesToCompare(output, rowsToCompareBytewise, objectsTotal, dataTotal); + const int objectsTotal = filesToCompareBytewise.size() * 2; + const wxULongLong bytesTotal = getBytesToCompare(filesToCompareBytewise); statusUpdater->initNewProcess(objectsTotal, - globalFunctions::convertToSigned(dataTotal), + globalFunctions::convertToSigned(bytesTotal), StatusHandler::PROCESS_COMPARING_CONTENT); //compare files (that have same size) bytewise... - for (FolderComparison::iterator j = output.begin(); j != output.end(); ++j) + for (std::vector<FileMapping*>::const_iterator j = filesToCompareBytewise.begin(); j != filesToCompareBytewise.end(); ++j) { - FileComparison& fileCmp = j->fileCmp; + FileMapping* const gridline = *j; - //mark erroneous rows for deletion from output - RemoveAtExit removeRowsAtExit(fileCmp); //note: running at individual folder pair level! + Zstring statusText = txtComparingContentOfFiles; + statusText.Replace(wxT("%x"), gridline->getRelativeName<LEFT_SIDE>(), false); + statusUpdater->updateStatusText(statusText); - const std::set<int>& index = rowsToCompareBytewise[j - output.begin()]; - for (std::set<int>::const_iterator i = index.begin(); i != index.end(); ++i) + //check files that exist in left and right model but have different content + while (true) { - FileCompareLine& gridline = fileCmp[*i]; - - Zstring statusText = txtComparingContentOfFiles; - statusText.Replace(wxT("%x"), gridline.fileDescrLeft.relativeName.c_str(), false); - statusUpdater->updateStatusText(statusText); + //trigger display refresh + statusUpdater->requestUiRefresh(); - //check files that exist in left and right model but have different content - while (true) + try { - //trigger display refresh - statusUpdater->requestUiRefresh(); + if (filesHaveSameContentUpdating(gridline->getFullName<LEFT_SIDE>(), + gridline->getFullName<RIGHT_SIDE>(), + gridline->getFileSize<LEFT_SIDE>() * 2, + statusUpdater)) + gridline->cmpResult = FILE_EQUAL; + else + gridline->cmpResult = FILE_DIFFERENT; - try + statusUpdater->updateProcessedData(2, 0); //processed data is communicated in subfunctions! + break; + } + catch (FileError& error) + { + ErrorHandler::Response rv = statusUpdater->reportError(error.show()); + if (rv == ErrorHandler::IGNORE_ERROR) { - if (filesHaveSameContentUpdating(gridline.fileDescrLeft.fullName, - gridline.fileDescrRight.fullName, - gridline.fileDescrLeft.fileSize * 2, - statusUpdater)) - gridline.cmpResult = FILE_EQUAL; - else - gridline.cmpResult = FILE_DIFFERENT; - - statusUpdater->updateProcessedData(2, 0); //processed data is communicated in subfunctions! + gridline->cmpResult = FILE_CONFLICT; //same date, different filesize + gridline->conflictDescription = wxString(_("Conflict detected:")) + wxT("\n") + _("Comparing files by content failed."); break; } - catch (FileError& error) - { - ErrorHandler::Response rv = statusUpdater->reportError(error.show()); - if (rv == ErrorHandler::IGNORE_ERROR) - { - removeRowsAtExit.markRow(*i); - break; - } - else if (rv == ErrorHandler::RETRY) - ; //continue with loop - else - assert (false); - } + + else if (rv == ErrorHandler::RETRY) + ; //continue with loop + else + assert (false); } } } } -class ThreadSorting : public wxThread +class MergeSides { public: - ThreadSorting(DirectoryDescrType* directory) : - wxThread(wxTHREAD_JOINABLE), - m_directory(directory) + MergeSides(const Zstring& baseDirLeftPf, + const Zstring& baseDirRightPf, + std::vector<FileMapping*>& appendUndefinedOut) : + baseDirLeft(baseDirLeftPf), + baseDirRight(baseDirRightPf), + appendUndefined(appendUndefinedOut) {} + + void execute(const DirContainer& leftSide, const DirContainer& rightSide, HierarchyObject& output) { - if (Create() != wxTHREAD_NO_ERROR) - throw RuntimeException(wxString(wxT("Error creating thread for sorting!"))); - } + //ATTENTION: HierarchyObject::retrieveById() can only work correctly if the following conditions are fulfilled: + //1. on each level, files are added first, then directories (=> file id < dir id) + //2. when a directory is added, all subdirectories must be added immediately (recursion) before the next dir on this level is added + //3. entries may be deleted but NEVER new ones inserted!!! + //=> this allows for a quasi-binary search by id! - ~ThreadSorting() {} + //reserve() fulfills two task here: 1. massive performance improvement! 2. ensure references in appendUndefined remain valid! + output.subFiles.reserve(leftSide.getSubFiles().size() + rightSide.getSubFiles().size()); //assume worst case! + output.subDirs.reserve( leftSide.getSubDirs().size() + rightSide.getSubDirs().size()); // + for (DirContainer::SubFileList::const_iterator i = leftSide.getSubFiles().begin(); i != leftSide.getSubFiles().end(); ++i) + { + DirContainer::SubFileList::const_iterator j = rightSide.getSubFiles().find(*i); + + //find files that exist on left but not on right + if (j == rightSide.getSubFiles().end()) + output.addSubFile(i->getData(), FILE_LEFT_SIDE_ONLY, FileMapping::nullData(), + RelNamesBuffered(baseDirLeft, //base sync dir postfixed + baseDirRight, + i->getParentRelNamePf())); //relative parent name postfixed + //find files that exist on left and right + else + { + appendUndefined.push_back( + &output.addSubFile(i->getData(), FILE_EQUAL, j->getData(), //FILE_EQUAL is just a dummy-value here + RelNamesBuffered(baseDirLeft, baseDirRight, i->getParentRelNamePf()))); + } + } - ExitCode Entry() - { - std::sort(m_directory->begin(), m_directory->end()); - return 0; - } + //find files that exist on right but not on left + for (DirContainer::SubFileList::const_iterator j = rightSide.getSubFiles().begin(); j != rightSide.getSubFiles().end(); ++j) + { + if (leftSide.getSubFiles().find(*j) == leftSide.getSubFiles().end()) + output.addSubFile(FileMapping::nullData(), FILE_RIGHT_SIDE_ONLY, j->getData(), + RelNamesBuffered(baseDirLeft, baseDirRight, j->getParentRelNamePf())); + } -private: - DirectoryDescrType* m_directory; -}; +//----------------------------------------------------------------------------------------------- + for (DirContainer::SubDirList::const_iterator i = leftSide.getSubDirs().begin(); i != leftSide.getSubDirs().end(); ++i) + { + DirContainer::SubDirList::const_iterator j = rightSide.getSubDirs().find(*i); -void CompareProcess::performBaseComparison(const FolderPair& pair, FileComparison& output) -{ - //PERF_START; - //retrieve sets of files (with description data) - DirectoryDescrType* directoryLeft = descriptionBuffer->getDirectoryDescription(pair.leftDirectory); - DirectoryDescrType* directoryRight = descriptionBuffer->getDirectoryDescription(pair.rightDirectory); + //find directories that exist on left but not on right + if (j == rightSide.getSubDirs().end()) + { + DirMapping& newDirMap = output.addSubDir(i->getData(), DIR_LEFT_SIDE_ONLY, DirMapping::nullData(), + RelNamesBuffered(baseDirLeft, baseDirRight, i->getParentRelNamePf())); + fillOneSide<true>(*i, newDirMap); //recurse into subdirectories + } + else //directories that exist on both sides + { + DirMapping& newDirMap = output.addSubDir(i->getData(), DIR_EQUAL, j->getData(), + RelNamesBuffered(baseDirLeft, baseDirRight, i->getParentRelNamePf())); + execute(*i, *j, newDirMap); //recurse into subdirectories + } + } - statusUpdater->updateStatusText(_("Generating file list...")); - statusUpdater->forceUiRefresh(); //keep total number of scanned files up to date - //PERF_STOP; + //find directories that exist on right but not on left + for (DirContainer::SubDirList::const_iterator j = rightSide.getSubDirs().begin(); j != rightSide.getSubDirs().end(); ++j) + { + if (leftSide.getSubDirs().find(*j) == leftSide.getSubDirs().end()) + { + DirMapping& newDirMap = output.addSubDir(DirMapping::nullData(), DIR_RIGHT_SIDE_ONLY, j->getData(), + RelNamesBuffered(baseDirLeft, baseDirRight, j->getParentRelNamePf())); + fillOneSide<false>(*j, newDirMap); //recurse into subdirectories + } + } + } - //we use binary search when comparing the directory structures: so sort() first - if (wxThread::GetCPUCount() >= 2) //do it the multithreaded way: +private: + template <bool leftSide> + void fillOneSide(const DirContainer& dirCont, HierarchyObject& output) { - //no synchronization (multithreading) needed here: directoryLeft and directoryRight are disjunct - //reference counting Zstring also shouldn't be an issue, as no strings are deleted during std::sort() - std::auto_ptr<ThreadSorting> sortLeft(new ThreadSorting(directoryLeft)); - std::auto_ptr<ThreadSorting> sortRight(new ThreadSorting(directoryRight)); + //reserve() fulfills two task here: 1. massive performance improvement! 2. ensure references in appendUndefined remain valid! + output.subFiles.reserve(dirCont.getSubFiles().size()); + output.subDirs.reserve( dirCont.getSubDirs(). size()); - if (sortLeft->Run() != wxTHREAD_NO_ERROR) - throw RuntimeException(wxString(wxT("Error starting thread for sorting!"))); + for (DirContainer::SubFileList::const_iterator i = dirCont.getSubFiles().begin(); i != dirCont.getSubFiles().end(); ++i) + { + if (leftSide) + output.addSubFile(i->getData(), FILE_LEFT_SIDE_ONLY, FileMapping::nullData(), + RelNamesBuffered(baseDirLeft, baseDirRight, i->getParentRelNamePf())); + else + output.addSubFile(FileMapping::nullData(), FILE_RIGHT_SIDE_ONLY, i->getData(), + RelNamesBuffered(baseDirLeft, baseDirRight, i->getParentRelNamePf())); + } - if (directoryLeft != directoryRight) //attention: might point to the same vector because of buffer! + for (DirContainer::SubDirList::const_iterator i = dirCont.getSubDirs().begin(); i != dirCont.getSubDirs().end(); ++i) { - if (sortRight->Run() != wxTHREAD_NO_ERROR) - throw RuntimeException(wxString(wxT("Error starting thread for sorting!"))); + DirMapping& newDirMap = leftSide ? + output.addSubDir(i->getData(), DIR_LEFT_SIDE_ONLY, DirMapping::nullData(), + RelNamesBuffered(baseDirLeft, baseDirRight, i->getParentRelNamePf())) : + output.addSubDir(DirMapping::nullData(), DIR_RIGHT_SIDE_ONLY, i->getData(), + RelNamesBuffered(baseDirLeft, baseDirRight, i->getParentRelNamePf())); - if (sortRight->Wait() != 0) - throw RuntimeException(wxString(wxT("Error waiting for thread (sorting)!"))); + fillOneSide<leftSide>(*i, newDirMap); //recurse into subdirectories } - - if (sortLeft->Wait() != 0) - throw RuntimeException(wxString(wxT("Error waiting for thread (sorting)!"))); } - else //single threaded - { - std::sort(directoryLeft->begin(), directoryLeft->end()); - if (directoryLeft != directoryRight) //attention: might point to the same vector because of buffer! - std::sort(directoryRight->begin(), directoryRight->end()); - } - //PERF_STOP; - //reserve some space to avoid too many vector reallocations: doesn't make much sense for multiple folder pairs, but doesn't hurt either - output.reserve(output.size() + unsigned(std::max(directoryLeft->size(), directoryRight->size()) * 1.2)); + const Zstring& baseDirLeft; + const Zstring& baseDirRight; + std::vector<FileMapping*>& appendUndefined; +}; - //begin base comparison - FileCompareLine newline(FILE_UNDEFINED, SYNC_DIR_NONE, true); - for (DirectoryDescrType::const_iterator i = directoryLeft->begin(); i != directoryLeft->end(); ++i) - { - //find files/folders that exist in left file tree but not in right one - DirectoryDescrType::const_iterator j = custom_binary_search(directoryRight->begin(), directoryRight->end(), *i); - if (j == directoryRight->end()) - { - newline.fileDescrLeft = *i; - newline.fileDescrRight = FileDescrLine(); - newline.cmpResult = FILE_LEFT_SIDE_ONLY; - output.push_back(newline); - } - //find files/folders that exist on left and right side - else - { - const FileDescrLine::ObjectType typeLeft = i->objType; - const FileDescrLine::ObjectType typeRight = j->objType; - //files... - if (typeLeft == FileDescrLine::TYPE_FILE && typeRight == FileDescrLine::TYPE_FILE) - { - newline.fileDescrLeft = *i; - newline.fileDescrRight = *j; - newline.cmpResult = FILE_UNDEFINED; //not yet determined! - output.push_back(newline); - } - //directories... - else if (typeLeft == FileDescrLine::TYPE_DIRECTORY && typeRight == FileDescrLine::TYPE_DIRECTORY) - { - newline.fileDescrLeft = *i; - newline.fileDescrRight = *j; - newline.cmpResult = FILE_EQUAL; - output.push_back(newline); - } - //if we have a nameclash between a file and a directory: split into two separate rows - else - { - assert (typeLeft != typeRight); +void CompareProcess::performBaseComparison(const FolderPairCfg& pair, BaseDirMapping& output, std::vector<FileMapping*>& appendUndefined) +{ + assert(output.subDirs.empty()); + assert(output.subFiles.empty()); - newline.fileDescrLeft = *i; - newline.fileDescrRight = FileDescrLine(); - newline.cmpResult = FILE_LEFT_SIDE_ONLY; - output.push_back(newline); + //PERF_START; - newline.fileDescrLeft = FileDescrLine(); - newline.fileDescrRight = *j; - newline.cmpResult = FILE_RIGHT_SIDE_ONLY; - output.push_back(newline); - } - } - } + //scan directories + const DirContainer& directoryLeft = directoryBuffer->getDirectoryDescription(pair.leftDirectory, + pair.filterIsActive, + pair.includeFilter, + pair.excludeFilter); + const DirContainer& directoryRight = directoryBuffer->getDirectoryDescription(pair.rightDirectory, + pair.filterIsActive, + pair.includeFilter, + pair.excludeFilter); - for (DirectoryDescrType::const_iterator j = directoryRight->begin(); j != directoryRight->end(); ++j) - { - //find files/folders that exist in right file model but not in left model - if (custom_binary_search(directoryLeft->begin(), directoryLeft->end(), *j) == directoryLeft->end()) - { - newline.fileDescrLeft = FileDescrLine(); - newline.fileDescrRight = *j; - newline.cmpResult = FILE_RIGHT_SIDE_ONLY; - output.push_back(newline); - } - } - //PERF_STOP + statusUpdater->updateStatusText(_("Generating file list...")); + statusUpdater->forceUiRefresh(); //keep total number of scanned files up to date + + //PERF_STOP; + + MergeSides(pair.leftDirectory, + pair.rightDirectory, + appendUndefined).execute(directoryLeft, directoryRight, output); + //PERF_STOP; } diff --git a/comparison.h b/comparison.h index 0833defe..dc8c4b8d 100644 --- a/comparison.h +++ b/comparison.h @@ -1,51 +1,74 @@ #ifndef COMPARISON_H_INCLUDED #define COMPARISON_H_INCLUDED -#include "structures.h" +#include "fileHierarchy.h" #include "library/processXml.h" class StatusHandler; -class DirectoryDescrBuffer; namespace FreeFileSync { class FilterProcess; + struct FolderPairCfg + { + FolderPairCfg(const Zstring& leftDir, + const Zstring& rightDir, + bool filterAct, + const wxString& include, + const wxString& exclude, + const SyncConfiguration& syncCfg) : + leftDirectory(leftDir), + rightDirectory(rightDir), + filterIsActive(filterAct), + includeFilter(include), + excludeFilter(exclude), + syncConfiguration(syncCfg) {} + + Zstring leftDirectory; + Zstring rightDirectory; + + bool filterIsActive; + wxString includeFilter; + wxString excludeFilter; + + SyncConfiguration syncConfiguration; + }; + std::vector<FolderPairCfg> extractCompareCfg(const MainConfiguration& mainCfg); + + //class handling comparison process class CompareProcess { public: CompareProcess(const bool traverseSymLinks, - const unsigned fileTimeTol, + const unsigned int fileTimeTol, const bool ignoreOneHourDiff, - xmlAccess::WarningMessages& warnings, - const FilterProcess* filter, //may be NULL + xmlAccess::OptionalDialogs& warnings, StatusHandler* handler); - ~CompareProcess(); - - void startCompareProcess(const std::vector<FolderPair>& directoryPairs, + void startCompareProcess(const std::vector<FolderPairCfg>& directoryPairs, const CompareVariant cmpVar, - const SyncConfiguration& config, FolderComparison& output); private: - void compareByTimeSize(const std::vector<FolderPair>& directoryPairsFormatted, FolderComparison& output); + void compareByTimeSize(const std::vector<FolderPairCfg>& directoryPairsFormatted, FolderComparison& output); - void compareByContent(const std::vector<FolderPair>& directoryPairsFormatted, FolderComparison& output); + void compareByContent(const std::vector<FolderPairCfg>& directoryPairsFormatted, FolderComparison& output); //create comparison result table and fill relation except for files existing on both sides - void performBaseComparison(const FolderPair& pair, FileComparison& output); + void performBaseComparison(const FolderPairCfg& pair, BaseDirMapping& output, std::vector<FileMapping*>& appendUndefined); //buffer accesses to the same directories; useful when multiple folder pairs are used - DirectoryDescrBuffer* descriptionBuffer; + class DirectoryBuffer; + boost::shared_ptr<DirectoryBuffer> directoryBuffer; //std::auto_ptr does not work with forward declarations! const unsigned int fileTimeTolerance; //max allowed file time deviation const bool ignoreOneHourDifference; - xmlAccess::WarningMessages& m_warnings; + xmlAccess::OptionalDialogs& m_warnings; - StatusHandler* statusUpdater; + StatusHandler* const statusUpdater; const Zstring txtComparingContentOfFiles; }; } diff --git a/fileHierarchy.cpp b/fileHierarchy.cpp new file mode 100644 index 00000000..2f4e39af --- /dev/null +++ b/fileHierarchy.cpp @@ -0,0 +1,105 @@ +#include "fileHierarchy.h" + +using namespace FreeFileSync; + + +struct LowerID +{ + bool operator()(const FileSystemObject& a, FileSystemObject::ObjectID b) const + { + return a.getId() < b; + } + + bool operator()(const FileSystemObject& a, const FileSystemObject& b) const //used by VC++ + { + return a.getId() < b.getId(); + } + + bool operator()(FileSystemObject::ObjectID a, const FileSystemObject& b) const + { + return a < b.getId(); + } +}; + + +const FileSystemObject* HierarchyObject::retrieveById(FileSystemObject::ObjectID id) const //returns NULL if object is not found +{ + //ATTENTION: HierarchyObject::retrieveById() can only work correctly if the following conditions are fulfilled: + //1. on each level, files are added first, then directories (=> file id < dir id) + //2. when a directory is added, all subdirectories must be added immediately (recursion) before the next dir on this level is added + //3. entries may be deleted but NEVER new ones inserted!!! + //=> this allows for a quasi-binary search by id! + + //See MergeSides::execute()! + + + //search within sub-files + SubFileMapping::const_iterator i = std::lower_bound(subFiles.begin(), subFiles.end(), id, LowerID()); //binary search! + if (i != subFiles.end()) + { //id <= i + if (LowerID()(id, *i)) + return NULL; // --i < id < i + else //id found + return &(*i); + } + else //search within sub-directories + { + SubDirMapping::const_iterator j = std::lower_bound(subDirs.begin(), subDirs.end(), id, LowerID()); //binary search! + if (j != subDirs.end()) //id <= j + { + if (LowerID()(id, *j)) // --j < id < j + { + if (j == subDirs.begin()) + return NULL; + else + return (--j)->retrieveById(id); + } + else //id found + return &(*j); + } + else //subdirs < id + { + if (j == subDirs.begin()) //empty vector + return NULL; + else // --j < id < j + return (--j)->retrieveById(id); + } + } +} + + +struct IsInvalid +{ + bool operator()(const FileSystemObject& fsObj) const + { + return fsObj.isEmpty(); + } +}; + + +void FileSystemObject::removeEmptyNonRec(HierarchyObject& hierObj) +{ + //remove invalid files + hierObj.subFiles.erase(std::remove_if(hierObj.subFiles.begin(), hierObj.subFiles.end(), IsInvalid()), + hierObj.subFiles.end()); + + //remove invalid directories + hierObj.subDirs.erase(std::remove_if(hierObj.subDirs.begin(), hierObj.subDirs.end(), IsInvalid()), + hierObj.subDirs.end()); +} + + +void removeEmptyRec(HierarchyObject& hierObj) +{ + FileSystemObject::removeEmptyNonRec(hierObj); + + //recurse into remaining directories + std::for_each(hierObj.subDirs.begin(), hierObj.subDirs.end(), removeEmptyRec); +} + + +void FileSystemObject::removeEmpty(BaseDirMapping& baseDir) +{ + removeEmptyRec(baseDir); +} + diff --git a/fileHierarchy.h b/fileHierarchy.h new file mode 100644 index 00000000..755bb97d --- /dev/null +++ b/fileHierarchy.h @@ -0,0 +1,794 @@ +#ifndef FILEHIERARCHY_H_INCLUDED +#define FILEHIERARCHY_H_INCLUDED + +#include "shared/zstring.h" +#include "shared/systemConstants.h" +#include <wx/longlong.h> +#include <set> +#include <vector> +#include "structures.h" + +class DirectoryBuffer; + + +namespace FreeFileSync +{ + struct FileDescriptor + { + FileDescriptor( const Zstring& shortNameIn, + const wxLongLong& lastWriteTimeRawIn, + const wxULongLong& fileSizeIn) : + shortName(shortNameIn), + lastWriteTimeRaw(lastWriteTimeRawIn), + fileSize(fileSizeIn) {} + + //fullname == baseDirectoryPf + parentRelNamePf + shortName + Zstring shortName; + wxLongLong lastWriteTimeRaw; //number of seconds since Jan. 1st 1970 UTC, same semantics like time_t (== signed long) + wxULongLong fileSize; + }; + + + struct DirDescriptor + { + DirDescriptor(const Zstring& shortNameIn) : + shortName(shortNameIn) {} + + //fullname == baseDirectoryPf + parentRelNamePf + shortName + Zstring shortName; + }; + + + class FileContainer; + class FileMapping; + class DirMapping; + class CompareProcess; + class HierarchyObject; + class BaseDirMapping; +//------------------------------------------------------------------ + /* + DirContainer FileContainer + + ERD: + DirContainer 1 -----> 0..n DirContainer + DirContainer 1 -----> 0..n FileContainer + */ + +//------------------------------------------------------------------ + class DirContainer + { + public: + void addSubFile(const FileDescriptor& fileData, const Zstring& parentRelativeNamePf); + DirContainer& addSubDir(const DirDescriptor& dirData, const Zstring& parentRelativeNamePf); + + //------------------------------------------------------------------ + template <class T> + struct CmpLess : public std::binary_function<T, T, bool> + { + bool operator()(const T& a, const T& b) const; + }; + + typedef std::set<DirContainer, CmpLess<DirContainer> > SubDirList; + typedef std::set<FileContainer, CmpLess<FileContainer> > SubFileList; + //------------------------------------------------------------------ + + const DirDescriptor& getData() const; + const Zstring& getParentRelNamePf() const; + + const SubDirList& getSubDirs() const; + const SubFileList& getSubFiles() const; + + private: + friend class CompareProcess; //only DirectoryBuffer is allowed to create (Base-)DirContainers + + DirContainer(const DirDescriptor& dirData, const Zstring& parentRelativeNamePf) : + data(dirData), + parentRelNamePf(parentRelativeNamePf) {} + + DirContainer() : data(Zstring()) {} //default constructor used by DirectoryBuffer only! + + const DirDescriptor data; + const Zstring parentRelNamePf; //buffer some redundant data: + + SubDirList subDirs; //contained directories + SubFileList subFiles; //contained files + }; + +//------------------------------------------------------------------ + class FileContainer + { + public: + const FileDescriptor& getData() const; + const Zstring& getParentRelNamePf() const; + + private: + friend class DirContainer; + + FileContainer(const FileDescriptor& fileData, const Zstring& parentRelativeNamePf) : + data(fileData), + parentRelNamePf(parentRelativeNamePf) {} + + const FileDescriptor data; + const Zstring parentRelNamePf; //buffer some redundant data: + }; + + +//------------------------------------------------------------------ + /* class hierarchy: + + FileSystemObject HierarchyObject + /|\ /|\ + ______|______ ______|______ + | | | | + FileMapping DirMapping BaseDirMapping + */ + +//------------------------------------------------------------------ + enum SelectedSide + { + LEFT_SIDE, + RIGHT_SIDE + }; + + struct RelNamesBuffered + { + RelNamesBuffered(const Zstring& baseDirPfLIn, //base sync dir postfixed + const Zstring& baseDirPfRIn, + const Zstring& parentRelNamePfIn) : //relative parent name postfixed + baseDirPfL(baseDirPfLIn), + baseDirPfR(baseDirPfRIn), + parentRelNamePf(parentRelNamePfIn) {} + + Zstring baseDirPfL; + Zstring baseDirPfR; + Zstring parentRelNamePf; + }; + +//------------------------------------------------------------------ + class FileSystemObject + { + public: + typedef unsigned int ObjectID; + template <SelectedSide side> bool isEmpty() const; + template <SelectedSide side> const Zstring& getShortName() const; + template <SelectedSide side> const Zstring getRelativeName() const; //get name relative to base sync dir without FILE_NAME_SEPARATOR prefix + template <SelectedSide side> const Zstring getParentRelativeName() const; //get name relative to base sync dir without FILE_NAME_SEPARATOR postfix + template <SelectedSide side> const Zstring getFullName() const; //getFullName() == getBaseDirPf() + getRelativeName() + template <SelectedSide side> const Zstring& getBaseDirPf() const; //base sync directory postfixed with FILE_NAME_SEPARATOR + ObjectID getId() const; //get unique id; ^= logical key + + void swap(); + + template <SelectedSide side> void copyTo(); //copy one side to the other (NOT recursive!!!) + template <SelectedSide side> void removeObject(); //removes file or directory (recursively!): used by manual deletion + bool isEmpty() const; //true, if both sides are empty + static void removeEmpty(BaseDirMapping& baseDir); //remove all invalid entries (where both sides are empty) recursively + static void removeEmptyNonRec(HierarchyObject& hierObj); //remove all invalid entries (where both sides are empty) non-recursively + + //sync settings: + SyncDirection syncDir; + bool selectedForSynchronization; + + virtual CompareFilesResult getCategory() const = 0; + virtual const wxString& getConflictDescription() const = 0; + + protected: + FileSystemObject(const RelNamesBuffered& relNameBuff) : + syncDir(SYNC_DIR_NONE), + selectedForSynchronization(true), + nameBuffer(relNameBuff), + uniqueId(getUniqueId()) {} + + ~FileSystemObject() {} //don't need polymorphic deletion + + private: + virtual void swapDescriptors() = 0; + + virtual bool isEmptyL() const = 0; + virtual bool isEmptyR() const = 0; + virtual void removeObjectL() = 0; + virtual void removeObjectR() = 0; + virtual void copyToL() = 0; + virtual void copyToR() = 0; + virtual const Zstring& getShortNameL() const = 0; + virtual const Zstring& getShortNameR() const = 0; + static ObjectID getUniqueId(); + + //buffer some redundant data: + RelNamesBuffered nameBuffer; //base sync dirs + relative parent name: this does NOT belong into FileDescriptor/DirDescriptor + + ObjectID uniqueId; + }; + +//------------------------------------------------------------------ + class HierarchyObject + { + public: + FileSystemObject* retrieveById(FileSystemObject::ObjectID id); //returns NULL if object is not found; logarithmic complexity + const FileSystemObject* retrieveById(FileSystemObject::ObjectID id) const; // + + DirMapping& addSubDir(const DirDescriptor& left, + CompareDirResult defaultCmpResult, + const DirDescriptor& right, + const RelNamesBuffered& relNameBuff); + FileMapping& addSubFile(const FileDescriptor& left, + CompareFilesResult defaultCmpResult, + const FileDescriptor& right, + const RelNamesBuffered& relNameBuff); + + typedef std::vector<FileMapping> SubFileMapping; + typedef std::vector<DirMapping> SubDirMapping; + + SubFileMapping subFiles; //contained file maps + SubDirMapping subDirs; //contained directory maps + + protected: + HierarchyObject() {} + ~HierarchyObject() {} //don't need polymorphic deletion + }; + +//------------------------------------------------------------------ + class DirMapping : public FileSystemObject, public HierarchyObject + { + public: + static const DirDescriptor& nullData(); + + virtual CompareFilesResult getCategory() const; + virtual const wxString& getConflictDescription() const; + + private: + friend class CompareProcess; //only CompareProcess shall be allowed to change cmpResult + friend class HierarchyObject; + virtual void swapDescriptors(); + virtual bool isEmptyL() const; + virtual bool isEmptyR() const; + virtual void removeObjectL(); + virtual void removeObjectR(); + virtual void copyToL(); + virtual void copyToR(); + virtual const Zstring& getShortNameL() const; + virtual const Zstring& getShortNameR() const; + //------------------------------------------------------------------ + + DirMapping(const DirDescriptor& left, CompareDirResult defaultCmpResult, const DirDescriptor& right, const RelNamesBuffered& relNameBuff) : + FileSystemObject(relNameBuff), + cmpResult(defaultCmpResult), + dataLeft(left), + dataRight(right) {} + + //categorization + CompareDirResult cmpResult; + + DirDescriptor dataLeft; + DirDescriptor dataRight; + }; + +//------------------------------------------------------------------ + class FileMapping : public FileSystemObject + { + public: + template <SelectedSide side> const wxLongLong& getLastWriteTime() const; + template <SelectedSide side> const wxULongLong& getFileSize() const; + + static const FileDescriptor& nullData(); + + virtual CompareFilesResult getCategory() const; + virtual const wxString& getConflictDescription() const; + + private: + friend class CompareProcess; //only CompareProcess shall be allowed to change cmpResult + friend class HierarchyObject; + + FileMapping(const FileDescriptor& left, CompareFilesResult defaultCmpResult, const FileDescriptor& right, const RelNamesBuffered& relNameBuff) : + FileSystemObject(relNameBuff), + cmpResult(defaultCmpResult), + dataLeft(left), + dataRight(right) {} + + virtual void swapDescriptors(); + virtual bool isEmptyL() const; + virtual bool isEmptyR() const; + virtual void removeObjectL(); + virtual void removeObjectR(); + virtual void copyToL(); + virtual void copyToR(); + virtual const Zstring& getShortNameL() const; + virtual const Zstring& getShortNameR() const; + //------------------------------------------------------------------ + + //categorization + CompareFilesResult cmpResult; + wxString conflictDescription; //only filled if cmpResult == FILE_CONFLICT + + FileDescriptor dataLeft; + FileDescriptor dataRight; + }; + +//------------------------------------------------------------------ + class BaseDirMapping : public HierarchyObject //synchronization base directory + { + public: + BaseDirMapping(const Zstring& dirPostfixedLeft, const Zstring& dirPostfixedRight) : + baseDirLeft(dirPostfixedLeft), + baseDirRight(dirPostfixedRight) {} + + template <SelectedSide side> const Zstring& getBaseDir() const; + + private: + Zstring baseDirLeft; //directory name ending with FILE_NAME_SEPARATOR + Zstring baseDirRight; //directory name ending with FILE_NAME_SEPARATOR + }; + + + typedef std::vector<BaseDirMapping> FolderComparison; + +//------------------------------------------------------------------ + + + + + + + + + + + + + + + + + + + +//---------------Inline Implementation--------------------------------------------------- + inline + FileSystemObject* HierarchyObject::retrieveById(FileSystemObject::ObjectID id) //returns NULL if object is not found + { + //code re-use of const method: see Meyers Effective C++ + return const_cast<FileSystemObject*>(static_cast<const HierarchyObject&>(*this).retrieveById(id)); + } + + + inline + FileSystemObject::ObjectID FileSystemObject::getId() const + { + return uniqueId; + } + + + inline + FileSystemObject::ObjectID FileSystemObject::getUniqueId() + { + static unsigned int id = 0; + return ++id; + } + + + template <class T> + inline + bool DirContainer::CmpLess<T>::operator()(const T& a, const T& b) const + { +// //quick check based on string length +// const size_t aLength = a.data.shortName.length(); +// const size_t bLength = b.data.shortName.length(); +// if (aLength != bLength) +// return aLength < bLength; + +#ifdef FFS_WIN //Windows does NOT distinguish between upper/lower-case + return a.data.shortName.CmpNoCase(b.data.shortName) < 0; +#elif defined FFS_LINUX //Linux DOES distinguish between upper/lower-case + return a.data.shortName.Cmp(b.data.shortName) < 0; +#endif + } + + + inline + const DirDescriptor& DirContainer::getData() const + { + return data; + } + + + inline + const Zstring& DirContainer::getParentRelNamePf() const + { + return parentRelNamePf; + } + + + inline + const DirContainer::SubDirList& DirContainer::getSubDirs() const + { + return subDirs; + } + + + inline + const DirContainer::SubFileList& DirContainer::getSubFiles() const + { + return subFiles; + } + + + inline + const FileDescriptor& FileContainer::getData() const + { + return data; + } + + + inline + const Zstring& FileContainer::getParentRelNamePf() const + { + return parentRelNamePf; + } + + + inline + DirContainer& DirContainer::addSubDir(const DirDescriptor& dirData, const Zstring& parentRelativeNamePf) + { + //const cast: another crime in the name of unproven performance optimization ;) however key part cannot be changed, so this should be safe + return const_cast<DirContainer&>(*subDirs.insert(DirContainer(dirData, parentRelativeNamePf)).first); + } + + + inline + void DirContainer::addSubFile(const FileDescriptor& fileData, const Zstring& parentRelativeNamePf) + { + subFiles.insert(FileContainer(fileData, parentRelativeNamePf)); + } + + + inline + CompareFilesResult FileMapping::getCategory() const + { + return cmpResult; + } + + + inline + const wxString& FileMapping::getConflictDescription() const + { + return conflictDescription; + } + + + inline + CompareFilesResult DirMapping::getCategory() const + { + return convertToFilesResult(cmpResult); + } + + + inline + const wxString& DirMapping::getConflictDescription() const + { + static wxString empty; + return empty; + } + + + template <SelectedSide side> + inline + bool FileSystemObject::isEmpty() const + { + return side == LEFT_SIDE ? isEmptyL() : isEmptyR(); + } + + + inline + bool FileSystemObject::isEmpty() const + { + return isEmptyL() && isEmptyR(); + } + + + template <SelectedSide side> + inline + const Zstring& FileSystemObject::getShortName() const + { + return side == LEFT_SIDE ? getShortNameL() : getShortNameR(); + } + + + template <SelectedSide side> + inline + const Zstring FileSystemObject::getRelativeName() const + { + return isEmpty<side>() ? Zstring() : nameBuffer.parentRelNamePf + getShortName<side>(); + } + + + template <SelectedSide side> + inline + const Zstring FileSystemObject::getParentRelativeName() const + { + return nameBuffer.parentRelNamePf.BeforeLast(globalFunctions::FILE_NAME_SEPARATOR); //returns empty string if char not found + } + + + template <SelectedSide side> + inline + const Zstring FileSystemObject::getFullName() const + { + return isEmpty<side>() ? Zstring() : getBaseDirPf<side>() + nameBuffer.parentRelNamePf + getShortName<side>(); + } + + + template <SelectedSide side> + inline + const Zstring& FileSystemObject::getBaseDirPf() const + { + return side == LEFT_SIDE ? nameBuffer.baseDirPfL : nameBuffer.baseDirPfR; + } + + + template <SelectedSide side> + inline + void FileSystemObject::removeObject() + { + if (side == LEFT_SIDE) + removeObjectL(); + else + removeObjectR(); + } + + + template <SelectedSide side> + inline + void FileSystemObject::copyTo() + { + syncDir = SYNC_DIR_NONE; + + if (side == LEFT_SIDE) + copyToL(); + else + copyToR(); + } + + + inline + void FileSystemObject::swap() + { + //swap file descriptors + swapDescriptors(); + } + + + inline + DirMapping& HierarchyObject::addSubDir(const DirDescriptor& left, + CompareDirResult defaultCmpResult, + const DirDescriptor& right, + const RelNamesBuffered& relNameBuff) + { + subDirs.push_back(DirMapping(left, defaultCmpResult, right, relNameBuff)); + return subDirs.back(); + } + + + inline + FileMapping& HierarchyObject::addSubFile(const FileDescriptor& left, + CompareFilesResult defaultCmpResult, + const FileDescriptor& right, + const RelNamesBuffered& relNameBuff) + { + subFiles.push_back(FileMapping(left, defaultCmpResult, right, relNameBuff)); + return subFiles.back(); + } + + + inline + void DirMapping::swapDescriptors() + { + //swap compare result + switch (cmpResult) + { + case DIR_LEFT_SIDE_ONLY: + cmpResult = DIR_RIGHT_SIDE_ONLY; + break; + case DIR_RIGHT_SIDE_ONLY: + cmpResult = DIR_LEFT_SIDE_ONLY; + break; + case DIR_EQUAL: + break; + } + + std::swap(dataLeft, dataRight); + } + + + inline + const DirDescriptor& DirMapping::nullData() + { + static DirDescriptor output( static_cast<Zstring>(Zstring())); + return output; + } + + + inline + void DirMapping::removeObjectL() + { + cmpResult = DIR_RIGHT_SIDE_ONLY; + dataLeft = nullData(); + std::for_each(subFiles.begin(), subFiles.end(), std::mem_fun_ref(&FileSystemObject::removeObject<LEFT_SIDE>)); + std::for_each(subDirs.begin(), subDirs.end(), std::mem_fun_ref(&FileSystemObject::removeObject<LEFT_SIDE>)); + } + + + inline + void DirMapping::removeObjectR() + { + cmpResult = DIR_LEFT_SIDE_ONLY; + dataRight = nullData(); + std::for_each(subFiles.begin(), subFiles.end(), std::mem_fun_ref(&FileSystemObject::removeObject<RIGHT_SIDE>)); + std::for_each(subDirs.begin(), subDirs.end(), std::mem_fun_ref(&FileSystemObject::removeObject<RIGHT_SIDE>)); + } + + + inline + void DirMapping::copyToL() + { + cmpResult = DIR_EQUAL; + dataLeft = dataRight; + } + + + inline + void DirMapping::copyToR() + { + cmpResult = DIR_EQUAL; + dataRight = dataLeft; + } + + + inline + const Zstring& DirMapping::getShortNameL() const + { + return dataLeft.shortName; + } + + + inline + const Zstring& DirMapping::getShortNameR() const + { + return dataRight.shortName; + } + + + inline + bool DirMapping::isEmptyL() const + { + return dataLeft.shortName.empty(); + } + + + inline + bool DirMapping::isEmptyR() const + { + return dataRight.shortName.empty(); + } + + + template <SelectedSide side> + inline + const Zstring& BaseDirMapping::getBaseDir() const + { + return side == LEFT_SIDE ? baseDirLeft : baseDirRight; + } + + + inline + void FileMapping::swapDescriptors() + { + //swap compare result + switch (cmpResult) + { + case FILE_LEFT_SIDE_ONLY: + cmpResult = FILE_RIGHT_SIDE_ONLY; + break; + case FILE_RIGHT_SIDE_ONLY: + cmpResult = FILE_LEFT_SIDE_ONLY; + break; + case FILE_LEFT_NEWER: + cmpResult = FILE_RIGHT_NEWER; + break; + case FILE_RIGHT_NEWER: + cmpResult = FILE_LEFT_NEWER; + break; + case FILE_DIFFERENT: + case FILE_EQUAL: + case FILE_CONFLICT: + break; + } + + std::swap(dataLeft, dataRight); + } + + + inline + void FileMapping::removeObjectL() + { + cmpResult = FILE_RIGHT_SIDE_ONLY; + dataLeft = nullData(); + } + + + inline + void FileMapping::removeObjectR() + { + cmpResult = FILE_LEFT_SIDE_ONLY; + dataRight = nullData(); + } + + + inline + void FileMapping::copyToL() + { + cmpResult = FILE_EQUAL; + dataLeft = dataRight; + } + + + inline + void FileMapping::copyToR() + { + cmpResult = FILE_EQUAL; + dataRight = dataLeft; + } + + + inline + const FileDescriptor& FileMapping::nullData() + { + static FileDescriptor output(Zstring(), 0, 0); + return output; + } + + + inline + const Zstring& FileMapping::getShortNameL() const + { + return dataLeft.shortName; + } + + + inline + const Zstring& FileMapping::getShortNameR() const + { + return dataRight.shortName; + } + + + inline + bool FileMapping::isEmptyL() const + { + return dataLeft.shortName.empty(); + } + + + inline + bool FileMapping::isEmptyR() const + { + return dataRight.shortName.empty(); + } + + + template <SelectedSide side> + inline + const wxLongLong& FileMapping::getLastWriteTime() const + { + return side == LEFT_SIDE ? dataLeft.lastWriteTimeRaw : dataRight.lastWriteTimeRaw; + } + + + template <SelectedSide side> + inline + const wxULongLong& FileMapping::getFileSize() const + { + return side == LEFT_SIDE ? dataLeft.fileSize : dataRight.fileSize; + } +} + +#endif // FILEHIERARCHY_H_INCLUDED diff --git a/library/CustomGrid.cpp b/library/CustomGrid.cpp index 4cb82961..e2384526 100644 --- a/library/CustomGrid.cpp +++ b/library/CustomGrid.cpp @@ -1,5 +1,5 @@ #include "customGrid.h" -#include "../shared/globalFunctions.h" +#include "../shared/systemConstants.h" #include "resources.h" #include <wx/dc.h> #include "../algorithm.h" @@ -73,7 +73,7 @@ public: virtual int GetNumberRows() { if (gridDataView) - return std::max(gridDataView->elementsOnView(), MIN_ROW_COUNT); + return std::max(gridDataView->rowsOnView(), MIN_ROW_COUNT); else return 0; //grid is initialized with zero number of rows } @@ -180,12 +180,11 @@ public: } - const FileCompareLine* getRawData(const unsigned int row) const + const FileSystemObject* getRawData(const unsigned int row) const { - if (gridDataView && row < gridDataView->elementsOnView()) - { - return &(*gridDataView)[row]; - } + if (gridDataView) + return gridDataView->getObject(row); //returns NULL if request is not valid or not data found + return NULL; } @@ -212,6 +211,8 @@ private: class CustomGridTableRim : public CustomGridTable { public: + virtual ~CustomGridTableRim() {} + virtual int GetNumberCols() { return columnPositions.size(); @@ -240,53 +241,55 @@ public: virtual Zstring getFileName(const unsigned int row) const = 0; -private: - std::vector<xmlAccess::ColumnTypes> columnPositions; -}; - - -class CustomGridTableLeft : public CustomGridTableRim -{ -public: - virtual wxString GetValue(int row, int col) +protected: + template <SelectedSide side> + wxString GetValueSub(int row, int col) { - const FileCompareLine* gridLine = getRawData(row); - if (gridLine) + const FileSystemObject* fsObj = getRawData(row); + if (fsObj) { - if (gridLine->fileDescrLeft.objType == FileDescrLine::TYPE_DIRECTORY) + if (!fsObj->isEmpty<side>()) { - switch (getTypeAtPos(col)) + const DirMapping* dirObj = dynamic_cast<const DirMapping*> (fsObj); + if (dirObj != NULL) { - case xmlAccess::FULL_PATH: - return wxString(gridLine->fileDescrLeft.fullName.c_str()); - case xmlAccess::FILENAME: - return wxEmptyString; - case xmlAccess::REL_PATH: - return gridLine->fileDescrLeft.relativeName.c_str(); - case xmlAccess::DIRECTORY: - return gridDataView->getFolderPair(row).leftDirectory.c_str(); - case xmlAccess::SIZE: //file size - return _("<Directory>"); - case xmlAccess::DATE: //date - return wxEmptyString; + switch (getTypeAtPos(col)) + { + case xmlAccess::FULL_PATH: + return dirObj->getFullName<side>().c_str(); + case xmlAccess::FILENAME: + return wxEmptyString; + case xmlAccess::REL_PATH: + return dirObj->getRelativeName<side>().c_str(); + case xmlAccess::DIRECTORY: + return dirObj->getBaseDirPf<side>().c_str(); + case xmlAccess::SIZE: //file size + return _("<Directory>"); + case xmlAccess::DATE: //date + return wxEmptyString; + } } - } - else if (gridLine->fileDescrLeft.objType == FileDescrLine::TYPE_FILE) - { - switch (getTypeAtPos(col)) + else { - case xmlAccess::FULL_PATH: - return wxString(gridLine->fileDescrLeft.fullName.c_str()).BeforeLast(globalFunctions::FILE_NAME_SEPARATOR); - case xmlAccess::FILENAME: //filename - return wxString(gridLine->fileDescrLeft.relativeName.c_str()).AfterLast(globalFunctions::FILE_NAME_SEPARATOR); - case xmlAccess::REL_PATH: //relative path - return wxString(gridLine->fileDescrLeft.relativeName.c_str()).BeforeLast(globalFunctions::FILE_NAME_SEPARATOR); - case xmlAccess::DIRECTORY: - return gridDataView->getFolderPair(row).leftDirectory.c_str(); - case xmlAccess::SIZE: //file size - return FreeFileSync::includeNumberSeparator(gridLine->fileDescrLeft.fileSize.ToString()); - case xmlAccess::DATE: //date - return FreeFileSync::utcTimeToLocalString(gridLine->fileDescrLeft.lastWriteTimeRaw, gridLine->fileDescrLeft.fullName); + const FileMapping* fileObj = dynamic_cast<const FileMapping*>(fsObj); + if (fileObj != NULL) + { + switch (getTypeAtPos(col)) + { + case xmlAccess::FULL_PATH: + return fileObj->getFullName<side>().BeforeLast(globalFunctions::FILE_NAME_SEPARATOR).c_str(); + case xmlAccess::FILENAME: //filename + return fileObj->getShortName<side>().c_str(); + case xmlAccess::REL_PATH: //relative path + return fileObj->getParentRelativeName<side>().c_str(); + case xmlAccess::DIRECTORY: + return fileObj->getBaseDirPf<side>().c_str(); + case xmlAccess::SIZE: //file size + return FreeFileSync::includeNumberSeparator(fileObj->getFileSize<side>().ToString()); + case xmlAccess::DATE: //date + return FreeFileSync::utcTimeToLocalString(fileObj->getLastWriteTime<side>(), fileObj->getFullName<side>()); + } + } } } } @@ -295,112 +298,63 @@ public: } - virtual Zstring getFileName(const unsigned int row) const - { - const FileCompareLine* gridLine = getRawData(row); - if (gridLine) - return Zstring(gridLine->fileDescrLeft.fullName); - else - return Zstring(); - } - - private: virtual const wxColour& getRowColor(int row) //rows that are filtered out are shown in different color { - const FileCompareLine* gridLine = getRawData(row); - if (gridLine) + const FileSystemObject* fsObj = getRawData(row); + if (fsObj) { //mark filtered rows - if (!gridLine->selectedForSynchronization) + if (!fsObj->selectedForSynchronization) return COLOR_BLUE; //mark directories - else if (gridLine->fileDescrLeft.objType == FileDescrLine::TYPE_DIRECTORY) + else if (dynamic_cast<const DirMapping*>(fsObj) != NULL) return COLOR_GREY; else return *wxWHITE; } return *wxWHITE; } + + std::vector<xmlAccess::ColumnTypes> columnPositions; }; -class CustomGridTableRight : public CustomGridTableRim +class CustomGridTableLeft : public CustomGridTableRim { public: + virtual wxString GetValue(int row, int col) { - const FileCompareLine* gridLine = getRawData(row); - if (gridLine) - { - if (gridLine->fileDescrRight.objType == FileDescrLine::TYPE_DIRECTORY) - { - switch (getTypeAtPos(col)) - { - case xmlAccess::FULL_PATH: - return wxString(gridLine->fileDescrRight.fullName.c_str()); - case xmlAccess::FILENAME: //filename - return wxEmptyString; - case xmlAccess::REL_PATH: //relative path - return gridLine->fileDescrRight.relativeName.c_str(); - case xmlAccess::DIRECTORY: - return gridDataView->getFolderPair(row).rightDirectory.c_str(); - case xmlAccess::SIZE: //file size - return _("<Directory>"); - case xmlAccess::DATE: //date - return wxEmptyString; - } - } - else if (gridLine->fileDescrRight.objType == FileDescrLine::TYPE_FILE) - { - switch (getTypeAtPos(col)) - { - case xmlAccess::FULL_PATH: - return wxString(gridLine->fileDescrRight.fullName.c_str()).BeforeLast(globalFunctions::FILE_NAME_SEPARATOR); - case xmlAccess::FILENAME: //filename - return wxString(gridLine->fileDescrRight.relativeName.c_str()).AfterLast(globalFunctions::FILE_NAME_SEPARATOR); - case xmlAccess::REL_PATH: //relative path - return wxString(gridLine->fileDescrRight.relativeName.c_str()).BeforeLast(globalFunctions::FILE_NAME_SEPARATOR); - case xmlAccess::DIRECTORY: - return gridDataView->getFolderPair(row).rightDirectory.c_str(); - case xmlAccess::SIZE: //file size - return FreeFileSync::includeNumberSeparator(gridLine->fileDescrRight.fileSize.ToString()); - case xmlAccess::DATE: //date - return FreeFileSync::utcTimeToLocalString(gridLine->fileDescrRight.lastWriteTimeRaw, gridLine->fileDescrRight.fullName); - } - } - } - //if data is not found: - return wxEmptyString; + return CustomGridTableRim::GetValueSub<LEFT_SIDE>(row, col); } - virtual Zstring getFileName(const unsigned int row) const { - const FileCompareLine* gridLine = getRawData(row); - if (gridLine) - return Zstring(gridLine->fileDescrRight.fullName); + const FileSystemObject* fsObj = getRawData(row); + if (fsObj && !fsObj->isEmpty<LEFT_SIDE>()) + return fsObj->getFullName<LEFT_SIDE>(); else return Zstring(); } +}; -private: - virtual const wxColour& getRowColor(int row) //rows that are filtered out are shown in different color +class CustomGridTableRight : public CustomGridTableRim +{ +public: + virtual wxString GetValue(int row, int col) { - const FileCompareLine* gridLine = getRawData(row); - if (gridLine) - { - //mark filtered rows - if (!gridLine->selectedForSynchronization) - return COLOR_BLUE; - //mark directories - else if (gridLine->fileDescrRight.objType == FileDescrLine::TYPE_DIRECTORY) - return COLOR_GREY; - else - return *wxWHITE; - } - return *wxWHITE; + return CustomGridTableRim::GetValueSub<RIGHT_SIDE>(row, col); + } + + virtual Zstring getFileName(const unsigned int row) const + { + const FileSystemObject* fsObj = getRawData(row); + if (fsObj && !fsObj->isEmpty<RIGHT_SIDE>()) + return fsObj->getFullName<RIGHT_SIDE>(); + else + return Zstring(); } }; @@ -423,8 +377,16 @@ public: return wxEmptyString; } - virtual wxString GetValue(int row, int col) + virtual wxString GetValue(int row, int col) //method used for exporting .csv file only! { + const FileSystemObject* fsObj = getRawData(row); + if (fsObj) + { + if (syncPreviewActive) //synchronization preview + return getSymbol(getSyncOperation(*fsObj)); + else + return getSymbol(fsObj->getCategory()); + } return wxEmptyString; } @@ -441,30 +403,31 @@ public: private: virtual const wxColour& getRowColor(int row) //rows that are filtered out are shown in different color { - const FileCompareLine* gridLine = getRawData(row); - if (gridLine) + const FileSystemObject* fsObj = getRawData(row); + if (fsObj) { //mark filtered rows - if (!gridLine->selectedForSynchronization) + if (!fsObj->selectedForSynchronization) return COLOR_BLUE; if (syncPreviewActive) //synchronization preview { - switch (gridLine->syncDir) + switch (fsObj->syncDir) { case SYNC_DIR_LEFT: return COLOR_SYNC_BLUE; case SYNC_DIR_RIGHT: return COLOR_SYNC_GREEN; case SYNC_DIR_NONE: - return *wxWHITE; - case SYNC_UNRESOLVED_CONFLICT: - return COLOR_YELLOW; + if (fsObj->getCategory() == FILE_CONFLICT) + return COLOR_YELLOW; + else + return *wxWHITE; } } else //comparison results view { - switch (gridLine->cmpResult) + switch (fsObj->getCategory()) { case FILE_LEFT_SIDE_ONLY: case FILE_RIGHT_SIDE_ONLY: @@ -539,7 +502,8 @@ void CustomGrid::initSettings(CustomGridLeft* gridLeft, Connect(wxEVT_SCROLLWIN_THUMBRELEASE, wxEventHandler(CustomGrid::onGridAccess), NULL, this); Connect(wxEVT_GRID_LABEL_LEFT_CLICK, wxEventHandler(CustomGrid::onGridAccess), NULL, this); GetGridWindow()->Connect(wxEVT_LEFT_DOWN, wxEventHandler(CustomGrid::onGridAccess), NULL, this); - GetGridWindow()->Connect(wxEVT_RIGHT_DOWN, wxEventHandler(CustomGrid::onGridAccess), NULL, this); + GetGridWindow()->Connect(wxEVT_RIGHT_DOWN, wxEventHandler(CustomGrid::onGridAccess), NULL, this); + GetGridWindow()->Connect(wxEVT_ENTER_WINDOW, wxEventHandler(CustomGrid::adjustGridHeights), NULL, this); } @@ -573,7 +537,7 @@ inline void moveCursorWhileSelecting(const int anchor, const int oldPos, const int newPos, wxGrid* grid) { //note: all positions are valid in this context! - grid->SetGridCursor(newPos, grid->GetGridCursorCol()); + grid->SetGridCursor( newPos, grid->GetGridCursorCol()); grid->MakeCellVisible(newPos, grid->GetGridCursorCol()); if (oldPos < newPos) @@ -595,23 +559,24 @@ void moveCursorWhileSelecting(const int anchor, const int oldPos, const int newP } -inline -void additionalGridCommands(wxEvent& event, wxGrid* grid) +void execGridCommands(wxEvent& event, wxGrid* grid) { static int anchorRow = 0; - assert(grid->GetNumberRows() != 0); + if ( grid->GetNumberRows() == 0 || + grid->GetNumberCols() == 0) + return; - try + const wxKeyEvent* keyEvent = dynamic_cast<const wxKeyEvent*> (&event); + if (keyEvent) { - const wxKeyEvent& keyEvent = dynamic_cast<const wxKeyEvent&> (event); + //ensure cursorOldPos is always a valid row! + const int cursorOldPos = std::max(std::min(grid->GetGridCursorRow(), grid->GetNumberRows() - 1), 0); + const int cursorOldColumn = std::max(std::min(grid->GetGridCursorCol(), grid->GetNumberCols() - 1), 0); - if (keyEvent.ShiftDown()) + if (keyEvent->ShiftDown()) { - //ensure cursorOldPos is always a valid row! - const int cursorOldPos = std::max(std::min(grid->GetGridCursorRow(), grid->GetNumberRows() - 1), 0); - //support for shift + PageUp and shift + PageDown - switch (keyEvent.GetKeyCode()) + switch (keyEvent->GetKeyCode()) { case WXK_UP: //move grid cursor also { @@ -627,6 +592,23 @@ void additionalGridCommands(wxEvent& event, wxGrid* grid) } return; //no event.Skip() + case WXK_LEFT: //move grid cursor also + { + const int cursorColumn = std::max(cursorOldColumn - 1, 0); + grid->SetGridCursor(cursorOldPos, cursorColumn); + grid->MakeCellVisible(cursorOldPos, cursorColumn); + } + return; //no event.Skip() + + case WXK_RIGHT: //move grid cursor also + { + const int cursorColumn = std::min(cursorOldColumn + 1, grid->GetNumberCols() - 1); + grid->SetGridCursor(cursorOldPos, cursorColumn); + grid->MakeCellVisible(cursorOldPos, cursorColumn); + } + return; //no event.Skip() + + case WXK_PAGEUP: case WXK_NUMPAD_PAGEUP: { @@ -665,8 +647,59 @@ void additionalGridCommands(wxEvent& event, wxGrid* grid) } else //button without shift is pressed { - switch (keyEvent.GetKeyCode()) + switch (keyEvent->GetKeyCode()) + { + case WXK_UP: //move grid cursor also { + const int cursorNewPos = std::max(cursorOldPos - 1, 0); + grid->SetGridCursor(cursorNewPos, grid->GetGridCursorCol()); + grid->MakeCellVisible(cursorNewPos, grid->GetGridCursorCol()); + } + return; //no event.Skip() + case WXK_DOWN: //move grid cursor also + { + const int cursorNewPos = std::min(cursorOldPos + 1, grid->GetNumberRows() - 1); + grid->SetGridCursor(cursorNewPos, grid->GetGridCursorCol()); + grid->MakeCellVisible(cursorNewPos, grid->GetGridCursorCol()); + } + return; //no event.Skip() + + case WXK_LEFT: //move grid cursor also + { + const int cursorColumn = std::max(cursorOldColumn - 1, 0); + grid->SetGridCursor(cursorOldPos, cursorColumn); + grid->MakeCellVisible(cursorOldPos, cursorColumn); + } + return; //no event.Skip() + case WXK_RIGHT: //move grid cursor also + { + const int cursorColumn = std::min(cursorOldColumn + 1, grid->GetNumberCols() - 1); + grid->SetGridCursor(cursorOldPos, cursorColumn); + grid->MakeCellVisible(cursorOldPos, cursorColumn); + } + return; //no event.Skip() + + + case WXK_PAGEUP: + case WXK_NUMPAD_PAGEUP: + { + const int rowsPerPage = grid->GetGridWindow()->GetSize().GetHeight() / grid->GetDefaultRowSize(); + const int cursorNewPos = std::max(cursorOldPos - rowsPerPage, 0); + grid->SetGridCursor(cursorNewPos, grid->GetGridCursorCol()); + grid->MakeCellVisible(cursorNewPos, grid->GetGridCursorCol()); + } + return; //no event.Skip() + + case WXK_PAGEDOWN: + case WXK_NUMPAD_PAGEDOWN: + { + const int rowsPerPage = grid->GetGridWindow()->GetSize().GetHeight() / grid->GetDefaultRowSize(); + const int cursorNewPos = std::min(cursorOldPos + rowsPerPage, grid->GetNumberRows() - 1); + grid->SetGridCursor(cursorNewPos, grid->GetGridCursorCol()); + grid->MakeCellVisible(cursorNewPos, grid->GetGridCursorCol()); + } + return; //no event.Skip() + case WXK_HOME: case WXK_NUMPAD_HOME: grid->SetGridCursor(0, grid->GetGridCursorCol()); @@ -681,62 +714,58 @@ void additionalGridCommands(wxEvent& event, wxGrid* grid) } } } - catch (std::bad_cast&) {} anchorRow = grid->GetGridCursorRow(); - event.Skip(); + event.Skip(); //let event delegate! } inline bool gridsShouldBeCleared(const wxEvent& event) { - try + const wxMouseEvent* mouseEvent = dynamic_cast<const wxMouseEvent*>(&event); + if (mouseEvent) { - const wxMouseEvent& mouseEvent = dynamic_cast<const wxMouseEvent&> (event); - - if (mouseEvent.ControlDown() || mouseEvent.ShiftDown()) + if (mouseEvent->ControlDown() || mouseEvent->ShiftDown()) return false; - if (mouseEvent.ButtonDown(wxMOUSE_BTN_LEFT)) + if (mouseEvent->ButtonDown(wxMOUSE_BTN_LEFT)) return true; return false; } - catch (std::bad_cast&) {} - - try + else { - const wxKeyEvent& keyEvent = dynamic_cast<const wxKeyEvent&> (event); + const wxKeyEvent* keyEvent = dynamic_cast<const wxKeyEvent*>(&event); + if (keyEvent) + { + if (keyEvent->ControlDown() || keyEvent->ShiftDown()) + return false; - if (keyEvent.ControlDown() || keyEvent.ShiftDown()) - return false; + switch (keyEvent->GetKeyCode()) + { + case WXK_TAB: + case WXK_RETURN: + case WXK_ESCAPE: + case WXK_NUMPAD_ENTER: + case WXK_LEFT: + case WXK_UP: + case WXK_RIGHT: + case WXK_DOWN: + case WXK_PAGEUP: + case WXK_PAGEDOWN: + case WXK_NUMPAD_PAGEUP: + case WXK_NUMPAD_PAGEDOWN: + case WXK_HOME: + case WXK_END: + case WXK_NUMPAD_HOME: + case WXK_NUMPAD_END: + return true; + } - switch (keyEvent.GetKeyCode()) - { - case WXK_SPACE: - case WXK_TAB: - case WXK_RETURN: - case WXK_ESCAPE: - case WXK_NUMPAD_ENTER: - case WXK_LEFT: - case WXK_UP: - case WXK_RIGHT: - case WXK_DOWN: - case WXK_PAGEUP: - case WXK_PAGEDOWN: - case WXK_NUMPAD_PAGEUP: - case WXK_NUMPAD_PAGEDOWN: - case WXK_HOME: - case WXK_END: - case WXK_NUMPAD_HOME: - case WXK_NUMPAD_END: - return true; + return false; } - - return false; } - catch (std::bad_cast&) {} return false; } @@ -766,8 +795,8 @@ void CustomGrid::onGridAccess(wxEvent& event) m_gridLeft->GetGridRowLabelWindow()->Update(); m_gridRight->GetGridRowLabelWindow()->Update(); - //support for additional short-cuts - additionalGridCommands(event, this); //event.Skip is handled here! + //support for custom short-cuts (overwriting wxWidgets functionality!) + execGridCommands(event, this); //event.Skip is handled here! } @@ -831,9 +860,9 @@ void CustomGrid::DrawColLabel(wxDC& dc, int col) } -std::set<int> CustomGrid::getAllSelectedRows() const +std::set<unsigned int> CustomGrid::getAllSelectedRows() const { - std::set<int> output; + std::set<unsigned int> output; const wxArrayInt selectedRows = this->GetSelectedRows(); if (!selectedRows.IsEmpty()) @@ -1526,8 +1555,8 @@ void CustomGridMiddle::OnLeaveWindow(wxMouseEvent& event) void CustomGridMiddle::showToolTip(int rowNumber, wxPoint pos) { - const FileCompareLine* const rowData = gridDataTable->getRawData(rowNumber); - if (rowData == NULL) //if invalid row... + const FileSystemObject* const fsObj = gridDataTable->getRawData(rowNumber); + if (fsObj == NULL) //if invalid row... { toolTip->hide(); return; @@ -1535,58 +1564,60 @@ void CustomGridMiddle::showToolTip(int rowNumber, wxPoint pos) if (gridDataTable->syncPreviewIsActive()) //synchronization preview { - switch (getSyncOperation(*rowData)) + const SyncOperation syncOp = getSyncOperation(*fsObj); + switch (syncOp) { case SO_CREATE_NEW_LEFT: - toolTip->show(_("Copy from right to left"), pos, GlobalResources::getInstance().bitmapSyncCreateLeftAct); + toolTip->show(getDescription(syncOp), pos, GlobalResources::getInstance().bitmapSyncCreateLeftAct); break; case SO_CREATE_NEW_RIGHT: - toolTip->show(_("Copy from left to right"), pos, GlobalResources::getInstance().bitmapSyncCreateRightAct); + toolTip->show(getDescription(syncOp), pos, GlobalResources::getInstance().bitmapSyncCreateRightAct); break; case SO_DELETE_LEFT: - toolTip->show(_("Delete files/folders existing on left side only"), pos, GlobalResources::getInstance().bitmapSyncDeleteLeftAct); + toolTip->show(getDescription(syncOp), pos, GlobalResources::getInstance().bitmapSyncDeleteLeftAct); break; case SO_DELETE_RIGHT: - toolTip->show(_("Delete files/folders existing on right side only"), pos, GlobalResources::getInstance().bitmapSyncDeleteRightAct); + toolTip->show(getDescription(syncOp), pos, GlobalResources::getInstance().bitmapSyncDeleteRightAct); break; case SO_OVERWRITE_LEFT: - toolTip->show(_("Copy from right to left overwriting"), pos, GlobalResources::getInstance().bitmapSyncDirLeftAct); + toolTip->show(getDescription(syncOp), pos, GlobalResources::getInstance().bitmapSyncDirLeftAct); break; case SO_OVERWRITE_RIGHT: - toolTip->show(_("Copy from left to right overwriting"), pos, GlobalResources::getInstance().bitmapSyncDirRightAct); + toolTip->show(getDescription(syncOp), pos, GlobalResources::getInstance().bitmapSyncDirRightAct); break; case SO_DO_NOTHING: - toolTip->show(_("Do nothing"), pos, GlobalResources::getInstance().bitmapSyncDirNoneAct); + toolTip->show(getDescription(syncOp), pos, GlobalResources::getInstance().bitmapSyncDirNoneAct); break; case SO_UNRESOLVED_CONFLICT: - toolTip->show(rowData->conflictDescription.get(), pos, GlobalResources::getInstance().bitmapConflictAct); + toolTip->show(fsObj->getConflictDescription(), pos, GlobalResources::getInstance().bitmapConflictAct); break; }; } else { - switch (rowData->cmpResult) + const CompareFilesResult cmpRes = fsObj->getCategory(); + switch (cmpRes) { case FILE_LEFT_SIDE_ONLY: - toolTip->show(_("Files/folders that exist on left side only"), pos, GlobalResources::getInstance().bitmapLeftOnlyAct); + toolTip->show(getDescription(cmpRes), pos, GlobalResources::getInstance().bitmapLeftOnlyAct); break; case FILE_RIGHT_SIDE_ONLY: - toolTip->show(_("Files/folders that exist on right side only"), pos, GlobalResources::getInstance().bitmapRightOnlyAct); + toolTip->show(getDescription(cmpRes), pos, GlobalResources::getInstance().bitmapRightOnlyAct); break; case FILE_LEFT_NEWER: - toolTip->show(_("Files that exist on both sides, left one is newer"), pos, GlobalResources::getInstance().bitmapLeftNewerAct); + toolTip->show(getDescription(cmpRes), pos, GlobalResources::getInstance().bitmapLeftNewerAct); break; case FILE_RIGHT_NEWER: - toolTip->show(_("Files that exist on both sides, right one is newer"), pos, GlobalResources::getInstance().bitmapRightNewerAct); + toolTip->show(getDescription(cmpRes), pos, GlobalResources::getInstance().bitmapRightNewerAct); break; case FILE_DIFFERENT: - toolTip->show(_("Files that exist on both sides and have different content"), pos, GlobalResources::getInstance().bitmapDifferentAct); + toolTip->show(getDescription(cmpRes), pos, GlobalResources::getInstance().bitmapDifferentAct); break; case FILE_EQUAL: - toolTip->show(_(""), pos, GlobalResources::getInstance().bitmapEqualAct); + toolTip->show(getDescription(cmpRes), pos, GlobalResources::getInstance().bitmapEqualAct); break; case FILE_CONFLICT: - toolTip->show(rowData->conflictDescription.get(), pos, GlobalResources::getInstance().bitmapConflictAct); + toolTip->show(fsObj->getConflictDescription(), pos, GlobalResources::getInstance().bitmapConflictAct); break; } } @@ -1711,8 +1742,8 @@ void GridCellRendererMiddle::Draw(wxGrid& grid, bool isSelected) { //retrieve grid data - const FileCompareLine* const rowData = m_gridMiddle->gridDataTable->getRawData(row); - if (rowData != NULL) //if valid row... + const FileSystemObject* const fsObj = m_gridMiddle->gridDataTable->getRawData(row); + if (fsObj != NULL) //if valid row... { if (rect.GetWidth() > CHECK_BOX_WIDTH) { @@ -1730,13 +1761,13 @@ void GridCellRendererMiddle::Draw(wxGrid& grid, //HIGHLIGHTNING: if (rowIsHighlighted && m_gridMiddle->highlightedPos == CustomGridMiddle::BLOCKPOS_CHECK_BOX) { - if (rowData->selectedForSynchronization) + if (fsObj->selectedForSynchronization) dc.DrawLabel(wxEmptyString, *GlobalResources::getInstance().bitmapCheckBoxTrueFocus, rectShrinked, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); else dc.DrawLabel(wxEmptyString, *GlobalResources::getInstance().bitmapCheckBoxFalseFocus, rectShrinked, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); } //default - else if (rowData->selectedForSynchronization) + else if (fsObj->selectedForSynchronization) dc.DrawLabel(wxEmptyString, *GlobalResources::getInstance().bitmapCheckBoxTrue, rectShrinked, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); else dc.DrawLabel(wxEmptyString, *GlobalResources::getInstance().bitmapCheckBoxFalse, rectShrinked, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); @@ -1758,24 +1789,24 @@ void GridCellRendererMiddle::Draw(wxGrid& grid, case CustomGridMiddle::BLOCKPOS_CHECK_BOX: break; case CustomGridMiddle::BLOCKPOS_LEFT: - dc.DrawLabel(wxEmptyString, getSyncOpImage(rowData->cmpResult, true, SYNC_DIR_LEFT), rectShrinked, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); + dc.DrawLabel(wxEmptyString, getSyncOpImage(fsObj->getCategory(), true, SYNC_DIR_LEFT), rectShrinked, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); break; case CustomGridMiddle::BLOCKPOS_MIDDLE: - dc.DrawLabel(wxEmptyString, getSyncOpImage(rowData->cmpResult, true, SYNC_DIR_NONE), rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); + dc.DrawLabel(wxEmptyString, getSyncOpImage(fsObj->getCategory(), true, SYNC_DIR_NONE), rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); break; case CustomGridMiddle::BLOCKPOS_RIGHT: - dc.DrawLabel(wxEmptyString, getSyncOpImage(rowData->cmpResult, true, SYNC_DIR_RIGHT), rectShrinked, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL); + dc.DrawLabel(wxEmptyString, getSyncOpImage(fsObj->getCategory(), true, SYNC_DIR_RIGHT), rectShrinked, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL); break; } else //default { - const wxBitmap& syncOpIcon = getSyncOpImage(rowData->cmpResult, rowData->selectedForSynchronization, rowData->syncDir); + const wxBitmap& syncOpIcon = getSyncOpImage(fsObj->getCategory(), fsObj->selectedForSynchronization, fsObj->syncDir); dc.DrawLabel(wxEmptyString, syncOpIcon, rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); } } else //comparison results view { - switch (rowData->cmpResult) + switch (fsObj->getCategory()) { case FILE_LEFT_SIDE_ONLY: dc.DrawLabel(wxEmptyString, *GlobalResources::getInstance().bitmapLeftOnlySmall, rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); diff --git a/library/CustomGrid.h b/library/CustomGrid.h index a71b985a..08ec5f55 100644 --- a/library/CustomGrid.h +++ b/library/CustomGrid.h @@ -6,6 +6,7 @@ #include "processXml.h" #include <map> #include <memory> +#include <set> class CustomGridTableRim; class CustomGridTableLeft; @@ -19,6 +20,7 @@ class CustomGridLeft; class CustomGridMiddle; class CustomGridRight; + namespace FreeFileSync { class GridView; @@ -59,7 +61,7 @@ public: CustomGridRight* gridRight, const FreeFileSync::GridView* gridDataView); - std::set<int> getAllSelectedRows() const; + std::set<unsigned int> getAllSelectedRows() const; //set sort direction indicator on UI typedef int SortColumn; diff --git a/library/ShadowCopy/shadow.cpp b/library/ShadowCopy/shadow.cpp index e10a4eb8..edc62e8b 100644 --- a/library/ShadowCopy/shadow.cpp +++ b/library/ShadowCopy/shadow.cpp @@ -82,6 +82,9 @@ bool shadow::createShadowCopy(const wchar_t* volumeName, //wait for shadow copy writers to complete hr = pWriteMetaData->Wait(); + if (SUCCEEDED(hr)) + pWriteMetaData->QueryStatus(&hr, NULL); //check if the async operation succeeded... + pWriteMetaData->Release(); if (FAILED(hr)) { @@ -126,6 +129,9 @@ bool shadow::createShadowCopy(const wchar_t* volumeName, } hr = pPrepare->Wait(); + if (SUCCEEDED(hr)) + pPrepare->QueryStatus(&hr, NULL); //check if the async operation succeeded... + pPrepare->Release(); if (FAILED(hr)) { @@ -144,7 +150,10 @@ bool shadow::createShadowCopy(const wchar_t* volumeName, } hr = pDoShadowCopy->Wait(); - pDoShadowCopy->Release(); + if (SUCCEEDED(hr)) + pDoShadowCopy->QueryStatus(&hr, NULL); //check if the async operation succeeded... + + pDoShadowCopy->Release(); if (FAILED(hr)) { releaseShadowCopy(pBackupComponents); diff --git a/library/customButton.cpp b/library/customButton.cpp deleted file mode 100644 index fc686d3f..00000000 --- a/library/customButton.cpp +++ /dev/null @@ -1,300 +0,0 @@ -#include "customButton.h" -#include <wx/dcmemory.h> -#include <wx/image.h> - -wxButtonWithImage::wxButtonWithImage(wxWindow *parent, - wxWindowID id, - const wxString& label, - const wxPoint& pos, - const wxSize& size, - long style, - const wxValidator& validator, - const wxString& name) : - wxBitmapButton(parent, id, wxNullBitmap, pos, size, style | wxBU_AUTODRAW, validator, name), - m_spaceAfter(0), - m_spaceBefore(0) -{ - setTextLabel(label); -} - - -void wxButtonWithImage::setBitmapFront(const wxBitmap& bitmap, unsigned spaceAfter) -{ - bitmapFront = bitmap; - m_spaceAfter = spaceAfter; - refreshButtonLabel(); -} - - -void wxButtonWithImage::setTextLabel(const wxString& text) -{ - textLabel = text; - wxBitmapButton::SetLabel(text); - refreshButtonLabel(); -} - - -void wxButtonWithImage::setBitmapBack(const wxBitmap& bitmap, unsigned spaceBefore) -{ - bitmapBack = bitmap; - m_spaceBefore = spaceBefore; - refreshButtonLabel(); -} - - -void makeWhiteTransparent(const wxColor exceptColor, wxImage& image) -{ - unsigned char* alphaData = image.GetAlpha(); - if (alphaData) - { - assert(exceptColor.Red() != 255); - - unsigned char exCol = exceptColor.Red(); //alpha value can be extracted from any one of (red/green/blue) - unsigned char* imageStart = image.GetData(); - - unsigned char* j = alphaData; - const unsigned char* const rowEnd = j + image.GetWidth() * image.GetHeight(); - while (j != rowEnd) - { - const unsigned char* imagePixel = imageStart + (j - alphaData) * 3; //each pixel consists of three chars - //exceptColor(red,green,blue) becomes fully opaque(255), while white(255,255,255) becomes transparent(0) - *(j++) = ((255 - imagePixel[0]) * wxIMAGE_ALPHA_OPAQUE) / (255 - exCol); - } - } -} - - -wxSize getSizeNeeded(const wxString& text, wxFont& font) -{ - wxCoord width, height; - wxMemoryDC dc; - - wxString textFormatted = text; - textFormatted.Replace(wxT("&"), wxT(""), false); //remove accelerator - dc.GetMultiLineTextExtent(textFormatted, &width, &height , NULL, &font); - return wxSize(width, height); -} - -/* -inline -void linearInterpolationHelper(const int shift, wxImage& img) -{ - unsigned char* const data = img.GetData(); - const int width = img.GetWidth(); - if (width < 2) - return; - - const float intensity = 0.25; - - for (int y = 1; y < img.GetHeight() - 1; ++y) - { - float back = 0; - float middle = 0; - float front = 0; - - unsigned char* location = data + 3 * (y * width) + shift; - const unsigned char* const endPos = location + 3 * width; - - middle = (*location + *(location - 3 * width) + *(location + 3 * width)) / 3;; - front = (*(location + 3) + *(location + 3 * (1 - width)) + *(location + 3 * (1 + width))) / 3; - *location += ((middle + front) / 2 - *location) * intensity; - location += 3; - - while (location < endPos - 3) - { - back = middle; - middle = front; - front = (*(location + 3) + *(location + 3 * (1 - width)) + *(location + 3 * (1 + width))) / 3; - *location += ((back + middle + front) / 3 - *location) * intensity; - location += 3; - } - - back = middle; - middle = front; - *location += ((back + middle) / 2 - *location) * intensity; - } -} - - -void linearInterpolation(wxImage& img) -{ - linearInterpolationHelper(0, img); //red channel - linearInterpolationHelper(1, img); //green channel - linearInterpolationHelper(2, img); //blue channel -} -*/ - -wxBitmap wxButtonWithImage::createBitmapFromText(const wxString& text) -{ - if (text.empty()) - return wxBitmap(); - - wxFont currentFont = wxBitmapButton::GetFont(); - wxColor textColor = wxBitmapButton::GetForegroundColour(); - - wxSize sizeNeeded = getSizeNeeded(text, currentFont); - wxBitmap newBitmap(sizeNeeded.GetWidth(), sizeNeeded.GetHeight()); - - wxMemoryDC dc; - dc.SelectObject(newBitmap); - - //set up white background - dc.SetBackground(*wxWHITE_BRUSH); - dc.Clear(); - - //find position of accelerator - int indexAccel = -1; - size_t accelPos; - wxString textLabelFormatted = text; - if ((accelPos = text.find(wxT("&"))) != wxString::npos) - { - textLabelFormatted.Replace(wxT("&"), wxT(""), false); //remove accelerator - indexAccel = accelPos; - } - - dc.SetTextForeground(textColor); - dc.SetTextBackground(*wxWHITE); - dc.SetFont(currentFont); - dc.DrawLabel(textLabelFormatted, wxNullBitmap, wxRect(0, 0, newBitmap.GetWidth(), newBitmap.GetHeight()), wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, indexAccel); - - dc.SelectObject(wxNullBitmap); - - //add alpha channel to image - wxImage finalImage(newBitmap.ConvertToImage()); - finalImage.SetAlpha(); - - //linearInterpolation(finalImage); - - //make white background transparent - makeWhiteTransparent(textColor, finalImage); - - return wxBitmap(finalImage); -} - - -//copy one image into another, allowing arbitrary overlapping! (pos may contain negative numbers) -void writeToImage(const wxImage& source, const wxPoint pos, wxImage& target) -{ - //determine startpositions in source and target image, as well as width and height to be copied - wxPoint posSrc, posTrg; - int width, height; - - //X-axis - if (pos.x < 0) - { - posSrc.x = -pos.x; - posTrg.x = 0; - width = std::min(pos.x + source.GetWidth(), target.GetWidth()); - } - else - { - posSrc.x = 0; - posTrg.x = pos.x; - width = std::min(target.GetWidth() - pos.x, source.GetWidth()); - } - - //Y-axis - if (pos.y < 0) - { - posSrc.y = -pos.y; - posTrg.y = 0; - height = std::min(pos.y + source.GetHeight(), target.GetHeight()); - } - else - { - posSrc.y = 0; - posTrg.y = pos.y; - height = std::min(target.GetHeight() - pos.y, source.GetHeight()); - } - - - if (width > 0 && height > 0) - { - //copy source to target respecting overlapping parts - const unsigned char* sourcePtr = source.GetData() + 3 * (posSrc.x + posSrc.y * source.GetWidth()); - const unsigned char* const sourcePtrEnd = source.GetData() + 3 * (posSrc.x + (posSrc.y + height) * source.GetWidth()); - unsigned char* targetPtr = target.GetData() + 3 * (posTrg.x + posTrg.y * target.GetWidth()); - - while (sourcePtr < sourcePtrEnd) - { - memcpy(targetPtr, sourcePtr, 3 * width); - sourcePtr += 3 * source.GetWidth(); - targetPtr += 3 * target.GetWidth(); - } - - //handle different cases concerning alpha channel - if (source.HasAlpha()) - { - if (!target.HasAlpha()) - { - target.SetAlpha(); - unsigned char* alpha = target.GetAlpha(); - memset(alpha, wxIMAGE_ALPHA_OPAQUE, target.GetWidth() * target.GetHeight()); - } - - //copy alpha channel - const unsigned char* sourcePtr = source.GetAlpha() + (posSrc.x + posSrc.y * source.GetWidth()); - const unsigned char* const sourcePtrEnd = source.GetAlpha() + (posSrc.x + (posSrc.y + height) * source.GetWidth()); - unsigned char* targetPtr = target.GetAlpha() + (posTrg.x + posTrg.y * target.GetWidth()); - - while (sourcePtr < sourcePtrEnd) - { - memcpy(targetPtr, sourcePtr, width); - sourcePtr += source.GetWidth(); - targetPtr += target.GetWidth(); - } - } - else if (target.HasAlpha()) - { - unsigned char* targetPtr = target.GetAlpha() + (posTrg.x + posTrg.y * target.GetWidth()); - const unsigned char* const targetPtrEnd = target.GetAlpha() + (posTrg.x + (posTrg.y + height) * target.GetWidth()); - - while (targetPtr < targetPtrEnd) - { - memset(targetPtr, wxIMAGE_ALPHA_OPAQUE, width); - targetPtr += target.GetWidth(); - } - } - } -} - - -void wxButtonWithImage::refreshButtonLabel() -{ - wxBitmap bitmapText = createBitmapFromText(textLabel); - - //calculate dimensions of new button - const int height = std::max(std::max(bitmapFront.GetHeight(), bitmapText.GetHeight()), bitmapBack.GetHeight()); - const int width = bitmapFront.GetWidth() + m_spaceAfter + bitmapText.GetWidth() + m_spaceBefore + bitmapBack.GetWidth(); - - //create a transparent image - wxImage transparentImage(width, height, false); - transparentImage.SetAlpha(); - unsigned char* alpha = transparentImage.GetAlpha(); - memset(alpha, wxIMAGE_ALPHA_TRANSPARENT, width * height); - - //wxDC::DrawLabel() unfortunately isn't working for transparent images on Linux, so we need to use custom image-concatenation - if (bitmapFront.IsOk()) - writeToImage(wxImage(bitmapFront.ConvertToImage()), - wxPoint(0, (transparentImage.GetHeight() - bitmapFront.GetHeight()) / 2), - transparentImage); - - if (bitmapText.IsOk()) - writeToImage(wxImage(bitmapText.ConvertToImage()), - wxPoint(bitmapFront.GetWidth() + m_spaceAfter, (transparentImage.GetHeight() - bitmapText.GetHeight()) / 2), - transparentImage); - - if (bitmapBack.IsOk()) - writeToImage(wxImage(bitmapBack.ConvertToImage()), - wxPoint(bitmapFront.GetWidth() + m_spaceAfter + bitmapText.GetWidth() + m_spaceBefore, (transparentImage.GetHeight() - bitmapBack.GetHeight()) / 2), - transparentImage); - - //adjust button size - wxSize minSize = GetMinSize(); - - //SetMinSize() instead of SetSize() is needed here for wxWindows layout determination to work corretly - wxBitmapButton::SetMinSize(wxSize(std::max(width + 10, minSize.GetWidth()), std::max(height + 5, minSize.GetHeight()))); - - //finally set bitmap - wxBitmapButton::SetBitmapLabel(wxBitmap(transparentImage)); -} diff --git a/library/customButton.h b/library/customButton.h deleted file mode 100644 index ce43828a..00000000 --- a/library/customButton.h +++ /dev/null @@ -1,42 +0,0 @@ -/*************************************************************** - * Purpose: wxButton with bitmap label - * Author: ZenJu (zhnmju123@gmx.de) - * Created: Feb. 2009 - **************************************************************/ - -#ifndef CUSTOMBUTTON_H_INCLUDED -#define CUSTOMBUTTON_H_INCLUDED - -#include <wx/bmpbuttn.h> - - -//wxButtonWithImage behaves like wxButton but optionally adds bitmap labels -class wxButtonWithImage : public wxBitmapButton -{ -public: - wxButtonWithImage(wxWindow *parent, - wxWindowID id, - const wxString& label, - const wxPoint& pos = wxDefaultPosition, - const wxSize& size = wxDefaultSize, - long style = 0, - const wxValidator& validator = wxDefaultValidator, - const wxString& name = wxButtonNameStr); - - void setBitmapFront(const wxBitmap& bitmap, unsigned spaceAfter = 0); - void setTextLabel( const wxString& text); - void setBitmapBack( const wxBitmap& bitmap, unsigned spaceBefore = 0); - -private: - wxBitmap createBitmapFromText(const wxString& text); - void refreshButtonLabel(); - - wxBitmap bitmapFront; - unsigned m_spaceAfter; - wxString textLabel; - unsigned m_spaceBefore; - wxBitmap bitmapBack; -}; - - -#endif // CUSTOMBUTTON_H_INCLUDED diff --git a/library/fileError.h b/library/fileError.h deleted file mode 100644 index 214cdecb..00000000 --- a/library/fileError.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef FILEERROR_H_INCLUDED -#define FILEERROR_H_INCLUDED - -#include "zstring.h" -#include "fileError.h" - -namespace FreeFileSync -{ - class FileError //Exception class used to notify file/directory copy/delete errors - { - public: - FileError(const Zstring& message) : - errorMessage(message) {} - - const Zstring& show() const - { - return errorMessage; - } - - private: - Zstring errorMessage; - }; -} - -#endif // FILEERROR_H_INCLUDED diff --git a/library/fileHandling.cpp b/library/fileHandling.cpp deleted file mode 100644 index 06431b81..00000000 --- a/library/fileHandling.cpp +++ /dev/null @@ -1,1075 +0,0 @@ -#include "fileHandling.h" -#include <wx/intl.h> -#include <wx/msgdlg.h> -#include "../algorithm.h" -#include <wx/filename.h> -#include "globalFunctions.h" - -#ifdef FFS_WIN -#include <wx/msw/wrapwin.h> //includes "windows.h" -#include "shadow.h" - -#elif defined FFS_LINUX -#include <sys/stat.h> -#include <time.h> -#include <utime.h> -#include <fstream> -#include <unistd.h> -#include <dirent.h> -#include <errno.h> -#endif - -using FreeFileSync::FileError; - - -class RecycleBin -{ -public: - static const RecycleBin& getInstance() - { - static RecycleBin instance; //lazy creation of RecycleBin - return instance; - } - - bool recycleBinExists() const - { - return recycleBinAvailable; - } - - bool moveToRecycleBin(const Zstring& filename) const; - -private: - RecycleBin() : - recycleBinAvailable(false) - { -#ifdef FFS_WIN - recycleBinAvailable = true; -#endif // FFS_WIN - } - - ~RecycleBin() {} - -private: - bool recycleBinAvailable; -}; - - -bool RecycleBin::moveToRecycleBin(const Zstring& filename) const -{ - if (!recycleBinAvailable) //this method should ONLY be called if recycle bin is available - throw RuntimeException(_("Initialization of Recycle Bin failed!")); - -#ifdef FFS_WIN - Zstring filenameDoubleNull = filename + wxChar(0); - - SHFILEOPSTRUCT fileOp; - fileOp.hwnd = NULL; - fileOp.wFunc = FO_DELETE; - fileOp.pFrom = filenameDoubleNull.c_str(); - fileOp.pTo = NULL; - fileOp.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_SILENT | FOF_NOERRORUI; - fileOp.fAnyOperationsAborted = false; - fileOp.hNameMappings = NULL; - fileOp.lpszProgressTitle = NULL; - - if (SHFileOperation(&fileOp) != 0 || fileOp.fAnyOperationsAborted) return false; -#endif - - return true; -} - - -bool FreeFileSync::recycleBinExists() -{ - return RecycleBin::getInstance().recycleBinExists(); -} - - -inline -bool moveToRecycleBin(const Zstring& filename) throw(RuntimeException) -{ - return RecycleBin::getInstance().moveToRecycleBin(filename); -} - - -bool FreeFileSync::fileExists(const Zstring& filename) -{ //symbolic links (broken or not) are also treated as existing files! -#ifdef FFS_WIN - // we must use GetFileAttributes() instead of the ANSI C functions because - // it can cope with network (UNC) paths unlike them - const DWORD ret = ::GetFileAttributes(filename.c_str()); - - return (ret != INVALID_FILE_ATTRIBUTES) && !(ret & FILE_ATTRIBUTE_DIRECTORY); - -#elif defined FFS_LINUX - struct stat fileInfo; - return (lstat(filename.c_str(), &fileInfo) == 0 && - (S_ISLNK(fileInfo.st_mode) || S_ISREG(fileInfo.st_mode))); -#endif -} - - -void FreeFileSync::removeFile(const Zstring& filename, const bool useRecycleBin) -{ - //no error situation if file is not existing! manual deletion relies on it! -#ifdef FFS_WIN - if (GetFileAttributes(filename.c_str()) == INVALID_FILE_ATTRIBUTES) - return; //neither file nor any other object with that name existing - -#elif defined FFS_LINUX - struct stat fileInfo; - if (lstat(filename.c_str(), &fileInfo) != 0) - return; //neither file nor any other object (e.g. broken symlink) with that name existing -#endif - - if (useRecycleBin) - { - if (!moveToRecycleBin(filename)) - throw FileError(Zstring(_("Error moving to Recycle Bin:")) + wxT("\n\"") + filename + wxT("\"")); - return; - } - -#ifdef FFS_WIN - //initialize file attributes - if (!SetFileAttributes( - filename.c_str(), //address of filename - FILE_ATTRIBUTE_NORMAL)) //attributes to set - { - Zstring errorMessage = Zstring(_("Error deleting file:")) + wxT("\n\"") + filename + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - } - - //remove file, support for \\?\-prefix - if (DeleteFile(filename.c_str()) == 0) - { - Zstring errorMessage = Zstring(_("Error deleting file:")) + wxT("\n\"") + filename + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - } -#elif defined FFS_LINUX - if (unlink(filename.c_str()) != 0) - { - Zstring errorMessage = Zstring(_("Error deleting file:")) + wxT("\n\"") + filename + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - } -#endif -} - - -class FilesDirsOnlyTraverser : public FreeFileSync::FullDetailFileTraverser -{ -public: - FilesDirsOnlyTraverser(std::vector<Zstring>& files, std::vector<Zstring>& dirs) : - m_files(files), - m_dirs(dirs) {} - - virtual wxDirTraverseResult OnFile(const Zstring& filename, const FreeFileSync::FileInfo& details) - { - m_files.push_back(filename); - return wxDIR_CONTINUE; - } - virtual wxDirTraverseResult OnDir(const Zstring& dirname) - { - m_dirs.push_back(dirname); - return wxDIR_IGNORE; //DON'T traverse into subdirs, removeDirectory works recursively! - } - virtual wxDirTraverseResult OnError(const Zstring& errorText) - { - throw FileError(errorText); - } - -private: - std::vector<Zstring>& m_files; - std::vector<Zstring>& m_dirs; -}; - - -void FreeFileSync::removeDirectory(const Zstring& directory, const bool useRecycleBin) -{ - //no error situation if directory is not existing! manual deletion relies on it! -#ifdef FFS_WIN - const DWORD dirAttr = GetFileAttributes(directory.c_str()); //name of a file or directory - if (dirAttr == INVALID_FILE_ATTRIBUTES) - return; //neither directory nor any other object with that name existing - -#elif defined FFS_LINUX - struct stat dirInfo; - if (lstat(directory.c_str(), &dirInfo) != 0) - return; //neither directory nor any other object (e.g. broken symlink) with that name existing -#endif - - if (useRecycleBin) - { - if (!moveToRecycleBin(directory)) - throw FileError(Zstring(_("Error moving to Recycle Bin:")) + wxT("\n\"") + directory + wxT("\"")); - return; - } - -//attention: check if directory is a symlink! Do NOT traverse into it deleting contained files!!! -#ifdef FFS_WIN - if (dirAttr & FILE_ATTRIBUTE_REPARSE_POINT) - { //remove symlink directly, support for \\?\-prefix - if (RemoveDirectory(directory.c_str()) == 0) - { - Zstring errorMessage = Zstring(_("Error deleting directory:")) + wxT("\n\"") + directory + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - } - return; - } - -#elif defined FFS_LINUX - if (S_ISLNK(dirInfo.st_mode)) - { - if (unlink(directory.c_str()) != 0) - { - Zstring errorMessage = Zstring(_("Error deleting directory:")) + wxT("\n\"") + directory + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - } - return; - } -#endif - - std::vector<Zstring> fileList; - std::vector<Zstring> dirList; - - //get all files and directories from current directory (WITHOUT subdirectories!) - FilesDirsOnlyTraverser traverser(fileList, dirList); - FreeFileSync::traverseInDetail(directory, false, &traverser); //don't traverse into symlinks to directories - - //delete files - for (std::vector<Zstring>::const_iterator j = fileList.begin(); j != fileList.end(); ++j) - FreeFileSync::removeFile(*j, false); - - //delete directories recursively - for (std::vector<Zstring>::const_iterator j = dirList.begin(); j != dirList.end(); ++j) - FreeFileSync::removeDirectory(*j, false); //call recursively to correctly handle symbolic links - - //parent directory is deleted last -#ifdef FFS_WIN - //initialize file attributes - if (!SetFileAttributes( - directory.c_str(), // address of directory name - FILE_ATTRIBUTE_NORMAL)) // attributes to set - { - Zstring errorMessage = Zstring(_("Error deleting directory:")) + wxT("\n\"") + directory + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - } - - //remove directory, support for \\?\-prefix - if (!RemoveDirectory(directory.c_str())) - { - Zstring errorMessage = Zstring(_("Error deleting directory:")) + wxT("\n\"") + directory + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - } -#else - if (rmdir(directory.c_str()) != 0) - { - Zstring errorMessage = Zstring(_("Error deleting directory:")) + wxT("\n\"") + directory + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - } -#endif -} - - -#ifdef FFS_WIN -class CloseHandleOnExit -{ -public: - CloseHandleOnExit(HANDLE fileHandle) : fileHandle_(fileHandle) {} - - ~CloseHandleOnExit() - { - CloseHandle(fileHandle_); - } - -private: - HANDLE fileHandle_; -}; - - -class KernelDllHandler //dynamically load windows API functions -{ - typedef DWORD (WINAPI *GetFinalPath)( - HANDLE hFile, - LPTSTR lpszFilePath, - DWORD cchFilePath, - DWORD dwFlags); - -public: - static const KernelDllHandler& getInstance() //lazy creation of KernelDllHandler - { - static KernelDllHandler instance; - return instance; - } - - GetFinalPath getFinalPathNameByHandle; - -private: - KernelDllHandler() : - getFinalPathNameByHandle(NULL), - hKernel(NULL) - { - //get a handle to the DLL module containing required functionality - hKernel = ::LoadLibrary(wxT("kernel32.dll")); - if (hKernel) - getFinalPathNameByHandle = reinterpret_cast<GetFinalPath>(::GetProcAddress(hKernel, "GetFinalPathNameByHandleW")); //load unicode version! - } - - ~KernelDllHandler() - { - if (hKernel) ::FreeLibrary(hKernel); - } - - HINSTANCE hKernel; -}; - - -Zstring resolveDirectorySymlink(const Zstring& dirLinkName) //get full target path of symbolic link to a directory -{ - //open handle to target of symbolic link - HANDLE hDir = CreateFile(dirLinkName.c_str(), - 0, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - NULL, - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, - NULL); - if (hDir == INVALID_HANDLE_VALUE) - return Zstring(); - - CloseHandleOnExit dummy(hDir); - - if (KernelDllHandler::getInstance().getFinalPathNameByHandle == NULL ) - throw FileError(Zstring(_("Error loading library function:")) + wxT("\n\"") + wxT("GetFinalPathNameByHandleW") + wxT("\"")); - - const unsigned BUFFER_SIZE = 10000; - TCHAR targetPath[BUFFER_SIZE]; - - const DWORD rv = KernelDllHandler::getInstance().getFinalPathNameByHandle( - hDir, - targetPath, - BUFFER_SIZE, - 0); - - if (rv >= BUFFER_SIZE || rv == 0) - return Zstring(); - - return targetPath; -} -#endif - - -void createDirectoryRecursively(const Zstring& directory, const Zstring& templateDir, const bool copyDirectorySymLinks, const int level) -{ - if (wxDirExists(directory.c_str())) - return; - - if (level == 50) //catch endless recursion - return; - - //try to create parent folders first - const Zstring dirParent = directory.BeforeLast(FreeFileSync::FILE_NAME_SEPARATOR); - if (!dirParent.empty() && !wxDirExists(dirParent)) - { - //call function recursively - const Zstring templateParent = templateDir.BeforeLast(FreeFileSync::FILE_NAME_SEPARATOR); - createDirectoryRecursively(dirParent, templateParent, false, level + 1); //don't create symbolic links in recursion! - } - - //now creation should be possible -#ifdef FFS_WIN - const DWORD templateAttr = ::GetFileAttributes(templateDir.c_str()); //replaces wxDirExists(): also returns successful for broken symlinks - if (templateAttr == INVALID_FILE_ATTRIBUTES) //fallback - { - if (CreateDirectory( - directory.c_str(), // pointer to a directory path string - NULL) == 0 && level == 0) - { - const Zstring errorMessage = Zstring(_("Error creating directory:")) + wxT("\n\"") + directory + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - } - } - else - { - //symbolic link handling - if (!copyDirectorySymLinks && templateAttr & FILE_ATTRIBUTE_REPARSE_POINT) //create directory based on target of symbolic link - { - //get target directory of symbolic link - const Zstring targetPath = resolveDirectorySymlink(templateDir); - if (targetPath.empty()) - { - if (level == 0) - throw FileError(Zstring(_("Error resolving symbolic link:")) + wxT("\n\"") + templateDir + wxT("\"")); - } - else - { - if (CreateDirectoryEx( // this function automatically copies symbolic links if encountered - targetPath.c_str(), // pointer to path string of template directory - directory.c_str(), // pointer to a directory path string - NULL) == 0 && level == 0) - { - const Zstring errorMessage = Zstring(_("Error creating directory:")) + wxT("\n\"") + directory + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - } - } - } - else //in all other cases - { - if (CreateDirectoryEx( // this function automatically copies symbolic links if encountered - templateDir.c_str(), // pointer to path string of template directory - directory.c_str(), // pointer to a directory path string - NULL) == 0 && level == 0) - { - const Zstring errorMessage = Zstring(_("Error creating directory:")) + wxT("\n\"") + directory + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - } - } - } -#elif defined FFS_LINUX - //symbolic link handling - if (copyDirectorySymLinks) - { - //test if templateDir is a symbolic link - struct stat linkInfo; - if (lstat(templateDir.c_str(), &linkInfo) == 0 && S_ISLNK(linkInfo.st_mode)) - { - //copy symbolic link - const int BUFFER_SIZE = 10000; - char buffer[BUFFER_SIZE]; - const int bytesWritten = readlink(templateDir.c_str(), buffer, BUFFER_SIZE); - if (bytesWritten < 0 || bytesWritten == BUFFER_SIZE) - { - Zstring errorMessage = Zstring(_("Error resolving symbolic link:")) + wxT("\n\"") + templateDir + wxT("\""); - if (bytesWritten < 0) errorMessage += Zstring(wxT("\n\n")) + FreeFileSync::getLastErrorFormatted(); - throw FileError(errorMessage); - } - //set null-terminating char - buffer[bytesWritten] = 0; - - if (symlink(buffer, directory.c_str()) != 0) - { - Zstring errorMessage = Zstring(_("Error creating directory:")) + wxT("\n\"") + directory + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - } - return; //symlink created successfully - } - } - - //default directory creation - if (mkdir(directory.c_str(), 0755) != 0 && level == 0) - { - Zstring errorMessage = Zstring(_("Error creating directory:")) + wxT("\n\"") + directory + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - } - -//copy directory permissions: not sure if this is a good idea: if a directory is read-only copying/sync'ing of files will fail... - /* - if (templateDirExists) - { - struct stat fileInfo; - if (stat(templateDir.c_str(), &fileInfo) != 0) //read permissions from template directory - throw FileError(Zstring(_("Error reading file attributes:")) + wxT("\n\"") + templateDir + wxT("\"")); - - // reset the umask as we want to create the directory with exactly the same permissions as the template - wxCHANGE_UMASK(0); - - if (mkdir(directory.c_str(), fileInfo.st_mode) != 0 && level == 0) - throw FileError(Zstring(_("Error creating directory:")) + wxT("\n\"") + directory + wxT("\"")); - } - else - { - if (mkdir(directory.c_str(), 0777) != 0 && level == 0) - throw FileError(Zstring(_("Error creating directory:")) + wxT("\n\"") + directory + wxT("\"")); - } - */ -#endif -} - - -void FreeFileSync::createDirectory(const Zstring& directory, const Zstring& templateDir, const bool copyDirectorySymLinks) -{ - //remove trailing separator - Zstring dirFormatted; - if (FreeFileSync::endsWithPathSeparator(directory)) - dirFormatted = directory.BeforeLast(FreeFileSync::FILE_NAME_SEPARATOR); - else - dirFormatted = directory; - - Zstring templateFormatted; - if (FreeFileSync::endsWithPathSeparator(templateDir)) - templateFormatted = templateDir.BeforeLast(FreeFileSync::FILE_NAME_SEPARATOR); - else - templateFormatted = templateDir; - - createDirectoryRecursively(dirFormatted, templateFormatted, copyDirectorySymLinks, 0); -} - - -#ifdef FFS_WIN - -#ifndef COPY_FILE_COPY_SYMLINK -const DWORD COPY_FILE_COPY_SYMLINK = 0x00000800; -#endif - -DWORD CALLBACK copyCallbackInternal( - LARGE_INTEGER totalFileSize, - LARGE_INTEGER totalBytesTransferred, - LARGE_INTEGER streamSize, - LARGE_INTEGER streamBytesTransferred, - DWORD dwStreamNumber, - DWORD dwCallbackReason, - HANDLE hSourceFile, - HANDLE hDestinationFile, - LPVOID lpData) -{ - using FreeFileSync::CopyFileCallback; - - //small performance optimization: it seems this callback function is called for every 64 kB (depending on cluster size). - static unsigned int callNr = 0; - if (++callNr % 50 == 0) //reduce by factor of 50 =^ 10-20 calls/sec - { - if (lpData != NULL) - { - //some odd check for some possible(?) error condition - if (totalBytesTransferred.HighPart < 0) //let's see if someone answers the call... - wxMessageBox(wxT("You've just discovered a bug in WIN32 API function \"CopyFileEx\"! \n\n\ - Please write a mail to the author of FreeFileSync at zhnmju123@gmx.de and simply state that\n\ - \"totalBytesTransferred.HighPart can be below zero\"!\n\n\ - This will then be handled in future versions of FreeFileSync.\n\nThanks -ZenJu"), wxT("Warning")); - - - CopyFileCallback* callback = static_cast<CopyFileCallback*>(lpData); - - switch (callback->updateCopyStatus(wxULongLong(totalBytesTransferred.HighPart, totalBytesTransferred.LowPart))) - { - case CopyFileCallback::CONTINUE: - break; - case CopyFileCallback::CANCEL: - return PROGRESS_CANCEL; - } - } - } - - return PROGRESS_CONTINUE; -} - - -void FreeFileSync::copyFile(const Zstring& sourceFile, - const Zstring& targetFile, - const bool copyFileSymLinks, - ShadowCopy* shadowCopyHandler, - CopyFileCallback* callback) -{ - DWORD copyFlags = COPY_FILE_FAIL_IF_EXISTS; - - if (copyFileSymLinks) //copy symbolic links instead of the files pointed at - copyFlags |= COPY_FILE_COPY_SYMLINK; - - if (!::CopyFileEx( //same performance as CopyFile() - sourceFile.c_str(), - targetFile.c_str(), - copyCallbackInternal, - callback, - NULL, - copyFlags)) - { - const DWORD lastError = ::GetLastError(); - - //if file is locked (try to) use Windows Volume Shadow Copy Service - if (lastError == ERROR_SHARING_VIOLATION && shadowCopyHandler != NULL) - { - const Zstring shadowFilename(shadowCopyHandler->makeShadowCopy(sourceFile)); - FreeFileSync::copyFile(shadowFilename, //transferred bytes is automatically reset when new file is copied - targetFile, - copyFileSymLinks, - shadowCopyHandler, - callback); - return; - } - - const Zstring errorMessage = Zstring(_("Error copying file:")) + wxT("\n\"") + sourceFile + wxT("\" -> \"") + targetFile + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted(lastError)); - } -} - - -#elif defined FFS_LINUX -struct MemoryAllocator -{ - MemoryAllocator() - { - buffer = new char[bufferSize]; - } - - ~MemoryAllocator() - { - delete [] buffer; - } - - static const unsigned int bufferSize = 512 * 1024; - char* buffer; -}; - - -void FreeFileSync::copyFile(const Zstring& sourceFile, - const Zstring& targetFile, - const bool copyFileSymLinks, - CopyFileCallback* callback) -{ - using FreeFileSync::CopyFileCallback; - - try - { - if (FreeFileSync::fileExists(targetFile.c_str())) - throw FileError(Zstring(_("Error copying file:")) + wxT("\n\"") + sourceFile + wxT("\" -> \"") + targetFile + wxT("\"\n") - + _("Target file already existing!")); - - //symbolic link handling - if (copyFileSymLinks) - { - //test if sourceFile is a symbolic link - struct stat linkInfo; - if (lstat(sourceFile.c_str(), &linkInfo) != 0) - { - Zstring errorMessage = Zstring(_("Error reading file attributes:")) + wxT("\n\"") + sourceFile + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - } - - if (S_ISLNK(linkInfo.st_mode)) - { - //copy symbolic link - const unsigned BUFFER_SIZE = 10000; - char buffer[BUFFER_SIZE]; - const int bytesWritten = readlink(sourceFile.c_str(), buffer, BUFFER_SIZE - 1); - if (bytesWritten < 0) - { - Zstring errorMessage = Zstring(_("Error resolving symbolic link:")) + wxT("\n\"") + sourceFile + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - } - //set null-terminating char - buffer[bytesWritten] = 0; - - if (symlink(buffer, targetFile.c_str()) != 0) - { - Zstring errorMessage = Zstring(_("Error writing file:")) + wxT("\n\"") + targetFile + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - } - - return; //symlink created successfully - } - } - - //begin of regular file copy - struct stat fileInfo; - if (stat(sourceFile.c_str(), &fileInfo) != 0) //read file attributes from source file (resolve symlinks) - { - Zstring errorMessage = Zstring(_("Error reading file attributes:")) + wxT("\n\"") + sourceFile + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - } - - //open sourceFile for reading - std::ifstream fileIn(sourceFile.c_str(), std::ios_base::binary); - if (fileIn.fail()) - throw FileError(Zstring(_("Error opening file:")) + wxT("\n\"") + sourceFile + wxT("\"")); - - //create targetFile and open it for writing - std::ofstream fileOut(targetFile.c_str(), std::ios_base::binary); - if (fileOut.fail()) - throw FileError(Zstring(_("Error opening file:")) + wxT("\n\"") + targetFile + wxT("\"")); - - //copy contents of sourceFile to targetFile - wxULongLong totalBytesTransferred; - static MemoryAllocator memory; - while (true) - { - fileIn.read(memory.buffer, memory.bufferSize); - if (fileIn.eof()) //end of file? fail bit is set in this case also! - { - fileOut.write(memory.buffer, fileIn.gcount()); - if (fileOut.bad()) - throw FileError(Zstring(_("Error writing file:")) + wxT("\n\"") + targetFile + wxT("\"")); - break; - } - else if (fileIn.fail()) - throw FileError(Zstring(_("Error reading file:")) + wxT("\n\"") + sourceFile + wxT("\"")); - - - fileOut.write(memory.buffer, memory.bufferSize); - if (fileOut.bad()) - throw FileError(Zstring(_("Error writing file:")) + wxT("\n\"") + targetFile + wxT("\"")); - - totalBytesTransferred += memory.bufferSize; - - //invoke callback method to update progress indicators - if (callback != NULL) - { - switch (callback->updateCopyStatus(totalBytesTransferred)) - { - case CopyFileCallback::CONTINUE: - break; - case CopyFileCallback::CANCEL: - fileOut.close(); - wxRemoveFile(targetFile.c_str()); //don't handle error situations! - return; - } - } - } - - //close streams before changing attributes - fileIn.close(); - fileOut.close(); - - //adapt file modification time: - struct utimbuf newTimes; - time(&newTimes.actime); //set file access time to current time - newTimes.modtime = fileInfo.st_mtime; - if (utime(targetFile.c_str(), &newTimes) != 0) - { - Zstring errorMessage = Zstring(_("Error changing modification time:")) + wxT("\n\"") + targetFile + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - } - - //set file access rights - if (chmod(targetFile.c_str(), fileInfo.st_mode) != 0) - { - Zstring errorMessage = Zstring(_("Error writing file attributes:")) + wxT("\n\"") + targetFile + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - } - } - catch (...) - { //try to delete target file if error occured, or exception was thrown in callback function - if (FreeFileSync::fileExists(targetFile.c_str())) - wxRemoveFile(targetFile.c_str()); //don't handle error situations! - - throw; - } -} -#endif - - -#ifdef FFS_WIN -class CloseFindHandleOnExit -{ -public: - CloseFindHandleOnExit(HANDLE searchHandle) : searchHandle_(searchHandle) {} - - ~CloseFindHandleOnExit() - { - FindClose(searchHandle_); - } - -private: - HANDLE searchHandle_; -}; - - -inline -void setWin32FileInformation(const FILETIME& lastWriteTime, const DWORD fileSizeHigh, const DWORD fileSizeLow, FreeFileSync::FileInfo& output) -{ - //convert UTC FILETIME to ANSI C format (number of seconds since Jan. 1st 1970 UTC) - wxLongLong writeTimeLong(wxInt32(lastWriteTime.dwHighDateTime), lastWriteTime.dwLowDateTime); - writeTimeLong /= 10000000; //reduce precision to 1 second (FILETIME has unit 10^-7 s) - writeTimeLong -= wxLongLong(2, 3054539008UL); //timeshift between ansi C time and FILETIME in seconds == 11644473600s - output.lastWriteTimeRaw = writeTimeLong; - - output.fileSize = wxULongLong(fileSizeHigh, fileSizeLow); -} - - -inline -bool setWin32FileInformationFromSymlink(const Zstring linkName, FreeFileSync::FileInfo& output) -{ - //open handle to target of symbolic link - HANDLE hFile = CreateFile(linkName.c_str(), - 0, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - NULL, - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, - NULL); - if (hFile == INVALID_HANDLE_VALUE) - return false; - - CloseHandleOnExit dummy(hFile); - - BY_HANDLE_FILE_INFORMATION fileInfoByHandle; - - if (!GetFileInformationByHandle( - hFile, - &fileInfoByHandle)) - return false; - - //write output - setWin32FileInformation(fileInfoByHandle.ftLastWriteTime, fileInfoByHandle.nFileSizeHigh, fileInfoByHandle.nFileSizeLow, output); - - return true; -} - - -#elif defined FFS_LINUX -class CloseDirOnExit -{ -public: - CloseDirOnExit(DIR* dir) : m_dir(dir) {} - - ~CloseDirOnExit() - { - closedir(m_dir); //no error handling here - } - -private: - DIR* m_dir; -}; -#endif - - -template <bool traverseDirectorySymlinks> -class TraverseRecursively -{ -public: - TraverseRecursively(FreeFileSync::FullDetailFileTraverser* sink) : m_sink(sink) {} - - bool traverse(const Zstring& directory, const int level) - { - if (level == 100) //catch endless recursion - { - if (m_sink->OnError(Zstring(_("Error traversing directory:")) + wxT("\n\"") + directory + wxT("\"")) == wxDIR_STOP) - return false; - else - return true; - } - -#ifdef FFS_WIN - //ensure directoryFormatted ends with backslash - const Zstring directoryFormatted = FreeFileSync::endsWithPathSeparator(directory) ? - directory : - directory + FreeFileSync::FILE_NAME_SEPARATOR; - - WIN32_FIND_DATA fileMetaData; - HANDLE searchHandle = FindFirstFile((directoryFormatted + DefaultChar('*')).c_str(), //pointer to name of file to search for - &fileMetaData); //pointer to returned information - - if (searchHandle == INVALID_HANDLE_VALUE) - { - const DWORD lastError = GetLastError(); - if (lastError == ERROR_FILE_NOT_FOUND) - return true; - - //else: we have a problem... report it: - Zstring errorMessage = Zstring(_("Error traversing directory:")) + wxT("\n\"") + directory + wxT("\"") ; - if (m_sink->OnError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted(lastError)) == wxDIR_STOP) - return false; - else - return true; - } - CloseFindHandleOnExit dummy(searchHandle); - - do - { //don't return "." and ".." - const wxChar* const name = fileMetaData.cFileName; - if ( name[0] == wxChar('.') && - ((name[1] == wxChar('.') && name[2] == wxChar('\0')) || - name[1] == wxChar('\0'))) - continue; - - const Zstring fullName = directoryFormatted + name; - - if (fileMetaData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) //a directory... (for directory symlinks this flag is set too!) - { - switch (m_sink->OnDir(fullName)) - { - case wxDIR_IGNORE: - break; - case wxDIR_CONTINUE: - //traverse into symbolic links, junctions, etc. if requested only: - if (traverseDirectorySymlinks || (~fileMetaData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) - if (!this->traverse(fullName, level + 1)) - return false; - break; - case wxDIR_STOP: - return false; - default: - assert(false); - } - } - else //a file... - { - FreeFileSync::FileInfo details; - - if (fileMetaData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) //dereference symlinks! - { - if (!setWin32FileInformationFromSymlink(fullName, details)) //broken symlink - { - details.lastWriteTimeRaw = 0; //we are not interested in the modifiation time of the link - details.fileSize = 0; - } - } - else - setWin32FileInformation(fileMetaData.ftLastWriteTime, fileMetaData.nFileSizeHigh, fileMetaData.nFileSizeLow, details); - - if (m_sink->OnFile(fullName, details) == wxDIR_STOP) - return false; - } - } - while (FindNextFile(searchHandle, // handle to search - &fileMetaData)); // pointer to structure for data on found file - - const DWORD lastError = GetLastError(); - if (lastError == ERROR_NO_MORE_FILES) - return true; //everything okay - - //else: we have a problem... report it: - Zstring errorMessage = Zstring(_("Error traversing directory:")) + wxT("\n\"") + directory + wxT("\"") ; - if (m_sink->OnError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted(lastError)) == wxDIR_STOP) - return false; - else - return true; - -#elif defined FFS_LINUX - DIR* dirObj = opendir(directory.c_str()); - if (dirObj == NULL) - { - Zstring errorMessage = Zstring(_("Error traversing directory:")) + wxT("\n\"") + directory+ wxT("\"") ; - if (m_sink->OnError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()) == wxDIR_STOP) - return false; - else - return true; - } - CloseDirOnExit dummy(dirObj); - - struct dirent* dirEntry; - while (!(errno = 0) && (dirEntry = readdir(dirObj)) != NULL) //set errno to 0 as unfortunately this isn't done when readdir() returns NULL when it is finished - { - //don't return "." and ".." - const wxChar* const name = dirEntry->d_name; - if ( name[0] == wxChar('.') && - ((name[1] == wxChar('.') && name[2] == wxChar('\0')) || - name[1] == wxChar('\0'))) - continue; - - const Zstring fullName = FreeFileSync::endsWithPathSeparator(directory) ? //e.g. "/" - directory + name : - directory + FreeFileSync::FILE_NAME_SEPARATOR + name; - - struct stat fileInfo; - if (lstat(fullName.c_str(), &fileInfo) != 0) //lstat() does not resolve symlinks - { - const Zstring errorMessage = Zstring(_("Error reading file attributes:")) + wxT("\n\"") + fullName + wxT("\""); - if (m_sink->OnError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()) == wxDIR_STOP) - return false; - continue; - } - - const bool isSymbolicLink = S_ISLNK(fileInfo.st_mode); - if (isSymbolicLink) //dereference symbolic links - { - if (stat(fullName.c_str(), &fileInfo) != 0) //stat() resolves symlinks - { - //a broken symbolic link - FreeFileSync::FileInfo details; - details.lastWriteTimeRaw = 0; //we are not interested in the modifiation time of the link - details.fileSize = 0; - - if (m_sink->OnFile(fullName, details) == wxDIR_STOP) - return false; - continue; - } - } - - - if (S_ISDIR(fileInfo.st_mode)) //a directory... (note: symbolic links need to be dereferenced to test if they point to a directory!) - { - switch (m_sink->OnDir(fullName)) - { - case wxDIR_IGNORE: - break; - case wxDIR_CONTINUE: - if (traverseDirectorySymlinks || !isSymbolicLink) //traverse into symbolic links if requested only - { - if (!this->traverse(fullName, level + 1)) - return false; - } - break; - case wxDIR_STOP: - return false; - default: - assert(false); - } - } - else //a file... - { - FreeFileSync::FileInfo details; - details.lastWriteTimeRaw = fileInfo.st_mtime; //UTC time(ANSI C format); unit: 1 second - details.fileSize = fileInfo.st_size; - - if (m_sink->OnFile(fullName, details) == wxDIR_STOP) - return false; - } - } - - if (errno == 0) - return true; //everything okay - - //else: we have a problem... report it: - const Zstring errorMessage = Zstring(_("Error traversing directory:")) + wxT("\n\"") + directory + wxT("\"") ; - if (m_sink->OnError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()) == wxDIR_STOP) - return false; - else - return true; -#endif - } - -private: - FreeFileSync::FullDetailFileTraverser* m_sink; -}; - - -void FreeFileSync::traverseInDetail(const Zstring& directory, - const bool traverseDirectorySymlinks, - FullDetailFileTraverser* sink) -{ - Zstring directoryFormatted = directory; -#ifdef FFS_LINUX //remove trailing slash - if (directoryFormatted.size() > 1 && FreeFileSync::endsWithPathSeparator(directoryFormatted)) - directoryFormatted = directoryFormatted.BeforeLast(FreeFileSync::FILE_NAME_SEPARATOR); -#endif - - if (traverseDirectorySymlinks) - { - TraverseRecursively<true> filewalker(sink); - filewalker.traverse(directoryFormatted, 0); - } - else - { - TraverseRecursively<false> filewalker(sink); - filewalker.traverse(directoryFormatted, 0); - } -} - - -/* -#ifdef FFS_WIN -inline -Zstring getDriveName(const Zstring& directoryName) //GetVolume() doesn't work under Linux! -{ - const Zstring volumeName = wxFileName(directoryName.c_str()).GetVolume().c_str(); - if (volumeName.empty()) - return Zstring(); - - return volumeName + wxFileName::GetVolumeSeparator().c_str() + FreeFileSync::FILE_NAME_SEPARATOR; -} - - -bool FreeFileSync::isFatDrive(const Zstring& directoryName) -{ - const Zstring driveName = getDriveName(directoryName); - if (driveName.empty()) - return false; - - wxChar fileSystem[32]; - if (!GetVolumeInformation(driveName.c_str(), NULL, 0, NULL, NULL, NULL, fileSystem, 32)) - return false; - - return Zstring(fileSystem).StartsWith(wxT("FAT")); -} -#endif //FFS_WIN -*/ diff --git a/library/fileHandling.h b/library/fileHandling.h deleted file mode 100644 index 3abdbe07..00000000 --- a/library/fileHandling.h +++ /dev/null @@ -1,71 +0,0 @@ -#ifndef RECYCLER_H_INCLUDED -#define RECYCLER_H_INCLUDED - -#include <wx/dir.h> -#include "zstring.h" -#include "fileError.h" - - -namespace FreeFileSync -{ - struct FileInfo - { - wxULongLong fileSize; //unit: bytes! - wxLongLong lastWriteTimeRaw; //number of seconds since Jan. 1st 1970 UTC - }; - - //traverser interface - class FullDetailFileTraverser - { - public: - virtual ~FullDetailFileTraverser() {} - virtual wxDirTraverseResult OnFile(const Zstring& filename, const FileInfo& details) = 0; - virtual wxDirTraverseResult OnDir(const Zstring& dirname) = 0; - virtual wxDirTraverseResult OnError(const Zstring& errorText) = 0; - }; - - //custom traverser with detail information about files - void traverseInDetail(const Zstring& directory, const bool traverseDirectorySymlinks, FullDetailFileTraverser* sink); - - bool fileExists(const Zstring& filename); //replaces wxFileExists()! - - //recycler - bool recycleBinExists(); //test existence of Recycle Bin API on current system - - //file handling - void removeDirectory(const Zstring& directory, const bool useRecycleBin); - void removeFile(const Zstring& filename, const bool useRecycleBin); - void createDirectory(const Zstring& directory, const Zstring& templateDir, const bool copyDirectorySymLinks); - - - class CopyFileCallback //callback functionality - { - public: - virtual ~CopyFileCallback() {} - - enum Response - { - CONTINUE, - CANCEL - }; - virtual Response updateCopyStatus(const wxULongLong& totalBytesTransferred) = 0; - }; - - class ShadowCopy; -#ifdef FFS_WIN - void copyFile(const Zstring& sourceFile, - const Zstring& targetFile, - const bool copyFileSymLinks, - ShadowCopy* shadowCopyHandler = NULL, //supply handler for making shadow copies - CopyFileCallback* callback = NULL); - -#elif defined FFS_LINUX - void copyFile(const Zstring& sourceFile, - const Zstring& targetFile, - const bool copyFileSymLinks, - CopyFileCallback* callback); -#endif -} - - -#endif // RECYCLER_H_INCLUDED diff --git a/library/filter.cpp b/library/filter.cpp index bd281c7c..30cd9341 100644 --- a/library/filter.cpp +++ b/library/filter.cpp @@ -3,113 +3,123 @@ #include <wx/string.h> #include <set> #include <vector> -#include "../shared/globalFunctions.h" +#include "../shared/systemConstants.h" #include "../structures.h" +#include <boost/bind.hpp> using FreeFileSync::FilterProcess; -void compoundStringToTable(const Zstring& compoundInput, const DefaultChar* delimiter, std::vector<Zstring>& output) +inline +void addFilterEntry(const Zstring& filtername, std::set<Zstring>& fileFilter, std::set<Zstring>& directoryFilter) { - output.clear(); - Zstring input(compoundInput); + Zstring filterFormatted = filtername; + +#ifdef FFS_WIN + //Windows does NOT distinguish between upper/lower-case + filterFormatted.MakeLower(); +#elif defined FFS_LINUX + //Linux DOES distinguish between upper/lower-case: nothing to do here +#endif - //make sure input ends with delimiter - no problem with empty strings here - if (!input.EndsWith(delimiter)) - input += delimiter; + static const Zstring sepAsterisk = Zstring() + globalFunctions::FILE_NAME_SEPARATOR + wxT('*'); + static const Zstring sepQuestionMark = Zstring() + globalFunctions::FILE_NAME_SEPARATOR + wxT('?'); + static const Zstring asteriskSep = Zstring(wxT("*")) + globalFunctions::FILE_NAME_SEPARATOR; + static const Zstring questionMarkSep = Zstring(wxT("?")) + globalFunctions::FILE_NAME_SEPARATOR; - unsigned int indexStart = 0; - unsigned int indexEnd = 0; - while ((indexEnd = input.find(delimiter, indexStart)) != Zstring::npos) + //add some syntactic sugar: handle beginning of filtername + if (filterFormatted.StartsWith(globalFunctions::FILE_NAME_SEPARATOR)) + { //remove leading separators (keep BEFORE test for Zstring::empty()!) + filterFormatted = filterFormatted.AfterFirst(globalFunctions::FILE_NAME_SEPARATOR); + } + else if (filterFormatted.StartsWith(asteriskSep) || // *\abc + filterFormatted.StartsWith(questionMarkSep)) // ?\abc { - if (indexStart != indexEnd) //do not add empty strings - { - Zstring newEntry = input.substr(indexStart, indexEnd - indexStart); + addFilterEntry(Zstring(filterFormatted.c_str() + 1), fileFilter, directoryFilter); //prevent further recursion by prefix + } - newEntry.Trim(true); //remove whitespace characters from right - newEntry.Trim(false); //remove whitespace characters from left - if (!newEntry.empty()) - output.push_back(newEntry); - } - indexStart = indexEnd + 1; + //even more syntactic sugar: handle end of filtername + if (filterFormatted.EndsWith(globalFunctions::FILE_NAME_SEPARATOR)) + { + const Zstring candidate = filterFormatted.BeforeLast(globalFunctions::FILE_NAME_SEPARATOR); + if (!candidate.empty()) + directoryFilter.insert(candidate); //only relevant for directory filtering + } + else if (filterFormatted.EndsWith(sepAsterisk) || // abc\* + filterFormatted.EndsWith(sepQuestionMark)) // abc\? + { + fileFilter.insert( filterFormatted); + directoryFilter.insert(filterFormatted); + + const Zstring candidate = filterFormatted.BeforeLast(globalFunctions::FILE_NAME_SEPARATOR); + if (!candidate.empty()) + directoryFilter.insert(candidate); //only relevant for directory filtering + } + else if (!filterFormatted.empty()) + { + fileFilter. insert(filterFormatted); + directoryFilter.insert(filterFormatted); } } -std::vector<Zstring> compoundStringToFilter(const Zstring& filterString) +class MatchFound : public std::unary_function<const DefaultChar*, bool> { - //delimiters may be ';' or '\n' - std::vector<Zstring> filterList; - std::vector<Zstring> filterPreProcessing; - compoundStringToTable(filterString, wxT(";"), filterPreProcessing); +public: + MatchFound(const DefaultChar* name) : name_(name) {} - for (std::vector<Zstring>::const_iterator i = filterPreProcessing.begin(); i != filterPreProcessing.end(); ++i) + bool operator()(const DefaultChar* mask) const { - std::vector<Zstring> newEntries; - compoundStringToTable(*i, wxT("\n"), newEntries); - - filterList.insert(filterList.end(), newEntries.begin(), newEntries.end()); + return Zstring::Matches(name_, mask); } - - return filterList; -} +private: + const DefaultChar* name_; +}; inline -void addFilterEntry(const Zstring& filtername, std::set<Zstring>& fileFilter, std::set<Zstring>& directoryFilter) +bool matchesFilter(const DefaultChar* name, const std::set<Zstring>& filter) { - Zstring filterFormatted = filtername; - -#ifdef FFS_WIN - //Windows does NOT distinguish between upper/lower-case - filterFormatted.MakeLower(); -#elif defined FFS_LINUX - //Linux DOES distinguish between upper/lower-case -//nothing to do here +#ifdef FFS_WIN //Windows does NOT distinguish between upper/lower-case + Zstring nameFormatted = name; + nameFormatted.MakeLower(); +#elif defined FFS_LINUX //Linux DOES distinguish between upper/lower-case + const DefaultChar* const nameFormatted = name; //nothing to do here #endif - //remove leading separators (keep BEFORE test for Zstring::empty()!) - if (filterFormatted.length() > 0 && *filterFormatted.c_str() == globalFunctions::FILE_NAME_SEPARATOR) - filterFormatted = Zstring(filterFormatted.c_str() + 1); + return std::find_if(filter.begin(), filter.end(), MatchFound(nameFormatted)) != filter.end(); +} - if (!filterFormatted.empty()) + +//returns true if string matches at least the beginning of mask +inline +bool matchesMaskBegin(const DefaultChar* string, const DefaultChar* mask) +{ + for (DefaultChar ch; (ch = *mask) != 0; ++mask, ++string) { - //Test if filterFormatted ends with globalFunctions::FILE_NAME_SEPARATOR, ignoring '*' and '?'. - //If so, treat as filter for directory and add to directoryFilter. - const DefaultChar* filter = filterFormatted.c_str(); - int i = filterFormatted.length() - 1; - while (filter[i] == DefaultChar('*') || filter[i] == DefaultChar('?')) + if (*string == 0) + return true; + + switch (ch) { - --i; + case DefaultChar('?'): + break; - if (i == -1) - break; - } + case DefaultChar('*'): + return true; - if (i >= 0 && filter[i] == globalFunctions::FILE_NAME_SEPARATOR) //last FILE_NAME_SEPARATOR found - { - if (i != int(filterFormatted.length()) - 1) // "name\*" - { - fileFilter.insert(filterFormatted); - directoryFilter.insert(filterFormatted); - } - //else // "name\" - - if (i > 0) // "name\*" or "name\": add "name" to directory filter - directoryFilter.insert(Zstring(filterFormatted.c_str(), i)); - } - else - { - fileFilter.insert(filterFormatted); - directoryFilter.insert(filterFormatted); + default: + if (*string != ch) + return false; } } + return *string == 0; } inline -bool matchesFilter(const DefaultChar* name, const std::set<Zstring>& filter) +bool matchesFilterBegin(const DefaultChar* name, const std::set<Zstring>& filter) { #ifdef FFS_WIN //Windows does NOT distinguish between upper/lower-case Zstring nameFormatted = name; @@ -118,13 +128,25 @@ bool matchesFilter(const DefaultChar* name, const std::set<Zstring>& filter) const DefaultChar* const nameFormatted = name; //nothing to do here #endif - for (std::set<Zstring>::const_iterator j = filter.begin(); j != filter.end(); ++j) - if (Zstring::Matches(nameFormatted, j->c_str())) - return true; - - return false; + return std::find_if(filter.begin(), filter.end(), + boost::bind(matchesMaskBegin, nameFormatted, _1)) != filter.end(); } + +std::vector<Zstring> compoundStringToFilter(const Zstring& filterString) +{ + //delimiters may be ';' or '\n' + std::vector<Zstring> output; + + const std::vector<Zstring> filterPreProcessing = filterString.Tokenize(wxT(';')); + for (std::vector<Zstring>::const_iterator i = filterPreProcessing.begin(); i != filterPreProcessing.end(); ++i) + { + const std::vector<Zstring> newEntries = i->Tokenize(wxT('\n')); + output.insert(output.end(), newEntries.begin(), newEntries.end()); + } + + return output; +} //############################################################## @@ -138,102 +160,173 @@ FilterProcess::FilterProcess(const wxString& includeFilter, const wxString& excl const std::vector<Zstring> excludeList = compoundStringToFilter(excludeFilter.c_str()); //setup include/exclude filters for files and directories - for (std::vector<Zstring>::const_iterator i = includeList.begin(); i != includeList.end(); ++i) - addFilterEntry(*i, filterFileIn, filterFolderIn); - - for (std::vector<Zstring>::const_iterator i = excludeList.begin(); i != excludeList.end(); ++i) - addFilterEntry(*i, filterFileEx, filterFolderEx); + std::for_each(includeList.begin(), includeList.end(), boost::bind(addFilterEntry, _1, boost::ref(filterFileIn), boost::ref(filterFolderIn))); + std::for_each(excludeList.begin(), excludeList.end(), boost::bind(addFilterEntry, _1, boost::ref(filterFileEx), boost::ref(filterFolderEx))); } -bool FilterProcess::matchesFileFilterIncl(const DefaultChar* relFilename) const +bool FilterProcess::passFileFilter(const DefaultChar* relFilename) const { - return matchesFilter(relFilename, filterFileIn); //process include filters + return matchesFilter(relFilename, filterFileIn) && //process include filters + !matchesFilter(relFilename, filterFileEx); //process exclude filters } -bool FilterProcess::matchesFileFilterExcl(const DefaultChar* relFilename) const +bool FilterProcess::passDirFilter(const DefaultChar* relDirname, bool* subObjMightMatch) const { - return matchesFilter(relFilename, filterFileEx); //process exclude filters + if (matchesFilter(relDirname, filterFolderEx)) //process exclude filters + { + if (subObjMightMatch) + *subObjMightMatch = false; //exclude subfolders/subfiles as well + return false; + } + + if (!matchesFilter(relDirname, filterFolderIn)) //process include filters + { + if (subObjMightMatch) + { + Zstring subNameBegin(relDirname); + subNameBegin += globalFunctions::FILE_NAME_SEPARATOR; + + *subObjMightMatch = matchesFilterBegin(subNameBegin, filterFileIn) || //might match a file in subdirectory + matchesFilterBegin(subNameBegin, filterFolderIn); //or another subdirectory + } + return false; + } + + assert(subObjMightMatch == NULL || *subObjMightMatch == true); + return true; } -bool FilterProcess::matchesDirFilterIncl(const DefaultChar* relDirname) const +class FilterData { - return matchesFilter(relDirname, filterFolderIn); //process include filters -} +public: + FilterData(const FilterProcess& filterProcIn) : filterProc(filterProcIn) {} + + void operator()(FreeFileSync::FileMapping& fileObj) + { + const Zstring relName = fileObj.isEmpty<FreeFileSync::LEFT_SIDE>() ? + fileObj.getRelativeName<FreeFileSync::RIGHT_SIDE>() : + fileObj.getRelativeName<FreeFileSync::LEFT_SIDE>(); + + fileObj.selectedForSynchronization = filterProc.passFileFilter(relName); + } + + void operator()(FreeFileSync::DirMapping& dirObj) + { + const Zstring relName = dirObj.isEmpty<FreeFileSync::LEFT_SIDE>() ? + dirObj.getRelativeName<FreeFileSync::RIGHT_SIDE>() : + dirObj.getRelativeName<FreeFileSync::LEFT_SIDE>(); + + bool subObjMightMatch = true; + dirObj.selectedForSynchronization = filterProc.passDirFilter(relName, &subObjMightMatch); + + if (subObjMightMatch) //use same logic like directory traversing here: evaluate filter in subdirs only if objects could match + filterProc.filterAll(dirObj); //process sub-dirs/files + else + FilterProcess::setActiveStatus(false, dirObj); //exclude all files dirs in subfolders + } +private: + const FilterProcess& filterProc; +}; -bool FilterProcess::matchesDirFilterExcl(const DefaultChar* relDirname) const + +void FilterProcess::filterAll(FreeFileSync::HierarchyObject& baseDirectory) const { - return matchesFilter(relDirname, filterFolderEx); //process exclude filters + //files + std::for_each(baseDirectory.subFiles.begin(), baseDirectory.subFiles.end(), FilterData(*this)); + + //directories + std::for_each(baseDirectory.subDirs.begin(), baseDirectory.subDirs.end(), FilterData(*this)); + + //recursion happens in FilterData } -void FilterProcess::filterGridData(FreeFileSync::FolderComparison& folderCmp) const +template <bool include> +struct SetSelected { - //execute filtering... - for (FolderComparison::iterator j = folderCmp.begin(); j != folderCmp.end(); ++j) + void operator()(FreeFileSync::FileSystemObject& fsObj) const { - FileComparison& fileCmp = j->fileCmp; + fsObj.selectedForSynchronization = include; + } +}; - for (FileComparison::iterator i = fileCmp.begin(); i != fileCmp.end(); ++i) - { - const FileDescrLine& fileDescr = i->fileDescrLeft.objType != FileDescrLine::TYPE_NOTHING ? - i->fileDescrLeft : - i->fileDescrRight; - - if (fileDescr.objType == FileDescrLine::TYPE_FILE) - { - if ( !matchesFileFilterIncl(fileDescr.relativeName.c_str()) || - matchesFileFilterExcl(fileDescr.relativeName.c_str())) - { - i->selectedForSynchronization = false; - continue; - } - } - else if (fileDescr.objType == FileDescrLine::TYPE_DIRECTORY) - { - if ( !matchesDirFilterIncl(fileDescr.relativeName.c_str()) || - matchesDirFilterExcl(fileDescr.relativeName.c_str())) - { - i->selectedForSynchronization = false; - continue; - } - } - else - assert(false); - - i->selectedForSynchronization = true; - } - } +template <bool include> +void inOrExcludeAllRows(FreeFileSync::HierarchyObject& hierObj) +{ + //directories + std::for_each(hierObj.subDirs.begin(), hierObj.subDirs.end(), SetSelected<include>()); + //files + std::for_each(hierObj.subFiles.begin(), hierObj.subFiles.end(), SetSelected<include>()); + //recurse into sub-dirs + std::for_each(hierObj.subDirs.begin(), hierObj.subDirs.end(), inOrExcludeAllRows<include>); } -template <bool includeRows> -inline -void inOrExcludeAllRows(FreeFileSync::FolderComparison& folderCmp) +void FilterProcess::setActiveStatus(bool newStatus, FreeFileSync::FolderComparison& folderCmp) { - //remove all filters on folderCmp - for (FreeFileSync::FolderComparison::iterator j = folderCmp.begin(); j != folderCmp.end(); ++j) + if (newStatus) + std::for_each(folderCmp.begin(), folderCmp.end(), inOrExcludeAllRows<true>); //include all rows + else + std::for_each(folderCmp.begin(), folderCmp.end(), inOrExcludeAllRows<false>); //exclude all rows +} + + +void FilterProcess::setActiveStatus(bool newStatus, FreeFileSync::FileSystemObject& fsObj) +{ + fsObj.selectedForSynchronization = newStatus; + + DirMapping* dirObj = dynamic_cast<DirMapping*>(&fsObj); + if (dirObj) //process subdirectories also! { - FreeFileSync::FileComparison& fileCmp = j->fileCmp; - for (FreeFileSync::FileComparison::iterator i = fileCmp.begin(); i != fileCmp.end(); ++i) - i->selectedForSynchronization = includeRows; + if (newStatus) + inOrExcludeAllRows<true>(*dirObj); + else + inOrExcludeAllRows<false>(*dirObj); } } -void FilterProcess::includeAllRowsOnGrid(FreeFileSync::FolderComparison& folderCmp) +const FilterProcess& FilterProcess::nullFilter() //filter equivalent to include '*', exclude '' +{ + static FilterProcess output(wxT("*"), wxEmptyString); + return output; +} + + +bool FilterProcess::operator==(const FilterProcess& other) const +{ + return filterFileIn == other.filterFileIn && + filterFolderIn == other.filterFolderIn && + filterFileEx == other.filterFileEx && + filterFolderEx == other.filterFolderEx; +} + + +bool FilterProcess::operator!=(const FilterProcess& other) const { - //remove all filters on currentGridData - inOrExcludeAllRows<true>(folderCmp); + return !(*this == other); } -void FilterProcess::excludeAllRowsOnGrid(FreeFileSync::FolderComparison& folderCmp) +bool FilterProcess::operator<(const FilterProcess& other) const { - //exclude all rows on currentGridData - inOrExcludeAllRows<false>(folderCmp); + if (filterFileIn != other.filterFileIn) + return filterFileIn < other.filterFileIn; + + if (filterFolderIn != other.filterFolderIn) + return filterFolderIn < other.filterFolderIn; + + if (filterFileEx != other.filterFileEx) + return filterFileEx < other.filterFileEx; + + if (filterFolderEx != other.filterFolderEx) + return filterFolderEx < other.filterFolderEx; + + return false; //vectors equal } + diff --git a/library/filter.h b/library/filter.h index 53e4d9b1..0adf81cd 100644 --- a/library/filter.h +++ b/library/filter.h @@ -4,7 +4,7 @@ #include <wx/string.h> #include "../shared/zstring.h" #include <set> -#include "../structures.h" +#include "../fileHierarchy.h" namespace FreeFileSync @@ -14,15 +14,18 @@ namespace FreeFileSync public: FilterProcess(const wxString& includeFilter, const wxString& excludeFilter); - bool matchesFileFilterIncl(const DefaultChar* relFilename) const; - bool matchesFileFilterExcl(const DefaultChar* relFilename) const; - bool matchesDirFilterIncl(const DefaultChar* relDirname) const; - bool matchesDirFilterExcl(const DefaultChar* relDirname) const; + bool passFileFilter(const DefaultChar* relFilename) const; + bool passDirFilter(const DefaultChar* relDirname, bool* subObjMightMatch) const; //subObjMightMatch: file/dir in subdirectories could(!) match + //note: variable is only set if passDirFilter returns false! + void filterAll(HierarchyObject& baseDirectory) const; //filter complete data: files and dirs - void filterGridData(FolderComparison& folderCmp) const; + static void setActiveStatus(bool newStatus, FolderComparison& folderCmp); //activate or deactivate all rows + static void setActiveStatus(bool newStatus, FileSystemObject& fsObj); //activate or deactivate row - static void includeAllRowsOnGrid(FolderComparison& folderCmp); - static void excludeAllRowsOnGrid(FolderComparison& folderCmp); + static const FilterProcess& nullFilter(); //filter equivalent to include '*', exclude '' + bool operator==(const FilterProcess& other) const; + bool operator!=(const FilterProcess& other) const; + bool operator<(const FilterProcess& other) const; private: std::set<Zstring> filterFileIn; diff --git a/library/globalFunctions.cpp b/library/globalFunctions.cpp deleted file mode 100644 index 2f5efab7..00000000 --- a/library/globalFunctions.cpp +++ /dev/null @@ -1,180 +0,0 @@ -#include "globalFunctions.h" -#include <wx/msgdlg.h> -#include <wx/file.h> -#include <fstream> -#include "../structures.h" -#include <wx/stream.h> -#include <wx/stopwatch.h> - - -wxString globalFunctions::numberToWxString(const unsigned int number) -{ - return wxString::Format(wxT("%u"), number); -} - - -wxString globalFunctions::numberToWxString(const int number) -{ - return wxString::Format(wxT("%i"), number); -} - - -wxString globalFunctions::numberToWxString(const float number) -{ - return wxString::Format(wxT("%f"), number); -} - - -int globalFunctions::stringToInt(const std::string& number) -{ - return atoi(number.c_str()); -} - - -long globalFunctions::stringToLong(const std::string& number) -{ - return atol(number.c_str()); -} - - -double globalFunctions::stringToDouble(const std::string& number) -{ - return atof(number.c_str()); -} - - -int globalFunctions::wxStringToInt(const wxString& number) -{ - long result = 0; - if (number.ToLong(&result)) - return result; - else - return 0; //don't throw exceptions here: wxEmptyString shall be interpreted as 0 - //throw RuntimeException(wxString(_("Conversion error:")) + wxT(" wxString -> long")); -} - - -double globalFunctions::wxStringToDouble(const wxString& number) -{ - double result = 0; - if (number.ToDouble(&result)) - return result; - else - return 0; //don't throw exceptions here: wxEmptyString shall be interpreted as 0 - //throw RuntimeException(wxString(_("Conversion error:")) + wxT(" wxString -> double")); -} - - -wxString globalFunctions::includeNumberSeparator(const wxString& number) -{ - wxString output(number); - for (int i = output.size() - 3; i > 0; i -= 3) - output.insert(i, FreeFileSync::THOUSANDS_SEPARATOR); - return output; -} - - -int globalFunctions::readInt(std::ifstream& stream) -{ - int result = 0; - char* buffer = reinterpret_cast<char*>(&result); - stream.read(buffer, sizeof(int)); - return result; -} - - -void globalFunctions::writeInt(std::ofstream& stream, const int number) -{ - const char* buffer = reinterpret_cast<const char*>(&number); - stream.write(buffer, sizeof(int)); -} - - -int globalFunctions::readInt(wxInputStream& stream) -{ - int result = 0; - char* buffer = reinterpret_cast<char*>(&result); - stream.Read(buffer, sizeof(int)); - return result; -} - - -void globalFunctions::writeInt(wxOutputStream& stream, const int number) -{ - const char* buffer = reinterpret_cast<const char*>(&number); - stream.Write(buffer, sizeof(int)); -} - - -//############################################################################ -Performance::Performance() : - resultWasShown(false), - timer(new wxStopWatch) -{ - timer->Start(); -} - - -Performance::~Performance() -{ - if (!resultWasShown) - showResult(); -} - - -void Performance::showResult() -{ - resultWasShown = true; - wxMessageBox(globalFunctions::numberToWxString(unsigned(timer->Time())) + wxT(" ms")); - timer->Start(); //reset timer -} - - -//############################################################################ -DebugLog::DebugLog() : - lineCount(0), - logFile(NULL) -{ - logFile = new wxFile; - logfileName = assembleFileName(); - logFile->Open(logfileName.c_str(), wxFile::write); -} - - -DebugLog::~DebugLog() -{ - delete logFile; //automatically closes file handle -} - - -wxString DebugLog::assembleFileName() -{ - wxString tmp = wxDateTime::Now().FormatISOTime(); - tmp.Replace(wxT(":"), wxEmptyString); - return wxString(wxT("DEBUG_")) + wxDateTime::Now().FormatISODate() + wxChar('_') + tmp + wxT(".log"); -} - - -void DebugLog::write(const wxString& logText) -{ - ++lineCount; - if (lineCount % 50000 == 0) //prevent logfile from becoming too big - { - logFile->Close(); - wxRemoveFile(logfileName); - - logfileName = assembleFileName(); - logFile->Open(logfileName.c_str(), wxFile::write); - } - - logFile->Write(wxString(wxT("[")) + wxDateTime::Now().FormatTime() + wxT("] ")); - logFile->Write(logText + wxChar('\n')); -} - -//DebugLog logDebugInfo; - - -wxString getCodeLocation(const wxString file, const int line) -{ - return wxString(file).AfterLast(FreeFileSync::FILE_NAME_SEPARATOR) + wxT(", LINE ") + globalFunctions::numberToWxString(line) + wxT(" | "); -} diff --git a/library/globalFunctions.h b/library/globalFunctions.h deleted file mode 100644 index 689fa5f1..00000000 --- a/library/globalFunctions.h +++ /dev/null @@ -1,162 +0,0 @@ -#ifndef GLOBALFUNCTIONS_H_INCLUDED -#define GLOBALFUNCTIONS_H_INCLUDED - -#include <string> -#include <algorithm> -#include <vector> -#include <set> -#include <wx/string.h> -#include <wx/longlong.h> -#include <memory> -#include <sstream> - -class wxInputStream; -class wxOutputStream; -class wxStopWatch; - - -namespace globalFunctions -{ - inline - int round(double d) //little rounding function - { - return static_cast<int>(d < 0 ? d - .5 : d + .5); - } - - template <class T> - inline - T abs(const T& d) //absolute value - { - return(d < 0 ? -d : d); - } - - template <class T> - inline std::string numberToString(const T& number) //convert number to string the C++ way - { - std::stringstream ss; - ss << number; - return ss.str(); - } - - wxString numberToWxString(const unsigned int number); //convert number to wxString - wxString numberToWxString(const int number); //convert number to wxString - wxString numberToWxString(const float number); //convert number to wxString - - int stringToInt( const std::string& number); //convert String to number - long stringToLong( const std::string& number); //convert String to number - double stringToDouble(const std::string& number); //convert String to number - - int wxStringToInt( const wxString& number); //convert wxString to number - double wxStringToDouble(const wxString& number); //convert wxString to number - - wxString includeNumberSeparator(const wxString& number); - - int readInt(std::ifstream& stream); //read int from file stream - void writeInt(std::ofstream& stream, const int number); //write int to filestream - - int readInt(wxInputStream& stream); //read int from file stream - void writeInt(wxOutputStream& stream, const int number); //write int to filestream - - inline - wxLongLong convertToSigned(const wxULongLong number) - { - return wxLongLong(number.GetHi(), number.GetLo()); - } - - - //Note: the following lines are a performance optimization for deleting elements from a vector. It is incredibly faster to create a new -//vector and leave specific elements out than to delete row by row and force recopying of most elements for each single deletion (linear vs quadratic runtime) - template <class T> - void removeRowsFromVector(const std::set<int>& rowsToRemove, std::vector<T>& grid) - { - if (rowsToRemove.size() > 0) - { - std::vector<T> temp; - - std::set<int>::const_iterator rowToSkipIndex = rowsToRemove.begin(); - int rowToSkip = *rowToSkipIndex; - - for (int i = 0; i < int(grid.size()); ++i) - { - if (i != rowToSkip) - temp.push_back(grid[i]); - else - { - ++rowToSkipIndex; - if (rowToSkipIndex != rowsToRemove.end()) - rowToSkip = *rowToSkipIndex; - } - } - grid.swap(temp); - } - } - - - template <class T> - inline - void mergeVectors(const std::vector<T>& input, std::vector<T>& changing) - { - for (typename std::vector<T>::const_iterator i = input.begin(); i != input.end(); ++i) //nested dependent typename: see Meyers effective C++ - changing.push_back(*i); - } -} - - -//############################################################################ -class Performance -{ -public: - wxDEPRECATED(Performance()); //generate compiler warnings as a reminder to remove code after measurements - ~Performance(); - void showResult(); - -private: - bool resultWasShown; - std::auto_ptr<wxStopWatch> timer; -}; - -//two macros for quick performance measurements -#define PERF_START Performance a; -#define PERF_STOP a.showResult(); - - -//############################################################################ -class wxFile; -class DebugLog -{ -public: - wxDEPRECATED(DebugLog()); - ~DebugLog(); - void write(const wxString& logText); - -private: - wxString assembleFileName(); - wxString logfileName; - int lineCount; - wxFile* logFile; //logFile.close(); <- not needed -}; -extern DebugLog logDebugInfo; -wxString getCodeLocation(const wxString file, const int line); - -//small macro for writing debug information into a logfile -#define WRITE_DEBUG_LOG(x) logDebugInfo.write(getCodeLocation(__TFILE__, __LINE__) + x); -//speed alternative: wxLogDebug(wxT("text")) + DebugView - - -//############################################################################ -class RuntimeException //Exception class used to notify of general runtime exceptions -{ -public: - RuntimeException(const wxString& txt) : errorMessage(txt) {} - - wxString show() const - { - return errorMessage; - } - -private: - wxString errorMessage; -}; - - -#endif // GLOBALFUNCTIONS_H_INCLUDED diff --git a/library/iconBuffer.cpp b/library/iconBuffer.cpp index f73164e0..cf21104e 100644 --- a/library/iconBuffer.cpp +++ b/library/iconBuffer.cpp @@ -1,12 +1,12 @@ #include "iconBuffer.h" #include <wx/thread.h> -#include "../shared/globalFunctions.h" #include <wx/bitmap.h> #include <wx/msw/wrapwin.h> //includes "windows.h" #include <wx/msgdlg.h> #include <wx/icon.h> #include <map> #include <queue> +#include <stdexcept> using FreeFileSync::IconBuffer; @@ -53,10 +53,10 @@ WorkerThread::WorkerThread(IconBuffer* iconBuff) : iconBuffer(iconBuff) { if (Create() != wxTHREAD_NO_ERROR) - throw RuntimeException(wxString(wxT("Error creating icon buffer worker thread!"))); + throw std::runtime_error("Error creating icon buffer worker thread!"); if (Run() != wxTHREAD_NO_ERROR) - throw RuntimeException(wxString(wxT("Error starting icon buffer worker thread!"))); + throw std::runtime_error("Error starting icon buffer worker thread!"); //wait until thread has aquired mutex bool hasMutex = false; @@ -115,9 +115,9 @@ wxThread::ExitCode WorkerThread::Entry() doWork(); } } - catch (RuntimeException& e) //exceptions must be catched per thread + catch (const std::exception& e) //exceptions must be catched per thread { - wxMessageBox(e.show()); + wxMessageBox(wxString::From8BitData(e.what()), _("An exception occured!"), wxOK | wxICON_ERROR); return 0; } } diff --git a/library/localization.cpp b/library/localization.cpp deleted file mode 100644 index 9b5918a8..00000000 --- a/library/localization.cpp +++ /dev/null @@ -1,275 +0,0 @@ -#include "localization.h" -#include <wx/msgdlg.h> -#include "../structures.h" -#include "globalFunctions.h" -#include <fstream> -#include <set> -#include <map> -#include "resources.h" - -using FreeFileSync::CustomLocale; -using FreeFileSync::LocalizationInfo; - -//_("Browse") <- dummy string for wxDirPickerCtrl to be recognized by automatic text extraction! - - -const std::vector<FreeFileSync::LocInfoLine>& LocalizationInfo::getMapping() -{ - static LocalizationInfo instance; - return instance.locMapping; -} - - -LocalizationInfo::LocalizationInfo() -{ - FreeFileSync::LocInfoLine newEntry; - - newEntry.languageID = wxLANGUAGE_GERMAN; - newEntry.languageName = wxT("Deutsch"); - newEntry.languageFile = "Languages/german.lng"; - newEntry.translatorName = wxT("ZenJu"); - newEntry.languageFlag = GlobalResources::getInstance().bitmapGermany; - locMapping.push_back(newEntry); - - newEntry.languageID = wxLANGUAGE_ENGLISH; - newEntry.languageName = wxT("English"); - newEntry.languageFile = ""; - newEntry.translatorName = wxT("ZenJu"); - newEntry.languageFlag = GlobalResources::getInstance().bitmapEngland; - locMapping.push_back(newEntry); - - newEntry.languageID = wxLANGUAGE_SPANISH; - newEntry.languageName = wxT("Español"); - newEntry.languageFile = "Languages/spanish.lng"; - newEntry.translatorName = wxT("David Rodríguez"); - newEntry.languageFlag = GlobalResources::getInstance().bitmapSpain; - locMapping.push_back(newEntry); - - newEntry.languageID = wxLANGUAGE_FRENCH; - newEntry.languageName = wxT("Français"); - newEntry.languageFile = "Languages/french.lng"; - newEntry.translatorName = wxT("Jean-François Hartmann"); - newEntry.languageFlag = GlobalResources::getInstance().bitmapFrance; - locMapping.push_back(newEntry); - - newEntry.languageID = wxLANGUAGE_ITALIAN; - newEntry.languageName = wxT("Italiano"); - newEntry.languageFile = "Languages/italian.lng"; - newEntry.translatorName = wxT("Emmo"); - newEntry.languageFlag = GlobalResources::getInstance().bitmapItaly; - locMapping.push_back(newEntry); - - newEntry.languageID = wxLANGUAGE_HUNGARIAN; - newEntry.languageName = wxT("Magyar"); - newEntry.languageFile = "Languages/hungarian.lng"; - newEntry.translatorName = wxT("Demon"); - newEntry.languageFlag = GlobalResources::getInstance().bitmapHungary; - locMapping.push_back(newEntry); - - newEntry.languageID = wxLANGUAGE_DUTCH; - newEntry.languageName = wxT("Nederlands"); - newEntry.languageFile = "Languages/dutch.lng"; - newEntry.translatorName = wxT("M.D. Vrakking"); - newEntry.languageFlag = GlobalResources::getInstance().bitmapHolland; - locMapping.push_back(newEntry); - - newEntry.languageID = wxLANGUAGE_RUSSIAN; - newEntry.languageName = wxT("Pусский язык"); - newEntry.languageFile = "Languages/russian.lng"; - newEntry.translatorName = wxT("Svobodniy"); - newEntry.languageFlag = GlobalResources::getInstance().bitmapRussia; - locMapping.push_back(newEntry); - - newEntry.languageID = wxLANGUAGE_POLISH; - newEntry.languageName = wxT("Polski"); - newEntry.languageFile = "Languages/polish.lng"; - newEntry.translatorName = wxT("Wojtek Pietruszewski"); - newEntry.languageFlag = GlobalResources::getInstance().bitmapPoland; - locMapping.push_back(newEntry); - - newEntry.languageID = wxLANGUAGE_PORTUGUESE; - newEntry.languageName = wxT("Português"); - newEntry.languageFile = "Languages/portuguese.lng"; - newEntry.translatorName = wxT("QuestMark"); - newEntry.languageFlag = GlobalResources::getInstance().bitmapPortugal; - locMapping.push_back(newEntry); - - newEntry.languageID = wxLANGUAGE_PORTUGUESE_BRAZILIAN; - newEntry.languageName = wxT("Português do Brasil"); - newEntry.languageFile = "Languages/portuguese_br.lng"; - newEntry.translatorName = wxT("Edison Aranha"); - newEntry.languageFlag = GlobalResources::getInstance().bitmapBrazil; - locMapping.push_back(newEntry); - - newEntry.languageID = wxLANGUAGE_SLOVENIAN; - newEntry.languageName = wxT("Slovenščina"); - newEntry.languageFile = "Languages/slovenian.lng"; - newEntry.translatorName = wxT("Matej Badalic"); - newEntry.languageFlag = GlobalResources::getInstance().bitmapSlovakia; - locMapping.push_back(newEntry); - - newEntry.languageID = wxLANGUAGE_JAPANESE; - newEntry.languageName = wxT("日本語"); - newEntry.languageFile = "Languages/japanese.lng"; - newEntry.translatorName = wxT("Tilt"); - newEntry.languageFlag = GlobalResources::getInstance().bitmapJapan; - locMapping.push_back(newEntry); - - newEntry.languageID = wxLANGUAGE_CHINESE_SIMPLIFIED; - newEntry.languageName = wxT("简体中文"); - newEntry.languageFile = "Languages/chinese_simple.lng"; - newEntry.translatorName = wxT("Misty Wu"); - newEntry.languageFlag = GlobalResources::getInstance().bitmapChina; - locMapping.push_back(newEntry); -} - - - -typedef wxString TextOriginal; -typedef wxString TextTranslation; - -class Translation : public std::map<TextOriginal, TextTranslation> {}; - - -CustomLocale::CustomLocale() : - wxLocale(), - currentLanguage(wxLANGUAGE_ENGLISH) -{ - translationDB = new Translation; -} - - -CustomLocale::~CustomLocale() -{ - delete translationDB; -} - - -inline -void exchangeEscapeChars(wxString& data) -{ - wxString output; - - const wxChar* input = data.c_str(); - - wxChar value; - while ((value = *input) != wxChar(0)) - { - //read backslash - if (value == wxChar('\\')) - { - //read next character - ++input; - if ((value = *input) == wxChar(0)) - break; - - switch (value) - { - case wxChar('\\'): - output += wxChar('\\'); - break; - case wxChar('n'): - output += wxChar('\n'); - break; - case wxChar('t'): - output += wxChar('\t'); - break; - case wxChar('\"'): - output += wxChar('\"'); - break; - default: - output += value; - } - } - else - output += value; - - ++input; - } - data = output; -} - - -void CustomLocale::setLanguage(const int language) -{ - //default: english - std::string languageFile; - currentLanguage = wxLANGUAGE_ENGLISH; - - //(try to) retrieve language filename - for (std::vector<LocInfoLine>::const_iterator i = LocalizationInfo::getMapping().begin(); i != LocalizationInfo::getMapping().end(); ++i) - if (language == i->languageID) - { - languageFile = i->languageFile; - currentLanguage = i->languageID; - break; - } - - - static bool initialized = false; //wxLocale is a static global too! - if (!initialized) - { - initialized = true; - wxLocale::Init(currentLanguage, wxLOCALE_LOAD_DEFAULT | wxLOCALE_CONV_ENCODING); - } - - //load language file into buffer - translationDB->clear(); - const int bufferSize = 100000; - char temp[bufferSize]; - if (!languageFile.empty()) - { - std::ifstream langFile(languageFile.c_str(), std::ios::binary); - if (langFile) - { - wxString original; - - //Delimiter: - //---------- - //Linux: 0xa \n - //Mac: 0xd \r - //Win: 0xd 0xa \r\n <- language files are in Windows format - for (int rowNumber = 0; langFile.getline(temp, bufferSize, 0xD); ++rowNumber) //specify delimiter explicitly - { - langFile.get(); //discard the 0xa character - - wxString formattedString = wxString::FromUTF8(temp); - //wxString formattedString = wxString::From8BitData(temp); - - exchangeEscapeChars(formattedString); - - if (rowNumber%2 == 0) - original = formattedString; - else - { - if (!formattedString.empty()) - { - const wxString translation = formattedString; - translationDB->insert(std::pair<TextOriginal, TextTranslation>(original, translation)); - } - } - } - langFile.close(); - } - else - wxMessageBox(wxString(_("Error reading file:")) + wxT(" \"") + wxString(languageFile.c_str(), wxConvUTF8) + wxT("\""), _("Error"), wxOK | wxICON_ERROR); - } - else - ; //if languageFile is empty texts will be english per default - - //these global variables need to be redetermined on language selection - FreeFileSync::DECIMAL_POINT = _("."); - FreeFileSync::THOUSANDS_SEPARATOR = _(","); -} - - -const wxChar* CustomLocale::GetString(const wxChar* szOrigString, const wxChar* szDomain) const -{ - //look for translation in buffer table - const Translation::const_iterator i = translationDB->find(szOrigString); - if (i != translationDB->end()) - return i->second.c_str(); - - //fallback - return szOrigString; -} diff --git a/library/localization.h b/library/localization.h deleted file mode 100644 index 7a63fd9c..00000000 --- a/library/localization.h +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef MISC_H_INCLUDED -#define MISC_H_INCLUDED - -#include <wx/intl.h> -#include <wx/bitmap.h> -#include <vector> - -class Translation; - - -namespace FreeFileSync -{ - struct LocInfoLine - { - int languageID; - wxString languageName; - std::string languageFile; - wxString translatorName; - wxBitmap* languageFlag; - }; - - - class LocalizationInfo - { - public: - static const std::vector<LocInfoLine>& getMapping(); - - private: - LocalizationInfo(); - - std::vector<LocInfoLine> locMapping; - }; - - - class CustomLocale : public wxLocale - { - public: - CustomLocale(); - ~CustomLocale(); - - void setLanguage(const int language); - - int getLanguage() const - { - return currentLanguage; - } - - const wxChar* GetString(const wxChar* szOrigString, const wxChar* szDomain = NULL) const; - - private: - Translation* translationDB; - int currentLanguage; - }; -} - -#endif // MISC_H_INCLUDED diff --git a/library/multithreading.cpp b/library/multithreading.cpp index 6c2612f1..899a426b 100644 --- a/library/multithreading.cpp +++ b/library/multithreading.cpp @@ -159,3 +159,90 @@ void UpdateWhileExecuting::execute(StatusHandler* statusUpdater) } } + + +// ------------------------------------------------------ +// |Pattern: workload queue and multiple worker threads | +// ------------------------------------------------------ +//typedef std::vector<DirectoryDescrType*> Workload; +// +//class ThreadSorting : public wxThread +//{ +//public: +// ThreadSorting(wxCriticalSection& syncWorkload, Workload& workload) : +// wxThread(wxTHREAD_JOINABLE), +// syncWorkload_(syncWorkload), +// workload_(workload) +// { +// if (Create() != wxTHREAD_NO_ERROR) +// throw RuntimeException(wxString(wxT("Error creating thread for sorting!"))); +// } +// +// ~ThreadSorting() {} +// +// +// ExitCode Entry() +// { +// while (true) +// { +// DirectoryDescrType* descr = NULL; +// { //see if there is work to do... +// wxCriticalSectionLocker dummy(syncWorkload_); +// if (workload_.empty()) +// return 0; +// else +// { +// descr = workload_.back(); +// workload_.pop_back(); +// } +// } +// //do work +// std::sort(descr->begin(), descr->end()); +// } +// } +// +//private: +// wxCriticalSection& syncWorkload_; +// Workload& workload_; +//}; +// +// +//void DirectoryDescrBuffer::preFillBuffers(const std::vector<FolderPairCfg>& fpConfigFormatted) +//{ +// //assemble workload +// ... +// +// //we use binary search when comparing the directory structures: so sort() first +// const int CPUCount = wxThread::GetCPUCount(); +// if (CPUCount >= 2) //do it the multithreaded way: +// { +// wxCriticalSection syncWorkload; +// +// typedef std::vector<boost::shared_ptr<ThreadSorting> > ThreadContainer; +// ThreadContainer sortThreads; +// sortThreads.reserve(CPUCount); +// +// //start CPUCount worker threads +// for (size_t i = 0; i < std::min(static_cast<size_t>(CPUCount), workload.size()); ++i) +// { +// boost::shared_ptr<ThreadSorting> newWorker(new ThreadSorting(syncWorkload, workload)); +// +// if (newWorker->Run() != wxTHREAD_NO_ERROR) +// throw RuntimeException(wxString(wxT("Error starting thread for sorting!"))); +// +// sortThreads.push_back(newWorker); +// } +// +// //wait until all worker are finished +// for (ThreadContainer::iterator i = sortThreads.begin(); i != sortThreads.end(); ++i) +// { +// if ((*i)->Wait() != 0) +// throw RuntimeException(wxString(wxT("Error waiting for thread (sorting)!"))); +// } +// } +// else //single threaded +// { +// for (Workload::iterator i = workload.begin(); i != workload.end(); ++i) +// std::sort((*i)->begin(), (*i)->end()); +// } +//} diff --git a/library/pch.h b/library/pch.h index 22ed251f..966bc103 100644 --- a/library/pch.h +++ b/library/pch.h @@ -33,6 +33,7 @@ do NOT use in release build! #endif //FFS_LINUX //other wxWidgets headers +#include <wx/log.h> #include <wx/grid.h> #include <wx/animate.h> #include <wx/app.h> @@ -89,6 +90,9 @@ do NOT use in release build! #include "../shared/tinyxml/tinyxml.h" #include <sys/stat.h> +//Boost +#include <boost/shared_ptr.hpp> + #ifdef FFS_WIN #include <wx/msw/wrapwin.h> //includes "windows.h" #endif //FFS_WIN diff --git a/library/processXml.cpp b/library/processXml.cpp index 43d7fdb9..e49e0b35 100644 --- a/library/processXml.cpp +++ b/library/processXml.cpp @@ -26,8 +26,11 @@ public: void readXmlGlobalSettings(xmlAccess::XmlGlobalSettings& outputCfg); private: + //read alternate configuration (optional) => might point to NULL + void readXmlAlternateConfig(const TiXmlElement& folderPair, FolderPairEnh& enhPair); + //read basic FreefileSync settings (used by commandline and GUI), return true if ALL values have been retrieved successfully - void readXmlMainConfig(MainConfiguration& mainCfg, std::vector<FolderPair>& directoryPairs); + void readXmlMainConfig(MainConfiguration& mainCfg); }; @@ -37,8 +40,10 @@ bool writeXmlGuiConfig(const xmlAccess::XmlGuiConfig& outputCfg, TiXmlDocument& bool writeXmlBatchConfig(const xmlAccess::XmlBatchConfig& outputCfg, TiXmlDocument& doc); //write global settings bool writeXmlGlobalSettings(const xmlAccess::XmlGlobalSettings& outputCfg, TiXmlDocument& doc); +//write alternate configuration (optional) => might point to NULL +void writeXmlAlternateConfig(const FolderPairEnh& enhPair, TiXmlElement& parent); //write basic FreefileSync settings (used by commandline and GUI), return true if everything was written successfully -bool writeXmlMainConfig(const MainConfiguration& mainCfg, const std::vector<FolderPair>& directoryPairs, TiXmlDocument& doc); +bool writeXmlMainConfig(const MainConfiguration& mainCfg, TiXmlDocument& doc); void xmlAccess::readGuiConfig(const wxString& filename, xmlAccess::XmlGuiConfig& config) @@ -215,6 +220,17 @@ bool readXmlElement(const std::string& name, const TiXmlElement* parent , FreeFi } +bool readXmlElement(const std::string& name, const TiXmlElement* parent, Zstring& output) +{ + wxString dummy; + if (!xmlAccess::readXmlElement(name, parent, dummy)) + return false; + + output = dummy.c_str(); + return true; +} + + bool readXmlAttribute(const std::string& name, const TiXmlElement* node, xmlAccess::ColumnTypes& output) { int dummy; @@ -229,59 +245,129 @@ bool readXmlAttribute(const std::string& name, const TiXmlElement* node, xmlAcce //################################################################################################################ -void FfsXmlParser::readXmlMainConfig(MainConfiguration& mainCfg, std::vector<FolderPair>& directoryPairs) +void FfsXmlParser::readXmlAlternateConfig(const TiXmlElement& folderPair, FolderPairEnh& enhPair) +{ + //read folder pairs + readXmlElementLogging("Left", &folderPair, enhPair.leftDirectory); + readXmlElementLogging("Right", &folderPair, enhPair.rightDirectory); + + +//########################################################### + //alternate sync configuration + const TiXmlElement* syncConfig = TiXmlHandleConst(&folderPair).FirstChild("AlternateSyncConfig").ToElement(); + if (syncConfig) + { + AlternateSyncConfig* altSyncCfg = new AlternateSyncConfig; + enhPair.altSyncConfig.reset(altSyncCfg); + + const TiXmlElement* syncDirections = TiXmlHandleConst(syncConfig).FirstChild("Synchronization").FirstChild("Directions").ToElement(); + + //read sync configuration + readXmlElementLogging("LeftOnly", syncDirections, altSyncCfg->syncConfiguration.exLeftSideOnly); + readXmlElementLogging("RightOnly", syncDirections, altSyncCfg->syncConfiguration.exRightSideOnly); + readXmlElementLogging("LeftNewer", syncDirections, altSyncCfg->syncConfiguration.leftNewer); + readXmlElementLogging("RightNewer", syncDirections, altSyncCfg->syncConfiguration.rightNewer); + readXmlElementLogging("Different", syncDirections, altSyncCfg->syncConfiguration.different); + readXmlElementLogging("Conflict", syncDirections, altSyncCfg->syncConfiguration.conflict); + + const TiXmlElement* miscSettings = TiXmlHandleConst(&folderPair).FirstChild("AlternateSyncConfig").FirstChild("Miscellaneous").ToElement(); + readXmlElementLogging("DeletionPolicy", miscSettings, altSyncCfg->handleDeletion); + readXmlElementLogging("CustomDeletionFolder", miscSettings, altSyncCfg->customDeletionDirectory); + } + +//########################################################### + //alternate filter configuration + const TiXmlElement* filterCfg = TiXmlHandleConst(&folderPair).FirstChild("AlternateFilter").ToElement(); + if (filterCfg) + { + AlternateFilter* altFilterCfg = new AlternateFilter; + enhPair.altFilter.reset(altFilterCfg); + + //read filter settings + readXmlElementLogging("Include", filterCfg, altFilterCfg->includeFilter); + readXmlElementLogging("Exclude", filterCfg, altFilterCfg->excludeFilter); + } +} + + +void FfsXmlParser::readXmlMainConfig(MainConfiguration& mainCfg) { TiXmlHandleConst hRoot(root); //custom const handle: TiXml API seems broken in this regard +//########################################################### const TiXmlElement* cmpSettings = hRoot.FirstChild("MainConfig").FirstChild("Comparison").ToElement(); - const TiXmlElement* syncConfig = hRoot.FirstChild("MainConfig").FirstChild("Synchronization").FirstChild("Directions").ToElement(); - const TiXmlElement* miscSettings = hRoot.FirstChild("MainConfig").FirstChild("Miscellaneous").ToElement(); - const TiXmlElement* filter = TiXmlHandleConst(miscSettings).FirstChild("Filter").ToElement(); -//########################################################### //read compare variant readXmlElementLogging("Variant", cmpSettings, mainCfg.compareVar); - //read folder pair(s) - const TiXmlElement* folderPair = TiXmlHandleConst(cmpSettings).FirstChild("Folders").FirstChild("Pair").ToElement(); - - //read folder pairs - directoryPairs.clear(); - while (folderPair) - { - wxString temp; - wxString temp2; - readXmlElementLogging("Left", folderPair, temp); - readXmlElementLogging("Right", folderPair, temp2); - directoryPairs.push_back(FolderPair(temp.c_str(), temp2.c_str())); + //max. allowed file time deviation + readXmlElementLogging("FileTimeTolerance", cmpSettings, mainCfg.hidden.fileTimeTolerance); - folderPair = folderPair->NextSiblingElement(); - } + //traverse into symbolic links (to folders) + readXmlElementLogging("TraverseDirectorySymlinks", cmpSettings, mainCfg.hidden.traverseDirectorySymlinks); //########################################################### + const TiXmlElement* syncDirections = hRoot.FirstChild("MainConfig").FirstChild("Synchronization").FirstChild("Directions").ToElement(); + //read sync configuration - readXmlElementLogging("LeftOnly", syncConfig, mainCfg.syncConfiguration.exLeftSideOnly); - readXmlElementLogging("RightOnly", syncConfig, mainCfg.syncConfiguration.exRightSideOnly); - readXmlElementLogging("LeftNewer", syncConfig, mainCfg.syncConfiguration.leftNewer); - readXmlElementLogging("RightNewer", syncConfig, mainCfg.syncConfiguration.rightNewer); - readXmlElementLogging("Different", syncConfig, mainCfg.syncConfiguration.different); + readXmlElementLogging("LeftOnly", syncDirections, mainCfg.syncConfiguration.exLeftSideOnly); + readXmlElementLogging("RightOnly", syncDirections, mainCfg.syncConfiguration.exRightSideOnly); + readXmlElementLogging("LeftNewer", syncDirections, mainCfg.syncConfiguration.leftNewer); + readXmlElementLogging("RightNewer", syncDirections, mainCfg.syncConfiguration.rightNewer); + readXmlElementLogging("Different", syncDirections, mainCfg.syncConfiguration.different); + readXmlElementLogging("Conflict", syncDirections, mainCfg.syncConfiguration.conflict); + +//########################################################### + const TiXmlElement* syncConfig = hRoot.FirstChild("MainConfig").FirstChild("Synchronization").ToElement(); + + //copy symbolic links to files + readXmlElementLogging("CopyFileSymlinks", syncConfig, mainCfg.hidden.copyFileSymlinks); + + //verify file copying + readXmlElementLogging("VerifyCopiedFiles", syncConfig, mainCfg.hidden.verifyFileCopy); //########################################################### + const TiXmlElement* miscSettings = hRoot.FirstChild("MainConfig").FirstChild("Miscellaneous").ToElement(); + + //misc + readXmlElementLogging("DeletionPolicy", miscSettings, mainCfg.handleDeletion); + readXmlElementLogging("CustomDeletionFolder", miscSettings, mainCfg.customDeletionDirectory); +//########################################################### + const TiXmlElement* filter = TiXmlHandleConst(miscSettings).FirstChild("Filter").ToElement(); + //read filter settings readXmlElementLogging("Active", filter, mainCfg.filterIsActive); readXmlElementLogging("Include", filter, mainCfg.includeFilter); readXmlElementLogging("Exclude", filter, mainCfg.excludeFilter); + //########################################################### - //other - readXmlElementLogging("DeletionPolicy", miscSettings, mainCfg.handleDeletion); - readXmlElementLogging("CustomDeletionFolder", miscSettings, mainCfg.customDeletionDirectory); + const TiXmlElement* pairs = hRoot.FirstChild("MainConfig").FirstChild("FolderPairs").FirstChild("Pair").ToElement(); + + //read main folder pair + if (pairs) + { + readXmlElementLogging("Left", pairs, mainCfg.mainFolderPair.leftDirectory); + readXmlElementLogging("Right", pairs, mainCfg.mainFolderPair.rightDirectory); + pairs = pairs->NextSiblingElement(); + } + + //read additional folder pairs + mainCfg.additionalPairs.clear(); + while (pairs) + { + FolderPairEnh newPair; + readXmlAlternateConfig(*pairs, newPair); + mainCfg.additionalPairs.push_back(newPair); + + pairs = pairs->NextSiblingElement(); + } } void FfsXmlParser::readXmlGuiConfig(xmlAccess::XmlGuiConfig& outputCfg) { //read main config - readXmlMainConfig(outputCfg.mainCfg, outputCfg.directoryPairs); + readXmlMainConfig(outputCfg.mainCfg); //read GUI specific config data const TiXmlElement* guiConfig = TiXmlHandleConst(root).FirstChild("GuiConfig").ToElement(); @@ -299,7 +385,7 @@ void FfsXmlParser::readXmlGuiConfig(xmlAccess::XmlGuiConfig& outputCfg) void FfsXmlParser::readXmlBatchConfig(xmlAccess::XmlBatchConfig& outputCfg) { //read main config - readXmlMainConfig(outputCfg.mainCfg, outputCfg.directoryPairs); + readXmlMainConfig(outputCfg.mainCfg); //read batch specific config const TiXmlElement* batchConfig = TiXmlHandleConst(root).FirstChild("BatchConfig").ToElement(); @@ -318,32 +404,28 @@ void FfsXmlParser::readXmlGlobalSettings(xmlAccess::XmlGlobalSettings& outputCfg //try to read program language setting readXmlElementLogging("Language", global, outputCfg.programLanguage); - //max. allowed file time deviation - readXmlElementLogging("FileTimeTolerance", global, outputCfg.fileTimeTolerance); - //ignore +/- 1 hour due to DST change readXmlElementLogging("IgnoreOneHourDifference", global, outputCfg.ignoreOneHourDiff); - //traverse into symbolic links (to folders) - readXmlElementLogging("TraverseDirectorySymlinks", global, outputCfg.traverseDirectorySymlinks); - - //copy symbolic links to files - readXmlElementLogging("CopyFileSymlinks", global, outputCfg.copyFileSymlinks); - //last update check readXmlElementLogging("LastCheckForUpdates", global, outputCfg.lastUpdateCheck); - const TiXmlElement* warnings = TiXmlHandleConst(root).FirstChild("Shared").FirstChild("Warnings").ToElement(); + const TiXmlElement* optionalDialogs = TiXmlHandleConst(root).FirstChild("Shared").FirstChild("ShowOptionalDialogs").ToElement(); //folder dependency check - readXmlElementLogging("CheckForDependentFolders", warnings, outputCfg.warnings.warningDependentFolders); + readXmlElementLogging("CheckForDependentFolders", optionalDialogs, outputCfg.optDialogs.warningDependentFolders); //significant difference check - readXmlElementLogging("CheckForSignificantDifference", warnings, outputCfg.warnings.warningSignificantDifference); + readXmlElementLogging("CheckForSignificantDifference", optionalDialogs, outputCfg.optDialogs.warningSignificantDifference); //check free disk space - readXmlElementLogging("CheckForFreeDiskSpace", warnings, outputCfg.warnings.warningNotEnoughDiskSpace); + readXmlElementLogging("CheckForFreeDiskSpace", optionalDialogs, outputCfg.optDialogs.warningNotEnoughDiskSpace); //check for unresolved conflicts - readXmlElementLogging("CheckForUnresolvedConflicts", warnings, outputCfg.warnings.warningUnresolvedConflicts); + readXmlElementLogging("CheckForUnresolvedConflicts", optionalDialogs, outputCfg.optDialogs.warningUnresolvedConflicts); + + readXmlElementLogging("PopupOnConfigChange", optionalDialogs, outputCfg.optDialogs.popupOnConfigChange); + + readXmlElementLogging("SummaryBeforeSync", optionalDialogs, outputCfg.optDialogs.showSummaryBeforeSync); + //gui specific global settings (optional) const TiXmlElement* mainWindow = TiXmlHandleConst(root).FirstChild("Gui").FirstChild("Windows").FirstChild("Main").ToElement(); @@ -359,8 +441,6 @@ void FfsXmlParser::readXmlGlobalSettings(xmlAccess::XmlGlobalSettings& outputCfg readXmlElementLogging("ManualDeletionUseRecycler", mainWindow, outputCfg.gui.useRecyclerForManualDeletion); readXmlElementLogging("ShowFileIconsLeft", mainWindow, outputCfg.gui.showFileIconsLeft); readXmlElementLogging("ShowFileIconsRight", mainWindow, outputCfg.gui.showFileIconsRight); - readXmlElementLogging("PopupOnConfigChange", mainWindow, outputCfg.gui.popupOnConfigChange); - readXmlElementLogging("SummaryBeforeSync", mainWindow, outputCfg.gui.showSummaryBeforeSync); //########################################################### //read column attributes @@ -416,10 +496,24 @@ void FfsXmlParser::readXmlGlobalSettings(xmlAccess::XmlGlobalSettings& outputCfg const TiXmlElement* gui = TiXmlHandleConst(root).FirstChild("Gui").ToElement(); - //commandline for file manager integration - readXmlElementLogging("FileManager", gui, outputCfg.gui.commandLineFileManager); - - + //external applications + const TiXmlElement* extApps = TiXmlHandleConst(gui).FirstChild("ExternalApplications").FirstChild("Commandline").ToElement(); + if (extApps) + { + outputCfg.gui.externelApplications.clear(); + while (extApps) + { + wxString description; + wxString cmdLine; + + readXmlAttributeLogging("Description", extApps, description); + const char* text = extApps->GetText(); + if (text) cmdLine = wxString::FromUTF8(text); //read commandline + outputCfg.gui.externelApplications.push_back(std::make_pair(description, cmdLine)); + + extApps = extApps->NextSiblingElement(); + } + } //load config file history const TiXmlElement* cfgHistory = TiXmlHandleConst(root).FirstChild("Gui").FirstChild("ConfigHistory").ToElement(); @@ -449,7 +543,7 @@ void addXmlElement(const std::string& name, const CompareVariant variant, TiXmlE } -void addXmlElement(TiXmlElement* parent, const std::string& name, const SyncDirectionCfg value) +void addXmlElement(const std::string& name, const SyncDirectionCfg value, TiXmlElement* parent) { switch (value) { @@ -500,14 +594,80 @@ void addXmlElement(const std::string& name, const FreeFileSync::DeletionPolicy v } +void addXmlElement(const std::string& name, const Zstring& value, TiXmlElement* parent) +{ + xmlAccess::addXmlElement(name, wxString(value.c_str()), parent); +} + + void addXmlAttribute(const std::string& name, const xmlAccess::ColumnTypes value, TiXmlElement* node) { xmlAccess::addXmlAttribute(name, static_cast<int>(value), node); } -bool writeXmlMainConfig(const MainConfiguration& mainCfg, const std::vector<FolderPair>& directoryPairs, TiXmlDocument& doc) +void writeXmlAlternateConfig(const FolderPairEnh& enhPair, TiXmlElement& parent) { + //write folder pairs + TiXmlElement* newfolderPair = new TiXmlElement("Pair"); + parent.LinkEndChild(newfolderPair); + + addXmlElement("Left", enhPair.leftDirectory, newfolderPair); + addXmlElement("Right", enhPair.rightDirectory, newfolderPair); + + + //alternate sync configuration + const AlternateSyncConfig* altSyncConfig = enhPair.altSyncConfig.get(); + if (altSyncConfig) + { + TiXmlElement* syncCfg = new TiXmlElement("AlternateSyncConfig"); + newfolderPair->LinkEndChild(syncCfg); + + TiXmlElement* syncSettings = new TiXmlElement("Synchronization"); + syncCfg->LinkEndChild(syncSettings); + + //write sync configuration + TiXmlElement* syncDirections = new TiXmlElement("Directions"); + syncSettings->LinkEndChild(syncDirections); + + ::addXmlElement("LeftOnly", altSyncConfig->syncConfiguration.exLeftSideOnly, syncDirections); + ::addXmlElement("RightOnly", altSyncConfig->syncConfiguration.exRightSideOnly, syncDirections); + ::addXmlElement("LeftNewer", altSyncConfig->syncConfiguration.leftNewer, syncDirections); + ::addXmlElement("RightNewer", altSyncConfig->syncConfiguration.rightNewer, syncDirections); + ::addXmlElement("Different", altSyncConfig->syncConfiguration.different, syncDirections); + ::addXmlElement("Conflict", altSyncConfig->syncConfiguration.conflict, syncDirections); + + + TiXmlElement* miscSettings = new TiXmlElement("Miscellaneous"); + syncCfg->LinkEndChild(miscSettings); + + //misc + addXmlElement("DeletionPolicy", altSyncConfig->handleDeletion, miscSettings); + xmlAccess::addXmlElement("CustomDeletionFolder", altSyncConfig->customDeletionDirectory, miscSettings); + } + +//########################################################### + //alternate filter configuration + const AlternateFilter* altFilter = enhPair.altFilter.get(); + if (altFilter) + { + TiXmlElement* filterCfg = new TiXmlElement("AlternateFilter"); + newfolderPair->LinkEndChild(filterCfg); + + //write filter settings + xmlAccess::addXmlElement("Include", altFilter->includeFilter, filterCfg); + xmlAccess::addXmlElement("Exclude", altFilter->excludeFilter, filterCfg); + } +} + + +bool writeXmlMainConfig(const MainConfiguration& mainCfg, TiXmlDocument& doc) +{ + //reset hidden settings: we don't want implicit behaviour! Hidden settings are loaded but changes are not saved! + MainConfiguration mainCfgLocal = mainCfg; + mainCfgLocal.hidden = HiddenSettings(); + + TiXmlElement* root = doc.RootElement(); if (!root) return false; @@ -519,35 +679,35 @@ bool writeXmlMainConfig(const MainConfiguration& mainCfg, const std::vector<Fold settings->LinkEndChild(cmpSettings); //write compare algorithm - ::addXmlElement("Variant", mainCfg.compareVar, cmpSettings); + ::addXmlElement("Variant", mainCfgLocal.compareVar, cmpSettings); - //write folder pair(s) - TiXmlElement* folders = new TiXmlElement("Folders"); - cmpSettings->LinkEndChild(folders); - - //write folder pairs - for (std::vector<FolderPair>::const_iterator i = directoryPairs.begin(); i != directoryPairs.end(); ++i) - { - TiXmlElement* folderPair = new TiXmlElement("Pair"); - folders->LinkEndChild(folderPair); + //max. allowed file time deviation + xmlAccess::addXmlElement("FileTimeTolerance", mainCfgLocal.hidden.fileTimeTolerance, cmpSettings); - xmlAccess::addXmlElement("Left", wxString(i->leftDirectory.c_str()), folderPair); - xmlAccess::addXmlElement("Right", wxString(i->rightDirectory.c_str()), folderPair); - } + //traverse into symbolic links (to folders) + xmlAccess::addXmlElement("TraverseDirectorySymlinks", mainCfgLocal.hidden.traverseDirectorySymlinks, cmpSettings); //########################################################### TiXmlElement* syncSettings = new TiXmlElement("Synchronization"); settings->LinkEndChild(syncSettings); //write sync configuration - TiXmlElement* syncConfig = new TiXmlElement("Directions"); - syncSettings->LinkEndChild(syncConfig); + TiXmlElement* syncDirections = new TiXmlElement("Directions"); + syncSettings->LinkEndChild(syncDirections); + + ::addXmlElement("LeftOnly", mainCfgLocal.syncConfiguration.exLeftSideOnly, syncDirections); + ::addXmlElement("RightOnly", mainCfgLocal.syncConfiguration.exRightSideOnly, syncDirections); + ::addXmlElement("LeftNewer", mainCfgLocal.syncConfiguration.leftNewer, syncDirections); + ::addXmlElement("RightNewer", mainCfgLocal.syncConfiguration.rightNewer, syncDirections); + ::addXmlElement("Different", mainCfgLocal.syncConfiguration.different, syncDirections); + ::addXmlElement("Conflict", mainCfgLocal.syncConfiguration.conflict, syncDirections); + +//########################################################### + //copy symbolic links to files + xmlAccess::addXmlElement("CopyFileSymlinks", mainCfgLocal.hidden.copyFileSymlinks, syncSettings); - ::addXmlElement(syncConfig, "LeftOnly", mainCfg.syncConfiguration.exLeftSideOnly); - ::addXmlElement(syncConfig, "RightOnly", mainCfg.syncConfiguration.exRightSideOnly); - ::addXmlElement(syncConfig, "LeftNewer", mainCfg.syncConfiguration.leftNewer); - ::addXmlElement(syncConfig, "RightNewer", mainCfg.syncConfiguration.rightNewer); - ::addXmlElement(syncConfig, "Different", mainCfg.syncConfiguration.different); + //verify file copying + xmlAccess::addXmlElement("VerifyCopiedFiles", mainCfgLocal.hidden.verifyFileCopy, syncSettings); //########################################################### TiXmlElement* miscSettings = new TiXmlElement("Miscellaneous"); @@ -557,14 +717,28 @@ bool writeXmlMainConfig(const MainConfiguration& mainCfg, const std::vector<Fold TiXmlElement* filter = new TiXmlElement("Filter"); miscSettings->LinkEndChild(filter); - xmlAccess::addXmlElement("Active", mainCfg.filterIsActive, filter); - xmlAccess::addXmlElement("Include", mainCfg.includeFilter, filter); - xmlAccess::addXmlElement("Exclude", mainCfg.excludeFilter, filter); + xmlAccess::addXmlElement("Active", mainCfgLocal.filterIsActive, filter); + xmlAccess::addXmlElement("Include", mainCfgLocal.includeFilter, filter); + xmlAccess::addXmlElement("Exclude", mainCfgLocal.excludeFilter, filter); //other - addXmlElement("DeletionPolicy", mainCfg.handleDeletion, miscSettings); - xmlAccess::addXmlElement("CustomDeletionFolder", mainCfg.customDeletionDirectory, miscSettings); + addXmlElement("DeletionPolicy", mainCfgLocal.handleDeletion, miscSettings); + xmlAccess::addXmlElement("CustomDeletionFolder", mainCfgLocal.customDeletionDirectory, miscSettings); + //########################################################### + TiXmlElement* pairs = new TiXmlElement("FolderPairs"); + settings->LinkEndChild(pairs); + + //write main folder pair + TiXmlElement* mainPair = new TiXmlElement("Pair"); + pairs->LinkEndChild(mainPair); + + addXmlElement("Left", mainCfgLocal.mainFolderPair.leftDirectory, mainPair); + addXmlElement("Right", mainCfgLocal.mainFolderPair.rightDirectory, mainPair); + + //write additional folder pairs + for (std::vector<FolderPairEnh>::const_iterator i = mainCfgLocal.additionalPairs.begin(); i != mainCfgLocal.additionalPairs.end(); ++i) + writeXmlAlternateConfig(*i, *pairs); return true; } @@ -573,7 +747,7 @@ bool writeXmlMainConfig(const MainConfiguration& mainCfg, const std::vector<Fold bool writeXmlGuiConfig(const xmlAccess::XmlGuiConfig& inputCfg, TiXmlDocument& doc) { //write main config - if (!writeXmlMainConfig(inputCfg.mainCfg, inputCfg.directoryPairs, doc)) + if (!writeXmlMainConfig(inputCfg.mainCfg, doc)) return false; //write GUI specific config @@ -596,7 +770,7 @@ bool writeXmlGuiConfig(const xmlAccess::XmlGuiConfig& inputCfg, TiXmlDocument& d bool writeXmlBatchConfig(const xmlAccess::XmlBatchConfig& inputCfg, TiXmlDocument& doc) { //write main config - if (!writeXmlMainConfig(inputCfg.mainCfg, inputCfg.directoryPairs, doc)) + if (!writeXmlMainConfig(inputCfg.mainCfg, doc)) return false; //write GUI specific config @@ -627,36 +801,32 @@ bool writeXmlGlobalSettings(const xmlAccess::XmlGlobalSettings& inputCfg, TiXmlD //program language xmlAccess::addXmlElement("Language", inputCfg.programLanguage, global); - //max. allowed file time deviation - xmlAccess::addXmlElement("FileTimeTolerance", inputCfg.fileTimeTolerance, global); - //ignore +/- 1 hour due to DST change xmlAccess::addXmlElement("IgnoreOneHourDifference", inputCfg.ignoreOneHourDiff, global); - //traverse into symbolic links (to folders) - xmlAccess::addXmlElement("TraverseDirectorySymlinks", inputCfg.traverseDirectorySymlinks, global); - - //copy symbolic links to files - xmlAccess::addXmlElement("CopyFileSymlinks", inputCfg.copyFileSymlinks, global); - //last update check xmlAccess::addXmlElement("LastCheckForUpdates", inputCfg.lastUpdateCheck, global); - //warnings - TiXmlElement* warnings = new TiXmlElement("Warnings"); - global->LinkEndChild(warnings); + + //optional dialogs + TiXmlElement* optionalDialogs = new TiXmlElement("ShowOptionalDialogs"); + global->LinkEndChild(optionalDialogs); //warning: dependent folders - xmlAccess::addXmlElement("CheckForDependentFolders", inputCfg.warnings.warningDependentFolders, warnings); + xmlAccess::addXmlElement("CheckForDependentFolders", inputCfg.optDialogs.warningDependentFolders, optionalDialogs); //significant difference check - xmlAccess::addXmlElement("CheckForSignificantDifference", inputCfg.warnings.warningSignificantDifference, warnings); + xmlAccess::addXmlElement("CheckForSignificantDifference", inputCfg.optDialogs.warningSignificantDifference, optionalDialogs); //check free disk space - xmlAccess::addXmlElement("CheckForFreeDiskSpace", inputCfg.warnings.warningNotEnoughDiskSpace, warnings); + xmlAccess::addXmlElement("CheckForFreeDiskSpace", inputCfg.optDialogs.warningNotEnoughDiskSpace, optionalDialogs); //check for unresolved conflicts - xmlAccess::addXmlElement("CheckForUnresolvedConflicts", inputCfg.warnings.warningUnresolvedConflicts, warnings); + xmlAccess::addXmlElement("CheckForUnresolvedConflicts", inputCfg.optDialogs.warningUnresolvedConflicts, optionalDialogs); + + xmlAccess::addXmlElement("PopupOnConfigChange", inputCfg.optDialogs.popupOnConfigChange, optionalDialogs); + + xmlAccess::addXmlElement("SummaryBeforeSync", inputCfg.optDialogs.showSummaryBeforeSync, optionalDialogs); //################################################################### @@ -683,8 +853,6 @@ bool writeXmlGlobalSettings(const xmlAccess::XmlGlobalSettings& inputCfg, TiXmlD xmlAccess::addXmlElement("ManualDeletionUseRecycler", inputCfg.gui.useRecyclerForManualDeletion, mainWindow); xmlAccess::addXmlElement("ShowFileIconsLeft", inputCfg.gui.showFileIconsLeft, mainWindow); xmlAccess::addXmlElement("ShowFileIconsRight", inputCfg.gui.showFileIconsRight, mainWindow); - xmlAccess::addXmlElement("PopupOnConfigChange", inputCfg.gui.popupOnConfigChange, mainWindow); - xmlAccess::addXmlElement("SummaryBeforeSync", inputCfg.gui.showSummaryBeforeSync, mainWindow); //write column attributes TiXmlElement* leftColumn = new TiXmlElement("LeftColumns"); @@ -737,8 +905,18 @@ bool writeXmlGlobalSettings(const xmlAccess::XmlGlobalSettings& inputCfg, TiXmlD xmlAccess::addXmlElement("SelectedTabBottomLeft", inputCfg.gui.selectedTabBottomLeft, mainWindow); - //commandline for file manager integration - xmlAccess::addXmlElement("FileManager", inputCfg.gui.commandLineFileManager, gui); + //external applications + TiXmlElement* extApp = new TiXmlElement("ExternalApplications"); + gui->LinkEndChild(extApp); + + for (xmlAccess::ExternalApps::const_iterator i = inputCfg.gui.externelApplications.begin(); i != inputCfg.gui.externelApplications.end(); ++i) + { + TiXmlElement* newEntry = new TiXmlElement("Commandline"); + extApp->LinkEndChild(newEntry); + + xmlAccess::addXmlAttribute("Description", i->first, newEntry); + newEntry->LinkEndChild(new TiXmlText(i->second.ToUTF8())); //commandline + } //write config file history TiXmlElement* cfgHistory = new TiXmlElement("ConfigHistory"); @@ -769,10 +947,47 @@ bool xmlAccess::recycleBinAvailable() } -void xmlAccess::WarningMessages::resetWarnings() +void xmlAccess::OptionalDialogs::resetDialogs() { warningDependentFolders = true; warningSignificantDifference = true; warningNotEnoughDiskSpace = true; warningUnresolvedConflicts = true; + popupOnConfigChange = true; + showSummaryBeforeSync = true; +} + + +xmlAccess::XmlGuiConfig convertBatchToGui(const xmlAccess::XmlBatchConfig& batchCfg) +{ + xmlAccess::XmlGuiConfig output; + output.mainCfg = batchCfg.mainCfg; + return output; +} + + +void xmlAccess::readGuiOrBatchConfig(const wxString& filename, XmlGuiConfig& config) //throw (xmlAccess::XmlError); +{ + if (xmlAccess::getXmlType(filename) != xmlAccess::XML_BATCH_CONFIG) + { + xmlAccess::readGuiConfig(filename, config); + return; + } + + //convert batch config to gui config + xmlAccess::XmlBatchConfig batchCfg; + try + { + xmlAccess::readBatchConfig(filename, batchCfg); //throw (xmlAccess::XmlError); + } + catch (const xmlAccess::XmlError& e) + { + if (e.getSeverity() != xmlAccess::XmlError::WARNING) + throw; + + config = convertBatchToGui(batchCfg); //do work despite parsing errors, then re-throw + throw; // + } + + config = convertBatchToGui(batchCfg); } diff --git a/library/processXml.h b/library/processXml.h index 9fad5cd7..868b859b 100644 --- a/library/processXml.h +++ b/library/processXml.h @@ -32,8 +32,12 @@ namespace xmlAccess }; typedef std::vector<ColumnAttrib> ColumnAttributes; -//--------------------------------------------------------------------- + typedef wxString Description; + typedef wxString Commandline; + typedef std::vector<std::pair<Description, Commandline> > ExternalApps; + +//--------------------------------------------------------------------- struct XmlGuiConfig { XmlGuiConfig() : @@ -42,7 +46,6 @@ namespace xmlAccess syncPreviewEnabled(true) {} //initialize values FreeFileSync::MainConfiguration mainCfg; - std::vector<FreeFileSync::FolderPair> directoryPairs; bool hideFilteredElements; bool ignoreErrors; //reaction on error situation during synchronization @@ -51,7 +54,6 @@ namespace xmlAccess bool operator==(const XmlGuiConfig& other) const { return mainCfg == other.mainCfg && - directoryPairs == other.directoryPairs && hideFilteredElements == other.hideFilteredElements && ignoreErrors == other.ignoreErrors && syncPreviewEnabled == other.syncPreviewEnabled; @@ -69,7 +71,6 @@ namespace xmlAccess XmlBatchConfig() : silent(false), handleError(ON_ERROR_POPUP) {} FreeFileSync::MainConfiguration mainCfg; - std::vector<FreeFileSync::FolderPair> directoryPairs; bool silent; OnError handleError; //reaction on error situation during synchronization @@ -80,19 +81,21 @@ namespace xmlAccess bool recycleBinAvailable(); - struct WarningMessages + struct OptionalDialogs { - WarningMessages() + OptionalDialogs() { - resetWarnings(); + resetDialogs(); } - void resetWarnings(); + void resetDialogs(); bool warningDependentFolders; bool warningSignificantDifference; bool warningNotEnoughDiskSpace; bool warningUnresolvedConflicts; + bool popupOnConfigChange; + bool showSummaryBeforeSync; }; @@ -102,20 +105,14 @@ namespace xmlAccess //Shared (GUI/BATCH) settings XmlGlobalSettings() : programLanguage(retrieveSystemLanguage()), - fileTimeTolerance(2), //default 2s: FAT vs NTFS - ignoreOneHourDiff(true), - traverseDirectorySymlinks(false), - copyFileSymlinks(true), + ignoreOneHourDiff(false), lastUpdateCheck(0) {} int programLanguage; - unsigned int fileTimeTolerance; //max. allowed file time deviation bool ignoreOneHourDiff; //ignore +/- 1 hour due to DST change - bool traverseDirectorySymlinks; - bool copyFileSymlinks; //copy symbolic link instead of target file - long lastUpdateCheck; //time of last update check + long lastUpdateCheck; //time of last update check - WarningMessages warnings; + OptionalDialogs optDialogs; //--------------------------------------------------------------------- struct _Gui @@ -128,11 +125,6 @@ namespace xmlAccess isMaximized(false), autoAdjustColumnsLeft(false), autoAdjustColumnsRight(false), -#ifdef FFS_WIN - commandLineFileManager(wxT("explorer /select, %name")), -#elif defined FFS_LINUX - commandLineFileManager(wxT("konqueror \"%dir\"")), -#endif cfgHistoryMax(10), folderHistLeftMax(12), folderHistRightMax(12), @@ -140,9 +132,14 @@ namespace xmlAccess deleteOnBothSides(false), useRecyclerForManualDeletion(recycleBinAvailable()), //enable if OS supports it; else user will have to activate first and then get an error message showFileIconsLeft(true), - showFileIconsRight(true), - popupOnConfigChange(true), - showSummaryBeforeSync(true) {} + showFileIconsRight(true) + { +#ifdef FFS_WIN + externelApplications.push_back(std::make_pair(wxT("Open with Explorer"), wxT("explorer /select, %name"))); +#elif defined FFS_LINUX + externelApplications.push_back(std::make_pair(wxT("Open with Konqueror"), wxT("konqueror \"%dir\""))); +#endif + } int widthNotMaximized; int heightNotMaximized; @@ -156,7 +153,7 @@ namespace xmlAccess bool autoAdjustColumnsLeft; bool autoAdjustColumnsRight; - wxString commandLineFileManager; + ExternalApps externelApplications; std::vector<wxString> cfgFileHistory; unsigned int cfgHistoryMax; @@ -173,8 +170,6 @@ namespace xmlAccess bool useRecyclerForManualDeletion; bool showFileIconsLeft; bool showFileIconsRight; - bool popupOnConfigChange; - bool showSummaryBeforeSync; } gui; //--------------------------------------------------------------------- @@ -210,6 +205,8 @@ namespace xmlAccess void readBatchConfig(const wxString& filename, XmlBatchConfig& config); //throw (xmlAccess::XmlError); void readGlobalSettings( XmlGlobalSettings& config); //throw (xmlAccess::XmlError); + void readGuiOrBatchConfig(const wxString& filename, XmlGuiConfig& config); //throw (xmlAccess::XmlError); + void writeGuiConfig( const XmlGuiConfig& outputCfg, const wxString& filename); //throw (xmlAccess::XmlError); void writeBatchConfig( const XmlBatchConfig& outputCfg, const wxString& filename); //throw (xmlAccess::XmlError); void writeGlobalSettings(const XmlGlobalSettings& outputCfg); //throw (xmlAccess::XmlError); diff --git a/library/resources.cpp b/library/resources.cpp index 3e7b5a4e..1b9ce12d 100644 --- a/library/resources.cpp +++ b/library/resources.cpp @@ -4,8 +4,8 @@ #include <wx/image.h> #include <wx/icon.h> #include <wx/mstream.h> -#include "../shared/globalFunctions.h" -//#include "../shared/systemFunctions.h" +#include "../shared/systemConstants.h" +#include <memory> #include "../shared/standardPaths.h" @@ -58,6 +58,8 @@ GlobalResources::GlobalResources() bitmapResource[wxT("equal.png")] = (bitmapEqual = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("equalAct.png")] = (bitmapEqualAct = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("equalDeact.png")] = (bitmapEqualDeact = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("conflict.png")] = (bitmapConflict = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("conflictGrey.png")] = (bitmapConflictGrey = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("conflictAct.png")] = (bitmapConflictAct = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("conflictDeact.png")] = (bitmapConflictDeact = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("include.png")] = (bitmapInclude = new wxBitmap(wxNullBitmap)); @@ -65,6 +67,7 @@ GlobalResources::GlobalResources() bitmapResource[wxT("filter active.png")] = (bitmapFilterOn = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("filter not active.png")] = (bitmapFilterOff = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("filter_small.png")] = (bitmapFilterSmall = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("filterSmallGrey.png")] = (bitmapFilterSmallGrey = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("warning.png")] = (bitmapWarning = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("warningSmall.png")] = (bitmapWarningSmall = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("error.png")] = (bitmapError = new wxBitmap(wxNullBitmap)); @@ -72,6 +75,9 @@ GlobalResources::GlobalResources() bitmapResource[wxT("small arrow down.png")] = (bitmapSmallDown = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("save.png")] = (bitmapSave = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("load.png")] = (bitmapLoad = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("saveSmall.png")] = (bitmapSaveSmall = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("loadSmall.png")] = (bitmapLoadSmall = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("newSmall.png")] = (bitmapNewSmall = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("FFS.png")] = (bitmapFFS = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("FFS paused.png")] = (bitmapFFSPaused = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("deleteFile.png")] = (bitmapDeleteFile = new wxBitmap(wxNullBitmap)); @@ -108,6 +114,8 @@ GlobalResources::GlobalResources() bitmapResource[wxT("recycler.png")] = (bitmapRecycler = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("shift.png")] = (bitmapShift = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("syncConfig.png")] = (bitmapSyncCfg = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("syncConfigSmall.png")] = (bitmapSyncCfgSmall = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("syncConfigSmallGrey.png")] = (bitmapSyncCfgSmallGrey = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("cmpConfig.png")] = (bitmapCmpCfg = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("syncPreview.png")] = (bitmapPreview = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("syncPreviewDisabl.png")] = (bitmapPreviewDisabled = new wxBitmap(wxNullBitmap)); @@ -119,6 +127,7 @@ GlobalResources::GlobalResources() bitmapResource[wxT("france.png")] = (bitmapFrance = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("germany.png")] = (bitmapGermany = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("hungary.png")] = (bitmapHungary = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("taiwan.png")] = (bitmapTaiwan = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("italy.png")] = (bitmapItaly = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("japan.png")] = (bitmapJapan = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("poland.png")] = (bitmapPoland = new wxBitmap(wxNullBitmap)); diff --git a/library/resources.h b/library/resources.h index 5f4e51f0..804dea7a 100644 --- a/library/resources.h +++ b/library/resources.h @@ -54,6 +54,8 @@ public: wxBitmap* bitmapDifferent; wxBitmap* bitmapDifferentAct; wxBitmap* bitmapDifferentDeact; + wxBitmap* bitmapConflict; + wxBitmap* bitmapConflictGrey; wxBitmap* bitmapConflictAct; wxBitmap* bitmapConflictDeact; wxBitmap* bitmapInclude; @@ -61,6 +63,7 @@ public: wxBitmap* bitmapFilterOn; wxBitmap* bitmapFilterOff; wxBitmap* bitmapFilterSmall; + wxBitmap* bitmapFilterSmallGrey; wxBitmap* bitmapWarning; wxBitmap* bitmapWarningSmall; wxBitmap* bitmapError; @@ -68,6 +71,9 @@ public: wxBitmap* bitmapSmallDown; wxBitmap* bitmapSave; wxBitmap* bitmapLoad; + wxBitmap* bitmapSaveSmall; + wxBitmap* bitmapLoadSmall; + wxBitmap* bitmapNewSmall; wxBitmap* bitmapFFS; wxBitmap* bitmapFFSPaused; wxBitmap* bitmapDeleteFile; @@ -104,6 +110,8 @@ public: wxBitmap* bitmapRecycler; wxBitmap* bitmapShift; wxBitmap* bitmapSyncCfg; + wxBitmap* bitmapSyncCfgSmall; + wxBitmap* bitmapSyncCfgSmallGrey; wxBitmap* bitmapCmpCfg; wxBitmap* bitmapPreview; wxBitmap* bitmapPreviewDisabled; @@ -115,6 +123,7 @@ public: wxBitmap* bitmapFrance; wxBitmap* bitmapGermany; wxBitmap* bitmapHungary; + wxBitmap* bitmapTaiwan; wxBitmap* bitmapItaly; wxBitmap* bitmapJapan; wxBitmap* bitmapPoland; diff --git a/library/shadow.cpp b/library/shadow.cpp deleted file mode 100644 index c353bcb2..00000000 --- a/library/shadow.cpp +++ /dev/null @@ -1,148 +0,0 @@ -#include "shadow.h" -#include <wx/msw/wrapwin.h> //includes "windows.h" -#include <wx/intl.h> -#include "../structures.h" - -using FreeFileSync::ShadowCopy; - - -class ShadowlDllHandler //dynamically load windows API functions -{ - typedef bool (*CreateShadowCopyFct)( //volumeName must end with "\", while shadowVolName does not end with "\" - const wchar_t* volumeName, - wchar_t* shadowVolName, - unsigned int shadowBufferLen, - void** backupHandle, - wchar_t* errorMessage, - unsigned int errorBufferLen); - - typedef void (*ReleaseShadowCopyFct)(void* backupHandle); - -public: - static const ShadowlDllHandler& getInstance() - { - static ShadowlDllHandler instance; - return instance; - } - - CreateShadowCopyFct createShadowCopy; - ReleaseShadowCopyFct releaseShadowCopy; - -private: - ShadowlDllHandler() : - createShadowCopy(NULL), - releaseShadowCopy(NULL), - hShadow(NULL) - { - //get a handle to the DLL module containing the required functionality - hShadow = ::LoadLibrary(L"Shadow.dll"); - if (hShadow) - { - createShadowCopy = reinterpret_cast<CreateShadowCopyFct>(::GetProcAddress(hShadow, "createShadowCopy")); - releaseShadowCopy = reinterpret_cast<ReleaseShadowCopyFct>(::GetProcAddress(hShadow, "releaseShadowCopy")); - } - } - - ~ShadowlDllHandler() - { - if (hShadow) ::FreeLibrary(hShadow); - } - - HINSTANCE hShadow; -}; - - -ShadowCopy::ShadowCopy() : - backupHandle(NULL) {} - - -ShadowCopy::~ShadowCopy() -{ - if (backupHandle != NULL) - ShadowlDllHandler::getInstance().releaseShadowCopy(backupHandle); -} - - -bool ShadowCopy::isOkay() -{ - //check that all functions could be loaded - return ShadowlDllHandler::getInstance().createShadowCopy != NULL && - ShadowlDllHandler::getInstance().releaseShadowCopy != NULL; -} - - -Zstring ShadowCopy::makeShadowCopy(const Zstring& inputFile) throw(FreeFileSync::FileError) -{ - //check if shadow copy dll was loaded correctly - if (!isOkay()) - throw FileError(Zstring(_("Error copying locked file %x!")).Replace(wxT("%x"), Zstring(wxT("\"")) + inputFile + wxT("\"")) + wxT("\n\n") + - _("Error starting Volume Shadow Copy Service!") + wxT("\n") + - _("Please copy the appropriate \"Shadow.dll\" (located in \"Shadow.zip\" archive) into the FreeFileSync installation directory to enable this feature.")); - - - wchar_t volumeNameRaw[1000]; - - if (!GetVolumePathName(inputFile.c_str(), //__in LPCTSTR lpszFileName, - volumeNameRaw, //__out LPTSTR lpszVolumePathName, - 1000)) //__in DWORD cchBufferLength - throw FileError(Zstring(_("Error copying locked file %x!")).Replace(wxT("%x"), Zstring(wxT("\"")) + inputFile + wxT("\"")) + wxT("\n\n") + - _("Could not determine volume name for file:") + wxT("\n\"") + inputFile + wxT("\"")); - - Zstring volumeNameFormatted = volumeNameRaw; - if (!volumeNameFormatted.EndsWith(FreeFileSync::FILE_NAME_SEPARATOR)) - volumeNameFormatted += FreeFileSync::FILE_NAME_SEPARATOR; - - if (volumeNameFormatted != realVolumeLast) - { - //release old shadow copy - if (backupHandle != NULL) - { - ShadowlDllHandler::getInstance().releaseShadowCopy(backupHandle); - backupHandle = NULL; - } - realVolumeLast.clear(); //...if next call fails... - shadowVolumeLast.clear(); //...if next call fails... - - //start shadow volume copy service: - wchar_t shadowVolName[1000]; - void* backupHandleTmp = NULL; - wchar_t errorMessage[1000]; - - if (!ShadowlDllHandler::getInstance().createShadowCopy( - volumeNameFormatted.c_str(), - shadowVolName, - 1000, - &backupHandleTmp, - errorMessage, - 1000)) - throw FileError(Zstring(_("Error copying locked file %x!")).Replace(wxT("%x"), Zstring(wxT("\"")) + inputFile + wxT("\"")) + wxT("\n\n") + - _("Error starting Volume Shadow Copy Service!") + wxT("\n") + - wxT("(") + errorMessage + wxT(")")); - - realVolumeLast = volumeNameFormatted; - shadowVolumeLast = Zstring(shadowVolName) + FreeFileSync::FILE_NAME_SEPARATOR; //shadowVolName NEVER has a trailing backslash - backupHandle = backupHandleTmp; - } - - const size_t pos = inputFile.find(volumeNameFormatted); - if (pos == Zstring::npos) - { - Zstring msg = _("Volume name %x not part of filename %y!"); - msg.Replace(wxT("%x"), Zstring(wxT("\"")) + volumeNameFormatted + wxT("\""), false); - msg.Replace(wxT("%y"), Zstring(wxT("\"")) + inputFile + wxT("\""), false); - throw FileError(Zstring(_("Error copying locked file %x!")).Replace(wxT("%x"), Zstring(wxT("\"")) + inputFile + wxT("\"")) + wxT("\n\n") + - msg); - } - - //return filename alias on shadow copy volume - return shadowVolumeLast + Zstring(inputFile.c_str() + pos + volumeNameFormatted.length()); -} - - - - - - - - - diff --git a/library/shadow.h b/library/shadow.h deleted file mode 100644 index ded9d746..00000000 --- a/library/shadow.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef SHADOW_H_INCLUDED -#define SHADOW_H_INCLUDED - -#ifndef FFS_WIN -#warning //this header should be used in the windows build only! -#endif - -#include "zstring.h" -#include "fileError.h" - - -namespace FreeFileSync -{ - class ShadowCopy //buffer access to Windows Volume Shadow Copy Service - { - public: - ShadowCopy(); - ~ShadowCopy(); - - Zstring makeShadowCopy(const Zstring& inputFile) throw(FileError); //returns filename on shadow copy - - private: - bool isOkay(); - - Zstring realVolumeLast; //buffer last volume name - Zstring shadowVolumeLast; //buffer last created shadow volume - void* backupHandle; - }; -} - -#endif // SHADOW_H_INCLUDED diff --git a/library/statistics.cpp b/library/statistics.cpp index f317c114..08bb5dee 100644 --- a/library/statistics.cpp +++ b/library/statistics.cpp @@ -124,14 +124,14 @@ void Statistics::addMeasurement(const int objectsCurrent, const double dataCurre //remove all records earlier than "currentTime - windowSize" const long newBegin = newEntry.time - windowMax; - while (measurements.size() > 0 && measurements.front().time < newBegin) + while (!measurements.empty() && measurements.front().time < newBegin) measurements.pop_front(); } wxString Statistics::getRemainingTime() const { - if (measurements.size() > 0) + if (!measurements.empty()) { //find start of records "window" const record backElement = measurements.back(); @@ -158,7 +158,7 @@ wxString Statistics::getRemainingTime() const wxString Statistics::getBytesPerSecond() const { - if (measurements.size() > 0) + if (!measurements.empty()) { //find start of records "window" const long frontTime = measurements.back().time - windowSizeBPS; diff --git a/library/statistics.h b/library/statistics.h index f0eafad8..fe247b47 100644 --- a/library/statistics.h +++ b/library/statistics.h @@ -61,8 +61,8 @@ private: struct record { int objects; - double data; - long time; + double data; //unit: bytes + long time; //unit: milliseconds }; std::list<record> measurements; diff --git a/library/zstring.cpp b/library/zstring.cpp deleted file mode 100644 index b26ee451..00000000 --- a/library/zstring.cpp +++ /dev/null @@ -1,390 +0,0 @@ -#include "zstring.h" -#include "globalFunctions.h" - -#ifdef FFS_WIN -#include <wx/msw/wrapwin.h> //includes "windows.h" -#endif //FFS_WIN - - - -#ifdef __WXDEBUG__ -AllocationCount::~AllocationCount() -{ - if (count != 0) -#ifdef FFS_WIN - MessageBox(NULL, wxT("Fatal Error! Allocation problem with Zstring! (No problem if it occurs while Unit testing only!)"), wxString::Format(wxT("%i"), count), 0); -#else - std::abort(); -#endif -} - - -AllocationCount& AllocationCount::getInstance() -{ - static AllocationCount global; - return global; -} -#endif - - -#ifdef FFS_WIN -int FreeFileSync::compareStringsWin32(const wchar_t* a, const wchar_t* b, const int aCount, const int bCount) -{ - //DON'T use lstrcmpi() here! It uses word sort, which unfortunately is NOT always a strict weak sorting function for some locales (e.g. swedish) - //Use CompareString() with "SORT_STRINGSORT" instead!!! - - const int rv = CompareString( - LOCALE_USER_DEFAULT, //locale identifier - NORM_IGNORECASE | SORT_STRINGSORT, //comparison-style options - a, //pointer to first string - aCount, //size, in bytes or characters, of first string - b, //pointer to second string - bCount); //size, in bytes or characters, of second string - - if (rv == 0) - throw RuntimeException(wxString(wxT("Error comparing strings!"))); - else - return rv - 2; //convert to C-style string compare result -} -#endif - - -Zstring& Zstring::Replace(const DefaultChar* old, const DefaultChar* replacement, bool replaceAll) -{ - const size_t oldLen = defaultLength(old); - const size_t replacementLen = defaultLength(replacement); - - size_t pos = 0; - while (true) - { - pos = find(old, pos); - if (pos == npos) - break; - - replace(pos, oldLen, replacement, replacementLen); - pos += replacementLen; //move past the string that was replaced - - // stop now? - if (!replaceAll) - break; - } - return *this; -} - - -bool matchesHelper(const DefaultChar* string, const DefaultChar* mask) -{ - for (DefaultChar ch; (ch = *mask) != 0; ++mask) - { - switch (ch) - { - case DefaultChar('?'): - if (*string == 0) - return false; - else - ++string; - break; - - case DefaultChar('*'): - //advance to next non-*/? char - do - { - ++mask; - ch = *mask; - } - while (ch == DefaultChar('*') || ch == DefaultChar('?')); - //if match ends with '*': - if (ch == DefaultChar(0)) - return true; - - ++mask; - while ((string = defaultStrFind(string, ch)) != NULL) - { - if (matchesHelper(string + 1, mask)) - return true; - ++string; - } - return false; - - default: - if (*string != ch) - return false; - else - ++string; - } - } - return *string == 0; -} - - -bool Zstring::Matches(const DefaultChar* mask) const -{ - return matchesHelper(c_str(), mask); -} - - -bool Zstring::Matches(const DefaultChar* name, const DefaultChar* mask) -{ - return matchesHelper(name, mask); -} - - -Zstring& Zstring::Trim(bool fromRight) -{ - const size_t thisLen = length(); - if (thisLen == 0) - return *this; - - if (fromRight) - { - const DefaultChar* cursor = data + thisLen - 1; - while (cursor != data - 1 && defaultIsWhiteSpace(*cursor)) //break when pointing one char further than last skipped element - --cursor; - ++cursor; - - const size_t newLength = cursor - data; - if (newLength != thisLen) - { - if (descr->refCount > 1) //allocate new string - *this = Zstring(data, newLength); - else //overwrite this strin - { - descr->length = newLength; - data[newLength] = DefaultChar(0); - } - } - } - else - { - DefaultChar* cursor = data; - DefaultChar ch; - while ((ch = *cursor) != 0 && defaultIsWhiteSpace(ch)) - ++cursor; - - const size_t diff = cursor - data; - if (diff) - { - if (descr->refCount > 1) //allocate new string - *this = Zstring(cursor, thisLen - diff); - else - { //overwrite this string - data = cursor; //no problem when deallocating data, since descr points to begin of allocated area - descr->capacity -= diff; - descr->length -= diff; - } - } - } - - return *this; -} - - -Zstring& Zstring::MakeLower() -{ - const size_t thisLen = length(); - if (thisLen == 0) - return *this; - - if (descr->refCount > 1) //allocate new string - { - StringDescriptor* newDescr; - DefaultChar* newData; - allocate(thisLen, newDescr, newData); - - for (unsigned int i = 0; i < thisLen; ++i) - newData[i] = defaultToLower(data[i]); - newData[thisLen] = 0; - - decRef(); - descr = newDescr; - data = newData; - } - else - { //overwrite this string - for (unsigned int i = 0; i < thisLen; ++i) - data[i] = defaultToLower(data[i]); - } - - return *this; -} - - -//############################################################### -//std::string functions -Zstring Zstring::substr(size_t pos, size_t len) const -{ - if (len == npos) - { - assert(pos <= length()); - return Zstring(c_str() + pos, length() - pos); //reference counting not used: different length - } - else - { - assert(length() - pos >= len); - return Zstring(c_str() + pos, len); - } -} - - -size_t Zstring::rfind(const DefaultChar ch, size_t pos) const -{ - const size_t thisLen = length(); - if (thisLen == 0) - return npos; - - if (pos == npos) - pos = thisLen - 1; - else - assert(pos <= length()); - - do //pos points to last char of the string - { - if (data[pos] == ch) - return pos; - } - while (--pos != static_cast<size_t>(-1)); - return npos; -} - - -Zstring& Zstring::replace(size_t pos1, size_t n1, const DefaultChar* str, size_t n2) -{ - assert(str < c_str() || c_str() + length() < str); //str mustn't point to data in this string - assert(n1 <= length() - pos1); - - const size_t oldLen = length(); - if (oldLen == 0) - { - assert(pos1 == 0 && n1 == 0); - return *this = Zstring(str, n2); - } - - const size_t newLen = oldLen - n1 + n2; - if (newLen > oldLen || descr->refCount > 1) - { //allocate a new string - StringDescriptor* newDescr; - DefaultChar* newData; - allocate(newLen, newDescr, newData); - - //assemble new string with replacement - memcpy(newData, data, pos1 * sizeof(DefaultChar)); - memcpy(newData + pos1, str, n2 * sizeof(DefaultChar)); - memcpy(newData + pos1 + n2, data + pos1 + n1, (oldLen - pos1 - n1) * sizeof(DefaultChar)); - newData[newLen] = 0; - - decRef(); - data = newData; - descr = newDescr; - } - else //overwrite current string: case "n2 == 0" is handled implicitly - { - memcpy(data + pos1, str, n2 * sizeof(DefaultChar)); - if (newLen < oldLen) - { - memmove(data + pos1 + n2, data + pos1 + n1, (oldLen - pos1 - n1) * sizeof(DefaultChar)); - data[newLen] = 0; - descr->length = newLen; - } - } - - return *this; -} - - -Zstring& Zstring::operator=(const DefaultChar* source) -{ - const size_t sourceLen = defaultLength(source); - if (sourceLen == 0) - return *this = Zstring(); - - if (descr->refCount > 1 || descr->capacity < sourceLen) //allocate new string - *this = Zstring(source, sourceLen); - else - { //overwrite this string - memcpy(data, source, sourceLen * sizeof(DefaultChar)); - data[sourceLen] = 0; - descr->length = sourceLen; - } - return *this; -} - - -Zstring& Zstring::operator+=(const Zstring& other) -{ - const size_t otherLen = other.length(); - if (otherLen != 0) - { - const size_t thisLen = length(); - const size_t newLen = thisLen + otherLen; - copyBeforeWrite(newLen); - - memcpy(data + thisLen, other.c_str(), otherLen * sizeof(DefaultChar)); - data[newLen] = 0; - descr->length = newLen; - } - return *this; -} - - -Zstring& Zstring::operator+=(const DefaultChar* other) -{ - const size_t otherLen = defaultLength(other); - if (otherLen != 0) - { - const size_t thisLen = length(); - const size_t newLen = thisLen + otherLen; - copyBeforeWrite(newLen); - - memcpy(data + thisLen, other, otherLen * sizeof(DefaultChar)); - data[newLen] = 0; - descr->length = newLen; - } - return *this; -} - - -Zstring& Zstring::operator+=(DefaultChar ch) -{ - const size_t oldLen = length(); - copyBeforeWrite(oldLen + 1); - data[oldLen] = ch; - data[oldLen + 1] = 0; - ++descr->length; - return *this; -} - - -void Zstring::copyBeforeWrite(const size_t capacityNeeded) -{ - assert(capacityNeeded != 0); - - if (descr->refCount > 1) - { //allocate a new string - const size_t oldLength = length(); - assert(oldLength <= getCapacityToAllocate(capacityNeeded)); - - StringDescriptor* newDescr; - DefaultChar* newData; - allocate(capacityNeeded, newDescr, newData); - newDescr->length = oldLength; - - if (oldLength) - { - memcpy(newData, data, oldLength * sizeof(DefaultChar)); - newData[oldLength] = 0; - } - decRef(); - descr = newDescr; - data = newData; - } - else if (descr->capacity < capacityNeeded) - { //try to resize the current string (allocate anew if necessary) - const size_t newCapacity = getCapacityToAllocate(capacityNeeded); - - descr = (StringDescriptor*) realloc(descr, sizeof(StringDescriptor) + (newCapacity + 1) * sizeof(DefaultChar)); - if (descr == NULL) - throw std::bad_alloc(); - data = (DefaultChar*)(descr + 1); - descr->capacity = newCapacity; - } -} diff --git a/library/zstring.h b/library/zstring.h deleted file mode 100644 index bca50862..00000000 --- a/library/zstring.h +++ /dev/null @@ -1,712 +0,0 @@ -/*************************************************************** - * Purpose: High performance string class - * Author: ZenJu (zhnmju123@gmx.de) - * Created: Jan. 2009 - **************************************************************/ - -#ifndef ZSTRING_H_INCLUDED -#define ZSTRING_H_INCLUDED - -#include <cstring> -#include <cctype> -#include <assert.h> -#include <new> - - -namespace FreeFileSync -{ -#ifdef FFS_WIN - //super fast case-insensitive string comparison: way faster than wxString::CmpNoCase()!!! - int compareStringsWin32(const wchar_t* a, const wchar_t* b, const int aCount = -1, const int bCount = -1); -#endif //FFS_WIN -} - - -#ifdef ZSTRING_CHAR -typedef char DefaultChar; //use char strings -#elif defined ZSTRING_WIDE_CHAR -typedef wchar_t DefaultChar; //use wide character strings -#endif - -class Zsubstr; - -class Zstring -{ -public: - Zstring(); - Zstring(const DefaultChar* source); //string is copied: O(length) - Zstring(const DefaultChar* source, size_t length); //string is copied: O(length) - Zstring(const Zstring& source); //reference-counting => O(1) - ~Zstring(); - - operator const DefaultChar*() const; //implicit conversion to C string - - //wxWidgets functions - bool StartsWith(const DefaultChar* begin) const; - bool StartsWith(const Zstring& begin) const; - bool EndsWith(const DefaultChar* end) const; - bool EndsWith(const DefaultChar end) const; - bool EndsWith(const Zstring& end) const; -#ifdef FFS_WIN - int CmpNoCase(const DefaultChar* other) const; - int CmpNoCase(const Zstring& other) const; -#endif - int Cmp(const DefaultChar* other) const; - int Cmp(const Zstring& other) const; - Zstring& Replace(const DefaultChar* old, const DefaultChar* replacement, bool replaceAll = true); - Zstring AfterLast(DefaultChar ch) const; - Zstring BeforeLast(DefaultChar ch) const; - size_t Find(DefaultChar ch, bool fromEnd) const; - bool Matches(const DefaultChar* mask) const; - static bool Matches(const DefaultChar* name, const DefaultChar* mask); - Zstring& Trim(bool fromRight); //from right or left - Zstring& MakeLower(); - - //std::string functions - size_t length() const; - const DefaultChar* c_str() const; - Zstring substr(size_t pos = 0, size_t len = npos) const; //allocate new string - Zsubstr zsubstr(size_t pos = 0) const; //use ref-counting! - bool empty() const; - void clear(); - int compare(const DefaultChar* other) const; - int compare(const Zstring& other) const; - int compare(const size_t pos1, const size_t n1, const DefaultChar* other) const; - size_t find(const DefaultChar* str, const size_t pos = 0 ) const; - size_t find(const DefaultChar ch, const size_t pos = 0) const; - size_t rfind(const DefaultChar ch, size_t pos = npos) const; - Zstring& replace(size_t pos1, size_t n1, const DefaultChar* str, size_t n2); - size_t size() const; - - Zstring& operator=(const Zstring& source); - Zstring& operator=(const DefaultChar* source); - - bool operator == (const Zstring& other) const; - bool operator == (const DefaultChar* other) const; - bool operator < (const Zstring& other) const; - bool operator < (const DefaultChar* other) const; - bool operator != (const Zstring& other) const; - bool operator != (const DefaultChar* other) const; - - DefaultChar operator[](const size_t pos) const; - - Zstring& operator+=(const Zstring& other); - Zstring& operator+=(const DefaultChar* other); - Zstring& operator+=(DefaultChar ch); - - const Zstring operator+(const Zstring& string2) const; - const Zstring operator+(const DefaultChar* string2) const; - const Zstring operator+(const DefaultChar ch) const; - - static const size_t npos = static_cast<size_t>(-1); - -private: - void initAndCopy(const DefaultChar* source, size_t length); - void incRef() const; //support for reference-counting - void decRef(); // - void copyBeforeWrite(const size_t capacityNeeded); //and copy-on-write - - struct StringDescriptor - { - mutable unsigned int refCount; - size_t length; - size_t capacity; //allocated length without null-termination - }; - static void allocate(const size_t newLength, StringDescriptor*& newDescr, DefaultChar*& newData); - - StringDescriptor* descr; - DefaultChar* data; -}; - - -class Zsubstr //ref-counted substring of a Zstring -{ -public: - Zsubstr(); - Zsubstr(const Zstring& ref, const size_t pos); - - const DefaultChar* c_str() const; - size_t length() const; - bool StartsWith(const Zstring& begin) const; - size_t findFromEnd(const DefaultChar ch) const; - -private: - Zstring m_ref; - const DefaultChar* m_data; - size_t m_length; -}; - - -//####################################################################################### -//begin of implementation - -#ifdef ZSTRING_CHAR -inline -size_t defaultLength(const char* input) -{ - return strlen(input); -} - -inline -int defaultCompare(const char* str1, const char* str2) -{ - return strcmp(str1, str2); -} - -inline -int defaultCompare(const char* str1, const char* str2, const size_t count) -{ - return strncmp(str1, str2, count); -} - -inline -char* defaultStrFind(const char* str1, const char* str2) -{ - return strstr(str1, str2); -} - -inline -char* defaultStrFind(const char* str1, int ch) -{ - return strchr(str1, ch); -} - -inline -bool defaultIsWhiteSpace(const char ch) -{ - // some compilers (e.g. VC++ 6.0) return true for a call to isspace('\xEA') => exclude char(128) to char(255) - return ((unsigned char)ch < 128) && isspace((unsigned char)ch) != 0; -} - -inline -char defaultToLower(const char ch) -{ - return tolower((unsigned char)ch); //caution: although tolower() has int as input parameter it expects unsigned chars! -} - -#elif defined ZSTRING_WIDE_CHAR -inline -size_t defaultLength(const wchar_t* input) -{ - return wcslen(input); -} - -inline -int defaultCompare(const wchar_t* str1, const wchar_t* str2) -{ - return wcscmp(str1, str2); -} - -inline -int defaultCompare(const wchar_t* str1, const wchar_t* str2, const size_t count) -{ - return wcsncmp(str1, str2, count); -} - -inline -const wchar_t* defaultStrFind(const wchar_t* str1, const wchar_t* str2) -{ - return wcsstr(str1, str2); -} - -inline -const wchar_t* defaultStrFind(const wchar_t* str1, int ch) -{ - return wcschr(str1, ch); -} - -inline -bool defaultIsWhiteSpace(const wchar_t ch) -{ - // some compilers (e.g. VC++ 6.0) return true for a call to isspace('\xEA') => exclude char(128) to char(255) - return (ch < 128 || ch > 255) && iswspace(ch) != 0; -} - -inline -wchar_t defaultToLower(const wchar_t ch) -{ - return towlower(ch); -} -#endif - - -#ifdef __WXDEBUG__ -class AllocationCount //small test for memory leaks in Zstring -{ -public: - void inc() - { - ++count; - } - - void dec() - { - --count; - } - - static AllocationCount& getInstance(); - -private: - AllocationCount() : count(0) {} - ~AllocationCount(); - - int count; -}; -#endif - - -inline -size_t getCapacityToAllocate(const size_t length) -{ - return (length + (19 - length % 16)); //allocate some additional length to speed up concatenation -} - - -inline -void Zstring::allocate(const size_t newLength, - StringDescriptor*& newDescr, - DefaultChar*& newData) -{ //allocate and set data for new string - const size_t newCapacity = getCapacityToAllocate(newLength); - assert(newCapacity); - - newDescr = static_cast<StringDescriptor*>(operator new [] (sizeof(StringDescriptor) + (newCapacity + 1) * sizeof(DefaultChar))); - newData = reinterpret_cast<DefaultChar*>(newDescr + 1); - - newDescr->refCount = 1; - newDescr->length = newLength; - newDescr->capacity = newCapacity; - -#ifdef __WXDEBUG__ - AllocationCount::getInstance().inc(); //test Zstring for memory leaks -#endif -} - - -inline -Zstring::Zstring() -{ - //static (dummy) empty Zstring -#ifdef ZSTRING_CHAR - static Zstring emptyString(""); -#elif defined ZSTRING_WIDE_CHAR - static Zstring emptyString(L""); -#endif - - emptyString.incRef(); - descr = emptyString.descr; - data = emptyString.data; -} - - -inline -Zstring::Zstring(const DefaultChar* source) -{ - initAndCopy(source, defaultLength(source)); -} - - -inline -Zstring::Zstring(const DefaultChar* source, size_t length) -{ - initAndCopy(source, length); -} - - -inline -Zstring::Zstring(const Zstring& source) -{ - descr = source.descr; - data = source.data; - incRef(); //reference counting! -} - - -inline -Zstring::~Zstring() -{ - decRef(); -} - - -inline -void Zstring::initAndCopy(const DefaultChar* source, size_t length) -{ - allocate(length, descr, data); - memcpy(data, source, length * sizeof(DefaultChar)); - data[length] = 0; -} - - -inline -void Zstring::incRef() const -{ - assert(descr); - ++descr->refCount; -} - - -inline -void Zstring::decRef() -{ - assert(descr && descr->refCount >= 1); //descr points to the begin of the allocated memory block - if (--descr->refCount == 0) - { - operator delete [] (descr); //this must NEVER be changed!! E.g. Trim() relies on descr being start of allocated memory block - descr = NULL; -#ifdef __WXDEBUG__ - AllocationCount::getInstance().dec(); //test Zstring for memory leaks -#endif - } -} - - -#ifdef FFS_WIN -inline -int Zstring::CmpNoCase(const DefaultChar* other) const -{ - return FreeFileSync::compareStringsWin32(c_str(), other); //way faster than wxString::CmpNoCase()!! -} - - -inline -int Zstring::CmpNoCase(const Zstring& other) const -{ - return FreeFileSync::compareStringsWin32(c_str(), other.c_str(), length(), other.length()); //way faster than wxString::CmpNoCase()!! -} -#endif - - -inline -Zstring::operator const DefaultChar*() const -{ - return c_str(); -} - - -inline -Zstring& Zstring::operator=(const Zstring& source) -{ - source.incRef(); //implicitly handle case "this == &source" and avoid this check - decRef(); // - descr = source.descr; - data = source.data; - - return *this; -} - - -inline -size_t Zstring::Find(DefaultChar ch, bool fromEnd) const -{ - if (fromEnd) - return rfind(ch, npos); - else - return find(ch, 0); -} - - -// get all characters after the last occurence of ch -// (returns the whole string if ch not found) -inline -Zstring Zstring::AfterLast(DefaultChar ch) const -{ - size_t pos = rfind(ch, npos); - if (pos == npos ) - return *this; - else - return c_str() + pos + 1; -} - - -// get all characters before the last occurence of ch -// (returns empty string if ch not found) -inline -Zstring Zstring::BeforeLast(DefaultChar ch) const -{ - size_t pos = rfind(ch, npos); - - if (pos != npos && pos != 0 ) - return Zstring(data, pos); //data is non-empty string in this context: else ch would not have been found! - else - return Zstring(); -} - - -inline -bool Zstring::StartsWith(const DefaultChar* begin) const -{ - const size_t beginLength = defaultLength(begin); - if (length() < beginLength) - return false; - return compare(0, beginLength, begin) == 0; -} - - -inline -bool Zstring::StartsWith(const Zstring& begin) const -{ - const size_t beginLength = begin.length(); - if (length() < beginLength) - return false; - return compare(0, beginLength, begin) == 0; -} - - -inline -bool Zstring::EndsWith(const DefaultChar* end) const -{ - const size_t thisLength = length(); - const size_t endLength = defaultLength(end); - if (thisLength < endLength) - return false; - return compare(thisLength - endLength, endLength, end) == 0; -} - - -inline -bool Zstring::EndsWith(const DefaultChar end) const -{ - const size_t thisLength = length(); - if (thisLength < 1) - return false; - return *(c_str() + thisLength - 1) == end; -} - - -inline -bool Zstring::EndsWith(const Zstring& end) const -{ - const size_t thisLength = length(); - const size_t endLength = end.length(); - if (thisLength < endLength) - return false; - return compare(thisLength - endLength, endLength, end) == 0; -} - - -inline -size_t Zstring::find(const DefaultChar* str, const size_t pos) const -{ - assert(pos <= length()); - const DefaultChar* thisStr = c_str(); - const DefaultChar* found = defaultStrFind(thisStr + pos, str); - return found == NULL ? npos : found - thisStr; -} - - -inline -size_t Zstring::find(const DefaultChar ch, const size_t pos) const -{ - assert(pos <= length()); - const DefaultChar* thisStr = c_str(); - const DefaultChar* found = defaultStrFind(thisStr + pos, ch); - return found == NULL ? npos : found - thisStr; -} - - -inline -int Zstring::Cmp(const DefaultChar* other) const -{ - return compare(other); -} - - -inline -int Zstring::Cmp(const Zstring& other) const -{ - return defaultCompare(c_str(), other.c_str()); //overload using strcmp(char*, char*) should be fastest! -} - - -inline -bool Zstring::operator == (const Zstring& other) const -{ - return length() != other.length() ? false : defaultCompare(c_str(), other.c_str()) == 0; -} - - -inline -bool Zstring::operator == (const DefaultChar* other) const -{ - return defaultCompare(c_str(), other) == 0; //overload using strcmp(char*, char*) should be fastest! -} - - -inline -bool Zstring::operator < (const Zstring& other) const -{ - return defaultCompare(c_str(), other.c_str()) < 0; -} - - -inline -bool Zstring::operator < (const DefaultChar* other) const -{ - return defaultCompare(c_str(), other) < 0; //overload using strcmp(char*, char*) should be fastest! -} - - -inline -bool Zstring::operator != (const Zstring& other) const -{ - return length() != other.length() ? true: defaultCompare(c_str(), other.c_str()) != 0; -} - - -inline -bool Zstring::operator != (const DefaultChar* other) const -{ - return defaultCompare(c_str(), other) != 0; //overload using strcmp(char*, char*) should be fastest! -} - - -inline -int Zstring::compare(const Zstring& other) const -{ - return defaultCompare(c_str(), other.c_str()); //overload using strcmp(char*, char*) should be fastest! -} - - -inline -int Zstring::compare(const DefaultChar* other) const -{ - return defaultCompare(c_str(), other); //overload using strcmp(char*, char*) should be fastest! -} - - -inline -int Zstring::compare(const size_t pos1, const size_t n1, const DefaultChar* other) const -{ - assert(length() - pos1 >= n1); - return defaultCompare(c_str() + pos1, other, n1); -} - - -inline -size_t Zstring::length() const -{ - return descr->length; -} - - -inline -size_t Zstring::size() const -{ - return descr->length; -} - - -inline -const DefaultChar* Zstring::c_str() const -{ - return data; -} - - -inline -bool Zstring::empty() const -{ - return descr->length == 0; -} - - -inline -void Zstring::clear() -{ - *this = Zstring(); -} - - -inline -DefaultChar Zstring::operator[](const size_t pos) const -{ - assert(pos < length()); - return data[pos]; -} - - -inline -const Zstring Zstring::operator+(const Zstring& string2) const -{ - return Zstring(*this)+=string2; -} - - -inline -const Zstring Zstring::operator+(const DefaultChar* string2) const -{ - return Zstring(*this)+=string2; -} - - -inline -const Zstring Zstring::operator+(const DefaultChar ch) const -{ - return Zstring(*this)+=ch; -} - -//##################### Zsubstr ############################# -inline -Zsubstr Zstring::zsubstr(size_t pos) const -{ - assert(pos <= length()); - return Zsubstr(*this, pos); //return reference counted string -} - - -inline -Zsubstr::Zsubstr() -{ - m_data = m_ref.c_str(); - m_length = 0; -} - - -inline -Zsubstr::Zsubstr(const Zstring& ref, const size_t pos) : - m_ref(ref), - m_data(ref.c_str() + pos), - m_length(ref.length() - pos) {} - - -inline -const DefaultChar* Zsubstr::c_str() const -{ - return m_data; -} - - -inline -size_t Zsubstr::length() const -{ - return m_length; -} - - -inline -bool Zsubstr::StartsWith(const Zstring& begin) const -{ - const size_t beginLength = begin.length(); - if (length() < beginLength) - return false; - - return defaultCompare(m_data, begin.c_str(), beginLength) == 0; -} - - -inline -size_t Zsubstr::findFromEnd(const DefaultChar ch) const -{ - size_t pos = length(); - while (--pos != static_cast<size_t>(-1)) - { - if (m_data[pos] == ch) - return pos; - } - return Zstring::npos; -} - - - -#endif // ZSTRING_H_INCLUDED diff --git a/shared/customTooltip.cpp b/shared/customTooltip.cpp index bffaa45d..cd9fbf05 100644 --- a/shared/customTooltip.cpp +++ b/shared/customTooltip.cpp @@ -37,8 +37,7 @@ PopupFrameGenerated::PopupFrameGenerated( bSizer158->Add( m_bitmapLeft, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); m_staticTextMain = new wxStaticText( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 ); - m_staticTextMain->Wrap( -1 ); - bSizer158->Add( m_staticTextMain, 0, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); + bSizer158->Add( m_staticTextMain, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5 ); this->SetSizer( bSizer158 ); this->Layout(); @@ -67,7 +66,10 @@ void CustomTooltip::show(const wxString& text, wxPoint pos, const wxBitmap* bmp) } if (text != tipWindow->m_staticTextMain->GetLabel()) + { tipWindow->m_staticTextMain->SetLabel(text); + tipWindow->m_staticTextMain->Wrap(600); + } #ifdef FFS_LINUX tipWindow->Fit(); //Alas Fit() seems to be somewhat broken => this needs to be called EVERY time inside show, not only if text or bmp change. diff --git a/shared/fileHandling.cpp b/shared/fileHandling.cpp index a5d3cf57..55fd7ff5 100644 --- a/shared/fileHandling.cpp +++ b/shared/fileHandling.cpp @@ -1,8 +1,13 @@ #include "fileHandling.h" #include <wx/intl.h> #include "systemFunctions.h" -#include "globalFunctions.h" +#include "systemConstants.h" #include "fileTraverser.h" +#include <wx/file.h> +#include <stdexcept> +#include <boost/bind.hpp> +#include <algorithm> +#include <wx/log.h> #ifdef FFS_WIN #include <wx/msw/wrapwin.h> //includes "windows.h" @@ -55,7 +60,7 @@ public: return recycleBinAvailable; } - bool moveToRecycleBin(const Zstring& filename) const; //throw (RuntimeException) + bool moveToRecycleBin(const Zstring& filename) const; //throw (std::logic_error) private: RecycleBin() : @@ -73,10 +78,10 @@ private: }; -bool RecycleBin::moveToRecycleBin(const Zstring& filename) const //throw (RuntimeException) +bool RecycleBin::moveToRecycleBin(const Zstring& filename) const //throw (std::logic_error) { if (!recycleBinAvailable) //this method should ONLY be called if recycle bin is available - throw RuntimeException(_("Initialization of Recycle Bin failed!")); + throw std::logic_error("Initialization of Recycle Bin failed!"); #ifdef FFS_WIN Zstring filenameDoubleNull = filename + wxChar(0); @@ -105,7 +110,7 @@ bool FreeFileSync::recycleBinExists() inline -bool moveToRecycleBin(const Zstring& filename) //throw (RuntimeException) +bool moveToRecycleBin(const Zstring& filename) //throw (std::logic_error) { return RecycleBin::getInstance().moveToRecycleBin(filename); } @@ -159,7 +164,54 @@ bool FreeFileSync::symlinkExists(const DefaultChar* objname) } -void FreeFileSync::removeFile(const Zstring& filename, const bool useRecycleBin) //throw (FileError, ::RuntimeException); +bool FreeFileSync::isMovable(const Zstring& pathFrom, const Zstring& pathTo) +{ + wxLogNull noWxLogs; //prevent wxWidgets logging if dummy file creation failed + + const Zstring dummyFileSource = pathFrom.EndsWith(globalFunctions::FILE_NAME_SEPARATOR) ? + pathFrom + wxT("DeleteMe.tmp") : + pathFrom + globalFunctions::FILE_NAME_SEPARATOR + wxT("DeleteMe.tmp"); + + const Zstring dummyFileTarget = pathTo.EndsWith(globalFunctions::FILE_NAME_SEPARATOR) ? + pathTo + wxT("DeleteMe.tmp") : + pathTo + globalFunctions::FILE_NAME_SEPARATOR + wxT("DeleteMe.tmp"); + try + { + removeFile(dummyFileSource, false); + removeFile(dummyFileTarget, false); + } + catch (...) {} + + //create dummy file + { + wxFile dummy(dummyFileSource.c_str(), wxFile::write); + if (!dummy.IsOpened()) + return false; //if there's no write access, files can't be moved neither + dummy.Write(wxT("FreeFileSync dummy file. May be deleted safely.\n")); + } + + const bool result = + //try to move the file +#ifdef FFS_WIN + ::MoveFileEx(dummyFileSource.c_str(), //__in LPCTSTR lpExistingFileName, + dummyFileTarget.c_str(), //__in_opt LPCTSTR lpNewFileName, + 0) != 0; //__in DWORD dwFlags +#elif defined FFS_LINUX + ::rename(dummyFileSource.c_str(), dummyFileTarget.c_str()) == 0; +#endif + + try + { + removeFile(dummyFileSource, false); + removeFile(dummyFileTarget, false); + } + catch (...) {} + + return result; +} + + +void FreeFileSync::removeFile(const Zstring& filename, const bool useRecycleBin) //throw (FileError, std::logic_error); { //no error situation if file is not existing! manual deletion relies on it! #ifdef FFS_WIN @@ -232,7 +284,7 @@ private: void FreeFileSync::moveFile(const Zstring& sourceFile, const Zstring& targetFile, MoveFileCallback* callback) //throw (FileError); { - if (fileExists(targetFile)) //test file existence: e.g. Linux might silently overwrite existing symlinks + if (fileExists(targetFile.c_str())) //test file existence: e.g. Linux might silently overwrite existing symlinks { const wxString errorMessage = wxString(_("Error moving file:")) + wxT("\n\"") + sourceFile + wxT("\" ->\n\"") + targetFile + wxT("\""); throw FileError(errorMessage + wxT("\n\n") + _("Target file already existing!")); @@ -481,18 +533,21 @@ void FreeFileSync::removeDirectory(const Zstring& directory, const bool useRecyc return; } -//attention: check if directory is a symlink! Do NOT traverse into it deleting contained files!!! + #ifdef FFS_WIN - if (dirAttr & FILE_ATTRIBUTE_REPARSE_POINT) //remove symlink directly, support for \\?\-prefix + //initialize file attributes + if (!::SetFileAttributes( // initialize file attributes: actually NEEDED for symbolic links also! + directory.c_str(), // address of directory name + FILE_ATTRIBUTE_NORMAL)) // attributes to set { - if (!::SetFileAttributes( // initialize file attributes: actually NEEDED for symbolic links also! - directory.c_str(), // address of directory name - FILE_ATTRIBUTE_NORMAL)) // attributes to set - { - wxString errorMessage = wxString(_("Error deleting directory:")) + wxT("\n\"") + directory.c_str() + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - } + wxString errorMessage = wxString(_("Error deleting directory:")) + wxT("\n\"") + directory.c_str() + wxT("\""); + throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); + } + +//attention: check if directory is a symlink! Do NOT traverse into it deleting contained files!!! + if (dirAttr & FILE_ATTRIBUTE_REPARSE_POINT) //remove symlink directly, support for \\?\-prefix + { if (!::RemoveDirectory(directory.c_str())) { wxString errorMessage = wxString(_("Error deleting directory:")) + wxT("\n\"") + directory.c_str() + wxT("\""); @@ -521,24 +576,13 @@ void FreeFileSync::removeDirectory(const Zstring& directory, const bool useRecyc FreeFileSync::traverseFolder(directory, false, &traverser); //delete files - for (std::vector<Zstring>::const_iterator j = fileList.begin(); j != fileList.end(); ++j) - FreeFileSync::removeFile(*j, false); + std::for_each(fileList.begin(), fileList.end(), boost::bind(removeFile, _1, false)); //delete directories recursively - for (std::vector<Zstring>::const_iterator j = dirList.begin(); j != dirList.end(); ++j) - FreeFileSync::removeDirectory(*j, false); //call recursively to correctly handle symbolic links + std::for_each(dirList.begin(), dirList.end(), boost::bind(removeDirectory, _1, false)); //call recursively to correctly handle symbolic links //parent directory is deleted last #ifdef FFS_WIN - //initialize file attributes - if (!::SetFileAttributes( - directory.c_str(), // address of directory name - FILE_ATTRIBUTE_NORMAL)) // attributes to set - { - wxString errorMessage = wxString(_("Error deleting directory:")) + wxT("\n\"") + directory.c_str() + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - } - //remove directory, support for \\?\-prefix if (!::RemoveDirectory(directory.c_str())) { @@ -563,14 +607,77 @@ public: ~CloseHandleOnExit() { - CloseHandle(fileHandle_); + ::CloseHandle(fileHandle_); } private: HANDLE fileHandle_; }; +#endif + + +//optionally: copy directory last change date, DO NOTHING if something fails +void FreeFileSync::copyDirLastChangeDate(const Zstring& sourceDir, const Zstring& targetDir) +{ + if (symlinkExists(sourceDir)) //don't handle symlinks (yet) + return; + +#ifdef FFS_WIN + HANDLE hDirRead = ::CreateFile(sourceDir.c_str(), + 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, //needed for directories + NULL); + + if (hDirRead == INVALID_HANDLE_VALUE) + return; + CloseHandleOnExit dummy(hDirRead); + + FILETIME lastWriteTime; + if (::GetFileTime(hDirRead, + NULL, + NULL, + &lastWriteTime)) + { + HANDLE hDirWrite = ::CreateFile(targetDir.c_str(), + FILE_WRITE_ATTRIBUTES, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, //needed for directories + NULL); + + if (hDirWrite != INVALID_HANDLE_VALUE) + { + CloseHandleOnExit dummy2(hDirWrite); + + //(try to) set new "last write time" + ::SetFileTime(hDirWrite, + NULL, + NULL, + &lastWriteTime); //return value not evalutated! + } + } +#elif defined FFS_LINUX + struct stat dirInfo; + if (::stat(sourceDir.c_str(), &dirInfo) == 0) //read file attributes from source directory + { + //adapt file modification time: + struct utimbuf newTimes; + ::time(&newTimes.actime); //set file access time to current time + newTimes.modtime = dirInfo.st_mtime; + //(try to) set new "last write time" + ::utime(targetDir.c_str(), &newTimes); //return value not evalutated! + } +#endif +} + + +#ifdef FFS_WIN class KernelDllHandler //dynamically load windows API functions { typedef DWORD (WINAPI *GetFinalPath)( @@ -640,6 +747,53 @@ Zstring resolveDirectorySymlink(const Zstring& dirLinkName) //get full target pa return targetPath; } + + +//#include <aclapi.h> +//optionally: copy additional metadata, DO NOTHING if something fails +//void copyAdditionalMetadata(const Zstring& sourceDir, const Zstring& targetDir) +//{ +// PSECURITY_DESCRIPTOR pSD; +// +// PSID pSidOwner; +// PSID pSidGroup; +// PACL pDacl; +// PACL pSacl; +// if (::GetNamedSecurityInfo( +// const_cast<DefaultChar*>(sourceDir.c_str()), +// SE_FILE_OBJECT, //file or directory +// OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION, +// &pSidOwner, +// &pSidGroup, +// &pDacl, +// &pSacl, +// &pSD +// ) == ERROR_SUCCESS) +// { +// //(try to) set new security information +// if (::SetNamedSecurityInfo( +// const_cast<DefaultChar*>(targetDir.c_str()), +// SE_FILE_OBJECT, //file or directory +// OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION, +// pSidOwner, +// pSidGroup, +// pDacl, +// pSacl) != ERROR_SUCCESS) //return value not evalutated! +// { +// const wxString errorMessage = wxString(wxT("Error 2:")) + wxT("\n\"") + targetDir.c_str() + wxT("\""); +// throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); +// } +//warning BUG! +// +// LocalFree(pSD); //pSidOwner, pSidGroup, pDacl, pSacl must not be freed! +// } +// else +// { +// const wxString errorMessage = wxString(wxT("Error 1:")) + wxT("\n\"") + sourceDir.c_str() + wxT("\""); +// throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); +// } +// +//} #endif @@ -694,6 +848,9 @@ void createDirectoryRecursively(const Zstring& directory, const Zstring& templat const wxString errorMessage = wxString(_("Error creating directory:")) + wxT("\n\"") + directory.c_str() + wxT("\""); throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); } + + //(try to) copy additional metadata like last modification time: no measurable performance drawback + //copyAdditionalMetadata(linkPath, directory); } } else //in all other cases @@ -706,6 +863,9 @@ void createDirectoryRecursively(const Zstring& directory, const Zstring& templat const wxString errorMessage = wxString(_("Error creating directory:")) + wxT("\n\"") + directory.c_str() + wxT("\""); throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); } + + //(try to) copy additional metadata like last modification time: no measurable performance drawback + //copyAdditionalMetadata(templateDir, directory); } } #elif defined FFS_LINUX @@ -874,13 +1034,15 @@ void FreeFileSync::copyFile(const Zstring& sourceFile, //don't suppress "lastError == ERROR_REQUEST_ABORTED": an user aborted operation IS an error condition! //if file is locked (try to) use Windows Volume Shadow Copy Service - if (lastError == ERROR_SHARING_VIOLATION && shadowCopyHandler != NULL) + if (shadowCopyHandler != NULL && + (lastError == ERROR_SHARING_VIOLATION || + lastError == ERROR_LOCK_VIOLATION)) { const Zstring shadowFilename(shadowCopyHandler->makeShadowCopy(sourceFile)); FreeFileSync::copyFile(shadowFilename, //transferred bytes is automatically reset when new file is copied targetFile, copyFileSymLinks, - shadowCopyHandler, + NULL, callback); return; } @@ -958,7 +1120,7 @@ void FreeFileSync::copyFile(const Zstring& sourceFile, //begin of regular file copy struct stat fileInfo; - if (stat(sourceFile.c_str(), &fileInfo) != 0) //read file attributes from source file (resolving symlinks) + if (stat(sourceFile.c_str(), &fileInfo) != 0) //read file attributes from source file (resolving symlinks; but cannot be one in this context) { const wxString errorMessage = wxString(_("Error reading file attributes:")) + wxT("\n\"") + sourceFile.c_str() + wxT("\""); throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); diff --git a/shared/fileHandling.h b/shared/fileHandling.h index b5e75c17..5ccd595f 100644 --- a/shared/fileHandling.h +++ b/shared/fileHandling.h @@ -10,16 +10,22 @@ namespace FreeFileSync { Zstring getFormattedDirectoryName(const Zstring& dirname); - bool fileExists(const DefaultChar* filename); //replaces wxFileExists()! - bool dirExists(const DefaultChar* dirname); //replaces wxDirExists(): optional 'cause wxDirExists treats symlinks correctly - bool symlinkExists(const DefaultChar* objname); //check if a symbolic link exists + bool fileExists(const DefaultChar* filename); //throw() replaces wxFileExists()! + bool dirExists(const DefaultChar* dirname); //throw() replaces wxDirExists(): optional 'cause wxDirExists treats symlinks correctly + bool symlinkExists(const DefaultChar* objname); //throw() check if a symbolic link exists + + //check if files can be moved between two EXISTING paths (without copying) + bool isMovable(const Zstring& pathFrom, const Zstring& pathTo); //throw() + + //optionally: copy directory last change date, DOES NOTHING if something fails + void copyDirLastChangeDate(const Zstring& sourceDir, const Zstring& targetDir); //recycler bool recycleBinExists(); //test existence of Recycle Bin API on current system //file handling - void removeFile(const Zstring& filename, const bool useRecycleBin); //throw (FileError, ::RuntimeException); - void removeDirectory(const Zstring& directory, const bool useRecycleBin); //throw (FileError); + void removeFile(const Zstring& filename, const bool useRecycleBin); //throw (FileError, std::logic_error) + void removeDirectory(const Zstring& directory, const bool useRecycleBin); //throw (FileError) class MoveFileCallback //callback functionality diff --git a/shared/fileTraverser.cpp b/shared/fileTraverser.cpp index 6438b358..ab223791 100644 --- a/shared/fileTraverser.cpp +++ b/shared/fileTraverser.cpp @@ -1,5 +1,5 @@ #include "fileTraverser.h" -#include "globalFunctions.h" +#include "systemConstants.h" #include "systemFunctions.h" #include <wx/intl.h> @@ -128,8 +128,9 @@ bool traverseDirectory(const Zstring& directory, FreeFileSync::TraverseCallback* directory + globalFunctions::FILE_NAME_SEPARATOR; WIN32_FIND_DATA fileMetaData; - HANDLE searchHandle = FindFirstFile((directoryFormatted + DefaultChar('*')).c_str(), //pointer to name of file to search for - &fileMetaData); //pointer to returned information + HANDLE searchHandle = FindFirstFile((directoryFormatted + DefaultChar('*')).c_str(), //__in LPCTSTR lpFileName + &fileMetaData); //__out LPWIN32_FIND_DATA lpFindFileData + //no noticable performance difference compared to FindFirstFileEx with FindExInfoBasic, FIND_FIRST_EX_CASE_SENSITIVE and/or FIND_FIRST_EX_LARGE_FETCH if (searchHandle == INVALID_HANDLE_VALUE) { diff --git a/shared/globalFunctions.cpp b/shared/globalFunctions.cpp index cbdf29bb..f50a3097 100644 --- a/shared/globalFunctions.cpp +++ b/shared/globalFunctions.cpp @@ -5,6 +5,7 @@ #include <wx/stream.h> #include <wx/stopwatch.h> #include <cmath> +#include "systemConstants.h" wxString globalFunctions::numberToWxString(const unsigned int number) @@ -50,7 +51,6 @@ int globalFunctions::wxStringToInt(const wxString& number) return result; else return 0; //don't throw exceptions here: wxEmptyString shall be interpreted as 0 - //throw RuntimeException(wxString(_("Conversion error:")) + wxT(" wxString -> long")); } @@ -61,7 +61,6 @@ double globalFunctions::wxStringToDouble(const wxString& number) return result; else return 0; //don't throw exceptions here: wxEmptyString shall be interpreted as 0 - //throw RuntimeException(wxString(_("Conversion error:")) + wxT(" wxString -> double")); } diff --git a/shared/globalFunctions.h b/shared/globalFunctions.h index 00566b5f..b53ba596 100644 --- a/shared/globalFunctions.h +++ b/shared/globalFunctions.h @@ -18,17 +18,6 @@ class wxStopWatch; namespace globalFunctions { //------------------------------------------------ -// GLOBALS -//------------------------------------------------ -#ifdef FFS_WIN - const wxChar FILE_NAME_SEPARATOR = '\\'; - static const wxChar* const LINE_BREAK = wxT("\r\n"); //internal linkage -#elif defined FFS_LINUX - const wxChar FILE_NAME_SEPARATOR = '/'; - static const wxChar* const LINE_BREAK = wxT("\n"); -#endif - -//------------------------------------------------ // FUNCTIONS //------------------------------------------------ inline @@ -78,32 +67,80 @@ namespace globalFunctions } - //Note: the following lines are a performance optimization for deleting elements from a vector. It is incredibly faster to create a new -//vector and leave specific elements out than to delete row by row and force recopying of most elements for each single deletion (linear vs quadratic runtime) +//Note: the following lines are a performance optimization for deleting elements from a vector: linear runtime at most! template <class T> - void removeRowsFromVector(const std::set<int>& rowsToRemove, std::vector<T>& grid) + void removeRowsFromVector(const std::set<unsigned int>& rowsToRemove, std::vector<T>& grid) { - if (rowsToRemove.size() > 0) - { - std::vector<T> temp; + if (rowsToRemove.empty()) + return; + + std::set<unsigned int>::const_iterator rowToSkipIndex = rowsToRemove.begin(); + unsigned int rowToSkip = *rowToSkipIndex; - std::set<int>::const_iterator rowToSkipIndex = rowsToRemove.begin(); - int rowToSkip = *rowToSkipIndex; + if (rowToSkip >= grid.size()) + return; - for (int i = 0; i < int(grid.size()); ++i) + typename std::vector<T>::iterator insertPos = grid.begin() + rowToSkip; + + for (unsigned int i = rowToSkip; i < grid.size(); ++i) + { + if (i != rowToSkip) + { + *insertPos = grid[i]; + ++insertPos; + } + else { - if (i != rowToSkip) - temp.push_back(grid[i]); - else + ++rowToSkipIndex; + if (rowToSkipIndex != rowsToRemove.end()) + rowToSkip = *rowToSkipIndex; + } + } + grid.erase(insertPos, grid.end()); + } + + //bubble sort using swap() instead of assignment: useful if assignment is very expensive + template <class VectorData, typename CompareFct> + void bubbleSwapSort(VectorData& folderCmp, CompareFct compare) + { + for (int i = folderCmp.size() - 2; i >= 0; --i) + { + bool swapped = false; + for (int j = 0; j <= i; ++j) + if (compare(folderCmp[j + 1], folderCmp[j])) { - ++rowToSkipIndex; - if (rowToSkipIndex != rowsToRemove.end()) - rowToSkip = *rowToSkipIndex; + folderCmp[j + 1].swap(folderCmp[j]); + swapped = true; } - } - grid.swap(temp); + + if (!swapped) + return; } } + + //enhanced binary search template: returns an iterator + template <class ForwardIterator, class T> + inline + ForwardIterator custom_binary_search(ForwardIterator first, ForwardIterator last, const T& value) + { + first = lower_bound(first, last, value); + if (first != last && !(value < *first)) + return first; + else + return last; + } + + //enhanced binary search template: returns an iterator + template <class ForwardIterator, class T, typename Compare> + inline + ForwardIterator custom_binary_search(ForwardIterator first, ForwardIterator last, const T& value, Compare comp) + { + first = lower_bound(first, last, value, comp); + if (first != last && !comp(value, *first)) + return first; + else + return last; + } } @@ -148,20 +185,4 @@ wxString getCodeLocation(const wxString file, const int line); //speed alternative: wxLogDebug(wxT("text")) + DebugView -//############################################################################ -class RuntimeException //Exception class used to notify of general runtime exceptions -{ -public: - RuntimeException(const wxString& txt) : errorMessage(txt) {} - - wxString show() const - { - return errorMessage; - } - -private: - wxString errorMessage; -}; - - #endif // GLOBALFUNCTIONS_H_INCLUDED diff --git a/shared/localization.cpp b/shared/localization.cpp index 56afd20d..20bf6d2e 100644 --- a/shared/localization.cpp +++ b/shared/localization.cpp @@ -1,7 +1,7 @@ #include "localization.h" #include <wx/msgdlg.h> #include "../shared/standardPaths.h" -#include "../shared/globalFunctions.h" +#include "systemConstants.h" #include <fstream> #include <map> #include <wx/ffile.h> @@ -87,7 +87,7 @@ LocalizationInfo::LocalizationInfo() newEntry.languageID = wxLANGUAGE_RUSSIAN; newEntry.languageName = wxT("Pусский"); newEntry.languageFile = wxT("russian.lng"); - newEntry.translatorName = wxT("Fayullin T.N. aka Svobodniy"); + newEntry.translatorName = wxT("Fayzullin T.N. aka Svobodniy"); newEntry.languageFlag = wxT("russia.png"); locMapping.push_back(newEntry); @@ -132,6 +132,13 @@ LocalizationInfo::LocalizationInfo() newEntry.translatorName = wxT("CyberCowBoy"); newEntry.languageFlag = wxT("china.png"); locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_CHINESE_TRADITIONAL; + newEntry.languageName = wxT("正體中文"); + newEntry.languageFile = wxT("chinese_traditional.lng"); + newEntry.translatorName = wxT("Carlos"); + newEntry.languageFlag = wxT("taiwan.png"); + locMapping.push_back(newEntry); } @@ -165,13 +172,15 @@ int mapLanguageDialect(const int language) //variants of wxLANGUAGE_CHINESE_SIMPLIFIED case wxLANGUAGE_CHINESE: - case wxLANGUAGE_CHINESE_TRADITIONAL: - case wxLANGUAGE_CHINESE_HONGKONG: - case wxLANGUAGE_CHINESE_MACAU: case wxLANGUAGE_CHINESE_SINGAPORE: - case wxLANGUAGE_CHINESE_TAIWAN: return wxLANGUAGE_CHINESE_SIMPLIFIED; + //variants of wxLANGUAGE_CHINESE_TRADITIONAL + case wxLANGUAGE_CHINESE_TAIWAN: + case wxLANGUAGE_CHINESE_HONGKONG: + case wxLANGUAGE_CHINESE_MACAU: + return wxLANGUAGE_CHINESE_TRADITIONAL; + //variants of wxLANGUAGE_RUSSIAN case wxLANGUAGE_RUSSIAN_UKRAINE: return wxLANGUAGE_RUSSIAN; @@ -227,7 +236,7 @@ CustomLocale& CustomLocale::getInstance() CustomLocale::CustomLocale() : - wxLocale(wxLocale::GetSystemLanguage()), //wxLocale is a static object too => can be initialized just once + wxLocale(wxLANGUAGE_DEFAULT), //setting a different language needn't be supported on all systems! translationDB(new Translation), currentLanguage(wxLANGUAGE_ENGLISH) {} @@ -372,14 +381,17 @@ void CustomLocale::setLanguage(const int language) const wxString& translation = tmpString; if (!translation.empty()) - translationDB->insert(std::pair<TextOriginal, TextTranslation>(original, translation)); + translationDB->insert(std::make_pair(original, translation)); } ++rowNumber; } } else + { wxMessageBox(wxString(_("Error reading file:")) + wxT(" \"") + languageFile + wxT("\""), _("Error"), wxOK | wxICON_ERROR); + currentLanguage = wxLANGUAGE_ENGLISH; //reset to english language to show this error just once + } } else ; //if languageFile is empty texts will be english per default diff --git a/shared/shadow.cpp b/shared/shadow.cpp index abdca1a9..02a6a522 100644 --- a/shared/shadow.cpp +++ b/shared/shadow.cpp @@ -1,7 +1,7 @@ #include "shadow.h" #include <wx/msw/wrapwin.h> //includes "windows.h" #include <wx/intl.h> -#include "globalfunctions.h" +#include "systemConstants.h" using FreeFileSync::ShadowCopy; diff --git a/shared/standardPaths.cpp b/shared/standardPaths.cpp index 0c333598..54774673 100644 --- a/shared/standardPaths.cpp +++ b/shared/standardPaths.cpp @@ -1,15 +1,15 @@ #include "standardPaths.h" #include <wx/stdpaths.h> -#include "globalFunctions.h" #include <wx/filename.h> +#include "systemConstants.h" wxString assembleFileForUserData(const wxString fileName) { static const bool isPortableVersion = !wxFileExists(FreeFileSync::getInstallationDir() + globalFunctions::FILE_NAME_SEPARATOR + wxT("uninstall.exe")); //this check is a bit lame... - if (isPortableVersion) //use same directory as executable - return FreeFileSync::getInstallationDir() + globalFunctions::FILE_NAME_SEPARATOR + fileName; + if (isPortableVersion) //use current working directory + return wxString(wxT(".")) + globalFunctions::FILE_NAME_SEPARATOR + fileName; else //usen OS' standard paths { wxString userDirectory = wxStandardPathsBase::Get().GetUserDataDir(); diff --git a/shared/systemConstants.h b/shared/systemConstants.h new file mode 100644 index 00000000..84c1ae85 --- /dev/null +++ b/shared/systemConstants.h @@ -0,0 +1,22 @@ +#ifndef SYSTEMCONSTANTS_H_INCLUDED +#define SYSTEMCONSTANTS_H_INCLUDED + +#include "zstring.h" + + +namespace globalFunctions +{ +//------------------------------------------------ +// GLOBALS +//------------------------------------------------ +#ifdef FFS_WIN + const DefaultChar FILE_NAME_SEPARATOR = '\\'; + static const DefaultChar* const LINE_BREAK = L"\r\n"; //internal linkage +#elif defined FFS_LINUX + const DefaultChar FILE_NAME_SEPARATOR = '/'; + static const DefaultChar* const LINE_BREAK = "\n"; +#endif +} + + +#endif // SYSTEMCONSTANTS_H_INCLUDED diff --git a/shared/toggleButton.cpp b/shared/toggleButton.cpp new file mode 100644 index 00000000..b83143ea --- /dev/null +++ b/shared/toggleButton.cpp @@ -0,0 +1,44 @@ +#include "toggleButton.h" + +void ToggleButton::init(const wxBitmap& activeBmp, + const wxString& activeTooltip, + const wxBitmap& inactiveBmp, + const wxString& inactiveTooltip) +{ + m_activeBmp = activeBmp; + m_activeTooltip = activeTooltip; + m_inactiveBmp = inactiveBmp; + m_inactiveTooltip = inactiveTooltip; + + //load resources + setActive(active); +} + + +bool ToggleButton::isActive() const +{ + return active; +} + + +void ToggleButton::toggle() +{ + setActive(!active); +} + + +void ToggleButton::setActive(bool value) +{ + active = value; + + if (active) + { + SetBitmapLabel(m_activeBmp); + SetToolTip(m_activeTooltip); + } + else + { + SetBitmapLabel(m_inactiveBmp); + SetToolTip(m_inactiveTooltip); + } +} diff --git a/shared/toggleButton.h b/shared/toggleButton.h new file mode 100644 index 00000000..b7475e0c --- /dev/null +++ b/shared/toggleButton.h @@ -0,0 +1,40 @@ +#ifndef TOGGLEBUTTON_H_INCLUDED +#define TOGGLEBUTTON_H_INCLUDED + +#include <wx/bmpbuttn.h> + +class ToggleButton : public wxBitmapButton +{ +public: + ToggleButton(wxWindow* parent, + wxWindowID id, + const wxBitmap& bitmap, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = 0, + const wxValidator& validator = wxDefaultValidator, + const wxString& name = wxButtonNameStr) : + wxBitmapButton(parent, id, bitmap, pos, size, style, validator, name), + active(false) {} + + void init(const wxBitmap& activeBmp, + const wxString& activeTooltip, + const wxBitmap& inactiveBmp, + const wxString& inactiveTooltip); + + void setActive(bool value); + bool isActive() const; + void toggle(); + +private: + bool active; + + wxBitmap m_activeBmp; + wxString m_activeTooltip; + + wxBitmap m_inactiveBmp; + wxString m_inactiveTooltip; +}; + + +#endif // TOGGLEBUTTON_H_INCLUDED diff --git a/shared/xmlBase.cpp b/shared/xmlBase.cpp index 19ec0ab6..ba96b663 100644 --- a/shared/xmlBase.cpp +++ b/shared/xmlBase.cpp @@ -37,8 +37,15 @@ xmlAccess::XmlType xmlAccess::getXmlType(const wxString& filename) FILE* inputFile = configFile.fp(); TiXmlDocument doc; - if (!doc.LoadFile(inputFile)) //fails if inputFile is no proper XML + try + { + if (!doc.LoadFile(inputFile)) //fails if inputFile is no proper XML + return XML_OTHER; + } + catch (const std::exception&) + { //unfortunately TiXml isn't very smart and tries to allocate space for the complete file: length_error exception is thrown for large files! return XML_OTHER; + } TiXmlElement* root = doc.RootElement(); @@ -118,7 +125,10 @@ bool xmlAccess::saveXmlDocument(const wxString& fileName, const TiXmlDocument& d FILE* outputFile = dummyFile.fp(); - return document.SaveFile(outputFile); //save XML + if (!document.SaveFile(outputFile)) //save XML + return false; + + return dummyFile.Flush(); //flush data to disk! (think of multiple batch jobs writing/reading) } @@ -244,6 +254,17 @@ bool xmlAccess::readXmlAttribute(const std::string& name, const TiXmlElement* no } +bool xmlAccess::readXmlAttribute(const std::string& name, const TiXmlElement* node, wxString& output) +{ + std::string tempString; + if (!readXmlAttribute(name, node, tempString)) + return false; + + output = wxString::FromUTF8(tempString.c_str()); + return true; +} + + bool xmlAccess::readXmlAttribute(const std::string& name, const TiXmlElement* node, int& output) { std::string dummy; @@ -344,6 +365,12 @@ void xmlAccess::addXmlAttribute(const std::string& name, const std::string& valu } +void xmlAccess::addXmlAttribute(const std::string& name, const wxString& value, TiXmlElement* node) +{ + addXmlAttribute(name, std::string(value.ToUTF8()), node); +} + + void xmlAccess::addXmlAttribute(const std::string& name, const int value, TiXmlElement* node) { addXmlAttribute(name, globalFunctions::numberToString(value), node); @@ -372,11 +399,13 @@ void XmlParser::logError(const std::string& nodeName) failedNodes.push_back(wxString::FromUTF8(nodeName.c_str())); } + bool XmlParser::errorsOccured() const { return !failedNodes.empty(); } + const wxString XmlParser::getErrorMessageFormatted() const { wxString errorMessage = wxString(_("Could not read values for the following XML nodes:")) + wxT("\n"); diff --git a/shared/xmlBase.h b/shared/xmlBase.h index 5fc13b65..ed838843 100644 --- a/shared/xmlBase.h +++ b/shared/xmlBase.h @@ -38,6 +38,7 @@ namespace xmlAccess bool readXmlElement(const std::string& name, const TiXmlElement* parent, std::vector<wxString>& output); bool readXmlAttribute(const std::string& name, const TiXmlElement* node, std::string& output); + bool readXmlAttribute(const std::string& name, const TiXmlElement* node, wxString& output); bool readXmlAttribute(const std::string& name, const TiXmlElement* node, int& output); bool readXmlAttribute(const std::string& name, const TiXmlElement* node, unsigned int& output); bool readXmlAttribute(const std::string& name, const TiXmlElement* node, bool& output); @@ -51,6 +52,7 @@ namespace xmlAccess void addXmlElement(const std::string& name, const std::vector<wxString>& value, TiXmlElement* parent); void addXmlAttribute(const std::string& name, const std::string& value, TiXmlElement* node); + void addXmlAttribute(const std::string& name, const wxString& value, TiXmlElement* node); void addXmlAttribute(const std::string& name, const int value, TiXmlElement* node); void addXmlAttribute(const std::string& name, const unsigned int value, TiXmlElement* node); void addXmlAttribute(const std::string& name, const bool value, TiXmlElement* node); diff --git a/shared/zstring.cpp b/shared/zstring.cpp index ba7292e3..6b2b68c6 100644 --- a/shared/zstring.cpp +++ b/shared/zstring.cpp @@ -1,10 +1,13 @@ #include "zstring.h" -#include "globalFunctions.h" #ifdef FFS_WIN #include <wx/msw/wrapwin.h> //includes "windows.h" +#include <stdexcept> #endif //FFS_WIN +#ifdef __WXDEBUG__ +#include <wx/string.h> +#endif #ifdef __WXDEBUG__ @@ -43,27 +46,42 @@ AllocationCount& AllocationCount::getInstance() #ifdef FFS_WIN -int FreeFileSync::compareStringsWin32(const wchar_t* a, const wchar_t* b, const int aCount, const int bCount) +inline +int compareStringsWin32(const wchar_t* a, const wchar_t* b, const int aCount = -1, const int bCount = -1) { //DON'T use lstrcmpi() here! It uses word sort, which unfortunately is NOT always a strict weak sorting function for some locales (e.g. swedish) //Use CompareString() with "SORT_STRINGSORT" instead!!! const int rv = CompareString( LOCALE_USER_DEFAULT, //locale identifier - NORM_IGNORECASE | SORT_STRINGSORT, //comparison-style options + NORM_IGNORECASE | SORT_STRINGSORT, //comparison-style options a, //pointer to first string aCount, //size, in bytes or characters, of first string b, //pointer to second string bCount); //size, in bytes or characters, of second string if (rv == 0) - throw RuntimeException(wxString(wxT("Error comparing strings!"))); + throw std::runtime_error("Error comparing strings!"); else return rv - 2; //convert to C-style string compare result } #endif +#ifdef FFS_WIN +int Zstring::CmpNoCase(const DefaultChar* other) const +{ + return ::compareStringsWin32(c_str(), other); //way faster than wxString::CmpNoCase()!! +} + + +int Zstring::CmpNoCase(const Zstring& other) const +{ + return ::compareStringsWin32(c_str(), other.c_str(), length(), other.length()); //way faster than wxString::CmpNoCase()!! +} +#endif + + Zstring& Zstring::Replace(const DefaultChar* old, const DefaultChar* replacement, bool replaceAll) { const size_t oldLen = defaultLength(old); @@ -89,15 +107,13 @@ Zstring& Zstring::Replace(const DefaultChar* old, const DefaultChar* replacement bool matchesHelper(const DefaultChar* string, const DefaultChar* mask) { - for (DefaultChar ch; (ch = *mask) != 0; ++mask) + for (DefaultChar ch; (ch = *mask) != 0; ++mask, ++string) { switch (ch) { case DefaultChar('?'): if (*string == 0) return false; - else - ++string; break; case DefaultChar('*'): @@ -124,8 +140,6 @@ bool matchesHelper(const DefaultChar* string, const DefaultChar* mask) default: if (*string != ch) return false; - else - ++string; } } return *string == 0; @@ -150,42 +164,43 @@ Zstring& Zstring::Trim(bool fromRight) if (thisLen == 0) return *this; + DefaultChar* const strBegin = data(); + if (fromRight) { - const DefaultChar* cursor = data + thisLen - 1; - while (cursor != data - 1 && defaultIsWhiteSpace(*cursor)) //break when pointing one char further than last skipped element + const DefaultChar* cursor = strBegin + thisLen - 1; + while (cursor != strBegin - 1 && defaultIsWhiteSpace(*cursor)) //break when pointing one char further than last skipped element --cursor; ++cursor; - const size_t newLength = cursor - data; + const size_t newLength = cursor - strBegin; if (newLength != thisLen) { if (descr->refCount > 1) //allocate new string - *this = Zstring(data, newLength); - else //overwrite this strin + *this = Zstring(strBegin, newLength); + else //overwrite this string { - descr->length = newLength; - data[newLength] = DefaultChar(0); + descr->length = newLength; + strBegin[newLength] = 0; } } } else { - DefaultChar* cursor = data; - DefaultChar ch; - while ((ch = *cursor) != 0 && defaultIsWhiteSpace(ch)) + const DefaultChar* cursor = strBegin; + const DefaultChar* const strEnd = strBegin + thisLen; + while (cursor != strEnd && defaultIsWhiteSpace(*cursor)) ++cursor; - const size_t diff = cursor - data; + const size_t diff = cursor - strBegin; if (diff) { if (descr->refCount > 1) //allocate new string *this = Zstring(cursor, thisLen - diff); else { //overwrite this string - data = cursor; //no problem when deallocating data, since descr points to begin of allocated area - descr->capacity -= diff; - descr->length -= diff; + ::memmove(strBegin, cursor, (thisLen - diff + 1) * sizeof(DefaultChar)); //note: do not simply let data point to different location: this corrupts reserve()! + descr->length -= diff; } } } @@ -194,34 +209,50 @@ Zstring& Zstring::Trim(bool fromRight) } -Zstring& Zstring::MakeLower() +std::vector<Zstring> Zstring::Tokenize(const DefaultChar delimiter) const { - const size_t thisLen = length(); - if (thisLen == 0) - return *this; + std::vector<Zstring> output; - if (descr->refCount > 1) //allocate new string + const size_t thisLen = length(); + size_t indexStart = 0; + while (true) { - StringDescriptor* newDescr; - DefaultChar* newData; - allocate(thisLen, newDescr, newData); + size_t indexEnd = find(delimiter, indexStart); + if (indexEnd == Zstring::npos) + indexEnd = thisLen; - for (unsigned int i = 0; i < thisLen; ++i) - newData[i] = defaultToLower(data[i]); - newData[thisLen] = 0; + if (indexStart != indexEnd) //do not add empty strings + { + Zstring newEntry = substr(indexStart, indexEnd - indexStart); + newEntry.Trim(true); //remove whitespace characters from right + newEntry.Trim(false); //remove whitespace characters from left - decRef(); - descr = newDescr; - data = newData; - } - else - { //overwrite this string - for (unsigned int i = 0; i < thisLen; ++i) - data[i] = defaultToLower(data[i]); + if (!newEntry.empty()) + output.push_back(newEntry); + } + if (indexEnd == thisLen) + break; + + indexStart = indexEnd + 1; //delimiter is a single character } + return output; +} + + +#ifdef FFS_WIN +Zstring& Zstring::MakeLower() +{ + const size_t thisLen = length(); + if (thisLen == 0) + return *this; + + reserve(thisLen); //make unshared + ::CharLower(data()); //use Windows' lower case conversion + return *this; } +#endif //############################################################### @@ -254,7 +285,7 @@ size_t Zstring::rfind(const DefaultChar ch, size_t pos) const do //pos points to last char of the string { - if (data[pos] == ch) + if (c_str()[pos] == ch) return pos; } while (--pos != static_cast<size_t>(-1)); @@ -277,27 +308,25 @@ Zstring& Zstring::replace(size_t pos1, size_t n1, const DefaultChar* str, size_t const size_t newLen = oldLen - n1 + n2; if (newLen > oldLen || descr->refCount > 1) { //allocate a new string - StringDescriptor* newDescr; - DefaultChar* newData; - allocate(newLen, newDescr, newData); + StringDescriptor* newDescr = allocate(newLen); //assemble new string with replacement - memcpy(newData, data, pos1 * sizeof(DefaultChar)); - memcpy(newData + pos1, str, n2 * sizeof(DefaultChar)); - memcpy(newData + pos1 + n2, data + pos1 + n1, (oldLen - pos1 - n1) * sizeof(DefaultChar)); + DefaultChar* const newData = reinterpret_cast<DefaultChar*>(newDescr + 1); + ::memcpy(newData, c_str(), pos1 * sizeof(DefaultChar)); + ::memcpy(newData + pos1, str, n2 * sizeof(DefaultChar)); + ::memcpy(newData + pos1 + n2, c_str() + pos1 + n1, (oldLen - pos1 - n1) * sizeof(DefaultChar)); newData[newLen] = 0; decRef(); - data = newData; descr = newDescr; } else //overwrite current string: case "n2 == 0" is handled implicitly { - memcpy(data + pos1, str, n2 * sizeof(DefaultChar)); + ::memcpy(data() + pos1, str, n2 * sizeof(DefaultChar)); if (newLen < oldLen) { - memmove(data + pos1 + n2, data + pos1 + n1, (oldLen - pos1 - n1) * sizeof(DefaultChar)); - data[newLen] = 0; + ::memmove(data() + pos1 + n2, data() + pos1 + n1, (oldLen - pos1 - n1) * sizeof(DefaultChar)); + data()[newLen] = 0; descr->length = newLen; } } @@ -309,21 +338,32 @@ Zstring& Zstring::replace(size_t pos1, size_t n1, const DefaultChar* str, size_t Zstring& Zstring::operator=(const DefaultChar* source) { const size_t sourceLen = defaultLength(source); - if (sourceLen == 0) - return *this = Zstring(); if (descr->refCount > 1 || descr->capacity < sourceLen) //allocate new string *this = Zstring(source, sourceLen); else { //overwrite this string - memcpy(data, source, sourceLen * sizeof(DefaultChar)); - data[sourceLen] = 0; + ::memcpy(data(), source, (sourceLen + 1) * sizeof(DefaultChar)); //include null-termination descr->length = sourceLen; } return *this; } +Zstring& Zstring::assign(const DefaultChar* source, size_t len) +{ + if (descr->refCount > 1 || descr->capacity < len) //allocate new string + *this = Zstring(source, len); + else + { //overwrite this string + ::memcpy(data(), source, len * sizeof(DefaultChar)); //don't know if source is null-terminated + data()[len] = 0; //include null-termination + descr->length = len; + } + return *this; +} + + Zstring& Zstring::operator+=(const Zstring& other) { const size_t otherLen = other.length(); @@ -331,10 +371,9 @@ Zstring& Zstring::operator+=(const Zstring& other) { const size_t thisLen = length(); const size_t newLen = thisLen + otherLen; - copyBeforeWrite(newLen); + reserve(newLen); //make unshared and check capacity - memcpy(data + thisLen, other.c_str(), otherLen * sizeof(DefaultChar)); - data[newLen] = 0; + ::memcpy(data() + thisLen, other.c_str(), (otherLen + 1) * sizeof(DefaultChar)); //include null-termination descr->length = newLen; } return *this; @@ -348,10 +387,9 @@ Zstring& Zstring::operator+=(const DefaultChar* other) { const size_t thisLen = length(); const size_t newLen = thisLen + otherLen; - copyBeforeWrite(newLen); + reserve(newLen); //make unshared and check capacity - memcpy(data + thisLen, other, otherLen * sizeof(DefaultChar)); - data[newLen] = 0; + ::memcpy(data() + thisLen, other, (otherLen + 1) * sizeof(DefaultChar)); //include NULL-termination descr->length = newLen; } return *this; @@ -361,15 +399,15 @@ Zstring& Zstring::operator+=(const DefaultChar* other) Zstring& Zstring::operator+=(DefaultChar ch) { const size_t oldLen = length(); - copyBeforeWrite(oldLen + 1); - data[oldLen] = ch; - data[oldLen + 1] = 0; + reserve(oldLen + 1); //make unshared and check capacity + data()[oldLen] = ch; + data()[oldLen + 1] = 0; ++descr->length; return *this; } -void Zstring::copyBeforeWrite(const size_t capacityNeeded) +void Zstring::reserve(size_t capacityNeeded) //make unshared and check capacity { assert(capacityNeeded != 0); @@ -378,36 +416,28 @@ void Zstring::copyBeforeWrite(const size_t capacityNeeded) const size_t oldLength = length(); assert(oldLength <= getCapacityToAllocate(capacityNeeded)); - StringDescriptor* newDescr; - DefaultChar* newData; - allocate(capacityNeeded, newDescr, newData); + StringDescriptor* newDescr = allocate(capacityNeeded); newDescr->length = oldLength; - if (oldLength) - { - memcpy(newData, data, oldLength * sizeof(DefaultChar)); - newData[oldLength] = 0; - } + ::memcpy(reinterpret_cast<DefaultChar*>(newDescr + 1), c_str(), (oldLength + 1) * sizeof(DefaultChar)); //include NULL-termination + decRef(); descr = newDescr; - data = newData; } else if (descr->capacity < capacityNeeded) { //try to resize the current string (allocate anew if necessary) const size_t newCapacity = getCapacityToAllocate(capacityNeeded); - descr = (StringDescriptor*) realloc(descr, sizeof(StringDescriptor) + (newCapacity + 1) * sizeof(DefaultChar)); - if (descr == NULL) - throw std::bad_alloc(); - #ifdef __WXDEBUG__ - AllocationCount::getInstance().dec(data); //test Zstring for memory leaks + AllocationCount::getInstance().dec(c_str()); //test Zstring for memory leaks #endif - data = (DefaultChar*)(descr + 1); + descr = static_cast<StringDescriptor*>(::realloc(descr, sizeof(StringDescriptor) + (newCapacity + 1) * sizeof(DefaultChar))); + if (descr == NULL) + throw std::bad_alloc(); #ifdef __WXDEBUG__ - AllocationCount::getInstance().inc(data); //test Zstring for memory leaks + AllocationCount::getInstance().inc(c_str()); //test Zstring for memory leaks #endif descr->capacity = newCapacity; diff --git a/shared/zstring.h b/shared/zstring.h index 0403a00c..018ca42e 100644 --- a/shared/zstring.h +++ b/shared/zstring.h @@ -11,27 +11,21 @@ #include <cctype> #include <assert.h> #include <new> +#include <stdlib.h> +#include <vector> + #ifdef __WXDEBUG__ #include <set> +#include <wx/thread.h> #endif -namespace FreeFileSync -{ -#ifdef FFS_WIN - //super fast case-insensitive string comparison: way faster than wxString::CmpNoCase()!!! - int compareStringsWin32(const wchar_t* a, const wchar_t* b, const int aCount = -1, const int bCount = -1); -#endif //FFS_WIN -} - - #ifdef ZSTRING_CHAR typedef char DefaultChar; //use char strings #elif defined ZSTRING_WIDE_CHAR typedef wchar_t DefaultChar; //use wide character strings #endif -class Zsubstr; class Zstring { @@ -44,8 +38,9 @@ public: operator const DefaultChar*() const; //implicit conversion to C string - //wxWidgets functions + //wxWidgets-like functions bool StartsWith(const DefaultChar* begin) const; + bool StartsWith(DefaultChar begin) const; bool StartsWith(const Zstring& begin) const; bool EndsWith(const DefaultChar* end) const; bool EndsWith(const DefaultChar end) const; @@ -54,23 +49,25 @@ public: #ifdef FFS_WIN int CmpNoCase(const DefaultChar* other) const; int CmpNoCase(const Zstring& other) const; + Zstring& MakeLower(); #endif int Cmp(const DefaultChar* other) const; int Cmp(const Zstring& other) const; Zstring& Replace(const DefaultChar* old, const DefaultChar* replacement, bool replaceAll = true); - Zstring AfterLast(DefaultChar ch) const; //returns the whole string if ch not found - Zstring BeforeLast(DefaultChar ch) const; //returns empty string if ch not found + Zstring AfterLast( DefaultChar ch) const; //returns the whole string if ch not found + Zstring BeforeLast( DefaultChar ch) const; //returns empty string if ch not found + Zstring AfterFirst( DefaultChar ch) const; //returns empty string if ch not found + Zstring BeforeFirst(DefaultChar ch) const; //returns the whole string if ch not found size_t Find(DefaultChar ch, bool fromEnd) const; bool Matches(const DefaultChar* mask) const; static bool Matches(const DefaultChar* name, const DefaultChar* mask); Zstring& Trim(bool fromRight); //from right or left - Zstring& MakeLower(); + std::vector<Zstring> Tokenize(const DefaultChar delimiter) const; //std::string functions size_t length() const; const DefaultChar* c_str() const; Zstring substr(size_t pos = 0, size_t len = npos) const; //allocate new string - Zsubstr zsubstr(size_t pos = 0) const; //use ref-counting! bool empty() const; void clear(); int compare(const DefaultChar* other) const; @@ -81,16 +78,18 @@ public: size_t rfind(const DefaultChar ch, size_t pos = npos) const; Zstring& replace(size_t pos1, size_t n1, const DefaultChar* str, size_t n2); size_t size() const; + void reserve(size_t minCapacity); + Zstring& assign(const DefaultChar* source, size_t len); Zstring& operator=(const Zstring& source); Zstring& operator=(const DefaultChar* source); - bool operator == (const Zstring& other) const; - bool operator == (const DefaultChar* other) const; - bool operator < (const Zstring& other) const; - bool operator < (const DefaultChar* other) const; - bool operator != (const Zstring& other) const; - bool operator != (const DefaultChar* other) const; + bool operator==(const Zstring& other) const; + bool operator==(const DefaultChar* other) const; + bool operator< (const Zstring& other) const; + bool operator< (const DefaultChar* other) const; + bool operator!=(const Zstring& other) const; + bool operator!=(const DefaultChar* other) const; const DefaultChar operator[](const size_t pos) const; @@ -106,11 +105,11 @@ public: private: Zstring(int); //indicates usage error + DefaultChar* data(); void initAndCopy(const DefaultChar* source, size_t length); - void incRef() const; //support for reference-counting - void decRef(); // - void copyBeforeWrite(const size_t capacityNeeded); //and copy-on-write + void incRef() const; //support for reference-counting + void decRef(); // struct StringDescriptor { @@ -118,28 +117,9 @@ private: size_t length; size_t capacity; //allocated length without null-termination }; - static void allocate(const size_t newLength, StringDescriptor*& newDescr, DefaultChar*& newData); + static StringDescriptor* allocate(const size_t newLength); StringDescriptor* descr; - DefaultChar* data; -}; - - -class Zsubstr //ref-counted substring of a Zstring -{ -public: - Zsubstr(); - Zsubstr(const Zstring& ref, const size_t pos); - - const DefaultChar* c_str() const; - size_t length() const; - bool StartsWith(const Zstring& begin) const; - size_t findFromEnd(const DefaultChar ch) const; - -private: - Zstring m_ref; - const DefaultChar* m_data; - size_t m_length; }; @@ -181,15 +161,15 @@ inline bool defaultIsWhiteSpace(const char ch) { // some compilers (e.g. VC++ 6.0) return true for a call to isspace('\xEA') => exclude char(128) to char(255) - return ((unsigned char)ch < 128) && isspace((unsigned char)ch) != 0; -} - -inline -char defaultToLower(const char ch) -{ - return tolower((unsigned char)ch); //caution: although tolower() has int as input parameter it expects unsigned chars! + return (static_cast<unsigned char>(ch) < 128) && isspace((unsigned char)ch) != 0; } +//inline +//char defaultToLower(const char ch) +//{ +// return tolower(static_cast<unsigned char>(ch)); //caution: although tolower() has int as input parameter it expects unsigned chars! +//} +// #elif defined ZSTRING_WIDE_CHAR inline size_t defaultLength(const wchar_t* input) @@ -228,11 +208,11 @@ bool defaultIsWhiteSpace(const wchar_t ch) return (ch < 128 || ch > 255) && iswspace(ch) != 0; } -inline -wchar_t defaultToLower(const wchar_t ch) -{ - return towlower(ch); -} +//inline +//wchar_t defaultToLower(const wchar_t ch) +//{ +// return towlower(ch); +//} #endif @@ -242,11 +222,13 @@ class AllocationCount //small test for memory leaks in Zstring public: void inc(const DefaultChar* object) { + wxCriticalSectionLocker dummy(lockActStrings); activeStrings.insert(object); } void dec(const DefaultChar* object) { + wxCriticalSectionLocker dummy(lockActStrings); activeStrings.erase(object); } @@ -256,6 +238,7 @@ private: AllocationCount() {} ~AllocationCount(); + wxCriticalSection lockActStrings; std::set<const DefaultChar*> activeStrings; }; #endif @@ -269,23 +252,23 @@ size_t getCapacityToAllocate(const size_t length) inline -void Zstring::allocate(const size_t newLength, - StringDescriptor*& newDescr, - DefaultChar*& newData) +Zstring::StringDescriptor* Zstring::allocate(const size_t newLength) { //allocate and set data for new string const size_t newCapacity = getCapacityToAllocate(newLength); assert(newCapacity); - newDescr = static_cast<StringDescriptor*>(operator new [] (sizeof(StringDescriptor) + (newCapacity + 1) * sizeof(DefaultChar))); - newData = reinterpret_cast<DefaultChar*>(newDescr + 1); + StringDescriptor* newDescr = static_cast<StringDescriptor*>(::malloc(sizeof(StringDescriptor) + (newCapacity + 1) * sizeof(DefaultChar))); //use C-memory functions because of realloc() + if (newDescr == NULL) + throw std::bad_alloc(); newDescr->refCount = 1; newDescr->length = newLength; newDescr->capacity = newCapacity; #ifdef __WXDEBUG__ - AllocationCount::getInstance().inc(newData); //test Zstring for memory leaks + AllocationCount::getInstance().inc(reinterpret_cast<DefaultChar*>(newDescr + 1)); //test Zstring for memory leaks #endif + return newDescr; } @@ -301,7 +284,6 @@ Zstring::Zstring() emptyString.incRef(); descr = emptyString.descr; - data = emptyString.data; } @@ -323,7 +305,6 @@ inline Zstring::Zstring(const Zstring& source) { descr = source.descr; - data = source.data; incRef(); //reference counting! } @@ -338,9 +319,9 @@ Zstring::~Zstring() inline void Zstring::initAndCopy(const DefaultChar* source, size_t sourceLen) { - allocate(sourceLen, descr, data); - memcpy(data, source, sourceLen * sizeof(DefaultChar)); - data[sourceLen] = 0; + descr = allocate(sourceLen); + ::memcpy(data(), source, sourceLen * sizeof(DefaultChar)); + data()[sourceLen] = 0; } @@ -358,31 +339,15 @@ void Zstring::decRef() assert(descr && descr->refCount >= 1); //descr points to the begin of the allocated memory block if (--descr->refCount == 0) { - operator delete [] (descr); //this must NEVER be changed!! E.g. Trim() relies on descr being start of allocated memory block - descr = NULL; #ifdef __WXDEBUG__ - AllocationCount::getInstance().dec(data); //test Zstring for memory leaks + AllocationCount::getInstance().dec(c_str()); //test Zstring for memory leaks #endif + ::free(descr); //beginning of whole memory block + descr = NULL; } } -#ifdef FFS_WIN -inline -int Zstring::CmpNoCase(const DefaultChar* other) const -{ - return FreeFileSync::compareStringsWin32(c_str(), other); //way faster than wxString::CmpNoCase()!! -} - - -inline -int Zstring::CmpNoCase(const Zstring& other) const -{ - return FreeFileSync::compareStringsWin32(c_str(), other.c_str(), length(), other.length()); //way faster than wxString::CmpNoCase()!! -} -#endif - - inline Zstring::operator const DefaultChar*() const { @@ -396,7 +361,6 @@ Zstring& Zstring::operator=(const Zstring& source) source.incRef(); //implicitly handle case "this == &source" and avoid this check decRef(); // descr = source.descr; - data = source.data; return *this; } @@ -417,11 +381,11 @@ size_t Zstring::Find(DefaultChar ch, bool fromEnd) const inline Zstring Zstring::AfterLast(DefaultChar ch) const { - size_t pos = rfind(ch, npos); - if (pos == npos ) - return *this; + const size_t pos = rfind(ch, npos); + if (pos != npos ) + return Zstring(c_str() + pos + 1, length() - pos - 1); else - return c_str() + pos + 1; + return *this; } @@ -430,12 +394,35 @@ Zstring Zstring::AfterLast(DefaultChar ch) const inline Zstring Zstring::BeforeLast(DefaultChar ch) const { - size_t pos = rfind(ch, npos); + const size_t pos = rfind(ch, npos); + if (pos != npos) + return Zstring(c_str(), pos); //data is non-empty string in this context: else ch would not have been found! + else + return Zstring(); +} - if (pos != npos && pos != 0 ) - return Zstring(data, pos); //data is non-empty string in this context: else ch would not have been found! + +//returns empty string if ch not found +inline +Zstring Zstring::AfterFirst(DefaultChar ch) const +{ + const size_t pos = find(ch, 0); + if (pos != npos) + return Zstring(c_str() + pos + 1, length() - pos - 1); else return Zstring(); + +} + +//returns the whole string if ch not found +inline +Zstring Zstring::BeforeFirst(DefaultChar ch) const +{ + const size_t pos = find(ch, 0); + if (pos != npos) + return Zstring(c_str(), pos); //data is non-empty string in this context: else ch would not have been found! + else + return *this; } @@ -450,12 +437,20 @@ bool Zstring::StartsWith(const DefaultChar* begin) const inline +bool Zstring::StartsWith(DefaultChar begin) const +{ + const size_t len = length(); + return len && (this->operator[](0) == begin); +} + + +inline bool Zstring::StartsWith(const Zstring& begin) const { const size_t beginLength = begin.length(); if (length() < beginLength) return false; - return compare(0, beginLength, begin) == 0; + return compare(0, beginLength, begin.c_str()) == 0; } @@ -485,7 +480,7 @@ bool Zstring::EndsWith(const Zstring& end) const const size_t endLength = end.length(); if (thisLength < endLength) return false; - return compare(thisLength - endLength, endLength, end) == 0; + return compare(thisLength - endLength, endLength, end.c_str()) == 0; } @@ -498,8 +493,8 @@ Zstring& Zstring::Truncate(size_t newLen) return *this = Zstring(c_str(), newLen); else //overwrite this string { - descr->length = newLen; - data[newLen] = DefaultChar(0); + descr->length = newLen; + data()[newLen] = 0; } } @@ -622,7 +617,14 @@ size_t Zstring::size() const inline const DefaultChar* Zstring::c_str() const { - return data; + return reinterpret_cast<DefaultChar*>(descr + 1); +} + + +inline +DefaultChar* Zstring::data() +{ + return reinterpret_cast<DefaultChar*>(descr + 1); } @@ -644,7 +646,7 @@ inline const DefaultChar Zstring::operator[](const size_t pos) const { assert(pos < length()); - return data[pos]; + return c_str()[pos]; } @@ -668,67 +670,5 @@ const Zstring Zstring::operator+(const DefaultChar ch) const return Zstring(*this)+=ch; } -//##################### Zsubstr ############################# -inline -Zsubstr Zstring::zsubstr(size_t pos) const -{ - assert(pos <= length()); - return Zsubstr(*this, pos); //return reference counted string -} - - -inline -Zsubstr::Zsubstr() -{ - m_data = m_ref.c_str(); - m_length = 0; -} - - -inline -Zsubstr::Zsubstr(const Zstring& ref, const size_t pos) : - m_ref(ref), - m_data(ref.c_str() + pos), - m_length(ref.length() - pos) {} - - -inline -const DefaultChar* Zsubstr::c_str() const -{ - return m_data; -} - - -inline -size_t Zsubstr::length() const -{ - return m_length; -} - - -inline -bool Zsubstr::StartsWith(const Zstring& begin) const -{ - const size_t beginLength = begin.length(); - if (length() < beginLength) - return false; - - return defaultCompare(m_data, begin.c_str(), beginLength) == 0; -} - - -inline -size_t Zsubstr::findFromEnd(const DefaultChar ch) const -{ - size_t pos = length(); - while (--pos != static_cast<size_t>(-1)) - { - if (m_data[pos] == ch) - return pos; - } - return Zstring::npos; -} - - #endif // ZSTRING_H_INCLUDED diff --git a/structures.cpp b/structures.cpp index 90dd0747..57bea755 100644 --- a/structures.cpp +++ b/structures.cpp @@ -1,15 +1,16 @@ #include "structures.h" #include "shared/fileHandling.h" #include <wx/intl.h> -#include "shared/globalFunctions.h" +#include "shared/systemConstants.h" +using FreeFileSync::SyncConfiguration; +using FreeFileSync::MainConfiguration; -FreeFileSync::MainConfiguration::MainConfiguration() : - compareVar(CMP_BY_TIME_SIZE), - filterIsActive(false), //do not filter by default - includeFilter(wxT("*")), //include all files/folders - excludeFilter(wxEmptyString), //exclude nothing - handleDeletion(FreeFileSync::recycleBinExists() ? MOVE_TO_RECYCLE_BIN : DELETE_PERMANENTLY) {} //enable if OS supports it; else user will have to activate first and then get an error message + +bool FreeFileSync::recycleBinExistsWrap() +{ + return recycleBinExists(); +} wxString FreeFileSync::getVariantName(CompareVariant var) @@ -27,54 +28,36 @@ wxString FreeFileSync::getVariantName(CompareVariant var) } -using FreeFileSync::SyncConfiguration; - - -SyncConfiguration::Variant SyncConfiguration::getVariant() +SyncConfiguration::Variant SyncConfiguration::getVariant() const { if ( exLeftSideOnly == SYNC_DIR_CFG_RIGHT && exRightSideOnly == SYNC_DIR_CFG_RIGHT && leftNewer == SYNC_DIR_CFG_RIGHT && rightNewer == SYNC_DIR_CFG_RIGHT && - different == SYNC_DIR_CFG_RIGHT) + different == SYNC_DIR_CFG_RIGHT && + conflict == SYNC_DIR_CFG_NONE) return MIRROR; //one way -> else if (exLeftSideOnly == SYNC_DIR_CFG_RIGHT && exRightSideOnly == SYNC_DIR_CFG_NONE && leftNewer == SYNC_DIR_CFG_RIGHT && rightNewer == SYNC_DIR_CFG_NONE && - different == SYNC_DIR_CFG_NONE) + different == SYNC_DIR_CFG_NONE && + conflict == SYNC_DIR_CFG_NONE) return UPDATE; //Update -> else if (exLeftSideOnly == SYNC_DIR_CFG_RIGHT && exRightSideOnly == SYNC_DIR_CFG_LEFT && leftNewer == SYNC_DIR_CFG_RIGHT && rightNewer == SYNC_DIR_CFG_LEFT && - different == SYNC_DIR_CFG_NONE) + different == SYNC_DIR_CFG_NONE && + conflict == SYNC_DIR_CFG_NONE) return TWOWAY; //two way <-> else return CUSTOM; //other } -wxString SyncConfiguration::getVariantName() -{ - switch (getVariant()) - { - case MIRROR: - return _("Mirror ->>"); - case UPDATE: - return _("Update ->"); - case TWOWAY: - return _("Two way <->"); - case CUSTOM: - return _("Custom"); - } - - return _("Error"); -} - - void SyncConfiguration::setVariant(const Variant var) { switch (var) @@ -85,6 +68,7 @@ void SyncConfiguration::setVariant(const Variant var) leftNewer = SYNC_DIR_CFG_RIGHT; rightNewer = SYNC_DIR_CFG_RIGHT; different = SYNC_DIR_CFG_RIGHT; + conflict = SYNC_DIR_CFG_NONE; break; case UPDATE: exLeftSideOnly = SYNC_DIR_CFG_RIGHT; @@ -92,6 +76,7 @@ void SyncConfiguration::setVariant(const Variant var) leftNewer = SYNC_DIR_CFG_RIGHT; rightNewer = SYNC_DIR_CFG_NONE; different = SYNC_DIR_CFG_NONE; + conflict = SYNC_DIR_CFG_NONE; break; case TWOWAY: exLeftSideOnly = SYNC_DIR_CFG_RIGHT; @@ -99,9 +84,140 @@ void SyncConfiguration::setVariant(const Variant var) leftNewer = SYNC_DIR_CFG_RIGHT; rightNewer = SYNC_DIR_CFG_LEFT; different = SYNC_DIR_CFG_NONE; + conflict = SYNC_DIR_CFG_NONE; break; case CUSTOM: assert(false); break; } } + +wxString MainConfiguration::getSyncVariantName() +{ + const SyncConfiguration::Variant mainVariant = syncConfiguration.getVariant(); + + //test if there's a deviating variant within the additional folder pairs + for (std::vector<FolderPairEnh>::const_iterator i = additionalPairs.begin(); i != additionalPairs.end(); ++i) + if (i->altSyncConfig.get() && i->altSyncConfig->syncConfiguration.getVariant() != mainVariant) + return _("Multiple..."); + + //seems to be all in sync... + switch (mainVariant) + { + case SyncConfiguration::MIRROR: + return _("Mirror ->>"); + case SyncConfiguration::UPDATE: + return _("Update ->"); + case SyncConfiguration::TWOWAY: + return _("Two way <->"); + case SyncConfiguration::CUSTOM: + return _("Custom"); + } + + return _("Error"); +} + + +wxString FreeFileSync::getDescription(CompareFilesResult cmpRes) +{ + switch (cmpRes) + { + case FILE_LEFT_SIDE_ONLY: + return _("Files/folders that exist on left side only"); + case FILE_RIGHT_SIDE_ONLY: + return _("Files/folders that exist on right side only"); + case FILE_LEFT_NEWER: + return _("Files that exist on both sides, left one is newer"); + case FILE_RIGHT_NEWER: + return _("Files that exist on both sides, right one is newer"); + case FILE_DIFFERENT: + return _("Files that exist on both sides and have different content"); + case FILE_EQUAL: + return _("Files that are equal on both sides"); + case FILE_CONFLICT: + return _("Conflicts/files that cannot be categorized"); + } + + assert(false); + return wxEmptyString; +} + + +wxString FreeFileSync::getSymbol(CompareFilesResult cmpRes) +{ + switch (cmpRes) + { + case FILE_LEFT_SIDE_ONLY: + return wxT("<|"); + case FILE_RIGHT_SIDE_ONLY: + return wxT("|>"); + case FILE_LEFT_NEWER: + return wxT("<<"); + case FILE_RIGHT_NEWER: + return wxT(">>"); + case FILE_DIFFERENT: + return wxT("!="); + case FILE_EQUAL: + return wxT("'=="); //added quotation mark to avoid error in Excel cell when exporting to *.cvs + case FILE_CONFLICT: + return wxT("\\/\\->"); + } + + assert(false); + return wxEmptyString; +} + + +wxString FreeFileSync::getDescription(SyncOperation op) +{ + switch (op) + { + case SO_CREATE_NEW_LEFT: + return _("Copy from right to left"); + case SO_CREATE_NEW_RIGHT: + return _("Copy from left to right"); + case SO_DELETE_LEFT: + return _("Delete files/folders existing on left side only"); + case SO_DELETE_RIGHT: + return _("Delete files/folders existing on right side only"); + case SO_OVERWRITE_LEFT: + return _("Copy from right to left overwriting"); + case SO_OVERWRITE_RIGHT: + return _("Copy from left to right overwriting"); + case SO_DO_NOTHING: + return _("Do nothing"); + case SO_UNRESOLVED_CONFLICT: + return _("Conflicts/files that cannot be categorized"); + }; + + assert(false); + return wxEmptyString; +} + + +wxString FreeFileSync::getSymbol(SyncOperation op) +{ + switch (op) + { + case SO_CREATE_NEW_LEFT: + return wxT("*-"); + case SO_CREATE_NEW_RIGHT: + return wxT("-*"); + case SO_DELETE_LEFT: + return wxT("D-"); + case SO_DELETE_RIGHT: + return wxT("-D"); + case SO_OVERWRITE_LEFT: + return wxT("<-"); + case SO_OVERWRITE_RIGHT: + return wxT("->"); + case SO_DO_NOTHING: + return wxT("-"); + case SO_UNRESOLVED_CONFLICT: + return wxT("\\/\\->"); + }; + + assert(false); + return wxEmptyString; +} + diff --git a/structures.h b/structures.h index daf502ad..4d383d22 100644 --- a/structures.h +++ b/structures.h @@ -2,11 +2,11 @@ #define FREEFILESYNC_H_INCLUDED #include <wx/string.h> -#include <set> #include <vector> #include "shared/zstring.h" -#include <wx/longlong.h> +#include "shared/systemConstants.h" #include "shared/staticAssert.h" +#include <boost/shared_ptr.hpp> namespace FreeFileSync @@ -29,20 +29,15 @@ namespace FreeFileSync //attention make sure these /|\ \|/ two enums match!!! enum SyncDirection { - SYNC_DIR_LEFT = 0, - SYNC_DIR_RIGHT, - SYNC_DIR_NONE, - - SYNC_UNRESOLVED_CONFLICT + SYNC_DIR_LEFT = SYNC_DIR_CFG_LEFT, + SYNC_DIR_RIGHT = SYNC_DIR_CFG_RIGHT, + SYNC_DIR_NONE = SYNC_DIR_CFG_NONE, }; + inline SyncDirection convertToSyncDirection(SyncDirectionCfg syncCfg) { - assert_static(static_cast<SyncDirection>(SYNC_DIR_CFG_LEFT) == SYNC_DIR_LEFT); - assert_static(static_cast<SyncDirection>(SYNC_DIR_CFG_RIGHT) == SYNC_DIR_RIGHT); - assert_static(static_cast<SyncDirection>(SYNC_DIR_CFG_NONE) == SYNC_DIR_NONE); - return static_cast<SyncDirection>(syncCfg); } @@ -54,13 +49,15 @@ namespace FreeFileSync exRightSideOnly(SYNC_DIR_CFG_LEFT), leftNewer( SYNC_DIR_CFG_RIGHT), rightNewer( SYNC_DIR_CFG_LEFT), - different( SYNC_DIR_CFG_NONE) {} + different( SYNC_DIR_CFG_NONE), + conflict( SYNC_DIR_CFG_NONE) {} SyncDirectionCfg exLeftSideOnly; SyncDirectionCfg exRightSideOnly; SyncDirectionCfg leftNewer; SyncDirectionCfg rightNewer; SyncDirectionCfg different; + SyncDirectionCfg conflict; bool operator==(const SyncConfiguration& other) const { @@ -68,7 +65,8 @@ namespace FreeFileSync exRightSideOnly == other.exRightSideOnly && leftNewer == other.leftNewer && rightNewer == other.rightNewer && - different == other.different; + different == other.different && + conflict == other.conflict; } //get/set default configuration variants @@ -79,8 +77,7 @@ namespace FreeFileSync TWOWAY, CUSTOM }; - Variant getVariant(); - wxString getVariantName(); + Variant getVariant() const; void setVariant(const Variant var); }; @@ -93,42 +90,83 @@ namespace FreeFileSync }; - struct MainConfiguration + struct HiddenSettings { - MainConfiguration(); + HiddenSettings() : + fileTimeTolerance(2), //default 2s: FAT vs NTFS + traverseDirectorySymlinks(false), + copyFileSymlinks(true), + verifyFileCopy(false) {} + + unsigned int fileTimeTolerance; //max. allowed file time deviation + bool traverseDirectorySymlinks; + bool copyFileSymlinks; //copy symbolic link instead of target file + bool verifyFileCopy; //verify copied files + + bool operator==(const HiddenSettings& other) const + { + return fileTimeTolerance == other.fileTimeTolerance && + traverseDirectorySymlinks == other.traverseDirectorySymlinks && + copyFileSymlinks == other.copyFileSymlinks && + verifyFileCopy == other.verifyFileCopy; + } + }; - //Compare setting - CompareVariant compareVar; + + bool recycleBinExistsWrap(); + + + struct AlternateSyncConfig + { + AlternateSyncConfig(const SyncConfiguration& syncCfg, + const DeletionPolicy handleDel, + const wxString& customDelDir) : + syncConfiguration(syncCfg), + handleDeletion(handleDel), + customDeletionDirectory(customDelDir) {}; + + AlternateSyncConfig() : //construct with default values + handleDeletion(FreeFileSync::recycleBinExistsWrap() ? MOVE_TO_RECYCLE_BIN : DELETE_PERMANENTLY) {} //enable if OS supports it; else user will have to activate first and then get an error message //Synchronisation settings SyncConfiguration syncConfiguration; - //Filter setting - bool filterIsActive; - wxString includeFilter; - wxString excludeFilter; - //misc options DeletionPolicy handleDeletion; //use Recycle, delete permanently or move to user-defined location wxString customDeletionDirectory; - bool operator==(const MainConfiguration& other) const + bool operator==(const AlternateSyncConfig& other) const { - return compareVar == other.compareVar && - syncConfiguration == other.syncConfiguration && - filterIsActive == other.filterIsActive && - includeFilter == other.includeFilter && - excludeFilter == other.excludeFilter && - handleDeletion == other.handleDeletion && + return syncConfiguration == other.syncConfiguration && + handleDeletion == other.handleDeletion && customDeletionDirectory == other.customDeletionDirectory; } }; - struct FolderPair + struct AlternateFilter { - FolderPair() {} + AlternateFilter(const wxString& include, const wxString& exclude) : + includeFilter(include), + excludeFilter(exclude) {} + + AlternateFilter() : //construct with default values + includeFilter(wxT("*")), //include all files/folders + excludeFilter(wxEmptyString) {} //exclude nothing + + wxString includeFilter; + wxString excludeFilter; + + bool operator==(const AlternateFilter& other) const + { + return includeFilter == other.includeFilter && + excludeFilter == other.excludeFilter; + } + }; + + struct FolderPair + { FolderPair(const Zstring& leftDir, const Zstring& rightDir) : leftDirectory(leftDir), rightDirectory(rightDir) {} @@ -144,129 +182,138 @@ namespace FreeFileSync }; - struct FileDescrLine + struct FolderPairEnh //enhanced folder pairs with (optional) alternate configuration { - FileDescrLine() : objType(TYPE_NOTHING) {} + FolderPairEnh() {} - enum ObjectType + FolderPairEnh(const Zstring& leftDir, + const Zstring& rightDir, + const boost::shared_ptr<const AlternateSyncConfig>& syncConfig, + const boost::shared_ptr<const AlternateFilter>& filter) : + leftDirectory(leftDir), + rightDirectory(rightDir) , + altSyncConfig(syncConfig), + altFilter(filter) {} + + Zstring leftDirectory; + Zstring rightDirectory; + + boost::shared_ptr<const AlternateSyncConfig> altSyncConfig; //optional + boost::shared_ptr<const AlternateFilter> altFilter; //optional + + bool operator==(const FolderPairEnh& other) const { - TYPE_NOTHING, - TYPE_DIRECTORY, - TYPE_FILE - }; + return leftDirectory == other.leftDirectory && + rightDirectory == other.rightDirectory && + + (altSyncConfig.get() && other.altSyncConfig.get() ? + *altSyncConfig == *other.altSyncConfig : + altSyncConfig.get() == other.altSyncConfig.get()) && + + (altFilter.get() && other.altFilter.get() ? + *altFilter == *other.altFilter : + altFilter.get() == other.altFilter.get()); + } + }; + - Zstring fullName; // == directory + relativeName - Zsubstr relativeName; //fullName without directory that is being synchronized - //Note on performance: Keep redundant information "relativeName"! - //Extracting info from "fullName" instead would result in noticeable performance loss, with only limited memory reduction (note ref. counting strings)! - wxLongLong lastWriteTimeRaw; //number of seconds since Jan. 1st 1970 UTC, same semantics like time_t (== signed long) - wxULongLong fileSize; - ObjectType objType; //is it a file or directory or initial? + struct MainConfiguration + { + MainConfiguration() : + mainFolderPair(Zstring(), Zstring()), + compareVar(CMP_BY_TIME_SIZE), + filterIsActive(false), //do not filter by default + includeFilter(wxT("*")), //include all files/folders + excludeFilter(wxEmptyString), //exclude nothing + handleDeletion(FreeFileSync::recycleBinExistsWrap() ? MOVE_TO_RECYCLE_BIN : DELETE_PERMANENTLY) {} //enable if OS supports it; else user will have to activate first and then get an error message - bool operator < (const FileDescrLine& b) const //used by template class "set" + FolderPair mainFolderPair; + std::vector<FolderPairEnh> additionalPairs; + + //Compare setting + CompareVariant compareVar; + + //Synchronisation settings + SyncConfiguration syncConfiguration; + + //Filter setting + bool filterIsActive; + wxString includeFilter; + wxString excludeFilter; + + //misc options + HiddenSettings hidden; //settings not visible on GUI + + DeletionPolicy handleDeletion; //use Recycle, delete permanently or move to user-defined location + wxString customDeletionDirectory; + + wxString getSyncVariantName(); + + bool operator==(const MainConfiguration& other) const { - //quick check based on string length: we are not interested in a lexicographical order! - const size_t aLength = relativeName.length(); - const size_t bLength = b.relativeName.length(); - if (aLength != bLength) - return aLength < bLength; -#ifdef FFS_WIN //Windows does NOT distinguish between upper/lower-case - return FreeFileSync::compareStringsWin32(relativeName.c_str(), b.relativeName.c_str()) < 0; //implementing a (reverse) comparison manually is a lot slower! -#elif defined FFS_LINUX //Linux DOES distinguish between upper/lower-case - return defaultCompare(relativeName.c_str(), b.relativeName.c_str()) < 0; -#endif + return mainFolderPair == other.mainFolderPair && + additionalPairs == other.additionalPairs && + compareVar == other.compareVar && + syncConfiguration == other.syncConfiguration && + filterIsActive == other.filterIsActive && + includeFilter == other.includeFilter && + excludeFilter == other.excludeFilter && + hidden == other.hidden && + handleDeletion == other.handleDeletion && + customDeletionDirectory == other.customDeletionDirectory; } }; - typedef std::vector<FileDescrLine> DirectoryDescrType; enum CompareFilesResult { - FILE_LEFT_SIDE_ONLY, + FILE_LEFT_SIDE_ONLY = 0, FILE_RIGHT_SIDE_ONLY, FILE_LEFT_NEWER, FILE_RIGHT_NEWER, FILE_DIFFERENT, FILE_EQUAL, - - FILE_CONFLICT, + FILE_CONFLICT }; - - //quick workaround until tr1::shared_ptr is available - class OptionalString + //attention make sure these /|\ \|/ two enums match!!! + enum CompareDirResult { - public: - OptionalString() : str(NULL) {} - OptionalString(const wxString& other) : str(new wxString(other)) {} - OptionalString(const OptionalString& other) : str(other.str == NULL ? NULL : new wxString(*other.str)) {} - ~OptionalString() - { - delete str; - } - - OptionalString& operator=(const OptionalString& other) - { - wxString* newStr = other.str == NULL ? NULL : new wxString(*other.str); - delete str; - str = newStr; - return *this; - } - - const wxString& get() const - { - static const wxString emptyStr; - return str == NULL ? emptyStr : *str; - } - - private: - wxString* str; + DIR_LEFT_SIDE_ONLY = FILE_LEFT_SIDE_ONLY, + DIR_RIGHT_SIDE_ONLY = FILE_RIGHT_SIDE_ONLY, + DIR_EQUAL = FILE_EQUAL }; - struct FileCompareLine - { - FileCompareLine(const CompareFilesResult defaultCmpResult, - const SyncDirection defaultDirection, - const bool selected) : - cmpResult(defaultCmpResult), - syncDir(defaultDirection), - selectedForSynchronization(selected) {} - FileDescrLine fileDescrLeft; - FileDescrLine fileDescrRight; + inline + CompareFilesResult convertToFilesResult(CompareDirResult value) + { + return static_cast<CompareFilesResult>(value); + } - CompareFilesResult cmpResult; - SyncDirection syncDir; - bool selectedForSynchronization; - OptionalString conflictDescription; //filled only if cmpResult == FILE_CONFLICT - }; - typedef std::vector<FileCompareLine> FileComparison; + wxString getDescription(CompareFilesResult cmpRes); + wxString getSymbol(CompareFilesResult cmpRes); - struct FolderCompareLine //support for multiple folder pairs + enum SyncOperation { - FolderPair syncPair; //directories to be synced (ending with separator) - FileComparison fileCmp; - - void swap(FolderCompareLine& other) - { - std::swap(syncPair, other.syncPair); - fileCmp.swap(other.fileCmp); - } + SO_CREATE_NEW_LEFT, + SO_CREATE_NEW_RIGHT, + SO_DELETE_LEFT, + SO_DELETE_RIGHT, + SO_OVERWRITE_LEFT, + SO_OVERWRITE_RIGHT, + SO_DO_NOTHING, + SO_UNRESOLVED_CONFLICT }; - typedef std::vector<FolderCompareLine> FolderComparison; - //References to single lines(in FileComparison) inside FolderComparison - typedef std::vector<std::set<int> > FolderCompRef; + wxString getDescription(SyncOperation op); + wxString getSymbol(SyncOperation op); - class AbortThisProcess //Exception class used to abort the "compare" and "sync" process - { - public: - AbortThisProcess() {} - ~AbortThisProcess() {} - }; + //Exception class used to abort the "compare" and "sync" process + class AbortThisProcess {}; } #endif // FREEFILESYNC_H_INCLUDED diff --git a/synchronization.cpp b/synchronization.cpp index a1c12784..59f3692d 100644 --- a/synchronization.cpp +++ b/synchronization.cpp @@ -1,11 +1,15 @@ #include "synchronization.h" +#include <stdexcept> #include <wx/intl.h> #include <wx/msgdlg.h> #include <wx/log.h> #include "algorithm.h" -#include "shared/globalFunctions.h" +#include "shared/systemConstants.h" #include "library/statusHandler.h" #include "shared/fileHandling.h" +#include <wx/file.h> +#include <boost/bind.hpp> +#include "shared/globalFunctions.h" using namespace FreeFileSync; @@ -19,33 +23,21 @@ void SyncStatistics::init() deleteLeft = 0; deleteRight = 0; conflict = 0; + rowsTotal = 0; } -SyncStatistics::SyncStatistics(const FileComparison& fileCmp) +SyncStatistics::SyncStatistics(const BaseDirMapping& baseDir) { init(); - - FileComparison::const_iterator start = fileCmp.begin(); - FileComparison::const_iterator end = fileCmp.end(); - for (; start != end; ++start) - getNumbers(*start); + getNumbersRecursively(baseDir); } SyncStatistics::SyncStatistics(const FolderComparison& folderCmp) { init(); - - for (FolderComparison::const_iterator j = folderCmp.begin(); j != folderCmp.end(); ++j) - { - const FileComparison& fileCmp = j->fileCmp; - - FileComparison::const_iterator start = fileCmp.begin(); - FileComparison::const_iterator end = fileCmp.end(); - for (; start != end; ++start) - getNumbers(*start); - } + std::for_each(folderCmp.begin(), folderCmp.end(), boost::bind(&SyncStatistics::getNumbersRecursively, this, _1)); } @@ -82,19 +74,44 @@ wxULongLong SyncStatistics::getDataToProcess() const } +int SyncStatistics::getRowCount() const +{ + return rowsTotal; +} + + inline -void SyncStatistics::getNumbers(const FileCompareLine& fileCmpLine) +void SyncStatistics::getNumbersRecursively(const HierarchyObject& hierObj) { - switch (FreeFileSync::getSyncOperation(fileCmpLine)) //evaluate comparison result and sync direction + //process directories + std::for_each(hierObj.subDirs.begin(), hierObj.subDirs.end(), + boost::bind(&SyncStatistics::getDirNumbers, this, _1)); + + //process files + std::for_each(hierObj.subFiles.begin(), hierObj.subFiles.end(), + boost::bind(&SyncStatistics::getFileNumbers, this, _1)); + + rowsTotal += hierObj.subDirs.size(); + rowsTotal += hierObj.subFiles.size(); + + //recurse into sub-dirs + std::for_each(hierObj.subDirs.begin(), hierObj.subDirs.end(), boost::bind(&SyncStatistics::getNumbersRecursively, this, _1)); +} + + +inline +void SyncStatistics::getFileNumbers(const FileMapping& fileObj) +{ + switch (FreeFileSync::getSyncOperation(fileObj)) //evaluate comparison result and sync direction { case SO_CREATE_NEW_LEFT: ++createLeft; - dataToProcess += fileCmpLine.fileDescrRight.fileSize; + dataToProcess += fileObj.getFileSize<RIGHT_SIDE>(); break; case SO_CREATE_NEW_RIGHT: ++createRight; - dataToProcess += fileCmpLine.fileDescrLeft.fileSize; + dataToProcess += fileObj.getFileSize<LEFT_SIDE>(); break; case SO_DELETE_LEFT: @@ -107,12 +124,12 @@ void SyncStatistics::getNumbers(const FileCompareLine& fileCmpLine) case SO_OVERWRITE_LEFT: ++overwriteLeft; - dataToProcess += fileCmpLine.fileDescrRight.fileSize; + dataToProcess += fileObj.getFileSize<RIGHT_SIDE>(); break; case SO_OVERWRITE_RIGHT: ++overwriteRight; - dataToProcess += fileCmpLine.fileDescrLeft.fileSize; + dataToProcess += fileObj.getFileSize<LEFT_SIDE>(); break; case SO_DO_NOTHING: @@ -125,87 +142,163 @@ void SyncStatistics::getNumbers(const FileCompareLine& fileCmpLine) } +inline +void SyncStatistics::getDirNumbers(const DirMapping& dirObj) +{ + switch (FreeFileSync::getSyncOperation(dirObj)) //evaluate comparison result and sync direction + { + case SO_CREATE_NEW_LEFT: + ++createLeft; + break; + + case SO_CREATE_NEW_RIGHT: + ++createRight; + break; + + case SO_DELETE_LEFT: + ++deleteLeft; + break; + + case SO_DELETE_RIGHT: + ++deleteRight; + break; + + case SO_OVERWRITE_LEFT: + case SO_OVERWRITE_RIGHT: + case SO_UNRESOLVED_CONFLICT: + assert(false); + break; + + case SO_DO_NOTHING: + break; + } +} + + +std::vector<FreeFileSync::FolderPairSyncCfg> FreeFileSync::extractSyncCfg(const MainConfiguration& mainCfg) +{ + std::vector<FolderPairSyncCfg> output; + + //add main pair + output.push_back( + FolderPairSyncCfg(mainCfg.handleDeletion, + mainCfg.customDeletionDirectory)); + + //add additional pairs + for (std::vector<FolderPairEnh>::const_iterator i = mainCfg.additionalPairs.begin(); i != mainCfg.additionalPairs.end(); ++i) + output.push_back( + FolderPairSyncCfg(i->altSyncConfig.get() ? i->altSyncConfig->handleDeletion : mainCfg.handleDeletion, + i->altSyncConfig.get() ? i->altSyncConfig->customDeletionDirectory : mainCfg.customDeletionDirectory)); + return output; +} + + template <bool recyclerUsed> -std::pair<wxLongLong, wxLongLong> spaceNeededSub(const FileComparison& fileCmp) +class DiskSpaceNeeded { - wxLongLong spaceNeededLeft; - wxLongLong spaceNeededRight; +public: + DiskSpaceNeeded(const BaseDirMapping& baseObj) + { + processRecursively(baseObj); + } - for (FileComparison::const_iterator i = fileCmp.begin(); i != fileCmp.end(); ++i) + std::pair<wxLongLong, wxLongLong> getSpaceTotal() const { - if (i->selectedForSynchronization) //do not add filtered entries - { - //get data to process - switch (i->syncDir) + return std::make_pair(spaceNeededLeft, spaceNeededRight); + } + +private: + void processRecursively(const HierarchyObject& hierObj) + { + //don't process directories + + //process files + for (HierarchyObject::SubFileMapping::const_iterator i = hierObj.subFiles.begin(); i != hierObj.subFiles.end(); ++i) + if (i->selectedForSynchronization) //do not add filtered entries { - case SYNC_DIR_LEFT: //copy from right to left - if (!recyclerUsed) - spaceNeededLeft -= globalFunctions::convertToSigned(i->fileDescrLeft.fileSize); - spaceNeededLeft += globalFunctions::convertToSigned(i->fileDescrRight.fileSize); - break; - - case SYNC_DIR_RIGHT: //copy from left to right - if (!recyclerUsed) - spaceNeededRight -= globalFunctions::convertToSigned(i->fileDescrRight.fileSize); - spaceNeededRight += globalFunctions::convertToSigned(i->fileDescrLeft.fileSize); - break; - - case SYNC_DIR_NONE: - case SYNC_UNRESOLVED_CONFLICT: - break; + //get data to process + switch (i->syncDir) + { + case SYNC_DIR_LEFT: //copy from right to left + if (!recyclerUsed) + spaceNeededLeft -= globalFunctions::convertToSigned(i->getFileSize<LEFT_SIDE>()); + spaceNeededLeft += globalFunctions::convertToSigned(i->getFileSize<RIGHT_SIDE>()); + break; + + case SYNC_DIR_RIGHT: //copy from left to right + if (!recyclerUsed) + spaceNeededRight -= globalFunctions::convertToSigned(i->getFileSize<RIGHT_SIDE>()); + spaceNeededRight += globalFunctions::convertToSigned(i->getFileSize<LEFT_SIDE>()); + break; + + case SYNC_DIR_NONE: + break; + } } - } + + //recurse into sub-dirs + std::for_each(hierObj.subDirs.begin(), hierObj.subDirs.end(), boost::bind(&DiskSpaceNeeded<recyclerUsed>::processRecursively, this, _1)); } - return std::pair<wxLongLong, wxLongLong>(spaceNeededLeft, spaceNeededRight); -} + wxLongLong spaceNeededLeft; + wxLongLong spaceNeededRight; +}; -std::pair<wxLongLong, wxLongLong> freeDiskSpaceNeeded(const FileComparison& fileCmp, const DeletionPolicy handleDeletion) +std::pair<wxLongLong, wxLongLong> freeDiskSpaceNeeded(const BaseDirMapping& baseDirObj, const DeletionPolicy handleDeletion) { switch (handleDeletion) { case FreeFileSync::DELETE_PERMANENTLY: - return spaceNeededSub<false>(fileCmp); + return DiskSpaceNeeded<false>(baseDirObj).getSpaceTotal(); case FreeFileSync::MOVE_TO_RECYCLE_BIN: - return spaceNeededSub<true>(fileCmp); + return DiskSpaceNeeded<true>(baseDirObj).getSpaceTotal(); case FreeFileSync::MOVE_TO_CUSTOM_DIRECTORY: - return spaceNeededSub<true>(fileCmp); //this is not necessarily correct! it needs to be checked if user-def recycle bin dir and sync-dir are on same drive + //warning: this is not necessarily correct! it needs to be checked if user-def recycle bin dir and sync-dir are on same drive + return DiskSpaceNeeded<true>(baseDirObj).getSpaceTotal(); } assert(false); - return spaceNeededSub<true>(fileCmp); //dummy + return std::make_pair(2000000000, 2000000000); //dummy +} + + +bool synchronizationNeeded(const SyncStatistics& statisticsTotal) +{ + return statisticsTotal.getCreate() + + statisticsTotal.getOverwrite() + + statisticsTotal.getDelete() + + //conflicts (unless excluded) justify a sync! Also note: if this method returns false, no warning about unresolved conflicts were shown! + statisticsTotal.getConflict() != 0; } bool FreeFileSync::synchronizationNeeded(const FolderComparison& folderCmp) { const SyncStatistics statisticsTotal(folderCmp); - return statisticsTotal.getCreate() + statisticsTotal.getOverwrite() + statisticsTotal.getDelete() != 0; + return ::synchronizationNeeded(statisticsTotal); } //test if user accidentally tries to sync the wrong folders -bool significantDifferenceDetected(const FileComparison& fileCmp) +bool significantDifferenceDetected(const SyncStatistics& folderPairStat) { - const SyncStatistics st(fileCmp); - //initial file copying shall not be detected as major difference - if ( st.getCreate(true, false) == 0 && - st.getOverwrite() == 0 && - st.getDelete() == 0 && - st.getConflict() == 0) return false; - if ( st.getCreate(false, true) == 0 && - st.getOverwrite() == 0 && - st.getDelete() == 0 && - st.getConflict() == 0) return false; - - const int changedRows = st.getCreate() + - st.getOverwrite() + - st.getDelete() + - st.getConflict(); - - return changedRows >= 10 && changedRows > 0.5 * fileCmp.size(); + if ( folderPairStat.getCreate(true, false) == 0 && + folderPairStat.getOverwrite() == 0 && + folderPairStat.getDelete() == 0 && + folderPairStat.getConflict() == 0) return false; + if ( folderPairStat.getCreate(false, true) == 0 && + folderPairStat.getOverwrite() == 0 && + folderPairStat.getDelete() == 0 && + folderPairStat.getConflict() == 0) return false; + + const int changedRows = folderPairStat.getCreate() + + folderPairStat.getOverwrite() + + folderPairStat.getDelete() + + folderPairStat.getConflict(); + + return changedRows >= 10 && changedRows > 0.5 * folderPairStat.getRowCount(); } @@ -227,8 +320,6 @@ FreeFileSync::SyncOperation FreeFileSync::getSyncOperation( //evaluate compariso return SO_CREATE_NEW_RIGHT; //copy files to right case SYNC_DIR_NONE: return SO_DO_NOTHING; - case SYNC_UNRESOLVED_CONFLICT: - assert(false); } break; @@ -241,8 +332,6 @@ FreeFileSync::SyncOperation FreeFileSync::getSyncOperation( //evaluate compariso return SO_DELETE_RIGHT; //delete files on right case SYNC_DIR_NONE: return SO_DO_NOTHING; - case SYNC_UNRESOLVED_CONFLICT: - assert(false); } break; @@ -257,8 +346,6 @@ FreeFileSync::SyncOperation FreeFileSync::getSyncOperation( //evaluate compariso return SO_OVERWRITE_RIGHT; //copy from left to right case SYNC_DIR_NONE: return SO_DO_NOTHING; - case SYNC_UNRESOLVED_CONFLICT: - assert(false); } break; @@ -270,8 +357,6 @@ FreeFileSync::SyncOperation FreeFileSync::getSyncOperation( //evaluate compariso case SYNC_DIR_RIGHT: return SO_OVERWRITE_RIGHT; //copy from left to right case SYNC_DIR_NONE: - return SO_DO_NOTHING; - case SYNC_UNRESOLVED_CONFLICT: return SO_UNRESOLVED_CONFLICT; } break; @@ -285,9 +370,9 @@ FreeFileSync::SyncOperation FreeFileSync::getSyncOperation( //evaluate compariso } -SyncOperation FreeFileSync::getSyncOperation(const FileCompareLine& line) //convenience function +SyncOperation FreeFileSync::getSyncOperation(const FileSystemObject& fsObj) //convenience function { - return getSyncOperation(line.cmpResult, line.selectedForSynchronization, line.syncDir); + return getSyncOperation(fsObj.getCategory(), fsObj.selectedForSynchronization, fsObj.syncDir); } @@ -324,16 +409,14 @@ wxString getSessionDeletionDir(const wxString& customDeletionDirectory) } -SyncProcess::SyncProcess(const DeletionPolicy handleDeletion, - const wxString& custDelFolder, - const bool copyFileSymLinks, +SyncProcess::SyncProcess(const bool copyFileSymLinks, const bool traverseDirSymLinks, - xmlAccess::WarningMessages& warnings, + xmlAccess::OptionalDialogs& warnings, + const bool verifyCopiedFiles, StatusHandler* handler) : - m_handleDeletion(handleDeletion), - sessionDeletionDirectory(getSessionDeletionDir(custDelFolder).c_str()), m_copyFileSymLinks(copyFileSymLinks), m_traverseDirSymLinks(traverseDirSymLinks), + m_verifyCopiedFiles(verifyCopiedFiles), m_warnings(warnings), #ifdef FFS_WIN shadowCopyHandler(new ShadowCopy), @@ -342,17 +425,23 @@ SyncProcess::SyncProcess(const DeletionPolicy handleDeletion, txtCopyingFile(Zstring(_("Copying file %x to %y")).Replace(wxT("%x"), wxT("\"%x\""), false).Replace(wxT("%y"), wxT("\n\"%y\""), false)), txtOverwritingFile(Zstring(_("Copying file %x to %y overwriting target")).Replace(wxT("%x"), wxT("\"%x\""), false).Replace(wxT("%y"), wxT("\n\"%y\""), false)), txtCreatingFolder(Zstring(_("Creating folder %x")).Replace(wxT("%x"), wxT("\n\"%x\""), false)), + txtDeletingFile(Zstring(_("Deleting file %x")).Replace(wxT("%x"), wxT("\n\"%x\""), false)), + txtDeletingFolder(Zstring(_("Deleting folder %x")).Replace( wxT("%x"), wxT("\n\"%x\""), false)), + txtMoveToRecycler(Zstring(_("Moving %x to Recycle Bin")).Replace(wxT("%x"), wxT("\"%x\""), false)), + txtVerifying(Zstring(_("Verifying file %x")).Replace(wxT("%x"), wxT("\n\"%x\""), false)) +{} - txtDeletingFile(m_handleDeletion == MOVE_TO_RECYCLE_BIN ? Zstring(_("Moving %x to Recycle Bin")).Replace(wxT("%x"), wxT("\"%x\""), false) : - m_handleDeletion == DELETE_PERMANENTLY ? Zstring(_("Deleting file %x")).Replace(wxT("%x"), wxT("\n\"%x\""), false) : - Zstring(_("Moving %x to custom directory")).Replace(wxT("%x"), wxT("\"%x\""), false)), - txtDeletingFolder(m_handleDeletion == MOVE_TO_RECYCLE_BIN ? Zstring(_("Moving %x to Recycle Bin")).Replace( wxT("%x"), wxT("\"%x\""), false) : - m_handleDeletion == DELETE_PERMANENTLY ? Zstring(_("Deleting folder %x")).Replace( wxT("%x"), wxT("\n\"%x\""), false) : - Zstring(_("Moving %x to custom directory")).Replace(wxT("%x"), wxT("\"%x\""), false)) +SyncProcess::DeletionHandling::DeletionHandling(const DeletionPolicy handleDel, + const wxString& custDelFolder) : + handleDeletion(handleDel), + currentDelFolder(getSessionDeletionDir(custDelFolder).c_str()), //ends with path separator + txtMoveFileUserDefined( Zstring(_("Moving file %x to user-defined directory %y")). Replace(wxT("%x"), wxT("\"%x\"\n"), false).Replace(wxT("%y"), Zstring(wxT("\"")) + custDelFolder.c_str() + wxT("\""), false)), + txtMoveFolderUserDefined(Zstring(_("Moving folder %x to user-defined directory %y")).Replace(wxT("%x"), wxT("\"%x\"\n"), false).Replace(wxT("%y"), Zstring(wxT("\"")) + custDelFolder.c_str() + wxT("\""), false)) {} + class MoveFileCallbackImpl : public MoveFileCallback //callback functionality { public: @@ -375,21 +464,45 @@ private: }; +template <FreeFileSync::SelectedSide side> inline -void SyncProcess::removeFile(const FileDescrLine& fildesc, const Zstring& altDeletionDir) +void SyncProcess::removeFile(const FileMapping& fileObj, const DeletionHandling& delHandling, bool showStatusUpdate) const { - switch (m_handleDeletion) + Zstring statusText; + + switch (delHandling.handleDeletion) { case FreeFileSync::DELETE_PERMANENTLY: - FreeFileSync::removeFile(fildesc.fullName, false); + if (showStatusUpdate) //status information + { + statusText = txtDeletingFile; + statusText.Replace(wxT("%x"), fileObj.getFullName<side>(), false); + statusUpdater->updateStatusText(statusText); + statusUpdater->requestUiRefresh(); //trigger display refresh + } + FreeFileSync::removeFile(fileObj.getFullName<side>(), false); break; case FreeFileSync::MOVE_TO_RECYCLE_BIN: - FreeFileSync::removeFile(fildesc.fullName, true); + if (showStatusUpdate) //status information + { + statusText = txtMoveToRecycler; + statusText.Replace(wxT("%x"), fileObj.getFullName<side>(), false); + statusUpdater->updateStatusText(statusText); + statusUpdater->requestUiRefresh(); //trigger display refresh + } + FreeFileSync::removeFile(fileObj.getFullName<side>(), true); break; case FreeFileSync::MOVE_TO_CUSTOM_DIRECTORY: - if (FreeFileSync::fileExists(fildesc.fullName)) + if (FreeFileSync::fileExists(fileObj.getFullName<side>())) { - const Zstring targetFile = altDeletionDir + fildesc.relativeName.c_str(); //altDeletionDir ends with path separator + if (showStatusUpdate) //status information + { + statusText = delHandling.txtMoveFileUserDefined; + statusText.Replace(wxT("%x"), fileObj.getFullName<side>(), false); + statusUpdater->updateStatusText(statusText); + statusUpdater->requestUiRefresh(); //trigger display refresh + } + const Zstring targetFile = delHandling.currentDelFolder + fileObj.getRelativeName<side>(); //altDeletionDir ends with path separator const Zstring targetDir = targetFile.BeforeLast(globalFunctions::FILE_NAME_SEPARATOR); if (!FreeFileSync::dirExists(targetDir)) @@ -405,76 +518,72 @@ void SyncProcess::removeFile(const FileDescrLine& fildesc, const Zstring& altDel } MoveFileCallbackImpl callBack(statusUpdater); //if file needs to be copied we need callback functionality to update screen and offer abort - FreeFileSync::moveFile(fildesc.fullName, targetFile, &callBack); + FreeFileSync::moveFile(fileObj.getFullName<side>(), targetFile, &callBack); } break; } } -void SyncProcess::synchronizeFile(const FileCompareLine& cmpLine, const FolderPair& folderPair, const Zstring& altDeletionDir) +void SyncProcess::synchronizeFile(FileMapping& fileObj, const DeletionHandling& delHandling) const { Zstring statusText; Zstring target; - switch (getSyncOperation(cmpLine)) //evaluate comparison result and sync direction + switch (getSyncOperation(fileObj)) //evaluate comparison result and sync direction { case SO_CREATE_NEW_LEFT: - target = folderPair.leftDirectory + cmpLine.fileDescrRight.relativeName.c_str(); + target = fileObj.getBaseDirPf<LEFT_SIDE>() + fileObj.getRelativeName<RIGHT_SIDE>(); statusText = txtCopyingFile; - statusText.Replace(wxT("%x"), cmpLine.fileDescrRight.fullName.AfterLast(globalFunctions::FILE_NAME_SEPARATOR), false); + statusText.Replace(wxT("%x"), fileObj.getShortName<RIGHT_SIDE>(), false); statusText.Replace(wxT("%y"), target.BeforeLast(globalFunctions::FILE_NAME_SEPARATOR), false); statusUpdater->updateStatusText(statusText); + statusUpdater->requestUiRefresh(); //trigger display refresh - copyFileUpdating(cmpLine.fileDescrRight.fullName, target, cmpLine.fileDescrRight.fileSize); + copyFileUpdating(fileObj.getFullName<RIGHT_SIDE>(), target, fileObj.getFileSize<RIGHT_SIDE>()); break; case SO_CREATE_NEW_RIGHT: - target = folderPair.rightDirectory + cmpLine.fileDescrLeft.relativeName.c_str(); + target = fileObj.getBaseDirPf<RIGHT_SIDE>() + fileObj.getRelativeName<LEFT_SIDE>(); statusText = txtCopyingFile; - statusText.Replace(wxT("%x"), cmpLine.fileDescrLeft.fullName.AfterLast(globalFunctions::FILE_NAME_SEPARATOR), false); + statusText.Replace(wxT("%x"), fileObj.getShortName<LEFT_SIDE>(), false); statusText.Replace(wxT("%y"), target.BeforeLast(globalFunctions::FILE_NAME_SEPARATOR), false); statusUpdater->updateStatusText(statusText); + statusUpdater->requestUiRefresh(); //trigger display refresh - copyFileUpdating(cmpLine.fileDescrLeft.fullName, target, cmpLine.fileDescrLeft.fileSize); + copyFileUpdating(fileObj.getFullName<LEFT_SIDE>(), target, fileObj.getFileSize<LEFT_SIDE>()); break; case SO_DELETE_LEFT: - statusText = txtDeletingFile; - statusText.Replace(wxT("%x"), cmpLine.fileDescrLeft.fullName, false); - statusUpdater->updateStatusText(statusText); - - removeFile(cmpLine.fileDescrLeft, altDeletionDir); + removeFile<LEFT_SIDE>(fileObj, delHandling, true); //status updates in subroutine break; case SO_DELETE_RIGHT: - statusText = txtDeletingFile; - statusText.Replace(wxT("%x"), cmpLine.fileDescrRight.fullName, false); - statusUpdater->updateStatusText(statusText); - - removeFile(cmpLine.fileDescrRight, altDeletionDir); + removeFile<RIGHT_SIDE>(fileObj, delHandling, true); //status updates in subroutine break; case SO_OVERWRITE_RIGHT: statusText = txtOverwritingFile; - statusText.Replace(wxT("%x"), cmpLine.fileDescrLeft.fullName.AfterLast(globalFunctions::FILE_NAME_SEPARATOR), false); - statusText.Replace(wxT("%y"), cmpLine.fileDescrRight.fullName.BeforeLast(globalFunctions::FILE_NAME_SEPARATOR), false); + statusText.Replace(wxT("%x"), fileObj.getShortName<LEFT_SIDE>(), false); + statusText.Replace(wxT("%y"), fileObj.getFullName<RIGHT_SIDE>().BeforeLast(globalFunctions::FILE_NAME_SEPARATOR), false); statusUpdater->updateStatusText(statusText); + statusUpdater->requestUiRefresh(); //trigger display refresh - removeFile(cmpLine.fileDescrRight, altDeletionDir); - copyFileUpdating(cmpLine.fileDescrLeft.fullName, cmpLine.fileDescrRight.fullName, cmpLine.fileDescrLeft.fileSize); + removeFile<RIGHT_SIDE>(fileObj, delHandling, false); + copyFileUpdating(fileObj.getFullName<LEFT_SIDE>(), fileObj.getFullName<RIGHT_SIDE>(), fileObj.getFileSize<LEFT_SIDE>()); break; case SO_OVERWRITE_LEFT: statusText = txtOverwritingFile; - statusText.Replace(wxT("%x"), cmpLine.fileDescrRight.fullName.AfterLast(globalFunctions::FILE_NAME_SEPARATOR), false); - statusText.Replace(wxT("%y"), cmpLine.fileDescrLeft.fullName.BeforeLast(globalFunctions::FILE_NAME_SEPARATOR), false); + statusText.Replace(wxT("%x"), fileObj.getShortName<RIGHT_SIDE>(), false); + statusText.Replace(wxT("%y"), fileObj.getFullName<LEFT_SIDE>().BeforeLast(globalFunctions::FILE_NAME_SEPARATOR), false); statusUpdater->updateStatusText(statusText); + statusUpdater->requestUiRefresh(); //trigger display refresh - removeFile(cmpLine.fileDescrLeft, altDeletionDir); - copyFileUpdating(cmpLine.fileDescrRight.fullName, cmpLine.fileDescrLeft.fullName, cmpLine.fileDescrRight.fileSize); + removeFile<LEFT_SIDE>(fileObj, delHandling, false); + copyFileUpdating(fileObj.getFullName<RIGHT_SIDE>(), fileObj.getFullName<LEFT_SIDE>(), fileObj.getFileSize<RIGHT_SIDE>()); break; case SO_DO_NOTHING: @@ -485,24 +594,59 @@ void SyncProcess::synchronizeFile(const FileCompareLine& cmpLine, const FolderPa //progress indicator update //indicator is updated only if file is sync'ed correctly (and if some sync was done)! statusUpdater->updateProcessedData(1, 0); //processed data is communicated in subfunctions! + + //update FileMapping + switch (fileObj.syncDir) + { + case SYNC_DIR_LEFT: + fileObj.copyTo<LEFT_SIDE>(); + break; + case SYNC_DIR_RIGHT: + fileObj.copyTo<RIGHT_SIDE>(); + break; + case SYNC_DIR_NONE: + assert(!"if nothing's todo then why arrive here?"); + break; + } } +template <FreeFileSync::SelectedSide side> inline -void SyncProcess::removeFolder(const FileDescrLine& fildesc, const Zstring& altDeletionDir) +void SyncProcess::removeFolder(const DirMapping& dirObj, const DeletionHandling& delHandling) const { - switch (m_handleDeletion) + Zstring statusText; + + switch (delHandling.handleDeletion) { case FreeFileSync::DELETE_PERMANENTLY: - FreeFileSync::removeDirectory(fildesc.fullName, false); + //status information + statusText = txtDeletingFolder; + statusText.Replace(wxT("%x"), dirObj.getFullName<side>(), false); + statusUpdater->updateStatusText(statusText); + statusUpdater->requestUiRefresh(); //trigger display refresh + + FreeFileSync::removeDirectory(dirObj.getFullName<side>(), false); break; case FreeFileSync::MOVE_TO_RECYCLE_BIN: - FreeFileSync::removeDirectory(fildesc.fullName, true); + //status information + statusText = txtMoveToRecycler; + statusText.Replace(wxT("%x"), dirObj.getFullName<side>(), false); + statusUpdater->updateStatusText(statusText); + statusUpdater->requestUiRefresh(); //trigger display refresh + + FreeFileSync::removeDirectory(dirObj.getFullName<side>(), true); break; case FreeFileSync::MOVE_TO_CUSTOM_DIRECTORY: - if (FreeFileSync::dirExists(fildesc.fullName)) + if (FreeFileSync::dirExists(dirObj.getFullName<side>())) { - const Zstring targetDir = altDeletionDir + fildesc.relativeName.c_str(); + //status information + statusText = delHandling.txtMoveFolderUserDefined; + statusText.Replace(wxT("%x"), dirObj.getFullName<side>(), false); + statusUpdater->updateStatusText(statusText); + statusUpdater->requestUiRefresh(); //trigger display refresh + + const Zstring targetDir = delHandling.currentDelFolder + dirObj.getRelativeName<side>(); const Zstring targetSuperDir = targetDir.BeforeLast(globalFunctions::FILE_NAME_SEPARATOR);; if (!FreeFileSync::dirExists(targetSuperDir)) @@ -518,61 +662,55 @@ void SyncProcess::removeFolder(const FileDescrLine& fildesc, const Zstring& altD } MoveFileCallbackImpl callBack(statusUpdater); //if files need to be copied, we need callback functionality to update screen and offer abort - FreeFileSync::moveDirectory(fildesc.fullName, targetDir, true, &callBack); + FreeFileSync::moveDirectory(dirObj.getFullName<side>(), targetDir, true, &callBack); } break; } } -void SyncProcess::synchronizeFolder(const FileCompareLine& cmpLine, const FolderPair& folderPair, const Zstring& altDeletionDir) +void SyncProcess::synchronizeFolder(DirMapping& dirObj, const DeletionHandling& delHandling) const { Zstring statusText; Zstring target; //synchronize folders: - switch (getSyncOperation(cmpLine)) //evaluate comparison result and sync direction + switch (getSyncOperation(dirObj)) //evaluate comparison result and sync direction { case SO_CREATE_NEW_LEFT: - target = folderPair.leftDirectory + cmpLine.fileDescrRight.relativeName.c_str(); + target = dirObj.getBaseDirPf<LEFT_SIDE>() + dirObj.getRelativeName<RIGHT_SIDE>(); statusText = txtCreatingFolder; statusText.Replace(wxT("%x"), target, false); statusUpdater->updateStatusText(statusText); + statusUpdater->requestUiRefresh(); //trigger display refresh //some check to catch the error that directory on source has been deleted externally after "compare"... - if (!FreeFileSync::dirExists(cmpLine.fileDescrRight.fullName)) - throw FileError(wxString(_("Error: Source directory does not exist anymore:")) + wxT("\n\"") + cmpLine.fileDescrRight.fullName.c_str() + wxT("\"")); - createDirectory(target, cmpLine.fileDescrRight.fullName, !m_traverseDirSymLinks); //traverse symlinks <=> !copy symlinks + if (!FreeFileSync::dirExists(dirObj.getFullName<RIGHT_SIDE>())) + throw FileError(wxString(_("Error: Source directory does not exist anymore:")) + wxT("\n\"") + dirObj.getFullName<RIGHT_SIDE>() + wxT("\"")); + createDirectory(target, dirObj.getFullName<RIGHT_SIDE>(), !m_traverseDirSymLinks); //traverse symlinks <=> !copy symlinks break; case SO_CREATE_NEW_RIGHT: - target = folderPair.rightDirectory + cmpLine.fileDescrLeft.relativeName.c_str(); + target = dirObj.getBaseDirPf<RIGHT_SIDE>() + dirObj.getRelativeName<LEFT_SIDE>(); statusText = txtCreatingFolder; statusText.Replace(wxT("%x"), target, false); statusUpdater->updateStatusText(statusText); + statusUpdater->requestUiRefresh(); //trigger display refresh //some check to catch the error that directory on source has been deleted externally after "compare"... - if (!FreeFileSync::dirExists(cmpLine.fileDescrLeft.fullName)) - throw FileError(wxString(_("Error: Source directory does not exist anymore:")) + wxT("\n\"") + cmpLine.fileDescrLeft.fullName.c_str() + wxT("\"")); - createDirectory(target, cmpLine.fileDescrLeft.fullName, !m_traverseDirSymLinks); //traverse symlinks <=> !copy symlinks + if (!FreeFileSync::dirExists(dirObj.getFullName<LEFT_SIDE>())) + throw FileError(wxString(_("Error: Source directory does not exist anymore:")) + wxT("\n\"") + dirObj.getFullName<LEFT_SIDE>() + wxT("\"")); + createDirectory(target, dirObj.getFullName<LEFT_SIDE>(), !m_traverseDirSymLinks); //traverse symlinks <=> !copy symlinks break; case SO_DELETE_LEFT: - statusText = txtDeletingFolder; - statusText.Replace(wxT("%x"), cmpLine.fileDescrLeft.fullName, false); - statusUpdater->updateStatusText(statusText); - - removeFolder(cmpLine.fileDescrLeft, altDeletionDir); + removeFolder<LEFT_SIDE>(dirObj, delHandling); break; case SO_DELETE_RIGHT: - statusText = txtDeletingFolder; - statusText.Replace(wxT("%x"), cmpLine.fileDescrRight.fullName, false); - statusUpdater->updateStatusText(statusText); - - removeFolder(cmpLine.fileDescrRight, altDeletionDir); + removeFolder<RIGHT_SIDE>(dirObj, delHandling); break; case SO_OVERWRITE_RIGHT: @@ -586,42 +724,137 @@ void SyncProcess::synchronizeFolder(const FileCompareLine& cmpLine, const Folder //progress indicator update //indicator is updated only if directory is sync'ed correctly (and if some work was done)! statusUpdater->updateProcessedData(1, 0); //each call represents one processed file + + //update DirMapping + switch (dirObj.syncDir) + { + case SYNC_DIR_LEFT: + dirObj.copyTo<LEFT_SIDE>(); + break; + case SYNC_DIR_RIGHT: + dirObj.copyTo<RIGHT_SIDE>(); + break; + case SYNC_DIR_NONE: + assert(!"if nothing's todo then why arrive here?"); + break; + } } inline -bool deletionImminent(const FileCompareLine& line) +bool deletionImminent(const FileSystemObject& fsObj) { //test if current sync-line will result in deletion of files -> used to avoid disc space bottlenecks - const SyncOperation op = FreeFileSync::getSyncOperation(line); + const SyncOperation op = FreeFileSync::getSyncOperation(fsObj); return op == FreeFileSync::SO_DELETE_LEFT || op == FreeFileSync::SO_DELETE_RIGHT; } -class RemoveAtExit //this class ensures, that the result of the method below is ALWAYS written on exit, even if exceptions are thrown! +class RemoveInvalid { public: - RemoveAtExit(FileComparison& fileCmp) : - gridToWrite(fileCmp) {} + RemoveInvalid(HierarchyObject& hierObj) : + hierObj_(hierObj) {} - ~RemoveAtExit() + ~RemoveInvalid() { - globalFunctions::removeRowsFromVector(rowsToDelete, gridToWrite); + FileSystemObject::removeEmptyNonRec(hierObj_); } - void markRow(int nr) +private: + HierarchyObject& hierObj_; +}; + + +template <bool deleteOnly> //"true" if files deletion shall happen only +class SyncProcess::SyncRecursively +{ +public: + SyncRecursively(const SyncProcess* const syncProc, const SyncProcess::DeletionHandling& delHandling) : + syncProc_(syncProc), + delHandling_(delHandling) {} + + void execute(HierarchyObject& hierObj) { - rowsToDelete.insert(nr); + //enforce removal of invalid entries (where both sides are empty) + RemoveInvalid dummy(hierObj); //non-recursive + + //synchronize folders: + for (HierarchyObject::SubDirMapping::iterator i = hierObj.subDirs.begin(); i != hierObj.subDirs.end(); ++i) + { + if (deleteOnly) //no need, to process folders more than once! + { + while (true) + { + try + { + syncProc_->synchronizeFolder(*i, delHandling_); + break; + } + catch (FileError& error) + { + //User abort when copying files or moving files/directories into custom deletion directory: + //windows build: abort if requested, don't show error message if cancelled by user! + //linux build: this refresh is not necessary, because user abort triggers an AbortThisProcess() exception without a FileError() + syncProc_->statusUpdater->requestUiRefresh(true); + + ErrorHandler::Response rv = syncProc_->statusUpdater->reportError(error.show()); + if (rv == ErrorHandler::IGNORE_ERROR) + break; + else if (rv == ErrorHandler::RETRY) + ; //continue with loop + else + assert (false); + } + } + } + + //recursive synchronization: don't recurse into already deleted subdirectories + if (!i->isEmpty()) + execute(*i); + } + + + //synchronize files: + for (HierarchyObject::SubFileMapping::iterator i = hierObj.subFiles.begin(); i != hierObj.subFiles.end(); ++i) + { + if ( ( deleteOnly && deletionImminent(*i)) || + (!deleteOnly && !deletionImminent(*i))) + { + while (true) + { + try + { + syncProc_->synchronizeFile(*i, delHandling_); + break; + } + catch (FileError& error) + { + //User abort when copying files or moving files/directories into custom deletion directory: + //windows build: abort if requested, don't show error message if cancelled by user! + //linux build: this refresh is not necessary, because user abort triggers an AbortThisProcess() exception without a FileError() + syncProc_->statusUpdater->requestUiRefresh(true); + + ErrorHandler::Response rv = syncProc_->statusUpdater->reportError(error.show()); + if ( rv == ErrorHandler::IGNORE_ERROR) + break; + else if (rv == ErrorHandler::RETRY) + ; //continue with loop + else + assert (false); + } + } + } + } } private: - FileComparison& gridToWrite; - std::set<int> rowsToDelete; + const SyncProcess* const syncProc_; + const SyncProcess::DeletionHandling& delHandling_; }; -//synchronize and returns all rows that have not been synced -void SyncProcess::startSynchronizationProcess(FolderComparison& folderCmp) +void SyncProcess::startSynchronizationProcess(const std::vector<FolderPairSyncCfg>& syncConfig, FolderComparison& folderCmp) { #ifndef __WXDEBUG__ wxLogNull noWxLogs; //prevent wxWidgets logging @@ -637,61 +870,67 @@ void SyncProcess::startSynchronizationProcess(FolderComparison& folderCmp) //-------------------some basic checks:------------------------------------------ - if (statisticsTotal.getOverwrite() + statisticsTotal.getDelete() > 0) + if (syncConfig.size() != folderCmp.size()) + throw std::logic_error("Programming Error: Contract violation!"); + + for (FolderComparison::const_iterator j = folderCmp.begin(); j != folderCmp.end(); ++j) { - //test existence of Recycle Bin - if (m_handleDeletion == FreeFileSync::MOVE_TO_RECYCLE_BIN && !FreeFileSync::recycleBinExists()) - { - statusUpdater->reportFatalError(_("Unable to initialize Recycle Bin!")); - return; //should be obsolete! - } + const SyncStatistics statisticsFolderPair(*j); + const FolderPairSyncCfg& folderPairCfg = syncConfig[j - folderCmp.begin()]; - if (m_handleDeletion == FreeFileSync::MOVE_TO_CUSTOM_DIRECTORY) + if (statisticsFolderPair.getOverwrite() + statisticsFolderPair.getDelete() > 0) { - //check if alternate directory for deletion was specified - if (sessionDeletionDirectory.empty()) + //test existence of Recycle Bin + if (folderPairCfg.handleDeletion == FreeFileSync::MOVE_TO_RECYCLE_BIN && !FreeFileSync::recycleBinExists()) { - statusUpdater->reportFatalError(_("Please specify alternate directory for deletion!")); + statusUpdater->reportFatalError(_("Unable to initialize Recycle Bin!")); return; //should be obsolete! } - } - } - for (FolderComparison::const_iterator j = folderCmp.begin(); j != folderCmp.end(); ++j) - { - const FileComparison& fileCmp = j->fileCmp; + if (folderPairCfg.handleDeletion == FreeFileSync::MOVE_TO_CUSTOM_DIRECTORY) + { + //check if user-defined directory for deletion was specified + if (FreeFileSync::getFormattedDirectoryName(folderPairCfg.custDelFolder.c_str()).empty()) + { + statusUpdater->reportFatalError(_("User-defined directory for deletion was not specified!")); + return; //should be obsolete! + } + } + } //check if more than 50% of total number of files/dirs are to be created/overwritten/deleted - if (significantDifferenceDetected(fileCmp)) + if (significantDifferenceDetected(statisticsFolderPair)) { statusUpdater->reportWarning(wxString(_("Significant difference detected:")) + wxT("\n") + - j->syncPair.leftDirectory + wxT(" <-> ") + wxT("\n") + - j->syncPair.rightDirectory + wxT("\n\n") + + j->getBaseDir<LEFT_SIDE>() + wxT(" <-> ") + wxT("\n") + + j->getBaseDir<RIGHT_SIDE>() + wxT("\n\n") + _("More than 50% of the total number of files will be copied or deleted!"), m_warnings.warningSignificantDifference); } //check for sufficient free diskspace in left directory - const std::pair<wxLongLong, wxLongLong> spaceNeeded = freeDiskSpaceNeeded(fileCmp, m_handleDeletion); + const std::pair<wxLongLong, wxLongLong> spaceNeeded = freeDiskSpaceNeeded(*j, folderPairCfg.handleDeletion); wxLongLong freeDiskSpaceLeft; - if (wxGetDiskSpace(j->syncPair.leftDirectory.c_str(), NULL, &freeDiskSpaceLeft)) + if (wxGetDiskSpace(j->getBaseDir<LEFT_SIDE>().c_str(), NULL, &freeDiskSpaceLeft)) { if (freeDiskSpaceLeft < spaceNeeded.first) statusUpdater->reportWarning(wxString(_("Not enough free disk space available in:")) + wxT("\n") + - j->syncPair.leftDirectory + wxT("\n\n") + - _("Total required free disk space:") + wxT(" ") + formatFilesizeToShortString(spaceNeeded.first).c_str(), + wxT("\"") + j->getBaseDir<LEFT_SIDE>() + wxT("\"\n\n") + + _("Total required free disk space:") + wxT(" ") + formatFilesizeToShortString(spaceNeeded.first) + wxT("\n") + + _("Free disk space available:") + wxT(" ") + formatFilesizeToShortString(freeDiskSpaceLeft), m_warnings.warningNotEnoughDiskSpace); } //check for sufficient free diskspace in right directory wxLongLong freeDiskSpaceRight; - if (wxGetDiskSpace(j->syncPair.rightDirectory.c_str(), NULL, &freeDiskSpaceRight)) + if (wxGetDiskSpace(j->getBaseDir<RIGHT_SIDE>().c_str(), NULL, &freeDiskSpaceRight)) { if (freeDiskSpaceRight < spaceNeeded.second) statusUpdater->reportWarning(wxString(_("Not enough free disk space available in:")) + wxT("\n") + - j->syncPair.rightDirectory + wxT("\n\n") + - _("Total required free disk space:") + wxT(" ") + formatFilesizeToShortString(spaceNeeded.second).c_str(), + wxT("\"") + j->getBaseDir<RIGHT_SIDE>() + wxT("\"\n\n") + + _("Total required free disk space:") + wxT(" ") + formatFilesizeToShortString(spaceNeeded.second) + wxT("\n") + + _("Free disk space available:") + wxT(" ") + formatFilesizeToShortString(freeDiskSpaceRight), m_warnings.warningNotEnoughDiskSpace); } } @@ -706,119 +945,25 @@ void SyncProcess::startSynchronizationProcess(FolderComparison& folderCmp) try { - //loop over folder pairs + //loop through all directory pairs + assert(syncConfig.size() == folderCmp.size()); for (FolderComparison::iterator j = folderCmp.begin(); j != folderCmp.end(); ++j) { - FileComparison& fileCmp = j->fileCmp; - const FolderPair& currentPair = j->syncPair; + const FolderPairSyncCfg& folderPairCfg = syncConfig[j - folderCmp.begin()]; //generate name of alternate deletion directory (unique for session AND folder pair) - const Zstring alternateDeletionDir = folderCmp.size() > 1 ? //if multiple folder pairs are used create a subfolder for each pair - sessionDeletionDirectory + globalFunctions::numberToWxString(j - folderCmp.begin() + 1).c_str() + globalFunctions::FILE_NAME_SEPARATOR : - sessionDeletionDirectory; - - //ensure that folderCmp is always written to, even if method is left via exceptions - RemoveAtExit removeRowsAtExit(fileCmp); //note: running at individual folder pair level! + const DeletionHandling currentDelHandling(folderPairCfg.handleDeletion, folderPairCfg.custDelFolder); - // it should never happen, that a directory on left side has same name as file on right side. startCompareProcess() should take care of this - // and split into two "exists on one side only" cases - // Note: this case is not handled by this tool and must be solved by the user + //execute synchronization recursively - //synchronize folders first; advantage: in case of deletions the whole folder is moved to recycle bin instead of single files - for (FileComparison::const_iterator i = fileCmp.begin(); i != fileCmp.end(); ++i) - { - if ( i->fileDescrLeft.objType == FileDescrLine::TYPE_DIRECTORY || - i->fileDescrRight.objType == FileDescrLine::TYPE_DIRECTORY) - { - while (true) - { //trigger display refresh - statusUpdater->requestUiRefresh(); - - try - { - synchronizeFolder(*i, currentPair, alternateDeletionDir); - - removeRowsAtExit.markRow(i - fileCmp.begin()); - break; - } - catch (FileError& error) - { - //User abort when copying files or moving files/directories into custom deletion directory: - //windows build: abort if requested, don't show error message if cancelled by user! - //linux build: this refresh is not necessary, because user abort triggers an AbortThisProcess() exception without a FileError() - statusUpdater->requestUiRefresh(true); - - - ErrorHandler::Response rv = statusUpdater->reportError(error.show()); - - if (rv == ErrorHandler::IGNORE_ERROR) - break; - else if (rv == ErrorHandler::RETRY) - ; //continue with loop - else - assert (false); - } - } - } - } - - //PERF_START; - - //synchronize files: - for (int k = 0; k < 2; ++k) //loop over all files twice; reason: first delete, then copy (no sorting necessary: performance) - { - const bool deleteLoop = k == 0; //-> first loop: delete files, second loop: copy files - - for (FileComparison::const_iterator i = fileCmp.begin(); i != fileCmp.end(); ++i) - { - //skip processing of unresolved errors (do not remove row from GUI grid) - if (getSyncOperation(*i) == SO_UNRESOLVED_CONFLICT) - continue; - - if ( i->fileDescrLeft.objType == FileDescrLine::TYPE_FILE || - i->fileDescrRight.objType == FileDescrLine::TYPE_FILE) - { - if ( (deleteLoop && deletionImminent(*i)) || - (!deleteLoop && !deletionImminent(*i))) - { - while (true) - { //trigger display refresh - statusUpdater->requestUiRefresh(); - - try - { - synchronizeFile(*i, currentPair, alternateDeletionDir); - - removeRowsAtExit.markRow(i - fileCmp.begin()); - break; - } - catch (FileError& error) - { - //User abort when copying files or moving files/directories into custom deletion directory: - //windows build: abort if requested, don't show error message if cancelled by user! - //linux build: this refresh is not necessary, because user abort triggers an AbortThisProcess() exception without a FileError() - statusUpdater->requestUiRefresh(true); - - - ErrorHandler::Response rv = statusUpdater->reportError(error.show()); - - if ( rv == ErrorHandler::IGNORE_ERROR) - break; - else if (rv == ErrorHandler::RETRY) - ; //continue with loop - else - assert (false); - } - } - } - } - } - } + //loop through all files twice; reason: first delete, then copy + SyncRecursively<true>( this, currentDelHandling).execute(*j); + SyncRecursively<false>(this, currentDelHandling).execute(*j); } } - catch (const RuntimeException& theException) + catch (const std::exception& e) { - statusUpdater->reportFatalError(theException.show().c_str()); + statusUpdater->reportFatalError(wxString::From8BitData(e.what())); return; //should be obsolete! } } @@ -861,7 +1006,7 @@ private: //copy file while executing statusUpdater->requestUiRefresh() calls -void SyncProcess::copyFileUpdating(const Zstring& source, const Zstring& target, const wxULongLong& totalBytesToCpy) +void SyncProcess::copyFileUpdating(const Zstring& source, const Zstring& target, const wxULongLong& totalBytesToCpy) const { //create folders first (see http://sourceforge.net/tracker/index.php?func=detail&aid=2628943&group_id=234430&atid=1093080) const Zstring targetDir = target.BeforeLast(globalFunctions::FILE_NAME_SEPARATOR); @@ -887,6 +1032,9 @@ void SyncProcess::copyFileUpdating(const Zstring& source, const Zstring& target, #elif defined FFS_LINUX FreeFileSync::copyFile(source, target, m_copyFileSymLinks, &callback); #endif + + if (m_verifyCopiedFiles) //verify if data was copied correctly + verifyFileCopy(source, target); } catch (...) { @@ -899,67 +1047,110 @@ void SyncProcess::copyFileUpdating(const Zstring& source, const Zstring& target, statusUpdater->updateProcessedData(0, globalFunctions::convertToSigned(totalBytesToCpy) - totalBytesTransferred); } -/* -//##### OLD IMPLEMENTATION WITHOUT SMOOTH PROGRESS INDICATOR ################ -#ifdef FFS_LINUX -//handle execution of a method while updating the UI -class UpdateWhileCopying : public UpdateWhileExecuting +//--------------------- data verification ------------------------- +struct MemoryAllocator +{ + MemoryAllocator() + { + buffer = new unsigned char[bufferSize]; + } + + ~MemoryAllocator() + { + delete [] buffer; + } + + static const unsigned int bufferSize = 512 * 1024; //512 kb seems to be the perfect buffer size + unsigned char* buffer; +}; + + +//callback functionality for status updates while verifying +class VerifyCallback { public: - UpdateWhileCopying() {} - ~UpdateWhileCopying() {} + virtual ~VerifyCallback() {} + virtual void updateStatus() = 0; +}; - Zstring source; - Zstring target; - bool success; - Zstring errorMessage; -private: - virtual void longRunner() +void verifyFiles(const Zstring& source, const Zstring& target, VerifyCallback* callback) // throw (FileError) +{ + static MemoryAllocator memory1; + static MemoryAllocator memory2; + + wxFile file1(source.c_str(), wxFile::read); //don't use buffered file input for verification! + if (!file1.IsOpened()) + throw FileError(wxString(_("Error opening file:")) + wxT(" \"") + source.c_str() + wxT("\"")); + + wxFile file2(target.c_str(), wxFile::read); + if (!file2.IsOpened()) //NO cleanup necessary for (wxFile) file1 + throw FileError(wxString(_("Error opening file:")) + wxT(" \"") + target.c_str() + wxT("\"")); + + do { - try - { - FreeFileSync::copyFile(source, target); - } - catch (const FileError& e) + const size_t length1 = file1.Read(memory1.buffer, MemoryAllocator::bufferSize); + if (file1.Error()) throw FileError(wxString(_("Error reading file:")) + wxT(" \"") + source.c_str() + wxT("\"")); + + const size_t length2 = file2.Read(memory2.buffer, MemoryAllocator::bufferSize); + if (file2.Error()) throw FileError(wxString(_("Error reading file:")) + wxT(" \"") + target.c_str() + wxT("\"")); + + if (length1 != length2 || ::memcmp(memory1.buffer, memory2.buffer, length1) != 0) { - success = false; - errorMessage = e.show(); - return; + const wxString errorMsg = wxString(_("Data verification error: Source and target file have different content!")) + wxT("\n"); + throw FileError(errorMsg + wxT("\"") + source + wxT("\" -> \n\"") + target + wxT("\"")); } - success = true; + //send progress updates + callback->updateStatus(); } -}; + while (!file1.Eof()); -void copyFileUpdating(const Zstring& source, const Zstring& target, const wxULongLong& totalBytesToCpy, StatusHandler* updateClass) -{ - //create folders first (see http://sourceforge.net/tracker/index.php?func=detail&aid=2628943&group_id=234430&atid=1093080) - const Zstring targetDir = target.BeforeLast(globalFunctions::FILE_NAME_SEPARATOR); - if (!wxDirExists(targetDir.c_str())) + if (!file2.Eof()) { - const Zstring templateDir = source.BeforeLast(globalFunctions::FILE_NAME_SEPARATOR); - FreeFileSync::createDirectory(targetDir, templateDir); //might throw FileError() exception + const wxString errorMsg = wxString(_("Data verification error: Source and target file have different content!")) + wxT("\n"); + throw FileError(errorMsg + wxT("\"") + source + wxT("\" -> \n\"") + target + wxT("\"")); } +} -//update while copying via additional worker thread - static UpdateWhileCopying copyAndUpdate; //single instantiation: thread enters wait phase after each execution - copyAndUpdate.waitUntilReady(); +class VerifyStatusUpdater : public VerifyCallback +{ +public: + VerifyStatusUpdater(StatusHandler* statusHandler) : m_statusHandler(statusHandler) {} + + virtual void updateStatus() + { + m_statusHandler->requestUiRefresh(); //trigger display refresh + } - //longRunner is called from thread, but no mutex needed here, since thread is in waiting state! - copyAndUpdate.source = source; - copyAndUpdate.target = target; +private: + StatusHandler* m_statusHandler; +}; - copyAndUpdate.execute(updateClass); - //no mutex needed here since longRunner is finished - if (!copyAndUpdate.success) - throw FileError(copyAndUpdate.errorMessage); +void SyncProcess::verifyFileCopy(const Zstring& source, const Zstring& target) const +{ + Zstring statusText = txtVerifying; + statusText.Replace(wxT("%x"), target, false); + statusUpdater->updateStatusText(statusText); + statusUpdater->requestUiRefresh(); //trigger display refresh - //inform about the processed amount of data - updateClass->updateProcessedData(0, totalBytesToCpy.ToDouble()); -#endif + VerifyStatusUpdater callback(statusUpdater); + try + { + verifyFiles(source, target, &callback); + } + catch (FileError& error) + { + switch (statusUpdater->reportError(error.show())) + { + case ErrorHandler::IGNORE_ERROR: + break; + case ErrorHandler::RETRY: + verifyFileCopy(source, target); + break; + } + } } -*/ diff --git a/synchronization.h b/synchronization.h index de8884c0..0e54b123 100644 --- a/synchronization.h +++ b/synchronization.h @@ -1,7 +1,7 @@ #ifndef SYNCHRONIZATION_H_INCLUDED #define SYNCHRONIZATION_H_INCLUDED -#include "structures.h" +#include "fileHierarchy.h" #include "library/processXml.h" #include <memory> @@ -10,7 +10,6 @@ #endif class StatusHandler; -class wxBitmap; namespace FreeFileSync @@ -18,7 +17,7 @@ namespace FreeFileSync class SyncStatistics { public: - SyncStatistics(const FileComparison& fileCmp); + SyncStatistics(const BaseDirMapping& baseDir); SyncStatistics(const FolderComparison& folderCmp); int getCreate( bool inclLeft = true, bool inclRight = true) const; @@ -26,76 +25,102 @@ namespace FreeFileSync int getDelete( bool inclLeft = true, bool inclRight = true) const; int getConflict() const; wxULongLong getDataToProcess() const; + int getRowCount() const; private: void init(); - void getNumbers(const FileCompareLine& fileCmpLine); + + void getNumbersRecursively(const HierarchyObject& hierObj); + + void getFileNumbers(const FileMapping& fileObj); + void getDirNumbers(const DirMapping& dirObj); int createLeft, createRight; int overwriteLeft, overwriteRight; int deleteLeft, deleteRight; int conflict; wxULongLong dataToProcess; + int rowsTotal; }; bool synchronizationNeeded(const FolderComparison& folderCmp); - - enum SyncOperation - { - SO_CREATE_NEW_LEFT, - SO_CREATE_NEW_RIGHT, - SO_DELETE_LEFT, - SO_DELETE_RIGHT, - SO_OVERWRITE_LEFT, - SO_OVERWRITE_RIGHT, - SO_DO_NOTHING, - SO_UNRESOLVED_CONFLICT - }; SyncOperation getSyncOperation(const CompareFilesResult cmpResult, const bool selectedForSynchronization, const SyncDirection syncDir); //evaluate comparison result and sync direction - SyncOperation getSyncOperation(const FileCompareLine& line); //convenience function + + SyncOperation getSyncOperation(const FileSystemObject& fsObj); //convenience function + + + struct FolderPairSyncCfg + { + FolderPairSyncCfg(const DeletionPolicy handleDel, + const wxString& custDelDir) : + handleDeletion(handleDel), + custDelFolder(custDelDir) {} + + DeletionPolicy handleDeletion; + wxString custDelFolder; + }; + std::vector<FolderPairSyncCfg> extractSyncCfg(const MainConfiguration& mainCfg); //class handling synchronization process class SyncProcess { public: - SyncProcess(const DeletionPolicy handleDeletion, - const wxString& custDelFolder, - const bool copyFileSymLinks, + SyncProcess(const bool copyFileSymLinks, const bool traverseDirSymLinks, - xmlAccess::WarningMessages& warnings, + xmlAccess::OptionalDialogs& warnings, + const bool verifyCopiedFiles, StatusHandler* handler); - void startSynchronizationProcess(FolderComparison& folderCmp); + //CONTRACT: syncConfig must have SAME SIZE folderCmp and correspond per row! + void startSynchronizationProcess(const std::vector<FolderPairSyncCfg>& syncConfig, FolderComparison& folderCmp); private: - void synchronizeFile(const FileCompareLine& cmpLine, const FolderPair& folderPair, const Zstring& altDeletionDir); //false if nothing was done - void synchronizeFolder(const FileCompareLine& cmpLine, const FolderPair& folderPair, const Zstring& altDeletionDir); //false if nothing was done + template <bool deleteOnly> + class SyncRecursively; + + struct DeletionHandling + { + DeletionHandling(const DeletionPolicy handleDel, + const wxString& custDelFolder); + + DeletionPolicy handleDeletion; + Zstring currentDelFolder; //alternate deletion folder for current folder pair (with timestamp, ends with path separator) + //preloaded status texts: + const Zstring txtMoveFileUserDefined; + const Zstring txtMoveFolderUserDefined; + }; + + void syncRecursively(HierarchyObject& hierObj); + void synchronizeFile(FileMapping& fileObj, const DeletionHandling& delHandling) const; + void synchronizeFolder(DirMapping& dirObj, const DeletionHandling& delHandling) const; - void removeFile(const FileDescrLine& fildesc, const Zstring& altDeletionDir); - void removeFolder(const FileDescrLine& fildesc, const Zstring& altDeletionDir); + template <FreeFileSync::SelectedSide side> + void removeFile(const FileMapping& fileObj, const DeletionHandling& delHandling, bool showStatusUpdate) const; - void copyFileUpdating(const Zstring& source, const Zstring& target, const wxULongLong& sourceFileSize); + template <FreeFileSync::SelectedSide side> + void removeFolder(const DirMapping& dirObj, const DeletionHandling& delHandling) const; - const DeletionPolicy m_handleDeletion; - const Zstring sessionDeletionDirectory; //ends with path separator + void copyFileUpdating(const Zstring& source, const Zstring& target, const wxULongLong& sourceFileSize) const; + void verifyFileCopy(const Zstring& source, const Zstring& target) const; const bool m_copyFileSymLinks; const bool m_traverseDirSymLinks; + const bool m_verifyCopiedFiles; //warnings - xmlAccess::WarningMessages& m_warnings; + xmlAccess::OptionalDialogs& m_warnings; #ifdef FFS_WIN //shadow copy buffer std::auto_ptr<ShadowCopy> shadowCopyHandler; #endif - StatusHandler* statusUpdater; + StatusHandler* const statusUpdater; //preload status texts const Zstring txtCopyingFile; @@ -103,6 +128,8 @@ namespace FreeFileSync const Zstring txtCreatingFolder; const Zstring txtDeletingFile; const Zstring txtDeletingFolder; + const Zstring txtMoveToRecycler; + const Zstring txtVerifying; }; } diff --git a/ui/MainDialog.cpp b/ui/MainDialog.cpp index 5f078cda..d053bed5 100644 --- a/ui/MainDialog.cpp +++ b/ui/MainDialog.cpp @@ -1,6 +1,7 @@ #include "mainDialog.h" #include <wx/filename.h> -#include "../shared/globalFunctions.h" +#include <stdexcept> +#include "../shared/systemConstants.h" #include <wx/clipbrd.h> #include <wx/dataobj.h> #include <wx/ffile.h> @@ -25,23 +26,61 @@ #include "../shared/fileHandling.h" #include "../shared/xmlBase.h" #include "../shared/standardPaths.h" +#include "../shared/toggleButton.h" +#include "folderPair.h" +#include "../shared/globalFunctions.h" using namespace FreeFileSync; using FreeFileSync::CustomLocale; -class FolderPairPanel : public FolderPairGenerated +typedef FreeFileSync::FolderPairPanelBasic<FolderPairGenerated> FolderPairParent; + +class FolderPairPanel : public FolderPairParent { public: - FolderPairPanel(wxWindow* parent) : - FolderPairGenerated(parent), - dragDropOnLeft(new DragDropOnDlg(m_panelLeft, m_dirPickerLeft, m_directoryLeft)), - dragDropOnRight(new DragDropOnDlg(m_panelRight, m_dirPickerRight, m_directoryRight)) {} + FolderPairPanel(wxWindow* parent, MainDialog* mainDialog) : + FolderPairParent(parent), + mainDlg(mainDialog) + {} private: - //support for drag and drop - std::auto_ptr<DragDropOnDlg> dragDropOnLeft; - std::auto_ptr<DragDropOnDlg> dragDropOnRight; + virtual void OnAltFilterCfgRemoveConfirm(wxCommandEvent& event) + { + FolderPairParent::OnAltFilterCfgRemoveConfirm(event); + mainDlg->updateFilterConfig(false); //update filter, leave activation status as it is + } + + virtual void OnAltSyncCfgRemoveConfirm(wxCommandEvent& event) + { + FolderPairParent::OnAltSyncCfgRemoveConfirm(event); + mainDlg->updateSyncConfig(); + } + + virtual wxWindow* getParentWindow() + { + return mainDlg; + } + + virtual MainConfiguration getMainConfig() const + { + return mainDlg->getCurrentConfiguration().mainCfg; + } + + virtual void OnAltSyncCfgChange() + { + mainDlg->updateSyncConfig(); + } + + virtual void OnAltFilterCfgChange(bool defaultValueSet) + { + if (defaultValueSet) //default + mainDlg->updateFilterConfig(false); //re-apply filter (without changing active-status) + else + mainDlg->updateFilterConfig(true); //activate(and apply) filter + } + + MainDialog* mainDlg; }; @@ -80,7 +119,7 @@ public: mainDlg_->syncPreview.enableSynchronization(false); //clear grids - mainDlg_->currentGridData.clear(); + mainDlg_->gridDataView->clearAllRows(); mainDlg_->updateGuiGrid(); return true; @@ -130,12 +169,12 @@ private: MenuItemMap menuItems; }; -//################################################################################################################################## - +//################################################################################################################################## MainDialog::MainDialog(wxFrame* frame, const wxString& cfgFileName, xmlAccess::XmlGlobalSettings& settings) : MainDialogGenerated(frame), globalSettings(settings), + gridDataView(new FreeFileSync::GridView()), contextMenu(new wxMenu), //initialize right-click context menu; will be dynamically re-created on each R-mouse-click cleanedUp(false), lastSortColumn(-1), @@ -147,9 +186,7 @@ MainDialog::MainDialog(wxFrame* frame, const wxString& cfgFileName, xmlAccess::X { wxWindowUpdateLocker dummy(this); //avoid display distortion - gridDataView.reset(new FreeFileSync::GridView(currentGridData)); - - m_bpButtonRemoveTopPair->Hide(); + initViewFilterButtons(); //initialize and load configuration readGlobalSettings(); @@ -168,7 +205,6 @@ MainDialog::MainDialog(wxFrame* frame, const wxString& cfgFileName, xmlAccess::X m_bpButtonSave->SetBitmapLabel(*GlobalResources::getInstance().bitmapSave); m_bpButtonLoad->SetBitmapLabel(*GlobalResources::getInstance().bitmapLoad); m_bpButtonAddPair->SetBitmapLabel(*GlobalResources::getInstance().bitmapAddFolderPair); - m_bpButtonRemoveTopPair->SetBitmapLabel(*GlobalResources::getInstance().bitmapRemoveFolderPair); m_bitmap15->SetBitmap(*GlobalResources::getInstance().bitmapStatusEdge); m_bitmapCreate->SetBitmap(*GlobalResources::getInstance().bitmapCreate); @@ -180,8 +216,11 @@ MainDialog::MainDialog(wxFrame* frame, const wxString& cfgFileName, xmlAccess::X //menu icons: workaround for wxWidgets: small hack to update menu items: actually this is a wxWidgets bug (affects Windows- and Linux-build) MenuItemUpdater updateMenuFile(m_menuFile); - updateMenuFile.addForUpdate(m_menuItem10, *GlobalResources::getInstance().bitmapCompareSmall); - updateMenuFile.addForUpdate(m_menuItem11, *GlobalResources::getInstance().bitmapSyncSmall); + updateMenuFile.addForUpdate(m_menuItem10, *GlobalResources::getInstance().bitmapCompareSmall); + updateMenuFile.addForUpdate(m_menuItem11, *GlobalResources::getInstance().bitmapSyncSmall); + updateMenuFile.addForUpdate(m_menuItemNew, *GlobalResources::getInstance().bitmapNewSmall); + updateMenuFile.addForUpdate(m_menuItemSave, *GlobalResources::getInstance().bitmapSaveSmall); + updateMenuFile.addForUpdate(m_menuItemLoad, *GlobalResources::getInstance().bitmapLoadSmall); MenuItemUpdater updateMenuAdv(m_menuAdvanced); updateMenuAdv.addForUpdate(m_menuItemGlobSett, *GlobalResources::getInstance().bitmapSettingsSmall); @@ -195,7 +234,6 @@ MainDialog::MainDialog(wxFrame* frame, const wxString& cfgFileName, xmlAccess::X for (std::vector<LocInfoLine>::const_iterator i = LocalizationInfo::getMapping().begin(); i != LocalizationInfo::getMapping().end(); ++i) { wxMenuItem* newItem = new wxMenuItem(m_menuLanguages, wxID_ANY, i->languageName, wxEmptyString, wxITEM_NORMAL ); - newItem->SetBitmap(GlobalResources::getInstance().getImageByName(i->languageFlag)); //map menu item IDs with language IDs: evaluated when processing event handler @@ -203,7 +241,6 @@ MainDialog::MainDialog(wxFrame* frame, const wxString& cfgFileName, xmlAccess::X //connect event Connect(newItem->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnMenuLanguageSwitch)); - m_menuLanguages->Append(newItem); } @@ -372,14 +409,18 @@ void MainDialog::writeGlobalSettings() } -void MainDialog::setSyncDirManually(const std::set<int>& rowsToSetOnUiTable, const FreeFileSync::SyncDirection dir) +void MainDialog::setSyncDirManually(const std::set<unsigned int>& rowsToSetOnUiTable, const FreeFileSync::SyncDirection dir) { if (rowsToSetOnUiTable.size() > 0) { - for (std::set<int>::const_iterator i = rowsToSetOnUiTable.begin(); i != rowsToSetOnUiTable.end(); ++i) + for (std::set<unsigned int>::const_iterator i = rowsToSetOnUiTable.begin(); i != rowsToSetOnUiTable.end(); ++i) { - (*gridDataView)[*i].syncDir = dir; - (*gridDataView)[*i].selectedForSynchronization = true; + FileSystemObject* fsObj = gridDataView->getObject(*i); + if (fsObj) + { + setSyncDirection(dir, *fsObj); //set new direction (recursively) + FilterProcess::setActiveStatus(true, *fsObj); //works recursively for directories + } } updateGuiGrid(); @@ -387,41 +428,26 @@ void MainDialog::setSyncDirManually(const std::set<int>& rowsToSetOnUiTable, con } -void MainDialog::filterRangeManually(const std::set<int>& rowsToFilterOnUiTable, const int leadingRow) +void MainDialog::filterRangeManually(const std::set<unsigned int>& rowsToFilterOnUiTable, const int leadingRow) { if (rowsToFilterOnUiTable.size() > 0) { bool newSelection = false; //default: deselect range //leadingRow determines de-/selection of all other rows - if (0 <= leadingRow && leadingRow < int(gridDataView->elementsOnView())) - newSelection = !(*gridDataView)[leadingRow].selectedForSynchronization; + const FileSystemObject* fsObj = gridDataView->getObject(leadingRow); + if (fsObj) + newSelection = !fsObj->selectedForSynchronization; //if hidefiltered is active, there should be no filtered elements on screen => current element was filtered out assert(!currentCfg.hideFilteredElements || !newSelection); - //get all lines that need to be filtered (e.g. if a folder is marked, then its subelements should be marked as well) - FolderCompRef compRef; - gridDataView->viewRefToFolderRef(rowsToFilterOnUiTable, compRef); - - assert(compRef.size() == currentGridData.size()); //GridView::viewRefToFolderRef() should ensure this! + //get all lines that need to be filtered + std::vector<FileSystemObject*> compRef; + gridDataView->getAllFileRef(rowsToFilterOnUiTable, compRef); //everything in compRef is bound - for (FolderComparison::iterator i = currentGridData.begin(); i != currentGridData.end(); ++i) - { - FileComparison& fileCmp = i->fileCmp; - const std::set<int>& markedRows = compRef[i - currentGridData.begin()]; - - std::set<int> markedRowsTotal; //retrieve additional rows that need to be filtered, too - for (std::set<int>::const_iterator j = markedRows.begin(); j != markedRows.end(); ++j) - { - markedRowsTotal.insert(*j); - FreeFileSync::addSubElements(fileCmp, fileCmp[*j], markedRowsTotal); - } - - //toggle selection of filtered rows - for (std::set<int>::iterator k = markedRowsTotal.begin(); k != markedRowsTotal.end(); ++k) - fileCmp[*k].selectedForSynchronization = newSelection; - } + for (std::vector<FileSystemObject*>::iterator i = compRef.begin(); i != compRef.end(); ++i) + FilterProcess::setActiveStatus(newSelection, **i); //works recursively for directories refreshGridAfterFilterChange(400); //call this instead of updateGuiGrid() to add some delay if hideFiltered == true and to handle some graphical artifacts } @@ -450,12 +476,12 @@ void MainDialog::OnIdleEvent(wxEvent& event) void MainDialog::copySelectionToClipboard(const CustomGrid* selectedGrid) { - const std::set<int> selectedRows = getSelectedRows(selectedGrid); + const std::set<unsigned int> selectedRows = getSelectedRows(selectedGrid); if (selectedRows.size() > 0) { wxString clipboardString; - for (std::set<int>::const_iterator i = selectedRows.begin(); i != selectedRows.end(); ++i) + for (std::set<unsigned int>::const_iterator i = selectedRows.begin(); i != selectedRows.end(); ++i) { for (int k = 0; k < const_cast<CustomGrid*>(selectedGrid)->GetNumberCols(); ++k) { @@ -479,38 +505,23 @@ void MainDialog::copySelectionToClipboard(const CustomGrid* selectedGrid) } -void removeInvalidRows(std::set<int>& rows, const int currentUiTableSize) +std::set<unsigned int> MainDialog::getSelectedRows(const CustomGrid* grid) const { - std::set<int> validRows; //temporal table IS needed here - for (std::set<int>::iterator i = rows.begin(); i != rows.end(); ++i) - if (0 <= *i) - { - if (*i >= currentUiTableSize) //set is sorted, so no need to continue here - break; - validRows.insert(*i); - } - rows.swap(validRows); -} + std::set<unsigned int> output = grid->getAllSelectedRows(); - -std::set<int> MainDialog::getSelectedRows(const CustomGrid* grid) const -{ - std::set<int> output = grid->getAllSelectedRows(); - - removeInvalidRows(output, gridDataView->elementsOnView()); + //remove invalid rows + output.erase(output.lower_bound(gridDataView->rowsOnView()), output.end()); return output; } -std::set<int> MainDialog::getSelectedRows() const +std::set<unsigned int> MainDialog::getSelectedRows() const { //merge selections from left and right grid - std::set<int> selection = getSelectedRows(m_gridLeft); - std::set<int> additional = getSelectedRows(m_gridRight); - for (std::set<int>::const_iterator i = additional.begin(); i != additional.end(); ++i) - selection.insert(*i); - + std::set<unsigned int> selection = getSelectedRows(m_gridLeft); + std::set<unsigned int> additional = getSelectedRows(m_gridRight); + selection.insert(additional.begin(), additional.end()); return selection; } @@ -607,22 +618,22 @@ private: void MainDialog::deleteSelectedFiles() { //get set of selected rows on view - const std::set<int> viewSelectionLeft = getSelectedRows(m_gridLeft); - const std::set<int> viewSelectionRight = getSelectedRows(m_gridRight); + const std::set<unsigned int> viewSelectionLeft = getSelectedRows(m_gridLeft); + const std::set<unsigned int> viewSelectionRight = getSelectedRows(m_gridRight); if (viewSelectionLeft.size() + viewSelectionRight.size()) { - //map lines from GUI view to grid line references for "currentGridData" - FolderCompRef compRefLeft; - gridDataView->viewRefToFolderRef(viewSelectionLeft, compRefLeft); + //map lines from GUI view to grid line references + std::vector<FileSystemObject*> compRefLeft; + gridDataView->getAllFileRef(viewSelectionLeft, compRefLeft); + + std::vector<FileSystemObject*> compRefRight; + gridDataView->getAllFileRef(viewSelectionRight, compRefRight); - FolderCompRef compRefRight; - gridDataView->viewRefToFolderRef(viewSelectionRight, compRefRight); int totalDeleteCount = 0; DeleteDialog* confirmDeletion = new DeleteDialog(this, //no destruction needed; attached to main window - currentGridData, compRefLeft, compRefRight, globalSettings.gui.deleteOnBothSides, @@ -636,98 +647,95 @@ void MainDialog::deleteSelectedFiles() return; } - //Attention! Modifying the grid is highly critical! There MUST NOT BE any accesses to gridDataView until this reference table is updated - //by updateGuiGrid()!! This is easily missed, e.g. when ClearSelection() or ShowModal() or possibly any other wxWidgets function is called - //that might want to redraw the UI (which implicitly uses the information in gridDataView (see CustomGrid) - gridDataView->clearView(); //no superfluous precaution: e.g. consider grid update when error message is shown in multiple folder pair scenario! - try { //handle errors when deleting files/folders ManualDeletionHandler statusHandler(this, totalDeleteCount); - assert(compRefLeft.size() == currentGridData.size()); //GridView::viewRefToFolderRef() should ensure this! - assert(compRefRight.size() == currentGridData.size()); - - for (FolderComparison::iterator i = currentGridData.begin(); i != currentGridData.end(); ++i) - { - const int folderPairNr = i - currentGridData.begin(); - - const std::set<int>& rowsToDeleteOnLeft = compRefLeft[folderPairNr]; - const std::set<int>& rowsToDeleteOnRight = compRefRight[folderPairNr]; - - FreeFileSync::deleteFromGridAndHD(i->fileCmp, - rowsToDeleteOnLeft, - rowsToDeleteOnRight, - globalSettings.gui.deleteOnBothSides, - globalSettings.gui.useRecyclerForManualDeletion, - currentCfg.mainCfg.syncConfiguration, - &statusHandler); - } - + FreeFileSync::deleteFromGridAndHD(gridDataView->getDataTentative(), + compRefLeft, + compRefRight, + globalSettings.gui.deleteOnBothSides, + globalSettings.gui.useRecyclerForManualDeletion, + getCurrentConfiguration().mainCfg, + &statusHandler); } catch (FreeFileSync::AbortThisProcess&) {} + //remove rows that empty: just a beautification, invalid rows shouldn't cause issues + gridDataView->removeInvalidRows(); + //redraw grid neccessary to update new dimensions and for UI-Backend data linkage updateGuiGrid(); //call immediately after deleteFromGridAndHD!!! - m_gridLeft->ClearSelection(); + m_gridLeft-> ClearSelection(); m_gridMiddle->ClearSelection(); - m_gridRight->ClearSelection(); + m_gridRight-> ClearSelection(); } } } -void exstractNames(const FileDescrLine& fileDescr, wxString& name, wxString& dir) +template <SelectedSide side> +void exstractNames(const FileSystemObject& fsObj, wxString& name, wxString& dir) { - switch (fileDescr.objType) + if (!fsObj.isEmpty<side>()) + { + const FileMapping* fileObj = dynamic_cast<const FileMapping*>(&fsObj); + if (fileObj != NULL) + { + name = fsObj.getFullName<side>().c_str(); + dir = name.BeforeLast(globalFunctions::FILE_NAME_SEPARATOR); + } + else + { + const DirMapping* dirObj = dynamic_cast<const DirMapping*>(&fsObj); + if (dirObj != NULL) + { + name = fsObj.getFullName<side>().c_str(); + dir = name; + } + } + } + else { - case FileDescrLine::TYPE_FILE: - name = fileDescr.fullName.c_str(); - dir = wxString(fileDescr.fullName.c_str()).BeforeLast(globalFunctions::FILE_NAME_SEPARATOR); - break; - - case FileDescrLine::TYPE_DIRECTORY: - name = fileDescr.fullName.c_str(); - dir = fileDescr.fullName.c_str(); - break; - - case FileDescrLine::TYPE_NOTHING: name.clear(); dir.clear(); - break; } } -void MainDialog::openWithFileManager(const int rowNumber, const bool leftSide) +void MainDialog::openExternalApplication(unsigned int rowNumber, bool leftSide, const wxString& commandline) { - wxString command = globalSettings.gui.commandLineFileManager; + if (commandline.empty()) + return; + + wxString command = commandline; wxString name; wxString dir; wxString nameCo; wxString dirCo; - if (0 <= rowNumber && rowNumber < int(gridDataView->elementsOnView())) + const FileSystemObject* fsObj = gridDataView->getObject(rowNumber); + if (fsObj) { if (leftSide) { - exstractNames((*gridDataView)[rowNumber].fileDescrLeft, name, dir); - exstractNames((*gridDataView)[rowNumber].fileDescrRight, nameCo, dirCo); + exstractNames<LEFT_SIDE>( *fsObj, name, dir); + exstractNames<RIGHT_SIDE>(*fsObj, nameCo, dirCo); } else { - exstractNames((*gridDataView)[rowNumber].fileDescrRight, name, dir); - exstractNames((*gridDataView)[rowNumber].fileDescrLeft, nameCo, dirCo); + exstractNames<RIGHT_SIDE>(*fsObj, name, dir); + exstractNames<LEFT_SIDE>( *fsObj, nameCo, dirCo); } #ifdef FFS_WIN if (name.empty()) { if (leftSide) - wxExecute(wxString(wxT("explorer ")) + gridDataView->getFolderPair(rowNumber).leftDirectory); + wxExecute(wxString(wxT("explorer ")) + fsObj->getBaseDirPf<LEFT_SIDE>()); else - wxExecute(wxString(wxT("explorer ")) + gridDataView->getFolderPair(rowNumber).rightDirectory); + wxExecute(wxString(wxT("explorer ")) + fsObj->getBaseDirPf<RIGHT_SIDE>()); return; } #endif @@ -746,10 +754,10 @@ void MainDialog::openWithFileManager(const int rowNumber, const bool leftSide) #endif } + command.Replace(wxT("%nameCo"), nameCo, true); //attention: replace %nameCo, %dirCo BEFORE %name, %dir to handle dependency + command.Replace(wxT("%dirCo"), dirCo, true); command.Replace(wxT("%name"), name, true); command.Replace(wxT("%dir"), dir, true); - command.Replace(wxT("%nameCo"), nameCo, true); - command.Replace(wxT("%dirCo"), dirCo, true); wxExecute(command); } @@ -902,10 +910,31 @@ void MainDialog::onGridLeftButtonEvent(wxKeyEvent& event) else if (keyCode == WXK_NUMPAD_ADD) //CTRL + '+' m_gridLeft->AutoSizeColumns(false); } - else if (keyCode == WXK_DELETE || keyCode == WXK_NUMPAD_DELETE) - deleteSelectedFiles(); + else + switch (keyCode) + { + case WXK_DELETE: + case WXK_NUMPAD_DELETE: + deleteSelectedFiles(); + break; - event.Skip(); + case WXK_SPACE: + { + wxCommandEvent dummy; + OnContextFilterTemp(dummy); + } + break; + + case WXK_RETURN: + case WXK_NUMPAD_ENTER: + { + wxCommandEvent dummy(wxEVT_NULL, externalAppIDFirst); //open with first external application + OnContextOpenWith(dummy); + } + break; + } + + //event.Skip(); -> swallow event! don't allow default grid commands! } @@ -919,7 +948,7 @@ void MainDialog::onGridMiddleButtonEvent(wxKeyEvent& event) copySelectionToClipboard(m_gridMiddle); } - event.Skip(); + //event.Skip(); -> swallow event! don't allow default grid commands! } @@ -936,13 +965,64 @@ void MainDialog::onGridRightButtonEvent(wxKeyEvent& event) else if (keyCode == WXK_NUMPAD_ADD) //CTRL + '+' m_gridRight->AutoSizeColumns(false); } - else if (keyCode == WXK_DELETE || keyCode == WXK_NUMPAD_DELETE) - deleteSelectedFiles(); + else + switch (keyCode) + { + case WXK_DELETE: + case WXK_NUMPAD_DELETE: + deleteSelectedFiles(); + break; - event.Skip(); + case WXK_SPACE: + { + wxCommandEvent dummy; + OnContextFilterTemp(dummy); + } + break; + + case WXK_RETURN: + case WXK_NUMPAD_ENTER: + { + wxCommandEvent dummy(wxEVT_NULL, externalAppIDFirst); //open with first external application + OnContextOpenWith(dummy); + } + break; + } + //event.Skip(); -> swallow event! don't allow default grid commands! } + +//------------------------------------------------------------ +//temporal variables used by exclude via context menu +struct SelectedExtension : public wxObject +{ + SelectedExtension(const wxString& ext) : extension(ext) {} + + wxString extension; +}; + +struct FilterObject +{ + FilterObject(const wxString& relName, bool isDirectory) : + relativeName(relName), + isDir(isDirectory) {} + wxString relativeName; + bool isDir; +}; + +typedef std::vector<FilterObject> FilterObjList; + +struct FilterObjContainer : public wxObject +{ + FilterObjContainer(const FilterObjList& objList) : selectedObjects(objList) {} + + FilterObjList selectedObjects; +}; +//------------------------------------------------------------ + + + void MainDialog::OnContextRim(wxGridEvent& event) { //usability: select row unter right-click if not already selected @@ -962,38 +1042,41 @@ void MainDialog::OnContextRim(wxGridEvent& event) m_gridLeft->ClearSelection(); } } -//------------------------------------------------------------------------------ + //------------------------------------------------------------------------------ - const std::set<int> selectionLeft = getSelectedRows(m_gridLeft); - const std::set<int> selectionRight = getSelectedRows(m_gridRight); + const std::set<unsigned int> selectionLeft = getSelectedRows(m_gridLeft); + const std::set<unsigned int> selectionRight = getSelectedRows(m_gridRight); - const int selectionBegin = selectionLeft.size() + selectionRight.size() == 0 ? 0 : - selectionLeft.size() == 0 ? *selectionRight.begin() : - selectionRight.size() == 0 ? *selectionLeft.begin() : - std::min(*selectionLeft.begin(), *selectionRight.begin()); + const unsigned int selectionBegin = selectionLeft.size() + selectionRight.size() == 0 ? 0 : + selectionLeft.size() == 0 ? *selectionRight.begin() : + selectionRight.size() == 0 ? *selectionLeft.begin() : + std::min(*selectionLeft.begin(), *selectionRight.begin()); -//####################################################### + const FileSystemObject* fsObj = gridDataView->getObject(selectionBegin); + + + //####################################################### //re-create context menu contextMenu.reset(new wxMenu); if (syncPreview.previewIsEnabled()) { - if (selectionLeft.size() + selectionRight.size() > 0) + if (fsObj && (selectionLeft.size() + selectionRight.size() > 0)) { //CONTEXT_SYNC_DIR_LEFT wxMenuItem* menuItemSyncDirLeft = new wxMenuItem(contextMenu.get(), CONTEXT_SYNC_DIR_LEFT, _("Change direction")); - menuItemSyncDirLeft->SetBitmap(getSyncOpImage((*gridDataView)[selectionBegin].cmpResult, true, SYNC_DIR_LEFT)); + menuItemSyncDirLeft->SetBitmap(getSyncOpImage(fsObj->getCategory(), true, SYNC_DIR_LEFT)); contextMenu->Append(menuItemSyncDirLeft); //CONTEXT_SYNC_DIR_NONE wxMenuItem* menuItemSyncDirNone = new wxMenuItem(contextMenu.get(), CONTEXT_SYNC_DIR_NONE, _("Change direction")); - menuItemSyncDirNone->SetBitmap(getSyncOpImage((*gridDataView)[selectionBegin].cmpResult, true, SYNC_DIR_NONE)); + menuItemSyncDirNone->SetBitmap(getSyncOpImage(fsObj->getCategory(), true, SYNC_DIR_NONE)); contextMenu->Append(menuItemSyncDirNone); //CONTEXT_SYNC_DIR_RIGHT wxMenuItem* menuItemSyncDirRight = new wxMenuItem(contextMenu.get(), CONTEXT_SYNC_DIR_RIGHT, _("Change direction")); - menuItemSyncDirRight->SetBitmap(getSyncOpImage((*gridDataView)[selectionBegin].cmpResult, true, SYNC_DIR_RIGHT)); + menuItemSyncDirRight->SetBitmap(getSyncOpImage(fsObj->getCategory(), true, SYNC_DIR_RIGHT)); contextMenu->Append(menuItemSyncDirRight); contextMenu->AppendSeparator(); @@ -1002,62 +1085,67 @@ void MainDialog::OnContextRim(wxGridEvent& event) //CONTEXT_FILTER_TEMP - if (selectionLeft.size() + selectionRight.size() > 0) + if (fsObj && (selectionLeft.size() + selectionRight.size() > 0)) { - if ((*gridDataView)[selectionBegin].selectedForSynchronization) //valid access, as getSelectedRows returns valid references only + if (fsObj->selectedForSynchronization) { - wxMenuItem* menuItemExclTemp = new wxMenuItem(contextMenu.get(), CONTEXT_FILTER_TEMP, _("Exclude temporarily")); + wxMenuItem* menuItemExclTemp = new wxMenuItem(contextMenu.get(), CONTEXT_FILTER_TEMP, wxString(_("Exclude temporarily")) + wxT("\tSPACE")); menuItemExclTemp->SetBitmap(*GlobalResources::getInstance().bitmapCheckBoxFalse); contextMenu->Append(menuItemExclTemp); } else { - wxMenuItem* menuItemInclTemp = new wxMenuItem(contextMenu.get(), CONTEXT_FILTER_TEMP, _("Include temporarily")); + wxMenuItem* menuItemInclTemp = new wxMenuItem(contextMenu.get(), CONTEXT_FILTER_TEMP, wxString(_("Include temporarily")) + wxT("\tSPACE")); menuItemInclTemp->SetBitmap(*GlobalResources::getInstance().bitmapCheckBoxTrue); contextMenu->Append(menuItemInclTemp); } } else { - contextMenu->Append(CONTEXT_FILTER_TEMP, _("Exclude temporarily")); //this element should always be visible + contextMenu->Append(CONTEXT_FILTER_TEMP, wxString(_("Exclude temporarily")) + wxT("\tSPACE")); //this element should always be visible contextMenu->Enable(CONTEXT_FILTER_TEMP, false); } -//############################################################################################### + //############################################################################################### //get list of relative file/dir-names for filtering - exFilterCandidateObj.clear(); - FilterObject newFilterEntry; - for (std::set<int>::const_iterator i = selectionLeft.begin(); i != selectionLeft.end(); ++i) + FilterObjList exFilterCandidateObj; + for (std::set<unsigned int>::const_iterator i = selectionLeft.begin(); i != selectionLeft.end(); ++i) { - const FileCompareLine& line = (*gridDataView)[*i]; - newFilterEntry.relativeName = line.fileDescrLeft.relativeName.c_str(); - newFilterEntry.type = line.fileDescrLeft.objType; - if (!newFilterEntry.relativeName.IsEmpty()) - exFilterCandidateObj.push_back(newFilterEntry); + const FileSystemObject* currObj = gridDataView->getObject(*i); + if (currObj && !currObj->isEmpty<LEFT_SIDE>()) + exFilterCandidateObj.push_back( + FilterObject(currObj->getRelativeName<LEFT_SIDE>().c_str(), + dynamic_cast<const DirMapping*>(currObj) != NULL)); } - for (std::set<int>::const_iterator i = selectionRight.begin(); i != selectionRight.end(); ++i) + for (std::set<unsigned int>::const_iterator i = selectionRight.begin(); i != selectionRight.end(); ++i) { - const FileCompareLine& line = (*gridDataView)[*i]; - newFilterEntry.relativeName = line.fileDescrRight.relativeName.c_str(); - newFilterEntry.type = line.fileDescrRight.objType; - if (!newFilterEntry.relativeName.IsEmpty()) - exFilterCandidateObj.push_back(newFilterEntry); + const FileSystemObject* currObj = gridDataView->getObject(*i); + if (currObj && !currObj->isEmpty<RIGHT_SIDE>()) + exFilterCandidateObj.push_back( + FilterObject(currObj->getRelativeName<RIGHT_SIDE>().c_str(), + dynamic_cast<const DirMapping*>(currObj) != NULL)); } -//############################################################################################### + //############################################################################################### //CONTEXT_EXCLUDE_EXT - exFilterCandidateExtension.clear(); - if (exFilterCandidateObj.size() > 0 && exFilterCandidateObj[0].type == FileDescrLine::TYPE_FILE) + if (exFilterCandidateObj.size() > 0 && !exFilterCandidateObj[0].isDir) { const wxString filename = exFilterCandidateObj[0].relativeName.AfterLast(globalFunctions::FILE_NAME_SEPARATOR); if (filename.Find(wxChar('.')) != wxNOT_FOUND) //be careful: AfterLast will return the whole string if '.' is not found! { - exFilterCandidateExtension = filename.AfterLast(wxChar('.')); + const wxString extension = filename.AfterLast(wxChar('.')); //add context menu item - wxMenuItem* menuItemExclExt = new wxMenuItem(contextMenu.get(), CONTEXT_EXCLUDE_EXT, wxString(_("Exclude via filter:")) + wxT(" ") + wxT("*.") + exFilterCandidateExtension); + wxMenuItem* menuItemExclExt = new wxMenuItem(contextMenu.get(), CONTEXT_EXCLUDE_EXT, wxString(_("Exclude via filter:")) + wxT(" ") + wxT("*.") + extension); menuItemExclExt->SetBitmap(*GlobalResources::getInstance().bitmapFilterSmall); contextMenu->Append(menuItemExclExt); + + //connect event + contextMenu->Connect(CONTEXT_EXCLUDE_EXT, + wxEVT_COMMAND_MENU_SELECTED, + wxCommandEventHandler(MainDialog::OnContextExcludeExtension), + new SelectedExtension(extension), //ownership passed! + this); } } @@ -1073,6 +1161,40 @@ void MainDialog::OnContextRim(wxGridEvent& event) { menuItemExclObj->SetBitmap(*GlobalResources::getInstance().bitmapFilterSmall); contextMenu->Append(menuItemExclObj); + + //connect event + contextMenu->Connect(CONTEXT_EXCLUDE_OBJ, + wxEVT_COMMAND_MENU_SELECTED, + wxCommandEventHandler(MainDialog::OnContextExcludeObject), + new FilterObjContainer(exFilterCandidateObj), //ownership passed! + this); + } + + + + //CONTEXT_EXTERNAL_APP + if (!globalSettings.gui.externelApplications.empty()) + { + contextMenu->AppendSeparator(); + + const bool externalAppEnabled = (m_gridLeft->isLeadGrid() || m_gridRight->isLeadGrid()) && + (selectionLeft.size() + selectionRight.size() == 1); + + int newID = externalAppIDFirst; + for (xmlAccess::ExternalApps::iterator i = globalSettings.gui.externelApplications.begin(); + i != globalSettings.gui.externelApplications.end(); + ++i, ++newID) + { + if (i == globalSettings.gui.externelApplications.begin()) + contextMenu->Append(newID, i->first + wxT("\t") + wxString(_("D-Click")) + wxT("; ENTER")); + else + contextMenu->Append(newID, i->first.empty() ? wxT(" ") : i->first); //wxWidgets doesn't like empty items + + contextMenu->Enable(newID, externalAppEnabled); + + //register event + contextMenu->Connect(newID, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextOpenWith), NULL, this); + } } @@ -1089,18 +1211,6 @@ void MainDialog::OnContextRim(wxGridEvent& event) contextMenu->Enable(CONTEXT_CLIPBOARD, false); - //CONTEXT_EXPLORER - contextMenu->Append(CONTEXT_EXPLORER, _("Open with File Manager\tD-Click")); - - if ( (m_gridLeft->isLeadGrid() && selectionLeft.size() <= 1) || - (m_gridRight->isLeadGrid() && selectionRight.size() <= 1)) - contextMenu->Enable(CONTEXT_EXPLORER, true); - else - contextMenu->Enable(CONTEXT_EXPLORER, false); - - contextMenu->AppendSeparator(); - - //CONTEXT_DELETE_FILES contextMenu->Append(CONTEXT_DELETE_FILES, _("Delete files\tDEL")); @@ -1108,98 +1218,78 @@ void MainDialog::OnContextRim(wxGridEvent& event) contextMenu->Enable(CONTEXT_DELETE_FILES, false); -//############################################################################################### - - contextMenu->Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextRimSelection), NULL, this); + //############################################################################################### + //connect events + contextMenu->Connect(CONTEXT_FILTER_TEMP, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextFilterTemp), NULL, this); + contextMenu->Connect(CONTEXT_CLIPBOARD, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextCopyClipboard), NULL, this); + contextMenu->Connect(CONTEXT_DELETE_FILES, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextDeleteFiles), NULL, this); + contextMenu->Connect(CONTEXT_SYNC_DIR_LEFT, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextSyncDirLeft), NULL, this); + contextMenu->Connect(CONTEXT_SYNC_DIR_NONE, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextSyncDirNone), NULL, this); + contextMenu->Connect(CONTEXT_SYNC_DIR_RIGHT, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextSyncDirRight), NULL, this); //show context menu PopupMenu(contextMenu.get()); } -void MainDialog::OnContextRimSelection(wxCommandEvent& event) +void MainDialog::OnContextFilterTemp(wxCommandEvent& event) { - const ContextIDRim eventId = static_cast<ContextIDRim>(event.GetId()); - switch (eventId) - { - case CONTEXT_SYNC_DIR_LEFT: - { - //merge selections from left and right grid - const std::set<int> selection = getSelectedRows(); - setSyncDirManually(selection, FreeFileSync::SYNC_DIR_LEFT); - } - break; - - case CONTEXT_SYNC_DIR_NONE: - { - //merge selections from left and right grid - const std::set<int> selection = getSelectedRows(); - setSyncDirManually(selection, FreeFileSync::SYNC_DIR_NONE); - } - break; + //merge selections from left and right grid + std::set<unsigned int> selection = getSelectedRows(); + filterRangeManually(selection, *selection.begin()); +} - case CONTEXT_SYNC_DIR_RIGHT: - { - //merge selections from left and right grid - const std::set<int> selection = getSelectedRows(); - setSyncDirManually(selection, FreeFileSync::SYNC_DIR_RIGHT); - } - break; - case CONTEXT_FILTER_TEMP: +void MainDialog::OnContextExcludeExtension(wxCommandEvent& event) +{ + SelectedExtension* selExtension = dynamic_cast<SelectedExtension*>(event.m_callbackUserData); + if (selExtension) { - //merge selections from left and right grid - std::set<int> selection = getSelectedRows(); - filterRangeManually(selection, *selection.begin()); - } - break; - - case CONTEXT_EXCLUDE_EXT: - if (!exFilterCandidateExtension.IsEmpty()) - { - if (!currentCfg.mainCfg.excludeFilter.IsEmpty() && !currentCfg.mainCfg.excludeFilter.EndsWith(wxT(";"))) - currentCfg.mainCfg.excludeFilter+= wxT("\n"); + if (!currentCfg.mainCfg.excludeFilter.IsEmpty() && !currentCfg.mainCfg.excludeFilter.EndsWith(wxT(";"))) + currentCfg.mainCfg.excludeFilter += wxT("\n"); - currentCfg.mainCfg.excludeFilter+= wxString(wxT("*.")) + exFilterCandidateExtension + wxT(";"); //';' is appended to 'mark' that next exclude extension entry won't write to new line + currentCfg.mainCfg.excludeFilter += wxString(wxT("*.")) + selExtension->extension + wxT(";"); //';' is appended to 'mark' that next exclude extension entry won't write to new line - currentCfg.mainCfg.filterIsActive = true; - updateFilterButton(m_bpButtonFilter, currentCfg.mainCfg.filterIsActive); + currentCfg.mainCfg.filterIsActive = true; + updateFilterButton(m_bpButtonFilter, currentCfg.mainCfg.filterIsActive); - FreeFileSync::FilterProcess filterInstance(currentCfg.mainCfg.includeFilter, currentCfg.mainCfg.excludeFilter); - filterInstance.filterGridData(currentGridData); + applyFiltering(getCurrentConfiguration().mainCfg, gridDataView->getDataTentative()); + updateGuiGrid(); - updateGuiGrid(); - if (currentCfg.hideFilteredElements) - { - m_gridLeft->ClearSelection(); - m_gridRight->ClearSelection(); - m_gridMiddle->ClearSelection(); - } + if (currentCfg.hideFilteredElements) + { + m_gridLeft-> ClearSelection(); + m_gridRight-> ClearSelection(); + m_gridMiddle->ClearSelection(); } - break; + } +} - case CONTEXT_EXCLUDE_OBJ: - if (exFilterCandidateObj.size() > 0) //check needed to determine if filtering is needed + +void MainDialog::OnContextExcludeObject(wxCommandEvent& event) +{ + FilterObjContainer* objCont = dynamic_cast<FilterObjContainer*>(event.m_callbackUserData); + if (objCont) + { + if (objCont->selectedObjects.size() > 0) //check needed to determine if filtering is needed { - for (std::vector<FilterObject>::const_iterator i = exFilterCandidateObj.begin(); i != exFilterCandidateObj.end(); ++i) + for (std::vector<FilterObject>::const_iterator i = objCont->selectedObjects.begin(); i != objCont->selectedObjects.end(); ++i) { if (!currentCfg.mainCfg.excludeFilter.IsEmpty() && !currentCfg.mainCfg.excludeFilter.EndsWith(wxT("\n"))) currentCfg.mainCfg.excludeFilter+= wxT("\n"); - if (i->type == FileDescrLine::TYPE_FILE) + if (!i->isDir) currentCfg.mainCfg.excludeFilter+= wxString(globalFunctions::FILE_NAME_SEPARATOR) + i->relativeName; - else if (i->type == FileDescrLine::TYPE_DIRECTORY) + else currentCfg.mainCfg.excludeFilter+= wxString(globalFunctions::FILE_NAME_SEPARATOR) + i->relativeName + globalFunctions::FILE_NAME_SEPARATOR + wxT("*"); - else assert(false); } currentCfg.mainCfg.filterIsActive = true; updateFilterButton(m_bpButtonFilter, currentCfg.mainCfg.filterIsActive); - FreeFileSync::FilterProcess filterInstance(currentCfg.mainCfg.includeFilter, currentCfg.mainCfg.excludeFilter); - filterInstance.filterGridData(currentGridData); - + applyFiltering(getCurrentConfiguration().mainCfg, gridDataView->getDataTentative()); updateGuiGrid(); + if (currentCfg.hideFilteredElements) { m_gridLeft->ClearSelection(); @@ -1207,40 +1297,67 @@ void MainDialog::OnContextRimSelection(wxCommandEvent& event) m_gridMiddle->ClearSelection(); } } - break; + } +} - case CONTEXT_CLIPBOARD: - if (m_gridLeft->isLeadGrid()) - copySelectionToClipboard(m_gridLeft); - else if (m_gridRight->isLeadGrid()) - copySelectionToClipboard(m_gridRight); - break; - case CONTEXT_EXPLORER: - if (m_gridLeft->isLeadGrid() || m_gridRight->isLeadGrid()) - { - const CustomGrid* leadGrid = NULL; - if (m_gridLeft->isLeadGrid()) - leadGrid = m_gridLeft; - else - leadGrid = m_gridRight; +void MainDialog::OnContextCopyClipboard(wxCommandEvent& event) +{ + if (m_gridLeft->isLeadGrid()) + copySelectionToClipboard(m_gridLeft); + else if (m_gridRight->isLeadGrid()) + copySelectionToClipboard(m_gridRight); +} - std::set<int> selection = getSelectedRows(leadGrid); - if (selection.size() == 1) - openWithFileManager(*selection.begin(), m_gridLeft->isLeadGrid()); - else if (selection.size() == 0) - openWithFileManager(-1, m_gridLeft->isLeadGrid()); - } - break; +void MainDialog::OnContextOpenWith(wxCommandEvent& event) +{ + if (m_gridLeft->isLeadGrid() || m_gridRight->isLeadGrid()) + { + const CustomGrid* leadGrid = m_gridLeft->isLeadGrid() ? + static_cast<CustomGrid*>(m_gridLeft) : + static_cast<CustomGrid*>(m_gridRight); + std::set<unsigned int> selection = getSelectedRows(leadGrid); - case CONTEXT_DELETE_FILES: - deleteSelectedFiles(); - break; + const int index = event.GetId() - externalAppIDFirst; + + if ( selection.size() == 1 && + 0 <= index && static_cast<unsigned>(index) < globalSettings.gui.externelApplications.size()) + openExternalApplication(*selection.begin(), m_gridLeft->isLeadGrid(), globalSettings.gui.externelApplications[index].second); } } +void MainDialog::OnContextDeleteFiles(wxCommandEvent& event) +{ + deleteSelectedFiles(); +} + + +void MainDialog::OnContextSyncDirLeft(wxCommandEvent& event) +{ + //merge selections from left and right grid + const std::set<unsigned int> selection = getSelectedRows(); + setSyncDirManually(selection, FreeFileSync::SYNC_DIR_LEFT); +} + + +void MainDialog::OnContextSyncDirNone(wxCommandEvent& event) +{ + //merge selections from left and right grid + const std::set<unsigned int> selection = getSelectedRows(); + setSyncDirManually(selection, FreeFileSync::SYNC_DIR_NONE); +} + + +void MainDialog::OnContextSyncDirRight(wxCommandEvent& event) +{ + //merge selections from left and right grid + const std::set<unsigned int> selection = getSelectedRows(); + setSyncDirManually(selection, FreeFileSync::SYNC_DIR_RIGHT); +} + + void MainDialog::OnContextRimLabelLeft(wxGridEvent& event) { contextMenu.reset(new wxMenu); //re-create context menu @@ -1252,7 +1369,9 @@ void MainDialog::OnContextRimLabelLeft(wxGridEvent& event) contextMenu->Append(itemAutoAdjust); itemAutoAdjust->Check(globalSettings.gui.autoAdjustColumnsLeft); - contextMenu->Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextRimLabelSelection), NULL, this); + contextMenu->Connect(CONTEXT_CUSTOMIZE_COLUMN_LEFT, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextCustColumnLeft), NULL, this); + contextMenu->Connect(CONTEXT_AUTO_ADJUST_COLUMN_LEFT, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextAutoAdjustLeft), NULL, this); + PopupMenu(contextMenu.get()); //show context menu } @@ -1268,62 +1387,60 @@ void MainDialog::OnContextRimLabelRight(wxGridEvent& event) contextMenu->Append(itemAutoAdjust); itemAutoAdjust->Check(globalSettings.gui.autoAdjustColumnsRight); - contextMenu->Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextRimLabelSelection), NULL, this); + contextMenu->Connect(CONTEXT_CUSTOMIZE_COLUMN_RIGHT, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextCustColumnRight), NULL, this); + contextMenu->Connect(CONTEXT_AUTO_ADJUST_COLUMN_RIGHT, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextAutoAdjustRight), NULL, this); + PopupMenu(contextMenu.get()); //show context menu } -void MainDialog::OnContextRimLabelSelection(wxCommandEvent& event) +void MainDialog::OnContextCustColumnLeft(wxCommandEvent& event) { - const ContextIDRimLabel eventId = static_cast<ContextIDRimLabel>(event.GetId()); - switch (eventId) - { - case CONTEXT_CUSTOMIZE_COLUMN_LEFT: + xmlAccess::ColumnAttributes colAttr = m_gridLeft->getColumnAttributes(); + CustomizeColsDlg* customizeDlg = new CustomizeColsDlg(this, colAttr, globalSettings.gui.showFileIconsLeft); + if (customizeDlg->ShowModal() == CustomizeColsDlg::BUTTON_OKAY) { - xmlAccess::ColumnAttributes colAttr = m_gridLeft->getColumnAttributes(); - CustomizeColsDlg* customizeDlg = new CustomizeColsDlg(this, colAttr, globalSettings.gui.showFileIconsLeft); - if (customizeDlg->ShowModal() == CustomizeColsDlg::BUTTON_OKAY) - { - m_gridLeft->setColumnAttributes(colAttr); + m_gridLeft->setColumnAttributes(colAttr); #ifdef FFS_WIN - m_gridLeft->enableFileIcons(globalSettings.gui.showFileIconsLeft); + m_gridLeft->enableFileIcons(globalSettings.gui.showFileIconsLeft); #endif - m_gridLeft->setSortMarker(CustomGrid::SortMarker(-1, CustomGrid::ASCENDING)); //hide sort direction indicator on GUI grids - m_gridMiddle->setSortMarker(CustomGrid::SortMarker(-1, CustomGrid::ASCENDING)); - m_gridRight->setSortMarker(CustomGrid::SortMarker(-1, CustomGrid::ASCENDING)); - } + m_gridLeft->setSortMarker(CustomGrid::SortMarker(-1, CustomGrid::ASCENDING)); //hide sort direction indicator on GUI grids + m_gridMiddle->setSortMarker(CustomGrid::SortMarker(-1, CustomGrid::ASCENDING)); + m_gridRight->setSortMarker(CustomGrid::SortMarker(-1, CustomGrid::ASCENDING)); } - break; +} - case CONTEXT_CUSTOMIZE_COLUMN_RIGHT: + +void MainDialog::OnContextCustColumnRight(wxCommandEvent& event) +{ + xmlAccess::ColumnAttributes colAttr = m_gridRight->getColumnAttributes(); + CustomizeColsDlg* customizeDlg = new CustomizeColsDlg(this, colAttr, globalSettings.gui.showFileIconsRight); + if (customizeDlg->ShowModal() == CustomizeColsDlg::BUTTON_OKAY) { - xmlAccess::ColumnAttributes colAttr = m_gridRight->getColumnAttributes(); - CustomizeColsDlg* customizeDlg = new CustomizeColsDlg(this, colAttr, globalSettings.gui.showFileIconsRight); - if (customizeDlg->ShowModal() == CustomizeColsDlg::BUTTON_OKAY) - { - m_gridRight->setColumnAttributes(colAttr); + m_gridRight->setColumnAttributes(colAttr); #ifdef FFS_WIN - m_gridRight->enableFileIcons(globalSettings.gui.showFileIconsRight); + m_gridRight->enableFileIcons(globalSettings.gui.showFileIconsRight); #endif - m_gridLeft->setSortMarker(CustomGrid::SortMarker(-1, CustomGrid::ASCENDING)); //hide sort direction indicator on GUI grids - m_gridMiddle->setSortMarker(CustomGrid::SortMarker(-1, CustomGrid::ASCENDING)); - m_gridRight->setSortMarker(CustomGrid::SortMarker(-1, CustomGrid::ASCENDING)); - } + m_gridLeft->setSortMarker(CustomGrid::SortMarker(-1, CustomGrid::ASCENDING)); //hide sort direction indicator on GUI grids + m_gridMiddle->setSortMarker(CustomGrid::SortMarker(-1, CustomGrid::ASCENDING)); + m_gridRight->setSortMarker(CustomGrid::SortMarker(-1, CustomGrid::ASCENDING)); } - break; +} - case CONTEXT_AUTO_ADJUST_COLUMN_LEFT: - globalSettings.gui.autoAdjustColumnsLeft = !globalSettings.gui.autoAdjustColumnsLeft; - updateGuiGrid(); - break; - case CONTEXT_AUTO_ADJUST_COLUMN_RIGHT: - globalSettings.gui.autoAdjustColumnsRight = !globalSettings.gui.autoAdjustColumnsRight; - updateGuiGrid(); - break; - } +void MainDialog::OnContextAutoAdjustLeft(wxCommandEvent& event) +{ + globalSettings.gui.autoAdjustColumnsLeft = !globalSettings.gui.autoAdjustColumnsLeft; + updateGuiGrid(); +} + + +void MainDialog::OnContextAutoAdjustRight(wxCommandEvent& event) +{ + globalSettings.gui.autoAdjustColumnsRight = !globalSettings.gui.autoAdjustColumnsRight; + updateGuiGrid(); } @@ -1334,31 +1451,16 @@ void MainDialog::OnContextMiddle(wxGridEvent& event) contextMenu->Append(CONTEXT_CHECK_ALL, _("Check all")); contextMenu->Append(CONTEXT_UNCHECK_ALL, _("Uncheck all")); - if (gridDataView->refGridIsEmpty()) + if (gridDataView->rowsTotal() == 0) { contextMenu->Enable(CONTEXT_CHECK_ALL, false); contextMenu->Enable(CONTEXT_UNCHECK_ALL, false); } - contextMenu->Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextMiddleSelection), NULL, this); - PopupMenu(contextMenu.get()); //show context menu -} - + contextMenu->Connect(CONTEXT_CHECK_ALL, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextIncludeAll), NULL, this); + contextMenu->Connect(CONTEXT_UNCHECK_ALL, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextExcludeAll), NULL, this); -void MainDialog::OnContextMiddleSelection(wxCommandEvent& event) -{ - const ContextIDMiddle eventId = static_cast<ContextIDMiddle>(event.GetId()); - switch (eventId) - { - case CONTEXT_CHECK_ALL: - FreeFileSync::FilterProcess::includeAllRowsOnGrid(currentGridData); - refreshGridAfterFilterChange(0); //call this instead of updateGuiGrid() to add some delay if hideFiltered == true and to handle some graphical artifacts break; - break; - case CONTEXT_UNCHECK_ALL: - FreeFileSync::FilterProcess::excludeAllRowsOnGrid(currentGridData); - refreshGridAfterFilterChange(400); //call this instead of updateGuiGrid() to add some delay if hideFiltered == true and to handle some graphical artifacts - break; - } + PopupMenu(contextMenu.get()); //show context menu } @@ -1377,25 +1479,36 @@ void MainDialog::OnContextMiddleLabel(wxGridEvent& event) contextMenu->Append(itemCmpResult); contextMenu->Append(itemSyncPreview); - contextMenu->Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextMiddleLabelSelection), NULL, this); + contextMenu->Connect(CONTEXT_SYNC_PREVIEW, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextSyncView), NULL, this); + contextMenu->Connect(CONTEXT_COMPARISON_RESULT, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextComparisonView), NULL, this); + PopupMenu(contextMenu.get()); //show context menu } -void MainDialog::OnContextMiddleLabelSelection(wxCommandEvent& event) +void MainDialog::OnContextIncludeAll(wxCommandEvent& event) { - const ContextIDMiddleLabel eventId = static_cast<ContextIDMiddleLabel>(event.GetId()); - switch (eventId) - { - case CONTEXT_COMPARISON_RESULT: - //change view - syncPreview.enablePreview(false); - break; - case CONTEXT_SYNC_PREVIEW: - //change view - syncPreview.enablePreview(true); - break; - } + FilterProcess::setActiveStatus(true, gridDataView->getDataTentative()); + refreshGridAfterFilterChange(0); //call this instead of updateGuiGrid() to add some delay if hideFiltered == true and to handle some graphical artifacts break; +} + + +void MainDialog::OnContextExcludeAll(wxCommandEvent& event) +{ + FilterProcess::setActiveStatus(false, gridDataView->getDataTentative()); + refreshGridAfterFilterChange(400); //call this instead of updateGuiGrid() to add some delay if hideFiltered == true and to handle some graphical artifacts +} + + +void MainDialog::OnContextComparisonView(wxCommandEvent& event) +{ + syncPreview.enablePreview(false); //change view +} + + +void MainDialog::OnContextSyncView(wxCommandEvent& event) +{ + syncPreview.enablePreview(true); //change view } @@ -1407,7 +1520,7 @@ void MainDialog::OnDirSelected(wxFileDirPickerEvent& event) syncPreview.enableSynchronization(false); //clear grids - currentGridData.clear(); + gridDataView->clearAllRows(); updateGuiGrid(); event.Skip(); @@ -1442,7 +1555,7 @@ bool sameFileSpecified(const wxString& file1, const wxString& file2) const wxString file2Full = getFullFilename(file2); #ifdef FFS_WIN //don't respect case in windows build - return FreeFileSync::compareStringsWin32(file1Full.c_str(), file2Full.c_str()) == 0; + return file1Full.CmpNoCase(file2Full) == 0; #elif defined FFS_LINUX return file1Full == file2Full; #endif @@ -1502,7 +1615,7 @@ int findTextPos(const wxArrayString& array, const wxString& text) { for (unsigned int i = 0; i < array.GetCount(); ++i) #ifdef FFS_WIN //don't respect case in windows build - if (FreeFileSync::compareStringsWin32(array[i].c_str(), text.c_str()) == 0) + if (array[i].CmpNoCase(text) == 0) #elif defined FFS_LINUX if (array[i] == text) #endif @@ -1579,12 +1692,29 @@ bool MainDialog::trySaveConfig() //return true if saved successfully void MainDialog::OnLoadConfig(wxCommandEvent& event) { - wxFileDialog* filePicker = new wxFileDialog(this, wxEmptyString, wxEmptyString, wxEmptyString, wxString(_("FreeFileSync configuration")) + wxT(" (*.ffs_gui)|*.ffs_gui"), wxFD_OPEN); + wxFileDialog* filePicker = new wxFileDialog(this, + wxEmptyString, + wxEmptyString, + wxEmptyString, + wxString(_("FreeFileSync configuration")) + wxT(" (*.ffs_gui;*.ffs_batch)|*.ffs_gui;*.ffs_batch"), wxFD_OPEN); + if (filePicker->ShowModal() == wxID_OK) loadConfiguration(filePicker->GetPath()); } +void MainDialog::OnNewConfig(wxCommandEvent& event) +{ + if (!saveOldConfig()) //notify user about changed settings + return; + + setCurrentConfiguration(xmlAccess::XmlGuiConfig()); + + SetTitle(wxString(wxT("FreeFileSync - ")) + _("Folder Comparison and Synchronization")); + currentConfigFileName.clear(); +} + + void MainDialog::OnLoadFromHistory(wxCommandEvent& event) { const int selectedItem = m_choiceHistory->GetSelection(); @@ -1593,14 +1723,14 @@ void MainDialog::OnLoadFromHistory(wxCommandEvent& event) } -void MainDialog::loadConfiguration(const wxString& filename) +bool MainDialog::saveOldConfig() //return false on user abort { //notify user about changed settings - if (globalSettings.gui.popupOnConfigChange && !currentConfigFileName.empty()) //only if check is active and non-default config file loaded + if (globalSettings.optDialogs.popupOnConfigChange && !currentConfigFileName.empty()) //only if check is active and non-default config file loaded { if (lastConfigurationSaved != getCurrentConfiguration()) { - bool dontShowAgain = !globalSettings.gui.popupOnConfigChange; + bool dontShowAgain = !globalSettings.optDialogs.popupOnConfigChange; QuestionDlg* notifyChangeDlg = new QuestionDlg(this, QuestionDlg::BUTTON_YES | QuestionDlg::BUTTON_NO | QuestionDlg::BUTTON_CANCEL, @@ -1611,22 +1741,26 @@ void MainDialog::loadConfiguration(const wxString& filename) { case QuestionDlg::BUTTON_YES: if (!trySaveConfig()) - return; + return false; break; case QuestionDlg::BUTTON_NO: - globalSettings.gui.popupOnConfigChange = !dontShowAgain; + globalSettings.optDialogs.popupOnConfigChange = !dontShowAgain; break; case QuestionDlg::BUTTON_CANCEL: - return; + return false; } } } - //------------------------------------------------------------------------------------ + return true; +} + +void MainDialog::loadConfiguration(const wxString& filename) +{ if (!filename.IsEmpty()) - { //clear grids - currentGridData.clear(); - updateGuiGrid(); + { + if (!saveOldConfig()) + return; if (readConfigurationFromXml(filename)) pushStatusInformation(_("Configuration loaded!")); @@ -1692,44 +1826,17 @@ void MainDialog::OnFolderHistoryKeyEvent(wxKeyEvent& event) void MainDialog::OnClose(wxCloseEvent &event) { - requestShutdown(); -} - + if (!saveOldConfig()) //notify user about changed settings + return; -void MainDialog::OnQuit(wxCommandEvent &event) -{ - requestShutdown(); + Destroy(); } -void MainDialog::requestShutdown() +void MainDialog::OnQuit(wxCommandEvent &event) { - //notify user about changed settings - if (globalSettings.gui.popupOnConfigChange && !currentConfigFileName.empty()) //only if check is active and non-default config file loaded - { - if (lastConfigurationSaved != getCurrentConfiguration()) - { - bool dontShowAgain = !globalSettings.gui.popupOnConfigChange; - - QuestionDlg* notifyChangeDlg = new QuestionDlg(this, - QuestionDlg::BUTTON_YES | QuestionDlg::BUTTON_NO | QuestionDlg::BUTTON_CANCEL, - _("Save changes to current configuration?"), - dontShowAgain); - - switch (notifyChangeDlg->ShowModal()) - { - case QuestionDlg::BUTTON_YES: - if (!trySaveConfig()) - return; - break; - case QuestionDlg::BUTTON_NO: - globalSettings.gui.popupOnConfigChange = !dontShowAgain; - break; - case QuestionDlg::BUTTON_CANCEL: - return; - } - } - } + if (!saveOldConfig()) //notify user about changed settings + return; Destroy(); } @@ -1742,9 +1849,9 @@ void MainDialog::OnCheckRows(FFSCheckRowsEvent& event) if (0 <= lowerBound) { - std::set<int> selectedRowsOnView; + std::set<unsigned int> selectedRowsOnView; - for (int i = lowerBound; i <= std::min(upperBound, int(gridDataView->elementsOnView()) - 1); ++i) + for (int i = lowerBound; i <= std::min(upperBound, int(gridDataView->rowsOnView()) - 1); ++i) selectedRowsOnView.insert(i); filterRangeManually(selectedRowsOnView, event.rowFrom); @@ -1759,10 +1866,14 @@ void MainDialog::OnSetSyncDirection(FFSSyncDirectionEvent& event) if (0 <= lowerBound) { - for (int i = lowerBound; i <= std::min(upperBound, int(gridDataView->elementsOnView()) - 1); ++i) + for (int i = lowerBound; i <= std::min(upperBound, int(gridDataView->rowsOnView()) - 1); ++i) { - (*gridDataView)[i].syncDir = event.direction; - (*gridDataView)[i].selectedForSynchronization = true; + FileSystemObject* fsObj = gridDataView->getObject(i); + if (fsObj) + { + setSyncDirection(event.direction, *fsObj); //set new direction (recursively) + FilterProcess::setActiveStatus(true, *fsObj); //works recursively for directories + } } updateGuiGrid(); @@ -1774,9 +1885,10 @@ bool MainDialog::readConfigurationFromXml(const wxString& filename, bool program { //load XML xmlAccess::XmlGuiConfig newGuiCfg; //structure to receive gui settings, already defaulted!! + bool parsingError = false; try { - xmlAccess::readGuiConfig(filename, newGuiCfg); + xmlAccess::readGuiOrBatchConfig(filename, newGuiCfg); //allow reading batch configurations also } catch (const xmlAccess::XmlError& error) { @@ -1785,7 +1897,10 @@ bool MainDialog::readConfigurationFromXml(const wxString& filename, bool program else { if (error.getSeverity() == xmlAccess::XmlError::WARNING) + { wxMessageBox(error.show(), _("Warning"), wxOK | wxICON_WARNING); + parsingError = true; + } else { wxMessageBox(error.show(), _("Error"), wxOK | wxICON_ERROR); @@ -1794,60 +1909,15 @@ bool MainDialog::readConfigurationFromXml(const wxString& filename, bool program } } - currentCfg = newGuiCfg; - - //evaluate new settings... - - //disable the sync button - syncPreview.enableSynchronization(false); - - //clear grids - currentGridData.clear(); - updateGuiGrid(); - - //(re-)set view filter buttons - gridDataView->resetSettings(); - updateViewFilterButtons(); - - updateFilterButton(m_bpButtonFilter, currentCfg.mainCfg.filterIsActive); - - //read folder pairs: - - //clear existing pairs first - FreeFileSync::setDirectoryName(wxEmptyString, m_directoryLeft, m_dirPickerLeft); - FreeFileSync::setDirectoryName(wxEmptyString, m_directoryRight, m_dirPickerRight); - - clearAddFolderPairs(); - - if (currentCfg.directoryPairs.size() > 0) - { - //set main folder pair - std::vector<FolderPair>::const_iterator main = currentCfg.directoryPairs.begin(); - - FreeFileSync::setDirectoryName(main->leftDirectory.c_str(), m_directoryLeft, m_dirPickerLeft); - FreeFileSync::setDirectoryName(main->rightDirectory.c_str(), m_directoryRight, m_dirPickerRight); - - addLeftFolderToHistory( main->leftDirectory.c_str()); //another hack: wxCombobox::Insert() asynchronously sends message - addRightFolderToHistory(main->rightDirectory.c_str()); //overwriting a later wxCombobox::SetValue()!!! :( - - //set additional pairs - addFolderPair(std::vector<FolderPair>(currentCfg.directoryPairs.begin() + 1, currentCfg.directoryPairs.end())); - } - - //read GUI layout - currentCfg.hideFilteredElements = currentCfg.hideFilteredElements; - m_checkBoxHideFilt->SetValue(currentCfg.hideFilteredElements); - - currentCfg.ignoreErrors = currentCfg.ignoreErrors; - - syncPreview.enablePreview(currentCfg.syncPreviewEnabled); + setCurrentConfiguration(newGuiCfg); //########################################################### addFileToCfgHistory(filename); //put filename on list of last used config files - lastConfigurationSaved = currentCfg; + lastConfigurationSaved = parsingError ? xmlAccess::XmlGuiConfig() : currentCfg; //simulate changed config on parsing errors - if (filename == lastConfigFileName()) //set title + //set title + if (filename == lastConfigFileName()) { SetTitle(wxString(wxT("FreeFileSync - ")) + _("Folder Comparison and Synchronization")); currentConfigFileName.clear(); @@ -1858,13 +1928,6 @@ bool MainDialog::readConfigurationFromXml(const wxString& filename, bool program currentConfigFileName = filename; } - //update compare variant name - m_staticTextCmpVariant->SetLabel(wxString(wxT("(")) + getVariantName(currentCfg.mainCfg.compareVar) + wxT(")")); - - //update sync variant name - m_staticTextSyncVariant->SetLabel(wxString(wxT("(")) + currentCfg.mainCfg.syncConfiguration.getVariantName() + wxT(")")); - bSizer6->Layout(); //adapt layout for variant text - return true; } @@ -1904,12 +1967,83 @@ bool MainDialog::writeConfigurationToXml(const wxString& filename) } +void MainDialog::setCurrentConfiguration(const xmlAccess::XmlGuiConfig& newGuiCfg) +{ + currentCfg = newGuiCfg; + + //evaluate new settings... + + //disable the sync button + syncPreview.enableSynchronization(false); + + //clear grids + gridDataView->clearAllRows(); + updateGuiGrid(); + + //(re-)set view filter buttons + initViewFilterButtons(); + + updateFilterButton(m_bpButtonFilter, currentCfg.mainCfg.filterIsActive); + + + //read main folder pair + const wxString mainFolderLeft = currentCfg.mainCfg.mainFolderPair.leftDirectory.c_str(); + const wxString mainFolderRight = currentCfg.mainCfg.mainFolderPair.rightDirectory.c_str(); + FreeFileSync::setDirectoryName(mainFolderLeft, m_directoryLeft, m_dirPickerLeft); + FreeFileSync::setDirectoryName(mainFolderRight, m_directoryRight, m_dirPickerRight); + + addLeftFolderToHistory( mainFolderLeft); //another hack: wxCombobox::Insert() asynchronously sends message + addRightFolderToHistory(mainFolderRight); //overwriting a later wxCombobox::SetValue()!!! :( + + //clear existing additional folder pairs + clearAddFolderPairs(); + + //set additional pairs + addFolderPair(currentCfg.mainCfg.additionalPairs); + + + //read GUI layout + m_checkBoxHideFilt->SetValue(currentCfg.hideFilteredElements); + + syncPreview.enablePreview(currentCfg.syncPreviewEnabled); + + //########################################################### + //update compare variant name + m_staticTextCmpVariant->SetLabel(wxString(wxT("(")) + getVariantName(currentCfg.mainCfg.compareVar) + wxT(")")); + + //update sync variant name + m_staticTextSyncVariant->SetLabel(wxString(wxT("(")) + currentCfg.mainCfg.getSyncVariantName() + wxT(")")); + bSizer6->Layout(); //adapt layout for variant text +} + + +inline +FolderPairEnh getEnahncedPair(const FolderPairPanel* panel) +{ + return FolderPairEnh(panel->m_directoryLeft->GetValue().c_str(), + panel->m_directoryRight->GetValue().c_str(), + panel->altSyncConfig, + panel->altFilter); +} + + xmlAccess::XmlGuiConfig MainDialog::getCurrentConfiguration() const { xmlAccess::XmlGuiConfig guiCfg = currentCfg; - //load settings whose ownership lies not in currentCfg - guiCfg.directoryPairs = getFolderPairs(); + //load settings whose ownership lies not in currentCfg: + + //main folder pair + guiCfg.mainCfg.mainFolderPair.leftDirectory = m_directoryLeft->GetValue().c_str(); + guiCfg.mainCfg.mainFolderPair.rightDirectory = m_directoryRight->GetValue().c_str(); + + //add additional pairs + guiCfg.mainCfg.additionalPairs.clear(); + std::transform(additionalFolderPairs.begin(), additionalFolderPairs.end(), + std::back_inserter(guiCfg.mainCfg.additionalPairs), getEnahncedPair); + + + //sync preview guiCfg.syncPreviewEnabled = syncPreview.previewIsEnabled(); return guiCfg; @@ -1936,7 +2070,7 @@ void MainDialog::refreshGridAfterFilterChange(const int delay) if (currentCfg.hideFilteredElements) { wxMilliSleep(delay); //some delay to show user the rows he has filtered out before they are removed - updateGuiGrid(); //redraw grid to remove excluded elements (keeping sort sequence) + updateGuiGrid(); //redraw grid to remove excluded elements (keeping sort sequence) m_gridLeft->ClearSelection(); m_gridRight->ClearSelection(); @@ -1952,17 +2086,7 @@ void MainDialog::OnFilterButton(wxCommandEvent &event) //make sure, button-appearance and "filterIsActive" are in sync. updateFilterButton(m_bpButtonFilter, currentCfg.mainCfg.filterIsActive); - if (currentCfg.mainCfg.filterIsActive) - { - FreeFileSync::FilterProcess filterInstance(currentCfg.mainCfg.includeFilter, currentCfg.mainCfg.excludeFilter); - filterInstance.filterGridData(currentGridData); - refreshGridAfterFilterChange(400); //call this instead of updateGuiGrid() to add some delay if hideFiltered == true and to handle some graphical artifacts - } - else - { - FreeFileSync::FilterProcess::includeAllRowsOnGrid(currentGridData); - refreshGridAfterFilterChange(0); //call this instead of updateGuiGrid() to add some delay if hideFiltered == true and to handle some graphical artifacts } - } + updateFilterConfig(false); //refresh filtering (without changing active-status) } @@ -1974,41 +2098,21 @@ void MainDialog::OnHideFilteredButton(wxCommandEvent &event) m_gridLeft->ClearSelection(); m_gridRight->ClearSelection(); + updateGuiGrid(); - refreshGridAfterFilterChange(0); - - event.Skip(); +// event.Skip(); } void MainDialog::OnConfigureFilter(wxHyperlinkEvent &event) { - const wxString beforeImage = currentCfg.mainCfg.includeFilter + wxChar(1) + currentCfg.mainCfg.excludeFilter; - FilterDlg* filterDlg = new FilterDlg(this, currentCfg.mainCfg.includeFilter, currentCfg.mainCfg.excludeFilter); - if (filterDlg->ShowModal() == FilterDlg::BUTTON_OKAY) + if (filterDlg->ShowModal() == FilterDlg::BUTTON_APPLY) { - const wxString afterImage = currentCfg.mainCfg.includeFilter + wxChar(1) + currentCfg.mainCfg.excludeFilter; - - if (beforeImage != afterImage) //if filter settings are changed: set filtering to "on" - { - if (afterImage == (wxString(wxT("*")) + wxChar(1))) //default - { - currentCfg.mainCfg.filterIsActive = false; - FreeFileSync::FilterProcess::includeAllRowsOnGrid(currentGridData); - } - else - { - currentCfg.mainCfg.filterIsActive = true; - - FreeFileSync::FilterProcess filterInstance(currentCfg.mainCfg.includeFilter, currentCfg.mainCfg.excludeFilter); - filterInstance.filterGridData(currentGridData); - } - - updateFilterButton(m_bpButtonFilter, currentCfg.mainCfg.filterIsActive); - - updateGuiGrid(); - } + if (currentCfg.mainCfg.includeFilter == wxT("*") && currentCfg.mainCfg.excludeFilter.empty()) //default + updateFilterConfig(false); //re-apply filter (without changing active-status) + else + updateFilterConfig(true); //activate(and apply) filter } //no event.Skip() here, to not start browser @@ -2017,269 +2121,193 @@ void MainDialog::OnConfigureFilter(wxHyperlinkEvent &event) void MainDialog::OnLeftOnlyFiles(wxCommandEvent& event) { - gridDataView->leftOnlyFilesActive = !gridDataView->leftOnlyFilesActive; - updateViewFilterButtons(); + m_bpButtonLeftOnly->toggle(); updateGuiGrid(); }; + void MainDialog::OnLeftNewerFiles(wxCommandEvent& event) { - gridDataView->leftNewerFilesActive = !gridDataView->leftNewerFilesActive; - updateViewFilterButtons(); + m_bpButtonLeftNewer->toggle(); updateGuiGrid(); }; + void MainDialog::OnDifferentFiles(wxCommandEvent& event) { - gridDataView->differentFilesActive = !gridDataView->differentFilesActive; - updateViewFilterButtons(); + m_bpButtonDifferent->toggle(); updateGuiGrid(); }; + void MainDialog::OnRightNewerFiles(wxCommandEvent& event) { - gridDataView->rightNewerFilesActive = !gridDataView->rightNewerFilesActive; - updateViewFilterButtons(); + m_bpButtonRightNewer->toggle(); updateGuiGrid(); }; + void MainDialog::OnRightOnlyFiles(wxCommandEvent& event) { - gridDataView->rightOnlyFilesActive = !gridDataView->rightOnlyFilesActive; - updateViewFilterButtons(); + m_bpButtonRightOnly->toggle(); updateGuiGrid(); }; void MainDialog::OnEqualFiles(wxCommandEvent& event) { - gridDataView->equalFilesActive = !gridDataView->equalFilesActive; - updateViewFilterButtons(); + m_bpButtonEqual->toggle(); updateGuiGrid(); }; void MainDialog::OnConflictFiles(wxCommandEvent& event) { - gridDataView->conflictFilesActive = !gridDataView->conflictFilesActive; - updateViewFilterButtons(); + m_bpButtonConflict->toggle(); updateGuiGrid(); }; void MainDialog::OnSyncCreateLeft(wxCommandEvent& event) { - gridDataView->syncCreateLeftActive = !gridDataView->syncCreateLeftActive; - updateViewFilterButtons(); + m_bpButtonSyncCreateLeft->toggle(); updateGuiGrid(); }; void MainDialog::OnSyncCreateRight(wxCommandEvent& event) { - gridDataView->syncCreateRightActive = !gridDataView->syncCreateRightActive; - updateViewFilterButtons(); + m_bpButtonSyncCreateRight->toggle(); updateGuiGrid(); }; void MainDialog::OnSyncDeleteLeft(wxCommandEvent& event) { - gridDataView->syncDeleteLeftActive = !gridDataView->syncDeleteLeftActive; - updateViewFilterButtons(); + m_bpButtonSyncDeleteLeft->toggle(); updateGuiGrid(); }; void MainDialog::OnSyncDeleteRight(wxCommandEvent& event) { - gridDataView->syncDeleteRightActive = !gridDataView->syncDeleteRightActive; - updateViewFilterButtons(); + m_bpButtonSyncDeleteRight->toggle(); updateGuiGrid(); }; void MainDialog::OnSyncDirLeft(wxCommandEvent& event) { - gridDataView->syncDirLeftActive = !gridDataView->syncDirLeftActive; - updateViewFilterButtons(); + m_bpButtonSyncDirOverwLeft->toggle(); updateGuiGrid(); }; void MainDialog::OnSyncDirRight(wxCommandEvent& event) { - gridDataView->syncDirRightActive = !gridDataView->syncDirRightActive; - updateViewFilterButtons(); + m_bpButtonSyncDirOverwRight->toggle(); updateGuiGrid(); }; void MainDialog::OnSyncDirNone(wxCommandEvent& event) { - gridDataView->syncDirNoneActive = !gridDataView->syncDirNoneActive; - updateViewFilterButtons(); + m_bpButtonSyncDirNone->toggle(); updateGuiGrid(); }; -void MainDialog::updateViewFilterButtons() +void MainDialog::initViewFilterButtons() { //compare result buttons - if (gridDataView->leftOnlyFilesActive) - { - m_bpButtonLeftOnly->SetBitmapLabel(*GlobalResources::getInstance().bitmapLeftOnlyAct); - m_bpButtonLeftOnly->SetToolTip(_("Hide files that exist on left side only")); - } - else - { - m_bpButtonLeftOnly->SetBitmapLabel(*GlobalResources::getInstance().bitmapLeftOnlyDeact); - m_bpButtonLeftOnly->SetToolTip(_("Show files that exist on left side only")); - } - - if (gridDataView->rightOnlyFilesActive) - { - m_bpButtonRightOnly->SetBitmapLabel(*GlobalResources::getInstance().bitmapRightOnlyAct); - m_bpButtonRightOnly->SetToolTip(_("Hide files that exist on right side only")); - } - else - { - m_bpButtonRightOnly->SetBitmapLabel(*GlobalResources::getInstance().bitmapRightOnlyDeact); - m_bpButtonRightOnly->SetToolTip(_("Show files that exist on right side only")); - } - - if (gridDataView->leftNewerFilesActive) - { - m_bpButtonLeftNewer->SetBitmapLabel(*GlobalResources::getInstance().bitmapLeftNewerAct); - m_bpButtonLeftNewer->SetToolTip(_("Hide files that are newer on left")); - } - else - { - m_bpButtonLeftNewer->SetBitmapLabel(*GlobalResources::getInstance().bitmapLeftNewerDeact); - m_bpButtonLeftNewer->SetToolTip(_("Show files that are newer on left")); - } - - if (gridDataView->rightNewerFilesActive) - { - m_bpButtonRightNewer->SetBitmapLabel(*GlobalResources::getInstance().bitmapRightNewerAct); - m_bpButtonRightNewer->SetToolTip(_("Hide files that are newer on right")); - } - else - { - m_bpButtonRightNewer->SetBitmapLabel(*GlobalResources::getInstance().bitmapRightNewerDeact); - m_bpButtonRightNewer->SetToolTip(_("Show files that are newer on right")); - } - - if (gridDataView->equalFilesActive) - { - m_bpButtonEqual->SetBitmapLabel(*GlobalResources::getInstance().bitmapEqualAct); - m_bpButtonEqual->SetToolTip(_("Hide files that are equal")); - } - else - { - m_bpButtonEqual->SetBitmapLabel(*GlobalResources::getInstance().bitmapEqualDeact); - m_bpButtonEqual->SetToolTip(_("Show files that are equal")); - } - - if (gridDataView->differentFilesActive) - { - m_bpButtonDifferent->SetBitmapLabel(*GlobalResources::getInstance().bitmapDifferentAct); - m_bpButtonDifferent->SetToolTip(_("Hide files that are different")); - } - else - { - m_bpButtonDifferent->SetBitmapLabel(*GlobalResources::getInstance().bitmapDifferentDeact); - m_bpButtonDifferent->SetToolTip(_("Show files that are different")); - } - - if (gridDataView->conflictFilesActive) - { - m_bpButtonConflict->SetBitmapLabel(*GlobalResources::getInstance().bitmapConflictAct); - m_bpButtonConflict->SetToolTip(_("Hide conflicts")); - } - else - { - m_bpButtonConflict->SetBitmapLabel(*GlobalResources::getInstance().bitmapConflictDeact); - m_bpButtonConflict->SetToolTip(_("Show conflicts")); - } + m_bpButtonLeftOnly->init(*GlobalResources::getInstance().bitmapLeftOnlyAct, + _("Hide files that exist on left side only"), + *GlobalResources::getInstance().bitmapLeftOnlyDeact, + _("Show files that exist on left side only")); + + m_bpButtonRightOnly->init(*GlobalResources::getInstance().bitmapRightOnlyAct, + _("Hide files that exist on right side only"), + *GlobalResources::getInstance().bitmapRightOnlyDeact, + _("Show files that exist on right side only")); + + m_bpButtonLeftNewer->init(*GlobalResources::getInstance().bitmapLeftNewerAct, + _("Hide files that are newer on left"), + *GlobalResources::getInstance().bitmapLeftNewerDeact, + _("Show files that are newer on left")); + + m_bpButtonRightNewer->init(*GlobalResources::getInstance().bitmapRightNewerAct, + _("Hide files that are newer on right"), + *GlobalResources::getInstance().bitmapRightNewerDeact, + _("Show files that are newer on right")); + + m_bpButtonEqual->init(*GlobalResources::getInstance().bitmapEqualAct, + _("Hide files that are equal"), + *GlobalResources::getInstance().bitmapEqualDeact, + _("Show files that are equal")); + + m_bpButtonDifferent->init(*GlobalResources::getInstance().bitmapDifferentAct, + _("Hide files that are different"), + *GlobalResources::getInstance().bitmapDifferentDeact, + _("Show files that are different")); + + m_bpButtonConflict->init(*GlobalResources::getInstance().bitmapConflictAct, + _("Hide conflicts"), + *GlobalResources::getInstance().bitmapConflictDeact, + _("Show conflicts")); //sync preview buttons - if (gridDataView->syncCreateLeftActive) - { - m_bpButtonSyncCreateLeft->SetBitmapLabel(*GlobalResources::getInstance().bitmapSyncCreateLeftAct); - m_bpButtonSyncCreateLeft->SetToolTip(_("Hide files that will be created on the left side")); - } - else - { - m_bpButtonSyncCreateLeft->SetBitmapLabel(*GlobalResources::getInstance().bitmapSyncCreateLeftDeact); - m_bpButtonSyncCreateLeft->SetToolTip(_("Show files that will be created on the left side")); - } - - if (gridDataView->syncCreateRightActive) - { - m_bpButtonSyncCreateRight->SetBitmapLabel(*GlobalResources::getInstance().bitmapSyncCreateRightAct); - m_bpButtonSyncCreateRight->SetToolTip(_("Hide files that will be created on the right side")); - } - else - { - m_bpButtonSyncCreateRight->SetBitmapLabel(*GlobalResources::getInstance().bitmapSyncCreateRightDeact); - m_bpButtonSyncCreateRight->SetToolTip(_("Show files that will be created on the right side")); - } - - if (gridDataView->syncDeleteLeftActive) - { - m_bpButtonSyncDeleteLeft->SetBitmapLabel(*GlobalResources::getInstance().bitmapSyncDeleteLeftAct); - m_bpButtonSyncDeleteLeft->SetToolTip(_("Hide files that will be deleted on the left side")); - } - else - { - m_bpButtonSyncDeleteLeft->SetBitmapLabel(*GlobalResources::getInstance().bitmapSyncDeleteLeftDeact); - m_bpButtonSyncDeleteLeft->SetToolTip(_("Show files that will be deleted on the left side")); - } - - if (gridDataView->syncDeleteRightActive) - { - m_bpButtonSyncDeleteRight->SetBitmapLabel(*GlobalResources::getInstance().bitmapSyncDeleteRightAct); - m_bpButtonSyncDeleteRight->SetToolTip(_("Hide files that will be deleted on the right side")); - } - else - { - m_bpButtonSyncDeleteRight->SetBitmapLabel(*GlobalResources::getInstance().bitmapSyncDeleteRightDeact); - m_bpButtonSyncDeleteRight->SetToolTip(_("Show files that will be deleted on the right side")); - } + m_bpButtonSyncCreateLeft->init(*GlobalResources::getInstance().bitmapSyncCreateLeftAct, + _("Hide files that will be created on the left side"), + *GlobalResources::getInstance().bitmapSyncCreateLeftDeact, + _("Show files that will be created on the left side")); + + m_bpButtonSyncCreateRight->init(*GlobalResources::getInstance().bitmapSyncCreateRightAct, + _("Hide files that will be created on the right side"), + *GlobalResources::getInstance().bitmapSyncCreateRightDeact, + _("Show files that will be created on the right side")); + + m_bpButtonSyncDeleteLeft->init(*GlobalResources::getInstance().bitmapSyncDeleteLeftAct, + _("Hide files that will be deleted on the left side"), + *GlobalResources::getInstance().bitmapSyncDeleteLeftDeact, + _("Show files that will be deleted on the left side")); + + m_bpButtonSyncDeleteRight->init(*GlobalResources::getInstance().bitmapSyncDeleteRightAct, + _("Hide files that will be deleted on the right side"), + *GlobalResources::getInstance().bitmapSyncDeleteRightDeact, + _("Show files that will be deleted on the right side")); + + m_bpButtonSyncDirOverwLeft->init(*GlobalResources::getInstance().bitmapSyncDirLeftAct, + _("Hide files that will be overwritten on left side"), + *GlobalResources::getInstance().bitmapSyncDirLeftDeact, + _("Show files that will be overwritten on left side")); + + m_bpButtonSyncDirOverwRight->init(*GlobalResources::getInstance().bitmapSyncDirRightAct, + _("Hide files that will be overwritten on right side"), + *GlobalResources::getInstance().bitmapSyncDirRightDeact, + _("Show files that will be overwritten on right side")); + + m_bpButtonSyncDirNone->init(*GlobalResources::getInstance().bitmapSyncDirNoneAct, + _("Hide files that won't be copied"), + *GlobalResources::getInstance().bitmapSyncDirNoneDeact, + _("Show files that won't be copied")); - if (gridDataView->syncDirLeftActive) - { - m_bpButtonSyncDirLeft->SetBitmapLabel(*GlobalResources::getInstance().bitmapSyncDirLeftAct); - m_bpButtonSyncDirLeft->SetToolTip(_("Hide files that will be copied to the left side")); - } - else - { - m_bpButtonSyncDirLeft->SetBitmapLabel(*GlobalResources::getInstance().bitmapSyncDirLeftDeact); - m_bpButtonSyncDirLeft->SetToolTip(_("Show files that will be copied to the left side")); - } - - if (gridDataView->syncDirRightActive) - { - m_bpButtonSyncDirRight->SetBitmapLabel(*GlobalResources::getInstance().bitmapSyncDirRightAct); - m_bpButtonSyncDirRight->SetToolTip(_("Hide files that will be copied to the right side")); - } - else - { - m_bpButtonSyncDirRight->SetBitmapLabel(*GlobalResources::getInstance().bitmapSyncDirRightDeact); - m_bpButtonSyncDirRight->SetToolTip(_("Show files that will be copied to the right side")); - } + //compare result buttons + m_bpButtonLeftOnly-> setActive(true); + m_bpButtonRightOnly-> setActive(true); + m_bpButtonLeftNewer-> setActive(true); + m_bpButtonRightNewer->setActive(true); + m_bpButtonEqual-> setActive(false); + m_bpButtonDifferent-> setActive(true); + m_bpButtonConflict-> setActive(true); - if (gridDataView->syncDirNoneActive) - { - m_bpButtonSyncDirNone->SetBitmapLabel(*GlobalResources::getInstance().bitmapSyncDirNoneAct); - m_bpButtonSyncDirNone->SetToolTip(_("Hide files that won't be copied")); - } - else - { - m_bpButtonSyncDirNone->SetBitmapLabel(*GlobalResources::getInstance().bitmapSyncDirNoneDeact); - m_bpButtonSyncDirNone->SetToolTip(_("Show files that won't be copied")); - } + //sync preview buttons + m_bpButtonSyncCreateLeft-> setActive(true); + m_bpButtonSyncCreateRight-> setActive(true); + m_bpButtonSyncDeleteLeft-> setActive(true); + m_bpButtonSyncDeleteRight-> setActive(true); + m_bpButtonSyncDirOverwLeft-> setActive(true); + m_bpButtonSyncDirOverwRight->setActive(true); + m_bpButtonSyncDirNone-> setActive(true); } @@ -2311,34 +2339,15 @@ void MainDialog::updateFilterButton(wxBitmapButton* filterButton, bool isActive) } -std::vector<FolderPair> MainDialog::getFolderPairs() const -{ - std::vector<FolderPair> output; - - //add main pair - output.push_back(FolderPair(m_directoryLeft->GetValue().c_str(), - m_directoryRight->GetValue().c_str())); - - //add additional pairs - for (std::vector<FolderPairPanel*>::const_iterator i = additionalFolderPairs.begin(); i != additionalFolderPairs.end(); ++i) - output.push_back(FolderPair((*i)->m_directoryLeft->GetValue().c_str(), - (*i)->m_directoryRight->GetValue().c_str())); - return output; -} - - void MainDialog::OnCompare(wxCommandEvent &event) { //PERF_START; - clearStatusBar(); wxBusyCursor dummy; //show hourglass cursor - //1. prevent temporary memory peak by clearing old result list - //2. ATTENTION: wxAPP->Yield() will be called in the following! - // when comparing there will be a mismatch "gridDataView <-> currentGridData": make sure gridDataView does NOT access currentGridData!!! - currentGridData.clear(); + //prevent temporary memory peak by clearing old result list + gridDataView->clearAllRows(); updateGuiGrid(); //refresh GUI grid bool aborted = false; @@ -2346,29 +2355,22 @@ void MainDialog::OnCompare(wxCommandEvent &event) { //class handling status display and error messages CompareStatusHandler statusHandler(this); - //prepare filter - std::auto_ptr<FreeFileSync::FilterProcess> filterInstance(NULL); - if (currentCfg.mainCfg.filterIsActive) - filterInstance.reset(new FreeFileSync::FilterProcess(currentCfg.mainCfg.includeFilter, currentCfg.mainCfg.excludeFilter)); - //begin comparison - FreeFileSync::CompareProcess comparison(globalSettings.traverseDirectorySymlinks, - globalSettings.fileTimeTolerance, + FreeFileSync::CompareProcess comparison(currentCfg.mainCfg.hidden.traverseDirectorySymlinks, + currentCfg.mainCfg.hidden.fileTimeTolerance, globalSettings.ignoreOneHourDiff, - globalSettings.warnings, - filterInstance.get(), + globalSettings.optDialogs, &statusHandler); - comparison.startCompareProcess(getFolderPairs(), - currentCfg.mainCfg.compareVar, - currentCfg.mainCfg.syncConfiguration, - currentGridData); + //technical representation of comparison data + FreeFileSync::FolderComparison newCompareData; - //if (output.size < 50000) - statusHandler.updateStatusText(_("Sorting file list...")); - statusHandler.forceUiRefresh(); //keep total number of scanned files up to date + comparison.startCompareProcess( + FreeFileSync::extractCompareCfg(getCurrentConfiguration().mainCfg), //call getCurrentCfg() to get current values for directory pairs! + currentCfg.mainCfg.compareVar, + newCompareData); - gridDataView->sortView(GridView::SORT_BY_DIRECTORY, true, true); + gridDataView->setData(newCompareData); //newCompareData is invalidated after this call } catch (AbortThisProcess&) { @@ -2394,9 +2396,9 @@ void MainDialog::OnCompare(wxCommandEvent &event) lastSortColumn = -1; lastSortGrid = NULL; - m_gridLeft->ClearSelection(); + m_gridLeft-> ClearSelection(); m_gridMiddle->ClearSelection(); - m_gridRight->ClearSelection(); + m_gridRight-> ClearSelection(); //add to folder history after successful comparison only addLeftFolderToHistory(m_directoryLeft->GetValue()); @@ -2416,8 +2418,8 @@ void MainDialog::updateGuiGrid() updateGridViewData(); //update gridDataView and write status information - //all three grids retrieve their data directly via gridDataView(which knows currentGridData)!!! - //the only thing left to do is notify the grids to update their sizes (nr of rows), since this has to be communicated by the grids via messages + //all three grids retrieve their data directly via gridDataView + //the only thing left to do is notify the grids to updafte their sizes (nr of rows), since this has to be communicated by the grids via messages m_gridLeft ->updateGridSizes(); m_gridMiddle->updateGridSizes(); m_gridRight ->updateGridSizes(); @@ -2454,7 +2456,7 @@ void MainDialog::updateGuiGrid() void MainDialog::calculatePreview() { //update preview of bytes to be transferred: - const SyncStatistics st(currentGridData); + const SyncStatistics st(gridDataView->getDataTentative()); const wxString toCreate = FreeFileSync::includeNumberSeparator(globalFunctions::numberToWxString(st.getCreate())); const wxString toUpdate = FreeFileSync::includeNumberSeparator(globalFunctions::numberToWxString(st.getOverwrite())); const wxString toDelete = FreeFileSync::includeNumberSeparator(globalFunctions::numberToWxString(st.getDelete())); @@ -2476,16 +2478,15 @@ void MainDialog::OnSwitchView(wxCommandEvent& event) void MainDialog::OnSyncSettings(wxCommandEvent& event) { - SyncCfgDialog* syncDlg = new SyncCfgDialog(this, currentGridData, currentCfg.mainCfg, currentCfg.ignoreErrors); - if (syncDlg->ShowModal() == SyncCfgDialog::BUTTON_OKAY) + SyncCfgDialog* syncDlg = new SyncCfgDialog(this, + currentCfg.mainCfg.compareVar, + currentCfg.mainCfg.syncConfiguration, + currentCfg.mainCfg.handleDeletion, + currentCfg.mainCfg.customDeletionDirectory, + ¤tCfg.ignoreErrors); + if (syncDlg->ShowModal() == SyncCfgDialog::BUTTON_APPLY) { - //update sync variant name - m_staticTextSyncVariant->SetLabel(wxString(wxT("(")) + currentCfg.mainCfg.syncConfiguration.getVariantName() + wxT(")")); - bSizer6->Layout(); //adapt layout for variant text - - redetermineSyncDirection(currentCfg.mainCfg.syncConfiguration, currentGridData); - - updateGuiGrid(); + updateSyncConfig(); } } @@ -2494,7 +2495,11 @@ void MainDialog::OnCmpSettings(wxCommandEvent& event) { CompareVariant newCmpVariant = currentCfg.mainCfg.compareVar; - CompareCfgDialog* syncDlg = new CompareCfgDialog(this, newCmpVariant); + //show window right next to the compare-config button + wxPoint windowPos = m_bpButtonCmpConfig->GetScreenPosition(); + windowPos.x += m_bpButtonCmpConfig->GetSize().GetWidth() + 5; + + CompareCfgDialog* syncDlg = new CompareCfgDialog(this, windowPos, newCmpVariant); if (syncDlg->ShowModal() == CompareCfgDialog::BUTTON_OKAY) { if (currentCfg.mainCfg.compareVar != newCmpVariant) @@ -2509,7 +2514,7 @@ void MainDialog::OnCmpSettings(wxCommandEvent& event) syncPreview.enableSynchronization(false); //clear grids - currentGridData.clear(); + gridDataView->clearAllRows(); updateGuiGrid(); m_buttonCompare->SetFocus(); @@ -2523,24 +2528,24 @@ void MainDialog::OnStartSync(wxCommandEvent& event) if (syncPreview.synchronizationIsEnabled()) { //show sync preview screen - if (globalSettings.gui.showSummaryBeforeSync) + if (globalSettings.optDialogs.showSummaryBeforeSync) { bool dontShowAgain = false; SyncPreviewDlg* preview = new SyncPreviewDlg( this, - currentCfg.mainCfg.syncConfiguration.getVariantName(), - FreeFileSync::SyncStatistics(currentGridData), + getCurrentConfiguration().mainCfg.getSyncVariantName(), + FreeFileSync::SyncStatistics(gridDataView->getDataTentative()), dontShowAgain); if (preview->ShowModal() != SyncPreviewDlg::BUTTON_START) return; - globalSettings.gui.showSummaryBeforeSync = !dontShowAgain; + globalSettings.optDialogs.showSummaryBeforeSync = !dontShowAgain; } //check if there are files/folders to be sync'ed at all - if (!synchronizationNeeded(currentGridData)) + if (!synchronizationNeeded(gridDataView->getDataTentative())) { wxMessageBox(_("Nothing to synchronize according to configuration!"), _("Information"), wxICON_WARNING); return; @@ -2548,62 +2553,60 @@ void MainDialog::OnStartSync(wxCommandEvent& event) wxBusyCursor dummy; //show hourglass cursor - //ATTENTION: wxAPP->Yield() will be called in the following! - //when sync'ing there will be a mismatch "gridDataView <-> currentGridData": make sure gridDataView does NOT access currentGridData!!! - gridDataView->clearView(); - clearStatusBar(); try { + //PERF_START; + //class handling status updates and error messages SyncStatusHandler statusHandler(this, currentCfg.ignoreErrors); - //start synchronization and return elements that were not sync'ed in currentGridData + //start synchronization and mark all elements processed FreeFileSync::SyncProcess synchronization( - currentCfg.mainCfg.handleDeletion, - currentCfg.mainCfg.customDeletionDirectory, - globalSettings.copyFileSymlinks, - globalSettings.traverseDirectorySymlinks, - globalSettings.warnings, + currentCfg.mainCfg.hidden.copyFileSymlinks, + currentCfg.mainCfg.hidden.traverseDirectorySymlinks, + globalSettings.optDialogs, + currentCfg.mainCfg.hidden.verifyFileCopy, &statusHandler); - synchronization.startSynchronizationProcess(currentGridData); + const std::vector<FreeFileSync::FolderPairSyncCfg> syncProcessCfg = FreeFileSync::extractSyncCfg(getCurrentConfiguration().mainCfg); + FolderComparison& dataToSync = gridDataView->getDataTentative(); + + //make sure syncProcessCfg and dataToSync have same size and correspond! + if (syncProcessCfg.size() != dataToSync.size()) + throw std::logic_error("Programming Error: Contract violation!"); //should never happen: sync button is deactivated if they are not in sync + + synchronization.startSynchronizationProcess(syncProcessCfg, dataToSync); } catch (AbortThisProcess&) { //do NOT disable the sync button: user might want to try to sync the REMAINING rows } //enableSynchronization(false); + //remove rows that empty: just a beautification, invalid rows shouldn't cause issues + gridDataView->removeInvalidRows(); - //show remaining files that have not been processed: put DIRECTLY after startSynchronizationProcess() and DON'T call any wxWidgets functions - //in between! Else CustomGrid might access obsolete data entries in currentGridData! updateGuiGrid(); - m_gridLeft->ClearSelection(); + m_gridLeft-> ClearSelection(); m_gridMiddle->ClearSelection(); - m_gridRight->ClearSelection(); - - if (!gridDataView->refGridIsEmpty()) - pushStatusInformation(_("Not all items have been synchronized! Have a look at the list.")); - else - { - pushStatusInformation(_("All items have been synchronized!")); - syncPreview.enableSynchronization(false); - } + m_gridRight-> ClearSelection(); } } void MainDialog::OnLeftGridDoubleClick(wxGridEvent& event) { - openWithFileManager(event.GetRow(), true); - event.Skip(); + if (!globalSettings.gui.externelApplications.empty()) + openExternalApplication(event.GetRow(), true, globalSettings.gui.externelApplications[0].second); + // event.Skip(); } void MainDialog::OnRightGridDoubleClick(wxGridEvent& event) { - openWithFileManager(event.GetRow(), false); - event.Skip(); + if (!globalSettings.gui.externelApplications.empty()) + openExternalApplication(event.GetRow(), false, globalSettings.gui.externelApplications[0].second); +// event.Skip(); } @@ -2743,27 +2746,38 @@ void MainDialog::OnSwapSides(wxCommandEvent& event) m_directoryRight->SetValue(leftDir); //additional pairs - wxString tmp; for (std::vector<FolderPairPanel*>::const_iterator i = additionalFolderPairs.begin(); i != additionalFolderPairs.end(); ++i) { FolderPairPanel* dirPair = *i; - tmp = dirPair->m_directoryLeft->GetValue(); + wxString tmp = dirPair->m_directoryLeft->GetValue(); dirPair->m_directoryLeft->SetValue(dirPair->m_directoryRight->GetValue()); dirPair->m_directoryRight->SetValue(tmp); } //swap view filter - std::swap(gridDataView->leftOnlyFilesActive, gridDataView->rightOnlyFilesActive); - std::swap(gridDataView->leftNewerFilesActive, gridDataView->rightNewerFilesActive); + bool tmp = m_bpButtonLeftOnly->isActive(); + m_bpButtonLeftOnly->setActive(m_bpButtonRightOnly->isActive()); + m_bpButtonRightOnly->setActive(tmp); + + tmp = m_bpButtonLeftNewer->isActive(); + m_bpButtonLeftNewer->setActive(m_bpButtonRightNewer->isActive()); + m_bpButtonRightNewer->setActive(tmp); + - std::swap(gridDataView->syncCreateLeftActive, gridDataView->syncCreateRightActive); - std::swap(gridDataView->syncDirLeftActive, gridDataView->syncDirRightActive); - std::swap(gridDataView->syncDeleteLeftActive, gridDataView->syncDeleteRightActive); + tmp = m_bpButtonSyncCreateLeft->isActive(); + m_bpButtonSyncCreateLeft->setActive(m_bpButtonSyncCreateRight->isActive()); + m_bpButtonSyncCreateRight->setActive(tmp); - updateViewFilterButtons(); + tmp = m_bpButtonSyncDeleteLeft->isActive(); + m_bpButtonSyncDeleteLeft->setActive(m_bpButtonSyncDeleteRight->isActive()); + m_bpButtonSyncDeleteRight->setActive(tmp); + + tmp = m_bpButtonSyncDirOverwLeft->isActive(); + m_bpButtonSyncDirOverwLeft->setActive(m_bpButtonSyncDirOverwRight->isActive()); + m_bpButtonSyncDirOverwRight->setActive(tmp); //swap grid information - FreeFileSync::swapGrids(currentCfg.mainCfg.syncConfiguration, currentGridData); + FreeFileSync::swapGrids2(getCurrentConfiguration().mainCfg, gridDataView->getDataTentative()); updateGuiGrid(); event.Skip(); } @@ -2771,103 +2785,120 @@ void MainDialog::OnSwapSides(wxCommandEvent& event) void MainDialog::updateGridViewData() { - const GridView::StatusInfo result = gridDataView->update(currentCfg.hideFilteredElements, syncPreview.previewIsEnabled()); - - //hide or enable view filter buttons - - //comparison result view buttons - if (result.existsLeftOnly) - m_bpButtonLeftOnly->Show(); - else - m_bpButtonLeftOnly->Hide(); - - if (result.existsRightOnly) - m_bpButtonRightOnly->Show(); - else - m_bpButtonRightOnly->Hide(); - - if (result.existsLeftNewer) - m_bpButtonLeftNewer->Show(); - else - m_bpButtonLeftNewer->Hide(); - - if (result.existsRightNewer) - m_bpButtonRightNewer->Show(); - else - m_bpButtonRightNewer->Hide(); + unsigned int filesOnLeftView = 0; + unsigned int foldersOnLeftView = 0; + unsigned int filesOnRightView = 0; + unsigned int foldersOnRightView = 0; + wxULongLong filesizeLeftView; + wxULongLong filesizeRightView; - if (result.existsDifferent) - m_bpButtonDifferent->Show(); - else - m_bpButtonDifferent->Hide(); - - if (result.existsEqual) - m_bpButtonEqual->Show(); - else - m_bpButtonEqual->Hide(); - - if (result.existsConflict) - m_bpButtonConflict->Show(); - else - m_bpButtonConflict->Hide(); - - //sync preview buttons - if (result.existsSyncCreateLeft) - m_bpButtonSyncCreateLeft->Show(); - else - m_bpButtonSyncCreateLeft->Hide(); - - if (result.existsSyncCreateRight) - m_bpButtonSyncCreateRight->Show(); - else - m_bpButtonSyncCreateRight->Hide(); + //disable all buttons per default + m_bpButtonLeftOnly-> Show(false); + m_bpButtonRightOnly-> Show(false); + m_bpButtonLeftNewer-> Show(false); + m_bpButtonRightNewer->Show(false); + m_bpButtonDifferent-> Show(false); + m_bpButtonEqual-> Show(false); + m_bpButtonConflict-> Show(false); - if (result.existsSyncDeleteLeft) - m_bpButtonSyncDeleteLeft->Show(); - else - m_bpButtonSyncDeleteLeft->Hide(); + m_bpButtonSyncCreateLeft-> Show(false); + m_bpButtonSyncCreateRight-> Show(false); + m_bpButtonSyncDeleteLeft-> Show(false); + m_bpButtonSyncDeleteRight-> Show(false); + m_bpButtonSyncDirOverwLeft-> Show(false); + m_bpButtonSyncDirOverwRight->Show(false); + m_bpButtonSyncDirNone-> Show(false); - if (result.existsSyncDeleteRight) - m_bpButtonSyncDeleteRight->Show(); - else - m_bpButtonSyncDeleteRight->Hide(); - if (result.existsSyncDirLeft) - m_bpButtonSyncDirLeft->Show(); - else - m_bpButtonSyncDirLeft->Hide(); - if (result.existsSyncDirRight) - m_bpButtonSyncDirRight->Show(); - else - m_bpButtonSyncDirRight->Hide(); + if (syncPreview.previewIsEnabled()) + { + const GridView::StatusSyncPreview result = gridDataView->updateSyncPreview(currentCfg.hideFilteredElements, + m_bpButtonSyncCreateLeft-> isActive(), + m_bpButtonSyncCreateRight-> isActive(), + m_bpButtonSyncDeleteLeft-> isActive(), + m_bpButtonSyncDeleteRight-> isActive(), + m_bpButtonSyncDirOverwLeft-> isActive(), + m_bpButtonSyncDirOverwRight->isActive(), + m_bpButtonSyncDirNone-> isActive(), + m_bpButtonConflict-> isActive()); + + filesOnLeftView = result.filesOnLeftView; + foldersOnLeftView = result.foldersOnLeftView; + filesOnRightView = result.filesOnRightView; + foldersOnRightView = result.foldersOnRightView; + filesizeLeftView = result.filesizeLeftView; + filesizeRightView = result.filesizeRightView; + + + //sync preview buttons + m_bpButtonSyncCreateLeft-> Show(result.existsSyncCreateLeft); + m_bpButtonSyncCreateRight-> Show(result.existsSyncCreateRight); + m_bpButtonSyncDeleteLeft-> Show(result.existsSyncDeleteLeft); + m_bpButtonSyncDeleteRight-> Show(result.existsSyncDeleteRight); + m_bpButtonSyncDirOverwLeft-> Show(result.existsSyncDirLeft); + m_bpButtonSyncDirOverwRight->Show(result.existsSyncDirRight); + m_bpButtonSyncDirNone-> Show(result.existsSyncDirNone); + m_bpButtonConflict-> Show(result.existsConflict); + + if ( m_bpButtonSyncCreateLeft-> IsShown() || + m_bpButtonSyncCreateRight-> IsShown() || + m_bpButtonSyncDeleteLeft-> IsShown() || + m_bpButtonSyncDeleteRight-> IsShown() || + m_bpButtonSyncDirOverwLeft-> IsShown() || + m_bpButtonSyncDirOverwRight->IsShown() || + m_bpButtonSyncDirNone-> IsShown() || + m_bpButtonConflict-> IsShown()) + { + m_panel112->Show(); + m_panel112->Layout(); + } + else + m_panel112->Hide(); - if (result.existsSyncDirNone) - m_bpButtonSyncDirNone->Show(); - else - m_bpButtonSyncDirNone->Hide(); - - - if ( result.existsLeftOnly || - result.existsRightOnly || - result.existsLeftNewer || - result.existsRightNewer || - result.existsDifferent || - result.existsEqual || - result.existsConflict || - result.existsSyncCreateLeft || - result.existsSyncCreateRight || - result.existsSyncDeleteLeft || - result.existsSyncDeleteRight || - result.existsSyncDirLeft || - result.existsSyncDirRight || - result.existsSyncDirNone) - { - m_panel112->Show(); - m_panel112->Layout(); } else - m_panel112->Hide(); + { + const GridView::StatusCmpResult result = gridDataView->updateCmpResult(currentCfg.hideFilteredElements, + m_bpButtonLeftOnly-> isActive(), + m_bpButtonRightOnly-> isActive(), + m_bpButtonLeftNewer-> isActive(), + m_bpButtonRightNewer->isActive(), + m_bpButtonDifferent-> isActive(), + m_bpButtonEqual-> isActive(), + m_bpButtonConflict-> isActive()); + + filesOnLeftView = result.filesOnLeftView; + foldersOnLeftView = result.foldersOnLeftView; + filesOnRightView = result.filesOnRightView; + foldersOnRightView = result.foldersOnRightView; + filesizeLeftView = result.filesizeLeftView; + filesizeRightView = result.filesizeRightView; + + //comparison result view buttons + m_bpButtonLeftOnly-> Show(result.existsLeftOnly); + m_bpButtonRightOnly-> Show(result.existsRightOnly); + m_bpButtonLeftNewer-> Show(result.existsLeftNewer); + m_bpButtonRightNewer->Show(result.existsRightNewer); + m_bpButtonDifferent-> Show(result.existsDifferent); + m_bpButtonEqual-> Show(result.existsEqual); + m_bpButtonConflict-> Show(result.existsConflict); + + if ( m_bpButtonLeftOnly-> IsShown() || + m_bpButtonRightOnly-> IsShown() || + m_bpButtonLeftNewer-> IsShown() || + m_bpButtonRightNewer->IsShown() || + m_bpButtonDifferent-> IsShown() || + m_bpButtonEqual-> IsShown() || + m_bpButtonConflict-> IsShown()) + { + m_panel112->Show(); + m_panel112->Layout(); + } + else + m_panel112->Hide(); + } + bSizer3->Layout(); @@ -2884,41 +2915,41 @@ void MainDialog::updateGridViewData() //format numbers to text: //show status information on "root" level. - if (result.foldersOnLeftView) + if (foldersOnLeftView) { - if (result.foldersOnLeftView == 1) + if (foldersOnLeftView == 1) statusLeftNew += _("1 directory"); else { - wxString folderCount = FreeFileSync::includeNumberSeparator(globalFunctions::numberToWxString(result.foldersOnLeftView)); + wxString folderCount = FreeFileSync::includeNumberSeparator(globalFunctions::numberToWxString(foldersOnLeftView)); wxString outputString = _("%x directories"); outputString.Replace(wxT("%x"), folderCount, false); statusLeftNew += outputString; } - if (result.filesOnLeftView) + if (filesOnLeftView) statusLeftNew += wxT(", "); } - if (result.filesOnLeftView) + if (filesOnLeftView) { - if (result.filesOnLeftView == 1) + if (filesOnLeftView == 1) statusLeftNew += _("1 file,"); else { - wxString fileCount = FreeFileSync::includeNumberSeparator(globalFunctions::numberToWxString(result.filesOnLeftView)); + wxString fileCount = FreeFileSync::includeNumberSeparator(globalFunctions::numberToWxString(filesOnLeftView)); wxString outputString = _("%x files,"); outputString.Replace(wxT("%x"), fileCount, false); statusLeftNew += outputString; } statusLeftNew += wxT(" "); - statusLeftNew += FreeFileSync::formatFilesizeToShortString(result.filesizeLeftView); + statusLeftNew += FreeFileSync::formatFilesizeToShortString(filesizeLeftView); } - const wxString objectsView = FreeFileSync::includeNumberSeparator(globalFunctions::numberToWxString(gridDataView->elementsOnView())); - if (result.objectsTotal == 1) + const wxString objectsView = FreeFileSync::includeNumberSeparator(globalFunctions::numberToWxString(gridDataView->rowsOnView())); + if (gridDataView->rowsTotal() == 1) { wxString outputString = _("%x of 1 row in view"); outputString.Replace(wxT("%x"), objectsView, false); @@ -2926,7 +2957,7 @@ void MainDialog::updateGridViewData() } else { - const wxString objectsTotal = FreeFileSync::includeNumberSeparator(globalFunctions::numberToWxString(result.objectsTotal)); + const wxString objectsTotal = FreeFileSync::includeNumberSeparator(globalFunctions::numberToWxString(gridDataView->rowsTotal())); wxString outputString = _("%x of %y rows in view"); outputString.Replace(wxT("%x"), objectsView, false); @@ -2934,30 +2965,30 @@ void MainDialog::updateGridViewData() statusMiddleNew = outputString; } - if (result.foldersOnRightView) + if (foldersOnRightView) { - if (result.foldersOnRightView == 1) + if (foldersOnRightView == 1) statusRightNew += _("1 directory"); else { - wxString folderCount = FreeFileSync::includeNumberSeparator(globalFunctions::numberToWxString(result.foldersOnRightView)); + wxString folderCount = FreeFileSync::includeNumberSeparator(globalFunctions::numberToWxString(foldersOnRightView)); wxString outputString = _("%x directories"); outputString.Replace(wxT("%x"), folderCount, false); statusRightNew += outputString; } - if (result.filesOnRightView) + if (filesOnRightView) statusRightNew += wxT(", "); } - if (result.filesOnRightView) + if (filesOnRightView) { - if (result.filesOnRightView == 1) + if (filesOnRightView == 1) statusRightNew += _("1 file,"); else { - wxString fileCount = FreeFileSync::includeNumberSeparator(globalFunctions::numberToWxString(result.filesOnRightView)); + wxString fileCount = FreeFileSync::includeNumberSeparator(globalFunctions::numberToWxString(filesOnRightView)); wxString outputString = _("%x files,"); outputString.Replace(wxT("%x"), fileCount, false); @@ -2965,7 +2996,7 @@ void MainDialog::updateGridViewData() } statusRightNew += wxT(" "); - statusRightNew += FreeFileSync::formatFilesizeToShortString(result.filesizeRightView); + statusRightNew += FreeFileSync::formatFilesizeToShortString(filesizeRightView); } @@ -2983,93 +3014,88 @@ void MainDialog::updateGridViewData() void MainDialog::OnAddFolderPair(wxCommandEvent& event) { - std::vector<FolderPair> newPairs; - newPairs.push_back(FolderPair(m_directoryLeft ->GetValue().c_str(), - m_directoryRight->GetValue().c_str())); + wxWindowUpdateLocker dummy(this); //avoid display distortion + + std::vector<FolderPairEnh> newPairs; + newPairs.push_back( + FolderPairEnh(Zstring(), + Zstring(), + boost::shared_ptr<AlternateSyncConfig>(), + boost::shared_ptr<AlternateFilter>())); - addFolderPair(newPairs, true); //add pair in front of additonal pairs + addFolderPair(newPairs, false); //add pair at the end of additional pairs - //clear existing pairs - FreeFileSync::setDirectoryName(wxEmptyString, m_directoryLeft, m_dirPickerLeft); - FreeFileSync::setDirectoryName(wxEmptyString, m_directoryRight, m_dirPickerRight); + FreeFileSync::scrollToBottom(m_scrolledWindowFolderPairs); //disable the sync button syncPreview.enableSynchronization(false); //clear grids - currentGridData.clear(); + gridDataView->clearAllRows(); updateGuiGrid(); } -void MainDialog::OnRemoveFolderPair(wxCommandEvent& event) +void MainDialog::updateFilterConfig(bool activateFilter) //true: activate filter false: stay as it is { - //find folder pair originating the event - const wxObject* const eventObj = event.GetEventObject(); - for (std::vector<FolderPairPanel*>::const_iterator i = additionalFolderPairs.begin(); i != additionalFolderPairs.end(); ++i) + if (activateFilter) { - if (eventObj == static_cast<wxObject*>((*i)->m_bpButtonRemovePair)) + //activate filter (if not yet active) + if (!currentCfg.mainCfg.filterIsActive) { - removeAddFolderPair(i - additionalFolderPairs.begin()); - - //disable the sync button - syncPreview.enableSynchronization(false); - - //clear grids - currentGridData.clear(); - updateGuiGrid(); - return; + currentCfg.mainCfg.filterIsActive = true; + updateFilterButton(m_bpButtonFilter, currentCfg.mainCfg.filterIsActive); } } -} - -void MainDialog::OnRemoveTopFolderPair(wxCommandEvent& event) -{ - if (additionalFolderPairs.size() > 0) + if (currentCfg.mainCfg.filterIsActive) { - const wxString leftDir = (*additionalFolderPairs.begin())->m_directoryLeft->GetValue().c_str(); - const wxString rightDir = (*additionalFolderPairs.begin())->m_directoryRight->GetValue().c_str(); - - FreeFileSync::setDirectoryName(leftDir, m_directoryLeft, m_dirPickerLeft); - FreeFileSync::setDirectoryName(rightDir, m_directoryRight, m_dirPickerRight); + applyFiltering(getCurrentConfiguration().mainCfg, gridDataView->getDataTentative()); + refreshGridAfterFilterChange(400); + } + else + { + FilterProcess::setActiveStatus(true, gridDataView->getDataTentative()); + refreshGridAfterFilterChange(0); + } +} - removeAddFolderPair(0); //remove first of additional folder pairs - //disable the sync button - syncPreview.enableSynchronization(false); +void MainDialog::updateSyncConfig() +{ + //update sync variant name + m_staticTextSyncVariant->SetLabel(wxString(wxT("(")) + getCurrentConfiguration().mainCfg.getSyncVariantName() + wxT(")")); + bSizer6->Layout(); //adapt layout for variant text - //clear grids - currentGridData.clear(); - updateGuiGrid(); - } + FreeFileSync::redetermineSyncDirection(getCurrentConfiguration().mainCfg, gridDataView->getDataTentative()); + updateGuiGrid(); } -void scrollToBottom(wxScrolledWindow* scrWindow) +void MainDialog::OnRemoveFolderPair(wxCommandEvent& event) { - int height = 0; - scrWindow->GetClientSize(NULL, &height); + const wxObject* const eventObj = event.GetEventObject(); //find folder pair originating the event + for (std::vector<FolderPairPanel*>::const_iterator i = additionalFolderPairs.begin(); i != additionalFolderPairs.end(); ++i) + if (eventObj == static_cast<wxObject*>((*i)->m_bpButtonRemovePair)) + { + removeAddFolderPair(i - additionalFolderPairs.begin()); - int pixelPerLine = 0; - scrWindow->GetScrollPixelsPerUnit(NULL, &pixelPerLine); + //disable the sync button + syncPreview.enableSynchronization(false); - if (height > 0 && pixelPerLine > 0) - { - const int scrollLinesTotal = scrWindow->GetScrollLines(wxVERTICAL); - const int scrollLinesOnScreen = height / pixelPerLine; - const int scrollPosBottom = scrollLinesTotal - scrollLinesOnScreen; + //clear grids + gridDataView->clearAllRows(); - if (0 <= scrollPosBottom) - scrWindow->Scroll(0, scrollPosBottom); - } + updateSyncConfig(); + return; + } } const size_t MAX_ADD_FOLDER_PAIRS = 5; -void MainDialog::addFolderPair(const std::vector<FolderPair>& newPairs, bool addFront) +void MainDialog::addFolderPair(const std::vector<FolderPairEnh>& newPairs, bool addFront) { if (newPairs.size() == 0) return; @@ -3077,12 +3103,13 @@ void MainDialog::addFolderPair(const std::vector<FolderPair>& newPairs, bool add wxWindowUpdateLocker dummy(this); //avoid display distortion int pairHeight = 0; - for (std::vector<FolderPair>::const_iterator i = newPairs.begin(); i != newPairs.end(); ++i) + for (std::vector<FolderPairEnh>::const_iterator i = newPairs.begin(); i != newPairs.end(); ++i) { //add new folder pair - FolderPairPanel* newPair = new FolderPairPanel(m_scrolledWindowFolderPairs); - newPair->m_bitmap23->SetBitmap(*GlobalResources::getInstance().bitmapLink); - newPair->m_bpButtonRemovePair->SetBitmapLabel(*GlobalResources::getInstance().bitmapRemoveFolderPair); + FolderPairPanel* newPair = new FolderPairPanel(m_scrolledWindowFolderPairs, this); + + //correct width of middle block + newPair->m_panel21->SetMinSize(wxSize(m_gridMiddle->GetSize().GetWidth(), -1)); //set width of left folder panel const int width = m_panelTopLeft->GetSize().GetWidth(); @@ -3104,21 +3131,22 @@ void MainDialog::addFolderPair(const std::vector<FolderPair>& newPairs, bool add pairHeight = newPair->GetSize().GetHeight(); //register events - newPair->m_bpButtonRemovePair->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MainDialog::OnRemoveFolderPair), NULL, this ); + newPair->m_bpButtonRemovePair->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MainDialog::OnRemoveFolderPair), NULL, this); //insert directory names FreeFileSync::setDirectoryName(i->leftDirectory.c_str(), newPair->m_directoryLeft, newPair->m_dirPickerLeft); FreeFileSync::setDirectoryName(i->rightDirectory.c_str(), newPair->m_directoryRight, newPair->m_dirPickerRight); + + //set alternate configuration + newPair->altSyncConfig = i->altSyncConfig; + newPair->altFilter = i->altFilter; + newPair->updateAltButtonColor(); } //set size of scrolled window const int visiblePairs = std::min(additionalFolderPairs.size(), MAX_ADD_FOLDER_PAIRS); //up to MAX_ADD_FOLDER_PAIRS additional pairs shall be shown m_scrolledWindowFolderPairs->SetMinSize(wxSize( -1, pairHeight * visiblePairs)); - //adapt delete top folder pair button - m_bpButtonRemoveTopPair->Show(); - m_panelTopRight->Layout(); - //update controls m_scrolledWindowFolderPairs->Fit(); //adjust scrolled window size m_scrolledWindowFolderPairs->Layout(); //adjust stuff inside scrolled window @@ -3129,11 +3157,11 @@ void MainDialog::addFolderPair(const std::vector<FolderPair>& newPairs, bool add } -void MainDialog::removeAddFolderPair(const int pos) +void MainDialog::removeAddFolderPair(const unsigned int pos) { wxWindowUpdateLocker dummy(this); //avoid display distortion - if (0 <= pos && pos < int(additionalFolderPairs.size())) + if (pos < additionalFolderPairs.size()) { //remove folder pairs from window FolderPairPanel* pairToDelete = additionalFolderPairs[pos]; @@ -3148,13 +3176,6 @@ void MainDialog::removeAddFolderPair(const int pos) if (additionalRows <= MAX_ADD_FOLDER_PAIRS) //up to MAX_ADD_FOLDER_PAIRS additional pairs shall be shown m_scrolledWindowFolderPairs->SetMinSize(wxSize(-1, pairHeight * additionalRows)); - //adapt delete top folder pair button - if (additionalFolderPairs.size() == 0) - { - m_bpButtonRemoveTopPair->Hide(); - m_panelTopRight->Layout(); - } - //update controls m_scrolledWindowFolderPairs->Fit(); //adjust scrolled window size m_scrolledWindowFolderPairs->Layout(); //adjust stuff inside scrolled window @@ -3170,9 +3191,6 @@ void MainDialog::clearAddFolderPairs() additionalFolderPairs.clear(); bSizerAddFolderPairs->Clear(true); - m_bpButtonRemoveTopPair->Hide(); - m_panelTopRight->Layout(); - m_scrolledWindowFolderPairs->SetMinSize(wxSize(-1, 0)); bSizer1->Layout(); } @@ -3209,25 +3227,71 @@ void MainDialog::OnMenuExportFileList(wxCommandEvent& event) } } - //begin work wxString exportString; + //write legend + exportString += wxString(_("Legend")) + wxT('\n'); + if (syncPreview.previewIsEnabled()) + { + exportString += wxString(wxT("\"")) + getDescription(SO_CREATE_NEW_LEFT) + wxT("\";") + getSymbol(SO_CREATE_NEW_LEFT) + wxT('\n'); + exportString += wxString(wxT("\"")) + getDescription(SO_CREATE_NEW_RIGHT) + wxT("\";") + getSymbol(SO_CREATE_NEW_RIGHT) + wxT('\n'); + exportString += wxString(wxT("\"")) + getDescription(SO_DELETE_LEFT) + wxT("\";") + getSymbol(SO_DELETE_LEFT) + wxT('\n'); + exportString += wxString(wxT("\"")) + getDescription(SO_DELETE_RIGHT) + wxT("\";") + getSymbol(SO_DELETE_RIGHT) + wxT('\n'); + exportString += wxString(wxT("\"")) + getDescription(SO_OVERWRITE_LEFT) + wxT("\";") + getSymbol(SO_OVERWRITE_LEFT) + wxT('\n'); + exportString += wxString(wxT("\"")) + getDescription(SO_OVERWRITE_RIGHT) + wxT("\";") + getSymbol(SO_OVERWRITE_RIGHT) + wxT('\n'); + exportString += wxString(wxT("\"")) + getDescription(SO_DO_NOTHING) + wxT("\";") + getSymbol(SO_DO_NOTHING) + wxT('\n'); + exportString += wxString(wxT("\"")) + getDescription(SO_UNRESOLVED_CONFLICT) + wxT("\";") + getSymbol(SO_UNRESOLVED_CONFLICT) + wxT('\n'); + } + else + { + exportString += wxString(wxT("\"")) + getDescription(FILE_LEFT_SIDE_ONLY) + wxT("\";") + getSymbol(FILE_LEFT_SIDE_ONLY) + wxT('\n'); + exportString += wxString(wxT("\"")) + getDescription(FILE_RIGHT_SIDE_ONLY) + wxT("\";") + getSymbol(FILE_RIGHT_SIDE_ONLY) + wxT('\n'); + exportString += wxString(wxT("\"")) + getDescription(FILE_LEFT_NEWER) + wxT("\";") + getSymbol(FILE_LEFT_NEWER) + wxT('\n'); + exportString += wxString(wxT("\"")) + getDescription(FILE_RIGHT_NEWER) + wxT("\";") + getSymbol(FILE_RIGHT_NEWER) + wxT('\n'); + exportString += wxString(wxT("\"")) + getDescription(FILE_DIFFERENT) + wxT("\";") + getSymbol(FILE_DIFFERENT) + wxT('\n'); + exportString += wxString(wxT("\"")) + getDescription(FILE_EQUAL) + wxT("\";") + getSymbol(FILE_EQUAL) + wxT('\n'); + exportString += wxString(wxT("\"")) + getDescription(FILE_CONFLICT) + wxT("\";") + getSymbol(FILE_CONFLICT) + wxT('\n'); + } + exportString += '\n'; + + //write header + for (int k = 0; k < m_gridLeft->GetNumberCols(); ++k) + { + exportString += m_gridLeft->GetColLabelValue(k); + exportString += ';'; + } + + for (int k = 0; k < m_gridMiddle->GetNumberCols(); ++k) + { + exportString += m_gridMiddle->GetColLabelValue(k); + exportString += ';'; + } + + for (int k = 0; k < m_gridRight->GetNumberCols(); ++k) + { + exportString += m_gridRight->GetColLabelValue(k); + if (k != m_gridRight->GetNumberCols() - 1) + exportString += ';'; + } + exportString += '\n'; + + //begin work for (int i = 0; i < m_gridLeft->GetNumberRows(); ++i) { for (int k = 0; k < m_gridLeft->GetNumberCols(); ++k) { - exportString+= m_gridLeft->GetCellValue(i, k); - exportString+= ';'; + exportString += m_gridLeft->GetCellValue(i, k); + exportString += ';'; } for (int k = 0; k < m_gridMiddle->GetNumberCols(); ++k) { - exportString+= m_gridMiddle->GetCellValue(i, k); - exportString+= ';'; + exportString += m_gridMiddle->GetCellValue(i, k); + exportString += ';'; } for (int k = 0; k < m_gridRight->GetNumberCols(); ++k) { - exportString+= m_gridRight->GetCellValue(i, k); + exportString += m_gridRight->GetCellValue(i, k); if (k != m_gridRight->GetNumberCols() - 1) exportString+= ';'; } @@ -3252,10 +3316,10 @@ void MainDialog::OnMenuExportFileList(wxCommandEvent& event) void MainDialog::OnMenuBatchJob(wxCommandEvent& event) { //fill batch config structure + xmlAccess::XmlGuiConfig currCfg = getCurrentConfiguration(); //get UP TO DATE config, with updated values for main and additional folders! + xmlAccess::XmlBatchConfig batchCfg; - batchCfg.mainCfg = currentCfg.mainCfg; - batchCfg.directoryPairs = getFolderPairs(); - batchCfg.silent = false; + batchCfg.mainCfg = currCfg.mainCfg; if (currentCfg.ignoreErrors) batchCfg.handleError = xmlAccess::ON_ERROR_IGNORE; @@ -3307,7 +3371,10 @@ void MainDialog::OnMenuAbout(wxCommandEvent& event) void MainDialog::OnMenuQuit(wxCommandEvent& event) { - requestShutdown(); + if (!saveOldConfig()) //notify user about changed settings + return; + + Destroy(); } //######################################################################################################### @@ -3387,7 +3454,7 @@ void MainDialog::SyncPreview::enableSynchronization(bool value) else { synchronizationEnabled = false; - mainDlg_->m_buttonStartSync->SetForegroundColour(wxColor(94, 94, 94)); //grey + mainDlg_->m_buttonStartSync->SetForegroundColour(wxColor(128, 128, 128)); //Some colors seem to have problems with 16Bit color depth, well this one hasn't! mainDlg_->m_buttonStartSync->setBitmapFront(*GlobalResources::getInstance().bitmapSyncDisabled); } } diff --git a/ui/MainDialog.h b/ui/MainDialog.h index b6a49b17..a4c1d897 100644 --- a/ui/MainDialog.h +++ b/ui/MainDialog.h @@ -13,16 +13,18 @@ #include "../library/processXml.h" #include <memory> #include <map> +#include <set> class CompareStatusHandler; class CompareStatus; class MainFolderDragDrop; -class FolderPairPanel; class CustomGrid; class FFSCheckRowsEvent; class FFSSyncDirectionEvent; class IconUpdater; class ManualDeletionHandler; +class FolderPairPanel; + namespace FreeFileSync { @@ -36,6 +38,7 @@ class MainDialog : public MainDialogGenerated friend class CompareStatusHandler; friend class ManualDeletionHandler; friend class MainFolderDragDrop; + friend class FolderPairPanel; //IDs for context menu items enum ContextIDRim //context menu for left and right grids @@ -44,7 +47,7 @@ class MainDialog : public MainDialogGenerated CONTEXT_EXCLUDE_EXT, CONTEXT_EXCLUDE_OBJ, CONTEXT_CLIPBOARD, - CONTEXT_EXPLORER, + CONTEXT_EXTERNAL_APP, CONTEXT_DELETE_FILES, CONTEXT_SYNC_DIR_LEFT, CONTEXT_SYNC_DIR_NONE, @@ -85,39 +88,45 @@ private: //configuration load/save bool readConfigurationFromXml(const wxString& filename, bool programStartup = false); bool writeConfigurationToXml(const wxString& filename); + xmlAccess::XmlGuiConfig getCurrentConfiguration() const; + void setCurrentConfiguration(const xmlAccess::XmlGuiConfig& newGuiCfg); + static const wxString& lastConfigFileName(); xmlAccess::XmlGuiConfig lastConfigurationSaved; //support for: "Save changed configuration?" dialog + //used when saving configuration + wxString currentConfigFileName; void readGlobalSettings(); void writeGlobalSettings(); - void updateViewFilterButtons(); + void initViewFilterButtons(); void updateFilterButton(wxBitmapButton* filterButton, bool isActive); void addFileToCfgHistory(const wxString& filename); void addLeftFolderToHistory(const wxString& leftFolder); void addRightFolderToHistory(const wxString& rightFolder); - void addFolderPair(const std::vector<FreeFileSync::FolderPair>& newPairs, bool addFront = false); - void removeAddFolderPair(const int pos); //keep it an int, allow negative values! + void addFolderPair(const std::vector<FreeFileSync::FolderPairEnh>& newPairs, bool addFront = false); + void removeAddFolderPair(const unsigned int pos); void clearAddFolderPairs(); //main method for putting gridDataView on UI: updates data respecting current view settings void updateGuiGrid(); - void updateGridViewData(); //context menu functions - std::set<int> getSelectedRows(const CustomGrid* grid) const; - std::set<int> getSelectedRows() const; - void setSyncDirManually(const std::set<int>& rowsToSetOnUiTable, const FreeFileSync::SyncDirection dir); - void filterRangeManually(const std::set<int>& rowsToFilterOnUiTable, const int leadingRow); + std::set<unsigned int> getSelectedRows(const CustomGrid* grid) const; + std::set<unsigned int> getSelectedRows() const; + void setSyncDirManually(const std::set<unsigned int>& rowsToSetOnUiTable, const FreeFileSync::SyncDirection dir); + void filterRangeManually(const std::set<unsigned int>& rowsToFilterOnUiTable, const int leadingRow); void copySelectionToClipboard(const CustomGrid* selectedGrid); - void openWithFileManager(const int rowNumber, const bool leftSide); void deleteSelectedFiles(); + void openExternalApplication(unsigned int rowNumber, bool leftSide, const wxString& commandline); + static const int externalAppIDFirst = 1000; //id of first external app item + //work to be done in idle time void OnIdleEvent(wxEvent& event); @@ -133,18 +142,31 @@ private: void onGridRightButtonEvent( wxKeyEvent& event); void onGridMiddleButtonEvent( wxKeyEvent& event); void OnContextRim( wxGridEvent& event); - void OnContextRimSelection( wxCommandEvent& event); void OnContextRimLabelLeft( wxGridEvent& event); void OnContextRimLabelRight( wxGridEvent& event); - void OnContextRimLabelSelection( wxCommandEvent& event); void OnContextMiddle( wxGridEvent& event); - void OnContextMiddleSelection( wxCommandEvent& event); void OnContextMiddleLabel( wxGridEvent& event); - void OnContextMiddleLabelSelection(wxCommandEvent& event); - void OnDirSelected(wxFileDirPickerEvent& event); + //context menu handler methods + void OnContextFilterTemp(wxCommandEvent& event); + void OnContextExcludeExtension(wxCommandEvent& event); + void OnContextExcludeObject(wxCommandEvent& event); + void OnContextCopyClipboard(wxCommandEvent& event); + void OnContextOpenWith(wxCommandEvent& event); + void OnContextDeleteFiles(wxCommandEvent& event); + void OnContextSyncDirLeft(wxCommandEvent& event); + void OnContextSyncDirNone(wxCommandEvent& event); + void OnContextSyncDirRight(wxCommandEvent& event); + void OnContextCustColumnLeft(wxCommandEvent& event); + void OnContextCustColumnRight(wxCommandEvent& event); + void OnContextAutoAdjustLeft(wxCommandEvent& event); + void OnContextAutoAdjustRight(wxCommandEvent& event); + void OnContextIncludeAll(wxCommandEvent& event); + void OnContextExcludeAll(wxCommandEvent& event); + void OnContextComparisonView(wxCommandEvent& event); + void OnContextSyncView(wxCommandEvent& event); - void requestShutdown(); //try to exit application + void OnDirSelected(wxFileDirPickerEvent& event); void OnCheckRows(FFSCheckRowsEvent& event); void OnSetSyncDirection(FFSSyncDirectionEvent& event); @@ -171,10 +193,13 @@ private: void OnSyncDirRight( wxCommandEvent& event); void OnSyncDirNone( wxCommandEvent& event); + void OnNewConfig( wxCommandEvent& event); void OnSaveConfig( wxCommandEvent& event); void OnLoadConfig( wxCommandEvent& event); void OnLoadFromHistory( wxCommandEvent& event); bool trySaveConfig(); //return true if saved successfully + bool saveOldConfig(); //return false on user abort + void loadConfiguration(const wxString& filename); void OnCfgHistoryKeyEvent( wxKeyEvent& event); @@ -202,7 +227,9 @@ private: void OnAddFolderPair( wxCommandEvent& event); void OnRemoveFolderPair( wxCommandEvent& event); - void OnRemoveTopFolderPair( wxCommandEvent& event); + + void updateFilterConfig(bool activateFilter); + void updateSyncConfig(); //menu events void OnMenuGlobalSettings( wxCommandEvent& event); @@ -225,10 +252,7 @@ private: //global settings used by GUI and batch mode xmlAccess::XmlGlobalSettings& globalSettings; - //technical representation of grid-data - FreeFileSync::FolderComparison currentGridData; - - //UI view of currentGridData + //UI view of FolderComparison structure std::auto_ptr<FreeFileSync::GridView> gridDataView; //------------------------------------- @@ -246,9 +270,6 @@ private: int posYNotMaximized; //------------------------------------- - //convenience method to get all folder pairs (unformatted) - std::vector<FreeFileSync::FolderPair> getFolderPairs() const; - //*********************************************** std::auto_ptr<wxMenu> contextMenu; @@ -263,17 +284,6 @@ private: //save the last used config filename history std::vector<wxString> cfgFileNames; - //used when saving configuration - wxString currentConfigFileName; - - //temporal variables used by exclude via context menu - wxString exFilterCandidateExtension; - struct FilterObject - { - wxString relativeName; - FreeFileSync::FileDescrLine::ObjectType type; - }; - std::vector<FilterObject> exFilterCandidateObj; bool cleanedUp; //determines if destructor code was already executed diff --git a/ui/SmallDialogs.cpp b/ui/SmallDialogs.cpp index beb2d975..39f805ba 100644 --- a/ui/SmallDialogs.cpp +++ b/ui/SmallDialogs.cpp @@ -1,5 +1,4 @@ #include "smallDialogs.h" -#include "../shared/globalFunctions.h" #include "../library/resources.h" #include "../algorithm.h" #include "../synchronization.h" @@ -11,6 +10,7 @@ #include "../shared/fileHandling.h" #include "../library/statusHandler.h" #include <wx/wupdlock.h> +#include "../shared/globalFunctions.h" using namespace FreeFileSync; @@ -168,14 +168,14 @@ void FilterDlg::OnDefault(wxCommandEvent& event) } -void FilterDlg::OnOK(wxCommandEvent& event) +void FilterDlg::OnApply(wxCommandEvent& event) { //only if user presses ApplyFilter, he wants the changes to be committed includeFilter = m_textCtrlInclude->GetValue(); excludeFilter = m_textCtrlExclude->GetValue(); //when leaving dialog: filter and redraw grid, if filter is active - EndModal(BUTTON_OKAY); + EndModal(BUTTON_APPLY); } @@ -193,14 +193,12 @@ void FilterDlg::OnClose(wxCloseEvent& event) //######################################################################################## DeleteDialog::DeleteDialog(wxWindow* main, - const FreeFileSync::FolderComparison& folderCmp, - const FreeFileSync::FolderCompRef& rowsOnLeft, - const FreeFileSync::FolderCompRef& rowsOnRight, + const std::vector<FileSystemObject*>& rowsOnLeft, + const std::vector<FileSystemObject*>& rowsOnRight, bool& deleteOnBothSides, bool& useRecycleBin, int& totalDeleteCount) : DeleteDlgGenerated(main), - m_folderCmp(folderCmp), rowsToDeleteOnLeft(rowsOnLeft), rowsToDeleteOnRight(rowsOnRight), m_deleteOnBothSides(deleteOnBothSides), @@ -217,41 +215,25 @@ DeleteDialog::DeleteDialog(wxWindow* main, void DeleteDialog::updateTexts() { - wxString headerText; if (m_checkBoxUseRecycler->GetValue()) { - headerText = _("Do you really want to move the following objects(s) to the Recycle Bin?"); + m_staticTextHeader->SetLabel(_("Do you really want to move the following objects(s) to the Recycle Bin?")); m_bitmap12->SetBitmap(*GlobalResources::getInstance().bitmapRecycler); } else { - headerText = _("Do you really want to delete the following objects(s)?"); + m_staticTextHeader->SetLabel(_("Do you really want to delete the following objects(s)?")); m_bitmap12->SetBitmap(*GlobalResources::getInstance().bitmapDeleteFile); } - m_staticTextHeader->SetLabel(headerText); - assert(m_folderCmp.size() == rowsToDeleteOnLeft.size()); - assert(m_folderCmp.size() == rowsToDeleteOnRight.size()); - - wxString filesToDelete; - totalDelCount = 0; - for (FolderComparison::const_iterator j = m_folderCmp.begin(); j != m_folderCmp.end(); ++j) - { - const FileComparison& fileCmp = j->fileCmp; + const std::pair<wxString, int> delInfo = FreeFileSync::deleteFromGridAndHDPreview( + rowsToDeleteOnLeft, + rowsToDeleteOnRight, + m_checkBoxDeleteBothSides->GetValue()); - const int pairIndex = j - m_folderCmp.begin(); - if ( pairIndex < int(rowsToDeleteOnLeft.size()) && //just to be sure - pairIndex < int(rowsToDeleteOnRight.size())) - { - const std::pair<wxString, int> delInfo = FreeFileSync::deleteFromGridAndHDPreview(fileCmp, - rowsToDeleteOnLeft[pairIndex], - rowsToDeleteOnRight[pairIndex], - m_checkBoxDeleteBothSides->GetValue()); + const wxString filesToDelete = delInfo.first; + totalDelCount = delInfo.second; - filesToDelete += delInfo.first; - totalDelCount += delInfo.second; - } - } m_textCtrlMessage->SetValue(filesToDelete); Layout(); @@ -636,10 +618,13 @@ void SyncPreviewDlg::OnStartSync(wxCommandEvent& event) //######################################################################################## -CompareCfgDialog::CompareCfgDialog(wxWindow* parentWindow, CompareVariant& cmpVar) : +CompareCfgDialog::CompareCfgDialog(wxWindow* parentWindow, const wxPoint& position, CompareVariant& cmpVar) : CmpCfgDlgGenerated(parentWindow), m_cmpVar(cmpVar) { + //move dialog up so that compare-config button and first config-variant are on same level + Move(wxPoint(position.x, std::max(0, position.y - (m_buttonTimeSize->GetScreenPosition() - GetScreenPosition()).y))); + m_bpButtonHelp->SetBitmapLabel(*GlobalResources::getInstance().bitmapHelp); switch (cmpVar) @@ -695,21 +680,22 @@ GlobalSettingsDlg::GlobalSettingsDlg(wxWindow* window, xmlAccess::XmlGlobalSetti settings(globalSettings) { m_bitmapSettings->SetBitmap(*GlobalResources::getInstance().bitmapSettings); - m_buttonResetWarnings->setBitmapFront(*GlobalResources::getInstance().bitmapWarningSmall, 5); + m_buttonResetDialogs->setBitmapFront(*GlobalResources::getInstance().bitmapWarningSmall, 5); + m_bpButtonAddRow->SetBitmapLabel(*GlobalResources::getInstance().bitmapAddFolderPair); + m_bpButtonRemoveRow->SetBitmapLabel(*GlobalResources::getInstance().bitmapRemoveFolderPair); - m_spinCtrlFileTimeTolerance->SetValue(globalSettings.fileTimeTolerance); m_checkBoxIgnoreOneHour->SetValue(globalSettings.ignoreOneHourDiff); - m_textCtrlCommand->SetValue(globalSettings.gui.commandLineFileManager); + set(globalSettings.gui.externelApplications); - const wxString toolTip = wxString(_("This commandline will be executed on each doubleclick. The following macros are available:")) + wxT("\n\n") + + const wxString toolTip = wxString(_("Integrate external applications into context menu. The following macros are available:")) + wxT("\n\n") + wxT("%name \t") + _("- full file or directory name") + wxT("\n") + - wxT("%dir \t") + _("- directory part only") + wxT("\n") + - wxT("%nameCo \t") + _("- sibling of %name") + wxT("\n") + - wxT("%dirCo \t") + _("- sibling of %dir"); + wxT("%dir \t") + _("- directory part only") + wxT("\n") + + wxT("%nameCo \t") + _("- Other side's counterpart to %name") + wxT("\n") + + wxT("%dirCo \t") + _("- Other side's counterpart to %dir"); - m_staticTextCommand->SetToolTip(toolTip); - m_textCtrlCommand->SetToolTip(toolTip); + m_gridCustomCommand->GetGridWindow()->SetToolTip(toolTip); + m_gridCustomCommand->GetGridColLabelWindow()->SetToolTip(toolTip); m_buttonOkay->SetFocus(); @@ -720,32 +706,29 @@ GlobalSettingsDlg::GlobalSettingsDlg(wxWindow* window, xmlAccess::XmlGlobalSetti void GlobalSettingsDlg::OnOkay(wxCommandEvent& event) { //write global settings only when okay-button is pressed! - settings.fileTimeTolerance = m_spinCtrlFileTimeTolerance->GetValue(); settings.ignoreOneHourDiff = m_checkBoxIgnoreOneHour->GetValue(); - settings.gui.commandLineFileManager = m_textCtrlCommand->GetValue(); + settings.gui.externelApplications = getExtApp(); EndModal(BUTTON_OKAY); } -void GlobalSettingsDlg::OnResetWarnings(wxCommandEvent& event) +void GlobalSettingsDlg::OnResetDialogs(wxCommandEvent& event) { - wxMessageDialog* messageDlg = new wxMessageDialog(this, _("Reset all warning messages?"), _("Warning") , wxOK | wxCANCEL); + wxMessageDialog* messageDlg = new wxMessageDialog(this, _("Re-enable all hidden dialogs?"), _("Warning") , wxOK | wxCANCEL); if (messageDlg->ShowModal() == wxID_OK) - settings.warnings.resetWarnings(); + settings.optDialogs.resetDialogs(); } void GlobalSettingsDlg::OnDefault(wxCommandEvent& event) { - m_spinCtrlFileTimeTolerance->SetValue(2); - m_checkBoxIgnoreOneHour->SetValue(true); -#ifdef FFS_WIN - m_textCtrlCommand->SetValue(wxT("explorer /select, %name")); -#elif defined FFS_LINUX - m_textCtrlCommand->SetValue(wxT("konqueror \"%dir\"")); -#endif + xmlAccess::XmlGlobalSettings defaultCfg; + + m_checkBoxIgnoreOneHour->SetValue(defaultCfg.ignoreOneHourDiff); + + set(defaultCfg.gui.externelApplications); } @@ -761,6 +744,54 @@ void GlobalSettingsDlg::OnClose(wxCloseEvent& event) } +void GlobalSettingsDlg::set(const xmlAccess::ExternalApps& extApp) +{ + const int rowCount = m_gridCustomCommand->GetNumberRows(); + if (rowCount > 0) + m_gridCustomCommand->DeleteRows(0, rowCount); + + m_gridCustomCommand->AppendRows(extApp.size()); + for (xmlAccess::ExternalApps::const_iterator i = extApp.begin(); i != extApp.end(); ++i) + { + const int row = i - extApp.begin(); + m_gridCustomCommand->SetCellValue(row, 0, i->first); //description + m_gridCustomCommand->SetCellValue(row, 1, i->second); //commandline + } + Fit(); +} + + +xmlAccess::ExternalApps GlobalSettingsDlg::getExtApp() +{ + xmlAccess::ExternalApps output; + for (int i = 0; i < m_gridCustomCommand->GetNumberRows(); ++i) + output.push_back( + std::make_pair(m_gridCustomCommand->GetCellValue(i, 0), //description + m_gridCustomCommand->GetCellValue(i, 1))); //commandline + return output; +} + + +void GlobalSettingsDlg::OnAddRow(wxCommandEvent& event) +{ + wxWindowUpdateLocker dummy(this); //avoid display distortion + + m_gridCustomCommand->AppendRows(); + Fit(); +} + + +void GlobalSettingsDlg::OnRemoveRow(wxCommandEvent& event) +{ + if (m_gridCustomCommand->GetNumberRows() > 0) + { + wxWindowUpdateLocker dummy(this); //avoid display distortion + + m_gridCustomCommand->DeleteRows(m_gridCustomCommand->GetNumberRows() - 1); + Fit(); + } +} + //######################################################################################## CompareStatus::CompareStatus(wxWindow* parentWindow) : @@ -1058,7 +1089,7 @@ void SyncStatus::updateStatusDialogNow() } //time elapsed - const wxString timeElapsedTmp = (wxTimeSpan::Milliseconds(timeElapsed.Time())).Format(); + const wxString timeElapsedTmp = wxTimeSpan::Milliseconds(timeElapsed.Time()).Format(); if (m_staticTextTimeElapsed->GetLabel() != timeElapsedTmp && (screenChanged = true)) //avoid screen flicker m_staticTextTimeElapsed->SetLabel(timeElapsedTmp); diff --git a/ui/SmallDialogs.h b/ui/SmallDialogs.h index f5ea53df..6e9d0414 100644 --- a/ui/SmallDialogs.h +++ b/ui/SmallDialogs.h @@ -1,7 +1,7 @@ #ifndef SMALLDIALOGS_H_INCLUDED #define SMALLDIALOGS_H_INCLUDED -#include "../structures.h" +#include "../fileHierarchy.h" #include "../library/processXml.h" #include "guiGenerated.h" #include <wx/stopwatch.h> @@ -48,13 +48,13 @@ public: enum { - BUTTON_OKAY + BUTTON_APPLY = 1 }; private: void OnHelp(wxCommandEvent& event); void OnDefault(wxCommandEvent& event); - void OnOK(wxCommandEvent& event); + void OnApply(wxCommandEvent& event); void OnCancel(wxCommandEvent& event); void OnClose(wxCloseEvent& event); @@ -67,9 +67,8 @@ class DeleteDialog : public DeleteDlgGenerated { public: DeleteDialog(wxWindow* main, - const FreeFileSync::FolderComparison& folderCmp, - const FreeFileSync::FolderCompRef& rowsOnLeft, - const FreeFileSync::FolderCompRef& rowsOnRight, + const std::vector<FreeFileSync::FileSystemObject*>& rowsOnLeft, + const std::vector<FreeFileSync::FileSystemObject*>& rowsOnRight, bool& deleteOnBothSides, bool& useRecycleBin, int& totalDeleteCount); @@ -78,7 +77,7 @@ public: enum { - BUTTON_OKAY, + BUTTON_OKAY = 1, BUTTON_CANCEL }; @@ -91,9 +90,8 @@ private: void updateTexts(); - const FreeFileSync::FolderComparison& m_folderCmp; - const FreeFileSync::FolderCompRef& rowsToDeleteOnLeft; - const FreeFileSync::FolderCompRef& rowsToDeleteOnRight; + const std::vector<FreeFileSync::FileSystemObject*>& rowsToDeleteOnLeft; + const std::vector<FreeFileSync::FileSystemObject*>& rowsToDeleteOnRight; bool& m_deleteOnBothSides; bool& m_useRecycleBin; int& totalDelCount; @@ -218,7 +216,7 @@ private: class CompareCfgDialog : public CmpCfgDlgGenerated { public: - CompareCfgDialog(wxWindow* parentWindow, FreeFileSync::CompareVariant& cmpVar); + CompareCfgDialog(wxWindow* parentWindow, const wxPoint& position, FreeFileSync::CompareVariant& cmpVar); enum { @@ -249,10 +247,15 @@ public: private: void OnOkay(wxCommandEvent& event); - void OnResetWarnings(wxCommandEvent& event); + void OnResetDialogs(wxCommandEvent& event); void OnDefault(wxCommandEvent& event); void OnCancel(wxCommandEvent& event); void OnClose(wxCloseEvent& event); + void OnAddRow(wxCommandEvent& event); + void OnRemoveRow(wxCommandEvent& event); + + void set(const xmlAccess::ExternalApps& extApp); + xmlAccess::ExternalApps getExtApp(); xmlAccess::XmlGlobalSettings& settings; }; diff --git a/ui/SyncDialog.cpp b/ui/SyncDialog.cpp index 3172a548..8f5066c5 100644 --- a/ui/SyncDialog.cpp +++ b/ui/SyncDialog.cpp @@ -1,5 +1,5 @@ #include "syncDialog.h" -#include "../shared/globalFunctions.h" +#include "../shared/systemConstants.h" #include "../library/resources.h" #include <wx/msgdlg.h> #include "../shared/customButton.h" @@ -10,31 +10,40 @@ #include "../shared/fileHandling.h" #include "../shared/xmlBase.h" #include <wx/wupdlock.h> +#include "folderPair.h" using namespace FreeFileSync; SyncCfgDialog::SyncCfgDialog(wxWindow* window, - const FolderComparison& folderCmpRef, - MainConfiguration& config, - bool& ignoreErrors) : + const CompareVariant compareVar, + SyncConfiguration& syncConfiguration, + DeletionPolicy& handleDeletion, + wxString& customDeletionDirectory, + bool* ignoreErrors) : SyncCfgDlgGenerated(window), - folderCmp(folderCmpRef), - cfg(config), - m_ignoreErrors(ignoreErrors), + cmpVariant(compareVar), + localSyncConfiguration(syncConfiguration), //make working copy of syncConfiguration + refSyncConfiguration(syncConfiguration), + refHandleDeletion(handleDeletion), + refCustomDeletionDirectory(customDeletionDirectory), + refIgnoreErrors(ignoreErrors), dragDropCustomDelFolder(new DragDropOnDlg(m_panelCustomDeletionDir, m_dirPickerCustomDelFolder, m_textCtrlCustomDelFolder)) { - //make working copy of mainDialog.cfg.syncConfiguration and recycler setting - localSyncConfiguration = config.syncConfiguration; - - setDeletionHandling(cfg.handleDeletion); - m_textCtrlCustomDelFolder->SetValue(cfg.customDeletionDirectory); + setDeletionHandling(handleDeletion); + m_textCtrlCustomDelFolder->SetValue(customDeletionDirectory); //error handling - setErrorHandling(m_ignoreErrors); + if (ignoreErrors) + setErrorHandling(*ignoreErrors); + else + { + sbSizerErrorHandling->Show(false); + Layout(); + } //set sync config icons - updateConfigIcons(cfg.compareVar, localSyncConfiguration); + updateConfigIcons(cmpVariant, localSyncConfiguration); //set icons for this dialog m_bitmapLeftOnly->SetBitmap(*GlobalResources::getInstance().bitmapLeftOnly); @@ -42,6 +51,7 @@ SyncCfgDialog::SyncCfgDialog(wxWindow* window, m_bitmapLeftNewer->SetBitmap(*GlobalResources::getInstance().bitmapLeftNewer); m_bitmapRightNewer->SetBitmap(*GlobalResources::getInstance().bitmapRightNewer); m_bitmapDifferent->SetBitmap(*GlobalResources::getInstance().bitmapDifferent); + m_bitmapConflict->SetBitmap(*GlobalResources::getInstance().bitmapConflictGrey); bSizer201->Layout(); //wxButtonWithImage size might have changed @@ -81,11 +91,13 @@ void SyncCfgDialog::updateConfigIcons(const FreeFileSync::CompareVariant cmpVar, m_bpButtonLeftNewer, m_bpButtonRightNewer, m_bpButtonDifferent, + m_bpButtonConflict, m_bitmapLeftOnly, m_bitmapRightOnly, m_bitmapLeftNewer, m_bitmapRightNewer, - m_bitmapDifferent); + m_bitmapDifferent, + m_bitmapConflict); } @@ -96,11 +108,13 @@ void SyncCfgDialog::updateConfigIcons(const CompareVariant compareVar, wxBitmapButton* buttonLeftNewer, wxBitmapButton* buttonRightNewer, wxBitmapButton* buttonDifferent, + wxBitmapButton* buttonConflict, wxStaticBitmap* bitmapLeftOnly, wxStaticBitmap* bitmapRightOnly, wxStaticBitmap* bitmapLeftNewer, wxStaticBitmap* bitmapRightNewer, - wxStaticBitmap* bitmapDifferent) + wxStaticBitmap* bitmapDifferent, + wxStaticBitmap* bitmapConflict) { //display only relevant sync options switch (compareVar) @@ -111,12 +125,14 @@ void SyncCfgDialog::updateConfigIcons(const CompareVariant compareVar, buttonLeftNewer ->Show(); buttonRightNewer->Show(); buttonDifferent ->Hide(); + buttonConflict ->Show(); bitmapLeftOnly ->Show(); bitmapRightOnly ->Show(); bitmapLeftNewer ->Show(); bitmapRightNewer->Show(); bitmapDifferent ->Hide(); + bitmapConflict ->Show(); break; case CMP_BY_CONTENT: @@ -125,12 +141,14 @@ void SyncCfgDialog::updateConfigIcons(const CompareVariant compareVar, buttonLeftNewer ->Hide(); buttonRightNewer->Hide(); buttonDifferent ->Show(); + buttonConflict ->Show(); bitmapLeftOnly ->Show(); bitmapRightOnly ->Show(); bitmapLeftNewer ->Hide(); bitmapRightNewer->Hide(); bitmapDifferent ->Show(); + bitmapConflict ->Show(); break; } @@ -139,15 +157,15 @@ void SyncCfgDialog::updateConfigIcons(const CompareVariant compareVar, { case SYNC_DIR_RIGHT: buttonLeftOnly->SetBitmapLabel(*GlobalResources::getInstance().bitmapArrowRightCr); - buttonLeftOnly->SetToolTip(_("Copy from left to right")); + buttonLeftOnly->SetToolTip(getDescription(SO_CREATE_NEW_RIGHT)); break; case SYNC_DIR_LEFT: buttonLeftOnly->SetBitmapLabel(*GlobalResources::getInstance().bitmapDeleteLeft); - buttonLeftOnly->SetToolTip(_("Delete files/folders existing on left side only")); + buttonLeftOnly->SetToolTip(getDescription(SO_DELETE_LEFT)); break; case SYNC_DIR_NONE: buttonLeftOnly->SetBitmapLabel(*GlobalResources::getInstance().bitmapArrowNone); - buttonLeftOnly->SetToolTip(_("Do nothing")); + buttonLeftOnly->SetToolTip(getDescription(SO_DO_NOTHING)); break; } @@ -155,15 +173,15 @@ void SyncCfgDialog::updateConfigIcons(const CompareVariant compareVar, { case SYNC_DIR_RIGHT: buttonRightOnly->SetBitmapLabel(*GlobalResources::getInstance().bitmapDeleteRight); - buttonRightOnly->SetToolTip(_("Delete files/folders existing on right side only")); + buttonRightOnly->SetToolTip(getDescription(SO_DELETE_RIGHT)); break; case SYNC_DIR_LEFT: buttonRightOnly->SetBitmapLabel(*GlobalResources::getInstance().bitmapArrowLeftCr); - buttonRightOnly->SetToolTip(_("Copy from right to left")); + buttonRightOnly->SetToolTip(getDescription(SO_CREATE_NEW_LEFT)); break; case SYNC_DIR_NONE: buttonRightOnly->SetBitmapLabel(*GlobalResources::getInstance().bitmapArrowNone); - buttonRightOnly->SetToolTip(_("Do nothing")); + buttonRightOnly->SetToolTip(getDescription(SO_DO_NOTHING)); break; } @@ -171,15 +189,15 @@ void SyncCfgDialog::updateConfigIcons(const CompareVariant compareVar, { case SYNC_DIR_RIGHT: buttonLeftNewer->SetBitmapLabel(*GlobalResources::getInstance().bitmapArrowRight); - buttonLeftNewer->SetToolTip(_("Copy from left to right overwriting")); + buttonLeftNewer->SetToolTip(getDescription(SO_OVERWRITE_RIGHT)); break; case SYNC_DIR_LEFT: buttonLeftNewer->SetBitmapLabel(*GlobalResources::getInstance().bitmapArrowLeft); - buttonLeftNewer->SetToolTip(_("Copy from right to left overwriting")); + buttonLeftNewer->SetToolTip(getDescription(SO_OVERWRITE_LEFT)); break; case SYNC_DIR_NONE: buttonLeftNewer->SetBitmapLabel(*GlobalResources::getInstance().bitmapArrowNone); - buttonLeftNewer->SetToolTip(_("Do nothing")); + buttonLeftNewer->SetToolTip(getDescription(SO_DO_NOTHING)); break; } @@ -187,15 +205,15 @@ void SyncCfgDialog::updateConfigIcons(const CompareVariant compareVar, { case SYNC_DIR_RIGHT: buttonRightNewer->SetBitmapLabel(*GlobalResources::getInstance().bitmapArrowRight); - buttonRightNewer->SetToolTip(_("Copy from left to right overwriting")); + buttonRightNewer->SetToolTip(getDescription(SO_OVERWRITE_RIGHT)); break; case SYNC_DIR_LEFT: buttonRightNewer->SetBitmapLabel(*GlobalResources::getInstance().bitmapArrowLeft); - buttonRightNewer->SetToolTip(_("Copy from right to left overwriting")); + buttonRightNewer->SetToolTip(getDescription(SO_OVERWRITE_LEFT)); break; case SYNC_DIR_NONE: buttonRightNewer->SetBitmapLabel(*GlobalResources::getInstance().bitmapArrowNone); - buttonRightNewer->SetToolTip(_("Do nothing")); + buttonRightNewer->SetToolTip(getDescription(SO_DO_NOTHING)); break; } @@ -203,15 +221,31 @@ void SyncCfgDialog::updateConfigIcons(const CompareVariant compareVar, { case SYNC_DIR_RIGHT: buttonDifferent->SetBitmapLabel(*GlobalResources::getInstance().bitmapArrowRight); - buttonDifferent->SetToolTip(_("Copy from left to right overwriting")); + buttonDifferent->SetToolTip(getDescription(SO_OVERWRITE_RIGHT)); break; case SYNC_DIR_LEFT: buttonDifferent->SetBitmapLabel(*GlobalResources::getInstance().bitmapArrowLeft); - buttonDifferent->SetToolTip(_("Copy from right to left overwriting")); + buttonDifferent->SetToolTip(getDescription(SO_OVERWRITE_LEFT)); break; case SYNC_DIR_NONE: buttonDifferent->SetBitmapLabel(*GlobalResources::getInstance().bitmapArrowNone); - buttonDifferent->SetToolTip(_("Do nothing")); + buttonDifferent->SetToolTip(getDescription(SO_DO_NOTHING)); + break; + } + + switch (syncConfig.conflict) + { + case SYNC_DIR_RIGHT: + buttonConflict->SetBitmapLabel(*GlobalResources::getInstance().bitmapArrowRight); + buttonConflict->SetToolTip(getDescription(SO_OVERWRITE_RIGHT)); + break; + case SYNC_DIR_LEFT: + buttonConflict->SetBitmapLabel(*GlobalResources::getInstance().bitmapArrowLeft); + buttonConflict->SetToolTip(getDescription(SO_OVERWRITE_LEFT)); + break; + case SYNC_DIR_NONE: + buttonConflict->SetBitmapLabel(*GlobalResources::getInstance().bitmapConflict); + buttonConflict->SetToolTip(_("Leave as unresolved conflict")); break; } } @@ -232,13 +266,13 @@ void SyncCfgDialog::OnCancel(wxCommandEvent& event) void SyncCfgDialog::OnApply(wxCommandEvent& event) { //write configuration to main dialog - cfg.syncConfiguration = localSyncConfiguration; - cfg.handleDeletion = getDeletionHandling(); - cfg.customDeletionDirectory = m_textCtrlCustomDelFolder->GetValue(); + refSyncConfiguration = localSyncConfiguration; + refHandleDeletion = getDeletionHandling(); + refCustomDeletionDirectory = m_textCtrlCustomDelFolder->GetValue(); + if (refIgnoreErrors) + *refIgnoreErrors = getErrorHandling(); - m_ignoreErrors = getErrorHandling(); - - EndModal(BUTTON_OKAY); + EndModal(BUTTON_APPLY); } @@ -308,7 +342,7 @@ void updateToolTipDeletionHandling(wxChoice* choiceHandleError, wxPanel* customD break; case FreeFileSync::MOVE_TO_CUSTOM_DIRECTORY: - choiceHandleError->SetToolTip(_("Move files to a custom directory.")); + choiceHandleError->SetToolTip(_("Move files to a user-defined directory.")); customDir->Enable(); break; } @@ -337,7 +371,7 @@ void SyncCfgDialog::setDeletionHandling(FreeFileSync::DeletionPolicy newValue) m_choiceHandleDeletion->Clear(); m_choiceHandleDeletion->Append(_("Delete permanently")); m_choiceHandleDeletion->Append(_("Use Recycle Bin")); - m_choiceHandleDeletion->Append(_("Move to custom directory")); + m_choiceHandleDeletion->Append(_("User-defined directory")); switch (newValue) { @@ -366,7 +400,7 @@ void SyncCfgDialog::OnSyncLeftToRight(wxCommandEvent& event) { localSyncConfiguration.setVariant(SyncConfiguration::MIRROR); - updateConfigIcons(cfg.compareVar, localSyncConfiguration); + updateConfigIcons(cmpVariant, localSyncConfiguration); //if event is triggered by button m_radioBtn1->SetValue(true); @@ -377,7 +411,7 @@ void SyncCfgDialog::OnSyncUpdate(wxCommandEvent& event) { localSyncConfiguration.setVariant(SyncConfiguration::UPDATE); - updateConfigIcons(cfg.compareVar, localSyncConfiguration); + updateConfigIcons(cmpVariant, localSyncConfiguration); //if event is triggered by button m_radioBtnUpdate->SetValue(true); @@ -388,7 +422,7 @@ void SyncCfgDialog::OnSyncBothSides(wxCommandEvent& event) { localSyncConfiguration.setVariant(SyncConfiguration::TWOWAY); - updateConfigIcons(cfg.compareVar, localSyncConfiguration); + updateConfigIcons(cmpVariant, localSyncConfiguration); //if event is triggered by button m_radioBtn2->SetValue(true); @@ -415,7 +449,7 @@ void toggleSyncDirection(SyncDirectionCfg& current) void SyncCfgDialog::OnExLeftSideOnly( wxCommandEvent& event ) { toggleSyncDirection(localSyncConfiguration.exLeftSideOnly); - updateConfigIcons(cfg.compareVar, localSyncConfiguration); + updateConfigIcons(cmpVariant, localSyncConfiguration); //set custom config button m_radioBtn3->SetValue(true); } @@ -424,7 +458,7 @@ void SyncCfgDialog::OnExLeftSideOnly( wxCommandEvent& event ) void SyncCfgDialog::OnExRightSideOnly( wxCommandEvent& event ) { toggleSyncDirection(localSyncConfiguration.exRightSideOnly); - updateConfigIcons(cfg.compareVar, localSyncConfiguration); + updateConfigIcons(cmpVariant, localSyncConfiguration); //set custom config button m_radioBtn3->SetValue(true); } @@ -433,7 +467,7 @@ void SyncCfgDialog::OnExRightSideOnly( wxCommandEvent& event ) void SyncCfgDialog::OnLeftNewer( wxCommandEvent& event ) { toggleSyncDirection(localSyncConfiguration.leftNewer); - updateConfigIcons(cfg.compareVar, localSyncConfiguration); + updateConfigIcons(cmpVariant, localSyncConfiguration); //set custom config button m_radioBtn3->SetValue(true); } @@ -442,7 +476,7 @@ void SyncCfgDialog::OnLeftNewer( wxCommandEvent& event ) void SyncCfgDialog::OnRightNewer( wxCommandEvent& event ) { toggleSyncDirection(localSyncConfiguration.rightNewer); - updateConfigIcons(cfg.compareVar, localSyncConfiguration); + updateConfigIcons(cmpVariant, localSyncConfiguration); //set custom config button m_radioBtn3->SetValue(true); } @@ -451,27 +485,54 @@ void SyncCfgDialog::OnRightNewer( wxCommandEvent& event ) void SyncCfgDialog::OnDifferent( wxCommandEvent& event ) { toggleSyncDirection(localSyncConfiguration.different); - updateConfigIcons(cfg.compareVar, localSyncConfiguration); + updateConfigIcons(cmpVariant, localSyncConfiguration); + //set custom config button + m_radioBtn3->SetValue(true); +} + + +void SyncCfgDialog::OnConflict(wxCommandEvent& event) +{ + toggleSyncDirection(localSyncConfiguration.conflict); + updateConfigIcons(cmpVariant, localSyncConfiguration); //set custom config button m_radioBtn3->SetValue(true); } //################################################################################################################################### -class BatchFolderPairPanel : public BatchFolderPairGenerated +typedef FreeFileSync::FolderPairPanelBasic<BatchFolderPairGenerated> FolderPairParent; + +class BatchFolderPairPanel : public FolderPairParent { public: - BatchFolderPairPanel(wxWindow* parent) : - BatchFolderPairGenerated(parent), - dragDropOnLeft(m_panelLeft, m_dirPickerLeft, m_directoryLeft), - dragDropOnRight(m_panelRight, m_dirPickerRight, m_directoryRight) {} + BatchFolderPairPanel(wxWindow* parent, BatchDialog* batchDialog) : + FolderPairParent(parent), + batchDlg(batchDialog) {} private: - //support for drag and drop - DragDropOnDlg dragDropOnLeft; - DragDropOnDlg dragDropOnRight; -}; + virtual wxWindow* getParentWindow() + { + return batchDlg; + } + + virtual MainConfiguration getMainConfig() const + { + return batchDlg->getCurrentConfiguration().mainCfg; + } + virtual void OnAltFilterCfgChange(bool defaultValueSet) + { + if (!defaultValueSet) + { + //activate filter + batchDlg->m_checkBoxFilter->SetValue(true); + batchDlg->updateVisibleTabs(); + } + } + + BatchDialog* batchDlg; +}; //################################################################################################################################### @@ -538,12 +599,12 @@ void BatchDialog::init() //set icons for this dialog m_bpButtonAddPair->SetBitmapLabel(*GlobalResources::getInstance().bitmapAddFolderPair); - m_bpButtonRemoveTopPair->SetBitmapLabel(*GlobalResources::getInstance().bitmapRemoveFolderPair); m_bitmapLeftOnly->SetBitmap(*GlobalResources::getInstance().bitmapLeftOnly); m_bitmapRightOnly->SetBitmap(*GlobalResources::getInstance().bitmapRightOnly); m_bitmapLeftNewer->SetBitmap(*GlobalResources::getInstance().bitmapLeftNewer); m_bitmapRightNewer->SetBitmap(*GlobalResources::getInstance().bitmapRightNewer); m_bitmapDifferent->SetBitmap(*GlobalResources::getInstance().bitmapDifferent); + m_bitmapConflict->SetBitmap(*GlobalResources::getInstance().bitmapConflictGrey); m_bitmap8->SetBitmap(*GlobalResources::getInstance().bitmapInclude); m_bitmap9->SetBitmap(*GlobalResources::getInstance().bitmapExclude); m_bitmap27->SetBitmap(*GlobalResources::getInstance().bitmapBatch); @@ -553,7 +614,7 @@ void BatchDialog::init() //------------------- error handling -------------------------- -xmlAccess::OnError BatchDialog::getSelectionHandleError() +xmlAccess::OnError BatchDialog::getSelectionHandleError() const { switch (m_choiceHandleError->GetSelection()) { @@ -612,7 +673,7 @@ void BatchDialog::OnExLeftSideOnly(wxCommandEvent& event) //------------------- deletion handling -------------------------- -FreeFileSync::DeletionPolicy BatchDialog::getDeletionHandling() +FreeFileSync::DeletionPolicy BatchDialog::getDeletionHandling() const { switch (m_choiceHandleDeletion->GetSelection()) { @@ -634,7 +695,7 @@ void BatchDialog::setDeletionHandling(FreeFileSync::DeletionPolicy newValue) m_choiceHandleDeletion->Clear(); m_choiceHandleDeletion->Append(_("Delete permanently")); m_choiceHandleDeletion->Append(_("Use Recycle Bin")); - m_choiceHandleDeletion->Append(_("Move to custom directory")); + m_choiceHandleDeletion->Append(_("User-defined directory")); switch (newValue) { @@ -688,6 +749,13 @@ void BatchDialog::OnDifferent(wxCommandEvent& event) } +void BatchDialog::OnConflict(wxCommandEvent& event) +{ + toggleSyncDirection(localSyncConfiguration.conflict); + updateConfigIcons(getCurrentCompareVar(), localSyncConfiguration); +} + + void BatchDialog::OnCheckFilter(wxCommandEvent& event) { updateVisibleTabs(); @@ -737,7 +805,7 @@ void BatchDialog::showNotebookpage(wxWindow* page, const wxString& pageName, boo } -CompareVariant BatchDialog::getCurrentCompareVar() +CompareVariant BatchDialog::getCurrentCompareVar() const { if (m_radioBtnSizeDate->GetValue()) return CMP_BY_TIME_SIZE; @@ -760,11 +828,13 @@ void BatchDialog::updateConfigIcons(const FreeFileSync::CompareVariant cmpVar, c m_bpButtonLeftNewer, m_bpButtonRightNewer, m_bpButtonDifferent, + m_bpButtonConflict, m_bitmapLeftOnly, m_bitmapRightOnly, m_bitmapLeftNewer, m_bitmapRightNewer, - m_bitmapDifferent); + m_bitmapDifferent, + m_bitmapConflict); } @@ -824,7 +894,18 @@ void BatchDialog::OnLoadBatchJob(wxCommandEvent& event) } -bool BatchDialog::saveBatchFile(const wxString& filename) + +inline +FolderPairEnh getEnahncedPair(const BatchFolderPairPanel* panel) +{ + return FolderPairEnh(panel->m_directoryLeft->GetValue().c_str(), + panel->m_directoryRight->GetValue().c_str(), + panel->altSyncConfig, + panel->altFilter); +} + + +xmlAccess::XmlBatchConfig BatchDialog::getCurrentConfiguration() const { xmlAccess::XmlBatchConfig batchCfg; @@ -837,14 +918,29 @@ bool BatchDialog::saveBatchFile(const wxString& filename) batchCfg.mainCfg.handleDeletion = getDeletionHandling(); batchCfg.mainCfg.customDeletionDirectory = m_textCtrlCustomDelFolder->GetValue(); - batchCfg.handleError = getSelectionHandleError(); + //main pair + batchCfg.mainCfg.mainFolderPair.leftDirectory = m_directoryLeft->GetValue().c_str(); + batchCfg.mainCfg.mainFolderPair.rightDirectory = m_directoryRight->GetValue().c_str(); + + //add additional pairs + batchCfg.mainCfg.additionalPairs.clear(); + std::transform(additionalFolderPairs.begin(), additionalFolderPairs.end(), + std::back_inserter(batchCfg.mainCfg.additionalPairs), getEnahncedPair); - batchCfg.directoryPairs = getFolderPairs(); //load structure with batch settings "batchCfg" - batchCfg.silent = m_checkBoxSilent->GetValue(); + batchCfg.silent = m_checkBoxSilent->GetValue(); + batchCfg.handleError = getSelectionHandleError(); batchCfg.logFileDirectory = m_textCtrlLogfileDir->GetValue(); + return batchCfg; +} + + +bool BatchDialog::saveBatchFile(const wxString& filename) +{ + const xmlAccess::XmlBatchConfig batchCfg = getCurrentConfiguration(); + //write config to XML try { @@ -919,26 +1015,16 @@ void BatchDialog::loadBatchCfg(const xmlAccess::XmlBatchConfig& batchCfg) m_checkBoxSilent->SetValue(batchCfg.silent); m_textCtrlLogfileDir->SetValue(batchCfg.logFileDirectory); - //remove existing folder pairs - FreeFileSync::setDirectoryName(wxEmptyString, m_directoryLeft, m_dirPickerLeft); - FreeFileSync::setDirectoryName(wxEmptyString, m_directoryRight, m_dirPickerRight); - clearAddFolderPairs(); - //add folder pairs - if (batchCfg.directoryPairs.size() > 0) - { - //set main folder pair - std::vector<FolderPair>::const_iterator main = batchCfg.directoryPairs.begin(); + //set main folder pair + FreeFileSync::setDirectoryName(batchCfg.mainCfg.mainFolderPair.leftDirectory.c_str(), m_directoryLeft, m_dirPickerLeft); + FreeFileSync::setDirectoryName(batchCfg.mainCfg.mainFolderPair.rightDirectory.c_str(), m_directoryRight, m_dirPickerRight); - FreeFileSync::setDirectoryName(main->leftDirectory.c_str(), m_directoryLeft, m_dirPickerLeft); - FreeFileSync::setDirectoryName(main->rightDirectory.c_str(), m_directoryRight, m_dirPickerRight); + //remove existing additional folder pairs + clearAddFolderPairs(); - //set additional pairs - std::vector<FolderPair> additionalPairs; //don't modify batchCfg.directoryPairs! - for (std::vector<FolderPair>::const_iterator i = batchCfg.directoryPairs.begin() + 1; i != batchCfg.directoryPairs.end(); ++i) - additionalPairs.push_back(*i); - addFolderPair(additionalPairs); - } + //set additional pairs + addFolderPair(batchCfg.mainCfg.additionalPairs); updateVisibleTabs(); @@ -950,14 +1036,14 @@ void BatchDialog::loadBatchCfg(const xmlAccess::XmlBatchConfig& batchCfg) void BatchDialog::OnAddFolderPair(wxCommandEvent& event) { - std::vector<FolderPair> newPairs; - newPairs.push_back(FolderPair(m_directoryLeft->GetValue().c_str(), - m_directoryRight->GetValue().c_str())); - addFolderPair(newPairs, true); //add pair in front of additional pairs - - //clear top folder pair - FreeFileSync::setDirectoryName(wxEmptyString, m_directoryLeft, m_dirPickerLeft); - FreeFileSync::setDirectoryName(wxEmptyString, m_directoryRight, m_dirPickerRight); + std::vector<FolderPairEnh> newPairs; + newPairs.push_back( + FolderPairEnh(Zstring(), + Zstring(), + boost::shared_ptr<AlternateSyncConfig>(), + boost::shared_ptr<AlternateFilter>())); + + addFolderPair(newPairs, false); //add pair at the end of additional pairs } @@ -994,7 +1080,7 @@ void BatchDialog::OnRemoveTopFolderPair(wxCommandEvent& event) const size_t MAX_FOLDER_PAIRS = 3; -void BatchDialog::addFolderPair(const std::vector<FolderPair>& newPairs, bool addFront) +void BatchDialog::addFolderPair(const std::vector<FreeFileSync::FolderPairEnh>& newPairs, bool addFront) { if (newPairs.size() == 0) return; @@ -1003,10 +1089,9 @@ void BatchDialog::addFolderPair(const std::vector<FolderPair>& newPairs, bool ad //add folder pairs int pairHeight = 0; - for (std::vector<FolderPair>::const_iterator i = newPairs.begin(); i != newPairs.end(); ++i) + for (std::vector<FreeFileSync::FolderPairEnh>::const_iterator i = newPairs.begin(); i != newPairs.end(); ++i) { - BatchFolderPairPanel* newPair = new BatchFolderPairPanel(m_scrolledWindow6); - newPair->m_bpButtonRemovePair->SetBitmapLabel(*GlobalResources::getInstance().bitmapRemoveFolderPair); + BatchFolderPairPanel* newPair = new BatchFolderPairPanel(m_scrolledWindow6, this); if (addFront) { @@ -1028,15 +1113,16 @@ void BatchDialog::addFolderPair(const std::vector<FolderPair>& newPairs, bool ad //insert directory names FreeFileSync::setDirectoryName(i->leftDirectory.c_str(), newPair->m_directoryLeft, newPair->m_dirPickerLeft); FreeFileSync::setDirectoryName(i->rightDirectory.c_str(), newPair->m_directoryRight, newPair->m_dirPickerRight); + + //set alternate configuration + newPair->altSyncConfig = i->altSyncConfig; + newPair->altFilter = i->altFilter; + newPair->updateAltButtonColor(); } //set size of scrolled window const int visiblePairs = std::min(additionalFolderPairs.size() + 1, MAX_FOLDER_PAIRS); //up to MAX_FOLDER_PAIRS pairs shall be shown m_scrolledWindow6->SetMinSize(wxSize( -1, pairHeight * visiblePairs)); - //adapt delete top folder pair button - m_bpButtonRemoveTopPair->Show(); - m_panelMainPair->Layout(); - //update controls m_scrolledWindow6->Fit(); //adjust scrolled window size m_panelOverview->Layout(); //adjust stuff inside scrolled window @@ -1065,12 +1151,6 @@ void BatchDialog::removeAddFolderPair(const int pos) const int visiblePairs = std::min(additionalFolderPairs.size() + 1, MAX_FOLDER_PAIRS); //up to MAX_FOLDER_PAIRS pairs shall be shown m_scrolledWindow6->SetMinSize(wxSize(-1, pairHeight * visiblePairs)); - if (additionalFolderPairs.size() == 0) - { - m_bpButtonRemoveTopPair->Hide(); - m_panelMainPair->Layout(); - } - //update controls m_scrolledWindow6->Fit(); //adjust scrolled window size m_panelOverview->Layout(); //adjust stuff inside scrolled window @@ -1091,29 +1171,10 @@ void BatchDialog::clearAddFolderPairs() additionalFolderPairs.clear(); bSizerAddFolderPairs->Clear(true); - m_bpButtonRemoveTopPair->Hide(); - m_panelMainPair->Layout(); - m_scrolledWindow6->SetMinSize(wxSize(-1, sbSizerMainPair->GetSize().GetHeight())); //respect height of main pair } -std::vector<FreeFileSync::FolderPair> BatchDialog::getFolderPairs() const -{ - std::vector<FolderPair> output; - - //add main pair - output.push_back(FolderPair(m_directoryLeft->GetValue().c_str(), - m_directoryRight->GetValue().c_str())); - - //add additional pairs - for (std::vector<BatchFolderPairPanel*>::const_iterator i = additionalFolderPairs.begin(); i != additionalFolderPairs.end(); ++i) - output.push_back(FolderPair((*i)->m_directoryLeft->GetValue().c_str(), - (*i)->m_directoryRight->GetValue().c_str())); - return output; -} - - /* #ifdef FFS_WIN #include <wx/msw/wrapwin.h> //includes "windows.h" diff --git a/ui/SyncDialog.h b/ui/SyncDialog.h index 57d220d3..53ab1b5a 100644 --- a/ui/SyncDialog.h +++ b/ui/SyncDialog.h @@ -18,15 +18,17 @@ class SyncCfgDialog : public SyncCfgDlgGenerated { public: SyncCfgDialog(wxWindow* window, - const FreeFileSync::FolderComparison& folderCmpRef, - FreeFileSync::MainConfiguration& config, - bool& ignoreErrors); + const FreeFileSync::CompareVariant compareVar, + FreeFileSync::SyncConfiguration& syncConfiguration, + FreeFileSync::DeletionPolicy& handleDeletion, + wxString& customDeletionDirectory, + bool* ignoreErrors); //optional input parameter ~SyncCfgDialog(); enum { - BUTTON_OKAY = 10 + BUTTON_APPLY = 10 }; static void updateConfigIcons(const FreeFileSync::CompareVariant compareVar, @@ -36,28 +38,31 @@ public: wxBitmapButton* buttonLeftNewer, wxBitmapButton* buttonRightNewer, wxBitmapButton* buttonDifferent, + wxBitmapButton* buttonConflict, wxStaticBitmap* bitmapLeftOnly, wxStaticBitmap* bitmapRightOnly, wxStaticBitmap* bitmapLeftNewer, wxStaticBitmap* bitmapRightNewer, - wxStaticBitmap* bitmapDifferent); + wxStaticBitmap* bitmapDifferent, + wxStaticBitmap* bitmapConflict); //some syntax relaxation void updateConfigIcons(const FreeFileSync::CompareVariant cmpVar, const FreeFileSync::SyncConfiguration& syncConfig); private: - void OnSyncLeftToRight( wxCommandEvent& event); - void OnSyncUpdate( wxCommandEvent& event); - void OnSyncBothSides( wxCommandEvent& event); + virtual void OnSyncLeftToRight( wxCommandEvent& event); + virtual void OnSyncUpdate( wxCommandEvent& event); + virtual void OnSyncBothSides( wxCommandEvent& event); - void OnExLeftSideOnly( wxCommandEvent& event); - void OnExRightSideOnly( wxCommandEvent& event); - void OnLeftNewer( wxCommandEvent& event); - void OnRightNewer( wxCommandEvent& event); - void OnDifferent( wxCommandEvent& event); + virtual void OnExLeftSideOnly( wxCommandEvent& event); + virtual void OnExRightSideOnly( wxCommandEvent& event); + virtual void OnLeftNewer( wxCommandEvent& event); + virtual void OnRightNewer( wxCommandEvent& event); + virtual void OnDifferent( wxCommandEvent& event); + virtual void OnConflict( wxCommandEvent& event); - void OnClose( wxCloseEvent& event); - void OnCancel( wxCommandEvent& event); - void OnApply( wxCommandEvent& event); + virtual void OnClose( wxCloseEvent& event); + virtual void OnCancel( wxCommandEvent& event); + virtual void OnApply( wxCommandEvent& event); //error handling bool getErrorHandling(); @@ -69,11 +74,16 @@ private: void setDeletionHandling(FreeFileSync::DeletionPolicy newValue); void OnChangeDeletionHandling(wxCommandEvent& event); + const FreeFileSync::CompareVariant cmpVariant; + //temporal copy of maindialog.cfg.syncConfiguration FreeFileSync::SyncConfiguration localSyncConfiguration; - const FreeFileSync::FolderComparison& folderCmp; - FreeFileSync::MainConfiguration& cfg; - bool& m_ignoreErrors; + + //changing data + FreeFileSync::SyncConfiguration& refSyncConfiguration; + FreeFileSync::DeletionPolicy& refHandleDeletion; + wxString& refCustomDeletionDirectory; + bool* refIgnoreErrors; std::auto_ptr<FreeFileSync::DragDropOnDlg> dragDropCustomDelFolder; }; @@ -82,6 +92,7 @@ private: class BatchDialog: public BatchDlgGenerated { friend class BatchFileDropEvent; + friend class BatchFolderPairPanel; public: BatchDialog(wxWindow* window, const xmlAccess::XmlBatchConfig& batchCfg); @@ -101,6 +112,7 @@ private: virtual void OnLeftNewer( wxCommandEvent& event); virtual void OnRightNewer( wxCommandEvent& event); virtual void OnDifferent( wxCommandEvent& event); + virtual void OnConflict( wxCommandEvent& event); virtual void OnCheckFilter( wxCommandEvent& event); virtual void OnCheckLogging( wxCommandEvent& event); @@ -113,12 +125,11 @@ private: virtual void OnRemoveFolderPair( wxCommandEvent& event); virtual void OnRemoveTopFolderPair(wxCommandEvent& event); - void addFolderPair(const std::vector<FreeFileSync::FolderPair>& newPairs, bool addFront = false); + void addFolderPair(const std::vector<FreeFileSync::FolderPairEnh>& newPairs, bool addFront = false); void removeAddFolderPair(const int pos); void clearAddFolderPairs(); - std::vector<FreeFileSync::FolderPair> getFolderPairs() const; - FreeFileSync::CompareVariant getCurrentCompareVar(); + FreeFileSync::CompareVariant getCurrentCompareVar() const; void updateConfigIcons(const FreeFileSync::CompareVariant cmpVar, const FreeFileSync::SyncConfiguration& syncConfig); @@ -126,12 +137,12 @@ private: void showNotebookpage(wxWindow* page, const wxString& pageName, bool show); //error handling - xmlAccess::OnError getSelectionHandleError(); + xmlAccess::OnError getSelectionHandleError() const; void setSelectionHandleError(const xmlAccess::OnError value); void OnChangeErrorHandling(wxCommandEvent& event); //deletion handling - FreeFileSync::DeletionPolicy getDeletionHandling(); + FreeFileSync::DeletionPolicy getDeletionHandling() const; void setDeletionHandling(FreeFileSync::DeletionPolicy newValue); void OnChangeDeletionHandling(wxCommandEvent& event); @@ -140,6 +151,7 @@ private: void loadBatchFile(const wxString& filename); void loadBatchCfg(const xmlAccess::XmlBatchConfig& batchCfg); + xmlAccess::XmlBatchConfig getCurrentConfiguration() const; FreeFileSync::SyncConfiguration localSyncConfiguration; std::vector<BatchFolderPairPanel*> additionalFolderPairs; diff --git a/ui/batchStatusHandler.cpp b/ui/batchStatusHandler.cpp index 294613dd..4abc49eb 100644 --- a/ui/batchStatusHandler.cpp +++ b/ui/batchStatusHandler.cpp @@ -4,10 +4,11 @@ #include "../algorithm.h" #include <wx/ffile.h> #include <wx/msgdlg.h> -#include "../shared/globalFunctions.h" +#include "../shared/systemConstants.h" #include "../shared/standardPaths.h" #include "../shared/fileHandling.h" #include "../library/resources.h" +#include "../shared/globalFunctions.h" class LogFile @@ -259,12 +260,14 @@ private: //############################################################################################################################## BatchStatusHandlerSilent::BatchStatusHandlerSilent(const xmlAccess::OnError handleError, const wxString& logfileDirectory, int& returnVal) : - m_handleError(handleError), + m_handleError(xmlAccess::ON_ERROR_POPUP), currentProcess(StatusHandler::PROCESS_NONE), returnValue(returnVal), trayIcon(new FfsTrayIcon(this)), m_log(new LogFile(logfileDirectory)) { + setErrorStrategy(handleError); + //test if log was instantiated successfully if (!m_log->isOkay()) { //handle error: file load @@ -446,6 +449,12 @@ void BatchStatusHandlerSilent::addFinalInfo(const wxString& infoMessage) } +void BatchStatusHandlerSilent::setErrorStrategy(xmlAccess::OnError handleError) +{ + m_handleError = handleError; +} + + void BatchStatusHandlerSilent::forceUiRefresh() { trayIcon->updateSysTray(); //needed by sys-tray icon only @@ -466,21 +475,7 @@ BatchStatusHandlerGui::BatchStatusHandlerGui(const xmlAccess::OnError handleErro currentProcess(StatusHandler::PROCESS_NONE), returnValue(returnVal) { - switch (handleError) - { - case xmlAccess::ON_ERROR_POPUP: - showPopups = true; - break; - - case xmlAccess::ON_ERROR_EXIT: //doesn't make much sense for "batch gui"-mode - showPopups = true; - break; - - case xmlAccess::ON_ERROR_IGNORE: - showPopups = false; - break; - } - + setErrorStrategy(handleError); syncStatusFrame = new SyncStatus(this, NULL); syncStatusFrame->Show(); @@ -684,3 +679,22 @@ void BatchStatusHandlerGui::addFinalInfo(const wxString& infoMessage) { finalInfo = infoMessage; } + + +void BatchStatusHandlerGui::setErrorStrategy(xmlAccess::OnError handleError) //change error handling during process +{ + switch (handleError) + { + case xmlAccess::ON_ERROR_POPUP: + showPopups = true; + break; + + case xmlAccess::ON_ERROR_EXIT: //doesn't make much sense for "batch gui"-mode + showPopups = true; + break; + + case xmlAccess::ON_ERROR_IGNORE: + showPopups = false; + break; + } +} diff --git a/ui/batchStatusHandler.h b/ui/batchStatusHandler.h index a075d7d7..811ab63c 100644 --- a/ui/batchStatusHandler.h +++ b/ui/batchStatusHandler.h @@ -15,6 +15,7 @@ class BatchStatusHandler : public StatusHandler { public: virtual void addFinalInfo(const wxString& infoMessage) = 0; +virtual void setErrorStrategy(xmlAccess::OnError handleError) = 0; //change error handling during process }; @@ -35,6 +36,8 @@ public: virtual void reportWarning(const wxString& warningMessage, bool& warningActive); virtual void addFinalInfo(const wxString& infoMessage); +virtual void setErrorStrategy(xmlAccess::OnError handleError); //change error handling during process + private: virtual void abortThisProcess(); @@ -64,6 +67,8 @@ public: virtual void reportWarning(const wxString& warningMessage, bool& warningActive); virtual void addFinalInfo(const wxString& infoMessage); +virtual void setErrorStrategy(xmlAccess::OnError handleError); //change error handling during process + private: virtual void abortThisProcess(); diff --git a/ui/dragAndDrop.cpp b/ui/dragAndDrop.cpp deleted file mode 100644 index 2989e544..00000000 --- a/ui/dragAndDrop.cpp +++ /dev/null @@ -1,207 +0,0 @@ -#include "dragAndDrop.h" -#include <wx/dnd.h> -#include <wx/window.h> -#include <wx/combobox.h> -#include <wx/textctrl.h> -#include <wx/filepicker.h> -#include <wx/filename.h> -#include "../algorithm.h" - - -//define new event type -const wxEventType FFS_DROP_FILE_EVENT = wxNewEventType(); - -typedef void (wxEvtHandler::*FFSFileDropEventFunction)(FFSFileDropEvent&); - -#define FFSFileDropEventHandler(func) \ - (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(FFSFileDropEventFunction, &func) - -class FFSFileDropEvent : public wxCommandEvent -{ -public: - FFSFileDropEvent(const wxString& nameDropped, const wxWindow* dropWindow) : - wxCommandEvent(FFS_DROP_FILE_EVENT), - nameDropped_(nameDropped), - dropWindow_(dropWindow) {} - - virtual wxEvent* Clone() const - { - return new FFSFileDropEvent(nameDropped_, dropWindow_); - } - - const wxString nameDropped_; - const wxWindow* dropWindow_; -}; - -//############################################################################################################## - - -class WindowDropTarget : public wxFileDropTarget -{ -public: - WindowDropTarget(wxWindow* dropWindow) : - dropWindow_(dropWindow) {} - - virtual bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames) - { - if (!filenames.IsEmpty()) - { - const wxString droppedFileName = filenames[0]; - - //create a custom event on drop window: execute event after file dropping is completed! (e.g. after mouse is released) - FFSFileDropEvent evt(droppedFileName, dropWindow_); - dropWindow_->AddPendingEvent(evt); - } - return false; - } - -private: - wxWindow* dropWindow_; -}; - - - -//############################################################################################################## - -using FreeFileSync::DragDropOnMainDlg; - -DragDropOnMainDlg::DragDropOnMainDlg(wxWindow* dropWindow1, - wxWindow* dropWindow2, - wxDirPickerCtrl* dirPicker, - wxComboBox* dirName) : - dropWindow1_(dropWindow1), - dropWindow2_(dropWindow2), - dirPicker_(dirPicker), - dirName_(dirName) -{ - //prepare drag & drop - dropWindow1->SetDropTarget(new WindowDropTarget(dropWindow1)); //takes ownership - dropWindow2->SetDropTarget(new WindowDropTarget(dropWindow2)); //takes ownership - - //redirect drag & drop event back to this class - dropWindow1->Connect(FFS_DROP_FILE_EVENT, FFSFileDropEventHandler(DragDropOnMainDlg::OnFilesDropped), NULL, this); - dropWindow2->Connect(FFS_DROP_FILE_EVENT, FFSFileDropEventHandler(DragDropOnMainDlg::OnFilesDropped), NULL, this); - - //keep dirPicker and dirName synchronous - dirName->Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( DragDropOnMainDlg::OnWriteDirManually ), NULL, this ); - dirPicker->Connect( wxEVT_COMMAND_DIRPICKER_CHANGED, wxFileDirPickerEventHandler( DragDropOnMainDlg::OnDirSelected ), NULL, this ); -} - - -void DragDropOnMainDlg::OnFilesDropped(FFSFileDropEvent& event) -{ - if ( this->dropWindow1_ == event.dropWindow_ || //file may be dropped on window 1 or 2 - this->dropWindow2_ == event.dropWindow_) - { - if (AcceptDrop(event.nameDropped_)) - { - wxString fileName = event.nameDropped_; - if (wxDirExists(fileName)) - { - dirName_->SetSelection(wxNOT_FOUND); - dirName_->SetValue(fileName); - dirPicker_->SetPath(fileName); - } - else - { - fileName = wxFileName(fileName).GetPath(); - if (wxDirExists(fileName)) - { - dirName_->SetSelection(wxNOT_FOUND); - dirName_->SetValue(fileName); - dirPicker_->SetPath(fileName); - } - } - } - } - else //should never be reached - event.Skip(); -}; - - -void DragDropOnMainDlg::OnWriteDirManually(wxCommandEvent& event) -{ - const wxString newDir = FreeFileSync::getFormattedDirectoryName(event.GetString().c_str()).c_str(); - if (wxDirExists(newDir)) - dirPicker_->SetPath(newDir); - - event.Skip(); -} - - -void DragDropOnMainDlg::OnDirSelected(wxFileDirPickerEvent& event) -{ - const wxString newPath = event.GetPath(); - dirName_->SetSelection(wxNOT_FOUND); - dirName_->SetValue(newPath); - - event.Skip(); -} - -//############################################################################################################## - - -using FreeFileSync::DragDropOnDlg; - -DragDropOnDlg::DragDropOnDlg(wxWindow* dropWindow, - wxDirPickerCtrl* dirPicker, - wxTextCtrl* dirName) : - dropWindow_(dropWindow), - dirPicker_(dirPicker), - dirName_(dirName) -{ - //prepare drag & drop - dropWindow->SetDropTarget(new WindowDropTarget(dropWindow)); //takes ownership - - //redirect drag & drop event back to this class - dropWindow->Connect(FFS_DROP_FILE_EVENT, FFSFileDropEventHandler(DragDropOnDlg::OnFilesDropped), NULL, this); - - //keep dirPicker and dirName synchronous - dirName->Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( DragDropOnDlg::OnWriteDirManually ), NULL, this ); - dirPicker->Connect( wxEVT_COMMAND_DIRPICKER_CHANGED, wxFileDirPickerEventHandler( DragDropOnDlg::OnDirSelected ), NULL, this ); -} - - -void DragDropOnDlg::OnFilesDropped(FFSFileDropEvent& event) -{ - if (this->dropWindow_ == event.dropWindow_) - { - wxString fileName = event.nameDropped_; - if (wxDirExists(fileName)) - { - dirName_->SetValue(fileName); - dirPicker_->SetPath(fileName); - } - else - { - fileName = wxFileName(fileName).GetPath(); - if (wxDirExists(fileName)) - { - dirName_->SetValue(fileName); - dirPicker_->SetPath(fileName); - } - } - } - else //should never be reached - event.Skip(); -}; - - -void DragDropOnDlg::OnWriteDirManually(wxCommandEvent& event) -{ - const wxString newDir = FreeFileSync::getFormattedDirectoryName(event.GetString().c_str()).c_str(); - if (wxDirExists(newDir)) - dirPicker_->SetPath(newDir); - - event.Skip(); -} - - -void DragDropOnDlg::OnDirSelected(wxFileDirPickerEvent& event) -{ - const wxString newPath = event.GetPath(); - dirName_->SetValue(newPath); - - event.Skip(); -} - diff --git a/ui/dragAndDrop.h b/ui/dragAndDrop.h deleted file mode 100644 index cca6f865..00000000 --- a/ui/dragAndDrop.h +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef DRAGANDDROP_H_INCLUDED -#define DRAGANDDROP_H_INCLUDED - -#include <wx/event.h> - -class wxWindow; -class wxDirPickerCtrl; -class wxComboBox; -class wxTextCtrl; -class wxString; -class FFSFileDropEvent; -class wxCommandEvent; -class wxFileDirPickerEvent; - - -namespace FreeFileSync -{ - //add drag and drop functionality, coordinating a wxWindow, wxDirPickerCtrl, and wxComboBox/wxTextCtrl - - class DragDropOnMainDlg : public wxEvtHandler - { - public: - DragDropOnMainDlg(wxWindow* dropWindow1, - wxWindow* dropWindow2, - wxDirPickerCtrl* dirPicker, - wxComboBox* dirName); - - virtual ~DragDropOnMainDlg() {} - - virtual bool AcceptDrop(const wxString& dropName) = 0; //return true if drop should be processed - - private: - void OnFilesDropped(FFSFileDropEvent& event); - void OnWriteDirManually(wxCommandEvent& event); - void OnDirSelected(wxFileDirPickerEvent& event); - - const wxWindow* dropWindow1_; - const wxWindow* dropWindow2_; - wxDirPickerCtrl* dirPicker_; - wxComboBox* dirName_; - }; - - - class DragDropOnDlg: public wxEvtHandler - { - public: - DragDropOnDlg(wxWindow* dropWindow, - wxDirPickerCtrl* dirPicker, - wxTextCtrl* dirName); - - private: - void OnFilesDropped(FFSFileDropEvent& event); - void OnWriteDirManually(wxCommandEvent& event); - void OnDirSelected(wxFileDirPickerEvent& event); - - const wxWindow* dropWindow_; - wxDirPickerCtrl* dirPicker_; - wxTextCtrl* dirName_; - }; -} - - -#endif // DRAGANDDROP_H_INCLUDED diff --git a/ui/folderPair.h b/ui/folderPair.h new file mode 100644 index 00000000..f2b57ea7 --- /dev/null +++ b/ui/folderPair.h @@ -0,0 +1,147 @@ +#ifndef FOLDERPAIR_H_INCLUDED +#define FOLDERPAIR_H_INCLUDED + +#include "../structures.h" +#include "../shared/dragAndDrop.h" +#include "../library/resources.h" +#include "smallDialogs.h" +#include "syncDialog.h" + + +namespace FreeFileSync +{ + //basic functionality for changing alternate folder pair configuration: adaptable to generated gui class + + template <class GuiPanel> + class FolderPairPanelBasic : public GuiPanel + { + using GuiPanel::m_bpButtonAltSyncCfg; + using GuiPanel::m_bpButtonAltFilter; + + public: + FolderPairPanelBasic(wxWindow* parent) : + GuiPanel(parent), + dragDropOnLeft(new DragDropOnDlg(GuiPanel::m_panelLeft, GuiPanel::m_dirPickerLeft, GuiPanel::m_directoryLeft)), + dragDropOnRight(new DragDropOnDlg(GuiPanel::m_panelRight, GuiPanel::m_dirPickerRight, GuiPanel::m_directoryRight)) + { + //register events for removal of alternate configuration + m_bpButtonAltFilter->Connect(wxEVT_RIGHT_DOWN, wxCommandEventHandler(FolderPairPanelBasic::OnAltFilterCfgRemove), NULL, this); + m_bpButtonAltSyncCfg->Connect(wxEVT_RIGHT_DOWN, wxCommandEventHandler(FolderPairPanelBasic::OnAltSyncCfgRemove), NULL, this); + + m_bpButtonAltSyncCfg->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FolderPairPanelBasic::OnAltSyncCfg), NULL, this); + m_bpButtonAltFilter-> Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FolderPairPanelBasic::OnAltFilterCfg), NULL, this); + + GuiPanel::m_bpButtonRemovePair->SetBitmapLabel(*GlobalResources::getInstance().bitmapRemoveFolderPair); + } + + //alternate configuration attached to it + boost::shared_ptr<const FreeFileSync::AlternateSyncConfig> altSyncConfig; //optional + boost::shared_ptr<const FreeFileSync::AlternateFilter> altFilter; //optional + + + void updateAltButtonColor() + { + if (altSyncConfig.get()) + m_bpButtonAltSyncCfg->SetBitmapLabel(*GlobalResources::getInstance().bitmapSyncCfgSmall); + else + m_bpButtonAltSyncCfg->SetBitmapLabel(*GlobalResources::getInstance().bitmapSyncCfgSmallGrey); + + if (altFilter.get()) + m_bpButtonAltFilter->SetBitmapLabel(*GlobalResources::getInstance().bitmapFilterSmall); + else + m_bpButtonAltFilter->SetBitmapLabel(*GlobalResources::getInstance().bitmapFilterSmallGrey); + } + + protected: + virtual void OnAltFilterCfgRemoveConfirm(wxCommandEvent& event) + { + altFilter.reset(); + updateAltButtonColor(); + } + + virtual void OnAltSyncCfgRemoveConfirm(wxCommandEvent& event) + { + altSyncConfig.reset(); + updateAltButtonColor(); + } + + + private: + void OnAltFilterCfgRemove(wxCommandEvent& event) + { + contextMenu.reset(new wxMenu); //re-create context menu + contextMenu->Append(wxID_ANY, _("Remove alternate settings")); + contextMenu->Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(FolderPairPanelBasic::OnAltFilterCfgRemoveConfirm), NULL, this); + GuiPanel::PopupMenu(contextMenu.get()); //show context menu + } + + void OnAltSyncCfgRemove(wxCommandEvent& event) + { + contextMenu.reset(new wxMenu); //re-create context menu + contextMenu->Append(wxID_ANY, _("Remove alternate settings")); + contextMenu->Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(FolderPairPanelBasic::OnAltSyncCfgRemoveConfirm), NULL, this); + GuiPanel::PopupMenu(contextMenu.get()); //show context menu + } + + virtual MainConfiguration getMainConfig() const = 0; + virtual wxWindow* getParentWindow() = 0; + + virtual void OnAltSyncCfgChange() {}; + + void OnAltSyncCfg(wxCommandEvent& event) + { + const MainConfiguration& mainCfg = getMainConfig(); + + AlternateSyncConfig altSyncCfg = altSyncConfig.get() ? + *altSyncConfig : + AlternateSyncConfig(mainCfg.syncConfiguration, + mainCfg.handleDeletion, + mainCfg.customDeletionDirectory); + SyncCfgDialog* syncDlg = new SyncCfgDialog(getParentWindow(), + mainCfg.compareVar, + altSyncCfg.syncConfiguration, + altSyncCfg.handleDeletion, + altSyncCfg.customDeletionDirectory, + NULL); + if (syncDlg->ShowModal() == SyncCfgDialog::BUTTON_APPLY) + { + altSyncConfig.reset(new AlternateSyncConfig(altSyncCfg)); + updateAltButtonColor(); + + OnAltSyncCfgChange(); + } + } + + virtual void OnAltFilterCfgChange(bool defaultValueSet) {}; + + void OnAltFilterCfg(wxCommandEvent& event) + { + const MainConfiguration& mainCfg = getMainConfig(); + + AlternateFilter altFilt = altFilter.get() ? + *altFilter : + AlternateFilter(mainCfg.includeFilter, mainCfg.excludeFilter); + + FilterDlg* filterDlg = new FilterDlg(getParentWindow(), altFilt.includeFilter, altFilt.excludeFilter); + if (filterDlg->ShowModal() == FilterDlg::BUTTON_APPLY) + { + altFilter.reset(new AlternateFilter(altFilt)); + updateAltButtonColor(); + + if (altFilt.includeFilter == wxT("*") && altFilt.excludeFilter.empty()) //default + OnAltFilterCfgChange(true); + else + OnAltFilterCfgChange(false); + } + } + + std::auto_ptr<wxMenu> contextMenu; + + //support for drag and drop + std::auto_ptr<DragDropOnDlg> dragDropOnLeft; + std::auto_ptr<DragDropOnDlg> dragDropOnRight; + }; +} + + +#endif // FOLDERPAIR_H_INCLUDED diff --git a/ui/gridView.cpp b/ui/gridView.cpp index 5406422b..9d9e9a1c 100644 --- a/ui/gridView.cpp +++ b/ui/gridView.cpp @@ -1,29 +1,12 @@ #include "gridView.h" #include "sorting.h" #include "../synchronization.h" +#include <boost/bind.hpp> -using FreeFileSync::GridView; +using namespace FreeFileSync; -GridView::GridView(FreeFileSync::FolderComparison& results) : - leftOnlyFilesActive(false), - rightOnlyFilesActive(false), - leftNewerFilesActive(false), - rightNewerFilesActive(false), - differentFilesActive(false), - equalFilesActive(false), - conflictFilesActive(false), - syncCreateLeftActive(false), - syncCreateRightActive(false), - syncDeleteLeftActive(false), - syncDeleteRightActive(false), - syncDirLeftActive(false), - syncDirRightActive(false), - syncDirNoneActive(false), - folderCmp(results) {} - - -GridView::StatusInfo::StatusInfo() : +GridView::StatusCmpResult::StatusCmpResult() : existsLeftOnly(false), existsRightOnly(false), existsLeftNewer(false), @@ -31,6 +14,104 @@ GridView::StatusInfo::StatusInfo() : existsDifferent(false), existsEqual(false), existsConflict(false), + + filesOnLeftView(0), + foldersOnLeftView(0), + filesOnRightView(0), + foldersOnRightView(0) {} + + +GridView::StatusCmpResult GridView::updateCmpResult(bool hideFiltered, //maps sortedRef to viewRef + bool leftOnlyFilesActive, + bool rightOnlyFilesActive, + bool leftNewerFilesActive, + bool rightNewerFilesActive, + bool differentFilesActive, + bool equalFilesActive, + bool conflictFilesActive) +{ + StatusCmpResult output; + + viewRef.clear(); + + for (std::vector<RefIndex>::const_iterator j = sortedRef.begin(); j != sortedRef.end(); ++j) + { + const FileSystemObject* fsObj = getReferencedRow(*j); + if (fsObj) + { + //hide filtered row, if corresponding option is set + if (hideFiltered && !fsObj->selectedForSynchronization) + continue; + + switch (fsObj->getCategory()) + { + case FILE_LEFT_SIDE_ONLY: + output.existsLeftOnly = true; + if (!leftOnlyFilesActive) continue; + break; + case FILE_RIGHT_SIDE_ONLY: + output.existsRightOnly = true; + if (!rightOnlyFilesActive) continue; + break; + case FILE_LEFT_NEWER: + output.existsLeftNewer = true; + if (!leftNewerFilesActive) continue; + break; + case FILE_RIGHT_NEWER: + output.existsRightNewer = true; + if (!rightNewerFilesActive) continue; + break; + case FILE_DIFFERENT: + output.existsDifferent = true; + if (!differentFilesActive) continue; + break; + case FILE_EQUAL: + output.existsEqual = true; + if (!equalFilesActive) continue; + break; + case FILE_CONFLICT: + output.existsConflict = true; + if (!conflictFilesActive) continue; + break; + } + + //calculate total number of bytes for each side + const FileMapping* fileObj = dynamic_cast<const FileMapping*>(fsObj); + if (fileObj) + { + if (!fileObj->isEmpty<LEFT_SIDE>()) + { + output.filesizeLeftView += fileObj->getFileSize<LEFT_SIDE>(); + ++output.filesOnLeftView; + } + if (!fileObj->isEmpty<RIGHT_SIDE>()) + { + output.filesizeRightView += fileObj->getFileSize<RIGHT_SIDE>(); + ++output.filesOnRightView; + } + } + else + { + const DirMapping* dirObj = dynamic_cast<const DirMapping*>(fsObj); + if (dirObj) + { + if (!dirObj->isEmpty<LEFT_SIDE>()) + ++output.foldersOnLeftView; + + if (!dirObj->isEmpty<RIGHT_SIDE>()) + ++output.foldersOnRightView; + } + } + + viewRef.push_back(*j); + } + } + + return output; +} + + +GridView::StatusSyncPreview::StatusSyncPreview() : existsSyncCreateLeft(false), existsSyncCreateRight(false), existsSyncDeleteLeft(false), @@ -38,308 +119,398 @@ GridView::StatusInfo::StatusInfo() : existsSyncDirLeft(false), existsSyncDirRight(false), existsSyncDirNone(false), + existsConflict(false), filesOnLeftView(0), foldersOnLeftView(0), filesOnRightView(0), - foldersOnRightView(0), - objectsTotal(0) {} - -template <bool syncPreviewActive> -GridView::StatusInfo GridView::update_sub(const bool hideFiltered) + foldersOnRightView(0) {} + + +GridView::StatusSyncPreview GridView::updateSyncPreview(bool hideFiltered, //maps sortedRef to viewRef + bool syncCreateLeftActive, + bool syncCreateRightActive, + bool syncDeleteLeftActive, + bool syncDeleteRightActive, + bool syncDirOverwLeftActive, + bool syncDirOverwRightActive, + bool syncDirNoneActive, + bool conflictFilesActive) { - StatusInfo output; + StatusSyncPreview output; - refView.clear(); + viewRef.clear(); - for (FolderComparison::const_iterator j = folderCmp.begin(); j != folderCmp.end(); ++j) + for (std::vector<RefIndex>::const_iterator j = sortedRef.begin(); j != sortedRef.end(); ++j) { - const FileComparison& fileCmp = j->fileCmp; - - RefIndex newEntry; - newEntry.folderIndex = j - folderCmp.begin(); - - for (FileComparison::const_iterator i = fileCmp.begin(); i != fileCmp.end(); ++i) + const FileSystemObject* fsObj = getReferencedRow(*j); + if (fsObj) { - //process UI filter settings - if (syncPreviewActive) //synchronization preview - { - //exclude result "==" - if (i->cmpResult == FILE_EQUAL) //note: consider "objectsTotal" - continue; + //synchronization preview - output.objectsTotal++; + //exclude result "==" +//#warning na dann consider mal! + if (fsObj->getCategory() == FILE_EQUAL) //note: consider "objectsTotal" + continue; - //hide filtered row, if corresponding option is set - if (hideFiltered && !i->selectedForSynchronization) //keep AFTER "objectsTotal++" - continue; + //hide filtered row, if corresponding option is set + if (hideFiltered && !fsObj->selectedForSynchronization) + continue; - switch (FreeFileSync::getSyncOperation(*i)) //evaluate comparison result and sync direction - { - case SO_CREATE_NEW_LEFT: - output.existsSyncCreateLeft = true; - if (!syncCreateLeftActive) continue; - break; - case SO_CREATE_NEW_RIGHT: - output.existsSyncCreateRight = true; - if (!syncCreateRightActive) continue; - break; - case SO_DELETE_LEFT: - output.existsSyncDeleteLeft = true; - if (!syncDeleteLeftActive) continue; - break; - case SO_DELETE_RIGHT: - output.existsSyncDeleteRight = true; - if (!syncDeleteRightActive) continue; - break; - case SO_OVERWRITE_RIGHT: - output.existsSyncDirRight = true; - if (!syncDirRightActive) continue; - break; - case SO_OVERWRITE_LEFT: - output.existsSyncDirLeft = true; - if (!syncDirLeftActive) continue; - break; - case SO_DO_NOTHING: - output.existsSyncDirNone = true; - if (!syncDirNoneActive) continue; - break; - case SO_UNRESOLVED_CONFLICT: - output.existsConflict = true; - if (!conflictFilesActive) continue; - break; - } - } - else //comparison results view + switch (FreeFileSync::getSyncOperation(*fsObj)) //evaluate comparison result and sync direction { - output.objectsTotal++; - - //hide filtered row, if corresponding option is set - if (hideFiltered && !i->selectedForSynchronization) - continue; - - switch (i->cmpResult) - { - case FILE_LEFT_SIDE_ONLY: - output.existsLeftOnly = true; - if (!leftOnlyFilesActive) continue; - break; - case FILE_RIGHT_SIDE_ONLY: - output.existsRightOnly = true; - if (!rightOnlyFilesActive) continue; - break; - case FILE_LEFT_NEWER: - output.existsLeftNewer = true; - if (!leftNewerFilesActive) continue; - break; - case FILE_RIGHT_NEWER: - output.existsRightNewer = true; - if (!rightNewerFilesActive) continue; - break; - case FILE_DIFFERENT: - output.existsDifferent = true; - if (!differentFilesActive) continue; - break; - case FILE_EQUAL: - output.existsEqual = true; - if (!equalFilesActive) continue; - break; - case FILE_CONFLICT: - output.existsConflict = true; - if (!conflictFilesActive) continue; - break; - } + case SO_CREATE_NEW_LEFT: + output.existsSyncCreateLeft = true; + if (!syncCreateLeftActive) continue; + break; + case SO_CREATE_NEW_RIGHT: + output.existsSyncCreateRight = true; + if (!syncCreateRightActive) continue; + break; + case SO_DELETE_LEFT: + output.existsSyncDeleteLeft = true; + if (!syncDeleteLeftActive) continue; + break; + case SO_DELETE_RIGHT: + output.existsSyncDeleteRight = true; + if (!syncDeleteRightActive) continue; + break; + case SO_OVERWRITE_RIGHT: + output.existsSyncDirRight = true; + if (!syncDirOverwRightActive) continue; + break; + case SO_OVERWRITE_LEFT: + output.existsSyncDirLeft = true; + if (!syncDirOverwLeftActive) continue; + break; + case SO_DO_NOTHING: + output.existsSyncDirNone = true; + if (!syncDirNoneActive) continue; + break; + case SO_UNRESOLVED_CONFLICT: + output.existsConflict = true; + if (!conflictFilesActive) continue; + break; } //calculate total number of bytes for each side - if (i->fileDescrLeft.objType == FileDescrLine::TYPE_FILE) + const FileMapping* fileObj = dynamic_cast<const FileMapping*>(fsObj); + if (fileObj) { - output.filesizeLeftView += i->fileDescrLeft.fileSize; - ++output.filesOnLeftView; + if (!fileObj->isEmpty<LEFT_SIDE>()) + { + output.filesizeLeftView += fileObj->getFileSize<LEFT_SIDE>(); + ++output.filesOnLeftView; + } + if (!fileObj->isEmpty<RIGHT_SIDE>()) + { + output.filesizeRightView += fileObj->getFileSize<RIGHT_SIDE>(); + ++output.filesOnRightView; + } } - else if (i->fileDescrLeft.objType == FileDescrLine::TYPE_DIRECTORY) - ++output.foldersOnLeftView; - - if (i->fileDescrRight.objType == FileDescrLine::TYPE_FILE) + else { - output.filesizeRightView += i->fileDescrRight.fileSize; - ++output.filesOnRightView; + const DirMapping* dirObj = dynamic_cast<const DirMapping*>(fsObj); + if (dirObj) + { + if (!dirObj->isEmpty<LEFT_SIDE>()) + ++output.foldersOnLeftView; + + if (!dirObj->isEmpty<RIGHT_SIDE>()) + ++output.foldersOnRightView; + } } - else if (i->fileDescrRight.objType == FileDescrLine::TYPE_DIRECTORY) - ++output.foldersOnRightView; - newEntry.rowIndex = i - fileCmp.begin(); - refView.push_back(newEntry); + viewRef.push_back(*j); } - -// //add some empty line after each folder pair -// RefIndex emptyLine; -// emptyLine.folderIndex = -1; -// emptyLine.rowIndex = 0; -// refView.push_back(emptyLine); } return output; } -GridView::StatusInfo GridView::update(const bool hideFiltered, const bool syncPreviewActive) +void GridView::getAllFileRef(const std::set<unsigned int>& guiRows, std::vector<FileSystemObject*>& output) { - return syncPreviewActive ? - update_sub<true>(hideFiltered) : - update_sub<false>(hideFiltered); + std::set<unsigned int>::const_iterator upperEnd = guiRows.lower_bound(rowsOnView()); //loop over valid rows only! + + output.clear(); + output.reserve(guiRows.size()); + for (std::set<unsigned int>::const_iterator i = guiRows.begin(); i != upperEnd; ++i) + { + FileSystemObject* fsObj = getReferencedRow(viewRef[*i]); + if (fsObj) + output.push_back(fsObj); + } } -void GridView::resetSettings() +inline +bool GridView::isInvalidRow(const RefIndex& ref) const { - leftOnlyFilesActive = true; - leftNewerFilesActive = true; - differentFilesActive = true; - rightNewerFilesActive = true; //do not save/load these bool values from harddisk! - rightOnlyFilesActive = true; //it's more convenient to have them defaulted at startup - equalFilesActive = false; - - conflictFilesActive = true; - - syncCreateLeftActive = true; - syncCreateRightActive = true; - syncDeleteLeftActive = true; - syncDeleteRightActive = true; - syncDirLeftActive = true; - syncDirRightActive = true; - syncDirNoneActive = true; + return getReferencedRow(ref) == NULL; } -void GridView::clearView() +void GridView::removeInvalidRows() { - refView.clear(); + viewRef.clear(); + + //remove rows that have been deleted meanwhile + sortedRef.erase(std::remove_if(sortedRef.begin(), sortedRef.end(), + boost::bind(&GridView::isInvalidRow, this, _1)), sortedRef.end()); } -void GridView::viewRefToFolderRef(const std::set<int>& viewRef, FreeFileSync::FolderCompRef& output) +void GridView::clearAllRows() { - output.clear(); - for (int i = 0; i < int(folderCmp.size()); ++i) - output.push_back(std::set<int>()); //avoid copy by value for full set<int> + viewRef.clear(); + sortedRef.clear(); + folderCmp.clear(); +} - for (std::set<int>::const_iterator i = viewRef.begin(); i != viewRef.end(); ++i) + +class GridView::SerializeHierarchy +{ +public: + SerializeHierarchy(std::vector<GridView::RefIndex>& sortedRef, unsigned int index) : + index_(index), + sortedRef_(sortedRef) {} + + void execute(const HierarchyObject& hierObj) { - const unsigned int folder = refView[*i].folderIndex; - const unsigned int row = refView[*i].rowIndex; + //add file references + std::for_each(hierObj.subFiles.begin(), hierObj.subFiles.end(), *this); - output[folder].insert(row); + //add dir references + std::for_each(hierObj.subDirs.begin(), hierObj.subDirs.end(), *this); } -} + void operator()(const FileMapping& fileObj) + { + sortedRef_.push_back(RefIndex(index_, fileObj.getId())); + } + + void operator()(const DirMapping& dirObj) + { + sortedRef_.push_back(RefIndex(index_, dirObj.getId())); + execute(dirObj); //add recursion here to list sub-objects directly below parent! + } + +private: + unsigned int index_; + std::vector<GridView::RefIndex>& sortedRef_; +}; -bool GridView::refGridIsEmpty() const + +void GridView::setData(FolderComparison& newData) { - for (FolderComparison::const_iterator j = folderCmp.begin(); j != folderCmp.end(); ++j) - if (!j->fileCmp.empty()) return false; + viewRef.clear(); + sortedRef.clear(); + folderCmp.swap(newData); - return true; + unsigned int index = 0; + + //fill sortedRef + for (FolderComparison::const_iterator j = folderCmp.begin(); j != folderCmp.end(); ++j) + SerializeHierarchy(sortedRef, index++).execute(*j); } -template <typename CompareFct> -void bubbleSort(FreeFileSync::FolderComparison& folderCmp, CompareFct compare) +//------------------------------------ SORTING TEMPLATES ------------------------------------------------ +template <bool ascending> +class GridView::SortByDirectory : public std::binary_function<RefIndex, RefIndex, bool> { - for (int i = folderCmp.size() - 2; i >= 0; --i) +public: + bool operator()(const RefIndex a, const RefIndex b) const { - bool swapped = false; - for (int j = 0; j <= i; ++j) - if (compare(folderCmp[j + 1], folderCmp[j])) - { - folderCmp[j + 1].swap(folderCmp[j]); - swapped = true; - } + return ascending ? + a.folderIndex < b.folderIndex : + a.folderIndex > b.folderIndex; + } +}; - if (!swapped) - return; + +template <bool ascending, FreeFileSync::SelectedSide side> +class GridView::SortByRelName : public std::binary_function<RefIndex, RefIndex, bool> +{ +public: + SortByRelName(const GridView& view) : m_view(view) {} + + bool operator()(const RefIndex a, const RefIndex b) const + { + //presort by folder pair + if (a.folderIndex != b.folderIndex) + return ascending ? + a.folderIndex < b.folderIndex : + a.folderIndex > b.folderIndex; + + const FileSystemObject* fsObjA = m_view.getReferencedRow(a); + const FileSystemObject* fsObjB = m_view.getReferencedRow(b); + if (fsObjA == NULL) //invalid rows shall appear at the end + return false; + else if (fsObjB == NULL) + return true; + + return sortByRelativeName<ascending, side>(*fsObjA, *fsObjB); } -} +private: + const GridView& m_view; +}; -template <class T> -struct CompareGreater +template <bool ascending, FreeFileSync::SelectedSide side> +class GridView::SortByFileName : public std::binary_function<RefIndex, RefIndex, bool> { - typedef bool (*CmpLess) (const T& a, const T& b); - CompareGreater(CmpLess cmpFct) : m_cmpFct(cmpFct) {} +public: + SortByFileName(const GridView& view) : m_view(view) {} - bool operator()(const T& a, const T& b) const + bool operator()(const RefIndex a, const RefIndex b) const { - return m_cmpFct(b, a); + const FileSystemObject* fsObjA = m_view.getReferencedRow(a); + const FileSystemObject* fsObjB = m_view.getReferencedRow(b); + if (fsObjA == NULL) //invalid rows shall appear at the end + return false; + else if (fsObjB == NULL) + return true; + + return sortByFileName<ascending, side>(*fsObjA, *fsObjB); } private: - CmpLess m_cmpFct; + const GridView& m_view; }; -void GridView::sortView(const SortType type, const bool onLeft, const bool ascending) +template <bool ascending, FreeFileSync::SelectedSide side> +class GridView::SortByFileSize : public std::binary_function<RefIndex, RefIndex, bool> { - using namespace FreeFileSync; - typedef CompareGreater<FolderCompareLine> FolderReverse; +public: + SortByFileSize(const GridView& view) : m_view(view) {} - if (type == SORT_BY_DIRECTORY) + bool operator()(const RefIndex a, const RefIndex b) const { - //specialization: use custom sorting function based on FolderComparison::swap() - //bubble sort is no performance issue since number of folder pairs should be "very small" - if (ascending && onLeft) bubbleSort(folderCmp, sortByDirectory<SORT_ON_LEFT>); - else if (ascending && !onLeft) bubbleSort(folderCmp, sortByDirectory<SORT_ON_RIGHT>); - else if (!ascending && onLeft) bubbleSort(folderCmp, FolderReverse(sortByDirectory<SORT_ON_LEFT>)); - else if (!ascending && !onLeft) bubbleSort(folderCmp, FolderReverse(sortByDirectory<SORT_ON_RIGHT>)); - - //then sort by relative name - GridView::sortView(SORT_BY_REL_NAME, onLeft, ascending); - return; + const FileSystemObject* fsObjA = m_view.getReferencedRow(a); + const FileSystemObject* fsObjB = m_view.getReferencedRow(b); + if (fsObjA == NULL) //invalid rows shall appear at the end + return false; + else if (fsObjB == NULL) + return true; + + return sortByFileSize<ascending, side>(*fsObjA, *fsObjB); } +private: + const GridView& m_view; +}; + - typedef CompareGreater<FileCompareLine> FileReverse; +template <bool ascending, FreeFileSync::SelectedSide side> +class GridView::SortByDate : public std::binary_function<RefIndex, RefIndex, bool> +{ +public: + SortByDate(const GridView& view) : m_view(view) {} - for (FolderComparison::iterator j = folderCmp.begin(); j != folderCmp.end(); ++j) + bool operator()(const RefIndex a, const RefIndex b) const { - FileComparison& fileCmp = j->fileCmp; + const FileSystemObject* fsObjA = m_view.getReferencedRow(a); + const FileSystemObject* fsObjB = m_view.getReferencedRow(b); + if (fsObjA == NULL) //invalid rows shall appear at the end + return false; + else if (fsObjB == NULL) + return true; + + return sortByDate<ascending, side>(*fsObjA, *fsObjB); + } +private: + const GridView& m_view; +}; - switch (type) - { - case SORT_BY_REL_NAME: - if ( ascending && onLeft) std::sort(fileCmp.begin(), fileCmp.end(), sortByRelativeName<SORT_ON_LEFT>); - else if ( ascending && !onLeft) std::sort(fileCmp.begin(), fileCmp.end(), sortByRelativeName<SORT_ON_RIGHT>); - else if (!ascending && onLeft) std::sort(fileCmp.begin(), fileCmp.end(), FileReverse(sortByRelativeName<SORT_ON_LEFT>)); - else if (!ascending && !onLeft) std::sort(fileCmp.begin(), fileCmp.end(), FileReverse(sortByRelativeName<SORT_ON_RIGHT>)); - break; - case SORT_BY_FILENAME: - if ( ascending && onLeft) std::sort(fileCmp.begin(), fileCmp.end(), sortByFileName<SORT_ON_LEFT>); - else if ( ascending && !onLeft) std::sort(fileCmp.begin(), fileCmp.end(), sortByFileName<SORT_ON_RIGHT>); - else if (!ascending && onLeft) std::sort(fileCmp.begin(), fileCmp.end(), FileReverse(sortByFileName<SORT_ON_LEFT>)); - else if (!ascending && !onLeft) std::sort(fileCmp.begin(), fileCmp.end(), FileReverse(sortByFileName<SORT_ON_RIGHT>)); - break; - case SORT_BY_FILESIZE: - if ( ascending && onLeft) std::sort(fileCmp.begin(), fileCmp.end(), sortByFileSize<SORT_ON_LEFT>); - else if ( ascending && !onLeft) std::sort(fileCmp.begin(), fileCmp.end(), sortByFileSize<SORT_ON_RIGHT>); - else if (!ascending && onLeft) std::sort(fileCmp.begin(), fileCmp.end(), FileReverse(sortByFileSize<SORT_ON_LEFT>)); - else if (!ascending && !onLeft) std::sort(fileCmp.begin(), fileCmp.end(), FileReverse(sortByFileSize<SORT_ON_RIGHT>)); - break; - case SORT_BY_DATE: - if ( ascending && onLeft) std::sort(fileCmp.begin(), fileCmp.end(), sortByDate<SORT_ON_LEFT>); - else if ( ascending && !onLeft) std::sort(fileCmp.begin(), fileCmp.end(), sortByDate<SORT_ON_RIGHT>); - else if (!ascending && onLeft) std::sort(fileCmp.begin(), fileCmp.end(), FileReverse(sortByDate<SORT_ON_LEFT>)); - else if (!ascending && !onLeft) std::sort(fileCmp.begin(), fileCmp.end(), FileReverse(sortByDate<SORT_ON_RIGHT>)); - break; - case SORT_BY_CMP_RESULT: - if ( ascending) std::sort(fileCmp.begin(), fileCmp.end(), sortByCmpResult); - else if (!ascending) std::sort(fileCmp.begin(), fileCmp.end(), FileReverse(sortByCmpResult)); - break; - case SORT_BY_SYNC_DIRECTION: - if ( ascending) std::sort(fileCmp.begin(), fileCmp.end(), sortBySyncDirection); - else if (!ascending) std::sort(fileCmp.begin(), fileCmp.end(), FileReverse(sortBySyncDirection)); - break; - case SORT_BY_DIRECTORY: - assert(false); - } + +template <bool ascending> +class GridView::SortByCmpResult : public std::binary_function<RefIndex, RefIndex, bool> +{ +public: + SortByCmpResult(const GridView& view) : m_view(view) {} + + bool operator()(const RefIndex a, const RefIndex b) const + { + const FileSystemObject* fsObjA = m_view.getReferencedRow(a); + const FileSystemObject* fsObjB = m_view.getReferencedRow(b); + if (fsObjA == NULL) //invalid rows shall appear at the end + return false; + else if (fsObjB == NULL) + return true; + + return sortByCmpResult<ascending>(*fsObjA, *fsObjB); + } +private: + const GridView& m_view; +}; + + +template <bool ascending> +class GridView::SortBySyncDirection : public std::binary_function<RefIndex, RefIndex, bool> +{ +public: + SortBySyncDirection(const GridView& view) : m_view(view) {} + + bool operator()(const RefIndex a, const RefIndex b) const + { + const FileSystemObject* fsObjA = m_view.getReferencedRow(a); + const FileSystemObject* fsObjB = m_view.getReferencedRow(b); + if (fsObjA == NULL) //invalid rows shall appear at the end + return false; + else if (fsObjB == NULL) + return true; + + return sortBySyncDirection<ascending>(*fsObjA, *fsObjB); + } +private: + const GridView& m_view; +}; + +//------------------------------------------------------------------------------------------------------- +void GridView::sortView(const SortType type, const bool onLeft, const bool ascending) +{ + viewRef.clear(); + + switch (type) + { + case SORT_BY_REL_NAME: + if ( ascending && onLeft) std::sort(sortedRef.begin(), sortedRef.end(), SortByRelName<true, LEFT_SIDE>(*this)); + else if ( ascending && !onLeft) std::sort(sortedRef.begin(), sortedRef.end(), SortByRelName<true, RIGHT_SIDE>(*this)); + else if (!ascending && onLeft) std::sort(sortedRef.begin(), sortedRef.end(), SortByRelName<false, LEFT_SIDE >(*this)); + else if (!ascending && !onLeft) std::sort(sortedRef.begin(), sortedRef.end(), SortByRelName<false, RIGHT_SIDE>(*this)); + break; + case SORT_BY_FILENAME: + if ( ascending && onLeft) std::sort(sortedRef.begin(), sortedRef.end(), SortByFileName<true, LEFT_SIDE >(*this)); + else if ( ascending && !onLeft) std::sort(sortedRef.begin(), sortedRef.end(), SortByFileName<true, RIGHT_SIDE>(*this)); + else if (!ascending && onLeft) std::sort(sortedRef.begin(), sortedRef.end(), SortByFileName<false, LEFT_SIDE >(*this)); + else if (!ascending && !onLeft) std::sort(sortedRef.begin(), sortedRef.end(), SortByFileName<false, RIGHT_SIDE>(*this)); + break; + case SORT_BY_FILESIZE: + if ( ascending && onLeft) std::sort(sortedRef.begin(), sortedRef.end(), SortByFileSize<true, LEFT_SIDE >(*this)); + else if ( ascending && !onLeft) std::sort(sortedRef.begin(), sortedRef.end(), SortByFileSize<true, RIGHT_SIDE>(*this)); + else if (!ascending && onLeft) std::sort(sortedRef.begin(), sortedRef.end(), SortByFileSize<false, LEFT_SIDE >(*this)); + else if (!ascending && !onLeft) std::sort(sortedRef.begin(), sortedRef.end(), SortByFileSize<false, RIGHT_SIDE>(*this)); + break; + case SORT_BY_DATE: + if ( ascending && onLeft) std::sort(sortedRef.begin(), sortedRef.end(), SortByDate<true, LEFT_SIDE >(*this)); + else if ( ascending && !onLeft) std::sort(sortedRef.begin(), sortedRef.end(), SortByDate<true, RIGHT_SIDE>(*this)); + else if (!ascending && onLeft) std::sort(sortedRef.begin(), sortedRef.end(), SortByDate<false, LEFT_SIDE >(*this)); + else if (!ascending && !onLeft) std::sort(sortedRef.begin(), sortedRef.end(), SortByDate<false, RIGHT_SIDE>(*this)); + break; + case SORT_BY_CMP_RESULT: + if ( ascending) std::stable_sort(sortedRef.begin(), sortedRef.end(), SortByCmpResult<true >(*this)); + else if (!ascending) std::stable_sort(sortedRef.begin(), sortedRef.end(), SortByCmpResult<false>(*this)); + break; + case SORT_BY_SYNC_DIRECTION: + if ( ascending) std::stable_sort(sortedRef.begin(), sortedRef.end(), SortBySyncDirection<true >(*this)); + else if (!ascending) std::stable_sort(sortedRef.begin(), sortedRef.end(), SortBySyncDirection<false>(*this)); + break; + case SORT_BY_DIRECTORY: + if ( ascending) std::stable_sort(sortedRef.begin(), sortedRef.end(), SortByDirectory<true>()); + else if (!ascending) std::stable_sort(sortedRef.begin(), sortedRef.end(), SortByDirectory<false>()); + break; } } diff --git a/ui/gridView.h b/ui/gridView.h index 8603bbd2..c7728b06 100644 --- a/ui/gridView.h +++ b/ui/gridView.h @@ -1,7 +1,7 @@ #ifndef GRIDVIEW_H_INCLUDED #define GRIDVIEW_H_INCLUDED -#include "../structures.h" +#include "../fileHierarchy.h" namespace FreeFileSync @@ -10,23 +10,18 @@ namespace FreeFileSync class GridView { public: - GridView(FolderComparison& results); + //direct data access via row number + const FileSystemObject* getObject(unsigned int row) const; //returns NULL if object is not found; logarithmic complexity + FileSystemObject* getObject(unsigned int row); // + unsigned int rowsOnView() const; //only the currently visible elements + unsigned int rowsTotal() const; //total number of rows available - const FileCompareLine& operator[] (unsigned row) const; - FileCompareLine& operator[] (unsigned row); + //get references to FileSystemObject: no NULL-check needed! Everything's bound. + void getAllFileRef(const std::set<unsigned int>& guiRows, std::vector<FileSystemObject*>& output); - unsigned int elementsOnView() const; //only the currently visible elements - - bool refGridIsEmpty() const; - - //convert view references to FolderCompRef - void viewRefToFolderRef(const std::set<int>& viewRef, FolderCompRef& output); - - const FolderPair getFolderPair(const unsigned int row) const; - - struct StatusInfo + struct StatusCmpResult { - StatusInfo(); + StatusCmpResult(); bool existsLeftOnly; bool existsRightOnly; @@ -36,6 +31,29 @@ namespace FreeFileSync bool existsEqual; bool existsConflict; + unsigned int filesOnLeftView; + unsigned int foldersOnLeftView; + unsigned int filesOnRightView; + unsigned int foldersOnRightView; + + wxULongLong filesizeLeftView; + wxULongLong filesizeRightView; + }; + + //comparison results view + StatusCmpResult updateCmpResult(bool hideFiltered, + bool leftOnlyFilesActive, + bool rightOnlyFilesActive, + bool leftNewerFilesActive, + bool rightNewerFilesActive, + bool differentFilesActive, + bool equalFilesActive, + bool conflictFilesActive); + + struct StatusSyncPreview + { + StatusSyncPreview(); + bool existsSyncCreateLeft; bool existsSyncCreateRight; bool existsSyncDeleteLeft; @@ -43,41 +61,34 @@ namespace FreeFileSync bool existsSyncDirLeft; bool existsSyncDirRight; bool existsSyncDirNone; + bool existsConflict; unsigned int filesOnLeftView; unsigned int foldersOnLeftView; unsigned int filesOnRightView; unsigned int foldersOnRightView; - unsigned int objectsTotal; - wxULongLong filesizeLeftView; wxULongLong filesizeRightView; }; - StatusInfo update(const bool hideFiltered, const bool syncPreviewActive); - - void clearView(); //clear all references on compare results table: needed if there is a mismatch between references and actual data - - //UI View Filter settings - //compare result - bool leftOnlyFilesActive; - bool rightOnlyFilesActive; - bool leftNewerFilesActive; - bool rightNewerFilesActive; - bool differentFilesActive; - bool equalFilesActive; - bool conflictFilesActive; - //sync preview - bool syncCreateLeftActive; - bool syncCreateRightActive; - bool syncDeleteLeftActive; - bool syncDeleteRightActive; - bool syncDirLeftActive; - bool syncDirRightActive; - bool syncDirNoneActive; - - void resetSettings(); + //synchronization preview + StatusSyncPreview updateSyncPreview(bool hideFiltered, + bool syncCreateLeftActive, + bool syncCreateRightActive, + bool syncDeleteLeftActive, + bool syncDeleteRightActive, + bool syncDirOverwLeftActive, + bool syncDirOverwRightActive, + bool syncDirNoneActive, + bool conflictFilesActive); + + + + FolderComparison& getDataTentative(); //get data for operation that does NOT add or reorder rows! (deletion is okay) + void setData(FolderComparison& newData); //set data, taking ownership: warning std::swap() is used!!! + void removeInvalidRows(); //remove rows that have been deleted meanwhile: call after manual deletion and synchronization! + void clearAllRows(); //clears everything //sorting... enum SortType @@ -91,55 +102,120 @@ namespace FreeFileSync SORT_BY_SYNC_DIRECTION }; - void sortView(const SortType type, const bool onLeft, const bool ascending); + void sortView(const SortType type, const bool onLeft, const bool ascending); //always call this method for sorting, never sort externally! private: - template <bool syncPreviewActive> - StatusInfo update_sub(const bool hideFiltered); + class SerializeHierarchy; struct RefIndex { + RefIndex(unsigned int folderInd, FileSystemObject::ObjectID id) : + folderIndex(folderInd), + objId(id) {} unsigned int folderIndex; - unsigned int rowIndex; + FileSystemObject::ObjectID objId; }; - std::vector<RefIndex> refView; - FolderComparison& folderCmp; + FileSystemObject* getReferencedRow(const RefIndex ref); //returns NULL if not found + const FileSystemObject* getReferencedRow(const RefIndex ref) const; //returns NULL if not found + bool isInvalidRow(const RefIndex& ref) const; + + + std::vector<RefIndex> viewRef; //partial view on sortedRef + // | + // | (update...) + // \|/ + std::vector<RefIndex> sortedRef; //equivalent to folerCmp, but may be sorted + // | + // | (setData) + // \|/ + FolderComparison folderCmp; //actual comparison data: owned by GridView! + + + //sorting classes + template <bool ascending> + class SortByDirectory; + + template <bool ascending, SelectedSide side> + class SortByRelName; + + template <bool ascending, SelectedSide side> + class SortByFileName; + + template <bool ascending, SelectedSide side> + class SortByFileSize; + + template <bool ascending, SelectedSide side> + class SortByDate; + + template <bool ascending> + class SortByCmpResult; + + template <bool ascending> + class SortBySyncDirection; }; + + + + + + + + //############################################################################ //inline implementation + inline - const FileCompareLine& GridView::operator[] (unsigned row) const + const FileSystemObject* GridView::getObject(unsigned int row) const { - const unsigned int folderInd = refView[row].folderIndex; - const unsigned int rowInd = refView[row].rowIndex; - - return folderCmp[folderInd].fileCmp[rowInd]; + if (row < rowsOnView()) + return getReferencedRow(viewRef[row]); + else + return NULL; } inline - FileCompareLine& GridView::operator[] (unsigned row) + FileSystemObject* GridView::getObject(unsigned int row) { //code re-use of const method: see Meyers Effective C++ - return const_cast<FileCompareLine&>(static_cast<const GridView&>(*this).operator[](row)); + return const_cast<FileSystemObject*>(static_cast<const GridView&>(*this).getObject(row)); } inline - unsigned int GridView::elementsOnView() const + unsigned int GridView::rowsOnView() const { - return refView.size(); + return viewRef.size(); } inline - const FolderPair GridView::getFolderPair(const unsigned int row) const + FolderComparison& GridView::getDataTentative() { - const unsigned int folderInd = refView[row].folderIndex; - const FolderCompareLine& folderCmpLine = folderCmp[folderInd]; - return folderCmpLine.syncPair; + return folderCmp; + } + + inline + unsigned int GridView::rowsTotal() const //total number of rows available + { + return sortedRef.size(); + } + + + inline + const FreeFileSync::FileSystemObject* GridView::getReferencedRow(const RefIndex ref) const + { + return folderCmp[ref.folderIndex].retrieveById(ref.objId); + } + + + inline + FreeFileSync::FileSystemObject* GridView::getReferencedRow(const RefIndex ref) + { + //code re-use of const method: see Meyers Effective C++ + return const_cast<FileSystemObject*>(static_cast<const GridView&>(*this).getReferencedRow(ref)); } } diff --git a/ui/guiGenerated.cpp b/ui/guiGenerated.cpp index a3db4dd8..61fc95d3 100644 --- a/ui/guiGenerated.cpp +++ b/ui/guiGenerated.cpp @@ -7,6 +7,7 @@ #include "../library/customGrid.h" #include "../shared/customButton.h" +#include "../shared/toggleButton.h" #include "guiGenerated.h" @@ -31,13 +32,14 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const m_menuFile->AppendSeparator(); - wxMenuItem* m_menuItem14; - m_menuItem14 = new wxMenuItem( m_menuFile, wxID_ANY, wxString( _("S&ave configuration") ) + wxT('\t') + wxT("CTRL-S"), wxEmptyString, wxITEM_NORMAL ); - m_menuFile->Append( m_menuItem14 ); + m_menuItemNew = new wxMenuItem( m_menuFile, wxID_ANY, wxString( _("&New") ) + wxT('\t') + wxT("CTRL-N"), wxEmptyString, wxITEM_NORMAL ); + m_menuFile->Append( m_menuItemNew ); - wxMenuItem* m_menuItem13; - m_menuItem13 = new wxMenuItem( m_menuFile, wxID_ANY, wxString( _("&Load configuration") ) + wxT('\t') + wxT("CTRL-L"), wxEmptyString, wxITEM_NORMAL ); - m_menuFile->Append( m_menuItem13 ); + m_menuItemSave = new wxMenuItem( m_menuFile, wxID_ANY, wxString( _("S&ave configuration") ) + wxT('\t') + wxT("CTRL-S"), wxEmptyString, wxITEM_NORMAL ); + m_menuFile->Append( m_menuItemSave ); + + m_menuItemLoad = new wxMenuItem( m_menuFile, wxID_ANY, wxString( _("&Load configuration") ) + wxT('\t') + wxT("CTRL-L"), wxEmptyString, wxITEM_NORMAL ); + m_menuFile->Append( m_menuItemLoad ); m_menuFile->AppendSeparator(); @@ -235,13 +237,6 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const sbSizer3->Add( m_bpButtonAddPair, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 3 ); - m_bpButtonRemoveTopPair = new wxBitmapButton( m_panelTopRight, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 19,21 ), wxBU_AUTODRAW ); - m_bpButtonRemoveTopPair->SetToolTip( _("Remove folder pair") ); - - m_bpButtonRemoveTopPair->SetToolTip( _("Remove folder pair") ); - - sbSizer3->Add( m_bpButtonRemoveTopPair, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); - m_directoryRight = new wxComboBox( m_panelTopRight, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, NULL, 0 ); sbSizer3->Add( m_directoryRight, 1, wxALIGN_CENTER_VERTICAL, 5 ); @@ -411,7 +406,7 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const bSizer139->Add( m_bpButtonLoad, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); wxArrayString m_choiceHistoryChoices; - m_choiceHistory = new wxChoice( m_panel30, wxID_ANY, wxDefaultPosition, wxSize( 150,-1 ), m_choiceHistoryChoices, 0 ); + m_choiceHistory = new wxChoice( m_panel30, wxID_ANY, wxDefaultPosition, wxSize( 170,-1 ), m_choiceHistoryChoices, 0 ); m_choiceHistory->SetSelection( 0 ); m_choiceHistory->SetToolTip( _("Load configuration history (press DEL to delete items)") ); @@ -465,46 +460,46 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const sbSizer31->Add( 0, 0, 1, wxEXPAND, 5 ); - m_bpButtonLeftOnly = new wxBitmapButton( m_panel112, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 40,40 ), wxBU_AUTODRAW ); + m_bpButtonLeftOnly = new ToggleButton( m_panel112, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 40,40 ), wxBU_AUTODRAW ); sbSizer31->Add( m_bpButtonLeftOnly, 0, wxALIGN_CENTER_VERTICAL, 5 ); - m_bpButtonLeftNewer = new wxBitmapButton( m_panel112, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 40,40 ), wxBU_AUTODRAW ); + m_bpButtonLeftNewer = new ToggleButton( m_panel112, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 40,40 ), wxBU_AUTODRAW ); sbSizer31->Add( m_bpButtonLeftNewer, 0, wxALIGN_CENTER_VERTICAL, 5 ); - m_bpButtonEqual = new wxBitmapButton( m_panel112, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 40,40 ), wxBU_AUTODRAW ); + m_bpButtonEqual = new ToggleButton( m_panel112, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 40,40 ), wxBU_AUTODRAW ); sbSizer31->Add( m_bpButtonEqual, 0, wxALIGN_CENTER_VERTICAL, 5 ); - m_bpButtonDifferent = new wxBitmapButton( m_panel112, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 40,40 ), wxBU_AUTODRAW ); + m_bpButtonDifferent = new ToggleButton( m_panel112, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 40,40 ), wxBU_AUTODRAW ); sbSizer31->Add( m_bpButtonDifferent, 0, wxALIGN_CENTER_VERTICAL, 5 ); - m_bpButtonRightNewer = new wxBitmapButton( m_panel112, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 40,40 ), wxBU_AUTODRAW ); + m_bpButtonRightNewer = new ToggleButton( m_panel112, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 40,40 ), wxBU_AUTODRAW ); sbSizer31->Add( m_bpButtonRightNewer, 0, wxALIGN_CENTER_VERTICAL, 5 ); - m_bpButtonRightOnly = new wxBitmapButton( m_panel112, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 40,40 ), wxBU_AUTODRAW ); + m_bpButtonRightOnly = new ToggleButton( m_panel112, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 40,40 ), wxBU_AUTODRAW ); sbSizer31->Add( m_bpButtonRightOnly, 0, wxALIGN_CENTER_VERTICAL, 5 ); - m_bpButtonSyncCreateLeft = new wxBitmapButton( m_panel112, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 40,40 ), wxBU_AUTODRAW ); + m_bpButtonSyncCreateLeft = new ToggleButton( m_panel112, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 40,40 ), wxBU_AUTODRAW ); sbSizer31->Add( m_bpButtonSyncCreateLeft, 0, wxALIGN_CENTER_VERTICAL, 5 ); - m_bpButtonSyncDirLeft = new wxBitmapButton( m_panel112, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 40,40 ), wxBU_AUTODRAW ); - sbSizer31->Add( m_bpButtonSyncDirLeft, 0, wxALIGN_CENTER_VERTICAL, 5 ); + m_bpButtonSyncDirOverwLeft = new ToggleButton( m_panel112, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 40,40 ), wxBU_AUTODRAW ); + sbSizer31->Add( m_bpButtonSyncDirOverwLeft, 0, wxALIGN_CENTER_VERTICAL, 5 ); - m_bpButtonSyncDeleteLeft = new wxBitmapButton( m_panel112, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 40,40 ), wxBU_AUTODRAW ); + m_bpButtonSyncDeleteLeft = new ToggleButton( m_panel112, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 40,40 ), wxBU_AUTODRAW ); sbSizer31->Add( m_bpButtonSyncDeleteLeft, 0, wxALIGN_CENTER_VERTICAL, 5 ); - m_bpButtonSyncDirNone = new wxBitmapButton( m_panel112, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 40,40 ), wxBU_AUTODRAW ); + m_bpButtonSyncDirNone = new ToggleButton( m_panel112, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 40,40 ), wxBU_AUTODRAW ); sbSizer31->Add( m_bpButtonSyncDirNone, 0, wxALIGN_CENTER_VERTICAL, 5 ); - m_bpButtonSyncDeleteRight = new wxBitmapButton( m_panel112, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 40,40 ), wxBU_AUTODRAW ); + m_bpButtonSyncDeleteRight = new ToggleButton( m_panel112, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 40,40 ), wxBU_AUTODRAW ); sbSizer31->Add( m_bpButtonSyncDeleteRight, 0, wxALIGN_CENTER_VERTICAL, 5 ); - m_bpButtonSyncDirRight = new wxBitmapButton( m_panel112, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 40,40 ), wxBU_AUTODRAW ); - sbSizer31->Add( m_bpButtonSyncDirRight, 0, wxALIGN_CENTER_VERTICAL, 5 ); + m_bpButtonSyncDirOverwRight = new ToggleButton( m_panel112, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 40,40 ), wxBU_AUTODRAW ); + sbSizer31->Add( m_bpButtonSyncDirOverwRight, 0, wxALIGN_CENTER_VERTICAL, 5 ); - m_bpButtonSyncCreateRight = new wxBitmapButton( m_panel112, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 40,40 ), wxBU_AUTODRAW ); + m_bpButtonSyncCreateRight = new ToggleButton( m_panel112, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 40,40 ), wxBU_AUTODRAW ); sbSizer31->Add( m_bpButtonSyncCreateRight, 0, wxALIGN_CENTER_VERTICAL, 5 ); - m_bpButtonConflict = new wxBitmapButton( m_panel112, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 40,40 ), wxBU_AUTODRAW ); + m_bpButtonConflict = new ToggleButton( m_panel112, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 40,40 ), wxBU_AUTODRAW ); sbSizer31->Add( m_bpButtonConflict, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 ); @@ -690,8 +685,9 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const this->Connect( m_menuItem10->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnCompare ) ); this->Connect( m_menuItem11->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnStartSync ) ); this->Connect( m_menuItemSwitchView->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnSwitchView ) ); - this->Connect( m_menuItem14->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnSaveConfig ) ); - this->Connect( m_menuItem13->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnLoadConfig ) ); + this->Connect( m_menuItemNew->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnNewConfig ) ); + this->Connect( m_menuItemSave->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnSaveConfig ) ); + this->Connect( m_menuItemLoad->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnLoadConfig ) ); this->Connect( m_menuItem4->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuQuit ) ); this->Connect( m_menuItemGlobSett->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuGlobalSettings ) ); this->Connect( m_menuItem7->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuBatchJob ) ); @@ -706,7 +702,6 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const m_dirPickerLeft->Connect( wxEVT_COMMAND_DIRPICKER_CHANGED, wxFileDirPickerEventHandler( MainDialogGenerated::OnDirSelected ), NULL, this ); m_bpButtonSwapSides->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSwapSides ), NULL, this ); m_bpButtonAddPair->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnAddFolderPair ), NULL, this ); - m_bpButtonRemoveTopPair->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnRemoveTopFolderPair ), NULL, this ); m_directoryRight->Connect( wxEVT_KEY_DOWN, wxKeyEventHandler( MainDialogGenerated::OnFolderHistoryKeyEvent ), NULL, this ); m_dirPickerRight->Connect( wxEVT_COMMAND_DIRPICKER_CHANGED, wxFileDirPickerEventHandler( MainDialogGenerated::OnDirSelected ), NULL, this ); m_gridLeft->Connect( wxEVT_GRID_CELL_LEFT_DCLICK, wxGridEventHandler( MainDialogGenerated::OnLeftGridDoubleClick ), NULL, this ); @@ -734,11 +729,11 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const m_bpButtonRightNewer->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnRightNewerFiles ), NULL, this ); m_bpButtonRightOnly->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnRightOnlyFiles ), NULL, this ); m_bpButtonSyncCreateLeft->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSyncCreateLeft ), NULL, this ); - m_bpButtonSyncDirLeft->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSyncDirLeft ), NULL, this ); + m_bpButtonSyncDirOverwLeft->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSyncDirLeft ), NULL, this ); m_bpButtonSyncDeleteLeft->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSyncDeleteLeft ), NULL, this ); m_bpButtonSyncDirNone->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSyncDirNone ), NULL, this ); m_bpButtonSyncDeleteRight->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSyncDeleteRight ), NULL, this ); - m_bpButtonSyncDirRight->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSyncDirRight ), NULL, this ); + m_bpButtonSyncDirOverwRight->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSyncDirRight ), NULL, this ); m_bpButtonSyncCreateRight->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSyncCreateRight ), NULL, this ); m_bpButtonConflict->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnConflictFiles ), NULL, this ); m_bpButton10->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnQuit ), NULL, this ); @@ -751,6 +746,7 @@ MainDialogGenerated::~MainDialogGenerated() this->Disconnect( wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnCompare ) ); this->Disconnect( wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnStartSync ) ); this->Disconnect( wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnSwitchView ) ); + this->Disconnect( wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnNewConfig ) ); this->Disconnect( wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnSaveConfig ) ); this->Disconnect( wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnLoadConfig ) ); this->Disconnect( wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuQuit ) ); @@ -767,7 +763,6 @@ MainDialogGenerated::~MainDialogGenerated() m_dirPickerLeft->Disconnect( wxEVT_COMMAND_DIRPICKER_CHANGED, wxFileDirPickerEventHandler( MainDialogGenerated::OnDirSelected ), NULL, this ); m_bpButtonSwapSides->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSwapSides ), NULL, this ); m_bpButtonAddPair->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnAddFolderPair ), NULL, this ); - m_bpButtonRemoveTopPair->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnRemoveTopFolderPair ), NULL, this ); m_directoryRight->Disconnect( wxEVT_KEY_DOWN, wxKeyEventHandler( MainDialogGenerated::OnFolderHistoryKeyEvent ), NULL, this ); m_dirPickerRight->Disconnect( wxEVT_COMMAND_DIRPICKER_CHANGED, wxFileDirPickerEventHandler( MainDialogGenerated::OnDirSelected ), NULL, this ); m_gridLeft->Disconnect( wxEVT_GRID_CELL_LEFT_DCLICK, wxGridEventHandler( MainDialogGenerated::OnLeftGridDoubleClick ), NULL, this ); @@ -795,11 +790,11 @@ MainDialogGenerated::~MainDialogGenerated() m_bpButtonRightNewer->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnRightNewerFiles ), NULL, this ); m_bpButtonRightOnly->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnRightOnlyFiles ), NULL, this ); m_bpButtonSyncCreateLeft->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSyncCreateLeft ), NULL, this ); - m_bpButtonSyncDirLeft->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSyncDirLeft ), NULL, this ); + m_bpButtonSyncDirOverwLeft->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSyncDirLeft ), NULL, this ); m_bpButtonSyncDeleteLeft->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSyncDeleteLeft ), NULL, this ); m_bpButtonSyncDirNone->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSyncDirNone ), NULL, this ); m_bpButtonSyncDeleteRight->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSyncDeleteRight ), NULL, this ); - m_bpButtonSyncDirRight->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSyncDirRight ), NULL, this ); + m_bpButtonSyncDirOverwRight->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSyncDirRight ), NULL, this ); m_bpButtonSyncCreateRight->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSyncCreateRight ), NULL, this ); m_bpButtonConflict->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnConflictFiles ), NULL, this ); m_bpButton10->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnQuit ), NULL, this ); @@ -837,20 +832,32 @@ FolderPairGenerated::FolderPairGenerated( wxWindow* parent, wxWindowID id, const m_panel21->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_MENU ) ); wxBoxSizer* bSizer96; - bSizer96 = new wxBoxSizer( wxVERTICAL ); + bSizer96 = new wxBoxSizer( wxHORIZONTAL ); + + bSizer96->Add( 0, 0, 1, wxEXPAND, 5 ); - bSizer96->Add( 0, 5, 0, 0, 5 ); + m_bpButtonAltFilter = new wxBitmapButton( m_panel21, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 20,20 ), wxBU_AUTODRAW ); + m_bpButtonAltFilter->SetToolTip( _("Select alternate filter settings") ); - m_bitmap23 = new wxStaticBitmap( m_panel21, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 44,17 ), 0 ); - m_bitmap23->SetToolTip( _("Folder pair") ); + m_bpButtonAltFilter->SetToolTip( _("Select alternate filter settings") ); - bSizer96->Add( m_bitmap23, 0, wxALIGN_CENTER_HORIZONTAL|wxRIGHT|wxLEFT, 8 ); + bSizer96->Add( m_bpButtonAltFilter, 0, wxRIGHT|wxALIGN_CENTER_VERTICAL, 5 ); + + m_bpButtonAltSyncCfg = new wxBitmapButton( m_panel21, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 20,20 ), wxBU_AUTODRAW ); + m_bpButtonAltSyncCfg->SetToolTip( _("Select alternate synchronization settings") ); + + m_bpButtonAltSyncCfg->SetToolTip( _("Select alternate synchronization settings") ); + + bSizer96->Add( m_bpButtonAltSyncCfg, 0, wxALIGN_CENTER_VERTICAL, 5 ); + + + bSizer96->Add( 0, 0, 1, wxEXPAND, 5 ); m_panel21->SetSizer( bSizer96 ); m_panel21->Layout(); bSizer96->Fit( m_panel21 ); - bSizer95->Add( m_panel21, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT|wxEXPAND, 5 ); + bSizer95->Add( m_panel21, 0, wxRIGHT|wxLEFT|wxEXPAND|wxALIGN_CENTER_VERTICAL, 5 ); m_panel20->SetSizer( bSizer95 ); m_panel20->Layout(); @@ -961,6 +968,13 @@ BatchFolderPairGenerated::BatchFolderPairGenerated( wxWindow* parent, wxWindowID bSizer114->Add( m_dirPickerLeft, 0, wxALIGN_CENTER_VERTICAL, 5 ); + m_bpButtonAltSyncCfg = new wxBitmapButton( m_panelLeft, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 20,20 ), wxBU_AUTODRAW ); + m_bpButtonAltSyncCfg->SetToolTip( _("Select alternate synchronization settings") ); + + m_bpButtonAltSyncCfg->SetToolTip( _("Select alternate synchronization settings") ); + + bSizer114->Add( m_bpButtonAltSyncCfg, 0, wxALIGN_CENTER_VERTICAL, 5 ); + m_panelLeft->SetSizer( bSizer114 ); m_panelLeft->Layout(); bSizer114->Fit( m_panelLeft ); @@ -978,6 +992,13 @@ BatchFolderPairGenerated::BatchFolderPairGenerated( wxWindow* parent, wxWindowID bSizer115->Add( m_dirPickerRight, 0, wxALIGN_CENTER_VERTICAL, 5 ); + m_bpButtonAltFilter = new wxBitmapButton( m_panelRight, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 20,20 ), wxBU_AUTODRAW ); + m_bpButtonAltFilter->SetToolTip( _("Select alternate filter settings") ); + + m_bpButtonAltFilter->SetToolTip( _("Select alternate filter settings") ); + + bSizer115->Add( m_bpButtonAltFilter, 0, wxALIGN_CENTER_VERTICAL, 5 ); + m_panelRight->SetSizer( bSizer115 ); m_panelRight->Layout(); bSizer115->Fit( m_panelRight ); @@ -1089,13 +1110,6 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS bSizer1361->Add( m_bpButtonAddPair, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 3 ); - m_bpButtonRemoveTopPair = new wxBitmapButton( m_panelMainPair, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 19,21 ), wxBU_AUTODRAW ); - m_bpButtonRemoveTopPair->SetToolTip( _("Remove folder pair") ); - - m_bpButtonRemoveTopPair->SetToolTip( _("Remove folder pair") ); - - bSizer1361->Add( m_bpButtonRemoveTopPair, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); - bSizer147->Add( bSizer1361, 0, wxALIGN_CENTER_VERTICAL, 5 ); wxBoxSizer* bSizer143; @@ -1183,11 +1197,10 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS bSizer100->Add( 0, 10, 0, 0, 5 ); - wxBoxSizer* bSizer57; - bSizer57 = new wxBoxSizer( wxVERTICAL ); - - wxBoxSizer* bSizer156; - bSizer156 = new wxBoxSizer( wxHORIZONTAL ); + wxFlexGridSizer* fgSizer15; + fgSizer15 = new wxFlexGridSizer( 2, 2, 10, 10 ); + fgSizer15->SetFlexibleDirection( wxBOTH ); + fgSizer15->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED ); wxStaticBoxSizer* sbSizer6; sbSizer6 = new wxStaticBoxSizer( new wxStaticBox( m_panelOverview, wxID_ANY, _("Compare by...") ), wxVERTICAL ); @@ -1203,10 +1216,7 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS sbSizer6->Add( m_radioBtnContent, 0, wxTOP, 5 ); - bSizer156->Add( sbSizer6, 0, wxALIGN_CENTER_VERTICAL|wxEXPAND, 5 ); - - - bSizer156->Add( 10, 10, 0, wxEXPAND, 5 ); + fgSizer15->Add( sbSizer6, 0, wxALIGN_CENTER_VERTICAL|wxEXPAND, 5 ); wxStaticBoxSizer* sbSizer24; sbSizer24 = new wxStaticBoxSizer( new wxStaticBox( m_panelOverview, wxID_ANY, wxEmptyString ), wxVERTICAL ); @@ -1229,15 +1239,7 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS sbSizer24->Add( 0, 0, 1, wxEXPAND, 5 ); - bSizer156->Add( sbSizer24, 0, wxEXPAND|wxALIGN_CENTER_VERTICAL, 5 ); - - bSizer57->Add( bSizer156, 0, 0, 5 ); - - - bSizer57->Add( 10, 10, 0, 0, 5 ); - - wxBoxSizer* bSizer721; - bSizer721 = new wxBoxSizer( wxHORIZONTAL ); + fgSizer15->Add( sbSizer24, 0, wxEXPAND|wxALIGN_CENTER_VERTICAL, 5 ); wxStaticBoxSizer* sbSizer25; sbSizer25 = new wxStaticBoxSizer( new wxStaticBox( m_panelOverview, wxID_ANY, _("Error handling") ), wxHORIZONTAL ); @@ -1247,10 +1249,7 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS m_choiceHandleError->SetSelection( 0 ); sbSizer25->Add( m_choiceHandleError, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); - bSizer721->Add( sbSizer25, 0, wxEXPAND, 5 ); - - - bSizer721->Add( 10, 10, 0, 0, 5 ); + fgSizer15->Add( sbSizer25, 0, wxEXPAND, 5 ); wxStaticBoxSizer* sbSizer23; sbSizer23 = new wxStaticBoxSizer( new wxStaticBox( m_panelOverview, wxID_ANY, _("Deletion handling") ), wxVERTICAL ); @@ -1277,11 +1276,9 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS bSizer1151->Fit( m_panelCustomDeletionDir ); sbSizer23->Add( m_panelCustomDeletionDir, 0, 0, 5 ); - bSizer721->Add( sbSizer23, 0, wxEXPAND, 5 ); - - bSizer57->Add( bSizer721, 0, 0, 5 ); + fgSizer15->Add( sbSizer23, 0, wxEXPAND, 5 ); - bSizer100->Add( bSizer57, 0, 0, 5 ); + bSizer100->Add( fgSizer15, 0, 0, 5 ); bSizer120->Add( bSizer100, 1, 0, 5 ); @@ -1317,7 +1314,7 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS wxBoxSizer* bSizer122; bSizer122 = new wxBoxSizer( wxHORIZONTAL ); - m_bitmapLeftOnly = new wxStaticBitmap( m_panelOverview, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 48,48 ), 0 ); + m_bitmapLeftOnly = new wxStaticBitmap( m_panelOverview, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 45,45 ), 0 ); m_bitmapLeftOnly->SetToolTip( _("Files/folders that exist on left side only") ); bSizer122->Add( m_bitmapLeftOnly, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); @@ -1333,7 +1330,7 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS wxBoxSizer* bSizer123; bSizer123 = new wxBoxSizer( wxHORIZONTAL ); - m_bitmapRightOnly = new wxStaticBitmap( m_panelOverview, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 48,48 ), 0 ); + m_bitmapRightOnly = new wxStaticBitmap( m_panelOverview, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 45,45 ), 0 ); m_bitmapRightOnly->SetToolTip( _("Files/folders that exist on right side only") ); bSizer123->Add( m_bitmapRightOnly, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); @@ -1349,7 +1346,7 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS wxBoxSizer* bSizer124; bSizer124 = new wxBoxSizer( wxHORIZONTAL ); - m_bitmapLeftNewer = new wxStaticBitmap( m_panelOverview, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 48,48 ), 0 ); + m_bitmapLeftNewer = new wxStaticBitmap( m_panelOverview, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 45,45 ), 0 ); m_bitmapLeftNewer->SetToolTip( _("Files that exist on both sides, left one is newer") ); bSizer124->Add( m_bitmapLeftNewer, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); @@ -1365,7 +1362,7 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS wxBoxSizer* bSizer125; bSizer125 = new wxBoxSizer( wxHORIZONTAL ); - m_bitmapRightNewer = new wxStaticBitmap( m_panelOverview, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 48,48 ), 0 ); + m_bitmapRightNewer = new wxStaticBitmap( m_panelOverview, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 45,45 ), 0 ); m_bitmapRightNewer->SetToolTip( _("Files that exist on both sides, right one is newer") ); bSizer125->Add( m_bitmapRightNewer, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); @@ -1381,7 +1378,7 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS wxBoxSizer* bSizer126; bSizer126 = new wxBoxSizer( wxHORIZONTAL ); - m_bitmapDifferent = new wxStaticBitmap( m_panelOverview, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 48,48 ), 0 ); + m_bitmapDifferent = new wxStaticBitmap( m_panelOverview, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 45,45 ), 0 ); m_bitmapDifferent->SetToolTip( _("Files that exist on both sides and have different content") ); bSizer126->Add( m_bitmapDifferent, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); @@ -1394,6 +1391,22 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS bSizer121->Add( bSizer126, 0, wxALIGN_CENTER_HORIZONTAL, 5 ); + wxBoxSizer* bSizer127; + bSizer127 = new wxBoxSizer( wxHORIZONTAL ); + + m_bitmapConflict = new wxStaticBitmap( m_panelOverview, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 45,45 ), 0 ); + m_bitmapConflict->SetToolTip( _("Conflicts/files that cannot be categorized") ); + + bSizer127->Add( m_bitmapConflict, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); + + + bSizer127->Add( 5, 0, 0, 0, 5 ); + + m_bpButtonConflict = new wxBitmapButton( m_panelOverview, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 42,42 ), wxBU_AUTODRAW ); + bSizer127->Add( m_bpButtonConflict, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); + + bSizer121->Add( bSizer127, 0, wxALIGN_CENTER_HORIZONTAL, 5 ); + sbSizer61->Add( bSizer121, 0, wxALIGN_CENTER_HORIZONTAL, 5 ); bSizer120->Add( sbSizer61, 0, wxALIGN_CENTER_VERTICAL, 5 ); @@ -1534,7 +1547,6 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS // Connect Events this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( BatchDlgGenerated::OnClose ) ); m_bpButtonAddPair->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( BatchDlgGenerated::OnAddFolderPair ), NULL, this ); - m_bpButtonRemoveTopPair->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( BatchDlgGenerated::OnRemoveTopFolderPair ), NULL, this ); m_radioBtnSizeDate->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( BatchDlgGenerated::OnChangeCompareVar ), NULL, this ); m_radioBtnContent->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( BatchDlgGenerated::OnChangeCompareVar ), NULL, this ); m_checkBoxFilter->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( BatchDlgGenerated::OnCheckFilter ), NULL, this ); @@ -1546,6 +1558,7 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS m_bpButtonLeftNewer->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( BatchDlgGenerated::OnLeftNewer ), NULL, this ); m_bpButtonRightNewer->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( BatchDlgGenerated::OnRightNewer ), NULL, this ); m_bpButtonDifferent->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( BatchDlgGenerated::OnDifferent ), NULL, this ); + m_bpButtonConflict->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( BatchDlgGenerated::OnConflict ), NULL, this ); m_buttonSave->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( BatchDlgGenerated::OnSaveBatchJob ), NULL, this ); m_buttonLoad->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( BatchDlgGenerated::OnLoadBatchJob ), NULL, this ); m_button6->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( BatchDlgGenerated::OnCancel ), NULL, this ); @@ -1556,7 +1569,6 @@ BatchDlgGenerated::~BatchDlgGenerated() // Disconnect Events this->Disconnect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( BatchDlgGenerated::OnClose ) ); m_bpButtonAddPair->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( BatchDlgGenerated::OnAddFolderPair ), NULL, this ); - m_bpButtonRemoveTopPair->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( BatchDlgGenerated::OnRemoveTopFolderPair ), NULL, this ); m_radioBtnSizeDate->Disconnect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( BatchDlgGenerated::OnChangeCompareVar ), NULL, this ); m_radioBtnContent->Disconnect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( BatchDlgGenerated::OnChangeCompareVar ), NULL, this ); m_checkBoxFilter->Disconnect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( BatchDlgGenerated::OnCheckFilter ), NULL, this ); @@ -1568,6 +1580,7 @@ BatchDlgGenerated::~BatchDlgGenerated() m_bpButtonLeftNewer->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( BatchDlgGenerated::OnLeftNewer ), NULL, this ); m_bpButtonRightNewer->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( BatchDlgGenerated::OnRightNewer ), NULL, this ); m_bpButtonDifferent->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( BatchDlgGenerated::OnDifferent ), NULL, this ); + m_bpButtonConflict->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( BatchDlgGenerated::OnConflict ), NULL, this ); m_buttonSave->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( BatchDlgGenerated::OnSaveBatchJob ), NULL, this ); m_buttonLoad->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( BatchDlgGenerated::OnLoadBatchJob ), NULL, this ); m_button6->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( BatchDlgGenerated::OnCancel ), NULL, this ); @@ -1824,18 +1837,14 @@ SyncCfgDlgGenerated::SyncCfgDlgGenerated( wxWindow* parent, wxWindowID id, const bSizer201 = new wxBoxSizer( wxHORIZONTAL ); - wxStaticBoxSizer* sbSizer27; - sbSizer27 = new wxStaticBoxSizer( new wxStaticBox( this, wxID_ANY, _("Error handling") ), wxHORIZONTAL ); + sbSizerErrorHandling = new wxStaticBoxSizer( new wxStaticBox( this, wxID_ANY, _("Error handling") ), wxHORIZONTAL ); wxArrayString m_choiceHandleErrorChoices; m_choiceHandleError = new wxChoice( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, m_choiceHandleErrorChoices, 0 ); m_choiceHandleError->SetSelection( 0 ); - sbSizer27->Add( m_choiceHandleError, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); - - bSizer201->Add( sbSizer27, 0, wxEXPAND, 5 ); - + sbSizerErrorHandling->Add( m_choiceHandleError, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); - bSizer201->Add( 10, 0, 0, 0, 5 ); + bSizer201->Add( sbSizerErrorHandling, 0, wxEXPAND|wxRIGHT, 10 ); wxStaticBoxSizer* sbSizer231; sbSizer231 = new wxStaticBoxSizer( new wxStaticBox( this, wxID_ANY, _("Deletion handling") ), wxVERTICAL ); @@ -1922,7 +1931,7 @@ SyncCfgDlgGenerated::SyncCfgDlgGenerated( wxWindow* parent, wxWindowID id, const wxBoxSizer* bSizer122; bSizer122 = new wxBoxSizer( wxHORIZONTAL ); - m_bitmapLeftOnly = new wxStaticBitmap( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 48,48 ), 0 ); + m_bitmapLeftOnly = new wxStaticBitmap( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 45,45 ), 0 ); m_bitmapLeftOnly->SetToolTip( _("Files/folders that exist on left side only") ); bSizer122->Add( m_bitmapLeftOnly, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); @@ -1938,7 +1947,7 @@ SyncCfgDlgGenerated::SyncCfgDlgGenerated( wxWindow* parent, wxWindowID id, const wxBoxSizer* bSizer123; bSizer123 = new wxBoxSizer( wxHORIZONTAL ); - m_bitmapRightOnly = new wxStaticBitmap( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 48,48 ), 0 ); + m_bitmapRightOnly = new wxStaticBitmap( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 45,45 ), 0 ); m_bitmapRightOnly->SetToolTip( _("Files/folders that exist on right side only") ); bSizer123->Add( m_bitmapRightOnly, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); @@ -1954,7 +1963,7 @@ SyncCfgDlgGenerated::SyncCfgDlgGenerated( wxWindow* parent, wxWindowID id, const wxBoxSizer* bSizer124; bSizer124 = new wxBoxSizer( wxHORIZONTAL ); - m_bitmapLeftNewer = new wxStaticBitmap( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 48,48 ), 0 ); + m_bitmapLeftNewer = new wxStaticBitmap( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 45,45 ), 0 ); m_bitmapLeftNewer->SetToolTip( _("Files that exist on both sides, left one is newer") ); bSizer124->Add( m_bitmapLeftNewer, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); @@ -1970,7 +1979,7 @@ SyncCfgDlgGenerated::SyncCfgDlgGenerated( wxWindow* parent, wxWindowID id, const wxBoxSizer* bSizer125; bSizer125 = new wxBoxSizer( wxHORIZONTAL ); - m_bitmapRightNewer = new wxStaticBitmap( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 48,48 ), 0 ); + m_bitmapRightNewer = new wxStaticBitmap( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 45,45 ), 0 ); m_bitmapRightNewer->SetToolTip( _("Files that exist on both sides, right one is newer") ); bSizer125->Add( m_bitmapRightNewer, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); @@ -1986,7 +1995,7 @@ SyncCfgDlgGenerated::SyncCfgDlgGenerated( wxWindow* parent, wxWindowID id, const wxBoxSizer* bSizer126; bSizer126 = new wxBoxSizer( wxHORIZONTAL ); - m_bitmapDifferent = new wxStaticBitmap( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 48,48 ), 0 ); + m_bitmapDifferent = new wxStaticBitmap( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 45,45 ), 0 ); m_bitmapDifferent->SetToolTip( _("Files that exist on both sides and have different content") ); bSizer126->Add( m_bitmapDifferent, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); @@ -1999,6 +2008,22 @@ SyncCfgDlgGenerated::SyncCfgDlgGenerated( wxWindow* parent, wxWindowID id, const bSizer121->Add( bSizer126, 0, wxALIGN_CENTER_HORIZONTAL, 5 ); + wxBoxSizer* bSizer127; + bSizer127 = new wxBoxSizer( wxHORIZONTAL ); + + m_bitmapConflict = new wxStaticBitmap( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 45,45 ), 0 ); + m_bitmapConflict->SetToolTip( _("Conflicts/files that cannot be categorized") ); + + bSizer127->Add( m_bitmapConflict, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); + + + bSizer127->Add( 5, 0, 0, 0, 5 ); + + m_bpButtonConflict = new wxBitmapButton( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 42,42 ), wxBU_AUTODRAW ); + bSizer127->Add( m_bpButtonConflict, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); + + bSizer121->Add( bSizer127, 0, wxALIGN_CENTER_HORIZONTAL, 5 ); + sbSizer6->Add( bSizer121, 0, wxALIGN_CENTER_HORIZONTAL, 5 ); bSizer181->Add( sbSizer6, 0, wxALIGN_CENTER_HORIZONTAL, 5 ); @@ -2029,6 +2054,7 @@ SyncCfgDlgGenerated::SyncCfgDlgGenerated( wxWindow* parent, wxWindowID id, const m_bpButtonLeftNewer->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SyncCfgDlgGenerated::OnLeftNewer ), NULL, this ); m_bpButtonRightNewer->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SyncCfgDlgGenerated::OnRightNewer ), NULL, this ); m_bpButtonDifferent->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SyncCfgDlgGenerated::OnDifferent ), NULL, this ); + m_bpButtonConflict->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SyncCfgDlgGenerated::OnConflict ), NULL, this ); } SyncCfgDlgGenerated::~SyncCfgDlgGenerated() @@ -2051,6 +2077,7 @@ SyncCfgDlgGenerated::~SyncCfgDlgGenerated() m_bpButtonLeftNewer->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SyncCfgDlgGenerated::OnLeftNewer ), NULL, this ); m_bpButtonRightNewer->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SyncCfgDlgGenerated::OnRightNewer ), NULL, this ); m_bpButtonDifferent->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SyncCfgDlgGenerated::OnDifferent ), NULL, this ); + m_bpButtonConflict->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SyncCfgDlgGenerated::OnConflict ), NULL, this ); } CmpCfgDlgGenerated::CmpCfgDlgGenerated( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style ) @@ -2451,9 +2478,6 @@ HelpDlgGenerated::HelpDlgGenerated( wxWindow* parent, wxWindowID id, const wxStr m_staticText80->Wrap( -1 ); bSizer70->Add( m_staticText80, 0, wxRIGHT|wxLEFT, 5 ); - - bSizer70->Add( 0, 10, 0, 0, 5 ); - m_staticText78 = new wxStaticText( m_scrolledWindow1, wxID_ANY, _("- conflict"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText78->Wrap( -1 ); bSizer70->Add( m_staticText78, 0, wxRIGHT|wxLEFT, 5 ); @@ -2914,7 +2938,7 @@ QuestionDlgGenerated::QuestionDlgGenerated( wxWindow* parent, wxWindowID id, con bSizer24->Add( bSizer26, 1, wxALIGN_CENTER_HORIZONTAL|wxEXPAND|wxBOTTOM, 5 ); - m_checkBoxDontAskAgain = new wxCheckBox( this, wxID_ANY, _("Don't ask me again"), wxDefaultPosition, wxDefaultSize, 0 ); + m_checkBoxDontAskAgain = new wxCheckBox( this, wxID_ANY, _("Do not show this dialog again"), wxDefaultPosition, wxDefaultSize, 0 ); bSizer24->Add( m_checkBoxDontAskAgain, 0, wxALL|wxALIGN_CENTER_HORIZONTAL, 5 ); @@ -3092,7 +3116,7 @@ FilterDlgGenerated::FilterDlgGenerated( wxWindow* parent, wxWindowID id, const w wxBoxSizer* bSizer70; bSizer70 = new wxBoxSizer( wxHORIZONTAL ); - m_staticText44 = new wxStaticText( this, wxID_ANY, _("Only files/directories that pass filtering will be selected for synchronization. The filter will be applied to the name relative(!) to the synchronization directories."), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText44 = new wxStaticText( this, wxID_ANY, _("Only files/directories that pass filtering will be selected for synchronization. The filter will be applied to the name relative(!) to the base synchronization directories."), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText44->Wrap( 400 ); bSizer70->Add( m_staticText44, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); @@ -3230,7 +3254,7 @@ FilterDlgGenerated::FilterDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer22->Add( 0, 0, 1, wxEXPAND, 5 ); - m_button10 = new wxButton( this, wxID_OK, _("&OK"), wxDefaultPosition, wxSize( -1,30 ), 0 ); + m_button10 = new wxButton( this, wxID_OK, _("&Apply"), wxDefaultPosition, wxSize( -1,30 ), 0 ); m_button10->SetDefault(); m_button10->SetFont( wxFont( 10, 74, 90, 92, false, wxT("Tahoma") ) ); @@ -3251,7 +3275,7 @@ FilterDlgGenerated::FilterDlgGenerated( wxWindow* parent, wxWindowID id, const w this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( FilterDlgGenerated::OnClose ) ); m_bpButtonHelp->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( FilterDlgGenerated::OnHelp ), NULL, this ); m_button9->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( FilterDlgGenerated::OnDefault ), NULL, this ); - m_button10->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( FilterDlgGenerated::OnOK ), NULL, this ); + m_button10->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( FilterDlgGenerated::OnApply ), NULL, this ); m_button17->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( FilterDlgGenerated::OnCancel ), NULL, this ); } @@ -3261,7 +3285,7 @@ FilterDlgGenerated::~FilterDlgGenerated() this->Disconnect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( FilterDlgGenerated::OnClose ) ); m_bpButtonHelp->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( FilterDlgGenerated::OnHelp ), NULL, this ); m_button9->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( FilterDlgGenerated::OnDefault ), NULL, this ); - m_button10->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( FilterDlgGenerated::OnOK ), NULL, this ); + m_button10->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( FilterDlgGenerated::OnApply ), NULL, this ); m_button17->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( FilterDlgGenerated::OnCancel ), NULL, this ); } @@ -3360,7 +3384,10 @@ GlobalSettingsDlgGenerated::GlobalSettingsDlgGenerated( wxWindow* parent, wxWind bSizer86 = new wxBoxSizer( wxHORIZONTAL ); m_bitmapSettings = new wxStaticBitmap( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 40,40 ), 0 ); - bSizer86->Add( m_bitmapSettings, 1, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 ); + bSizer86->Add( m_bitmapSettings, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 ); + + + bSizer86->Add( 0, 0, 1, wxALIGN_CENTER_VERTICAL, 5 ); m_panel8 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER|wxTAB_TRAVERSAL ); m_panel8->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_3DLIGHT ) ); @@ -3379,9 +3406,6 @@ GlobalSettingsDlgGenerated::GlobalSettingsDlgGenerated( wxWindow* parent, wxWind bSizer72->Fit( m_panel8 ); bSizer86->Add( m_panel8, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 5 ); - - bSizer86->Add( 0, 0, 1, wxALIGN_CENTER_VERTICAL, 5 ); - bSizer95->Add( bSizer86, 0, wxALIGN_CENTER_HORIZONTAL, 5 ); @@ -3390,24 +3414,6 @@ GlobalSettingsDlgGenerated::GlobalSettingsDlgGenerated( wxWindow* parent, wxWind wxStaticBoxSizer* sbSizer23; sbSizer23 = new wxStaticBoxSizer( new wxStaticBox( this, wxID_ANY, wxEmptyString ), wxVERTICAL ); - wxBoxSizer* bSizer100; - bSizer100 = new wxBoxSizer( wxHORIZONTAL ); - - m_staticText99 = new wxStaticText( this, wxID_ANY, _("File Time tolerance (seconds):"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticText99->Wrap( -1 ); - m_staticText99->Hide(); - m_staticText99->SetToolTip( _("File times that differ by up to the specified number of seconds are still handled as having same time.") ); - - bSizer100->Add( m_staticText99, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); - - m_spinCtrlFileTimeTolerance = new wxSpinCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS|wxSP_WRAP, 0, 2000000000, 0 ); - m_spinCtrlFileTimeTolerance->Hide(); - m_spinCtrlFileTimeTolerance->SetToolTip( _("File times that differ by up to the specified number of seconds are still handled as having same time.") ); - - bSizer100->Add( m_spinCtrlFileTimeTolerance, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); - - sbSizer23->Add( bSizer100, 1, wxEXPAND, 5 ); - wxBoxSizer* bSizer120; bSizer120 = new wxBoxSizer( wxHORIZONTAL ); @@ -3431,44 +3437,81 @@ GlobalSettingsDlgGenerated::GlobalSettingsDlgGenerated( wxWindow* parent, wxWind m_staticline10 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); sbSizer23->Add( m_staticline10, 0, wxEXPAND|wxALIGN_CENTER_HORIZONTAL|wxTOP|wxBOTTOM, 5 ); - wxBoxSizer* bSizer104; - bSizer104 = new wxBoxSizer( wxHORIZONTAL ); - - m_staticTextCommand = new wxStaticText( this, wxID_ANY, _("File Manager integration:"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticTextCommand->Wrap( -1 ); - bSizer104->Add( m_staticTextCommand, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); - - m_textCtrlCommand = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( 150,-1 ), 0 ); - bSizer104->Add( m_textCtrlCommand, 1, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); - - sbSizer23->Add( bSizer104, 1, wxEXPAND, 5 ); - - m_staticline101 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); - sbSizer23->Add( m_staticline101, 0, wxEXPAND | wxALL, 5 ); - wxBoxSizer* bSizer101; bSizer101 = new wxBoxSizer( wxHORIZONTAL ); - m_staticText100 = new wxStaticText( this, wxID_ANY, _("Warnings:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText100 = new wxStaticText( this, wxID_ANY, _("Hidden dialogs:"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText100->Wrap( -1 ); bSizer101->Add( m_staticText100, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); bSizer101->Add( 0, 0, 1, wxEXPAND, 5 ); - m_buttonResetWarnings = new wxButtonWithImage( this, wxID_ANY, _("Reset"), wxDefaultPosition, wxSize( 80,-1 ), 0 ); - m_buttonResetWarnings->SetFont( wxFont( 8, 74, 90, 92, false, wxT("Tahoma") ) ); - m_buttonResetWarnings->SetToolTip( _("Reset all warning messages") ); + m_buttonResetDialogs = new wxButtonWithImage( this, wxID_ANY, _("Reset"), wxDefaultPosition, wxSize( 80,-1 ), 0 ); + m_buttonResetDialogs->SetFont( wxFont( 8, 74, 90, 92, false, wxT("Tahoma") ) ); + m_buttonResetDialogs->SetToolTip( _("Show hidden dialogs") ); - bSizer101->Add( m_buttonResetWarnings, 0, wxALIGN_CENTER_HORIZONTAL|wxRIGHT|wxLEFT|wxALIGN_CENTER_VERTICAL, 5 ); + bSizer101->Add( m_buttonResetDialogs, 0, wxALIGN_CENTER_HORIZONTAL|wxRIGHT|wxLEFT|wxALIGN_CENTER_VERTICAL, 5 ); sbSizer23->Add( bSizer101, 1, wxEXPAND, 5 ); - bSizer95->Add( sbSizer23, 0, wxALIGN_CENTER_HORIZONTAL|wxRIGHT|wxLEFT, 5 ); + bSizer95->Add( sbSizer23, 1, wxALIGN_CENTER_HORIZONTAL|wxRIGHT|wxLEFT|wxEXPAND, 5 ); bSizer95->Add( 0, 10, 0, 0, 5 ); + wxStaticBoxSizer* sbSizer26; + sbSizer26 = new wxStaticBoxSizer( new wxStaticBox( this, wxID_ANY, _("External applications") ), wxHORIZONTAL ); + + + sbSizer26->Add( 0, 0, 1, wxEXPAND, 5 ); + + m_gridCustomCommand = new wxGrid( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 ); + + // Grid + m_gridCustomCommand->CreateGrid( 5, 2 ); + m_gridCustomCommand->EnableEditing( true ); + m_gridCustomCommand->EnableGridLines( true ); + m_gridCustomCommand->EnableDragGridSize( false ); + m_gridCustomCommand->SetMargins( 0, 0 ); + + // Columns + m_gridCustomCommand->SetColSize( 0, 98 ); + m_gridCustomCommand->SetColSize( 1, 179 ); + m_gridCustomCommand->EnableDragColMove( false ); + m_gridCustomCommand->EnableDragColSize( true ); + m_gridCustomCommand->SetColLabelSize( 20 ); + m_gridCustomCommand->SetColLabelValue( 0, _("Description") ); + m_gridCustomCommand->SetColLabelValue( 1, _("Commandline") ); + m_gridCustomCommand->SetColLabelAlignment( wxALIGN_CENTRE, wxALIGN_CENTRE ); + + // Rows + m_gridCustomCommand->EnableDragRowSize( false ); + m_gridCustomCommand->SetRowLabelSize( 0 ); + m_gridCustomCommand->SetRowLabelAlignment( wxALIGN_CENTRE, wxALIGN_CENTRE ); + + // Label Appearance + + // Cell Defaults + m_gridCustomCommand->SetDefaultCellAlignment( wxALIGN_LEFT, wxALIGN_TOP ); + sbSizer26->Add( m_gridCustomCommand, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL|wxEXPAND, 5 ); + + wxBoxSizer* bSizer157; + bSizer157 = new wxBoxSizer( wxVERTICAL ); + + m_bpButtonAddRow = new wxBitmapButton( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 19,21 ), wxBU_AUTODRAW ); + bSizer157->Add( m_bpButtonAddRow, 0, 0, 5 ); + + m_bpButtonRemoveRow = new wxBitmapButton( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 19,21 ), wxBU_AUTODRAW ); + bSizer157->Add( m_bpButtonRemoveRow, 0, 0, 5 ); + + sbSizer26->Add( bSizer157, 0, wxALIGN_CENTER_HORIZONTAL, 5 ); + + + sbSizer26->Add( 0, 0, 1, wxEXPAND, 5 ); + + bSizer95->Add( sbSizer26, 0, wxALIGN_CENTER_HORIZONTAL|wxEXPAND|wxRIGHT|wxLEFT, 5 ); + wxBoxSizer* bSizer97; bSizer97 = new wxBoxSizer( wxHORIZONTAL ); @@ -3496,7 +3539,9 @@ GlobalSettingsDlgGenerated::GlobalSettingsDlgGenerated( wxWindow* parent, wxWind // Connect Events this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( GlobalSettingsDlgGenerated::OnClose ) ); - m_buttonResetWarnings->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( GlobalSettingsDlgGenerated::OnResetWarnings ), NULL, this ); + m_buttonResetDialogs->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( GlobalSettingsDlgGenerated::OnResetDialogs ), NULL, this ); + m_bpButtonAddRow->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( GlobalSettingsDlgGenerated::OnAddRow ), NULL, this ); + m_bpButtonRemoveRow->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( GlobalSettingsDlgGenerated::OnRemoveRow ), NULL, this ); m_buttonOkay->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( GlobalSettingsDlgGenerated::OnOkay ), NULL, this ); m_button9->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( GlobalSettingsDlgGenerated::OnDefault ), NULL, this ); m_button29->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( GlobalSettingsDlgGenerated::OnCancel ), NULL, this ); @@ -3506,7 +3551,9 @@ GlobalSettingsDlgGenerated::~GlobalSettingsDlgGenerated() { // Disconnect Events this->Disconnect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( GlobalSettingsDlgGenerated::OnClose ) ); - m_buttonResetWarnings->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( GlobalSettingsDlgGenerated::OnResetWarnings ), NULL, this ); + m_buttonResetDialogs->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( GlobalSettingsDlgGenerated::OnResetDialogs ), NULL, this ); + m_bpButtonAddRow->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( GlobalSettingsDlgGenerated::OnAddRow ), NULL, this ); + m_bpButtonRemoveRow->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( GlobalSettingsDlgGenerated::OnRemoveRow ), NULL, this ); m_buttonOkay->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( GlobalSettingsDlgGenerated::OnOkay ), NULL, this ); m_button9->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( GlobalSettingsDlgGenerated::OnDefault ), NULL, this ); m_button29->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( GlobalSettingsDlgGenerated::OnCancel ), NULL, this ); @@ -3725,7 +3772,7 @@ PopupFrameGenerated1::PopupFrameGenerated1( wxWindow* parent, wxWindowID id, con bSizer158->Add( m_bitmapLeft, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); m_staticTextMain = new wxStaticText( this, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticTextMain->Wrap( -1 ); + m_staticTextMain->Wrap( 600 ); bSizer158->Add( m_staticTextMain, 0, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); this->SetSizer( bSizer158 ); diff --git a/ui/guiGenerated.h b/ui/guiGenerated.h index 79c07c47..5f4cc208 100644 --- a/ui/guiGenerated.h +++ b/ui/guiGenerated.h @@ -13,6 +13,7 @@ class CustomGridLeft; class CustomGridMiddle; class CustomGridRight; +class ToggleButton; class wxButtonWithImage; #include <wx/string.h> @@ -48,7 +49,6 @@ class wxButtonWithImage; #include <wx/animate.h> #include <wx/treectrl.h> #include <wx/checklst.h> -#include <wx/spinctrl.h> /////////////////////////////////////////////////////////////////////////// @@ -66,6 +66,9 @@ class MainDialogGenerated : public wxFrame wxMenuItem* m_menuItem10; wxMenuItem* m_menuItem11; wxMenuItem* m_menuItemSwitchView; + wxMenuItem* m_menuItemNew; + wxMenuItem* m_menuItemSave; + wxMenuItem* m_menuItemLoad; wxMenu* m_menuAdvanced; wxMenu* m_menuLanguages; wxMenuItem* m_menuItemGlobSett; @@ -97,7 +100,6 @@ class MainDialogGenerated : public wxFrame wxBitmapButton* m_bpButtonSwapSides; wxPanel* m_panelTopRight; wxBitmapButton* m_bpButtonAddPair; - wxBitmapButton* m_bpButtonRemoveTopPair; wxComboBox* m_directoryRight; wxDirPickerCtrl* m_dirPickerRight; wxScrolledWindow* m_scrolledWindowFolderPairs; @@ -120,20 +122,20 @@ class MainDialogGenerated : public wxFrame wxCheckBox* m_checkBoxHideFilt; wxPanel* m_panel112; - wxBitmapButton* m_bpButtonLeftOnly; - wxBitmapButton* m_bpButtonLeftNewer; - wxBitmapButton* m_bpButtonEqual; - wxBitmapButton* m_bpButtonDifferent; - wxBitmapButton* m_bpButtonRightNewer; - wxBitmapButton* m_bpButtonRightOnly; - wxBitmapButton* m_bpButtonSyncCreateLeft; - wxBitmapButton* m_bpButtonSyncDirLeft; - wxBitmapButton* m_bpButtonSyncDeleteLeft; - wxBitmapButton* m_bpButtonSyncDirNone; - wxBitmapButton* m_bpButtonSyncDeleteRight; - wxBitmapButton* m_bpButtonSyncDirRight; - wxBitmapButton* m_bpButtonSyncCreateRight; - wxBitmapButton* m_bpButtonConflict; + ToggleButton* m_bpButtonLeftOnly; + ToggleButton* m_bpButtonLeftNewer; + ToggleButton* m_bpButtonEqual; + ToggleButton* m_bpButtonDifferent; + ToggleButton* m_bpButtonRightNewer; + ToggleButton* m_bpButtonRightOnly; + ToggleButton* m_bpButtonSyncCreateLeft; + ToggleButton* m_bpButtonSyncDirOverwLeft; + ToggleButton* m_bpButtonSyncDeleteLeft; + ToggleButton* m_bpButtonSyncDirNone; + ToggleButton* m_bpButtonSyncDeleteRight; + ToggleButton* m_bpButtonSyncDirOverwRight; + ToggleButton* m_bpButtonSyncCreateRight; + ToggleButton* m_bpButtonConflict; wxBoxSizer* bSizerBottomRight; @@ -166,6 +168,7 @@ class MainDialogGenerated : public wxFrame virtual void OnCompare( wxCommandEvent& event ){ event.Skip(); } virtual void OnStartSync( wxCommandEvent& event ){ event.Skip(); } virtual void OnSwitchView( wxCommandEvent& event ){ event.Skip(); } + virtual void OnNewConfig( wxCommandEvent& event ){ event.Skip(); } virtual void OnSaveConfig( wxCommandEvent& event ){ event.Skip(); } virtual void OnLoadConfig( wxCommandEvent& event ){ event.Skip(); } virtual void OnMenuQuit( wxCommandEvent& event ){ event.Skip(); } @@ -180,7 +183,6 @@ class MainDialogGenerated : public wxFrame virtual void OnDirSelected( wxFileDirPickerEvent& event ){ event.Skip(); } virtual void OnSwapSides( wxCommandEvent& event ){ event.Skip(); } virtual void OnAddFolderPair( wxCommandEvent& event ){ event.Skip(); } - virtual void OnRemoveTopFolderPair( wxCommandEvent& event ){ event.Skip(); } virtual void OnLeftGridDoubleClick( wxGridEvent& event ){ event.Skip(); } virtual void OnContextRim( wxGridEvent& event ){ event.Skip(); } virtual void OnSortLeftGrid( wxGridEvent& event ){ event.Skip(); } @@ -228,14 +230,16 @@ class FolderPairGenerated : public wxPanel protected: wxPanel* m_panel20; - wxPanel* m_panel21; + + wxBitmapButton* m_bpButtonAltFilter; + wxBitmapButton* m_bpButtonAltSyncCfg; public: wxPanel* m_panelLeft; wxTextCtrl* m_directoryLeft; wxDirPickerCtrl* m_dirPickerLeft; - wxStaticBitmap* m_bitmap23; + wxPanel* m_panel21; wxPanel* m_panelRight; wxBitmapButton* m_bpButtonRemovePair; wxTextCtrl* m_directoryRight; @@ -257,7 +261,9 @@ class BatchFolderPairGenerated : public wxPanel wxStaticText* m_staticText53; wxStaticText* m_staticText541; wxPanel* m_panelLeft; + wxBitmapButton* m_bpButtonAltSyncCfg; wxPanel* m_panelRight; + wxBitmapButton* m_bpButtonAltFilter; public: @@ -303,13 +309,10 @@ class BatchDlgGenerated : public wxDialog wxRadioButton* m_radioBtnSizeDate; wxRadioButton* m_radioBtnContent; - wxCheckBox* m_checkBoxFilter; wxCheckBox* m_checkBoxSilent; - wxChoice* m_choiceHandleError; - wxChoice* m_choiceHandleDeletion; wxPanel* m_panelCustomDeletionDir; wxTextCtrl* m_textCtrlCustomDelFolder; @@ -333,6 +336,9 @@ class BatchDlgGenerated : public wxDialog wxStaticBitmap* m_bitmapDifferent; wxBitmapButton* m_bpButtonDifferent; + wxStaticBitmap* m_bitmapConflict; + + wxBitmapButton* m_bpButtonConflict; wxPanel* m_panelFilter; wxStaticText* m_staticText15; @@ -353,7 +359,6 @@ class BatchDlgGenerated : public wxDialog // Virtual event handlers, overide them in your derived class virtual void OnClose( wxCloseEvent& event ){ event.Skip(); } virtual void OnAddFolderPair( wxCommandEvent& event ){ event.Skip(); } - virtual void OnRemoveTopFolderPair( wxCommandEvent& event ){ event.Skip(); } virtual void OnChangeCompareVar( wxCommandEvent& event ){ event.Skip(); } virtual void OnCheckFilter( wxCommandEvent& event ){ event.Skip(); } virtual void OnCheckLogging( wxCommandEvent& event ){ event.Skip(); } @@ -364,6 +369,7 @@ class BatchDlgGenerated : public wxDialog virtual void OnLeftNewer( wxCommandEvent& event ){ event.Skip(); } virtual void OnRightNewer( wxCommandEvent& event ){ event.Skip(); } virtual void OnDifferent( wxCommandEvent& event ){ event.Skip(); } + virtual void OnConflict( wxCommandEvent& event ){ event.Skip(); } virtual void OnSaveBatchJob( wxCommandEvent& event ){ event.Skip(); } virtual void OnLoadBatchJob( wxCommandEvent& event ){ event.Skip(); } virtual void OnCancel( wxCommandEvent& event ){ event.Skip(); } @@ -371,7 +377,6 @@ class BatchDlgGenerated : public wxDialog public: wxBitmapButton* m_bpButtonAddPair; - wxBitmapButton* m_bpButtonRemoveTopPair; wxTextCtrl* m_directoryLeft; wxDirPickerCtrl* m_dirPickerLeft; wxTextCtrl* m_directoryRight; @@ -440,8 +445,8 @@ class SyncCfgDlgGenerated : public wxDialog wxStaticText* m_staticText9; wxBoxSizer* bSizer201; + wxStaticBoxSizer* sbSizerErrorHandling; wxChoice* m_choiceHandleError; - wxChoice* m_choiceHandleDeletion; wxPanel* m_panelCustomDeletionDir; wxTextCtrl* m_textCtrlCustomDelFolder; @@ -469,6 +474,9 @@ class SyncCfgDlgGenerated : public wxDialog wxStaticBitmap* m_bitmapDifferent; wxBitmapButton* m_bpButtonDifferent; + wxStaticBitmap* m_bitmapConflict; + + wxBitmapButton* m_bpButtonConflict; // Virtual event handlers, overide them in your derived class virtual void OnClose( wxCloseEvent& event ){ event.Skip(); } @@ -485,6 +493,7 @@ class SyncCfgDlgGenerated : public wxDialog virtual void OnLeftNewer( wxCommandEvent& event ){ event.Skip(); } virtual void OnRightNewer( wxCommandEvent& event ){ event.Skip(); } virtual void OnDifferent( wxCommandEvent& event ){ event.Skip(); } + virtual void OnConflict( wxCommandEvent& event ){ event.Skip(); } public: @@ -605,7 +614,6 @@ class HelpDlgGenerated : public wxDialog wxStaticText* m_staticText77; wxStaticText* m_staticText79; wxStaticText* m_staticText80; - wxStaticText* m_staticText78; wxScrolledWindow* m_scrolledWindow5; wxStaticText* m_staticText65; @@ -850,7 +858,7 @@ class FilterDlgGenerated : public wxDialog virtual void OnClose( wxCloseEvent& event ){ event.Skip(); } virtual void OnHelp( wxCommandEvent& event ){ event.Skip(); } virtual void OnDefault( wxCommandEvent& event ){ event.Skip(); } - virtual void OnOK( wxCommandEvent& event ){ event.Skip(); } + virtual void OnApply( wxCommandEvent& event ){ event.Skip(); } virtual void OnCancel( wxCommandEvent& event ){ event.Skip(); } @@ -900,22 +908,22 @@ class GlobalSettingsDlgGenerated : public wxDialog protected: wxStaticBitmap* m_bitmapSettings; + wxPanel* m_panel8; wxStaticText* m_staticText56; - - wxStaticText* m_staticText99; - wxSpinCtrl* m_spinCtrlFileTimeTolerance; wxStaticText* m_staticText114; wxCheckBox* m_checkBoxIgnoreOneHour; wxStaticLine* m_staticline10; - wxStaticText* m_staticTextCommand; - wxTextCtrl* m_textCtrlCommand; - wxStaticLine* m_staticline101; wxStaticText* m_staticText100; - wxButtonWithImage* m_buttonResetWarnings; + wxButtonWithImage* m_buttonResetDialogs; + + + wxGrid* m_gridCustomCommand; + wxBitmapButton* m_bpButtonAddRow; + wxBitmapButton* m_bpButtonRemoveRow; wxButton* m_buttonOkay; wxButton* m_button9; @@ -923,7 +931,9 @@ class GlobalSettingsDlgGenerated : public wxDialog // Virtual event handlers, overide them in your derived class virtual void OnClose( wxCloseEvent& event ){ event.Skip(); } - virtual void OnResetWarnings( wxCommandEvent& event ){ event.Skip(); } + virtual void OnResetDialogs( wxCommandEvent& event ){ event.Skip(); } + virtual void OnAddRow( wxCommandEvent& event ){ event.Skip(); } + virtual void OnRemoveRow( wxCommandEvent& event ){ event.Skip(); } virtual void OnOkay( wxCommandEvent& event ){ event.Skip(); } virtual void OnDefault( wxCommandEvent& event ){ event.Skip(); } virtual void OnCancel( wxCommandEvent& event ){ event.Skip(); } diff --git a/ui/guiStatusHandler.cpp b/ui/guiStatusHandler.cpp index 280328da..4e69b66e 100644 --- a/ui/guiStatusHandler.cpp +++ b/ui/guiStatusHandler.cpp @@ -1,8 +1,9 @@ #include "guiStatusHandler.h" #include "smallDialogs.h" -#include "../shared/globalFunctions.h" +#include "../shared/systemConstants.h" #include "mainDialog.h" #include <wx/wupdlock.h> +#include "../shared/globalFunctions.h" CompareStatusHandler::CompareStatusHandler(MainDialog* dlg) : @@ -30,9 +31,6 @@ CompareStatusHandler::CompareStatusHandler(MainDialog* dlg) : CompareStatusHandler::~CompareStatusHandler() { - //ATTENTION: wxAPP->Yield() is called! at this point in time there is a mismatch between - //gridDataView and currentGridData!! make sure gridDataView does NOT access currentGridData!! - updateUiNow(); //ui update before enabling buttons again: prevent strange behaviour of delayed button clicks //reenable complete main dialog @@ -225,19 +223,19 @@ SyncStatusHandler::~SyncStatusHandler() //notify to syncStatusFrame that current process has ended if (abortIsRequested()) { - result+= wxString(_("Synchronization aborted!")) + wxT(" ") + _("You may try to synchronize remaining items again (WITHOUT having to re-compare)!"); + result += wxString(_("Synchronization aborted!")) + wxT(" ") + _("You may try to synchronize remaining items again (WITHOUT having to re-compare)!"); syncStatusFrame->setStatusText_NoUpdate(result.c_str()); syncStatusFrame->processHasFinished(SyncStatus::ABORTED); //enable okay and close events } else if (errorLog.errorsTotal() > 0) { - result+= wxString(_("Synchronization completed with errors!")) + wxT(" ") + _("You may try to synchronize remaining items again (WITHOUT having to re-compare)!"); + result += wxString(_("Synchronization completed with errors!")) + wxT(" ") + _("You may try to synchronize remaining items again (WITHOUT having to re-compare)!"); syncStatusFrame->setStatusText_NoUpdate(result.c_str()); syncStatusFrame->processHasFinished(SyncStatus::FINISHED_WITH_ERROR); } else { - result+= _("Synchronization completed successfully!"); + result += _("Synchronization completed successfully!"); syncStatusFrame->setStatusText_NoUpdate(result.c_str()); syncStatusFrame->processHasFinished(SyncStatus::FINISHED_WITH_SUCCESS); } diff --git a/ui/sorting.h b/ui/sorting.h index c3cf8f97..33a6404f 100644 --- a/ui/sorting.h +++ b/ui/sorting.h @@ -1,282 +1,169 @@ #ifndef SORTING_H_INCLUDED #define SORTING_H_INCLUDED -#include "../structures.h" -#include "../shared/globalFunctions.h" +#include "../fileHierarchy.h" +#include "../shared/systemConstants.h" +#include "../synchronization.h" namespace FreeFileSync { - enum SideToSort - { - SORT_ON_LEFT, - SORT_ON_RIGHT, - }; - - inline - bool stringSmallerThan(const DefaultChar* stringA, const DefaultChar* stringB) + int compareString(const Zstring& stringA, const Zstring& stringB) { -#ifdef FFS_WIN - //case-insensitive comparison! - return FreeFileSync::compareStringsWin32(stringA, stringB) < 0; //way faster than wxString::CmpNoCase() in windows build!!! -#else - return defaultCompare(stringA, stringB) < 0; +#ifdef FFS_WIN //Windows does NOT distinguish between upper/lower-case + return stringA.CmpNoCase(stringB); +#elif defined FFS_LINUX //Linux DOES distinguish between upper/lower-case + return stringA.Cmp(stringB); #endif } inline - int compareString(const wxChar* stringA, const wxChar* stringB, const int lengthA, const int lengthB) + bool stringSmallerThan(const Zstring& stringA, const Zstring& stringB) { -#ifdef FFS_WIN - //case-insensitive comparison! - return FreeFileSync::compareStringsWin32(stringA, stringB, lengthA, lengthB); //way faster than wxString::CmpNoCase() in the windows build!!! -#else - for (int i = 0; i < std::min(lengthA, lengthB); ++i) - { - if (stringA[i] != stringB[i]) - return stringA[i] - stringB[i]; - } - return lengthA - lengthB; - - //equivalent: - //const int rv = strncmp(stringA, stringB, std::min(lengthA, lengthB)); - //return rv != 0 ? rv : lengthA - lengthB; -#endif + return compareString(stringA, stringB) < 0; } - template <SideToSort side> + template <bool ascending, SelectedSide side> inline - bool sortByFileName(const FileCompareLine& a, const FileCompareLine& b) + bool sortByFileName(const FileSystemObject& a, const FileSystemObject& b) { - const FileDescrLine* const descrLineA = side == SORT_ON_LEFT ? &a.fileDescrLeft : &a.fileDescrRight; - const FileDescrLine* const descrLineB = side == SORT_ON_LEFT ? &b.fileDescrLeft : &b.fileDescrRight; - //presort types: first files, then directories then empty rows - if (descrLineA->objType == FileDescrLine::TYPE_NOTHING) + if (a.isEmpty<side>()) return false; //empty rows always last - else if (descrLineB->objType == FileDescrLine::TYPE_NOTHING) + else if (b.isEmpty<side>()) return true; //empty rows always last - if (descrLineA->objType == FileDescrLine::TYPE_DIRECTORY) //sort directories by relative name + if (dynamic_cast<const DirMapping*>(&a)) //sort directories by relative name { - if (descrLineB->objType == FileDescrLine::TYPE_DIRECTORY) - return stringSmallerThan(descrLineA->relativeName.c_str(), descrLineB->relativeName.c_str()); + if (dynamic_cast<const DirMapping*>(&b)) + return stringSmallerThan(a.getRelativeName<side>(), b.getRelativeName<side>()); else return false; } else { - if (descrLineB->objType == FileDescrLine::TYPE_DIRECTORY) + if (dynamic_cast<const DirMapping*>(&b)) return true; else { - const wxChar* stringA = descrLineA->relativeName.c_str(); - const wxChar* stringB = descrLineB->relativeName.c_str(); - - size_t pos = descrLineA->relativeName.findFromEnd(globalFunctions::FILE_NAME_SEPARATOR); //start search beginning from end - if (pos != std::string::npos) - stringA += pos + 1; - - pos = descrLineB->relativeName.findFromEnd(globalFunctions::FILE_NAME_SEPARATOR); //start search beginning from end - if (pos != std::string::npos) - stringB += pos + 1; - - return stringSmallerThan(stringA, stringB); + return ascending ? + stringSmallerThan(a.getShortName<side>(), b.getShortName<side>()) : + stringSmallerThan(b.getShortName<side>(), a.getShortName<side>()); } } } - template <SideToSort side> - bool sortByRelativeName(const FileCompareLine& a, const FileCompareLine& b) + template <bool ascending, SelectedSide side> + bool sortByRelativeName(const FileSystemObject& a, const FileSystemObject& b) { - const FileDescrLine* const descrLineA = side == SORT_ON_LEFT ? &a.fileDescrLeft : &a.fileDescrRight; - const FileDescrLine* const descrLineB = side == SORT_ON_LEFT ? &b.fileDescrLeft : &b.fileDescrRight; - - //extract relative name and filename - const wxChar* const relStringA = descrLineA->relativeName.c_str(); //mustn't be NULL for CompareString() API to work correctly - const wxChar* fileStringA = relStringA; - int relLengthA = 0; - int fileLengthA = 0; - - if (descrLineA->objType == FileDescrLine::TYPE_DIRECTORY) - relLengthA = descrLineA->relativeName.length(); - else if (descrLineA->objType == FileDescrLine::TYPE_FILE) - { - relLengthA = descrLineA->relativeName.findFromEnd(globalFunctions::FILE_NAME_SEPARATOR); //start search beginning from end - if (relLengthA == wxNOT_FOUND) - { - relLengthA = 0; - fileLengthA = descrLineA->relativeName.length(); - } - else - { - fileStringA += relLengthA + 1; - fileLengthA = descrLineA->relativeName.length() - (relLengthA + 1); - } - } - else - return false; //empty rows should be on end of list + if (a.isEmpty<side>()) + return false; //empty rows always last + else if (b.isEmpty<side>()) + return true; //empty rows always last - const wxChar* const relStringB = descrLineB->relativeName.c_str(); //mustn't be NULL for CompareString() API to work correctly - const wxChar* fileStringB = relStringB; - int relLengthB = 0; - int fileLengthB = 0; + const FileMapping* fileObjA = dynamic_cast<const FileMapping*>(&a); + const Zstring relDirNameA = fileObjA != NULL ? + a.getParentRelativeName<side>() : //file + a.getRelativeName<side>(); //directory - if (descrLineB->objType == FileDescrLine::TYPE_DIRECTORY) - relLengthB = descrLineB->relativeName.length(); - else if (descrLineB->objType == FileDescrLine::TYPE_FILE) - { - relLengthB = descrLineB->relativeName.findFromEnd(globalFunctions::FILE_NAME_SEPARATOR); //start search beginning from end - if (relLengthB == wxNOT_FOUND) - { - relLengthB = 0; - fileLengthB = descrLineB->relativeName.length(); - } - else - { - fileStringB += relLengthB + 1; - fileLengthB = descrLineB->relativeName.length() - (relLengthB + 1); - } - } - else - return true; //empty rows should be on end of list + const FileMapping* fileObjB = dynamic_cast<const FileMapping*>(&b); + const Zstring relDirNameB = fileObjB != NULL ? + b.getParentRelativeName<side>() : //file + b.getRelativeName<side>(); //directory //compare relative names without filenames first - const int rv = compareString(relStringA, relStringB, relLengthA, relLengthB); + const int rv = compareString(relDirNameA, relDirNameB); if (rv != 0) - return rv < 0; + return ascending ? rv < 0 : rv > 0; else //compare the filenames { - if (descrLineB->objType == FileDescrLine::TYPE_DIRECTORY) //directories shall appear before files + if (fileObjB == NULL) //directories shall appear before files return false; - else if (descrLineA->objType == FileDescrLine::TYPE_DIRECTORY) + else if (fileObjA == NULL) return true; - return compareString(fileStringA, fileStringB, fileLengthA, fileLengthB) < 0; + return stringSmallerThan(a.getShortName<side>(), b.getShortName<side>()); } } - template <SideToSort side> + template <bool ascending, SelectedSide side> inline - bool sortByFullName(const FileCompareLine& a, const FileCompareLine& b) + bool sortByFileSize(const FileSystemObject& a, const FileSystemObject& b) { - const FileDescrLine* const descrLineA = side == SORT_ON_LEFT ? &a.fileDescrLeft : &a.fileDescrRight; - const FileDescrLine* const descrLineB = side == SORT_ON_LEFT ? &b.fileDescrLeft : &b.fileDescrRight; - - //presort types: first files, then directories then empty rows - if (descrLineA->objType == FileDescrLine::TYPE_NOTHING) + if (a.isEmpty<side>()) return false; //empty rows always last - else if (descrLineB->objType == FileDescrLine::TYPE_NOTHING) + else if (b.isEmpty<side>()) return true; //empty rows always last - else -#ifdef FFS_WIN //case-insensitive comparison! - return FreeFileSync::compareStringsWin32(descrLineA->fullName.c_str(), descrLineB->fullName.c_str()) < 0; //way faster than wxString::CmpNoCase() in windows build!!! -#else - return descrLineA->fullName.Cmp(descrLineB->fullName) < 0; -#endif - } - - template <SideToSort side> - inline - bool sortByFileSize(const FileCompareLine& a, const FileCompareLine& b) - { - const FileDescrLine* const descrLineA = side == SORT_ON_LEFT ? &a.fileDescrLeft : &a.fileDescrRight; - const FileDescrLine* const descrLineB = side == SORT_ON_LEFT ? &b.fileDescrLeft : &b.fileDescrRight; - //presort types: first files, then directories then empty rows - if (descrLineA->objType == FileDescrLine::TYPE_NOTHING) - return false; //empty rows always last - else if (descrLineB->objType == FileDescrLine::TYPE_NOTHING) - return true; //empty rows always last + const FileMapping* fileObjA = dynamic_cast<const FileMapping*>(&a); + const FileMapping* fileObjB = dynamic_cast<const FileMapping*>(&b); + if (fileObjA == NULL) + return false; //directories last + else if (fileObjB == NULL) + return true; //directories last - if (descrLineA->objType == FileDescrLine::TYPE_DIRECTORY) //sort directories by relative name - { - if (descrLineB->objType == FileDescrLine::TYPE_DIRECTORY) - return stringSmallerThan(descrLineA->relativeName.c_str(), descrLineB->relativeName.c_str()); - else - return false; - } - else - { - if (descrLineB->objType == FileDescrLine::TYPE_DIRECTORY) - return true; - else - return descrLineA->fileSize > descrLineB->fileSize; //sortAscending shall result in list beginning with largest files first - } + return ascending ? + fileObjA->getFileSize<side>() > fileObjB->getFileSize<side>() : //sortAscending shall result in list beginning with largest files first + fileObjA->getFileSize<side>() < fileObjB->getFileSize<side>(); } - template <SideToSort side> + template <bool ascending, SelectedSide side> inline - bool sortByDate(const FileCompareLine& a, const FileCompareLine& b) + bool sortByDate(const FileSystemObject& a, const FileSystemObject& b) { - const FileDescrLine* const descrLineA = side == SORT_ON_LEFT ? &a.fileDescrLeft : &a.fileDescrRight; - const FileDescrLine* const descrLineB = side == SORT_ON_LEFT ? &b.fileDescrLeft : &b.fileDescrRight; - - //presort types: first files, then directories then empty rows - if (descrLineA->objType == FileDescrLine::TYPE_NOTHING) + if (a.isEmpty<side>()) return false; //empty rows always last - else if (descrLineB->objType == FileDescrLine::TYPE_NOTHING) + else if (b.isEmpty<side>()) return true; //empty rows always last - if (descrLineA->objType == FileDescrLine::TYPE_DIRECTORY) //sort directories by relative name - { - if (descrLineB->objType == FileDescrLine::TYPE_DIRECTORY) - return stringSmallerThan(descrLineA->relativeName.c_str(), descrLineB->relativeName.c_str()); - else - return false; - } - else - { - if (descrLineB->objType == FileDescrLine::TYPE_DIRECTORY) - return true; - else - return descrLineA->lastWriteTimeRaw > descrLineB->lastWriteTimeRaw; - } + + const FileMapping* fileObjA = dynamic_cast<const FileMapping*>(&a); + const FileMapping* fileObjB = dynamic_cast<const FileMapping*>(&b); + + if (fileObjA == NULL) + return false; //directories last + else if (fileObjB == NULL) + return true; //directories last + + return ascending ? + fileObjA->getLastWriteTime<side>() > fileObjB->getLastWriteTime<side>() : + fileObjA->getLastWriteTime<side>() < fileObjB->getLastWriteTime<side>(); } + template <bool ascending> inline - bool sortByCmpResult(const FileCompareLine& a, const FileCompareLine& b) + bool sortByCmpResult(const FileSystemObject& a, const FileSystemObject& b) { //presort result: equal shall appear at end of list - if (a.cmpResult == FILE_EQUAL) + if (a.getCategory() == FILE_EQUAL) return false; - if (b.cmpResult == FILE_EQUAL) + if (b.getCategory() == FILE_EQUAL) return true; - return a.cmpResult < b.cmpResult; - } - - - inline - bool sortBySyncDirection(const FileCompareLine& a, const FileCompareLine& b) - { - return a.syncDir < b.syncDir; + return ascending ? + a.getCategory() < b.getCategory() : + a.getCategory() > b.getCategory(); } - template <SideToSort side> + template <bool ascending> inline - bool sortByDirectory(const FolderCompareLine& a, const FolderCompareLine& b) + bool sortBySyncDirection(const FileSystemObject& a, const FileSystemObject& b) { - const Zstring& dirNameA = side == SORT_ON_LEFT ? a.syncPair.leftDirectory : a.syncPair.rightDirectory; - const Zstring& dirNameB = side == SORT_ON_LEFT ? b.syncPair.leftDirectory : b.syncPair.rightDirectory; - -#ifdef FFS_WIN //case-insensitive comparison! - return FreeFileSync::compareStringsWin32(dirNameA.c_str(), dirNameB.c_str()) < 0; //way faster than wxString::CmpNoCase() in windows build!!! -#elif defined FFS_LINUX - return dirNameA.Cmp(dirNameB) < 0; -#endif + return ascending ? + getSyncOperation(a) < getSyncOperation(b) : + getSyncOperation(a) > getSyncOperation(b); } } diff --git a/version/version.h b/version/version.h index d5f88ca8..15fb7512 100644 --- a/version/version.h +++ b/version/version.h @@ -2,5 +2,5 @@ namespace FreeFileSync { - static const wxString currentVersion = wxT("2.2"); //internal linkage! + static const wxString currentVersion = wxT("2.3"); //internal linkage! } |