From 2dd6739826c86ca96a6a1548fd2e0fb7c0eb8dd0 Mon Sep 17 00:00:00 2001 From: Daniel Wilhelm Date: Wed, 9 May 2018 00:13:16 +0200 Subject: 10.0 --- Changelog.txt | 16 + FreeFileSync/Build/Help/FreeFileSync.hhc | 12 +- FreeFileSync/Build/Help/FreeFileSync.hhp | 5 +- FreeFileSync/Build/Help/html/command-line.html | 2 +- .../Build/Help/html/comparison-settings.html | 7 +- .../Build/Help/html/daylight-saving-time.html | 2 +- FreeFileSync/Build/Help/html/exclude-items.html | 2 +- FreeFileSync/Build/Help/html/ftp-setup.html | 79 + FreeFileSync/Build/Help/html/performance.html | 72 + FreeFileSync/Build/Help/html/realtimesync.html | 24 +- FreeFileSync/Build/Help/html/run-as-service.html | 2 +- .../Build/Help/html/schedule-a-batch-job.html | 126 -- .../Build/Help/html/schedule-batch-jobs.html | 126 ++ .../Build/Help/html/synchronize-with-sftp.html | 75 - .../Build/Help/html/volume-shadow-copy.html | 4 +- .../Build/Help/images/comparison-settings.png | Bin 13744 -> 23375 bytes .../images/comparison-variant-double-click.png | Bin 4051 -> 5818 bytes FreeFileSync/Build/Help/images/filter.png | Bin 18843 -> 25733 bytes FreeFileSync/Build/Help/images/gnome-scheduler.png | Bin 46979 -> 49329 bytes .../Build/Help/images/ignore-time-shift.png | Bin 11712 -> 16368 bytes FreeFileSync/Build/Help/images/performance.png | Bin 0 -> 5457 bytes .../Build/Help/images/realtimesync-schedule.png | Bin 8708 -> 12688 bytes FreeFileSync/Build/Help/images/sftp-login.png | Bin 8127 -> 11307 bytes .../Build/Help/images/sftp-performance.png | Bin 4676 -> 7369 bytes .../Build/Help/images/synchronization-settings.png | Bin 28610 -> 36070 bytes .../synchronization-variant-double-click.png | Bin 4033 -> 6422 bytes .../Build/Help/images/windows-scheduler.png | Bin 9166 -> 13628 bytes FreeFileSync/Build/Help/images/xp-scheduler.png | Bin 18530 -> 20957 bytes FreeFileSync/Build/Languages/arabic.lng | 2059 -------------------- FreeFileSync/Build/Languages/bulgarian.lng | 100 +- FreeFileSync/Build/Languages/chinese_simple.lng | 100 +- .../Build/Languages/chinese_traditional.lng | 100 +- FreeFileSync/Build/Languages/croatian.lng | 100 +- FreeFileSync/Build/Languages/czech.lng | 100 +- FreeFileSync/Build/Languages/danish.lng | 100 +- FreeFileSync/Build/Languages/dutch.lng | 102 +- FreeFileSync/Build/Languages/english_uk.lng | 100 +- FreeFileSync/Build/Languages/finnish.lng | 1999 ------------------- FreeFileSync/Build/Languages/french.lng | 100 +- FreeFileSync/Build/Languages/german.lng | 100 +- FreeFileSync/Build/Languages/greek.lng | 100 +- FreeFileSync/Build/Languages/hebrew.lng | 100 +- FreeFileSync/Build/Languages/hindi.lng | 100 +- FreeFileSync/Build/Languages/hungarian.lng | 102 +- FreeFileSync/Build/Languages/italian.lng | 102 +- FreeFileSync/Build/Languages/japanese.lng | 100 +- FreeFileSync/Build/Languages/korean.lng | 110 +- FreeFileSync/Build/Languages/lithuanian.lng | 100 +- FreeFileSync/Build/Languages/norwegian.lng | 100 +- FreeFileSync/Build/Languages/polish.lng | 197 +- FreeFileSync/Build/Languages/portuguese.lng | 100 +- FreeFileSync/Build/Languages/portuguese_br.lng | 100 +- FreeFileSync/Build/Languages/romanian.lng | 100 +- FreeFileSync/Build/Languages/russian.lng | 100 +- FreeFileSync/Build/Languages/slovak.lng | 100 +- FreeFileSync/Build/Languages/slovenian.lng | 100 +- FreeFileSync/Build/Languages/spanish.lng | 110 +- FreeFileSync/Build/Languages/swedish.lng | 100 +- FreeFileSync/Build/Languages/turkish.lng | 102 +- FreeFileSync/Build/Languages/ukrainian.lng | 100 +- FreeFileSync/Build/Resources.zip | Bin 318587 -> 319981 bytes FreeFileSync/Source/Makefile | 145 +- FreeFileSync/Source/RealTimeSync/Makefile | 73 +- FreeFileSync/Source/RealTimeSync/main_dlg.cpp | 10 +- FreeFileSync/Source/RealTimeSync/xml_proc.cpp | 165 +- FreeFileSync/Source/RealTimeSync/xml_proc.h | 6 +- FreeFileSync/Source/algorithm.cpp | 101 +- FreeFileSync/Source/algorithm.h | 2 +- FreeFileSync/Source/application.cpp | 22 +- FreeFileSync/Source/comparison.cpp | 92 +- FreeFileSync/Source/comparison.h | 3 +- FreeFileSync/Source/file_hierarchy.cpp | 2 +- FreeFileSync/Source/file_hierarchy.h | 5 +- FreeFileSync/Source/fs/abstract.cpp | 92 +- FreeFileSync/Source/fs/abstract.h | 210 +- FreeFileSync/Source/fs/native.cpp | 365 +++- FreeFileSync/Source/lib/dir_exist_async.h | 60 +- FreeFileSync/Source/lib/dir_lock.cpp | 9 +- FreeFileSync/Source/lib/generate_logfile.cpp | 246 +++ FreeFileSync/Source/lib/generate_logfile.h | 174 +- FreeFileSync/Source/lib/hard_filter.cpp | 6 +- FreeFileSync/Source/lib/hard_filter.h | 2 +- FreeFileSync/Source/lib/icon_buffer.cpp | 12 +- FreeFileSync/Source/lib/parallel_scan.cpp | 288 +-- FreeFileSync/Source/lib/parallel_scan.h | 13 +- FreeFileSync/Source/lib/parse_lng.h | 6 +- FreeFileSync/Source/lib/process_xml.cpp | 110 +- FreeFileSync/Source/lib/status_handler.h | 14 +- FreeFileSync/Source/lib/status_handler_impl.h | 28 +- FreeFileSync/Source/lib/versioning.cpp | 99 +- FreeFileSync/Source/lib/versioning.h | 20 +- FreeFileSync/Source/process_callback.h | 23 +- FreeFileSync/Source/structures.cpp | 15 +- FreeFileSync/Source/structures.h | 18 +- FreeFileSync/Source/synchronization.cpp | 1504 ++++++++++---- FreeFileSync/Source/synchronization.h | 18 +- FreeFileSync/Source/ui/batch_config.cpp | 5 +- FreeFileSync/Source/ui/batch_status_handler.cpp | 82 +- FreeFileSync/Source/ui/batch_status_handler.h | 4 +- FreeFileSync/Source/ui/folder_selector.cpp | 13 +- FreeFileSync/Source/ui/folder_selector.h | 14 +- FreeFileSync/Source/ui/gui_generated.cpp | 569 +++--- FreeFileSync/Source/ui/gui_generated.h | 94 +- FreeFileSync/Source/ui/gui_status_handler.cpp | 16 +- FreeFileSync/Source/ui/gui_status_handler.h | 6 +- FreeFileSync/Source/ui/main_dlg.cpp | 356 ++-- FreeFileSync/Source/ui/main_dlg.h | 5 +- FreeFileSync/Source/ui/small_dlgs.cpp | 7 +- FreeFileSync/Source/ui/sync_cfg.cpp | 210 +- FreeFileSync/Source/ui/sync_cfg.h | 4 +- FreeFileSync/Source/ui/tree_grid.cpp | 6 +- FreeFileSync/Source/ui/version_check.cpp | 4 +- FreeFileSync/Source/version/version.h | 2 +- wx+/image_resources.cpp | 29 +- xBRZ/src/xbrz.cpp | 1262 ++++++++++++ xBRZ/src/xbrz.h | 80 + xBRZ/src/xbrz_config.h | 35 + xBRZ/src/xbrz_tools.h | 268 +++ zen/deprecate.h | 4 + zen/file_access.cpp | 69 +- zen/file_traverser.cpp | 25 +- zen/guid.h | 4 + zen/scope_guard.h | 2 +- zen/stl_tools.h | 44 +- zen/thread.h | 72 +- zen/type_traits.h | 6 + zen/warn_static.h | 2 + zen/xml_io.cpp | 2 +- zen/xml_io.h | 2 +- zen/zstring.cpp | 6 +- zen/zstring.h | 4 +- 131 files changed, 7460 insertions(+), 7548 deletions(-) create mode 100755 FreeFileSync/Build/Help/html/ftp-setup.html create mode 100755 FreeFileSync/Build/Help/html/performance.html delete mode 100755 FreeFileSync/Build/Help/html/schedule-a-batch-job.html create mode 100755 FreeFileSync/Build/Help/html/schedule-batch-jobs.html delete mode 100755 FreeFileSync/Build/Help/html/synchronize-with-sftp.html create mode 100755 FreeFileSync/Build/Help/images/performance.png delete mode 100755 FreeFileSync/Build/Languages/arabic.lng delete mode 100755 FreeFileSync/Build/Languages/finnish.lng create mode 100755 FreeFileSync/Source/lib/generate_logfile.cpp create mode 100755 xBRZ/src/xbrz.cpp create mode 100755 xBRZ/src/xbrz.h create mode 100755 xBRZ/src/xbrz_config.h create mode 100755 xBRZ/src/xbrz_tools.h diff --git a/Changelog.txt b/Changelog.txt index 47476388..44d49196 100755 --- a/Changelog.txt +++ b/Changelog.txt @@ -1,3 +1,19 @@ +FreeFileSync 10.0 +----------------- +The installer is now ad-free! +Sync multiple files in parallel (Donation Edition) +Compare multiple files in parallel within a single folder tree +Aggregate worker threads per device during folder traversal +Reset GUI layout configuration for high DPI displays +Keep GUI responsive during synchronization +Remember maximum number of visible folder pairs +Fixed high DPI issues in installer +Don't delay errors by callback interval during comparison +Handle concurrent intermediate folder creation for versioning +Sync all folder level items before recursion (avoid CWDs) +Updated translation files + + FreeFileSync 9.9 [2018-03-09] ----------------------------- High DPI display support diff --git a/FreeFileSync/Build/Help/FreeFileSync.hhc b/FreeFileSync/Build/Help/FreeFileSync.hhc index 1b2ba311..637167ca 100755 --- a/FreeFileSync/Build/Help/FreeFileSync.hhc +++ b/FreeFileSync/Build/Help/FreeFileSync.hhc @@ -48,16 +48,20 @@
  • - - + + + +
  • + +
  • - - + +
  • diff --git a/FreeFileSync/Build/Help/FreeFileSync.hhp b/FreeFileSync/Build/Help/FreeFileSync.hhp index 92006f89..45b4497c 100755 --- a/FreeFileSync/Build/Help/FreeFileSync.hhp +++ b/FreeFileSync/Build/Help/FreeFileSync.hhp @@ -18,9 +18,10 @@ html\expert-settings.html html\external-applications.html html\freefilesync.html html\macros.html -html\schedule-a-batch-job.html +html\performance.html +html\schedule-batch-jobs.html html\synchronization-settings.html -html\synchronize-with-sftp.html +html\ftp-setup.html html\tips-and-tricks.html html\variable-drive-letters.html html\versioning.html diff --git a/FreeFileSync/Build/Help/html/command-line.html b/FreeFileSync/Build/Help/html/command-line.html index 37ebf902..78a9fc75 100755 --- a/FreeFileSync/Build/Help/html/command-line.html +++ b/FreeFileSync/Build/Help/html/command-line.html @@ -20,7 +20,7 @@
    Command line syntax -
    +

    1. Run a FreeFileSync batch job

    diff --git a/FreeFileSync/Build/Help/html/comparison-settings.html b/FreeFileSync/Build/Help/html/comparison-settings.html index eb885f4b..d83877c8 100755 --- a/FreeFileSync/Build/Help/html/comparison-settings.html +++ b/FreeFileSync/Build/Help/html/comparison-settings.html @@ -108,10 +108,9 @@ the target of each link is copied during synchronization.
     

  • Direct: - Evaluate the symbolic link object - directly. Symbolic links will be shown as separate entities. - Links pointing to directories are not traversed and the link object - is copied directly during synchronization. + Evaluate the symbolic link object directly. Symbolic links will be shown as separate entities. + Links pointing to directories are not traversed and the link object itself + is copied during synchronization.
    diff --git a/FreeFileSync/Build/Help/html/daylight-saving-time.html b/FreeFileSync/Build/Help/html/daylight-saving-time.html index c627b45f..1b68d93e 100755 --- a/FreeFileSync/Build/Help/html/daylight-saving-time.html +++ b/FreeFileSync/Build/Help/html/daylight-saving-time.html @@ -59,7 +59,7 @@
    -
  • Alternatively, you can avoid the problem in first place by only synchronizing from FAT to FAT or NTFS to NTFS file systems. +
  • Alternatively, you can avoid the problem in the first place by only synchronizing from FAT to FAT or NTFS to NTFS file systems. Since most local disks are formatted with NTFS and USB memory sticks with FAT, this situation could be handled by formatting the USB stick with NTFS as well. diff --git a/FreeFileSync/Build/Help/html/exclude-items.html b/FreeFileSync/Build/Help/html/exclude-items.html index 7e12a70c..0563f5ef 100755 --- a/FreeFileSync/Build/Help/html/exclude-items.html +++ b/FreeFileSync/Build/Help/html/exclude-items.html @@ -91,7 +91,7 @@ Note
    • For simple exclusions, just right-click and exclude one item or a list - of items directly via the context menu on main dialog. + of items directly via the context menu on main dialog.
    • A filter phrase is compared against both file and directory paths. If you want to consider directories diff --git a/FreeFileSync/Build/Help/html/ftp-setup.html b/FreeFileSync/Build/Help/html/ftp-setup.html new file mode 100755 index 00000000..e75c54d3 --- /dev/null +++ b/FreeFileSync/Build/Help/html/ftp-setup.html @@ -0,0 +1,79 @@ + + + + + + SFTP and FTP Setup + + + +

      SFTP and FTP Setup (Windows, macOS)

      + +

      + FreeFileSync supports synchronization with SFTP and FTP natively. Just enter your login information into the dialog shown for cloud folder selection: + Cloud folder button
      +
      + Enter SFTP login data +

      + +
      + Note
      In case the (S)FTP server sets file modification times to the current time + you can do a Compare by File Size as a workaround. + Another solution is to set up the Two way variant and have the files with the newer dates + be copied back from the server during the next synchronization. +
      +
      + +

      Configure SFTP for best performance

      +

      + By default, FreeFileSync creates one connection to the server and uses one SFTP channel, i.e. only a single SFTP command can be sent and received at a time. + Since most of this time is spent waiting due to the high latency of the remote connection, you can speed up reading large folder hierarchies + by increasing both the connection and channel count.
      +
      + The folder reading time is reduced by a factor of N x M when using N connections with M channels each. +

      + + Example: 10 connections using 2 channels each can yield a 20 times faster folder reading.
      + +
       
      + Set up SFTP for best performance +
       
      + +
        +
      • The creation of additional connections and channels takes time. If you are only scanning a small remote folder, + setting up too many connections and channels might actually slow the overall process down. + Creating extra connections is slower than creating extra channels.
          + +
      • SFTP servers have internal limits on the number of allowed connections and channels. + Generally, servers expect one connection per user, so this number should be kept rather low. + If too many connections and channels are used, the server may decide to stop responding.
          + +
      • Unlike connections, additional SFTP channels are (currently) only used during folder reading (comparison), but not during synchronization. +
      +
      +
      + Advice
      Start with low numbers and make tests with different combinations of connections and channels for your + particular SFTP synchronization scenario to see what gives the highest speed. + Note, however, that FreeFileSync reuses existing SFTP connections/channels. + Therefore, you should restart FreeFileSync before measuring SFTP speed. +
      +
      + +

      SFTP Setup (Linux)

      + +

      An SFTP share can be mapped to a local folder for use with FreeFileSync:

      + +
      +
        +
      • Install: +
        sudo apt-get install sshfs

        + +
      • Mount SFTP share: +
        sshfs ssh-account@ssh-server:<path> mountpoint

        + +
      • Unmount:
        +
        fusermount -u mountpoint
        +
      +
      + + diff --git a/FreeFileSync/Build/Help/html/performance.html b/FreeFileSync/Build/Help/html/performance.html new file mode 100755 index 00000000..37098ed3 --- /dev/null +++ b/FreeFileSync/Build/Help/html/performance.html @@ -0,0 +1,72 @@ + + + + + + Performance Improvements + + + +

      Performance Improvements

      + +

      + Performance settings + FreeFileSync can be set up to issue multiple file accesses + in parallel. This speeds up synchronization times dramatically in + cases where single I/O operations have significant latency + (e.g. long response times on a slow network connection) + or they cannot use the full bandwidth available + (e.g. a SFTP server that has a speed limit for each connection).
      +
      + The number of parallel file operations that FreeFileSync should use + can be set up for each device individually + in the Comparison Settings dialog. + It is considered for all folder pairs of a configuration as follows: +

      +
        +
      • During comparison FreeFileSync first groups all folders by their root devices.
        +
         
        + For example, consider a configuration with two folder pairs and parallel file operations set up: +
        + + + +
        C:\Source D:\Target
        C:\Source2 E:\Target
        + + + + + + + + +
        Parallel operationsDevice root
        1 C:\
        2 D:\
        3 E:\
        +
        +
         
        + FreeFileSync will put the folders C:\Source and + C:\Source2 + into the same group and allow only 1 file operation at a time. + Folder D:\Target will be traversed using 2 operations, + and E:\Target using 3 operations at a time. + In total FreeFileSync will be scanning all folders + employing 6 file operations in parallel.
        +
        + +
      • When synchronizing a folder pair FreeFileSync + will use the maximum of the number of parallel operations + that the two folders support.
        +
        + In the previous example the folder pair + C:\SourceD:\Target + will be synchronized using 2 parallel operations, and + C:\Source2E:\target + will be using 3. +
      +
      + Note
      + FreeFileSync implements parallel file operations by opening multiple connections to a device. + Some devices like SFTP servers have limits on how many connections they allow and will + fail if too many are attempted; see (S)FTP Setup. +
      + + diff --git a/FreeFileSync/Build/Help/html/realtimesync.html b/FreeFileSync/Build/Help/html/realtimesync.html index 6053ba62..6abbdbb2 100755 --- a/FreeFileSync/Build/Help/html/realtimesync.html +++ b/FreeFileSync/Build/Help/html/realtimesync.html @@ -85,21 +85,19 @@

      Example: Log names of changed files and directories (Windows)

      -

      -

      - Show which file or directory has triggered a change. Enter command line:
      -
      -     cmd /c echo %change_action% "%change_path%" & pause -
      -
      - - Write a list of all changes to a log file:
      -
      -     cmd /c echo %change_action% "%change_path%" >> %csidl_Desktop%\log.txt -
      +
      + Show which file or directory has triggered a change. Enter command line:
      +
      +     cmd /c echo %change_action% "%change_path%" & pause
      -

      +
      + Write a list of all changes to a log file:
      +
      +     cmd /c echo %change_action% "%change_path%" >> %csidl_Desktop%\log.txt +
      +
      +
      Note
      When RealTimeSync executes a Windows batch file (bat or cmd) a black console window is shown. You can hide it using the Visual Basic script diff --git a/FreeFileSync/Build/Help/html/run-as-service.html b/FreeFileSync/Build/Help/html/run-as-service.html index 27fc39ee..165ed84b 100755 --- a/FreeFileSync/Build/Help/html/run-as-service.html +++ b/FreeFileSync/Build/Help/html/run-as-service.html @@ -41,7 +41,7 @@
    • RealTimeSync should be monitoring while Windows is running, irrespective of currently logged in users:
      Create a new task in your operating systems's task scheduler and have it execute the command line above when the system starts. - See Schedule a Batch Job for an example of how to add a task. Then change + See Schedule Batch Jobs for an example of how to add a task. Then change the user which runs the task to SYSTEM - a special user account always running in the background.

      Schedule RealTimeSync diff --git a/FreeFileSync/Build/Help/html/schedule-a-batch-job.html b/FreeFileSync/Build/Help/html/schedule-a-batch-job.html deleted file mode 100755 index fa9a1e2d..00000000 --- a/FreeFileSync/Build/Help/html/schedule-a-batch-job.html +++ /dev/null @@ -1,126 +0,0 @@ - - - - - - Schedule a Batch Job - - - -

      Schedule a Batch Job

      - -
        -
      1. Create a new batch job via FreeFileSync's main dialog: Menu → File → Save as a batch job...
          -
        - Setup a FreeFileSync batch job -

        - - -
      2. By default, FreeFileSync will show a progress dialog during synchronization and will wait while the summary dialog is shown. - If the progress dialog is not needed, enable checkbox Run minimized and - also set Auto-Close if you want to skip the summary dialog at the end. -

        - -
        - Note
        - Even if the progress dialog is not shown at the beginning, you can make it visible at any time during - synchronization by double-clicking the FreeFileSync icon in the notification area. -
        -
        - -
      3. If you don't want error or warning messages to stall synchronization when no user is available to respond, - either check Ignore errors or set Cancel to stop the synchronization at the first error.
        -   - -
      4. If log files are required, enable Save log and enter a folder path. - If the path is left empty, the logs will be saved under the current user's roaming profile, - %appdata%\FreeFileSync\Logs.
        - Additionally, FreeFileSync always stores the result of the last - synchronization in file LastSyncs.log (up to a user-defined size, see Expert Settings).
        -   -
      5. Set up the FreeFileSync batch job in your operating system's scheduler:
        -
      - -
      -
      - -

      A. Windows Task Scheduler:

      -
        -
      • Open the Task Scheduler either via the start menu, or enter taskschd.msc in the run dialog (keyboard shortcut: Windows + R). - -
      • Create a new basic task and follow the wizard. - -
      • Make Program/script point to the location of FreeFileSync.exe and insert the ffs_batch file into Add arguments. - -
      • Use quotation marks to protect spaces in path names, e.g. "D:\Backup Projects.ffs_batch"
        -
        - Windows Task Scheduler -
      - -
      - Note
      -
        -
      • In Windows 7 Program/script always needs to point to an executable file like FreeFileSync.exe even - when the ffs_batch file association is registered. If an ffs_batch file was entered instead, the task would return with - error code 2147942593 (0x800700C1), "%1 is not a valid Win32 application".
        - For Windows 8 and later this limitation does not apply and you may enter the ffs_batch file path directly into Program/script and leave out Add arguments. - -
      • If you schedule FreeFileSync to run under a different user account, note that settings (e.g. GlobalSettings.xml) - will also be read from a different path, C:\Users\<username>\AppData\Roaming\FreeFileSync, or in the case of the SYSTEM account from - C:\Windows\System32\config\systemprofile\AppData\Roaming\FreeFileSync. -
      -
      -
      - -
      - -

      B. macOS Automator and Calendar:

      -
        -
      • Open Launchpad and run Automator.
        - Launch macOS Automator
          - -
      • Create a new Calendar Alarm.
        - Create Calendar Alarm
          - -
      • Drag and drop the ffs_batch file on the workflow panel.
        - Drop FreeFileSync batch file in Automator
          - -
      • Drag and drop action Files & Folders/Open Finder Items and add it to the workflow.
        - Add open Finder items
          - -
      • Go to File → Save... and save the Automator job.
        - Save Automator job
          - -
      • The Calendar app will start automatically with the Automator job scheduled to the current day. You can now select a different time for synchronization or make it a recurring task.
        - Edit batch job in Calendar
          -
      -
      - -

      C. Windows XP Scheduled Tasks:

      -
        -
      • Go to Start → Control Panel → Scheduled Tasks and select Add Scheduled Task. - -
      • Follow the wizard and select FreeFileSync.exe as program to run. - -
      • Fill the input field Run: - <FreeFileSync installation folder>\FreeFileSync.exe <job name>.ffs_batch
        -
        - Windows XP Task Scheduler
          -
      -
      - -

      D. Ubuntu Linux Gnome Scheduled Tasks:

      -
        -
      • Install Gnome-schedule if necessary: sudo apt-get install gnome-schedule - -
      • Search the Ubuntu Unity Dash for Scheduled tasks - -
      • Enter the command: - <FreeFileSync installation folder>/FreeFileSync <job name>.ffs_batch
        - -
      • Select X application since FreeFileSync requires access to GUI -
        - Gnome Scheduler -
      - - diff --git a/FreeFileSync/Build/Help/html/schedule-batch-jobs.html b/FreeFileSync/Build/Help/html/schedule-batch-jobs.html new file mode 100755 index 00000000..c15dcf5a --- /dev/null +++ b/FreeFileSync/Build/Help/html/schedule-batch-jobs.html @@ -0,0 +1,126 @@ + + + + + + Schedule Batch Jobs + + + +

      Schedule Batch Jobs

      + +
        +
      1. Create a new batch job via FreeFileSync's main dialog: Menu → File → Save as a batch job...
          +
        + Setup a FreeFileSync batch job +

        + + +
      2. By default, FreeFileSync will show a progress dialog during synchronization and will wait while the summary dialog is shown. + If the progress dialog is not needed, enable checkbox Run minimized and + also set Auto-Close if you want to skip the summary dialog at the end. +

        + +
        + Note
        + Even if the progress dialog is not shown at the beginning, you can make it visible at any time during + synchronization by double-clicking the FreeFileSync icon in the notification area. +
        +
        + +
      3. If you don't want error or warning messages to stall synchronization when no user is available to respond, + either check Ignore errors or set Cancel to stop the synchronization at the first error.
        +   + +
      4. If log files are required, enable Save log and enter a folder path. + If the path is left empty, the logs will be saved under the current user's roaming profile, + %appdata%\FreeFileSync\Logs.
        + Additionally, FreeFileSync always stores the result of the last + synchronization in file LastSyncs.log (up to a user-defined size, see Expert Settings).
        +   +
      5. Set up the FreeFileSync batch job in your operating system's scheduler:
        +
      + +
      +
      + +

      A. Windows Task Scheduler:

      +
        +
      • Open the Task Scheduler either via the start menu, or enter taskschd.msc in the run dialog (keyboard shortcut: Windows + R). + +
      • Create a new basic task and follow the wizard. + +
      • Make Program/script point to the location of FreeFileSync.exe and insert the ffs_batch file into Add arguments. + +
      • Use quotation marks to protect spaces in path names, e.g. "D:\Backup Projects.ffs_batch"
        +
        + Windows Task Scheduler +
      + +
      + Note
      +
        +
      • In Windows 7 Program/script always needs to point to an executable file like FreeFileSync.exe even + when the ffs_batch file association is registered. If an ffs_batch file was entered instead, the task would return with + error code 2147942593 (0x800700C1), "%1 is not a valid Win32 application".
        + For Windows 8 and later this limitation does not apply and you may enter the ffs_batch file path directly into Program/script and leave out Add arguments. + +
      • If you schedule FreeFileSync to run under a different user account, note that settings (e.g. GlobalSettings.xml) + will also be read from a different path, C:\Users\<username>\AppData\Roaming\FreeFileSync, or in the case of the SYSTEM account from + C:\Windows\System32\config\systemprofile\AppData\Roaming\FreeFileSync. +
      +
      +
      + +
      + +

      B. macOS Automator and Calendar:

      +
        +
      • Open Launchpad and run Automator.
        + Launch macOS Automator
          + +
      • Create a new Calendar Alarm.
        + Create Calendar Alarm
          + +
      • Drag and drop the ffs_batch file on the workflow panel.
        + Drop FreeFileSync batch file in Automator
          + +
      • Drag and drop action Files & Folders/Open Finder Items and add it to the workflow.
        + Add open Finder items
          + +
      • Go to File → Save... and save the Automator job.
        + Save Automator job
          + +
      • The Calendar app will start automatically with the Automator job scheduled to the current day. You can now select a different time for synchronization or make it a recurring task.
        + Edit batch job in Calendar
          +
      +
      + +

      C. Windows XP Scheduled Tasks:

      +
        +
      • Go to Start → Control Panel → Scheduled Tasks and select Add Scheduled Task. + +
      • Follow the wizard and select FreeFileSync.exe as program to run. + +
      • Fill the input field Run: + <FreeFileSync installation folder>\FreeFileSync.exe <job name>.ffs_batch
        +
        + Windows XP Task Scheduler
          +
      +
      + +

      D. Ubuntu Linux Gnome Scheduled Tasks:

      +
        +
      • Install Gnome-schedule if necessary: sudo apt-get install gnome-schedule + +
      • Search the Ubuntu Unity Dash for Scheduled tasks + +
      • Enter the command: + <FreeFileSync installation folder>/FreeFileSync <job name>.ffs_batch
        + +
      • Select X application since FreeFileSync requires access to GUI +
        + Gnome Scheduler +
      + + diff --git a/FreeFileSync/Build/Help/html/synchronize-with-sftp.html b/FreeFileSync/Build/Help/html/synchronize-with-sftp.html deleted file mode 100755 index f3d853bf..00000000 --- a/FreeFileSync/Build/Help/html/synchronize-with-sftp.html +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - Synchronize Files with SFTP - - - -

      Synchronize Files with SFTP (Windows, macOS)

      - -

      - FreeFileSync supports synchronization with SFTP natively. Just enter your SFTP login information into the dialog shown for cloud folder selection: - Cloud folder button
      -
      - Enter SFTP login data -

      - -
      - Note
      In case the SFTP server sets file modification times to the current time - you can do a Compare by File Size as a workaround. - Another solution is to set up the Two way variant and have the files with the newer dates - be copied back from the server during the next synchronization. -
      -
      - -

      Set up SFTP for best performance

      -

      - By default, FreeFileSync creates one connection to the server and uses one SFTP channel, i.e. only a single SFTP command can be sent and received at a time. - Since most of this time is spent waiting due to the high latency of the remote connection, you can speed up reading large directory hierarchies - by increasing both the connection and channel count.
      -
      - The directory reading time is reduced by a factor of N x M when using N connections with M channels each. -

      - Example: 2 connections using 10 channels each can yield a 20 times faster directory reading. -

      - Set up SFTP for best performance - -

        -
      • The creation of additional connections and channels takes time. If you are only scanning a small remote directory, - setting up too many connections and channels might actually slow the overall process down. - Creating extra connections is slower than creating extra channels.
          - -
      • SFTP servers have internal limits on the number of allowed connections and channels. - Generally, servers expect one connection per user, so this number should be kept rather low. - If too many connections and channels are used, the server may decide to stop responding. -
      -

      - -
      - Advice
      Start with low numbers and make tests with different combinations of connections and channels for your - particular SFTP synchronization scenario to see what gives the highest speed. - Note, however, that FreeFileSync reuses existing SFTP connections/channels. - Therefore, you should restart FreeFileSync before measuring SFTP speed. -
      -
      - -

      Synchronize with SFTP (Linux)

      - -

      An SFTP share can be mapped to a local folder for use with FreeFileSync:

      - -
      -
        -
      • Install: -
        sudo apt-get install sshfs

        - -
      • Mount SFTP share: -
        sshfs ssh-account@ssh-server:<path> mountpoint

        - -
      • Unmount:
        -
        fusermount -u mountpoint
        -
      -
      - - diff --git a/FreeFileSync/Build/Help/html/volume-shadow-copy.html b/FreeFileSync/Build/Help/html/volume-shadow-copy.html index c9004c35..fa98238a 100755 --- a/FreeFileSync/Build/Help/html/volume-shadow-copy.html +++ b/FreeFileSync/Build/Help/html/volume-shadow-copy.html @@ -3,11 +3,11 @@ - Shadow Copy Service + Volume Shadow Copy -

      Shadow Copy Service (Windows only)

      +

      Volume Shadow Copy (Windows only)

      FreeFileSync supports copying locked or shared files by creating a Volume Shadow diff --git a/FreeFileSync/Build/Help/images/comparison-settings.png b/FreeFileSync/Build/Help/images/comparison-settings.png index 371b28ed..5ffa2f48 100755 Binary files a/FreeFileSync/Build/Help/images/comparison-settings.png and b/FreeFileSync/Build/Help/images/comparison-settings.png differ diff --git a/FreeFileSync/Build/Help/images/comparison-variant-double-click.png b/FreeFileSync/Build/Help/images/comparison-variant-double-click.png index eade94b5..1d998755 100755 Binary files a/FreeFileSync/Build/Help/images/comparison-variant-double-click.png and b/FreeFileSync/Build/Help/images/comparison-variant-double-click.png differ diff --git a/FreeFileSync/Build/Help/images/filter.png b/FreeFileSync/Build/Help/images/filter.png index 9e688264..a4d3a0f8 100755 Binary files a/FreeFileSync/Build/Help/images/filter.png and b/FreeFileSync/Build/Help/images/filter.png differ diff --git a/FreeFileSync/Build/Help/images/gnome-scheduler.png b/FreeFileSync/Build/Help/images/gnome-scheduler.png index fee122bd..05d0f1f7 100755 Binary files a/FreeFileSync/Build/Help/images/gnome-scheduler.png and b/FreeFileSync/Build/Help/images/gnome-scheduler.png differ diff --git a/FreeFileSync/Build/Help/images/ignore-time-shift.png b/FreeFileSync/Build/Help/images/ignore-time-shift.png index fe9361be..ceadf60a 100755 Binary files a/FreeFileSync/Build/Help/images/ignore-time-shift.png and b/FreeFileSync/Build/Help/images/ignore-time-shift.png differ diff --git a/FreeFileSync/Build/Help/images/performance.png b/FreeFileSync/Build/Help/images/performance.png new file mode 100755 index 00000000..70bed081 Binary files /dev/null and b/FreeFileSync/Build/Help/images/performance.png differ diff --git a/FreeFileSync/Build/Help/images/realtimesync-schedule.png b/FreeFileSync/Build/Help/images/realtimesync-schedule.png index cd67e71d..34ba5b88 100755 Binary files a/FreeFileSync/Build/Help/images/realtimesync-schedule.png and b/FreeFileSync/Build/Help/images/realtimesync-schedule.png differ diff --git a/FreeFileSync/Build/Help/images/sftp-login.png b/FreeFileSync/Build/Help/images/sftp-login.png index b1d3f60b..d28b12a9 100755 Binary files a/FreeFileSync/Build/Help/images/sftp-login.png and b/FreeFileSync/Build/Help/images/sftp-login.png differ diff --git a/FreeFileSync/Build/Help/images/sftp-performance.png b/FreeFileSync/Build/Help/images/sftp-performance.png index 4e2d1528..6125aa5e 100755 Binary files a/FreeFileSync/Build/Help/images/sftp-performance.png and b/FreeFileSync/Build/Help/images/sftp-performance.png differ diff --git a/FreeFileSync/Build/Help/images/synchronization-settings.png b/FreeFileSync/Build/Help/images/synchronization-settings.png index c7bffa5f..4eab9306 100755 Binary files a/FreeFileSync/Build/Help/images/synchronization-settings.png and b/FreeFileSync/Build/Help/images/synchronization-settings.png differ diff --git a/FreeFileSync/Build/Help/images/synchronization-variant-double-click.png b/FreeFileSync/Build/Help/images/synchronization-variant-double-click.png index 9e2597c7..cb6cd370 100755 Binary files a/FreeFileSync/Build/Help/images/synchronization-variant-double-click.png and b/FreeFileSync/Build/Help/images/synchronization-variant-double-click.png differ diff --git a/FreeFileSync/Build/Help/images/windows-scheduler.png b/FreeFileSync/Build/Help/images/windows-scheduler.png index ff52b34a..bf214a8f 100755 Binary files a/FreeFileSync/Build/Help/images/windows-scheduler.png and b/FreeFileSync/Build/Help/images/windows-scheduler.png differ diff --git a/FreeFileSync/Build/Help/images/xp-scheduler.png b/FreeFileSync/Build/Help/images/xp-scheduler.png index 6a643d78..cfb74050 100755 Binary files a/FreeFileSync/Build/Help/images/xp-scheduler.png and b/FreeFileSync/Build/Help/images/xp-scheduler.png differ diff --git a/FreeFileSync/Build/Languages/arabic.lng b/FreeFileSync/Build/Languages/arabic.lng deleted file mode 100755 index f77e5105..00000000 --- a/FreeFileSync/Build/Languages/arabic.lng +++ /dev/null @@ -1,2059 +0,0 @@ -

      - العربية - Majed Alotaibi (ماجد العتيبي) - ar - flag_arabic.png - 6 - n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5 -
      - -Both sides have changed since last synchronization. -كلا الجانبين قد تغير منذ المزامنة الأخيرة. - -Cannot determine sync-direction: -لا يمكن تحديد اتجاه المزامنة: - -No change since last synchronization. -لم يطرأ أي تغيير منذ المزامنة الأخيرة. - -The database entry is not in sync considering current settings. -مدخلات قواعد البيانات غير متزامنة حسب إعدادات المزامنة الحالية. - -Setting default synchronization directions: Old files will be overwritten with newer files. -تحديد الاتجاهات الافتراضية للمزامنة: ستتم الكتابة فوق الملفات القديمة بالملفات الأحدث. - -Creating file %x -إنشاء الملف %x - -Creating folder %x -إنشاء مجلد %x - -Creating symbolic link %x -إنشاء ارتباط رمزي %x - -Moving file %x to the recycle bin -نقل الملف %x إلى سلة المهملات - -Moving folder %x to the recycle bin -نقل المجلد %x إلى سلة المهملات - -Moving symbolic link %x to the recycle bin -نقل الارتباط الرمزي %x إلى سلة المهملات - -Deleting file %x -حذف الملف %x - -Deleting folder %x -حذف المجلد %x - -Deleting symbolic link %x -حذف الارتباط الرمزي %x - -Checking recycle bin availability for folder %x... -التحقق من توافر سلة المحذوفات من أجل المجلد %x... - -The recycle bin is not supported by the following folders. Deleted or overwritten files will not be able to be restored: -سلة المهملات غير مدعومة بواسطة المجلدات التالية. لن تتمكن من استعادة الملفات المحذوفة أو التي يتم استبدالها: - -An exception occurred -حدث استثناء - -A directory path is expected after %x. -مسار متوقع بعد %x. - -Syntax error -خطأ في البنية - -A left and a right directory path are expected after %x. - - -Cannot find file %x. -لا يمكن العثور على المجلد %x. - -Error -خطأ - -File %x does not contain a valid configuration. -لا يحتوي الملف %x تكويناً صحيحاً. - -Unequal number of left and right directories specified. -لم يتم تحديد عدد متساوي من المسارات على الطرفين. - -The config file must not contain settings at directory pair level when directories are set via command line. -يجب أن يحتوي ملف الخيارات الخيارات على مستوى أزواج المسارات عند تحديد المسارات بواسطة سطر الأوامر. - -Directories cannot be set for more than one configuration file. -لا يمكن اختيار المسارات لأكثر من ملف خيارات واحد. - -Command line -سطر الأوامر - -Syntax: -بنية: - -config files: -ملفات الخيارات: - -directory -مسار - -global config file: -ملف الخيارات العام: - -Any number of FreeFileSync .ffs_gui and/or .ffs_batch configuration files. -أي عدد من ملفات تكوين FreeFileSync بامتداد .ffs_gui أو/و .ffs_batch. - -Any number of alternative directory pairs for at most one config file. -أي عدد من أزواج المسارات البديلة من أجل ملف خيارات واحد. - -Open the selected configuration for editing only without executing it. -فتح التكوين المحدد لتعديله فقط بدون تنفيذه. - -Path to an alternate GlobalSettings.xml file. -تحديد مسار مختلف لملف GlobalSettings.xml. - -Cannot find the following folders: -تعذر العثور على المجلدات التالية: - -If this error is ignored the folders will be considered empty. Missing folders are created automatically when needed. -إذا تم تجاهل هذا الخطأ سيتم اعتبار المجلدات فارغة. يتم إنشاء المجلدات المفقودة تلقائيا عند الحاجة. - -Comparison finished: - - - -1 item found -%x items found - - - - -File %x has an invalid date. -يحتوي الملف %x تاريخ غير صالح. - -Date: -التاريخ: - -Files have the same date but a different size. -الملفات لها نفس التاريخ ولكن حجم مختلف. - -Size: -الحجم: - -Content comparison was skipped for excluded files. -تم تخطي مقارنة المحتوى لملفات مستبعدة. - -Items differ in attributes only -العناصر مختلفة في السمات فقط - -Resolving symbolic link %x -جاري حل المسار الرمزي %x - -Comparing content of files %x -مقارنة محتويات الملفات %x - -Generating file list... -إنشاء قائمة الملفات... - -Fail-safe file copy -نسخ ملفات آمن من الفشل - -Enabled -ممكن - -Disabled -معطل - -Copy locked files -نسخ الملفات المقفلة - -Copy file access permissions -نسخ أذونات الوصول إلى الملف - -File time tolerance -التفاوت في وقت الملف - -Folder access timeout -مهلة وصول المجلد - -Run with background priority -تشغيل مع أولوية في الخلفية - -Lock directories during sync -قفل المسارات أثناء المزامنة - -Verify copied files -التحقق من الملفات التي تم نسخها - -Using non-default global settings: -استخدام إعدادات عامة غير افتراضية: - -A folder input field is empty. -حقل إدخال خاص بمجلد فارغ. - -The corresponding folder will be considered as empty. -سيتم اعتبار المجلد الموافق كمجلد فارغ. - -Exclude: -استثناء: - -One base folder of a folder pair is contained in the other one. -يوجد مجلد أساسي لزوج من المجلدات في المجلد الآخر. - -The folder should be excluded from synchronization via filter. -يجب استبعاد المجلد من المزامنة عبر المرشح. - -Calculating sync directions... -جاري حساب اتجاهات المزامنة... - -Out of memory. -نفدت الذاكرة. - -Item exists on left side only -العنصر موجود على الجانب الأيمن فقط - -Item exists on right side only -العنصر موجود في الجانب الأيسر فقط - -Left side is newer -الجانب الأيمن أحدث - -Right side is newer -الجانب الأيسر أحدث - -Items have different content -العناصر مختلفة بالمحتوى - -Both sides are equal -كلا الجانبين متماثلان - -Conflict/item cannot be categorized -الاختلاف\العنصر لا يمكن تصنيفه - -Copy new item to left -نسخ عنصر جديد إلى اليمين - -Copy new item to right -نسخ عنصر جديد إلى اليسار - -Delete left item -حذف العنصر الأيمن - -Delete right item -حذف العنصر الأيسر - -Move file on left -نقل ملف على اليمين - -Move file on right -نقل ملف على اليسار - -Update left item -تحديث العنصر اليميني - -Update right item -تحديث العنصر اليساري - -Do nothing -لا تفعل شيئا - -Update attributes on left -تحديث السمات على اليمين - -Update attributes on right -تحديث السمات على اليسار - -Cannot read file %x. -لا يمكن قراءة الملف %x. - - -Unexpected size of data stream. -Expected: %x bytes -Actual: %y bytes - - -حجم غير متوقع لتدفق البيانات. -المتوقع: ‎%x bytes -الفعلي: ‎%y bytes - - -Cannot write permissions of %x. -لا يمكن كتابة أذونات %x. - -Operation not supported for different base folder types. -العملية غير مدعومة لأنواع المجلدات مختلفة الأساس. - -Cannot write file %x. -لا يمكن كتابة الملف %x. - -Cannot move file %x to %y. -لا يمكن نقل الملف %x إلى %y. - -Cannot copy symbolic link %x to %y. -لا يمكن نسخ الرابط الرمزي من %x إلى %y. - -Unable to connect to %x. -لا يمكن الاتصال بـ %x. - -Failed to get information about server %x. -فشل الحصول على معلومات حول الخادم %x. - -Cannot open directory %x. -لا يمكن فتح المسار %x. - -Cannot read directory %x. -لا يمكن قراءة الدليل %x. - -Cannot read file attributes of %x. -لا يمكن قراءة سمات الملف %x. - -Cannot create directory %x. -لا يمكن إنشاء المسار %x. - -Cannot delete file %x. -لا يمكن حذف الملف %x. - -Cannot delete directory %x. -لا يمكن حذف المسار %x. - -Cannot write modification time of %x. -لا يمكن كتابة وقت تعديل %x. - -Cannot determine final path for %x. -تعذر تحديد المسار النهائي لـ %x. - -Cannot resolve symbolic link %x. -لا يمكن حل الارتباط الرمزي %x. - -Unable to move %x to the recycle bin. -تعذر نقل %x إلى سلة المحذوفات. - -Cannot find %x. -لا يمكن العثور على %x. - -Cannot open file %x. -تعذر فتح الملف %x. - -Cannot find device %x. -لا يمكن العثور على الجهاز %x. - -Type of item %x is not supported: -نوع العنصر %x غير مدعوم: - -Cannot delete symbolic link %x. -لا يمكن حذف الرابط الرمزي %x. - -Cannot determine free disk space for %x. -لا يمكن تحديد مساحة القرص الحرة لـ %x. - -Incorrect command line: -سطر أوامر خاطئ: - -Error Code %x -رمز الخطأ %x - -The server does not support authentication via %x. -لا يدعم الخادم المصادقة عبر %x. - -Required: -مطلوب: - -Unable to access %x. -لا يمكن الوصول إلى %x. - - -Operation timed out after 1 second. -Operation timed out after %x seconds. - - -انقضت مهلة العملية بعد 0 ثانية. -انقضت مهلة العملية بعد 1 ثانية. -انقضت مهلة العملية بعد 2 ثانية. -انقضت مهلة العملية بعد %x ثوان. -انقضت مهلة العملية بعد %x ثانية. -انقضت مهلة العملية بعد %x ثانية. - - - -Cannot wait on more than 1 connection at a time. -Cannot wait on more than %x connections at a time. - - -لا يمكن الانتظار على أكثر 0 اتصال في وقت واحد. -لا يمكن الانتظار على أكثر 1 اتصال في وقت واحد. -لا يمكن الانتظار على أكثر 2 اتصال في وقت واحد. -لا يمكن الانتظار على أكثر %x اتصالات في وقت واحد. -لا يمكن الانتظار على أكثر من %x اتصالات في وقت واحد. -لا يمكن الانتظار على أكثر من %x اتصالات في وقت واحد. - - -Active connections: %x -الاتصال النشط: %x - -Failed to open SFTP channel number %x. -فشل فتح قناة SFTP رقم %x. - - -1 byte -%x bytes - - -‎0 byte -‎1 byte -‎2 bytes -‎%x bytes -‎%x bytes -‎%x bytes - - -%x MB -‎%x MB - -%x KB -‎%x KB - -%x GB -‎%x GB - -Cannot load file %x. -لا يمكن فتح الملف %x. - -Database file %x is incompatible. -ملف قاعدة البيانات %x غير متوافق. - -Initial synchronization: -المزامنة الأولية: - -Database file %x does not yet exist. -ملف قاعدة البيانات %x غير موجود حتى الآن. - -Database file is corrupted: -ملف قاعدة البيانات تالف: - -The database files do not yet contain information about the last synchronization. -لا تحتوي ملفات قاعدة البيانات حتى الآن على معلومات حول المزامنة الأخيرة. - -Loading file %x... -جار تحميل الملف %x... - -Saving file %x... -جاري حفظ الملف %x... - -Searching for folder %x... -البحث عن المجلد %x... - -Timeout while searching for folder %x. -انقضت المهلة أثناء البحث عن مجلد %x. - -Cannot get process information. -لا يمكن الحصول على معلومات العملية. - -Waiting while directory is locked: -انتظر بينما يتم إنشاء قفل للمسار: - -Lock owner: -صاحب القفل: - -Detecting abandoned lock... -اكتشاف قفل مهمل... - - -1 sec -%x sec - - -0 ثانية -1 ثانية واحدة -2 ثانيتين -%x ثواني -%x ثانية -%x ثانية - - -Items processed: -معالجة العناصر: - -Items remaining: -العناصر المتبقية: - -Total time: -مجموع الوقت: - -Error parsing file %x, row %y, column %z. -حدث خطأ أثناء تحليل الملف %x، الصف %y، و العمود %z. - -Cannot set directory locks for the following folders: - - - -1 thread -%x threads - - -0 بند -1 بند واحد -2 بندان -%x بنود -%x بنداً -%x بند - - -Scanning: -الفحص: - -/sec -\ثانية - -%x items/sec -%x عنصر\الثانية - -Show in Explorer -إظهار في المستكشف - -Open with default application -فتح باستخدام التطبيق الافتراضي - -Browse directory -تصفح المسار - -Cannot access the Volume Shadow Copy Service. -لا يمكن الوصول إلى خدمة "نسخ الظل لوحدة التخزين". - -Please run the 64-bit version of FreeFileSync to create shadow copies on this system. -الرجاء تشغيل نسخة 64-بت من FreeFileSync لإنشاء نسخ الظل على هذا النظام. - -Cannot determine volume name for %x. -تعذر تحديد اسم الوسط %x. - -Volume name %x is not part of file path %y. -اسم وحدة التخزين %x ليس جزءاُ من اسم الملف %y. - -Unable to create time stamp for versioning: -تعذر إنشاء بصمة زمنية من أجل المفاضلة الزمنية: - -Drag && drop -سحب و إفلات - -Cannot find folder %x. -تعذر العثور على المجلد %x. - -Select a folder -تحديد مجلد - -&New -&جديد - -&Open... -&فتح... - -Save &as... -&حفظ باسم... - -E&xit -&إغلاق - -&File -&ملف - -&View help -إ&ظهار المساعدة - -&About -&حول - -&Help -&تعليمات - -Usage: -الاستخدام: - -1. Select folders to watch. -1. حدد المجلدات للمتابعة. - -2. Enter a command line. -2. إدخال سطر أوامر. - -3. Press 'Start'. -3. اضغط على 'ابدأ'. - -To get started just import a .ffs_batch file. -للبدء قم باستيراد ملف .ffs_batch. - -Folders to watch: -المجلدات للمتابعة: - -Add folder -إضافة مجلد - -Remove folder -إزالة مجلد - -Browse -تصفح - -Idle time (in seconds): -وقت الخمول (بالثانية): - -Idle time between last detected change and execution of command -وقت الخمول بين آخر تغيير تم الكشف عنه وتنفيذ الأوامر - -Command line: -سطر الأوامر: - - -The command is triggered if: -- files or subfolders change -- new folders arrive (e.g. USB stick insert) - - -يتم تشغيل الأمر إذا: --حدوث تغيير في الملفات أو المجلدات الفرعية --ظهور مجلدات جديدة (مثال: إدخال USB stick) - - -Start -بدء - -About -حول - -Build: %x -بناء: %x - -All files -جميع الملفات - -Automated Synchronization -مزامنة تلقائية - -The %x protocol does not support directory monitoring: -بروتوكول %x لا يدعم مراقبة المجلدات: - -Directory monitoring active -مراقبة المسارات فعالة - -Waiting until all directories are available... -انتظر حتى توفر جميع المسارات... - -&Restore -&استعادة - -&Show error -إ&ظهار الخطأ - -&Quit -إ&نهاء - -&Retry -إ&عادة المحاولة - -File time and size -تاريخ الملف و حجمه - -File content -محتوى الملف - -File size -حجم الملف - -Two way -بالاتجاهين - -Mirror -انعكاس - -Update -تحديث - -Custom -مخصص - -Multiple... -متعددة... - -Moving file %x to %y -نقل الملف %x إلى %y - -Moving folder %x to %y -نقل المجلد %x إلى %y - -Moving symbolic link %x to %y -نقل الارتباط الرمزي %x إلى %y - -Removing old versions... -إزالة الإصدارات القديمة... - -Updating file %x -جاري تحديث الملف %x - -Updating symbolic link %x -جاري تحديث المسار الرمزي %x - -Verifying file %x -التحقق من الملف %x - -Updating attributes of %x -تحديث سمات %x - -Cannot write file attributes of %x. -لا يمكن كتابة سمات الملف %x. - -%x and %y have different content. -%x و %y لديهما محتوى مختلف. - -Data verification error: -خطأ في التحقق من البيانات: - -Creating a Volume Shadow Copy for %x... -جاري إنشاء نسخة ظل وسيطة لـ %x... - -Target folder %x already existing. -المجلد الهدف %x موجود سابقاً. - -Target folder input field must not be empty. -يجب أن لا يكون حقل إدخال المجلد الهدف فارغاً. - -Source folder %x not found. -لم يتم العثور على المجلد المصدر %x. - -Please enter a target folder for versioning. -الرجاء تحديد مجلد هدف من أجل الوسم حسب الإصدار. - -The following items have unresolved conflicts and will not be synchronized: -العناصر التالية لم تحل اختلافاتها، و لن يتم مزامنتها: - -The following folders are significantly different. Please check that the correct folders are selected for synchronization. -المجلدات التالية تختلف اختلافا كبيرا. يرجى التحقق من تحديد المجلدات الصحيحة لإجراء التزامن. - -Not enough free disk space available in: -المساحة الحرة المتوفرة على القرص غير كافية: - -Available: -متاح: - -Some files will be synchronized as part of multiple base folders. -ستتم مزامنة بعض الملفات كجزء من مجلدات قاعدة متعددة. - -To avoid conflicts, set up exclude filters so that each updated file is considered by only one base folder. -لتجنب التعارضات، قم بإعداد مرشحات استبعاد بحيث يتم النظر في كل ملف محدث من قبل مجلد أساسي واحد فقط. - -Versioning folder: -مجلد الإصدار: - -Base folder: -المجلد الأساس: - -The versioning folder is contained in a base folder. -مجلد الإصدار موجود داخل مجلد أساس. - -Synchronizing folder pair: -مزامنة زوج مجلدات: - -Generating database... -إنشاء قاعدة بيانات... - -Loading... -تحميل... - -job name -اسم المهمة - -System: Sleep - - -System: Shut down - - -Cleaning up old log files... -جاري تنظيف ملفات المتابعة القديمة... - -Stopped -توقف - -Completed with errors - - -Completed with warnings - - -Warning -تحذير - -Nothing to synchronize -لا يوجد شيء للمزامنة - -Completed successfully - - -Executing command %x -تنفيذ الأمر %x - -You can switch to FreeFileSync's main window to resolve this issue. -بإمكانك العودة إلى نافذة FreeFileSync الرئيسية لحل هذه المشكلة. - -&Don't show this warning again -&لا تظهر هذا التنبيه مرة ثانية - -&Ignore -&تجاهل - -&Switch -&تبديل - -Switching to FreeFileSync's main window -العودة إلى نافذة FreeFileSync الرئيسية - -Automatic retry - - -Ignore &all -تجاهل &الكل - -Retrying operation... -إعادة محاولة العملية... - -Serious Error -خطأ فادح - -Last session -مصدر الجلسة - -Today -اليوم - - -1 day -%x days - - -0 يوم -1 يوم واحد -2 يومان -%x أيام -%x يوماً -%x يوم - - -Name -الاسم - -Last sync - - -Folder -المجلد - -Symlink -ارتباط-رمزي - -Full path -المسار الكامل - -Relative path -المسار النسبي - -Item name -اسم العنصر - -Size -الحجم - -Date -تاريخ - -Extension -اللاحقة - -Category -الفئة - -Action -التصرف - -Local comparison settings -إعدادات المقارنة المحلية - -Local synchronization settings -إعدادات المزامنة المحلية - -Local filter -فلتر محلي - -Active -مفعل - -None -لا شيء - -Remove local settings -إزالة الإعدادات المحلية - -Clear local filter -إزالة الفلتر المحلي - -Copy -نسخ - -Paste -لصق - -The selected folder %x cannot be used with FreeFileSync. -المجلد المحدد %x لا يمكن استخدامه مع FreeFileSync. - -Please select a folder on a local file system, network or an MTP device. -الرجاء تحديد مجلد على نظام الملفات المحلي، الشبكة أو جهاز MTP. - -&Save -&حفظ - -Save as &batch job... -&حفظ كمهمة دفعية... - -Start &comparison -بدأ الم&قارنة - -C&omparison settings -&مقارنة - -&Filter settings -إعدادات ال&فلتر - -S&ynchronization settings -إ&عدادات - -Start &synchronization -بدأ الم&زامنة - -&Actions -&مهام - -&Preferences -&التفضيلات - -&Language -الل&غة - -&Find... -&بحث... - -&Export file list... -&تصدير قائمة الملفات... - -&Reset layout -إعادة ال&تنسيق إلى الإفتراضي - -&Tools -أ&دوات - -&Check for updates now -&تحقق من وجود تحديثات الآن - -Check &automatically once a week -&تحقق أوتوماتيكياً بشكل أسبوعي - -Cancel -إلغاء الأمر - -Compare -قارن - -Synchronize -مزامنة - -Add folder pair -إضافة زوج مجلدات - -Remove folder pair -إزالة زوج مجلدات - -Access online storage -الوصول إلى التخزين عبر الإنترنت - -Swap sides -مبادلة الجانبين - -Close search bar -إغلاق شريط البحث - -Find: -بحث: - -Match case -مطابقة الحالة - -New -جديد - -Open... -فتح... - -Save -حفظ - -Save as... -حفظ كـ... - -View type: -عرض النوع: - -Select view: -اختيار نمط العرض: - -Statistics: -إحصائيات: - -Number of files and folders that will be deleted -عدد الملفات و المجلدات التي سيتم حذفها - -Number of files that will be updated -عدد الملفات التي سيتم تحديثها - -Number of files and folders that will be created -عدد الملفات و المجلدات التي سيتم إنشاؤها - -Total bytes to copy -إجمالي عدد الـ bytes التي سيتم نسخها - -Arrange folder pair -ترتيب زوج المجلدات - -Folder pair: -زوج المجلدات: - -Main settings: -الإعدادات الرئيسية: - -Use local settings: -استخدام الإعدادات المحلية: - -Select a variant: -اختيار بديل: - -Include &symbolic links: -تضمين &الروابط الرمزية: - -&Follow -&متابعة - -&Direct -&مباشر - -More information -المزيد من المعلومات - -&Ignore time shift [hh:mm] -&تجاهل تغيير الوقت [hh:mm] - -List of file time offsets to ignore -قائمة إزاحات وقت للملفات ليتم تجاهلها - -Example: -مثال: - -Handle daylight saving time -تعامل مع التوقيت الصيفي - -Local settings: -الإعدادات المحلية: - -Include: -إدخال: - -Show examples -إظهار أمثلة - -Time span: -المجال الزمني: - -File size: -حجم الملف: - -Minimum: -الحد الأدنى: - -Maximum: -الحد الأقصى: - -Select filter rules to exclude certain files from synchronization. Enter file paths relative to their corresponding folder pair. -اختيار قوانين فلترة لاستثناء ملفات معينة من المزامنة. أدخل مسارات الملفات منسوبة إلى زوج المجلدات المقابل. - -C&lear -إ&زالة - -Detect moved files -اكتشاف الملفات المنقولة - - -- Not supported by all file systems -- Requires and creates database files -- Detection not available for first sync - - -- غير مدعوم من كل أنظمة الملفات -- يتطلب وينشىء ملفات قواعد البيانات -- الاكتشاف غير متاح للمزامنة الأولى - - -Delete files: -حذف الملفات: - -&Recycle bin -&سلة المهملات - -&Permanent -&دائم - -&Versioning -ال&وسم حسب الإصدار - -Naming convention: -اصطلاح التسمية: - -Ignore errors - - -Retry count: -تعداد محاولات الإعادة: - -Delay (in seconds): -التأخير (بالثواني): - -Run a command after synchronization: -تشغيل أمر بعد المزامنة: - -OK -موافق - -Enter your login details: -أدخل تفاصيل تسجيل الدخول: - -Connection type: -نوع الاتصال: - -Server name or IP address: -اسم الخادم أو عنوان IP: - -Port: -المنفذ: - -Encryption: -التشفير: - -&Disabled -&معطل - -&Explicit SSL/TLS -&Explicit SSL/TLS - -Authentication: -المصادقة: - -&Password -&كلمة المرو - -&Key file -&ملف المفتاح - -&SSH agent -&عميل بروتوكول SSH - -User name: -اسم المستخدم: - -Private key file: -ملف المفتاح الخاص: - -&Show password -&إظهار كلمة المرور - -Directory on server: -المسار على الخادم: - -Performance improvements: -تحسينات الأداء: - -How to get best performance? -كيفية الحصول على أفضل أداء؟ - -Connections for directory reading: -عدد الاتصالات لقراءة المجلد: - -SFTP channels per connection: -قنوات SFTP لكل اتصال: - -Detect server limit -اكتشاف حد الخادم - -Select a directory on the server: -تحديد دليل على الخادم: - -Select Folder -اختر مجلد - -Start synchronization now? -بدأ المزامنة الآن؟ - -Variant: -بديل: - -&Don't show this dialog again -&لا تظهر نافذة الحوار هذه مرة ثانية - -Items found: -العناصر التي تم العثور عليها: - -Time remaining: -الوقت المتبقي: - -Time elapsed: -الوقت المنقضي: - -Bytes -بايت - -Items -العناصر - -Synchronizing... -مزامنة... - -Minimize to notification area -تصغير إلى منطقة التنبيهات - -When finished: -عند الانتهاء: - -Auto-close - - -Close -إغلاق - -&Pause -إ&يقاف مؤقت - -Stop -توقف - -Create a batch file for unattended synchronization. To start, double-click this file or schedule in a task planner: %x -إنشاء ملف دفعي من أجل عمليات المزامنة غير المحضورة. للبدأ, انقر نقراً مزدوجاً على الملف أو المهمة المجدولة في منظم المهام: %x - -Progress dialog: - - -Run minimized -تشغيل بوضع التصغير - -&Show error dialog -&إظهار نافذة الأخطاء - -Show pop-up on errors or warnings -إظهار إطارات منبثقة عند حصول أخطاء أو تحذيرات - -&Cancel -&إلغاء - -Stop synchronization at first error -إحباط المزامنة عند أول خطأ - -Save log: -حفظ السجل: - -Limit: -حدود: - -Limit maximum number of log files -تقييد الحد الأقصى لعدد ملفات السجل - -How can I schedule a batch job? -كيف يمكنني جدولة مهمة دفعية؟ - -&Keep relative paths -&إبقاء المسارات النسبية - -&Overwrite existing files -&الكتابة فوق الملفات الموجودة - -The following settings are used for all synchronization jobs. -هذه الإعدادات مستخدمة لجميع مهمات المزامنة. - - -Copy to a temporary file (*.ffs_tmp) before overwriting target. -This guarantees a consistent state even in case of a serious error. - - -قم بالنسخ إلى ملف مؤقت (*.ffs_tmp) قبل استبدال الملف الهدف. -هذه العملية تضمن حالة مستقرة حتى في حال حدوث خطأ فادح. - - -recommended -‏منصوح به‏ - -Copy shared or locked files using the Volume Shadow Copy Service. -نسخ الملفات المشتركة مع مستخدمين آخرين أو المقفولة باستخدام خدمة نسخ الظل الوسيط. - -requires administrator rights -يتطلب صلاحيات مسؤول - -Transfer file and folder permissions. -نقل أذونات الملفات و المجلدات. - -Show hidden dialogs again -إظهار التنبهات و نوافذ الحوار المخفية - -Show all permanently hidden dialogs and warning messages again -إعادة إظهار جميع التنبهات و نوافذ الحوار التي تم إخفاؤها - -Customize context menu: -تخصيص القائمة المحلية: - -Description -الوصف - -&Default -الا&فتراضي - -Source code written in C++ using: -الرماز المصدري مكتوب بلغة C++‎ باستخدام: - -If you like FreeFileSync: -إذا أعجبك FreeFileSync: - -Support with a donation -دعم عبر التبرع - -Donation details -تفاصيل التبرع - -The auto updater was disabled by the administrator. -تم تعطيل المحدث التلقائي بواسطة المسؤول. - -Feedback and suggestions are welcome -التعليقات و الاقتراحات موضع ترحيب - -Home page -الصفحة الرئيسية - -Email -البريد الإلكتروني - -Published under the GNU General Public License -نشر تحت رخصة GNU General Public License - -Many thanks for localization: -شكرا جزيلا للترجمة: - -Activate the FreeFileSync Donation Edition by one of the following methods: -قم بتنشيط FreeFileSync Donation Edition بإحدى الطرق التالية: - -1. Activate via internet now: -1. تنشيط عبر الإنترنت الآن: - -Activate online -التنشيط عبر الإنترنت - -2. Retrieve an offline activation key from the following URL: -2. استرداد مفتاح تنشيط دون اتصال من الرابط التالي: - -&Copy to clipboard -&نسخ من الذاكرة - -Enter activation key: -أدخل مفتاح التنشيط: - -Activate offline -تنشيط دون اتصال - -Highlight configurations that have not been run for more than the following number of days: - - -Synchronization Settings -إعدادات المزامنة - -Access Online Storage - - -Save as a Batch Job -حفظ كمهمة دفعية - -Delete Items -حذف العناصر - -Copy Items - - -Options -خيارات - -Select Time Span -اختيار المطال الزمني - -FreeFileSync Donation Edition -FreeFileSync Donation Edition - -Highlight Configurations - - -&Options -&خيارات - -Main Bar -الشريط الرئيسي - -Folder Pairs -أزواج المجلدات - -Find -بحث - -View Settings -عرض الإعدادات - -Configuration -التكوين - -Overview -نظرة عامة - -Show "%x" -إظهار "%x" - -&Show details -&عرض التفاصيل - -FreeFileSync %x is available! -FreeFileSync %x متوفر! - -Installation files are corrupted. Please reinstall FreeFileSync. -ملفات التثبيت تالفة. الرجاء إعادة تثبيت FreeFileSync. - -Local path not available for %x. -المسار المحلي غير متوفر لـ %x. - -Confirm -تأكيد - - -Do you really want to execute the command %y for one item? -Do you really want to execute the command %y for %x items? - - -هل تريد حقاً تنفيذ العملية %y من أجل عنصر 0 -هل تريد حقاً تنفيذ العملية %y من أجل عنصر وحيد 1 -هل تريد حقاً تنفيذ العملية %y من أجل عنصرين اثنين 2 -هل تريد حقاً تنفيذ العملية %y من أجل %x عناصر -هل تريد حقاً تنفيذ العملية %y من أجل %x عنصراً -هل تريد حقاً تنفيذ العملية %y من أجل %x عنصر - - -&Execute -&تنفيذ - - -1 directory -%x directories - - -0 مسار -1 مسار واحد -2 مساران -%x مسارات -%x مساراً -%x مسار - - - -1 file -%x files - - -0 ملف -1 ملف واحد -2 ملفان -%x ملفات -%x ملفاً -%x ملف - - - -Showing %y of 1 row -Showing %y of %x rows - - -الظاهر %y من أصل سطر 0 -الظاهر %y من أصل سطر وحيد 1 -الظاهر %y من أصل سطرين اثنين 2 -الظاهر %y من أصل %x سطور -الظاهر %y من أصل %x سطراً -الظاهر %y من أصل %x سطر - - -Set direction: -تحديد الاتجاه: - -multiple selection -تحديد متعدد - -Include via filter: -تضمن عن طريق فلتر: - -Exclude via filter: -استبعاد باستخدام عامل الفلترة: - -Include temporarily -شمول مؤقتاً - -Exclude temporarily -استبعاد مؤقتاً - -&Copy to... -&نسخ إلى... - -&Delete -&حذف - -Include all -شمول الكل - -Exclude all -استبعاد الكل - -Show icons: -إظهار الأيقونات: - -Small -صغيرة - -Medium -متوسطة - -Large -كبيرة - -Select time span... -حدد المجال الزمني... - -Folder Comparison and Synchronization -مقارنة و مزامنة المجلد - -Configuration saved -تم حفظ التكوين - -FreeFileSync batch -دفعة FreeFileSync - -Do you want to save changes to %x? -هل تريد حفظ التغييرات إلى %x؟ - -Never save &changes -&لا تقم بحفظ التغيرات - -Do&n't save -&لا تحفظ - -Hide configuration - - -Highlight... - - -Clear filter -إزالة الفلاتر الحالية - -Show files that exist on left side only -إظهار الملفات الموجودة في الجانب الأيمن فقط - -Show files that exist on right side only -إظهار الملفات الموجودة في الجانب الأيسر فقط - -Show files that are newer on left -إظهار الملفات الأحدث في اليمين - -Show files that are newer on right -إظهار الملفات الأحدث في اليسار - -Show files that are equal -إظهار الملفات المتماثلة على الطرفين - -Show files that are different -إظهار الملفات المختلفة على الطرفين - -Show conflicts -إظهار الاختلافات - -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 updated on the left side -إظهار الملفات التي سيتم تحديثها في الجانب الأيسر - -Show files that will be updated on the right side -إظهار الملفات التي سيتم تحديثها في الجانب الأيمن - -Show files that won't be copied -إظهار الملفات التي لن يتم نسخها - -Show filtered or temporarily excluded files -إظهار الملفات التي تم فلترتها أو استبعادها بشكل مؤقت - -Save as default -حفظ كافتراضي - -Filter -عامل الفلترة - -All files are in sync -جميع الملفات في تزامن كامل - -Cannot find %x -لا يمكن العثور على %x - -Move up -تحريك لأعلى - -Move down -تحريك لأسفل - -Comma-separated values -قائمة قيم مفصولة بفواصل - -File list exported -تم تصدير قائمة الملفات - -Searching for program updates... -جاري البحث عن تحديثات للبرنامج... - -Paused -تم الإيقاف مؤقتاً - -Stop requested... - - -Initializing... -التجهيز للبدأ... - -Scanning... -جاري الفحص... - -Comparing content... -مقارنة المحتوى... - -Info -معلومات - -Select all -اختيار الجميع - -&Continue -&مواصلة - -Progress -التقدم - -Log -السجل - -Thank you, %x, for your donation and support! -شكرا لك، %x, للتبرع والدعم. - -Recommended range: -النطاق المستحسن: - -Password: -كلمة المرور: - -Key password: -كلمة المرور الرئيسية: - -Please enter a file path. -الرجاء إدخال مسار الملف. - - -Copy the following item to another folder? -Copy the following %x items to another folder? - - -نسخ العنصر 0 التالي إلى مجلد آخر؟ -نسخ العنصر 1 التالي إلى مجلد آخر؟ -نسخ العنصران التاليان إلى مجلد آخر؟ -نسخ الـ %x عناصر التالية إلى مجلد آخر؟ -نسخ الـ %x عنصراً التالية إلى مجلد آخر؟ -نسخ الـ %x عنصر التالية إلى مجلد آخر؟ - - -Please enter a target folder. -الرجاء إدخال المجلد الهدف. - - -Do you really want to move the following item to the recycle bin? -Do you really want to move the following %x items to the recycle bin? - - -هل تريد حقاً نقل 0 ملف إلى سلة المهملات ؟ -هل تريد حقاً نقل هذا الملف الوحيد 1 إلى سلة المهملات ؟ -هل تريد حقاً نقل هذين الملفبن 2 إلى سلة المهملات ؟ -هل تريد حقاً نقل %x ملفات إلى سلة المهملات ؟ -هل تريد حقاً نقل %x ملفاً إلى سلة المهملات ؟ -هل تريد حقاً نقل %x ملف إلى سلة المهملات ؟ - - -Move -نقل - - -Do you really want to delete the following item? -Do you really want to delete the following %x items? - - -هل تريد حقاً حذف 0 ملف التالي ؟ -هل تريد حقاً حذف الملف 1 التالي ؟ -هل تريد حقاً حذف الملفين 2 التاليين ؟ -هل تريد حقاً حذف الـ %x ملفات التالية ؟ -هل تريد حقاً حذف الـ %x ملفاً التالية ؟ -هل تريد حقاً حذف الـ %x ملف التالية ؟ - - -Copy DACL, SACL, Owner, Group -نسخ DACL, SACL, Owner, Group - -Integrate external applications into context menu. The following macros are available: -دمج تطبيقات خارجية في قائمة السياق. تتوفر وحدات الماكرو التالية: - -Full file or folder path -المسار الكامل للملف أو المجلد - -Parent folder path -مسار المجلد الحاوي - -Temporary local copy for SFTP and MTP storage -نسخة محلية مؤقتة لتخزين SFTP و MTP - -Parameters for opposite side -معلمات الجانب المعاكس - -Downloading update... -جار تحميل التحديث... - -Identify equal files by comparing modification time and size. -التعرف على الملفات المتساوية عن طريق مقارنة التاريخ و الحجم. - -Identify equal files by comparing the file content. -التعرف على الملفات المتساوية عن طريق مقارنة محتوى الملف. - -Identify equal files by comparing their file size. -تحديد الملفات المتساوية بمقارنة حجم الملف لها. - -Identify and propagate changes on both sides. Deletions, moves and conflicts are detected automatically using a database. -تحديد التغيرات و مواكبتها على الجانبين. عمليات الحذف, النقل و المشاكل المكتشفة باستخدام قواعد البيانات. - -Create a mirror backup of the left folder by adapting the right folder to match. -إنشاء نسخة احتياطية من الجانب الأيمن عن طريق تعديل الطرف الأيسر ليطابق الأيمن. - -Copy new and updated files to the right folder. -نسخ الملفات المحدثة إلى المجلد المناسب. - -Configure your own synchronization rules. -تحديد قواعد المزامنة الخاصة بك. - -Comparison -المقارنة - -Synchronization -المزامنة - -This week -هذا الأسبوع - -This month -هذا الشهر - -This year -هذه السنة - -Last x days -الأيام x الماضية - -Byte -Byte - -KB -KB - -MB -MB - -Retain deleted and overwritten files in the recycle bin -الاحتفاظ بالملفات المحذوفة والمستبدلة في سلة المهملات - -Delete and overwrite files permanently -حذف واستبدال الملفات نهائيا - -Move files to a user-defined folder -نقل الملفات إلى المجلد المحدد من قبل المستخدم - -Replace -استبدال - -Move files and replace if existing -نقل الملفات و استبدال الموجودة بها إن وجدت - -Time stamp -البصمة الزمنية - -Append a time stamp to each file name -إلحاق ختم زمني بكل اسم ملف - -On completion: -عند الانتهاء: - -On errors: -عند حصول أخطاء: - -On success: -عند النجاح: - -Main config -التكوين الرئيسي - -empty -فارغ - -Leave as unresolved conflict -ترك كاختلافات من دون حل - -File -ملف - -YYYY-MM-DD hhmmss -YYYY-MM-DD hhmmss - -Files -ملفات - -Percentage -النسبة المئوية - -Failed to retrieve update information. -فشل استرداد معلومات التحديث. - -Automatic updates: -التحديثات التلقائية: - -Requires FreeFileSync Donation Edition -يتطلب FreeFileSync Donation Edition - -Check for Program Updates -تفقد وجود تحديثات للبرنامج - -Auto-update now or download manually from the FreeFileSync home page? -التحديث التلقائي الآن أو تحميل يدويا من الصفحة الرئيسية لـ FreeFileSync؟ - -&Auto-update -&تحديث تلقائي - -&Home page -&الصفحة الرئيسية - -Download now? -تنزيل الآن؟ - -&Download -&تنزيل - -FreeFileSync is up to date. -FreeFileSync بأحدث إصدار بالفعل. - -Cannot find current FreeFileSync version number online. A newer version is likely available. Check manually now? -لا يمكن إيجاد رقم النسخة الحالية لـ FreeFileSync عبر الإنترنت. ربما تتوفر نسخة جديدة، هل تريد فعل ذلك يدويا؟ - -&Check -&تحقق - -Consistency check failed for %x. -فشل فحص التناسق لـ %x. - -Installation was registered on a different operating system. -تم تسجيل التثبيت على نظام تشغيل مختلف. - -Failed to activate FreeFileSync Donation Edition. -فشل تفعيل FreeFileSync Donation Edition. - -Incorrect activation key. -مفتاح التفعيل غير صحيح. - -Unable to register to receive system messages. -تعذر التسجيل لاستقبال رسائل النظام. - -Cannot find system function %x. -لا يمكن العثور على وظيفة نظام %x. - -Unable to register device notifications for %x. -تعذر تسجيل تنبيهات الجهاز لـ %x. - -Cannot monitor directory %x. -لا يمكن مراقبة المسار %x. - -The file is locked by another process: -الملف مقفول من قبل عملية أخرى: - -Cannot read security context of %x. -لا يمكن قراءة سياق الأمان %x. - -Cannot write security context of %x. -لا يمكن كتابة سياق الأمان %x. - -Cannot read permissions of %x. -لا يمكن قراءة أذونات %x. - -Cannot copy permissions from %x to %y. -لا يمكن نسخ الأذونات من %x إلى %y. - -%x is not a regular directory name. -%x ليس اسم دليل اعتيادي. - -Cannot copy file %x to %y. -لا يمكن نسخ الملف %x إلى %y. - -Cannot copy attributes from %x to %y. -لا يمكن نسخ السمات من %x إلى %y. - -%x TB -‎%x TB - -%x PB -‎%x PB - - -1 min -%x min - - -0 دقيقة -1 دقيقة واحدة -2 دقيقتان -%x دقائق -%x دقيقة -%x دقيقة - - - -1 hour -%x hours - - -0 ساعة -1 ساعة واحدة -2 ساعتين -%x ساعات -%x ساعة -%x ساعة - - -Cannot set privilege %x. -لا يمكن تعيين امتيازات %x. - -Unable to suspend system sleep mode. -تعذر تعليق وضع النوم للنظام. - -Cannot change process I/O priorities. -تعذر تغيير أولويات I/O للعملية. - -Unable to shut down the system. -غير فادر على إيقاف تشغيل النظام. - -Checking recycle bin failed for folder %x. -فشل تصفح سلة المهملات من أجل الملف %x. - -The following XML elements could not be read: -عناصر XML التالية لا يمكن قراءتها: - -Configuration file %x is incomplete. The missing elements will be set to their default values. -ملف التكوين %x غير مكتمل. سيتم إعادة تعيين العناصر المفقودة إلى قيمها الافتراضية. - -Prepare installation -الاستعداد للتثبيت - -Choose which components you want to install. -اختر المكونات التي تريد تنصيبها. - -Select installation type: -اختيار نوع التنصيب: - -Local -محلي - -Portable -متنقل - -Save settings to "%APPDATA%\FreeFileSync" -حفظ الإعدادات إلى ‏‪"%APPDATA%\FreeFileSync" - -Register FreeFileSync file extensions -تسجيل امتدادات الملفات لـ FreeFileSync - -Create Explorer context menu entries -إنشاء مدخلات القوائم المحلية في متصفح الملفات - -Save settings in installation directory -احفظ إعدادات التثبيت في مسار التثبيت - -Do not write to Registry -لا تسجل في ملفات الرجيستري - -Just copy the files -انسخ الملفات فقط - -Choose a directory for installation: -اختيار مسار التثبيت: - -Create shortcuts: -إنشاء اختصار: - -Desktop -سطح المكتب - -Start Menu - - -Send To - - -Registering FreeFileSync file extensions -جار تسجيل امتدادات الملفات لـ FreeFileSync - -Unregistering FreeFileSync file extensions -جار إلغاء تسجيل امتدادات الملفات لـ FreeFileSync - -FreeFileSync Configuration -تضبيطات FreeFileSync - -FreeFileSync Batch File -الملف الدفعي الخاص بـ FreeFileSync - -FreeFileSync Synchronization Database -قاعدة بيانات المزامنة الخاصة بـ FreeFileSync - -RealTimeSync Configuration -تضبيطات RealTimeSync - -Edit with FreeFileSync -تعديل بواسطة FreeFileSync - -The FreeFileSync portable version cannot install into a subfolder of %x. -لا يمكن تثبيت النسخة المحمولة من FreeFileSync في مجلد فرعي داخل %x. - -Please choose the local installation type or select a different folder for installation. -الرجاء اختيار نوع التثبيت المحلي أو اختيار مجلد آخر للتثبيت. - -The %x installation option is only available in the FreeFileSync Donation Edition. - - diff --git a/FreeFileSync/Build/Languages/bulgarian.lng b/FreeFileSync/Build/Languages/bulgarian.lng index 4d30763f..f016e045 100755 --- a/FreeFileSync/Build/Languages/bulgarian.lng +++ b/FreeFileSync/Build/Languages/bulgarian.lng @@ -112,6 +112,9 @@ Path to an alternate GlobalSettings.xml file. Път до алтернативен файл GlobalSettings.xml. +Installation files are corrupted. Please reinstall FreeFileSync. +Инсталационните файлове са повредени. Моля, преинсталирайте FreeFileSync. + Cannot find the following folders: Не са намерени следните папки: @@ -333,12 +336,12 @@ Actual: %y bytes Cannot find %x. Не е намерен %x. -Cannot open file %x. -Не може да отвори файл %x. - Cannot find device %x. Не е намерено устройство %x. +Cannot open file %x. +Не може да отвори файл %x. + Type of item %x is not supported: Типа на елемент %x не се поддържа: @@ -465,6 +468,9 @@ Actual: %y bytes Total time: Общо време: +Cleaning up old log files... +Изчиства старите протоколни файлове... + Error parsing file %x, row %y, column %z. Грешка при анализ на файл %x, ред %y, колона %z. @@ -653,6 +659,15 @@ The command is triggered if: Multiple... Разни... +Cannot write file attributes of %x. +Не може да запише файловите атрибути на %x. + +%x and %y have different content. +%x и %y имат различно съдържание. + +Data verification error: +Грешка при верификация на данните: + Moving file %x to %y Премества файл %x към %y @@ -677,14 +692,8 @@ The command is triggered if: Updating attributes of %x Актуализира атрибутите на %x -Cannot write file attributes of %x. -Не може да запише файловите атрибути на %x. - -%x and %y have different content. -%x и %y имат различно съдържание. - -Data verification error: -Грешка при верификация на данните: +Source item %x not found +Изходен елемент %x не е намерен Creating a Volume Shadow Copy for %x... Създава се Volume Shadow Copy за %x... @@ -746,9 +755,6 @@ The command is triggered if: System: Shut down Система: Изключване -Cleaning up old log files... -Изчиства старите протоколни файлове... - Stopped Спряно @@ -881,6 +887,12 @@ The command is triggered if: Please select a folder on a local file system, network or an MTP device. Моля, изберете папка от локална файлова система, мрежа или MTP-устройство. +Defined by context of use + + +Requires FreeFileSync Donation Edition +Изисква FreeFileSync Дарителско Издание + &Save &Запази @@ -1031,6 +1043,15 @@ The command is triggered if: Handle daylight saving time Отчети лятното време +Performance improvements: +Подобряване на производителността: + +Parallel file operations: +Паралелни файлови операции: + +How to get best performance? +Как да се получи най-добра производителност? + Local settings: Локални настройки: @@ -1147,15 +1168,6 @@ The command is triggered if: Directory on server: Директория на сървъра: -Performance improvements: -Подобряване на производителността: - -How to get best performance? -Как да се получи най-добра производителност? - -Connections for directory reading: -Връзки за четене на папка: - SFTP channels per connection: SFTP-канали на връзка: @@ -1291,8 +1303,17 @@ This guarantees a consistent state even in case of a serious error. &Default &По подразбиране -Source code written in C++ using: -Изходния код е написан на C++ със: +Feedback and suggestions are welcome +Забележки и предложения са добре дошли + +Home page +Домашна страница + +FreeFileSync Forum +FreeFileSync-Форум + +Email +Ел.поща If you like FreeFileSync: Ако харесвате FreeFileSync: @@ -1300,20 +1321,14 @@ This guarantees a consistent state even in case of a serious error. Support with a donation Подкрепете с дарение -Donation details -Подробности за дарение - The auto updater was disabled by the administrator. Автоматичното обновяване бе деактивирано от администратора. -Feedback and suggestions are welcome -Забележки и предложения са добре дошли - -Home page -Домашна страница +Donation details +Подробности за дарение -Email -Ел.поща +Source code written in C++ using: +Изходния код е написан на C++ със: Published under the GNU General Public License Публикува се по лиценза GNU General Public License @@ -1402,9 +1417,6 @@ This guarantees a consistent state even in case of a serious error. FreeFileSync %x is available! FreeFileSync %x е наличен! -Installation files are corrupted. Please reinstall FreeFileSync. -Инсталационните файлове са повредени. Моля, преинсталирайте FreeFileSync. - Local path not available for %x. Няма наличен локален път за %x. @@ -1627,6 +1639,9 @@ This guarantees a consistent state even in case of a serious error. Thank you, %x, for your donation and support! %x, благодаря за Вашето дарение и подкрепа! +Connections +Свързвания + Recommended range: Препоръчителен обхват: @@ -1798,9 +1813,6 @@ This guarantees a consistent state even in case of a serious error. Automatic updates: Автоматично актуализиране: -Requires FreeFileSync Donation Edition -Изисква FreeFileSync Дарителско Издание - Check for Program Updates Проверка за нова версия на програмата @@ -1990,6 +2002,9 @@ This guarantees a consistent state even in case of a serious error. Edit with FreeFileSync Редактиране с FreeFileSync +Instead of an ad, here's an animal. +Вместо реклама, ето една животинка. + The FreeFileSync portable version cannot install into a subfolder of %x. Преносимата версия на FreeFileSync не може да се инсталира в подпапка на %x. @@ -1999,3 +2014,6 @@ This guarantees a consistent state even in case of a serious error. The %x installation option is only available in the FreeFileSync Donation Edition. Опцията %x инсталиране е възможна само за FreeFileSync Дарителско Издание. +Get the Donation Edition with bonus features and help keep FreeFileSync ad-free. +Вземете Дарителско Издание с бонуси и помогнете FreeFileSync да остане свободна. + diff --git a/FreeFileSync/Build/Languages/chinese_simple.lng b/FreeFileSync/Build/Languages/chinese_simple.lng index 03a647de..4bef3554 100755 --- a/FreeFileSync/Build/Languages/chinese_simple.lng +++ b/FreeFileSync/Build/Languages/chinese_simple.lng @@ -112,6 +112,9 @@ Path to an alternate GlobalSettings.xml file. 指向一个替代的 GlobalSettings.xml 文件的路径. +Installation files are corrupted. Please reinstall FreeFileSync. +安装文件已损坏, 请重新安装FreeFileSync. + Cannot find the following folders: 无法找到如下文件夹: @@ -332,12 +335,12 @@ Actual: %y bytes Cannot find %x. 无法找到 %x. -Cannot open file %x. -无法打开文件 %x. - Cannot find device %x. 无法找到设备 %x. +Cannot open file %x. +无法打开文件 %x. + Type of item %x is not supported: 项目 %x 的类型不被支持: @@ -460,6 +463,9 @@ Actual: %y bytes Total time: 总共时间: +Cleaning up old log files... +正在清理旧日志文件... + Error parsing file %x, row %y, column %z. 当分析文件 %x , 行 %y, 列 %z 时出错. @@ -647,6 +653,15 @@ The command is triggered if: Multiple... 并联... +Cannot write file attributes of %x. +无法写入 %x 的文件属性. + +%x and %y have different content. +%x 和 %y 有着不同的内容. + +Data verification error: +数据校验出错: + Moving file %x to %y 正在移动文件 %x 到 %y @@ -671,14 +686,8 @@ The command is triggered if: Updating attributes of %x 正在更新 %x 的属性 -Cannot write file attributes of %x. -无法写入 %x 的文件属性. - -%x and %y have different content. -%x 和 %y 有着不同的内容. - -Data verification error: -数据校验出错: +Source item %x not found +来源项目 %x 未找到 Creating a Volume Shadow Copy for %x... 正在为 %x 创建一个卷影副本... @@ -740,9 +749,6 @@ The command is triggered if: System: Shut down 系统: 关机 -Cleaning up old log files... -正在清理旧日志文件... - Stopped 已停止 @@ -874,6 +880,12 @@ The command is triggered if: Please select a folder on a local file system, network or an MTP device. 请选择在本地文件系统, 网络或MTP设备上的文件夹. +Defined by context of use + + +Requires FreeFileSync Donation Edition +需要FreeFileSync捐赠版 + &Save 保存(&S) @@ -1024,6 +1036,15 @@ The command is triggered if: Handle daylight saving time 处理夏令时 +Performance improvements: +性能改进: + +Parallel file operations: +并行文件操作: + +How to get best performance? +如何取得最佳性能? + Local settings: 本地设置: @@ -1140,15 +1161,6 @@ The command is triggered if: Directory on server: 服务器上的目录: -Performance improvements: -性能改进: - -How to get best performance? -如何取得最佳性能? - -Connections for directory reading: -用于目录读取的连接: - SFTP channels per connection: 每个连接的SFTP通道数: @@ -1281,8 +1293,17 @@ This guarantees a consistent state even in case of a serious error. &Default 默认(&D) -Source code written in C++ using: -源代码使用如下工具由C++写成: +Feedback and suggestions are welcome +欢迎反馈意见和提出建议 + +Home page +主页 + +FreeFileSync Forum +FreeFileSync 论坛 + +Email +邮箱 If you like FreeFileSync: 如果你喜欢 FreeFileSync: @@ -1290,20 +1311,14 @@ This guarantees a consistent state even in case of a serious error. Support with a donation 通过捐款来支持我们 -Donation details -捐款详情 - The auto updater was disabled by the administrator. 自动更新程序已由管理员禁用. -Feedback and suggestions are welcome -欢迎反馈意见和提出建议 - -Home page -主页 +Donation details +捐款详情 -Email -邮箱 +Source code written in C++ using: +源代码使用如下工具由C++写成: Published under the GNU General Public License 在GNU通用公共许可下发布 @@ -1392,9 +1407,6 @@ This guarantees a consistent state even in case of a serious error. FreeFileSync %x is available! FreeFileSync %x 可用! -Installation files are corrupted. Please reinstall FreeFileSync. -安装文件已损坏, 请重新安装FreeFileSync. - Local path not available for %x. 本地路径不适用于 %x. @@ -1613,6 +1625,9 @@ This guarantees a consistent state even in case of a serious error. Thank you, %x, for your donation and support! 谢谢你, %x , 对我们的捐款和支持! +Connections +连接数 + Recommended range: 建议的范围: @@ -1781,9 +1796,6 @@ This guarantees a consistent state even in case of a serious error. Automatic updates: 自动更新: -Requires FreeFileSync Donation Edition -需要FreeFileSync捐赠版 - Check for Program Updates 检查程序的更新 @@ -1971,6 +1983,9 @@ This guarantees a consistent state even in case of a serious error. Edit with FreeFileSync 使用 FreeFileSync 编辑 +Instead of an ad, here's an animal. +这里是一只动物, 而不是广告. + The FreeFileSync portable version cannot install into a subfolder of %x. FreeFileSync 便携版无法安装到 %x 的一个子目录. @@ -1980,3 +1995,6 @@ This guarantees a consistent state even in case of a serious error. The %x installation option is only available in the FreeFileSync Donation Edition. 安装选项 %x 只在 FreeFileSync 捐赠版有效. +Get the Donation Edition with bonus features and help keep FreeFileSync ad-free. +获取有额外功能的捐赠版本, 并帮助保持 FreeFileSync 无广告. + diff --git a/FreeFileSync/Build/Languages/chinese_traditional.lng b/FreeFileSync/Build/Languages/chinese_traditional.lng index defe3748..6550e726 100755 --- a/FreeFileSync/Build/Languages/chinese_traditional.lng +++ b/FreeFileSync/Build/Languages/chinese_traditional.lng @@ -112,6 +112,9 @@ Path to an alternate GlobalSettings.xml file. 備用的GlobalSettings.xml檔案路徑。 +Installation files are corrupted. Please reinstall FreeFileSync. +安裝檔已損壞。請重新安裝FreeFileSync。 + Cannot find the following folders: 找不到下列資料夾: @@ -332,12 +335,12 @@ Actual: %y bytes Cannot find %x. 找不到 %x。 -Cannot open file %x. -無法開啟檔案 %x。 - Cannot find device %x. 找不到裝置 %x。 +Cannot open file %x. +無法開啟檔案 %x。 + Type of item %x is not supported: 項目類型 %x 不被支援: @@ -460,6 +463,9 @@ Actual: %y bytes Total time: 全部時間: +Cleaning up old log files... +清理舊日誌檔… + Error parsing file %x, row %y, column %z. 解析 %x 檔案,第 %y 列,第 %z 行出現錯誤。 @@ -647,6 +653,15 @@ The command is triggered if: Multiple... 多個… +Cannot write file attributes of %x. +無法寫入 %x 的檔案屬性。 + +%x and %y have different content. +%x 和 %y 具有不同的內容。 + +Data verification error: +資料驗證錯誤: + Moving file %x to %y 正在移動檔案 %x 到 %y @@ -671,14 +686,8 @@ The command is triggered if: Updating attributes of %x 正在更新 %x 的屬性 -Cannot write file attributes of %x. -無法寫入 %x 的檔案屬性。 - -%x and %y have different content. -%x 和 %y 具有不同的內容。 - -Data verification error: -資料驗證錯誤: +Source item %x not found +找不到來源項目 %x Creating a Volume Shadow Copy for %x... 正在建立磁碟區陰影複製為 %x… @@ -740,9 +749,6 @@ The command is triggered if: System: Shut down 系統:關機 -Cleaning up old log files... -清理舊日誌檔… - Stopped 已停止 @@ -874,6 +880,12 @@ The command is triggered if: Please select a folder on a local file system, network or an MTP device. 請選擇一個本機檔案系統、網路或MTP裝置上的資料夾。 +Defined by context of use + + +Requires FreeFileSync Donation Edition +需要FreeFileSync贊助版 + &Save 儲存(&S) @@ -1024,6 +1036,15 @@ The command is triggered if: Handle daylight saving time 處理日光節約時間 +Performance improvements: +效能改善: + +Parallel file operations: +並列檔案操作: + +How to get best performance? +如何獲得最佳效能? + Local settings: 本機設定: @@ -1140,15 +1161,6 @@ The command is triggered if: Directory on server: 在伺服器上的目錄: -Performance improvements: -效能改善: - -How to get best performance? -如何獲得最佳效能? - -Connections for directory reading: -用於讀取目錄的連接: - SFTP channels per connection: 每個連接SFTP通道數: @@ -1284,8 +1296,17 @@ This guarantees a consistent state even in case of a serious error. &Default 預設(&D) -Source code written in C++ using: -使用C++編寫的原始碼: +Feedback and suggestions are welcome +歡迎反映意見和建議 + +Home page +主頁 + +FreeFileSync Forum +FreeFileSync論壇 + +Email +信箱 If you like FreeFileSync: 如果您喜歡FreeFileSync: @@ -1293,20 +1314,14 @@ This guarantees a consistent state even in case of a serious error. Support with a donation 贊助與支持 -Donation details -贊助詳情 - The auto updater was disabled by the administrator. 自動更新程式已被管理員禁用。 -Feedback and suggestions are welcome -歡迎反映意見和建議 - -Home page -主頁 +Donation details +贊助詳情 -Email -信箱 +Source code written in C++ using: +使用C++編寫的原始碼: Published under the GNU General Public License 在GNU通用公共許可證下發佈 @@ -1395,9 +1410,6 @@ This guarantees a consistent state even in case of a serious error. FreeFileSync %x is available! FreeFileSync %x 可用! -Installation files are corrupted. Please reinstall FreeFileSync. -安裝檔已損壞。請重新安裝FreeFileSync。 - Local path not available for %x. 本機路徑不適用於 %x。 @@ -1616,6 +1628,9 @@ This guarantees a consistent state even in case of a serious error. Thank you, %x, for your donation and support! %x, 感謝您的贊助與支持! +Connections +連線數 + Recommended range: 建議範圍: @@ -1784,9 +1799,6 @@ This guarantees a consistent state even in case of a serious error. Automatic updates: 自動更新: -Requires FreeFileSync Donation Edition -需要FreeFileSync贊助版 - Check for Program Updates 檢查程式更新 @@ -1974,6 +1986,9 @@ This guarantees a consistent state even in case of a serious error. Edit with FreeFileSync 使用FreeFileSync進行編輯 +Instead of an ad, here's an animal. +這是一隻動物,而不是廣告。 + The FreeFileSync portable version cannot install into a subfolder of %x. FreeFileSync可攜式版本無法安裝到 %x 的子資料夾。 @@ -1983,3 +1998,6 @@ This guarantees a consistent state even in case of a serious error. The %x installation option is only available in the FreeFileSync Donation Edition. %x 安裝選項僅在FreeFileSync贊助版可用。 +Get the Donation Edition with bonus features and help keep FreeFileSync ad-free. +獲得贊助版的獎勵功能,並協助維持FreeFileSync無廣告。 + diff --git a/FreeFileSync/Build/Languages/croatian.lng b/FreeFileSync/Build/Languages/croatian.lng index 7748df23..dce705ac 100755 --- a/FreeFileSync/Build/Languages/croatian.lng +++ b/FreeFileSync/Build/Languages/croatian.lng @@ -112,6 +112,9 @@ Path to an alternate GlobalSettings.xml file. Putanja do alternativne GlobalSettings.xml datoteke. +Installation files are corrupted. Please reinstall FreeFileSync. +Instalacijske datoteke su oštećene. Molimo ponovno instalirajte FreeFileSync. + Cannot find the following folders: Ne mogu pronaći slijedeće mape: @@ -334,12 +337,12 @@ Stvarno: %y bajta Cannot find %x. Nije moguće pronaći %x. -Cannot open file %x. -Ne mogu otvoriti datoteku %x. - Cannot find device %x. Nije moguće pronaći uređaj %x. +Cannot open file %x. +Ne mogu otvoriti datoteku %x. + Type of item %x is not supported: Vrsta stavke %x koji nije podržan: @@ -470,6 +473,9 @@ Stvarno: %y bajta Total time: Ukupno vrijeme: +Cleaning up old log files... +Čistim stare datoteke o izvješću... + Error parsing file %x, row %y, column %z. Greška u analizi datoteke %x, red %y, stupac %z. @@ -659,6 +665,15 @@ Naredba će biti pokrenuta ako se: Multiple... Mnogostruko... +Cannot write file attributes of %x. +Ne mogu zapisati svojstva od %x. + +%x and %y have different content. +%x. i %y imaju različit sadržaj. + +Data verification error: +Pogreška verificiranja podataka: + Moving file %x to %y Premještanje datoteke %x u %y @@ -683,14 +698,8 @@ Naredba će biti pokrenuta ako se: Updating attributes of %x Obnavljam atribute od %x -Cannot write file attributes of %x. -Ne mogu zapisati svojstva od %x. - -%x and %y have different content. -%x. i %y imaju različit sadržaj. - -Data verification error: -Pogreška verificiranja podataka: +Source item %x not found +Izvorna datoteka %x nije pronađena Creating a Volume Shadow Copy for %x... Kreiranje Volume Shadow Copy za %x... @@ -752,9 +761,6 @@ Naredba će biti pokrenuta ako se: System: Shut down Sistem: Isključivanje -Cleaning up old log files... -Čistim stare datoteke o izvješću... - Stopped Zaustavljeno @@ -888,6 +894,12 @@ Naredba će biti pokrenuta ako se: Please select a folder on a local file system, network or an MTP device. Molimo odaberite mapu na lokalnom disku, mreži ili MTP uređaju. +Defined by context of use + + +Requires FreeFileSync Donation Edition +Zahtjeva FreeFileSync Donatorsku Verziju + &Save &Spremi @@ -1038,6 +1050,15 @@ Naredba će biti pokrenuta ako se: Handle daylight saving time Upravljaj ljetnim računanjem vremena +Performance improvements: +Poboljšanje perfomansi: + +Parallel file operations: +Paralelne operacije datotekama: + +How to get best performance? +Kako dobiti najbolje perfomanse? + Local settings: Lokalne postavke: @@ -1154,15 +1175,6 @@ Naredba će biti pokrenuta ako se: Directory on server: Mapa na serveru: -Performance improvements: -Poboljšanje perfomansi: - -How to get best performance? -Kako dobiti najbolje perfomanse? - -Connections for directory reading: -Broj veza za čitanje mapa: - SFTP channels per connection: SFTP kanala po konekciji: @@ -1298,8 +1310,17 @@ Ovo garantira stabilno stanje čak u slučaju ozbiljne greške. &Default &Zadano -Source code written in C++ using: -Izvorni kod napisan u C++ uz korištenje: +Feedback and suggestions are welcome +Povratne informacije i prijedlozi su dobrodošli + +Home page +Web stranica + +FreeFileSync Forum +FreeFileSync Forum + +Email +Email If you like FreeFileSync: Ako volite FreeFileSync: @@ -1307,20 +1328,14 @@ Ovo garantira stabilno stanje čak u slučaju ozbiljne greške. Support with a donation Podržite nas donacijom -Donation details -Detalji donacije - The auto updater was disabled by the administrator. Auto updater je onemogućen od strane administratora. -Feedback and suggestions are welcome -Povratne informacije i prijedlozi su dobrodošli - -Home page -Web stranica +Donation details +Detalji donacije -Email -Email +Source code written in C++ using: +Izvorni kod napisan u C++ uz korištenje: Published under the GNU General Public License Objavljeno pod licencom GNU General Public @@ -1409,9 +1424,6 @@ Ovo garantira stabilno stanje čak u slučaju ozbiljne greške. FreeFileSync %x is available! FreeFileSync %x je dostupan! -Installation files are corrupted. Please reinstall FreeFileSync. -Instalacijske datoteke su oštećene. Molimo ponovno instalirajte FreeFileSync. - Local path not available for %x. Nije dostupna lokalna putanja za %x. @@ -1638,6 +1650,9 @@ Ovo garantira stabilno stanje čak u slučaju ozbiljne greške. Thank you, %x, for your donation and support! Hvala Vam, %x, na vašoj donaciji i podršci! +Connections +Konekcije + Recommended range: Predloženi raspon: @@ -1812,9 +1827,6 @@ Ovo garantira stabilno stanje čak u slučaju ozbiljne greške. Automatic updates: Automatsko ažuriranje: -Requires FreeFileSync Donation Edition -Zahtjeva FreeFileSync Donatorsku Verziju - Check for Program Updates Provjeri za nadogradnje programa @@ -2006,6 +2018,9 @@ Ovo garantira stabilno stanje čak u slučaju ozbiljne greške. Edit with FreeFileSync Uredi pomoću FreeFileSynca +Instead of an ad, here's an animal. +Umjesto reklama, prikazujemo vam životinje. + The FreeFileSync portable version cannot install into a subfolder of %x. FreeFileSync prijenosna verzija se ne može instalirati u podfolder od %x. @@ -2015,3 +2030,6 @@ Ovo garantira stabilno stanje čak u slučaju ozbiljne greške. The %x installation option is only available in the FreeFileSync Donation Edition. %x opcija instalacije je dostupna samo u FreeFileSync Donatorskoj verziji. +Get the Donation Edition with bonus features and help keep FreeFileSync ad-free. +Preuzmite donatorsku verziju sa bonus opcijama i pomognite da FreeFileSync ostane bez reklama. + diff --git a/FreeFileSync/Build/Languages/czech.lng b/FreeFileSync/Build/Languages/czech.lng index 499fe4e3..6c04eadc 100755 --- a/FreeFileSync/Build/Languages/czech.lng +++ b/FreeFileSync/Build/Languages/czech.lng @@ -7,6 +7,9 @@ n==1 ? 0 : n>=2 && n<=4 ? 1 : 2 +Defined by context of use + + Both sides have changed since last synchronization. Došlo ke změně obou stran od poslední synchronizace. @@ -112,6 +115,9 @@ Path to an alternate GlobalSettings.xml file. Cesta k alternativnímu souboru GlobalSettings.xml. +Installation files are corrupted. Please reinstall FreeFileSync. +Instalace programu je poškozena. Prosím nainstalujte znovu FreeFileSync. + Cannot find the following folders: Nelze najít následující složky: @@ -334,12 +340,12 @@ Aktuálně: %y b Cannot find %x. Nelze najít %x. -Cannot open file %x. -Nelze otevřít soubor %x. - Cannot find device %x. Nelze nalézt zařízení %x. +Cannot open file %x. +Nelze otevřít soubor %x. + Type of item %x is not supported: Typ položky %x není podporován: @@ -470,6 +476,9 @@ Aktuálně: %y b Total time: Celkový čas: +Cleaning up old log files... +Odstraňování starých žurnálů... + Error parsing file %x, row %y, column %z. Chyba zpracování souboru %x: na řádku %y ve sloupci %z. @@ -659,6 +668,15 @@ Příkaz je spuštěn když: Multiple... Různé... +Cannot write file attributes of %x. +Nelze zapsat vlastnosti souboru %x. + +%x and %y have different content. +%x a %y mají odlišný obsah. + +Data verification error: +Chyba verifikace dat: + Moving file %x to %y Přesouvání souboru %x do %y @@ -683,14 +701,8 @@ Příkaz je spuštěn když: Updating attributes of %x Aktualizace vlastností souboru %x -Cannot write file attributes of %x. -Nelze zapsat vlastnosti souboru %x. - -%x and %y have different content. -%x a %y mají odlišný obsah. - -Data verification error: -Chyba verifikace dat: +Source item %x not found +Zdrojová položka %x nenalezena. Creating a Volume Shadow Copy for %x... Vytváření Stínové kopie pro %x... @@ -752,9 +764,6 @@ Příkaz je spuštěn když: System: Shut down Vypnutí počítače -Cleaning up old log files... -Odstraňování starých žurnálů... - Stopped Zastaveno @@ -888,6 +897,9 @@ Příkaz je spuštěn když: Please select a folder on a local file system, network or an MTP device. Prosím vyberte složku na místním souborovém systému, síti nebo multimediální zařízení. +Requires FreeFileSync Donation Edition +Je vyžadována předplacená verze FreeFileSync Donation Edition + &Save &Uložit @@ -1038,6 +1050,15 @@ Příkaz je spuštěn když: Handle daylight saving time Používat letní čas +Performance improvements: +Vylepšení rychlosti: + +Parallel file operations: +Paralelní operace se soubory: + +How to get best performance? +Jak získat nejlepší rychlost? + Local settings: Nastavení: @@ -1154,15 +1175,6 @@ Příkaz je spuštěn když: Directory on server: Adresář na serveru: -Performance improvements: -Vylepšení rychlosti: - -How to get best performance? -Jak získat nejlepší rychlost? - -Connections for directory reading: -Spojení pro čtení adresáře: - SFTP channels per connection: SFTP kanálů na spojení: @@ -1295,8 +1307,17 @@ This guarantees a consistent state even in case of a serious error. &Default &Předdefinované -Source code written in C++ using: -Zdrojový kód byl napsán kompletně v C++ pomocí: +Feedback and suggestions are welcome +Komentáře a náměty jsou vždy vítány + +Home page +Navštivte + +FreeFileSync Forum +Diskuzní fórum FreeFileSync + +Email +Napište If you like FreeFileSync: Pokud se Vám FreeFileSync líbí: @@ -1304,20 +1325,14 @@ This guarantees a consistent state even in case of a serious error. Support with a donation Podpořit darem -Donation details -Podrobnosti - The auto updater was disabled by the administrator. Automatická aktualizace byla správcem zakázána. -Feedback and suggestions are welcome -Komentáře a náměty jsou vždy vítány - -Home page -Navštivte +Donation details +Podrobnosti -Email -Napište +Source code written in C++ using: +Zdrojový kód byl napsán kompletně v C++ pomocí: Published under the GNU General Public License Vydáno pod GNU General Public License (GPL) @@ -1406,9 +1421,6 @@ This guarantees a consistent state even in case of a serious error. FreeFileSync %x is available! Je k dostupná verze FreeFileSync %x! -Installation files are corrupted. Please reinstall FreeFileSync. -Instalace programu je poškozena. Prosím nainstalujte znovu FreeFileSync. - Local path not available for %x. Místní cesta není k dispozici pro %x. @@ -1635,6 +1647,9 @@ This guarantees a consistent state even in case of a serious error. Thank you, %x, for your donation and support! Děkuji, %x, za příspěvek a podporu! +Connections +Připojení + Recommended range: Doporučený rozsah: @@ -1809,9 +1824,6 @@ This guarantees a consistent state even in case of a serious error. Automatic updates: Automatická aktualizace: -Requires FreeFileSync Donation Edition -Je vyžadována předplacená verze FreeFileSync Donation Edition - Check for Program Updates Hledání aktualizací programu @@ -2003,6 +2015,9 @@ This guarantees a consistent state even in case of a serious error. Edit with FreeFileSync Upravit v FreeFileSync +Instead of an ad, here's an animal. +Místo reklamy zvířátko. + The FreeFileSync portable version cannot install into a subfolder of %x. Přenosná verze FreeFileSync nemůže být instalována do složky %x. @@ -2012,3 +2027,6 @@ This guarantees a consistent state even in case of a serious error. The %x installation option is only available in the FreeFileSync Donation Edition. %x instalace je k dispozici pouze v předplacené verzi FreeFileSync Donation Edition. +Get the Donation Edition with bonus features and help keep FreeFileSync ad-free. +Získat předplacenou verzi s funikcemi navíc a pomoci tím udržet FreeFileSync bez reklam. + diff --git a/FreeFileSync/Build/Languages/danish.lng b/FreeFileSync/Build/Languages/danish.lng index a0822e1d..20764c0c 100755 --- a/FreeFileSync/Build/Languages/danish.lng +++ b/FreeFileSync/Build/Languages/danish.lng @@ -112,6 +112,9 @@ Path to an alternate GlobalSettings.xml file. Sti til alternativ GlobalSettings.xml fil. +Installation files are corrupted. Please reinstall FreeFileSync. +Ødelagte installationsfiler. Geninstaller FreeFileSync. + Cannot find the following folders: Kan ikke finde følgende mapper: @@ -333,12 +336,12 @@ Aktuel: %y byte Cannot find %x. Kan ikke finde %x. -Cannot open file %x. -Filen %x kan ikke åbnes. - Cannot find device %x. Kan ikke finde enheden %x. +Cannot open file %x. +Filen %x kan ikke åbnes. + Type of item %x is not supported: Filtypen %x understøttes ikke: @@ -465,6 +468,9 @@ Aktuel: %y byte Total time: Samlet tid: +Cleaning up old log files... +Fjerner gamle logfiler... + Error parsing file %x, row %y, column %z. Behandlingsfejl i filen %x, række %y, kolonne %z. @@ -653,6 +659,15 @@ Kommandoen udføres hvis: Multiple... Flere... +Cannot write file attributes of %x. +Kan ikke skrive filattributter til %x. + +%x and %y have different content. +%x og %y har forskelligt indhold. + +Data verification error: +Verifikationsfejl: + Moving file %x to %y Flytter filen %x til %y @@ -677,14 +692,8 @@ Kommandoen udføres hvis: Updating attributes of %x Opdaterer attributter for %x -Cannot write file attributes of %x. -Kan ikke skrive filattributter til %x. - -%x and %y have different content. -%x og %y har forskelligt indhold. - -Data verification error: -Verifikationsfejl: +Source item %x not found +Kildeemne %x ikke fundet Creating a Volume Shadow Copy for %x... Opretter VSS kopi for %x... @@ -746,9 +755,6 @@ Kommandoen udføres hvis: System: Shut down System: Luk -Cleaning up old log files... -Fjerner gamle logfiler... - Stopped Afbrudt @@ -881,6 +887,12 @@ Kommandoen udføres hvis: Please select a folder on a local file system, network or an MTP device. Vælg lokal mappe, netværksmappe eller mappe på MTP enhed. +Defined by context of use + + +Requires FreeFileSync Donation Edition +Kræver FreeFileSync donationsudgave + &Save &Gem @@ -1031,6 +1043,15 @@ Kommandoen udføres hvis: Handle daylight saving time Tag hensyn til sommertid +Performance improvements: +Ydelsesforbedring: + +Parallel file operations: +Parallelle filhandlinger: + +How to get best performance? +Hvordan får du bedste ydelse? + Local settings: Lokale indstillinger: @@ -1147,15 +1168,6 @@ Kommandoen udføres hvis: Directory on server: Servermappe: -Performance improvements: -Ydelsesforbedring: - -How to get best performance? -Hvordan får du bedste ydelse? - -Connections for directory reading: -Forbindelser til mappelæsning: - SFTP channels per connection: SFTP kanaler pr. forbindelse: @@ -1291,8 +1303,17 @@ Sikrer processen ved alvorlige fejl. &Default S&tandard -Source code written in C++ using: -Kildekoden er skrevet i C++ med hjælp fra: +Feedback and suggestions are welcome +Kritik og forslag er meget velkomne + +Home page +Hjemmeside + +FreeFileSync Forum +FreeFileSync forum + +Email +Email If you like FreeFileSync: Er du glad for FreeFileSync: @@ -1300,20 +1321,14 @@ Sikrer processen ved alvorlige fejl. Support with a donation Donér for at støtte -Donation details -Donationsdetaljer - The auto updater was disabled by the administrator. Autoopdatering deaktiveret af administrator. -Feedback and suggestions are welcome -Kritik og forslag er meget velkomne - -Home page -Hjemmeside +Donation details +Donationsdetaljer -Email -Email +Source code written in C++ using: +Kildekoden er skrevet i C++ med hjælp fra: Published under the GNU General Public License Udgivet under GNU General Public Licence @@ -1402,9 +1417,6 @@ Sikrer processen ved alvorlige fejl. FreeFileSync %x is available! FreeFileSync %x er udkommet! -Installation files are corrupted. Please reinstall FreeFileSync. -Ødelagte installationsfiler. Geninstaller FreeFileSync. - Local path not available for %x. Placeringen er ikke tilgængelig for %x. @@ -1627,6 +1639,9 @@ Sikrer processen ved alvorlige fejl. Thank you, %x, for your donation and support! Tak %x for donationen og støtten! +Connections +Forbindelser + Recommended range: Anbefalet interval: @@ -1798,9 +1813,6 @@ Sikrer processen ved alvorlige fejl. Automatic updates: Automatiske opdateringer: -Requires FreeFileSync Donation Edition -Kræver FreeFileSync donationsudgave - Check for Program Updates Søg efter opdatering @@ -1990,6 +2002,9 @@ Sikrer processen ved alvorlige fejl. Edit with FreeFileSync Rediger med FreeFileSync +Instead of an ad, here's an animal. +Her er et dyr, i stedet for annoncer. + The FreeFileSync portable version cannot install into a subfolder of %x. FreeFileSync portable kan ikke installeres i en undermappe til %x. @@ -1999,3 +2014,6 @@ Sikrer processen ved alvorlige fejl. The %x installation option is only available in the FreeFileSync Donation Edition. Installationsmuligheden %x er kun mulig i FreeFileSync Donation Edition. +Get the Donation Edition with bonus features and help keep FreeFileSync ad-free. +Hent donationsudgave med flere funktioner, og hold FreeFileSync fri for annoncer. + diff --git a/FreeFileSync/Build/Languages/dutch.lng b/FreeFileSync/Build/Languages/dutch.lng index 99875a32..321314c7 100755 --- a/FreeFileSync/Build/Languages/dutch.lng +++ b/FreeFileSync/Build/Languages/dutch.lng @@ -112,6 +112,9 @@ Path to an alternate GlobalSettings.xml file. Bestandspad naar plaatsvervangend GlobalSettings.xml bestand. +Installation files are corrupted. Please reinstall FreeFileSync. +Installatiebestanden zijn beschadigd. Installeer FreeFileSync opnieuw. + Cannot find the following folders: De volgende mappen konden niet worden gevonden: @@ -333,12 +336,12 @@ Werkelijk: %y bytes Cannot find %x. Kan %x niet vinden. -Cannot open file %x. -Het bestand %x kan niet geopend worden. - Cannot find device %x. Kan apparaat %x niet vinden. +Cannot open file %x. +Het bestand %x kan niet geopend worden. + Type of item %x is not supported: Dit itemtype %x wordt niet ondersteund: @@ -465,6 +468,9 @@ Werkelijk: %y bytes Total time: Totale tijd: +Cleaning up old log files... +Opruimen oude logboekbestanden... + Error parsing file %x, row %y, column %z. Fout bij het analyseren van bestand %x, rij %y, kolom %z. @@ -653,6 +659,15 @@ De opdracht wordt geactiveerd als: Multiple... Meerdere... +Cannot write file attributes of %x. +De bestandskenmerken voor %x kunnen niet geschreven worden. + +%x and %y have different content. +%x en %y hebben verschillende inhoud. + +Data verification error: +Gegevens verificatiefout: + Moving file %x to %y Bestand %x verplaatsen naar %y @@ -677,14 +692,8 @@ De opdracht wordt geactiveerd als: Updating attributes of %x Kenmerken van %x bijwerken -Cannot write file attributes of %x. -De bestandskenmerken voor %x kunnen niet geschreven worden. - -%x and %y have different content. -%x en %y hebben verschillende inhoud. - -Data verification error: -Gegevens verificatiefout: +Source item %x not found +Bronitem %x niet gevonden Creating a Volume Shadow Copy for %x... Maken van een volume schaduwkopie voor %x... @@ -746,9 +755,6 @@ De opdracht wordt geactiveerd als: System: Shut down Systeem: Uitschakelen -Cleaning up old log files... -Opruimen oude logboekbestanden... - Stopped Gestopt @@ -881,6 +887,12 @@ De opdracht wordt geactiveerd als: Please select a folder on a local file system, network or an MTP device. Selecteer een map op een lokaal bestandssysteem, netwerk of een MTP-apparaat. +Defined by context of use + + +Requires FreeFileSync Donation Edition +Vereist FreeFileSync donatie Editie + &Save &Opslaan @@ -1031,6 +1043,15 @@ De opdracht wordt geactiveerd als: Handle daylight saving time Zomertijd gebruiken +Performance improvements: +Prestatieverbeteringen: + +Parallel file operations: +Parallelle bestandsbewerkingen: + +How to get best performance? +Hoe kom je aan de beste prestaties? + Local settings: Lokale instellingen: @@ -1147,15 +1168,6 @@ De opdracht wordt geactiveerd als: Directory on server: Map op de server: -Performance improvements: -Prestatieverbeteringen: - -How to get best performance? -Hoe kom je aan de beste prestaties? - -Connections for directory reading: -Verbindingen voor het lezen van mappen: - SFTP channels per connection: SFTP kanalen per verbinding: @@ -1199,7 +1211,7 @@ De opdracht wordt geactiveerd als: Minimaliseren naar systeemvak When finished: -Wanneer je gereed bent: +Indien gereed: Auto-close Automatisch sluiten @@ -1291,8 +1303,17 @@ Dit garandeert een consistente status zelfs in het geval van een ernstige fout. &Default &Standaard -Source code written in C++ using: -Broncode werd in C++ geschreven met: +Feedback and suggestions are welcome +Terugkoppeling en suggesties zijn welkom + +Home page +Startpagina + +FreeFileSync Forum +FreeFileSync Forum + +Email +Email If you like FreeFileSync: Als FreeFileSync je bevalt: @@ -1300,20 +1321,14 @@ Dit garandeert een consistente status zelfs in het geval van een ernstige fout. Support with a donation Steunen met een donatie -Donation details -Donatie details - The auto updater was disabled by the administrator. De automatische updater werd uitgeschakeld door de beheerder. -Feedback and suggestions are welcome -Terugkoppeling en suggesties zijn welkom - -Home page -Startpagina +Donation details +Donatie details -Email -Email +Source code written in C++ using: +Broncode werd in C++ geschreven met: Published under the GNU General Public License Gepubliceerd onder de GNU General Public License @@ -1402,9 +1417,6 @@ Dit garandeert een consistente status zelfs in het geval van een ernstige fout. FreeFileSync %x is available! FreeFileSync %x is beschikbaar! -Installation files are corrupted. Please reinstall FreeFileSync. -Installatiebestanden zijn beschadigd. Installeer FreeFileSync opnieuw. - Local path not available for %x. Lokaal pad niet beschikbaar voor %x. @@ -1627,6 +1639,9 @@ Dit garandeert een consistente status zelfs in het geval van een ernstige fout. Thank you, %x, for your donation and support! Dank u, %x, voor uw donatie en ondersteuning! +Connections +Verbindingen + Recommended range: Aanbevolen bereik: @@ -1798,9 +1813,6 @@ Dit garandeert een consistente status zelfs in het geval van een ernstige fout. Automatic updates: Automatische updates: -Requires FreeFileSync Donation Edition -Vereist FreeFileSync donatie Editie - Check for Program Updates Controleer voor programma-updates @@ -1990,6 +2002,9 @@ Dit garandeert een consistente status zelfs in het geval van een ernstige fout. Edit with FreeFileSync Bewerken met FreeFileSync +Instead of an ad, here's an animal. +In plaats van een advertentie is hier een dier. + The FreeFileSync portable version cannot install into a subfolder of %x. De draagbare versie van FreeFileSync kan niet worden geïnstalleerd in een submap van %x. @@ -1999,3 +2014,6 @@ Dit garandeert een consistente status zelfs in het geval van een ernstige fout. The %x installation option is only available in the FreeFileSync Donation Edition. De %x installatieoptie is alleen beschikbaar in de FreeFileSync Donatie-editie. +Get the Donation Edition with bonus features and help keep FreeFileSync ad-free. +Ontvang de donatie uitgave met bonuseigenschappen en help FreeFileSync advertentie-vrij te houden. + diff --git a/FreeFileSync/Build/Languages/english_uk.lng b/FreeFileSync/Build/Languages/english_uk.lng index 1f3fcaf0..07f178b9 100755 --- a/FreeFileSync/Build/Languages/english_uk.lng +++ b/FreeFileSync/Build/Languages/english_uk.lng @@ -112,6 +112,9 @@ Path to an alternate GlobalSettings.xml file. Path to an alternate GlobalSettings.xml file. +Installation files are corrupted. Please reinstall FreeFileSync. +Installation files are corrupted. Please reinstall FreeFileSync. + Cannot find the following folders: Cannot find the following folders: @@ -333,12 +336,12 @@ Actual: %y bytes Cannot find %x. Cannot find %x. -Cannot open file %x. -Cannot open file %x. - Cannot find device %x. Cannot find device %x. +Cannot open file %x. +Cannot open file %x. + Type of item %x is not supported: Type of item %x is not supported: @@ -465,6 +468,9 @@ Actual: %y bytes Total time: Total time: +Cleaning up old log files... +Cleaning up old log files... + Error parsing file %x, row %y, column %z. Error parsing file %x, row %y, column %z. @@ -653,6 +659,15 @@ The command is triggered if: Multiple... Multiple... +Cannot write file attributes of %x. +Cannot write file attributes of %x. + +%x and %y have different content. +%x and %y have different content. + +Data verification error: +Data verification error: + Moving file %x to %y Moving file %x to %y @@ -677,14 +692,8 @@ The command is triggered if: Updating attributes of %x Updating attributes of %x -Cannot write file attributes of %x. -Cannot write file attributes of %x. - -%x and %y have different content. -%x and %y have different content. - -Data verification error: -Data verification error: +Source item %x not found +Source item %x not found Creating a Volume Shadow Copy for %x... Creating a Volume Shadow Copy for %x... @@ -746,9 +755,6 @@ The command is triggered if: System: Shut down System: Shut down -Cleaning up old log files... -Cleaning up old log files... - Stopped Stopped @@ -881,6 +887,12 @@ The command is triggered if: Please select a folder on a local file system, network or an MTP device. Please select a folder on a local file system, network or an MTP device. +Defined by context of use + + +Requires FreeFileSync Donation Edition +Requires FreeFileSync Donation Edition + &Save &Save @@ -1031,6 +1043,15 @@ The command is triggered if: Handle daylight saving time Handle daylight saving time +Performance improvements: +Performance improvements: + +Parallel file operations: +Parallel file operations: + +How to get best performance? +How to get best performance? + Local settings: Local settings: @@ -1147,15 +1168,6 @@ The command is triggered if: Directory on server: Directory on server: -Performance improvements: -Performance improvements: - -How to get best performance? -How to get best performance? - -Connections for directory reading: -Connections for directory reading: - SFTP channels per connection: SFTP channels per connection: @@ -1291,8 +1303,17 @@ This guarantees a consistent state even in case of a serious error. &Default &Default -Source code written in C++ using: -Source code written in C++ using: +Feedback and suggestions are welcome +Feedback and suggestions are welcome + +Home page +Home page + +FreeFileSync Forum +FreeFileSync Forum + +Email +E-mail If you like FreeFileSync: If you like FreeFileSync: @@ -1300,20 +1321,14 @@ This guarantees a consistent state even in case of a serious error. Support with a donation Support with a donation -Donation details -Donation details - The auto updater was disabled by the administrator. The auto updater was disabled by the administrator. -Feedback and suggestions are welcome -Feedback and suggestions are welcome - -Home page -Home page +Donation details +Donation details -Email -E-mail +Source code written in C++ using: +Source code written in C++ using: Published under the GNU General Public License Published under the GNU General Public Licence @@ -1402,9 +1417,6 @@ This guarantees a consistent state even in case of a serious error. FreeFileSync %x is available! FreeFileSync %x is available! -Installation files are corrupted. Please reinstall FreeFileSync. -Installation files are corrupted. Please reinstall FreeFileSync. - Local path not available for %x. Local path not available for %x. @@ -1627,6 +1639,9 @@ This guarantees a consistent state even in case of a serious error. Thank you, %x, for your donation and support! Thank you, %x, for your donation and support! +Connections +Connections + Recommended range: Recommended range: @@ -1798,9 +1813,6 @@ This guarantees a consistent state even in case of a serious error. Automatic updates: Automatic updates: -Requires FreeFileSync Donation Edition -Requires FreeFileSync Donation Edition - Check for Program Updates Check for Program Updates @@ -1990,6 +2002,9 @@ This guarantees a consistent state even in case of a serious error. Edit with FreeFileSync Edit with FreeFileSync +Instead of an ad, here's an animal. +Instead of an ad, here's an animal. + The FreeFileSync portable version cannot install into a subfolder of %x. The FreeFileSync portable version cannot install into a subfolder of %x. @@ -1999,3 +2014,6 @@ This guarantees a consistent state even in case of a serious error. The %x installation option is only available in the FreeFileSync Donation Edition. The %x installation option is only available in the FreeFileSync Donation Edition. +Get the Donation Edition with bonus features and help keep FreeFileSync ad-free. +Get the Donation Edition with bonus features and help keep FreeFileSync ad-free. + diff --git a/FreeFileSync/Build/Languages/finnish.lng b/FreeFileSync/Build/Languages/finnish.lng deleted file mode 100755 index dfdf1145..00000000 --- a/FreeFileSync/Build/Languages/finnish.lng +++ /dev/null @@ -1,1999 +0,0 @@ -
      - Suomi - Nalle Juslén - fi_FI - flag_finland.png - 2 - n == 1 ? 0 : 1 -
      - -Both sides have changed since last synchronization. -Molemmat puolet muuttuneet edellisestä täsmäytyksestä. - -Cannot determine sync-direction: -Tuntematon suunta täsmäytykselle: - -No change since last synchronization. -Ei muutoksia edellisen täsmäytyksen jälkeen. - -The database entry is not in sync considering current settings. -Tietokanta ei vastaa nykyisiä asetuksia. - -Setting default synchronization directions: Old files will be overwritten with newer files. -Asetetaan oletussuunta täsmäytykselle: vanhat tiedostot korvataan uudemilla tiedostoilla. - -Creating file %x -Luodaan tiedosto %x - -Creating folder %x -Luodaan hakemisto %x - -Creating symbolic link %x -Luodaan pikakuvake %x - -Moving file %x to the recycle bin -Siirretään tiedosto %x Roskakoriin - -Moving folder %x to the recycle bin -Siirretään hakemisto %x Roskakoriin - -Moving symbolic link %x to the recycle bin -Siirretään pikakuvike %x Roskakoriin - -Deleting file %x -Poistetaan tiedosto %x - -Deleting folder %x -Poistetaan hakemisto %x - -Deleting symbolic link %x -Pistetaan pikakuvake %x - -Checking recycle bin availability for folder %x... -Tarkistetaan hakemisto %x:n käyttöoikeus Roskakoriin ... - -The recycle bin is not supported by the following folders. Deleted or overwritten files will not be able to be restored: -Roskakoria ei tueta näissä hakemistoissa. Poistettuja tai muutettuja tiedostoja ei voida palauttaa: - -An exception occurred -Poikkeama havaittu - -A directory path is expected after %x. -%x:n jälkeen kuuluisi olla hakemistopolku. - -Syntax error -Muotovirhe - -A left and a right directory path are expected after %x. - - -Cannot find file %x. -Tiedosto %x ei löydy. - -Error -Virhe - -File %x does not contain a valid configuration. -Tiedosto %x ei sisällä kelvollista kokoonpanoa. - -Unequal number of left and right directories specified. -Oikealla ja vasemmalla eri määrä hakemistoja. - -The config file must not contain settings at directory pair level when directories are set via command line. -Määritystiedosto ei saa sisältää hakemistoparitason asetuksia, jos hakemistot määritellään komentoriviltä. - -Directories cannot be set for more than one configuration file. -Hakemistot voi asettaa vain yhtä määrittelyä varten. - -Command line -Komentokehote - -Syntax: -Syntaksi: - -config files: -määritystiedostot: - -directory -hakemisto - -global config file: -yleinen määritystiedosto: - -Any number of FreeFileSync .ffs_gui and/or .ffs_batch configuration files. -Vapaa määrä FreeFileSync .ffs_gui ja/tai .ffs_batch määrittelytiedostoja. - -Any number of alternative directory pairs for at most one config file. -Vapaa määrä eri hakemistopareja yhdelle tietylle määritystiedostolle. - -Open the selected configuration for editing only without executing it. -Avaa asetus editointia varten, ei suoriteta. - -Path to an alternate GlobalSettings.xml file. -Polku toiseen GlobalSettings.xml tiedostolle. - -Cannot find the following folders: -Seuraavia hakemistoja ei löydy: - -If this error is ignored the folders will be considered empty. Missing folders are created automatically when needed. -Hakemistot luodaan tyhjinä jos virhe ohitetaan. Puuttuvat hakemistot luodaan tarvittaessa. - -Comparison finished: - - - -1 item found -%x items found - - - - -File %x has an invalid date. -Tiedostolla %x on virheellinen päiväys. - -Date: -Pvm: - -Files have the same date but a different size. -Tiedostojen päiväys täsmäämutta koke ei. - -Size: -Koko: - -Content comparison was skipped for excluded files. -Sisällön tarkastus ohitettiin, tiedosto suljettu pois. - -Items differ in attributes only -Kohteet eroavat vain ominaisuuksiltaan - -Resolving symbolic link %x -Tulkitaan pikakuvike %x - -Comparing content of files %x -Verrataan tiedostojen %x sisältöä - -Generating file list... -Luodaan tiedostoluettelo... - -Fail-safe file copy -Varmennettu tiedostokopiointi - -Enabled -Käytössä - -Disabled -Estetty - -Copy locked files -Kopioi lukitut tiedostot - -Copy file access permissions -Kopioi tiedoston käyttöoikeudet - -File time tolerance -Tiedoston toleranssiaika - -Folder access timeout -Hakemiston aikakatkaisu - -Run with background priority -Aja tausta-ajo prioriteetillä - -Lock directories during sync -Lukitse hakemistot täsmäytyksen ajaksi - -Verify copied files -Tarkista monistetut tiedostot - -Using non-default global settings: -Käytä mukautettuja yleisasetuksia: - -A folder input field is empty. -Hakemiston syötekenttä on tyhjä. - -The corresponding folder will be considered as empty. -Vastaava hakemisto tulkitaan tyhjäksi. - -Exclude: -Sulje pois: - -One base folder of a folder pair is contained in the other one. -Hakemiston yksi juurihakemisto sisältyy jo toiseen. - -The folder should be excluded from synchronization via filter. -Sulje hakemisto pois käyttämällä suodinta. - -Calculating sync directions... -Lasketaan täsmäytyksen suuntaa... - -Out of memory. -Muisti loppui. - -Item exists on left side only -Kohde on vain vasemmalla - -Item exists on right side only -Kohde on vain oikealla - -Left side is newer -Uudempi vasemmalla - -Right side is newer -Uudempi oikealla - -Items have different content -Kohteilla on eri sisältö - -Both sides are equal -Puolet ovat identtiset - -Conflict/item cannot be categorized -Ristiriita/kohde ei ole luokiteltavissa - -Copy new item to left -Kopioi uusi kohde vasemmalle - -Copy new item to right -Kopioi uusi kohde oikealle - -Delete left item -Poista vasen kohde - -Delete right item -Poista oikea kohde - -Move file on left -Siirrä vasen tiedosto - -Move file on right -Siirrä oikea tiedosto - -Update left item -Päivitä vasen kohde - -Update right item -Päivitä oikea kohde - -Do nothing -Älä tee mitään - -Update attributes on left -Päivitä oikeudet vasemmalla - -Update attributes on right -Päivitä määritteet oikealla - -Cannot read file %x. -Tiedosto %x on lukukelvoton. - - -Unexpected size of data stream. -Expected: %x bytes -Actual: %y bytes - - -Tietovuon suuruus yllätti. -Oletettu: %x tavua -Todellinen: %y tavua - - -Cannot write permissions of %x. -Ei voi tallentaa %x:n oikeuksia. - -Operation not supported for different base folder types. -Toiminto ei tue erilaisia lähdehakemistoja. - -Cannot write file %x. -Tiedoston %x kirjoittaminen ei onnistu. - -Cannot move file %x to %y. -Tiedostoa %x ei voida siirtää kohtaan %y. - -Cannot copy symbolic link %x to %y. -Pikakuvike %x - %y kopiointi epäonnistui. - -Unable to connect to %x. -Yhteyttä %x:n ei voi luoda. - -Failed to get information about server %x. -Palvelimen %x tietoja ei saatu. - -Cannot open directory %x. -Hakemistoa %x ei voi avata. - -Cannot read directory %x. -Hekemisto %x on lukukelvoton. - -Cannot read file attributes of %x. -Tiedoston %x määritteitä ei voitu lukea. - -Cannot create directory %x. -Hakemistoa %x ei voitu luoda. - -Cannot delete file %x. -Ei voi poistaa tiedostoa %x. - -Cannot delete directory %x. -Hakemistoa %x ei voida poistaa. - -Cannot write modification time of %x. -Tiedoston %x aikaleimaa ei voida kirjoittaa. - -Cannot determine final path for %x. -%x:n lopullista polkua ei voida määrittää. - -Cannot resolve symbolic link %x. -Pikakuvike %x on virheellinen. - -Unable to move %x to the recycle bin. -%x:n siirto Roskakoriin epäonnistui. - -Cannot find %x. -%x ei löydy. - -Cannot open file %x. -Tiedosto %x ei aukea. - -Cannot find device %x. -Laite %x ei löydy. - -Type of item %x is not supported: -Kohteen %x tyyppiä ei tueta: - -Cannot delete symbolic link %x. -Pikakuvike %x:n poisto ei onnistu. - -Cannot determine free disk space for %x. -%x:n vaapaan tilan määrittely ei onnistu. - -Incorrect command line: -Virheellinen komento: - -Error Code %x -Virhe %x. - -The server does not support authentication via %x. -Palvelin ei salli tunnistautumista %x:n kautta. - -Required: -Vaadittu: - -Unable to access %x. -Ei saatu yhteyttä %x:n. - - -Operation timed out after 1 second. -Operation timed out after %x seconds. - - -Toiminto keskeytyy 1 sekunnin kuluttua. -Toiminto keskeytyy %x sekunnin kuluttua. - - - -Cannot wait on more than 1 connection at a time. -Cannot wait on more than %x connections at a time. - - -Odottaminen sallittu vain 1 yhteydelle. -Odottaminen sallittu vain %x yhtäaikaiselle yhteydelle. - - -Active connections: %x -Aktiivinen yhteys: %x - -Failed to open SFTP channel number %x. -SFTP kanava %x ei voitu avata. - - -1 byte -%x bytes - - -1 tavu -%x tavua - - -%x MB -%x Mt - -%x KB -%x kt - -%x GB -%x Gt - -Cannot load file %x. -Tiedostoa %x ei voida ladata. - -Database file %x is incompatible. -Tietokanta %x ei ole yhteensopiva. - -Initial synchronization: -Ensimmäinen täsmäytys: - -Database file %x does not yet exist. -Tietokantaa %x ei vielä ole. - -Database file is corrupted: -Tietokanta on tuhoutunut: - -The database files do not yet contain information about the last synchronization. -Tietokannasa ei vielä ole tietoja viimeisestä täsmäyksestä. - -Loading file %x... -Ladataan tiedostoa %x... - -Saving file %x... -Tallennetaan tiedostoa %x... - -Searching for folder %x... -Etsitään hakemistoa %x... - -Timeout while searching for folder %x. -Aikaviive hakiessa hakemistoa %x. - -Cannot get process information. -Prosessin tietoja ei saada. - -Waiting while directory is locked: -Odotetaan, kunnes hakemiston lukitus aukeaa: - -Lock owner: -Lukitsija: - -Detecting abandoned lock... -Hylätyn lukituksen etsintä... - - -1 sec -%x sec - - -1 s -%x s - - -Items processed: -Osioita käsitelty: - -Items remaining: -Osioita jäljellä: - -Total time: -Kokonaisaika: - -Error parsing file %x, row %y, column %z. -Jäsennysvirhe - tiedosto: %x, rivi: %y, sarake: %z. - -Cannot set directory locks for the following folders: - - - -1 thread -%x threads - - -1 säije -%x säijettä - - -Scanning: -Skannaus: - -/sec -/s - -%x items/sec -%x kohteita/s - -Show in Explorer -Näytä Explorerissa - -Open with default application -Avaa oletussovelluksessa - -Browse directory -Selaa hakemistoa - -Cannot access the Volume Shadow Copy Service. -Aseman tilannevedospalvelu ei vastaa. - -Please run the 64-bit version of FreeFileSync to create shadow copies on this system. -Käytä FreeFileSync:n 64-bittinen versio jos haluat luoda järjestelmästä tilannevedoksia. - -Cannot determine volume name for %x. -Aseman %x määrittäminen ei onnistu. - -Volume name %x is not part of file path %y. -Asema %x ei esiinny tiedostopolussa %y. - -Unable to create time stamp for versioning: -Versionhallinnan aikaleimaa ei voida luoda: - -Drag && drop -Vedä ja pudota - -Cannot find folder %x. -Hakemistoa %x ei löydy. - -Select a folder -Valitse hakemisto - -&New -&Uusi - -&Open... -&Avaa... - -Save &as... -Tallenna n&imellä... - -E&xit -&Lopeta - -&File -&Tiedosto - -&View help -&Näytä ohje - -&About -&Ohje - -&Help -&Ohje - -Usage: -Käyttö: - -1. Select folders to watch. -1. Valitse seurattavat hakemistot. - -2. Enter a command line. -2. Anna komentokehote. - -3. Press 'Start'. -3. Paina 'Käynnistä'. - -To get started just import a .ffs_batch file. -Aloita lataamalla joku .ffs_batch-tiedosto. - -Folders to watch: -Seurattavat hakemistot: - -Add folder -Lisää hakemisto - -Remove folder -Poista hakemisto - -Browse -Selaa - -Idle time (in seconds): -Joutoaika (s): - -Idle time between last detected change and execution of command -Joutoaika edellisen havaitun muutoksen ja käskyn suorittamisen välillä - -Command line: -Komentokehoite: - - -The command is triggered if: -- files or subfolders change -- new folders arrive (e.g. USB stick insert) - - -Käsky suoritetaan jos: -- tiedosto tai alihakemisto muuttuu -- uusi hakemisto ilmestyy (esim. muistitikku liitetään) - - -Start -Aloita - -About -Ohje - -Build: %x -Versio: %x - -All files -Kaikki tiedostot - -Automated Synchronization -Automaattinen täsmäytys - -The %x protocol does not support directory monitoring: -Valittu protokolla %x ei tue seurantaa: - -Directory monitoring active -Hakemistovalvonta päällä - -Waiting until all directories are available... -Odota kunnes kaikki hakemistot ovat saatavilla... - -&Restore -&Palauta - -&Show error -&Näytä virhe - -&Quit -&Lopeta - -&Retry -&Uudestaan - -File time and size -Tiedoston aika ja koko - -File content -Tiedoston sisältö - -File size -Tiedostokoko - -Two way -Kaksisuuntainen - -Mirror -Peilaava - -Update -Päivittävä - -Custom -Oma määritelmä - -Multiple... -Moninkertainen... - -Moving file %x to %y -Siirretään tiedosto %x -> %y - -Moving folder %x to %y -Siirretään hakemisto %x -> %y - -Moving symbolic link %x to %y -Siirrä pikakuvike %x -> %y - -Removing old versions... -Vanhat versiot poistetaan... - -Updating file %x -Tiedostoa %x päivitetään - -Updating symbolic link %x -Pikakuvitetta %x päivitetään - -Verifying file %x -Tarkistetaan tiedostoa %x - -Updating attributes of %x -Päivitetään %x:n ominaisuuksia - -Cannot write file attributes of %x. -Tiedoston %x ominaisuuksia ei voitu tallentaa. - -%x and %y have different content. -Eri sisältö %x:llä ja %y:llä. - -Data verification error: -Tiedon tarkistusvirhe: - -Creating a Volume Shadow Copy for %x... -Luodaan %x:n tilannevedos ... - -Target folder %x already existing. -Kohdehakemisto %x on jo olemassa. - -Target folder input field must not be empty. -Kohde hakemiston kenttä on annettava. - -Source folder %x not found. -Lähdehakemistoa %x ei löydy. - -Please enter a target folder for versioning. -Anna versioinnin kohdehakemisto. - -The following items have unresolved conflicts and will not be synchronized: -Näissä kohteissa on selvittämättömiä ristiriitoja, niitä ei täsmäytetä: - -The following folders are significantly different. Please check that the correct folders are selected for synchronization. -Seuraavat hakemistot eroavat oleellisesti. Varmista valitut täsmäytettävät hakemistot. - -Not enough free disk space available in: -Vapaa levytila ei riitä: - -Available: -Saatavilla: - -Some files will be synchronized as part of multiple base folders. -Jotkut tiedostot täsmäytetään osana useampaa juurihakemistoa. - -To avoid conflicts, set up exclude filters so that each updated file is considered by only one base folder. -Ristiriidan välttämiseksi, aseta poisulkeva suodin jotta tiedosto käsitellään vain yhdessä juurihakemistossa. - -Versioning folder: -Versiointi hakemisto: - -Base folder: -Juurihakemisto: - -The versioning folder is contained in a base folder. -Versionnin hakemisto löytyy peruskansiosta. - -Synchronizing folder pair: -Täsmäytetään hakemistoparia: - -Generating database... -Tietokantaa luodaan... - -Loading... -Lataan... - -job name -työnnimi - -System: Sleep - - -System: Shut down - - -Cleaning up old log files... -Siivotaan vanhat lokitiedostot... - -Stopped -Keskeytys - -Completed with errors - - -Completed with warnings - - -Warning -Varoitus - -Nothing to synchronize -Ei mitään täsmäytettävää - -Completed successfully - - -Executing command %x -Suorita komentua %x - -You can switch to FreeFileSync's main window to resolve this issue. -Voit siirtyä FreeFileSyncin pääikkunaan korjataksesi tämä virhe. - -&Don't show this warning again -&Älä enää näytä tätä varoitusta - -&Ignore -&Sivuta - -&Switch -&Vaihda - -Switching to FreeFileSync's main window -Vaihdetaan FreeFileSyncin pääikkunaan - -Automatic retry - - -Ignore &all -Ohita &kaikki - -Retrying operation... -Yritetään uudestaan... - -Serious Error -Vakava virhe - -Last session -Viime istunto - -Today -Tänään - - -1 day -%x days - - -1 päivä -%x päivää - - -Name -Nimi - -Last sync - - -Folder -Hakemisto - -Symlink -Pikakuvake - -Full path -Koko polku - -Relative path -Suhteellinen polku - -Item name -Kohteen nimi - -Size -Koko - -Date -Päiväys - -Extension -Tunniste - -Category -Luokka - -Action -Toiminto - -Local comparison settings -Vertailun paikalliset asetukset - -Local synchronization settings -Täsmäytyksen paikalliset asetukset - -Local filter -Paikallinen suodin - -Active -Aktiivinen - -None -Ei ole - -Remove local settings -Poista paikalliset asetukset - -Clear local filter -Nollaa paikallinen suodin - -Copy -Monista - -Paste -Liitä - -The selected folder %x cannot be used with FreeFileSync. -Kansio %x ei toimi FreeFileSync:n kanssa. - -Please select a folder on a local file system, network or an MTP device. -Valitse kansio joka on paikallinen, verkossa tai MTP laitteella. - -&Save -&Tallenna - -Save as &batch job... -Tallenna &eräajona... - -Start &comparison -Aloita &vertailu - -C&omparison settings -&Vertailu asetukset - -&Filter settings -&Suodin asetukset - -S&ynchronization settings -&Täsmäytyksen asetukset - -Start &synchronization -Aloita &täsmäytys - -&Actions -&Toinnot - -&Preferences -&Ominaisuudet - -&Language -&Kieli - -&Find... -&Etsi... - -&Export file list... -&Vie tiedostojoukko... - -&Reset layout -&Nollaa asettelu - -&Tools -&Asetukset - -&Check for updates now -&Etsi päivityksiä nyt - -Check &automatically once a week -Tarkista &viikoittain - -Cancel -Lopeta - -Compare -Vertaile - -Synchronize -Täsmäytä - -Add folder pair -Lisää hakemistopari - -Remove folder pair -Poista hakemistopari - -Access online storage -Osoita online taltiota - -Swap sides -Puoltenvaihto - -Close search bar -Sulje hakukenttä - -Find: -Etsi: - -Match case -Täsmäytä kirjainkoko - -New -Uusi - -Open... -Avaa... - -Save -Tallenna - -Save as... -Tallenna nimellä... - -View type: -Näytä tyyppi: - -Select view: -Valitse näkymä: - -Statistics: -Tilastot: - -Number of files and folders that will be deleted -Poistettavien tiedostojen ja hakemistojen määrä - -Number of files that will be updated -Päitettävien tiedostojen määrä - -Number of files and folders that will be created -Luotavien tiedostojen ja hakemistojen määrä - -Total bytes to copy -Kopioitava määrä tavuja - -Arrange folder pair -Järjestä hakemistoparit - -Folder pair: -Hakemistopari: - -Main settings: -Pääasetukset: - -Use local settings: -Käytä paikallisia asetuksia: - -Select a variant: -Valitse vaihtoehto: - -Include &symbolic links: -Sisällytä &pikalinkit: - -&Follow -&Seuraa - -&Direct -S&uoraan - -More information -Lisää tietoa - -&Ignore time shift [hh:mm] -&Jätä aikasiirtymä huomiotta [hh:mm] - -List of file time offsets to ignore -Aikasiirtymät joita ei huomioida - -Example: -Esim.: - -Handle daylight saving time -Käsittele kesäaika - -Local settings: -Paikalliset aseukset: - -Include: -Sisällytä: - -Show examples -Näytä esimerkkejä - -Time span: -Aikajakso: - -File size: -Tiedoston koko: - -Minimum: -Vähintään: - -Maximum: -Enintään: - -Select filter rules to exclude certain files from synchronization. Enter file paths relative to their corresponding folder pair. -Määrittele suodatussäännöt täsmäyksestä pois jätettäville tiedostoille. Anna hakemistopariin nähden suhteellinen osoite. - -C&lear -&Nollaa - -Detect moved files -Tunnista siirretyt tiedostot - - -- Not supported by all file systems -- Requires and creates database files -- Detection not available for first sync - - -- Ei tueta kaikissa tiedostojärjestelmissä -- Vaatii ja luo tarvittavat tietokannat -- Tunnistus alkaa ensimmäisestä täsmäyksestä - - -Delete files: -Poista tiedosto: - -&Recycle bin -&Roskakori - -&Permanent -&Pysyvä - -&Versioning -&Versiointi - -Naming convention: -Nimeämiskäytäntö: - -Ignore errors - - -Retry count: -Uusintayrityksiä: - -Delay (in seconds): -Viive (sekunneissa): - -Run a command after synchronization: -Suorita käsky kun täsmäytys on tehty: - -OK -Kyllä - -Enter your login details: -Anna kirjautumistietosi: - -Connection type: -Yhteystapa: - -Server name or IP address: -Palvelimen nimi tai IP: - -Port: -Portti: - -Encryption: -Salaus: - -&Disabled -&ei käytössä - -&Explicit SSL/TLS -&Valikoiva SSL/TLS - -Authentication: -Todennus: - -&Password -&Salasana - -&Key file -&Avaintiedosto - -&SSH agent -&SSH agentti - -User name: -Käyttäjänimi: - -Private key file: -Avaintiedosto: - -&Show password -&Näytä salasana - -Directory on server: -Hakemisto palvelimella: - -Performance improvements: -Suorituskyvyn tehostus: - -How to get best performance? -Miten saan parhaan suorituskyvyn? - -Connections for directory reading: -Yhteyksiä hakemistojen lukemiseen: - -SFTP channels per connection: -SFTP kanavia per yhteys: - -Detect server limit -Tunnista palvelimen rajat - -Select a directory on the server: -Valitse palvelimelta hakemisto: - -Select Folder -Valitse kansio - -Start synchronization now? -Käynnistetäänkö täsmäytys? - -Variant: -Vaihtoehto: - -&Don't show this dialog again -&Älä näytä tätä uudestaan - -Items found: -Kohteita löytyi: - -Time remaining: -Aikaa jäljellä: - -Time elapsed: -Aikaa kulunut: - -Bytes -Tavua - -Items -Kohteita - -Synchronizing... -Täsmäytetään... - -Minimize to notification area -Pienennä ilmaisinalueelle - -When finished: -Kun valmis: - -Auto-close - - -Close -Sulje - -&Pause -&Keskeytä - -Stop -Lopeta - -Create a batch file for unattended synchronization. To start, double-click this file or schedule in a task planner: %x -Luo eräajotiedosto automaattista täsmäytystä varten. Käynnistä kaksoisnapsauttamalla tai ajasta tehtävä: %x - -Progress dialog: - - -Run minimized -Suorita pienennettynä - -&Show error dialog -&Näytä virheilmoitus - -Show pop-up on errors or warnings -Näytä ponnahdusikkuna virheiden tai varoituksien kohdalla - -&Cancel -&Peruuta - -Stop synchronization at first error -Lopeta täsmäytys ensimmäiseen virheeseen - -Save log: -Tallenna loki: - -Limit: -Raja: - -Limit maximum number of log files -Rajoita lokien maksimimäärä - -How can I schedule a batch job? -Miten ajastan eräajon? - -&Keep relative paths -&Säilytä suhteelliset polut - -&Overwrite existing files -&Korvaa olemassa olevat tiedostot - -The following settings are used for all synchronization jobs. -Näitä asetuksia käytetään kaikkiin täsmäyksiin. - - -Copy to a temporary file (*.ffs_tmp) before overwriting target. -This guarantees a consistent state even in case of a serious error. - - -Kopioi väliaikaistiedostoon (*.ffs_tmp) ennen kohteen korvaamista. -Tällä varmistetaan eheys, vaikka vakava virhe tapahtuisi. - - -recommended -suositus - -Copy shared or locked files using the Volume Shadow Copy Service. -Jaettujen tai lukittujen tiedostojen kopiointi käyttäen aseman tilannevedospalvelua. - -requires administrator rights -edellyttää järjestelmänvalvojan oikeuksia - -Transfer file and folder permissions. -Siirrä tiedostojen ja hakemistojen käyttöoikeudet. - -Show hidden dialogs again -Näytä piiloitetut valintaikkunat uudestaan - -Show all permanently hidden dialogs and warning messages again -Näytä pysyvästi piiloitetut valintaikkunat ja varoitukset uudestaan - -Customize context menu: -Muokkaa pikavalikkoa: - -Description -Kuvaus - -&Default -&Oletus - -Source code written in C++ using: -Koodattu C++-kielellä käyttäen: - -If you like FreeFileSync: -Jos pidät FreeFileSyncistä: - -Support with a donation -Tue meitä lahjoituksella - -Donation details -Lahjoituksen tiedot - -The auto updater was disabled by the administrator. -Järjestelmävalvoka keskeytti päivityksen. - -Feedback and suggestions are welcome -Palaute ja uudet ehdotukset ovat tervetulleita - -Home page -Kotisivu - -Email -S-posti - -Published under the GNU General Public License -Julkaistu noudattaen GNU General Public Licensen ehtoja: - -Many thanks for localization: -Paljon kiitoksia kääntäjille: - -Activate the FreeFileSync Donation Edition by one of the following methods: -Käynnistä FreeFileSyncin Lahjoittajan versiota käyttäen: - -1. Activate via internet now: -1. Internetin kautta: - -Activate online -Aktivoi netissä - -2. Retrieve an offline activation key from the following URL: -2. Hae erillinen aktivointiavain sivulta: - -&Copy to clipboard -&Kopioi leikepöydälle - -Enter activation key: -Syötä aktivointiavain: - -Activate offline -Aktivoi yhteyttä - -Highlight configurations that have not been run for more than the following number of days: - - -Synchronization Settings -Täsmäyksen asetukset - -Access Online Storage - - -Save as a Batch Job -Tallenna eräajona> - -Delete Items -Poista kohteet - -Copy Items - - -Options -Asetukset - -Select Time Span -Valitse aikajakso - -FreeFileSync Donation Edition -FreeFileSync Lahjoittajan versio - -Highlight Configurations - - -&Options -&Asetukset - -Main Bar -Pääpalkki - -Folder Pairs -Hakemistoparit - -Find -Etsi - -View Settings -Näytä asetukset - -Configuration -Asetukset - -Overview -Yleiskatsaus - -Show "%x" -Näytä "%x" - -&Show details -&Näytä tarkenteet - -FreeFileSync %x is available! -FreeFileSync %x on saatavilla! - -Installation files are corrupted. Please reinstall FreeFileSync. -Asennustiedostot ovat viottuneet. Yritä asentaa FreeFileSync uudestaan. - -Local path not available for %x. -Paikallinen polku %x puuttuu. - -Confirm -Vahvista - - -Do you really want to execute the command %y for one item? -Do you really want to execute the command %y for %x items? - - -Haluatko varmasti suorittaa %y tälle kohteelle? -Haluatko varmasti suorittaa %y näille %x kohteelle? - - -&Execute -&Suorita - - -1 directory -%x directories - - -1 hakemisto -%x hakemistoja - - - -1 file -%x files - - -1 tiedosto -%x tiedostoja - - - -Showing %y of 1 row -Showing %y of %x rows - - -Näytetään %y (1 rivi) -Näytetään %y (%x riviä) - - -Set direction: -Aseta suunta: - -multiple selection -monivalinta - -Include via filter: -Sisällytä suotimella: - -Exclude via filter: -Suodata suotimella: - -Include temporarily -Sisällytä, tilapäisesti - -Exclude temporarily -Suodat tilapäisesti - -&Copy to... -&Monista... - -&Delete -&Poista - -Include all -Valitse kaikki - -Exclude all -Suodata kaikki - -Show icons: -Näytä kuvakkeet: - -Small -Pieni - -Medium -Keskikoko - -Large -Iso - -Select time span... -Valitse aikajana... - -Folder Comparison and Synchronization -Hakemistojen vertailu ja täsmäytys - -Configuration saved -Asetukset tallennettu - -FreeFileSync batch -FreeFileSync eräajo - -Do you want to save changes to %x? -Haluatko tallentaa muutokset: %x? - -Never save &changes -Älä tallenna &muutoksia - -Do&n't save -Äl&ä tallenna - -Hide configuration - - -Highlight... - - -Clear filter -Nollaa suodin - -Show files that exist on left side only -Näytä vain vasemmalla esiintyvät tiedostot - -Show files that exist on right side only -Näytä vain oikealla esiintyvät tiedostot - -Show files that are newer on left -Näytä vasemmalla olevat uudemmat tiedostot - -Show files that are newer on right -Näytä oikealla olevat uudemmat tiedostot - -Show files that are equal -Näytä yhteneväiset tiedostot - -Show files that are different -Näytä erilaiset tiedostot - -Show conflicts -Näytä ristiriidat - -Show files that will be created on the left side -Näytä vasemmalle luotavat tiedostot - -Show files that will be created on the right side -Näytä oikealle luotavat tiedostot - -Show files that will be deleted on the left side -Näytä vasemmalta poistettavat tiedostot - -Show files that will be deleted on the right side -Näytä oikealta poistettavat tiedostot - -Show files that will be updated on the left side -Näytä ne tiedostot vasemmalla joita päivitetään - -Show files that will be updated on the right side -Näytä ne tiedostot oikealla joita päivitetään - -Show files that won't be copied -Näytä kopioimatta jäävät tiedostot - -Show filtered or temporarily excluded files -Näytä suodatetut tai tilapäisesti pois suljetut tiedostot - -Save as default -Tallenna oletukseksi - -Filter -Suodin - -All files are in sync -Kaikki tiedostot ovat ajantasalla - -Cannot find %x -En löydä %x - -Move up -Siirrä ylös - -Move down -Siirrä alas - -Comma-separated values -Pilkulla erotetut arvot - -File list exported -Tiedostolista viety - -Searching for program updates... -Ohjelmapäivytystä haetaan... - -Paused -Pysäytetty - -Stop requested... - - -Initializing... -Alustetaan... - -Scanning... -Tiedostoja haetaan... - -Comparing content... -Sisältöä vertaillaan... - -Info -Info - -Select all -Valitse kaikki - -&Continue -&Jatka - -Progress -Etenemä - -Log -Loki - -Thank you, %x, for your donation and support! -Kiitos %x, arvostan tukeasi ja lahjoitustasi! - -Recommended range: -Suositeltu alue: - -Password: -Salasana: - -Key password: -Salasana: - -Please enter a file path. -Anna tiedostopolku. - - -Copy the following item to another folder? -Copy the following %x items to another folder? - - -Monistetaanko kohde toiseen kansioon? -Monista %x kohteet toiseen kansioon? - - -Please enter a target folder. -Kerro kohde kansio. - - -Do you really want to move the following item to the recycle bin? -Do you really want to move the following %x items to the recycle bin? - - -Haluatko varmasti siirtää kohteen Roskakoriin? -Haluatko varmasti siirtää nämä %x kohteet Roskakoriin? - - -Move -Siirrä - - -Do you really want to delete the following item? -Do you really want to delete the following %x items? - - -Haluatko todella poistaa tämän kohteen? -Haluatko todella poistaa nämä %x kohdetta? - - -Copy DACL, SACL, Owner, Group -Monista DACL, SACL, omistaja, ryhmä - -Integrate external applications into context menu. The following macros are available: -Liitä ulkoinen sovellus pikavalikkoon. Seuraavat makrot ovat valittavissa: - -Full file or folder path -Koko tiedosto/hakemisto-polku - -Parent folder path -Ylätason kansio - -Temporary local copy for SFTP and MTP storage -Tilapäinen paikalliskopio SFTP/MTP:lle - -Parameters for opposite side -Toisen puolen parametrit - -Downloading update... -Ladataan päivitystä... - -Identify equal files by comparing modification time and size. -Tunnista identtiset tiedostot aikaleimoja ja kokoa vertailemalla. - -Identify equal files by comparing the file content. -Tunnista identtiset tiedostot sisältöä vertailemalla. - -Identify equal files by comparing their file size. -Löydä vastaavat tiedostot tiedostokoon mukaan. - -Identify and propagate changes on both sides. Deletions, moves and conflicts are detected automatically using a database. -Löydä ja monista muutokset molemmille puolille. Poistot, siirrot ja eroavuudet tunnistetaan tietokannan avulla. - -Create a mirror backup of the left folder by adapting the right folder to match. -Luo peilikuva vasemmasta kansiosta muokkaamalla oikeata kansiota samanlaiseksi. - -Copy new and updated files to the right folder. -Monista uudet ja päivitetyt tiedostot oikealle. - -Configure your own synchronization rules. -Määritä omat täsmäytyssäännöt. - -Comparison -Vertailu - -Synchronization -Täsmäytys - -This week -Tällä viikolla - -This month -Tässä kuussa - -This year -Tänä vuonna - -Last x days -Viimeiset x päivää - -Byte -tavua - -KB -kt - -MB -Mt - -Retain deleted and overwritten files in the recycle bin -Säilytä poistetut ja korjatut tiedostot roskakorissa - -Delete and overwrite files permanently -Poista ja korvaa tiedostot suoraan - -Move files to a user-defined folder -Siirrä tiedostot itse määräämäsi hakemistoon - -Replace -Korvaa - -Move files and replace if existing -Siirrä tiedostot ja korvaa olemassaolevat - -Time stamp -Aikaleima - -Append a time stamp to each file name -Lisää tiedostoihin aikaleimat - -On completion: -Kun valmis: - -On errors: -Kun virhe: - -On success: -Kun onnistui: - -Main config -Pääasetus - -empty -tyhjä - -Leave as unresolved conflict -Jätä ratkaisemattomana ristiriitana - -File -Tiedosto - -YYYY-MM-DD hhmmss -VVVV-KK-PP ttmmss - -Files -Tiedostot - -Percentage -Prosenttia - -Failed to retrieve update information. -Päivytyksen noutaminen epäonnistui. - -Automatic updates: -Automaattinen päivitys: - -Requires FreeFileSync Donation Edition -Edellyttää FreeFileSyncin Lahjoittajan versiota - -Check for Program Updates -Etsi ohjelmaan päivitystä - -Auto-update now or download manually from the FreeFileSync home page? -Päivitä automaattisesti tai lataa päivitys FreeFileSyncin kotisivulta? - -&Auto-update -&Automattisesti - -&Home page -&Kotisivu - -Download now? -Ladataanko nyt? - -&Download -&Lataa - -FreeFileSync is up to date. -FreeFileSync on ajan tasalla. - -Cannot find current FreeFileSync version number online. A newer version is likely available. Check manually now? -Tätä FreeFileSync versiota ei lödy. Uudempi versio luultavasti löytyy, tarkistetaanko manuaalisesti? - -&Check -&Tarkista - -Consistency check failed for %x. -%x:n yhtenäisyystarkistus epäonnistui. - -Installation was registered on a different operating system. -Asennus on rekisteröity toiselle käyttöjärjestelmälle. - -Failed to activate FreeFileSync Donation Edition. -FreeFileSyncin Lahjoittajan versio ei aktivoitunut. - -Incorrect activation key. -Väärä aktivointiavain. - -Unable to register to receive system messages. -Kirjautuminen järjestelmäviestien vastaanottamiseksi ei onnistu. - -Cannot find system function %x. -Järjestelmäfunktiota %x ei löydy. - -Unable to register device notifications for %x. -%x laiteviestien kirjaaminen ei onnistu. - -Cannot monitor directory %x. -Hakemistoa %x ei voida tarkkaila. - -The file is locked by another process: -Tiedosto on toisen prosessin lukitsema: - -Cannot read security context of %x. -Ei voi lukea %x:n suojauskontekstia. - -Cannot write security context of %x. -Ei voi tallentaa %x:n suojauskontekstia. - -Cannot read permissions of %x. -Ei voi lukea %x:n oikeuksia. - -Cannot copy permissions from %x to %y. -Oikeuksien monistus %x:ltä %y:lle ei onnistu. - -%x is not a regular directory name. -%x on epämääräinen hakemisto nimi. - -Cannot copy file %x to %y. -Tiedostoa %x ei voida kopioida kohtaan %y. - -Cannot copy attributes from %x to %y. -Ominaisuuksien monistus %x:ltä %y:lle ei onnistu. - -%x TB -%x Tt - -%x PB -%x Pt - - -1 min -%x min - - -1 min -%x min - - - -1 hour -%x hours - - -1 tunti -%x tuntia - - -Cannot set privilege %x. -Oikeutta %x ei voitu asettaa. - -Unable to suspend system sleep mode. -Lepotilan keskeyttäminen ei onnistu. - -Cannot change process I/O priorities. -Prosessin I/O-prioriteetin muutos ei onnistu. - -Unable to shut down the system. -Järjestelmää ei voi sammuttaa. - -Checking recycle bin failed for folder %x. -Roskakorin tarkistus hakemistolle %x epäonnistui. - -The following XML elements could not be read: -Nämä XML elementit ovat lukukelvottimia: - -Configuration file %x is incomplete. The missing elements will be set to their default values. -Kokoonpanotiedosto %x ladattu vain osittain. Puuttuvat asetetaan oletusarvoihin. - -Prepare installation -Alusta asennus - -Choose which components you want to install. -Valitse asennettavat osiot. - -Select installation type: -Valitse asennustapa: - -Local -Paikallinen - -Portable -Siirrettävä - -Save settings to "%APPDATA%\FreeFileSync" -Tallenna asetukset "%APPDATA%\FreeFileSync" - -Register FreeFileSync file extensions -Rekisteröi FreeFileSync tiedostotunnisteet - -Create Explorer context menu entries -Luo merkinnät Explorer-pikavalikoon - -Save settings in installation directory -Tallenna asetukset asennus hakemistoon - -Do not write to Registry -Älä kirjoita rekisteritietoja - -Just copy the files -Kopio vain tiedostot - -Choose a directory for installation: -Valitse asennushakemisto: - -Create shortcuts: -Luodaan pikavalinnat: - -Desktop -Työpöydälle - -Start Menu - - -Send To - - -Registering FreeFileSync file extensions -Liitään FreeFileSync tiedostotunnisteet - -Unregistering FreeFileSync file extensions -Poistetaan liitos, FreeFileSync tiedostotunnisteet - -FreeFileSync Configuration -FreeFileSync määrittelyt - -FreeFileSync Batch File -FreeFileSync eräajo - -FreeFileSync Synchronization Database -FreeFileSync täsmäytyksen tietokanta - -RealTimeSync Configuration -RealTimeSync määrittelyt - -Edit with FreeFileSync -Muokkaa FreeFileSync:llä - -The FreeFileSync portable version cannot install into a subfolder of %x. -FreeFileSync:n siirrettävä versio ei voi asentaa alikansioon %x. - -Please choose the local installation type or select a different folder for installation. -Valitse paikallinen asennus tai vaihda asennushakemistoa. - -The %x installation option is only available in the FreeFileSync Donation Edition. - - diff --git a/FreeFileSync/Build/Languages/french.lng b/FreeFileSync/Build/Languages/french.lng index 67627ec3..915bbc94 100755 --- a/FreeFileSync/Build/Languages/french.lng +++ b/FreeFileSync/Build/Languages/french.lng @@ -7,6 +7,9 @@ n <= 1 ? 0 : 1 +Defined by context of use + + Both sides have changed since last synchronization. Les deux côtés ont changé depuis la dernière synchronisation. @@ -112,6 +115,9 @@ Path to an alternate GlobalSettings.xml file. Chemin d'un fichier GlobalSettings.xml distinct. +Installation files are corrupted. Please reinstall FreeFileSync. +Un ou plusieurs fichiers installés sont abîmés. Veuillez réinstaller FreeFileSync. + Cannot find the following folders: Impossible de trouver les dossiers suivants : @@ -333,12 +339,12 @@ Trouvé : %y octets Cannot find %x. Impossible de trouver %x. -Cannot open file %x. -Impossible d'ouvrir le fichier %x. - Cannot find device %x. Impossible de trouver le périphérique %x. +Cannot open file %x. +Impossible d'ouvrir le fichier %x. + Type of item %x is not supported: Le type de l'élément %x n'est pas accepté : @@ -465,6 +471,9 @@ Trouvé : %y octets Total time: Durée totale : +Cleaning up old log files... +Nettoyage des anciens fichiers log ... + Error parsing file %x, row %y, column %z. Erreur lors de l'analyse du fichier %x, ligne %y, colonne %z. @@ -653,6 +662,15 @@ La commande est déclenchée si : Multiple... Multiple ... +Cannot write file attributes of %x. +Impossible d'écrire les attributs de fichier de %x. + +%x and %y have different content. +%x et %y ont des contenus différents. + +Data verification error: +Erreur de contrôle des données : + Moving file %x to %y Déplacement du fichier %x vers %y @@ -677,14 +695,8 @@ La commande est déclenchée si : Updating attributes of %x Mise à jour des attributs de %x -Cannot write file attributes of %x. -Impossible d'écrire les attributs de fichier de %x. - -%x and %y have different content. -%x et %y ont des contenus différents. - -Data verification error: -Erreur de contrôle des données : +Source item %x not found +L'élément source %x n'a pas été trouvé Creating a Volume Shadow Copy for %x... Création d'un Volume Shadow Copy pour %x ... @@ -746,9 +758,6 @@ La commande est déclenchée si : System: Shut down Sustème : Arrêt -Cleaning up old log files... -Nettoyage des anciens fichiers log ... - Stopped Arrêté @@ -881,6 +890,9 @@ La commande est déclenchée si : Please select a folder on a local file system, network or an MTP device. Veuillez choisir un dossier local, en réseau ou un périphérique MTP. +Requires FreeFileSync Donation Edition +Nécessite FreeFileSync Donation Edition + &Save &Sauvegarder @@ -1031,6 +1043,15 @@ La commande est déclenchée si : Handle daylight saving time Gérer l'heure d'été +Performance improvements: +Amélioration des performances : + +Parallel file operations: +Opérations sur les fichiers parallèles : + +How to get best performance? +Comment améliorer les performances ? + Local settings: Paramètres locaux : @@ -1147,15 +1168,6 @@ La commande est déclenchée si : Directory on server: Répertoire sur le serveur : -Performance improvements: -Amélioration des performances : - -How to get best performance? -Comment améliorer les performances ? - -Connections for directory reading: -Connexions pour la lecture des répertoires : - SFTP channels per connection: Ports SFTP par connexion : @@ -1291,8 +1303,17 @@ Cela garantit la cohérence du système de fichiers en cas d'erreur grave. &Default &Défaut -Source code written in C++ using: -Code source écrit en C++ utilisant : +Feedback and suggestions are welcome +Vos commentaires et vos suggestions sont les bienvenus + +Home page +Page d'accueil + +FreeFileSync Forum +Forum FreeFileSync + +Email +Email If you like FreeFileSync: Si vous aimez FreeFileSync : @@ -1300,20 +1321,14 @@ Cela garantit la cohérence du système de fichiers en cas d'erreur grave. Support with a donation Soutenir avec un don -Donation details -Détails pour les donations - The auto updater was disabled by the administrator. La mise à jour automatique a été désactivée par l'administrateur. -Feedback and suggestions are welcome -Vos commentaires et vos suggestions sont les bienvenus - -Home page -Page d'accueil +Donation details +Détails pour les donations -Email -Email +Source code written in C++ using: +Code source écrit en C++ utilisant : Published under the GNU General Public License Publié sous licence GNU General Public License @@ -1402,9 +1417,6 @@ Cela garantit la cohérence du système de fichiers en cas d'erreur grave. FreeFileSync %x is available! FreeFileSync %x est disponible ! -Installation files are corrupted. Please reinstall FreeFileSync. -Un ou plusieurs fichiers installés sont abîmés. Veuillez réinstaller FreeFileSync. - Local path not available for %x. Chemin local invalide pour %x. @@ -1627,6 +1639,9 @@ Cela garantit la cohérence du système de fichiers en cas d'erreur grave. Thank you, %x, for your donation and support! Merci %x pour votre don et votre aide ! +Connections +Connexions + Recommended range: Plage recommandée : @@ -1798,9 +1813,6 @@ Cela garantit la cohérence du système de fichiers en cas d'erreur grave. Automatic updates: Mise à jour automatique : -Requires FreeFileSync Donation Edition -Nécessite FreeFileSync Donation Edition - Check for Program Updates Recherche des Mises à Jour @@ -1990,6 +2002,9 @@ Cela garantit la cohérence du système de fichiers en cas d'erreur grave. Edit with FreeFileSync Modification avec FreeFileSync +Instead of an ad, here's an animal. +A la place d'une publicité, voici un animal. + The FreeFileSync portable version cannot install into a subfolder of %x. La version portable de FreeFileSync ne peut pas être intallée dans le sous-dossier de %x. @@ -1999,3 +2014,6 @@ Cela garantit la cohérence du système de fichiers en cas d'erreur grave. The %x installation option is only available in the FreeFileSync Donation Edition. L'option d'installation %x n'est disponible que dans FreeFileSync Donation Edition. +Get the Donation Edition with bonus features and help keep FreeFileSync ad-free. +Obtenez l'Édition Don avec des fonctionnalités bonus et aidez FreeFileSync à rester sans publicité. + diff --git a/FreeFileSync/Build/Languages/german.lng b/FreeFileSync/Build/Languages/german.lng index b993aab4..2039e816 100755 --- a/FreeFileSync/Build/Languages/german.lng +++ b/FreeFileSync/Build/Languages/german.lng @@ -112,6 +112,9 @@ Path to an alternate GlobalSettings.xml file. Pfad zu alternativer GlobalSettings.xml Datei. +Installation files are corrupted. Please reinstall FreeFileSync. +Die Installationsdateien sind beschädigt. Bitte installieren Sie FreeFileSync neu. + Cannot find the following folders: Die folgenden Ordner wurden nicht gefunden: @@ -333,12 +336,12 @@ Tatsächlich: %y bytes Cannot find %x. %x wurde nicht gefunden. -Cannot open file %x. -Die Datei %x kann nicht geöffnet werden. - Cannot find device %x. Das Gerät %x wurde nicht gefunden. +Cannot open file %x. +Die Datei %x kann nicht geöffnet werden. + Type of item %x is not supported: Der Typ des Elements %x wird nicht unterstützt: @@ -465,6 +468,9 @@ Tatsächlich: %y bytes Total time: Gesamtzeit: +Cleaning up old log files... +Bereinige alte Protokolldateien... + Error parsing file %x, row %y, column %z. Fehler beim Auswerten der Datei %x, Zeile %y, Spalte %z. @@ -653,6 +659,15 @@ Die Befehlszeile wird ausgelöst, wenn: Multiple... Verschiedene... +Cannot write file attributes of %x. +Die Dateiattribute von %x können nicht geschrieben werden. + +%x and %y have different content. +%x und %y haben unterschiedlichen Inhalt. + +Data verification error: +Verifizierungsfehler: + Moving file %x to %y Verschiebe Datei %x nach %y @@ -677,14 +692,8 @@ Die Befehlszeile wird ausgelöst, wenn: Updating attributes of %x Aktualisiere Attribute von %x -Cannot write file attributes of %x. -Die Dateiattribute von %x können nicht geschrieben werden. - -%x and %y have different content. -%x und %y haben unterschiedlichen Inhalt. - -Data verification error: -Verifizierungsfehler: +Source item %x not found +Das Quellelement %x wurde nicht gefunden. Creating a Volume Shadow Copy for %x... Erstelle eine Volumenschattenkopie für %x... @@ -746,9 +755,6 @@ Die Befehlszeile wird ausgelöst, wenn: System: Shut down System: Herunterfahren -Cleaning up old log files... -Bereinige alte Protokolldateien... - Stopped Gestoppt @@ -881,6 +887,12 @@ Die Befehlszeile wird ausgelöst, wenn: Please select a folder on a local file system, network or an MTP device. Bitte wählen Sie einen Ordner auf einem lokalen Dateisystem, Netzwerk oder MTP Gerät. +Defined by context of use + + +Requires FreeFileSync Donation Edition +Benötigt FreeFileSync Spendenversion + &Save &Speichern @@ -1031,6 +1043,15 @@ Die Befehlszeile wird ausgelöst, wenn: Handle daylight saving time Sommerzeit berücksichtigen +Performance improvements: +Leistungsverbesserungen: + +Parallel file operations: +Parallele Dateioperationen: + +How to get best performance? +Wie erhält man die beste Leistung? + Local settings: Lokale Einstellungen: @@ -1147,15 +1168,6 @@ Die Befehlszeile wird ausgelöst, wenn: Directory on server: Verzeichnis auf Server: -Performance improvements: -Leistungsverbesserungen: - -How to get best performance? -Wie erhält man die beste Leistung? - -Connections for directory reading: -Verbindungen zum Verzeichnislesen: - SFTP channels per connection: SFTP Kanäle je Verbindung: @@ -1291,8 +1303,17 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. &Default &Standard -Source code written in C++ using: -Der Quellcode wurde in C++ geschrieben mit: +Feedback and suggestions are welcome +Feedback und Vorschläge sind willkommen + +Home page +Homepage + +FreeFileSync Forum +FreeFileSync Forum + +Email +Email If you like FreeFileSync: Wenn Sie FreeFileSync mögen: @@ -1300,20 +1321,14 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. Support with a donation Mit einer Spende unterstützen -Donation details -Spendendetails - The auto updater was disabled by the administrator. Die automatische Aktualisierung wurde vom Administrator deaktiviert. -Feedback and suggestions are welcome -Feedback und Vorschläge sind willkommen - -Home page -Homepage +Donation details +Spendendetails -Email -Email +Source code written in C++ using: +Der Quellcode wurde in C++ geschrieben mit: Published under the GNU General Public License Veröffentlicht unter der Allgemeinen Öffentlichen GNU-Lizenz @@ -1402,9 +1417,6 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. FreeFileSync %x is available! FreeFileSync %x ist verfügbar! -Installation files are corrupted. Please reinstall FreeFileSync. -Die Installationsdateien sind beschädigt. Bitte installieren Sie FreeFileSync neu. - Local path not available for %x. Lokaler Pfad ist nicht verfügbar für %x. @@ -1627,6 +1639,9 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. Thank you, %x, for your donation and support! Danke %x für die Spende und Unterstützung! +Connections +Verbindungen + Recommended range: Empfohlener Bereich: @@ -1798,9 +1813,6 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. Automatic updates: Automatische Aktualisierung: -Requires FreeFileSync Donation Edition -Benötigt FreeFileSync Spendenversion - Check for Program Updates Suche nach neuer Programmversion @@ -1990,6 +2002,9 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. Edit with FreeFileSync Mit FreeFileSync editieren +Instead of an ad, here's an animal. +Hier ein Tierbild statt Werbung. + The FreeFileSync portable version cannot install into a subfolder of %x. Die portable Version von FreeFileSync kann nicht in einen Unterordner von %x installiert werden. @@ -1999,3 +2014,6 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. The %x installation option is only available in the FreeFileSync Donation Edition. Die %x Installationsoption ist nur in der FreeFileSync Spendenversion verfügbar. +Get the Donation Edition with bonus features and help keep FreeFileSync ad-free. +Jetzt die Spendenversion mit Bonusfunktionen holen und mithelfen, FreeFileSync werbefrei zu halten. + diff --git a/FreeFileSync/Build/Languages/greek.lng b/FreeFileSync/Build/Languages/greek.lng index d611c0df..81494217 100755 --- a/FreeFileSync/Build/Languages/greek.lng +++ b/FreeFileSync/Build/Languages/greek.lng @@ -112,6 +112,9 @@ Path to an alternate GlobalSettings.xml file. Διαδρομή σε ένα εναλλακτικό αρχείο GlobalSettings.xml. +Installation files are corrupted. Please reinstall FreeFileSync. +Τα αρχεία εγκατάστασης είναι κατεστραμμένα. Παρακαλούμε επανεγκαταστείστε το FreeFileSync. + Cannot find the following folders: Οι ακόλουθοι φάκελοι δεν ήταν δυνατό να βρεθούν: @@ -333,12 +336,12 @@ Actual: %y bytes Cannot find %x. Το %x δεν μπορεί να βρεθεί. -Cannot open file %x. -Δεν είναι δυνατό το άνοιγμα του αρχείου %x. - Cannot find device %x. Η συσκευή %x δεν μπορεί να βρεθεί. +Cannot open file %x. +Δεν είναι δυνατό το άνοιγμα του αρχείου %x. + Type of item %x is not supported: Ο τύπος του στοιχείου %x δεν υποστηρίζεται: @@ -465,6 +468,9 @@ Actual: %y bytes Total time: Συνολική διάρκεια: +Cleaning up old log files... +Καθαρισμός των παλιών αρχείων καταγραφής... + Error parsing file %x, row %y, column %z. Σφάλμα κατά την ανάλυση του αρχείου %x, γραμμή %y, στήλη %z. @@ -653,6 +659,15 @@ The command is triggered if: Multiple... Πολλαπλές ρυθμίσεις... +Cannot write file attributes of %x. +Δεν μπορεί να γίνει εγγραφή των χαρακτηριστικών αρχείου του %x. + +%x and %y have different content. +Τα %x και %y έχουν διαφορετικό περιεχόμενο. + +Data verification error: +Σφάλμα επαλήθευσης δεδομένων: + Moving file %x to %y Μεταφορά του αρχείου %x στο %y @@ -677,14 +692,8 @@ The command is triggered if: Updating attributes of %x Ενημέρωση των χαρακτηριστικών αρχείου του %x -Cannot write file attributes of %x. -Δεν μπορεί να γίνει εγγραφή των χαρακτηριστικών αρχείου του %x. - -%x and %y have different content. -Τα %x και %y έχουν διαφορετικό περιεχόμενο. - -Data verification error: -Σφάλμα επαλήθευσης δεδομένων: +Source item %x not found +Το στοιχείο προέλευσης %x δεν βρέθηκε Creating a Volume Shadow Copy for %x... Δημιουργία Σκιώδους Αντίγραφου Τόμου για το %x... @@ -746,9 +755,6 @@ The command is triggered if: System: Shut down Σύστημα: Τερματισμός λειτουργίας -Cleaning up old log files... -Καθαρισμός των παλιών αρχείων καταγραφής... - Stopped Διακοπή @@ -881,6 +887,12 @@ The command is triggered if: Please select a folder on a local file system, network or an MTP device. Παρακαλούμε επιλέξτε ένα φάκελο σε ένα τοπικό σύστημα αρχείων, σε ένα δίκτυο ή σε μια συσκευή MTP. +Defined by context of use + + +Requires FreeFileSync Donation Edition +Απαιτεί την Έκδοση Δωρητή του FreeFileSync + &Save &Αποθήκευση @@ -1031,6 +1043,15 @@ The command is triggered if: Handle daylight saving time Διαχείριση θερινής ώρας +Performance improvements: +Βελτιώσεις της απόδοσης: + +Parallel file operations: +Παράλληλες λειτουργίες αρχείων: + +How to get best performance? +Πώς να έχετε την καλύτερη απόδοση; + Local settings: Τοπικές ρυθμίσεις: @@ -1147,15 +1168,6 @@ The command is triggered if: Directory on server: Υποκατάλογος στον διακομιστή: -Performance improvements: -Βελτιώσεις της απόδοσης: - -How to get best performance? -Πώς να έχετε την καλύτερη απόδοση; - -Connections for directory reading: -Συνδέσεις για ανάγνωση καταλόγου: - SFTP channels per connection: Αριθμός καναλιών SFTP ανά σύνδεση: @@ -1291,8 +1303,17 @@ This guarantees a consistent state even in case of a serious error. &Default &Προεπιλογή -Source code written in C++ using: -Ο πηγαίος κώδικας γράφτηκε σε C++ χρησιμοποιώντας τα: +Feedback and suggestions are welcome +Τα σχόλια και οι προτάσεις σας είναι ευπρόσδεκτα + +Home page +Αρχική σελίδα + +FreeFileSync Forum +FreeFileSync Forum + +Email +Email If you like FreeFileSync: Αν σας αρέσει το FreeFileSync: @@ -1300,20 +1321,14 @@ This guarantees a consistent state even in case of a serious error. Support with a donation Υποστηρίξτε με μια δωρεά -Donation details -Λεπτομέρειες δωρεάς - The auto updater was disabled by the administrator. Η αυτόματη ενημέρωση απενεργοποιήθηκε από το διαχειριστή. -Feedback and suggestions are welcome -Τα σχόλια και οι προτάσεις σας είναι ευπρόσδεκτα - -Home page -Αρχική σελίδα +Donation details +Λεπτομέρειες δωρεάς -Email -Email +Source code written in C++ using: +Ο πηγαίος κώδικας γράφτηκε σε C++ χρησιμοποιώντας τα: Published under the GNU General Public License Διανέμεται υπό την Γενική Άδεια Δημόσιας Χρήσης GNU @@ -1402,9 +1417,6 @@ This guarantees a consistent state even in case of a serious error. FreeFileSync %x is available! Το FreeFileSync %x είναι διαθέσιμο! -Installation files are corrupted. Please reinstall FreeFileSync. -Τα αρχεία εγκατάστασης είναι κατεστραμμένα. Παρακαλούμε επανεγκαταστείστε το FreeFileSync. - Local path not available for %x. Η τοπική διαδρομή δεν είναι διαθέσιμη για το %x. @@ -1627,6 +1639,9 @@ This guarantees a consistent state even in case of a serious error. Thank you, %x, for your donation and support! Σας ευχαριστούμε, %x, για τη δωρεά και την υποστήριξή σας! +Connections +Συνδέσεις + Recommended range: Προτεινόμενο εύρος: @@ -1798,9 +1813,6 @@ This guarantees a consistent state even in case of a serious error. Automatic updates: Αυτόματες ενημερώσεις: -Requires FreeFileSync Donation Edition -Απαιτεί την Έκδοση Δωρητή του FreeFileSync - Check for Program Updates Έλεγχος για ενημερώσεις του προγράμματος @@ -1990,6 +2002,9 @@ This guarantees a consistent state even in case of a serious error. Edit with FreeFileSync Επεξεργασία με FreeFileSync +Instead of an ad, here's an animal. +Αντί για διαφήμιση, δείτε ένα ζωάκι. + The FreeFileSync portable version cannot install into a subfolder of %x. Η φορητή έκδοση του FreeFileSync δεν μπορεί να εγκατασταθεί σε υποφάκελο του %x. @@ -1999,3 +2014,6 @@ This guarantees a consistent state even in case of a serious error. The %x installation option is only available in the FreeFileSync Donation Edition. Η επιλογή εγκατάστασης %x είναι διαθέσιμη μόνο στην Έκδοση Δωρητή του FreeFileSync. +Get the Donation Edition with bonus features and help keep FreeFileSync ad-free. +Αγοράστε την Έκδοση Δωρητή με περισσότερες δυνατότητες και βοηθήστε να μείνει το FreeFileSync χωρίς διαφημίσεις. + diff --git a/FreeFileSync/Build/Languages/hebrew.lng b/FreeFileSync/Build/Languages/hebrew.lng index a8157cc9..a3a23712 100755 --- a/FreeFileSync/Build/Languages/hebrew.lng +++ b/FreeFileSync/Build/Languages/hebrew.lng @@ -7,6 +7,9 @@ n == 1 ? 0 : 1 +Defined by context of use + + Both sides have changed since last synchronization. שני הצדדים שונו מאז הסנכרון האחרון. @@ -112,6 +115,9 @@ Path to an alternate GlobalSettings.xml file. נתיב אל קובץ GlobalSettingd.xml אלטרנטיבי. +Installation files are corrupted. Please reinstall FreeFileSync. +קבצי ההתקנה פגומים. בבקשה התקן מחדש את FreeFileSync. + Cannot find the following folders: לא יכול למצוא את התיקיות הבאות: @@ -333,12 +339,12 @@ Actual: %y bytes Cannot find %x. לא מוצא %x. -Cannot open file %x. -לא יכול לפתוח קובץ %x. - Cannot find device %x. לא מוצא התקן %x. +Cannot open file %x. +לא יכול לפתוח קובץ %x. + Type of item %x is not supported: סוג של פריט %x אינו נתמך: @@ -465,6 +471,9 @@ Actual: %y bytes Total time: זמן כולל: +Cleaning up old log files... +מנקה קבצי יומן ישנים... + Error parsing file %x, row %y, column %z. שגיאה בפענוח קובץ %x, שורה %y, טור %z. @@ -653,6 +662,15 @@ The command is triggered if: Multiple... הכפל... +Cannot write file attributes of %x. +לא יכול לכתוב תכונות קובץ של %x. + +%x and %y have different content. +%x ו- %y הם בעלי צוכן שונה. + +Data verification error: +שגיאת בדיקת מידע: + Moving file %x to %y מעביר קובץ %x אל %y @@ -677,14 +695,8 @@ The command is triggered if: Updating attributes of %x מעדכן תכונות של %x -Cannot write file attributes of %x. -לא יכול לכתוב תכונות קובץ של %x. - -%x and %y have different content. -%x ו- %y הם בעלי צוכן שונה. - -Data verification error: -שגיאת בדיקת מידע: +Source item %x not found +פריט מקור %x לא נמצא Creating a Volume Shadow Copy for %x... מייצר Volume Shadow Copy עבור %x... @@ -746,9 +758,6 @@ The command is triggered if: System: Shut down מערכת: כיבוי -Cleaning up old log files... -מנקה קבצי יומן ישנים... - Stopped נעצר @@ -881,6 +890,9 @@ The command is triggered if: Please select a folder on a local file system, network or an MTP device. אנא בחר תיקייה במערכת הקבצים המקומית, ברשת או בהתקן MTP. +Requires FreeFileSync Donation Edition +נדרשת מהדורת התרומות של FreeFileSync + &Save &שמור @@ -1031,6 +1043,15 @@ The command is triggered if: Handle daylight saving time התמודד עם שעון קיץ +Performance improvements: +שיפורים בביצועים: + +Parallel file operations: +פעולות קובץ מקבילות: + +How to get best performance? +כיצד לקבל את הביצועים הטובים ביותר? + Local settings: הגדרות מקומיות: @@ -1147,15 +1168,6 @@ The command is triggered if: Directory on server: מחיצה על שרת: -Performance improvements: -שיפורים בביצועים: - -How to get best performance? -כיצד לקבל את הביצועים הטובים ביותר? - -Connections for directory reading: -חיבורים לקריאת מחיצה: - SFTP channels per connection: ערוצי SFTP לחיבור: @@ -1291,8 +1303,17 @@ This guarantees a consistent state even in case of a serious error. &Default &ברירת מחדל -Source code written in C++ using: -קוד מקור נכתב ב- C++‎ באמצעות: +Feedback and suggestions are welcome +משוב והצעות יתקבלו בברכה + +Home page +דף הבית + +FreeFileSync Forum +פורום FreeFileSync + +Email +דוא"ל If you like FreeFileSync: במידה ו-FreeFileSync מוצאת חן בעינכם: @@ -1300,20 +1321,14 @@ This guarantees a consistent state even in case of a serious error. Support with a donation תמיכה באמצאות תרומה -Donation details -פרטי תרומה: - The auto updater was disabled by the administrator. עידכון אוטומטי הושבת על ידי מנהל מערכת. -Feedback and suggestions are welcome -משוב והצעות יתקבלו בברכה - -Home page -דף הבית +Donation details +פרטי תרומה: -Email -דוא"ל +Source code written in C++ using: +קוד מקור נכתב ב- C++‎ באמצעות: Published under the GNU General Public License מפורסם במסגרת GNU General Public License @@ -1402,9 +1417,6 @@ This guarantees a consistent state even in case of a serious error. FreeFileSync %x is available! FreeFileSync %x זמין ! -Installation files are corrupted. Please reinstall FreeFileSync. -קבצי ההתקנה פגומים. בבקשה התקן מחדש את FreeFileSync. - Local path not available for %x. נתיב מקומי לא זמין עבור %x. @@ -1627,6 +1639,9 @@ This guarantees a consistent state even in case of a serious error. Thank you, %x, for your donation and support! תודה לך %x, עבור תרומתך ותמיכתך! +Connections +קשרים + Recommended range: טווח מומלץ: @@ -1798,9 +1813,6 @@ This guarantees a consistent state even in case of a serious error. Automatic updates: עדכונים אוטומטיים: -Requires FreeFileSync Donation Edition -נדרשת מהדורת התרומות של FreeFileSync - Check for Program Updates בדוק קיום עדכוני תוכנה @@ -1990,6 +2002,9 @@ This guarantees a consistent state even in case of a serious error. Edit with FreeFileSync ערוך עם FreeFileSync +Instead of an ad, here's an animal. +במקום פרסומת, להלן חיה. + The FreeFileSync portable version cannot install into a subfolder of %x. הגירסה הניידת של FreeFileSync אינה ניתנת להתקנה בתת תיקייה של %x. @@ -1999,3 +2014,6 @@ This guarantees a consistent state even in case of a serious error. The %x installation option is only available in the FreeFileSync Donation Edition. אפשרת התקנה %x זמינה רק במהדורת תרומות של FreeFileSync. +Get the Donation Edition with bonus features and help keep FreeFileSync ad-free. +קבל את מהדורת התרומות עם תכונות נוספות ועזור לשמר את FreeFileSync נטול-פרסומות. + diff --git a/FreeFileSync/Build/Languages/hindi.lng b/FreeFileSync/Build/Languages/hindi.lng index be7a4d4a..d575a013 100755 --- a/FreeFileSync/Build/Languages/hindi.lng +++ b/FreeFileSync/Build/Languages/hindi.lng @@ -112,6 +112,9 @@ Path to an alternate GlobalSettings.xml file. वैकल्पिक GlobalSettings.xml फ़ाइल का पथ। +Installation files are corrupted. Please reinstall FreeFileSync. +स्थापना फ़ाइलें दूषित हैं। FreeFileSync पुनर्स्थापित करें। + Cannot find the following folders: निम्न फ़ोलडर्स नहीं मिले: @@ -333,12 +336,12 @@ Actual: %y bytes Cannot find %x. %x नहीं मिला। -Cannot open file %x. -%x फ़ाइल को खोलने में असमर्थ। - Cannot find device %x. डिवाइस %x नहीं मिला। +Cannot open file %x. +%x फ़ाइल को खोलने में असमर्थ। + Type of item %x is not supported: %x प्रकार का आइटम समर्थित नहीं है: @@ -465,6 +468,9 @@ Actual: %y bytes Total time: कुल समय: +Cleaning up old log files... +पुराने लॉग फ़ाइल्स की सफाई हो रही है... + Error parsing file %x, row %y, column %z. फ़ाइल %x, पंक्ति %y, स्तंभ %z पदच्छेदन में त्रुटि। @@ -653,6 +659,15 @@ The command is triggered if: Multiple... एकाधिक... +Cannot write file attributes of %x. +%x के फ़ाइल गुण लिख नहीं सकते। + +%x and %y have different content. +%x और %y की सामग्री भिन्न है। + +Data verification error: +डेटा सत्यापन त्रुटि: + Moving file %x to %y फ़ाइल %x को %y यहाँ ले जाया जा रहा है @@ -677,14 +692,8 @@ The command is triggered if: Updating attributes of %x %x के गुण अद्यतित हो रहे हैं -Cannot write file attributes of %x. -%x के फ़ाइल गुण लिख नहीं सकते। - -%x and %y have different content. -%x और %y की सामग्री भिन्न है। - -Data verification error: -डेटा सत्यापन त्रुटि: +Source item %x not found +स्रोत आइटम %x नहीं मिला Creating a Volume Shadow Copy for %x... %x के लिए वॉल्यूम शॅडो प्रतिलिपि बनाई जा रही है... @@ -746,9 +755,6 @@ The command is triggered if: System: Shut down सिस्टम: बंद करें -Cleaning up old log files... -पुराने लॉग फ़ाइल्स की सफाई हो रही है... - Stopped रुका @@ -881,6 +887,12 @@ The command is triggered if: Please select a folder on a local file system, network or an MTP device. स्थानीय फ़ाइल सिस्टम, नेटवर्क या MTP डिव्हाइस पर कोई निर्देशिका चुनें। +Defined by context of use + + +Requires FreeFileSync Donation Edition +FreeFileSync दान संस्‍करण आवश्यक + &Save सहेजें (&S) @@ -1031,6 +1043,15 @@ The command is triggered if: Handle daylight saving time दिवालोक बचत समय (डेलाईट सेविंग टाइम) प्रहस्तन करें +Performance improvements: +प्रदर्शन सुधार: + +Parallel file operations: +समांतर फ़ाइल कार्रवाई: + +How to get best performance? +सर्वश्रेष्ठ प्रदर्शन कैसे पाएं? + Local settings: स्थानीय सेटिंग्स: @@ -1147,15 +1168,6 @@ The command is triggered if: Directory on server: सर्वर पर निर्देशिका: -Performance improvements: -प्रदर्शन सुधार: - -How to get best performance? -सर्वश्रेष्ठ प्रदर्शन कैसे पाएं? - -Connections for directory reading: -निर्देशिका पठन के लिए कनेक्शन्स: - SFTP channels per connection: SFTP चैनल्स प्रति कनेक्शन: @@ -1291,8 +1303,17 @@ This guarantees a consistent state even in case of a serious error. &Default डिफ़ॉल्ट (&D) -Source code written in C++ using: -स्रोत कोड C++ में लिखा गया इनके प्रयोग से: +Feedback and suggestions are welcome +प्रतिक्रिया और सुझावों का स्वागत है + +Home page +मुख पृष्ठ + +FreeFileSync Forum +FreeFileSync फ़ोरम + +Email +ई-मेल If you like FreeFileSync: यदि आपको FreeFileSync पसंद है: @@ -1300,20 +1321,14 @@ This guarantees a consistent state even in case of a serious error. Support with a donation दान के साथ समर्थन करें -Donation details -दान विवरण - The auto updater was disabled by the administrator. स्वचालित अद्यतन व्यवस्थापक द्वारा अक्षम किया गया है। -Feedback and suggestions are welcome -प्रतिक्रिया और सुझावों का स्वागत है - -Home page -मुख पृष्ठ +Donation details +दान विवरण -Email -ई-मेल +Source code written in C++ using: +स्रोत कोड C++ में लिखा गया इनके प्रयोग से: Published under the GNU General Public License जीएनयू जनरल पब्लिक लाइसेंस (GNU General Public License) के अंतर्गत प्रकाशित @@ -1402,9 +1417,6 @@ This guarantees a consistent state even in case of a serious error. FreeFileSync %x is available! FreeFileSync %x उपलब्ध है! -Installation files are corrupted. Please reinstall FreeFileSync. -स्थापना फ़ाइलें दूषित हैं। FreeFileSync पुनर्स्थापित करें। - Local path not available for %x. %x के लिए स्थानीय पथ उपलब्ध नहीं है। @@ -1627,6 +1639,9 @@ This guarantees a consistent state even in case of a serious error. Thank you, %x, for your donation and support! धन्यवाद, %x, आपके दान और समर्थन के लिए! +Connections +कनेक्शन्स + Recommended range: अनुशंसित श्रेणी: @@ -1798,9 +1813,6 @@ This guarantees a consistent state even in case of a serious error. Automatic updates: स्वचालित अद्यतन: -Requires FreeFileSync Donation Edition -FreeFileSync दान संस्‍करण आवश्यक - Check for Program Updates प्रोग्राम अद्यतनों के लिए जाँच करें @@ -1990,6 +2002,9 @@ This guarantees a consistent state even in case of a serious error. Edit with FreeFileSync FreeFileSync के साथ संपादित करें +Instead of an ad, here's an animal. +कोई विज्ञापन के बजाय, यहाँ एक पशु है। + The FreeFileSync portable version cannot install into a subfolder of %x. FreeFileSync पोर्टेबल संस्करण %x के उप-निर्देशिका में स्थापित नहीं किया जा सकता। @@ -1999,3 +2014,6 @@ This guarantees a consistent state even in case of a serious error. The %x installation option is only available in the FreeFileSync Donation Edition. %x स्थापना विकल्प केवल FreeFileSync दान संस्‍करण में ही उपलब्ध है। +Get the Donation Edition with bonus features and help keep FreeFileSync ad-free. +दान संस्‍करण बोनस सुविधाओं के साथ प्राप्त करें और FreeFileSync विज्ञापन-रहित रखने में मदद करें। + diff --git a/FreeFileSync/Build/Languages/hungarian.lng b/FreeFileSync/Build/Languages/hungarian.lng index 307ff95e..7ba7daa5 100755 --- a/FreeFileSync/Build/Languages/hungarian.lng +++ b/FreeFileSync/Build/Languages/hungarian.lng @@ -112,6 +112,9 @@ Path to an alternate GlobalSettings.xml file. Útvonal egy alternatív GlobalSettings.xml állományhoz. +Installation files are corrupted. Please reinstall FreeFileSync. +A telepítő állományok sérültek. Installálja újra a FreeFileSync-et. + Cannot find the following folders: A következő könyvtárak nem találhatóak: @@ -333,12 +336,12 @@ Tényleges: %y bájt Cannot find %x. %x nem található. -Cannot open file %x. -%x állomány nem nyitható meg. - Cannot find device %x. %x eszköz nem található. +Cannot open file %x. +%x állomány nem nyitható meg. + Type of item %x is not supported: %x elem típusa nem támogatott: @@ -465,6 +468,9 @@ Tényleges: %y bájt Total time: Összes időszükséglet: +Cleaning up old log files... +Régi log állományok törlése... + Error parsing file %x, row %y, column %z. Hiba történt a feldolgozás közben: %x állomány, %y sor, %z oszlop. @@ -653,6 +659,15 @@ A parancs végrehajtódik, ha: Multiple... Sokszoroz... +Cannot write file attributes of %x. +%x állomány attribútumainak írása nem sikerült. + +%x and %y have different content. +%x és %y tartalma különböző. + +Data verification error: +Adat ellenőrzési hiba: + Moving file %x to %y %x állomány mozgatása ide: %y @@ -677,14 +692,8 @@ A parancs végrehajtódik, ha: Updating attributes of %x %x attribútumainak frissítése -Cannot write file attributes of %x. -%x állomány attribútumainak írása nem sikerült. - -%x and %y have different content. -%x és %y tartalma különböző. - -Data verification error: -Adat ellenőrzési hiba: +Source item %x not found +%x forrás állományt nem találom Creating a Volume Shadow Copy for %x... %x számára árnyékmásolat-kötetet készítése... @@ -746,9 +755,6 @@ A parancs végrehajtódik, ha: System: Shut down Rendszer: Leállítva -Cleaning up old log files... -Régi log állományok törlése... - Stopped Leállítva @@ -881,6 +887,12 @@ A parancs végrehajtódik, ha: Please select a folder on a local file system, network or an MTP device. Kérem válasszon egy könyvtárat a helyi fájlrendszerben, a hálózaton vagy egy MTP eszközön. +Defined by context of use + + +Requires FreeFileSync Donation Edition +A FreeFileSync támogatói kiadása szükséges + &Save &Ment @@ -1031,6 +1043,15 @@ A parancs végrehajtódik, ha: Handle daylight saving time Kezelje a nyári időszámítás különbségét +Performance improvements: +Teljesítmény növelése: + +Parallel file operations: +Párhuzamos fájl-műveletek: + +How to get best performance? +Hogyan lehet elérni a legjobb teljesítményt? + Local settings: Helyi beállítások: @@ -1147,15 +1168,6 @@ A parancs végrehajtódik, ha: Directory on server: Könyvtár az alábbi szerveren: -Performance improvements: -Teljesítmény növelése: - -How to get best performance? -Hogyan lehet elérni a legjobb teljesítményt? - -Connections for directory reading: -Kapcsolatok a könyvtár olvasásához: - SFTP channels per connection: SFTP csatornák száma kapcsolatonként: @@ -1291,8 +1303,17 @@ Ez garantálja a konzisztens állapotot egy komoly hiba esetén is. &Default &Alapértelmezett -Source code written in C++ using: -A programot C++-ban fejlesztették a következők felhasználásával: +Feedback and suggestions are welcome +Várjuk a visszajelzéseket és az ötleteket + +Home page +Honlap + +FreeFileSync Forum +FreeFileSync Fórum + +Email +E-mail If you like FreeFileSync: Ha szereted a FreeFileSync-et: @@ -1300,20 +1321,14 @@ Ez garantálja a konzisztens állapotot egy komoly hiba esetén is. Support with a donation Támogassa adománnyal -Donation details -Támogatás részletei - The auto updater was disabled by the administrator. Az automatikus frissítést a rendszergazda kapcsolta ki. -Feedback and suggestions are welcome -Várjuk a visszajelzéseket és az ötleteket - -Home page -Honlap +Donation details +Támogatás részletei -Email -E-mail +Source code written in C++ using: +A programot C++-ban fejlesztették a következők felhasználásával: Published under the GNU General Public License Közzétéve a GNU General Public License alatt @@ -1402,9 +1417,6 @@ Ez garantálja a konzisztens állapotot egy komoly hiba esetén is. FreeFileSync %x is available! FreeFileSync %x elérhető! -Installation files are corrupted. Please reinstall FreeFileSync. -A telepítő állományok sérültek. Installálja újra a FreeFileSync-et. - Local path not available for %x. %x-hez a helyi útvonal nem érhető el. @@ -1627,6 +1639,9 @@ Ez garantálja a konzisztens állapotot egy komoly hiba esetén is. Thank you, %x, for your donation and support! %x, köszönöm Önnek adományát és támogatását! +Connections +Kapcsolatok + Recommended range: A javasolt tartomány: @@ -1798,9 +1813,6 @@ Ez garantálja a konzisztens állapotot egy komoly hiba esetén is. Automatic updates: Automatikus frissítések: -Requires FreeFileSync Donation Edition -A FreeFileSync támogatói kiadása szükséges - Check for Program Updates Program-frissítések ellenőrzése @@ -1990,6 +2002,9 @@ Ez garantálja a konzisztens állapotot egy komoly hiba esetén is. Edit with FreeFileSync Szerkesztés FreeFileSync-kel +Instead of an ad, here's an animal. +A reklám helyett itt egy állat. + The FreeFileSync portable version cannot install into a subfolder of %x. A FreeFileSync hordozható változatát nem lehet telepíteni %x egyik alkönyvtárába. @@ -1997,5 +2012,8 @@ Ez garantálja a konzisztens állapotot egy komoly hiba esetén is. Válassza a helyi telepítési módot vagy válasszon másik könytárat a telepítéshez. The %x installation option is only available in the FreeFileSync Donation Edition. -%x telepítési opció csak a FreeFileSync adományozói kiadásában érhető el. +%x telepítési opció csak a FreeFileSync Támogatói Kiadásában érhető el. + +Get the Donation Edition with bonus features and help keep FreeFileSync ad-free. +Használd a Támogatói Kiadást bónusz szolgáltatásokkal és segits reklám-mentesen tartani a FreeFileSync-et. diff --git a/FreeFileSync/Build/Languages/italian.lng b/FreeFileSync/Build/Languages/italian.lng index 707c244f..0a50b5e6 100755 --- a/FreeFileSync/Build/Languages/italian.lng +++ b/FreeFileSync/Build/Languages/italian.lng @@ -112,6 +112,9 @@ Path to an alternate GlobalSettings.xml file. Percorso di un file GlobalSettings.xml alternativo. +Installation files are corrupted. Please reinstall FreeFileSync. +I file di installazione sono danneggiati. Si prega di reinstallare FreeFileSync. + Cannot find the following folders: Impossibile trovare le seguenti cartelle: @@ -333,12 +336,12 @@ Attuale: %y byte Cannot find %x. Impossibile trovare %x. -Cannot open file %x. -Impossibile aprire il file %x. - Cannot find device %x. Impossibile trovare dispositivo %x. +Cannot open file %x. +Impossibile aprire il file %x. + Type of item %x is not supported: Il tipo di oggetto %x non è supportato: @@ -465,6 +468,9 @@ Attuale: %y byte Total time: Tempo totale: +Cleaning up old log files... +Pulizia vecchi file di log ... + Error parsing file %x, row %y, column %z. Errore nel parsing del file %x, riga %y, colonna %z. @@ -653,6 +659,15 @@ Il comando è attivato se: Multiple... Multiplo... +Cannot write file attributes of %x. +Impossibile scrivere gli attributi del file %x. + +%x and %y have different content. +%x e %y hanno un contenuto diverso. + +Data verification error: +Errore di verifica dei dati: + Moving file %x to %y Spostamento file %x in %y @@ -677,14 +692,8 @@ Il comando è attivato se: Updating attributes of %x Aggiornamento attributi di %x -Cannot write file attributes of %x. -Impossibile scrivere gli attributi del file %x. - -%x and %y have different content. -%x e %y hanno un contenuto diverso. - -Data verification error: -Errore di verifica dei dati: +Source item %x not found +Elemento di origine %x non trovato Creating a Volume Shadow Copy for %x... Creazione di un Volume Shadow Copy per %x... @@ -746,9 +755,6 @@ Il comando è attivato se: System: Shut down Sistema: Spento -Cleaning up old log files... -Pulizia vecchi file di log ... - Stopped Arrestato @@ -881,6 +887,12 @@ Il comando è attivato se: Please select a folder on a local file system, network or an MTP device. Si prega di selezionare una cartella su un file system locale, di rete o un dispositivo MTP. +Defined by context of use + + +Requires FreeFileSync Donation Edition +Richiede FreeFileSync Donation Edition + &Save &Salva @@ -1031,6 +1043,15 @@ Il comando è attivato se: Handle daylight saving time Maneggiare l'ora legale +Performance improvements: +Miglioramenti delle prestazioni: + +Parallel file operations: +Operazioni parallele sul file: + +How to get best performance? +Come ottenere le migliori prestazioni? + Local settings: Impostazioni Locali: @@ -1147,15 +1168,6 @@ Il comando è attivato se: Directory on server: Directory su server: -Performance improvements: -Miglioramenti delle prestazioni: - -How to get best performance? -Come ottenere le migliori prestazioni? - -Connections for directory reading: -Collegamenti per la lettura delle directory: - SFTP channels per connection: Canali SFTP per il collegamento: @@ -1260,7 +1272,7 @@ Copy to a temporary file (*.ffs_tmp) before overwriting target. This guarantees a consistent state even in case of a serious error. -Copiare in un file temporaneo ( *.ffs_tmp ) prima di sovrascrivere il bersaglio. +Copiare in un file temporaneo (*.ffs_tmp) prima di sovrascrivere il bersaglio. Questo garantisce uno stato consistente anche in caso di errore grave. @@ -1291,8 +1303,17 @@ Questo garantisce uno stato consistente anche in caso di errore grave. &Default &Predefinito -Source code written in C++ using: -Codice sorgente scritto in C++ utilizzando: +Feedback and suggestions are welcome +Ogni commento o suggerimento è ben accetto + +Home page +Pagina Iniziale + +FreeFileSync Forum +Forum di FreeFileSync + +Email +Email If you like FreeFileSync: Se ti piace FreeFileSync: @@ -1300,20 +1321,14 @@ Questo garantisce uno stato consistente anche in caso di errore grave. Support with a donation Supporto con una donazione -Donation details -dettagli Donazione - The auto updater was disabled by the administrator. L'aggiornamento automatico è stato disabilitato dall'amministratore. -Feedback and suggestions are welcome -Ogni commento o suggerimento è ben accetto - -Home page -Pagina Iniziale +Donation details +dettagli Donazione -Email -Email +Source code written in C++ using: +Codice sorgente scritto in C++ utilizzando: Published under the GNU General Public License Pubblicato con licenza GNU General Public @@ -1402,9 +1417,6 @@ Questo garantisce uno stato consistente anche in caso di errore grave. FreeFileSync %x is available! FreeFileSync %x è disponibile! -Installation files are corrupted. Please reinstall FreeFileSync. -I file di installazione sono danneggiati. Si prega di reinstallare FreeFileSync. - Local path not available for %x. Percorso locale non disponibile per %x. @@ -1627,6 +1639,9 @@ Questo garantisce uno stato consistente anche in caso di errore grave. Thank you, %x, for your donation and support! Grazie, %x, per la vostra donazione e il supporto! +Connections +Connessioni + Recommended range: Intervallo consigliato: @@ -1798,9 +1813,6 @@ Questo garantisce uno stato consistente anche in caso di errore grave. Automatic updates: Aggiornamenti automatici: -Requires FreeFileSync Donation Edition -Richiede FreeFileSync Donation Edition - Check for Program Updates Controlla Aggiornamenti del Programma @@ -1990,6 +2002,9 @@ Questo garantisce uno stato consistente anche in caso di errore grave. Edit with FreeFileSync Modifica con FreeFileSync +Instead of an ad, here's an animal. +Invece di un annuncio, ecco un animale. + The FreeFileSync portable version cannot install into a subfolder of %x. La versione portatile FreeFileSync non può essere installata in una sottocartella di %x. @@ -1999,3 +2014,6 @@ Questo garantisce uno stato consistente anche in caso di errore grave. The %x installation option is only available in the FreeFileSync Donation Edition. L'opzione di installazione %x è disponibile solo nell'edizione di donazione FreeFileSync. +Get the Donation Edition with bonus features and help keep FreeFileSync ad-free. +Ottieni l'Edizione Donazione con funzioni bonus e aiuto per mantenere FreeFileSync senza pubblicità. + diff --git a/FreeFileSync/Build/Languages/japanese.lng b/FreeFileSync/Build/Languages/japanese.lng index cbbc32ac..0776af38 100755 --- a/FreeFileSync/Build/Languages/japanese.lng +++ b/FreeFileSync/Build/Languages/japanese.lng @@ -112,6 +112,9 @@ Path to an alternate GlobalSettings.xml file. 代替グローバル設定.xml ファイルのパス. +Installation files are corrupted. Please reinstall FreeFileSync. +インストール ファイルが破損しています、FreeFileSync を再インストールしてください. + Cannot find the following folders: 以下のフォルダがみつかりません: @@ -332,12 +335,12 @@ Actual: %y bytes Cannot find %x. %x がみつかりません. -Cannot open file %x. -ファイル %x を開けません. - Cannot find device %x. デバイス %x がみつかりません. +Cannot open file %x. +ファイル %x を開けません. + Type of item %x is not supported: 項目 %x には対応していません: @@ -460,6 +463,9 @@ Actual: %y bytes Total time: 合計時間: +Cleaning up old log files... +古いログファイルをクリーン... + Error parsing file %x, row %y, column %z. ファイル %x の構文解析エラー, 行 %y, 列 %z. @@ -647,6 +653,15 @@ The command is triggered if: Multiple... 複数処理... +Cannot write file attributes of %x. +%x のファイル属性を書き込めません. + +%x and %y have different content. +%x と %y の内容は異なります. + +Data verification error: +データ検証エラー: + Moving file %x to %y ファイル %x を %y に移動中 @@ -671,14 +686,8 @@ The command is triggered if: Updating attributes of %x %x の属性を更新 -Cannot write file attributes of %x. -%x のファイル属性を書き込めません. - -%x and %y have different content. -%x と %y の内容は異なります. - -Data verification error: -データ検証エラー: +Source item %x not found +ソース項目 %x が見つかりません Creating a Volume Shadow Copy for %x... ボリュームシャドウコピーを作成中 %x... @@ -740,9 +749,6 @@ The command is triggered if: System: Shut down システム: シャットダウン -Cleaning up old log files... -古いログファイルをクリーン... - Stopped 停止 @@ -874,6 +880,12 @@ The command is triggered if: Please select a folder on a local file system, network or an MTP device. ローカルファイルシステム、ネットワークまたは MTP デバイス上のフォルダを選択. +Defined by context of use + + +Requires FreeFileSync Donation Edition +FreeFileSync 寄付版が必要です + &Save 保存(&S) @@ -1024,6 +1036,15 @@ The command is triggered if: Handle daylight saving time 夏時間の取り扱い +Performance improvements: +パフォーマンス向上: + +Parallel file operations: +並列ファイル操作: + +How to get best performance? +最適なパフォーマンスとは? + Local settings: ローカル設定: @@ -1140,15 +1161,6 @@ The command is triggered if: Directory on server: サーバ上のディレクトリ: -Performance improvements: -パフォーマンス向上: - -How to get best performance? -最適なパフォーマンスとは? - -Connections for directory reading: -ディレクトリの読み取り接続数: - SFTP channels per connection: 接続当たりの SFTP チャンネル数: @@ -1284,8 +1296,17 @@ This guarantees a consistent state even in case of a serious error. &Default デフォルト(&D) -Source code written in C++ using: -ソースコードは C++ で書かれています: +Feedback and suggestions are welcome +フィードバック、提案などはこちらから + +Home page +ホーム ページ + +FreeFileSync Forum +FreeFileSync フォーラム + +Email +E-メール If you like FreeFileSync: FreeFileSync を気に入ってくれた方へ: @@ -1293,20 +1314,14 @@ This guarantees a consistent state even in case of a serious error. Support with a donation 寄付によるサポート -Donation details -寄付の詳細 - The auto updater was disabled by the administrator. 自動アップデートは管理者によって無効にされています. -Feedback and suggestions are welcome -フィードバック、提案などはこちらから - -Home page -ホーム ページ +Donation details +寄付の詳細 -Email -E-メール +Source code written in C++ using: +ソースコードは C++ で書かれています: Published under the GNU General Public License GNU 一般共有使用許諾に基づき公開 @@ -1395,9 +1410,6 @@ This guarantees a consistent state even in case of a serious error. FreeFileSync %x is available! FreeFileSync %x が利用できます! -Installation files are corrupted. Please reinstall FreeFileSync. -インストール ファイルが破損しています、FreeFileSync を再インストールしてください. - Local path not available for %x. 利用できないローカスパス %x. @@ -1616,6 +1628,9 @@ This guarantees a consistent state even in case of a serious error. Thank you, %x, for your donation and support! ありがとう %x, あなたの寄付と支援に感謝します!! +Connections +接続 + Recommended range: 推奨される範囲: @@ -1784,9 +1799,6 @@ This guarantees a consistent state even in case of a serious error. Automatic updates: 自動アップデート: -Requires FreeFileSync Donation Edition -FreeFileSync 寄付版が必要です - Check for Program Updates プログラムのアップデートを確認 @@ -1974,6 +1986,9 @@ This guarantees a consistent state even in case of a serious error. Edit with FreeFileSync FreeFileSync で編集 +Instead of an ad, here's an animal. +これは広告の代わりです. + The FreeFileSync portable version cannot install into a subfolder of %x. FreeFileSync ポータブル版は %x のサブフォルダにはインストールできません. @@ -1983,3 +1998,6 @@ This guarantees a consistent state even in case of a serious error. The %x installation option is only available in the FreeFileSync Donation Edition. インストール オプション %x は、FreeFileSync 寄付版でのみ利用可能です. +Get the Donation Edition with bonus features and help keep FreeFileSync ad-free. +寄付をすることで広告が一切無く、ボーナス機能が付いた FreeFileSync を使用できます. + diff --git a/FreeFileSync/Build/Languages/korean.lng b/FreeFileSync/Build/Languages/korean.lng index b12f0763..1225d556 100755 --- a/FreeFileSync/Build/Languages/korean.lng +++ b/FreeFileSync/Build/Languages/korean.lng @@ -7,6 +7,9 @@ 0 +Defined by context of use + + Both sides have changed since last synchronization. 마지막 동기화 작업 이후, 양측 모두 변경 되었습니다. @@ -112,6 +115,9 @@ Path to an alternate GlobalSettings.xml file. GlobalSettings.xml 대체 파일에 대한 경로. +Installation files are corrupted. Please reinstall FreeFileSync. +설치 파일이 손상되었습니다. FreeFileSync를 다시 설치하십시오. + Cannot find the following folders: 다음 폴더를 찾을 수 없습니다: @@ -332,12 +338,12 @@ Actual: %y bytes Cannot find %x. %x을(를) 찾을 수 없습니다. -Cannot open file %x. -파일 %x을(를) 열 수 없습니다. - Cannot find device %x. 장치 %x을(를) 찾을 수 없습니다. +Cannot open file %x. +파일 %x을(를) 열 수 없습니다. + Type of item %x is not supported: 항목 %x의 형식은 지원되지 않습니다: @@ -460,6 +466,9 @@ Actual: %y bytes Total time: 전체 시간: +Cleaning up old log files... +이전 로그 파일 정리 중... + Error parsing file %x, row %y, column %z. 분석 오류 - 파일: %x; 행: %y; 열: %z. @@ -647,6 +656,15 @@ The command is triggered if: Multiple... 다중처리 (멀티플) 작업... +Cannot write file attributes of %x. +%x의 파일 속성을 쓸 수 없습니다. + +%x and %y have different content. +%x와(과) %y의 콘텐츠가 다릅니다. + +Data verification error: +데이터 확인 오류: + Moving file %x to %y 파일 %x을(를) %y(으)로 이동 중 @@ -671,14 +689,8 @@ The command is triggered if: Updating attributes of %x %x 속성 업데이트 중 -Cannot write file attributes of %x. -%x의 파일 속성을 쓸 수 없습니다. - -%x and %y have different content. -%x와(과) %y의 콘텐츠가 다릅니다. - -Data verification error: -데이터 확인 오류: +Source item %x not found +원본 항목 %x을(를) 찾을 수 없음 Creating a Volume Shadow Copy for %x... %x을(를) 위한 Volume Shadow Copy 생성 중... @@ -740,9 +752,6 @@ The command is triggered if: System: Shut down 시스템: 종료 -Cleaning up old log files... -이전 로그 파일 정리 중... - Stopped 중단 @@ -874,6 +883,9 @@ The command is triggered if: Please select a folder on a local file system, network or an MTP device. 로컬 파일 시스템, 네트워크 또는 MTP 장치에서의 폴더 하나를 선택하십시오. +Requires FreeFileSync Donation Edition +FreeFileSync 기부자 에디션이 필요합니다. + &Save 저장(&S) @@ -1024,6 +1036,15 @@ The command is triggered if: Handle daylight saving time 서머타임 설정 +Performance improvements: +성능 향상: + +Parallel file operations: +병렬 파일 작업: + +How to get best performance? +최상의 성능을 얻는 방법은? + Local settings: 로컬 설정: @@ -1140,15 +1161,6 @@ The command is triggered if: Directory on server: 서버 디렉터리: -Performance improvements: -성능 향상: - -How to get best performance? -최상의 성능을 얻는 방법은? - -Connections for directory reading: -디렉토리 읽기를 위한 연결: - SFTP channels per connection: 연결 당 SFTP 채널 수: @@ -1284,8 +1296,17 @@ This guarantees a consistent state even in case of a serious error. &Default 기본 설정/값(&D) -Source code written in C++ using: -소스코드는 C++ 언어로 아래 툴을 사용하여 작성되었습니다: +Feedback and suggestions are welcome +모든 의견 및 건의/제안을 환영합니다 + +Home page +홈페이지 + +FreeFileSync Forum +FreeFileSync 포럼 + +Email +이메일 If you like FreeFileSync: FreeFileSync를 위한 기부: @@ -1293,20 +1314,14 @@ This guarantees a consistent state even in case of a serious error. Support with a donation 기부금 지원 -Donation details -기부 관련 세부정보 - The auto updater was disabled by the administrator. 관리자가 자동 업데이터를 비활성화했습니다. -Feedback and suggestions are welcome -모든 의견 및 건의/제안을 환영합니다 - -Home page -홈페이지 +Donation details +기부 관련 세부정보 -Email -이메일 +Source code written in C++ using: +소스코드는 C++ 언어로 아래 툴을 사용하여 작성되었습니다: Published under the GNU General Public License GNU 일반 공용 라이센스에 의한 출시 @@ -1315,7 +1330,7 @@ This guarantees a consistent state even in case of a serious error. 현지화 작업에 깊은 감사 드립니다: Activate the FreeFileSync Donation Edition by one of the following methods: -다음 중 한 방법을 사용하여 FreeFileSync 유료 버전을 활성화하세요: +다음 중 한 방법을 사용하여 FreeFileSync 기부자 에디션을 활성화하세요: 1. Activate via internet now: 1. 인터넷을 통해 지금 바로 활성화: @@ -1360,7 +1375,7 @@ This guarantees a consistent state even in case of a serious error. 시간간격(타임스팬) 선택 FreeFileSync Donation Edition -FreeFileSync 유료 버전 +FreeFileSync 기부자 에디션 Highlight Configurations 강조 표시 구성 @@ -1395,9 +1410,6 @@ This guarantees a consistent state even in case of a serious error. FreeFileSync %x is available! FreeFileSync %x 버전 출시! -Installation files are corrupted. Please reinstall FreeFileSync. -설치 파일이 손상되었습니다. FreeFileSync를 다시 설치하십시오. - Local path not available for %x. %x에서 로컬 경로를 사용할 수 없습니다. @@ -1616,6 +1628,9 @@ This guarantees a consistent state even in case of a serious error. Thank you, %x, for your donation and support! %x님의 기부와 지원에 감사드립니다! +Connections +연결 + Recommended range: 권장 범위: @@ -1784,9 +1799,6 @@ This guarantees a consistent state even in case of a serious error. Automatic updates: 자동 업데이트: -Requires FreeFileSync Donation Edition -FreeFileSync 유료 버전이 필요합니다. - Check for Program Updates 프로그램 업데이트 확인 @@ -1806,7 +1818,7 @@ This guarantees a consistent state even in case of a serious error. 다운로드(&D) FreeFileSync is up to date. -FreeFileSync는 현재 최신버전 상태입니다. +현재 FreeFileSync는 최신 버전입니다. Cannot find current FreeFileSync version number online. A newer version is likely available. Check manually now? 현재 사용 중인 FreeFileSync 버전 번호를 온라인에서 찾을 수 없습니다. 새로운 버전이 있을 수 있으니 수동으로 확인해 보시겠습니까? @@ -1821,7 +1833,7 @@ This guarantees a consistent state even in case of a serious error. 설치가 다른 운영체제에 등록되었습니다. Failed to activate FreeFileSync Donation Edition. -FreeFileSync 유료 버전 활성화에 실패했습니다. +FreeFileSync 기부자 에디션 활성화에 실패했습니다. Incorrect activation key. 잘못된 활성화 키. @@ -1974,6 +1986,9 @@ This guarantees a consistent state even in case of a serious error. Edit with FreeFileSync FreeFileSync로 편집 +Instead of an ad, here's an animal. +광고 대신에 여기 동물 한마리가 있어요. + The FreeFileSync portable version cannot install into a subfolder of %x. FreeFileSync 휴대용 버전은 %x의 하위 폴더에 설치할 수 없습니다. @@ -1981,5 +1996,8 @@ This guarantees a consistent state even in case of a serious error. 로컬 설치 유형을 선택하거나 설치할 다른 폴더를 선택하십시오. The %x installation option is only available in the FreeFileSync Donation Edition. -%x 설치 옵션은 FreeFileSync 유료 버전에서만 사용 가능합니다. +%x 설치 옵션은 FreeFileSync 기부자 에디션에서만 사용 가능합니다. + +Get the Donation Edition with bonus features and help keep FreeFileSync ad-free. +보너스 기능이 있는 기부자 에디션을 사용하시어 광고없는 FreeFileSync가 유지되도록 도와주세요. diff --git a/FreeFileSync/Build/Languages/lithuanian.lng b/FreeFileSync/Build/Languages/lithuanian.lng index 97c435fe..beceddd9 100755 --- a/FreeFileSync/Build/Languages/lithuanian.lng +++ b/FreeFileSync/Build/Languages/lithuanian.lng @@ -7,6 +7,9 @@ n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2 +Defined by context of use + + Both sides have changed since last synchronization. Abi pusės buvo pakeistos nuo paskutinio suvienodinimo. @@ -112,6 +115,9 @@ Path to an alternate GlobalSettings.xml file. Kelias iki alternatyvaus GlobalSettings.xml failo. +Installation files are corrupted. Please reinstall FreeFileSync. +Įdiegimo failai yra pažeisti. Prašome įdiegti iš naujo FreeFileSync. + Cannot find the following folders: Nepavyksta rasti šių aplankų: @@ -334,12 +340,12 @@ Esamas: %y baitai Cannot find %x. Negalima surasti %x. -Cannot open file %x. -%x failo nepavyko atidaryti. - Cannot find device %x. Negalima surasti įrenginio %x. +Cannot open file %x. +%x failo nepavyko atidaryti. + Type of item %x is not supported: Elemento tipas %x nepalaikomas: @@ -470,6 +476,9 @@ Esamas: %y baitai Total time: Visas laikas: +Cleaning up old log files... +Išvalomi seni žurnalo įrašai... + Error parsing file %x, row %y, column %z. Klaida trinant failą %x, eilė %y, stulpelis %z. @@ -659,6 +668,15 @@ Komanda inicijuojama jei: Multiple... Keletas... +Cannot write file attributes of %x. +Nepavyksta įrašyti atributų failui %x. + +%x and %y have different content. +%x ir %y turi skirtingą turinį. + +Data verification error: +Duomenų tikrinimo klaida: + Moving file %x to %y Perkeliamas failas %x į %y @@ -683,14 +701,8 @@ Komanda inicijuojama jei: Updating attributes of %x Atnaujinami atributai %x -Cannot write file attributes of %x. -Nepavyksta įrašyti atributų failui %x. - -%x and %y have different content. -%x ir %y turi skirtingą turinį. - -Data verification error: -Duomenų tikrinimo klaida: +Source item %x not found +Šaltinio elementas %x nerastas: Creating a Volume Shadow Copy for %x... %x kuriamas Duomenų Šešėlinė Kopija... @@ -752,9 +764,6 @@ Komanda inicijuojama jei: System: Shut down Sistema: Išjungti -Cleaning up old log files... -Išvalomi seni žurnalo įrašai... - Stopped Sustabdyta @@ -888,6 +897,9 @@ Komanda inicijuojama jei: Please select a folder on a local file system, network or an MTP device. Prašome pasirinkti aplanką vietinėje failų sistemoje, tinkle arba MTP įrenginyje. +Requires FreeFileSync Donation Edition +Reikalinga FreeFileSync Donoro Versija + &Save &Išsaugoti @@ -1038,6 +1050,15 @@ Komanda inicijuojama jei: Handle daylight saving time Naudoti vasaros laiką +Performance improvements: +Veiklos gerinimai: + +Parallel file operations: +Lygiagrečios failų operacijos: + +How to get best performance? +Kaip gauti geriausius rezultatus? + Local settings: Vietiniai nustatymai: @@ -1154,15 +1175,6 @@ Komanda inicijuojama jei: Directory on server: Katalogas serveryje: -Performance improvements: -Veiklos gerinimai: - -How to get best performance? -Kaip gauti geriausius rezultatus? - -Connections for directory reading: -Prisijungimai katalogų nuskaitymui: - SFTP channels per connection: SFTP kanalai per jungtį: @@ -1298,8 +1310,17 @@ Tai garantuos pastovią buseną, netgi įvykus rimtai klaidai. &Default &Numatyta -Source code written in C++ using: -Šaltinio kodas parašytas su C++ naudojant: +Feedback and suggestions are welcome +Nuomonė ir patarimai laukiami + +Home page +Pagrindinis puslapis + +FreeFileSync Forum +FreeFileSync Diskusijos + +Email +El. paštas If you like FreeFileSync: Jei Jums patinka FreeFileSync: @@ -1307,20 +1328,14 @@ Tai garantuos pastovią buseną, netgi įvykus rimtai klaidai. Support with a donation Pailaikymas su paaukojimu -Donation details -Išsamiau apie aukojimą - The auto updater was disabled by the administrator. Automatinis atnaujinimas yra išjungtas administratoriaus. -Feedback and suggestions are welcome -Nuomonė ir patarimai laukiami - -Home page -Pagrindinis puslapis +Donation details +Išsamiau apie aukojimą -Email -El. paštas +Source code written in C++ using: +Šaltinio kodas parašytas su C++ naudojant: Published under the GNU General Public License Platinama su GNU General Public licenzija @@ -1409,9 +1424,6 @@ Tai garantuos pastovią buseną, netgi įvykus rimtai klaidai. FreeFileSync %x is available! FreeFileSync %x yra pasiekiamas! -Installation files are corrupted. Please reinstall FreeFileSync. -Įdiegimo failai yra pažeisti. Prašome įdiegti iš naujo FreeFileSync. - Local path not available for %x. Nerastas %x vietinis kelias. @@ -1638,6 +1650,9 @@ Tai garantuos pastovią buseną, netgi įvykus rimtai klaidai. Thank you, %x, for your donation and support! Ačiū, %x, už jūsų auką, bei parėmimą! +Connections +Prisijungimai + Recommended range: Rekomenduojamas intervalas: @@ -1812,9 +1827,6 @@ Tai garantuos pastovią buseną, netgi įvykus rimtai klaidai. Automatic updates: Automatiniai atnaujinimai: -Requires FreeFileSync Donation Edition -Reikalinga FreeFileSync Donoro Versija - Check for Program Updates Tikrinti programos atnaujinimus @@ -2006,6 +2018,9 @@ Tai garantuos pastovią buseną, netgi įvykus rimtai klaidai. Edit with FreeFileSync Koreguoti su FreeFileSync +Instead of an ad, here's an animal. +Vietoje reklamos, štai čia yra gyvūnas. + The FreeFileSync portable version cannot install into a subfolder of %x. FreeFileSync kilnojama versija negali būti įdiegta į poaplankį %x. @@ -2015,3 +2030,6 @@ Tai garantuos pastovią buseną, netgi įvykus rimtai klaidai. The %x installation option is only available in the FreeFileSync Donation Edition. %x įdiegiamoji parinktis yra tiktai FreeFileSync Donoro Versijoje. +Get the Donation Edition with bonus features and help keep FreeFileSync ad-free. +Pasirinkti Parėmimo Versiją su papildomomis funkcijomis ir padėkite išlaikyti FreeFileSync be reklamų. + diff --git a/FreeFileSync/Build/Languages/norwegian.lng b/FreeFileSync/Build/Languages/norwegian.lng index f7829a75..5f806a08 100755 --- a/FreeFileSync/Build/Languages/norwegian.lng +++ b/FreeFileSync/Build/Languages/norwegian.lng @@ -7,6 +7,9 @@ n == 1 ? 0 : 1 +Defined by context of use + + Both sides have changed since last synchronization. Begge sider er endret siden siste synkronisering. @@ -112,6 +115,9 @@ Path to an alternate GlobalSettings.xml file. Sti til en alternativ GlobalSettings.xml fil. +Installation files are corrupted. Please reinstall FreeFileSync. +Installasjonsfilene er ødelagte. Installer FreeFileSync på nytt. + Cannot find the following folders: Kan ikke finne følgende mapper: @@ -333,12 +339,12 @@ Faktisk: %y bytes Cannot find %x. Kan ikke finne %x. -Cannot open file %x. -Kan ikke åpne filen %x. - Cannot find device %x. Kan ikke finne enhet %x. +Cannot open file %x. +Kan ikke åpne filen %x. + Type of item %x is not supported: Elementtypen %x støttes ikke: @@ -465,6 +471,9 @@ Faktisk: %y bytes Total time: Samlet tid: +Cleaning up old log files... +Fjerner gamle loggfiler... + Error parsing file %x, row %y, column %z. Behandlingsfeil i filen %x, rad %y, kolonne %z. @@ -653,6 +662,15 @@ Kommandoen utføres hvis: Multiple... Flere... +Cannot write file attributes of %x. +Kan ikke skrive filattributter til %x. + +%x and %y have different content. +%x og %y har forskjellig innhold. + +Data verification error: +Data verifikasjons-feil: + Moving file %x to %y Flytter filen %x til %y @@ -677,14 +695,8 @@ Kommandoen utføres hvis: Updating attributes of %x Oppdaterer attributter for %x -Cannot write file attributes of %x. -Kan ikke skrive filattributter til %x. - -%x and %y have different content. -%x og %y har forskjellig innhold. - -Data verification error: -Data verifikasjons-feil: +Source item %x not found +Kildeelementet %x ikke funnet Creating a Volume Shadow Copy for %x... Oppretter en Volume Shadow-kopi for %x... @@ -746,9 +758,6 @@ Kommandoen utføres hvis: System: Shut down System: Avslutt -Cleaning up old log files... -Fjerner gamle loggfiler... - Stopped Stoppet @@ -881,6 +890,9 @@ Kommandoen utføres hvis: Please select a folder on a local file system, network or an MTP device. Velg en mappe på lokalt filsystem, nettverk eller på en MTP-enhet. +Requires FreeFileSync Donation Edition +Behøver FreeFileSync Donation Edition + &Save &Lagre @@ -1031,6 +1043,15 @@ Kommandoen utføres hvis: Handle daylight saving time Behandle sommertid +Performance improvements: +Ytelsesforbedringer: + +Parallel file operations: +Parallelle fil-operasjoner: + +How to get best performance? +Hvordan få best mulig ytelse? + Local settings: Lokale innstillinger: @@ -1147,15 +1168,6 @@ Kommandoen utføres hvis: Directory on server: Katalog på server: -Performance improvements: -Ytelsesforbedringer: - -How to get best performance? -Hvordan få best mulig ytelse? - -Connections for directory reading: -Tilkoblinger for kataloglesing: - SFTP channels per connection: SFTP kanaler per tilkobling: @@ -1291,8 +1303,17 @@ Sikrer prosessen ved alvorlige feil. &Default &Standard -Source code written in C++ using: -Kildekoden er skrevet i C++ med hjelp fra: +Feedback and suggestions are welcome +Tilbakemeldinger og forslag er ønsket + +Home page +Hjemmeside + +FreeFileSync Forum +FreeFileSync sitt forum + +Email +Epost If you like FreeFileSync: Hvis du liker FreeFileSync: @@ -1300,20 +1321,14 @@ Sikrer prosessen ved alvorlige feil. Support with a donation Støtte med en donasjon -Donation details -Donasjon detaljer - The auto updater was disabled by the administrator. Den automatiske oppdateringen ble deaktivert av administratoren. -Feedback and suggestions are welcome -Tilbakemeldinger og forslag er ønsket - -Home page -Hjemmeside +Donation details +Donasjon detaljer -Email -Epost +Source code written in C++ using: +Kildekoden er skrevet i C++ med hjelp fra: Published under the GNU General Public License Utgitt under GNU General Public Licence @@ -1402,9 +1417,6 @@ Sikrer prosessen ved alvorlige feil. FreeFileSync %x is available! FreeFileSync %x er tilgjengelig! -Installation files are corrupted. Please reinstall FreeFileSync. -Installasjonsfilene er ødelagte. Installer FreeFileSync på nytt. - Local path not available for %x. Lokal sti er ikke tilgjengelig for %x. @@ -1627,6 +1639,9 @@ Sikrer prosessen ved alvorlige feil. Thank you, %x, for your donation and support! Takk, %x, for ditt bidrag og støtte! +Connections +Tilkoblinger + Recommended range: Anbefalt område: @@ -1798,9 +1813,6 @@ Sikrer prosessen ved alvorlige feil. Automatic updates: Automatiske oppdateringer: -Requires FreeFileSync Donation Edition -Behøver FreeFileSync Donation Edition - Check for Program Updates Søk etter program-oppdateringer @@ -1990,6 +2002,9 @@ Sikrer prosessen ved alvorlige feil. Edit with FreeFileSync Rediger med FreeFileSync +Instead of an ad, here's an animal. +I stedet for en annonse er her et dyr. + The FreeFileSync portable version cannot install into a subfolder of %x. Den bærbare versjonen av FreeFileSync kan ikke installeres i en undermappe av %x. @@ -1999,3 +2014,6 @@ Sikrer prosessen ved alvorlige feil. The %x installation option is only available in the FreeFileSync Donation Edition. Installasjonsalternativet %x er bare tilgjengelig i FreeFileSync Donation Edition. +Get the Donation Edition with bonus features and help keep FreeFileSync ad-free. +Få Donation Edition med bonus-tillegg og hjelp til med å holde FreeFileSync annonsefri. + diff --git a/FreeFileSync/Build/Languages/polish.lng b/FreeFileSync/Build/Languages/polish.lng index 077c5115..e31c349d 100755 --- a/FreeFileSync/Build/Languages/polish.lng +++ b/FreeFileSync/Build/Languages/polish.lng @@ -1,6 +1,6 @@
      Polski - Wojciech Pietruszewski + Józef Łuszczek pl_PL flag_poland.png 3 @@ -26,7 +26,7 @@ Tworzenie pliku %x Creating folder %x -Tworzenie folderu %x +Tworzenie katalogu %x Creating symbolic link %x Tworzenie dowiązania symbolicznego %x @@ -44,7 +44,7 @@ Usuwanie pliku %x Deleting folder %x -Usuwanie folderu %x +Usuwanie katalogu %x Deleting symbolic link %x Usuwanie dowiązania symbolicznego %x @@ -65,7 +65,7 @@ Błąd składni A left and a right directory path are expected after %x. - +Po %x należy określić lewy i prawy katalog. Cannot find file %x. Nie można odnaleźć pliku %x. @@ -112,6 +112,9 @@ Path to an alternate GlobalSettings.xml file. Alternatywna ścieżka do pliku GlobalSettings.xml. +Installation files are corrupted. Please reinstall FreeFileSync. +Pliki instalacyjne są uszkodzone. Przeinstaluj program FreeFileSync. + Cannot find the following folders: Nie można znaleźć następujących katalogów: @@ -119,13 +122,16 @@ Jeżeli ten błąd zostanie zignorowany, katalogi zostaną uznane za puste. Brakujące katalogi będą w razie potrzeby utworzone automatycznie. Comparison finished: - +Porównywanie zakończone: 1 item found %x items found +Znaleziono 1 element +Znaleziono %x elementy +Znaleziono %x elementów File %x has an invalid date. @@ -331,12 +337,12 @@ Przesłany: %y bajtów Cannot find %x. Nie można odnaleźć %x. -Cannot open file %x. -Nie można otworzyć pliku %x. - Cannot find device %x. Nie można odnaleźć urządzenia %x. +Cannot open file %x. +Nie można otworzyć pliku %x. + Type of item %x is not supported: Element typu %x nie jest wspierany: @@ -422,7 +428,7 @@ Przesłany: %y bajtów Plik bazy danych jest uszkodzony: The database files do not yet contain information about the last synchronization. -Pliki bazy danych nie posiadają żadnych informacji o poprzednich synchronizacjach. +Pliki bazy danych nie posiadają żadnych informacji o ostatniej synchronizacji. Loading file %x... Wczytywanie pliku %x... @@ -467,11 +473,14 @@ Przesłany: %y bajtów Total time: Całkowity czas: +Cleaning up old log files... +Usuwanie starych plików logów... + Error parsing file %x, row %y, column %z. Błąd podczas parsowania pliku %x, rząd %y, kolumna %z. Cannot set directory locks for the following folders: - +Nie można zablokować katalogów dla poniższych folderów: 1 thread @@ -505,16 +514,16 @@ Przesłany: %y bajtów Nie można uzyskać dostępu do usługi Volume Shadow Copy. Please run the 64-bit version of FreeFileSync to create shadow copies on this system. -Uruchom 64-bitową wersję FreeFileSync aby utworzyć Shadow Copies na tym systemie. +Uruchom 64-bitową wersję FreeFileSync aby utworzyć Shadow Copies w tym systemie. Cannot determine volume name for %x. Nie można określić nazwy dysku dla %x. Volume name %x is not part of file path %y. -Nazwa zasobu %x ni jest częścią ścieżki %y. +Nazwa wolumenu %x nie jest częścią ścieżki %y. Unable to create time stamp for versioning: -Nie można utworzyć znacznika czasu: +Nie można utworzyć znacznika czasu dla wersjonowania: Drag && drop Drag && Drop @@ -593,7 +602,7 @@ The command is triggered if: Komenda jest wykonywana gdy: - pliki lub podkatalogi ulegną zmianie -- zostaną utworzony nowe katalogi (n.p włożenie pamięci USB) +- zostaną utworzone nowe katalogi (np. włożenie pamięci USB) Start @@ -612,7 +621,7 @@ Komenda jest wykonywana gdy: Automatyczna synchronizacja The %x protocol does not support directory monitoring: -Protokuł %x nie wspiera monitorowania katalogów: +Protokół %x nie wspiera monitorowania katalogów: Directory monitoring active Monitorowanie katalogów aktywne @@ -656,6 +665,15 @@ Komenda jest wykonywana gdy: Multiple... Wiele... +Cannot write file attributes of %x. +Nie można zapisać atrybutów %x. + +%x and %y have different content. +%x i %y mają różną zawartość. + +Data verification error: +Błąd weryfikacji danych: + Moving file %x to %y Przenoszenie pliku %x do %y @@ -680,14 +698,8 @@ Komenda jest wykonywana gdy: Updating attributes of %x Aktualizowanie atrybutów %x -Cannot write file attributes of %x. -Nie można zapisać atrybutów %x. - -%x and %y have different content. -%x i %y mają różną zawartość. - -Data verification error: -Błąd weryfikacji danych: +Source item %x not found +Nie znaleziono elementu źródłowego %x Creating a Volume Shadow Copy for %x... Tworzenie Volume Shadow Copy dla %x... @@ -696,13 +708,13 @@ Komenda jest wykonywana gdy: Katalog docelowy %x już istnieje. Target folder input field must not be empty. -Pole katalog docelowy nie może być puste. +Pole katalogu docelowego nie może być puste. Source folder %x not found. -Nie znaleziono katalogu docelowego %x. +Nie znaleziono katalogu źródłowego %x. Please enter a target folder for versioning. -Określ katalog do wersjonowania. +Określ katalog docelowy do wersjonowania. The following items have unresolved conflicts and will not be synchronized: Te elementy znajdują się w konflikcie, którego nie można rozwiązać. Pliki nie zostaną zsynchronizowane: @@ -720,7 +732,7 @@ Komenda jest wykonywana gdy: Niektóre pliki będą zsynchronizowane jako część kilku katalogów źródłowych. To avoid conflicts, set up exclude filters so that each updated file is considered by only one base folder. -Aby uniknąc konfliktów ustaw filtry tak aby wykluczyć przynależność pliku do wielu katalogów źródłowych. +Aby uniknąć konfliktów ustaw filtry tak aby wykluczyć przynależność pliku do wielu katalogów źródłowych. Versioning folder: Wersjonowanie katalogów: @@ -732,7 +744,7 @@ Komenda jest wykonywana gdy: Katalog wersjonowania znajduje się w katalogu bazowym. Synchronizing folder pair: -Synchronizacja katalgów: +Synchronizacja katalogów parami: Generating database... Generowanie bazy danych... @@ -744,22 +756,19 @@ Komenda jest wykonywana gdy: nazwa zadania System: Sleep - +System: Uśpienie System: Shut down - - -Cleaning up old log files... -Usuwanie starych plików logów... +System: Wyłączenie Stopped Zatrzymana Completed with errors - +Zakończono z błędami Completed with warnings - +Zakończono z ostrzeżeniami Warning Ostrzeżenie @@ -768,7 +777,7 @@ Komenda jest wykonywana gdy: Brak plików do synchronizacji Completed successfully - +Zakończono bez błędów Executing command %x Wykonywanie polecenia %x @@ -789,7 +798,7 @@ Komenda jest wykonywana gdy: Przejdź do głównego okna FreeFileSync. Automatic retry - +Automatyczne ponowienie Ignore &all Ignoruj &wszystkie @@ -820,7 +829,7 @@ Komenda jest wykonywana gdy: Nazwa Last sync - +Ostatnia synchronizacja Folder Katalog @@ -885,6 +894,12 @@ Komenda jest wykonywana gdy: Please select a folder on a local file system, network or an MTP device. Okreś katalog lokalny, sieciowy bądź urządzenie MTP. +Defined by context of use + + +Requires FreeFileSync Donation Edition +Wymaga FreeFileSync Donation Edition + &Save &Zapisz @@ -1035,6 +1050,15 @@ Komenda jest wykonywana gdy: Handle daylight saving time Uwzględniaj przesunięcie czasu +Performance improvements: +Polepszenie wydajności: + +Parallel file operations: +Równoległe operacje plikowe: + +How to get best performance? +Jak uzyskać lepszą wydajność? + Local settings: Ustawienia lokalne: @@ -1042,7 +1066,7 @@ Komenda jest wykonywana gdy: Dołącz: Show examples -Przykład +Pokaż przykłady Time span: Przedział czasu: @@ -1092,7 +1116,7 @@ Komenda jest wykonywana gdy: Konwencja nazewnictwa: Ignore errors - +Ignoruj błędy Retry count: Liczba prób: @@ -1113,7 +1137,7 @@ Komenda jest wykonywana gdy: Typ połączenia: Server name or IP address: -Nazwa serwera, lub adres IP: +Nazwa serwera lub adres IP: Port: Port: @@ -1151,15 +1175,6 @@ Komenda jest wykonywana gdy: Directory on server: Katalog na serwerze: -Performance improvements: -Polepszenie wydajności: - -How to get best performance? -Jak uzyskać lepszą wydajność? - -Connections for directory reading: -Połączenie do odczytu katalogów: - SFTP channels per connection: Liczba kanałów dla połączenia SFTP: @@ -1206,7 +1221,7 @@ Komenda jest wykonywana gdy: Gdy zakończono: Auto-close - +Auto-zamykanie Close Zamknij @@ -1218,10 +1233,10 @@ Komenda jest wykonywana gdy: Przerwij Create a batch file for unattended synchronization. To start, double-click this file or schedule in a task planner: %x -Utwórz plik wsadowy do zautomatyzowania procesu synchronizacji. Aby rozpocząć, klknij dwa razy plik lub zaplanuj zadanie w harmonogramie zadań: %x +Utwórz plik wsadowy do zautomatyzowania procesu synchronizacji. Aby rozpocząć, kliknij dwa razy plik lub zaplanuj zadanie w harmonogramie zadań: %x Progress dialog: - +Okno postępu: Run minimized Uruchom zminimalizowane @@ -1295,8 +1310,17 @@ program kopiuje zawartość do pliku tymczasowego (*.ffs_tmp), a następnie nadp &Default &Domyślne -Source code written in C++ using: -Kod stworzony w C++ z wykorzystaniem: +Feedback and suggestions are welcome +Wszelkie opinie i sugestie mile widziane + +Home page +Strona domowa: + +FreeFileSync Forum +Forum FreeFileSync + +Email +Poczta If you like FreeFileSync: Jeżeli lubisz FreeFileSync: @@ -1304,20 +1328,14 @@ program kopiuje zawartość do pliku tymczasowego (*.ffs_tmp), a następnie nadp Support with a donation Wesprzyj dotacją -Donation details -Szczegóły dotacji - The auto updater was disabled by the administrator. Auto aktualizacja została wyłączona przez administratora. -Feedback and suggestions are welcome -Wszelkie opinie i sugestie mile widziane - -Home page -Strona domowa: +Donation details +Szczegóły dotacji -Email -Poczta +Source code written in C++ using: +Kod stworzony w C++ z wykorzystaniem: Published under the GNU General Public License Udostępnione na zasadach licencji GNU General Public License @@ -1338,7 +1356,7 @@ program kopiuje zawartość do pliku tymczasowego (*.ffs_tmp), a następnie nadp 2. Pobierz offline'owy klucz aktywacyjny z poniższego adresu: &Copy to clipboard -&Kopuj do schowka +&Kopiuj do schowka Enter activation key: Wprowadź klucz aktywacyjny: @@ -1347,13 +1365,13 @@ program kopiuje zawartość do pliku tymczasowego (*.ffs_tmp), a następnie nadp Aktywuj offline Highlight configurations that have not been run for more than the following number of days: - +Pokaż konfiguracje, które nie były uruchamiane więcej niż następująca liczba dni: Synchronization Settings Ustawienia synchronizacji Access Online Storage - +Dostęp do Online Storage Save as a Batch Job Zapisz jako plik wsadowy. @@ -1362,19 +1380,19 @@ program kopiuje zawartość do pliku tymczasowego (*.ffs_tmp), a następnie nadp Usuń elementy Copy Items - +Kopiowanie elementów Options Opcje Select Time Span -Określ +Określ przedział czasu FreeFileSync Donation Edition FreeFileSync Donation Edition Highlight Configurations - +Pokaż konfiguracje &Options &Opcje @@ -1406,9 +1424,6 @@ program kopiuje zawartość do pliku tymczasowego (*.ffs_tmp), a następnie nadp FreeFileSync %x is available! FreeFileSync %x jest już dostępny! -Installation files are corrupted. Please reinstall FreeFileSync. -Pliki instalacyjne są uszkodzone. Przeinstaluj program FreeFileSync. - Local path not available for %x. Lokalna ścieżka nie jest dostępna dla %x. @@ -1522,10 +1537,10 @@ program kopiuje zawartość do pliku tymczasowego (*.ffs_tmp), a następnie nadp &Nie zapisuj Hide configuration - +Ukryj konfiguracje Highlight... - +Pokaż... Clear filter Wyczyść filtr @@ -1573,7 +1588,7 @@ program kopiuje zawartość do pliku tymczasowego (*.ffs_tmp), a następnie nadp Pokaż pliki, które nie będą kopiowane Show filtered or temporarily excluded files -Pokaż pliki wykluczone tymczasowo, lub pliki wykluczone tymczasowo +Pokaż pliki wyfiltrowane lub wykluczone tymczasowo Save as default Zapisz jako domyślne @@ -1606,7 +1621,7 @@ program kopiuje zawartość do pliku tymczasowego (*.ffs_tmp), a następnie nadp Pauza Stop requested... - +Zatrzymaj... Initializing... Inicjalizacja... @@ -1635,6 +1650,9 @@ program kopiuje zawartość do pliku tymczasowego (*.ffs_tmp), a następnie nadp Thank you, %x, for your donation and support! Dziękujemy %x za Twoje wsparcie! +Connections +Połączenia + Recommended range: Rekomendowany zakres: @@ -1753,7 +1771,7 @@ program kopiuje zawartość do pliku tymczasowego (*.ffs_tmp), a następnie nadp MB Retain deleted and overwritten files in the recycle bin -Zachowaj pliku usunięte i nadpisane w koszy systemowym +Zachowaj pliku usunięte i nadpisane w koszu systemowym Delete and overwrite files permanently Usuwaj i nadpisuj pliki permanentnie @@ -1809,9 +1827,6 @@ program kopiuje zawartość do pliku tymczasowego (*.ffs_tmp), a następnie nadp Automatic updates: Automatyczne aktualizacje: -Requires FreeFileSync Donation Edition -Wymaga FreeFileSync Donation Edition - Check for Program Updates Sprawdź dostępne aktualizacje. @@ -1849,7 +1864,7 @@ program kopiuje zawartość do pliku tymczasowego (*.ffs_tmp), a następnie nadp Błąd aktywacji FreeFileSync Donation Edition. Incorrect activation key. -Nipoprawny klucz aktywacyjny. +Niepoprawny klucz aktywacyjny. Unable to register to receive system messages. Błąd podczas rejestrowania do odbioru komunikatów systemowych. @@ -1932,7 +1947,7 @@ program kopiuje zawartość do pliku tymczasowego (*.ffs_tmp), a następnie nadp Poniższy element XML nie może zostać odczytany: Configuration file %x is incomplete. The missing elements will be set to their default values. -Plik konfiguracyjny %x jest niekompletny. Zostaną ustawione domyśle wartości dla brakujących elementów. +Plik konfiguracyjny %x jest niekompletny. Zostaną ustawione domyślne wartości dla brakujących elementów. Prepare installation Przygotuj instalację @@ -1941,7 +1956,7 @@ program kopiuje zawartość do pliku tymczasowego (*.ffs_tmp), a następnie nadp Określ komponenty do instalacji. Select installation type: -Typ instalacji: +Wybierz typ instalacji: Local Lokalna @@ -1977,10 +1992,10 @@ program kopiuje zawartość do pliku tymczasowego (*.ffs_tmp), a następnie nadp Na Pulpicie Start Menu - +Start Menu Send To - +Wyślij Do Registering FreeFileSync file extensions Rejestrowanie rozszerzeń plików FreeFileSync @@ -2003,6 +2018,9 @@ program kopiuje zawartość do pliku tymczasowego (*.ffs_tmp), a następnie nadp Edit with FreeFileSync Edytuj przy użyciu FreeFileSync +Instead of an ad, here's an animal. +Zamiast reklamy, oto jest zwierzę. + The FreeFileSync portable version cannot install into a subfolder of %x. Przenośna wersja FreeFileSync nie może być zainstalowana w podkatalogu w %x. @@ -2010,5 +2028,8 @@ program kopiuje zawartość do pliku tymczasowego (*.ffs_tmp), a następnie nadp Wybierz lokalny typ instalacji lub określ inny katalog. The %x installation option is only available in the FreeFileSync Donation Edition. - +Opcja instalacyjna %x jest dostępna wyłącznie w wersji FreeFileSync Donation Edition. + +Get the Donation Edition with bonus features and help keep FreeFileSync ad-free. +Pobierz Donation Edition z funkcjami dodatkowymi i pomóż utrzymać FreeFileSync bez reklam. diff --git a/FreeFileSync/Build/Languages/portuguese.lng b/FreeFileSync/Build/Languages/portuguese.lng index 493c0a0b..77bd1d3b 100755 --- a/FreeFileSync/Build/Languages/portuguese.lng +++ b/FreeFileSync/Build/Languages/portuguese.lng @@ -112,6 +112,9 @@ Path to an alternate GlobalSettings.xml file. Caminho para o ficheiro GlobalSettings.xml alternativo. +Installation files are corrupted. Please reinstall FreeFileSync. +Os ficheiros de instalação estão corrompidos. Por favor, reinstale o FreeFileSync. + Cannot find the following folders: Não é possível encontrar as seguintes pastas: @@ -333,12 +336,12 @@ Actual: %y bytes Cannot find %x. Não é possível encontrar %x. -Cannot open file %x. -Não é possível abrir o ficheiro %x. - Cannot find device %x. Não é possível encontrar o dispositivo %x. +Cannot open file %x. +Não é possível abrir o ficheiro %x. + Type of item %x is not supported: Tipo de item %x não é suportado: @@ -465,6 +468,9 @@ Actual: %y bytes Total time: Tempo total: +Cleaning up old log files... +A limpar ficheiros de log antigos... + Error parsing file %x, row %y, column %z. Erro ao analisar ficheiro %x, linha %y, coluna %z. @@ -653,6 +659,15 @@ O comando é executado se: Multiple... Múltiplo... +Cannot write file attributes of %x. +Não é possível escrever os atributos de %x. + +%x and %y have different content. +%x e %y tem conteúdos distintos. + +Data verification error: +Erro de verificação dos dados: + Moving file %x to %y A mover ficheiro %x para %y @@ -677,14 +692,8 @@ O comando é executado se: Updating attributes of %x A actualizar atributos de %x -Cannot write file attributes of %x. -Não é possível escrever os atributos de %x. - -%x and %y have different content. -%x e %y tem conteúdos distintos. - -Data verification error: -Erro de verificação dos dados: +Source item %x not found +Item de origem %x não encontrado Creating a Volume Shadow Copy for %x... A criar Volume Shadow Copy para %x... @@ -746,9 +755,6 @@ O comando é executado se: System: Shut down Desligar -Cleaning up old log files... -A limpar ficheiros de log antigos... - Stopped Parado @@ -881,6 +887,12 @@ O comando é executado se: Please select a folder on a local file system, network or an MTP device. Seleccione a pasta no sistema de ficheiros local, rede ou dispositivo MTP. +Defined by context of use + + +Requires FreeFileSync Donation Edition +Requer o FreeFileSync Donation Edition + &Save G&uardar @@ -1031,6 +1043,15 @@ O comando é executado se: Handle daylight saving time Lidar com horário de verão +Performance improvements: +Melhorias de desempenho: + +Parallel file operations: +Operações de arquivos paralelos: + +How to get best performance? +Como obter um melhor desempenho? + Local settings: Opções locais: @@ -1147,15 +1168,6 @@ O comando é executado se: Directory on server: Directório no servidor: -Performance improvements: -Melhorias de desempenho: - -How to get best performance? -Como obter um melhor desempenho? - -Connections for directory reading: -Conexões para ler directório: - SFTP channels per connection: Canais SFTP por conexão: @@ -1291,8 +1303,17 @@ Isto garante um estado consistente mesmo em caso de falha grave. &Default &Config. Iniciais -Source code written in C++ using: -Código fonte escrito em C++ utilizando: +Feedback and suggestions are welcome +Comentários e sugestões são apreciados + +Home page +Sítio da web + +FreeFileSync Forum +Fórum do FreeFileSync + +Email +Email If you like FreeFileSync: Se gosta do FreeFileSync: @@ -1300,20 +1321,14 @@ Isto garante um estado consistente mesmo em caso de falha grave. Support with a donation Apoiar com uma doação -Donation details -Detalhes da doação - The auto updater was disabled by the administrator. A actualização automática foi desactivada pelo administrador. -Feedback and suggestions are welcome -Comentários e sugestões são apreciados - -Home page -Sítio da web +Donation details +Detalhes da doação -Email -Email +Source code written in C++ using: +Código fonte escrito em C++ utilizando: Published under the GNU General Public License Publicado sobre GNU General Public License @@ -1402,9 +1417,6 @@ Isto garante um estado consistente mesmo em caso de falha grave. FreeFileSync %x is available! O FreeFileSync %x está disponível! -Installation files are corrupted. Please reinstall FreeFileSync. -Os ficheiros de instalação estão corrompidos. Por favor, reinstale o FreeFileSync. - Local path not available for %x. Caminho local não disponível para %x. @@ -1627,6 +1639,9 @@ Isto garante um estado consistente mesmo em caso de falha grave. Thank you, %x, for your donation and support! Obrigado, %x, pela sua doação e suporte! +Connections +Conexões + Recommended range: Gama recomendada: @@ -1798,9 +1813,6 @@ Isto garante um estado consistente mesmo em caso de falha grave. Automatic updates: Actualizações automáticas: -Requires FreeFileSync Donation Edition -Requer o FreeFileSync Donation Edition - Check for Program Updates Procurar actualizações do programa @@ -1990,6 +2002,9 @@ Isto garante um estado consistente mesmo em caso de falha grave. Edit with FreeFileSync Editar com FreeFileSync +Instead of an ad, here's an animal. +Em vez de um anúncio, aqui está um animal. + The FreeFileSync portable version cannot install into a subfolder of %x. A versão portátil do FreeFileSync não pode ser instalada em uma sub-pasta de %x. @@ -1999,3 +2014,6 @@ Isto garante um estado consistente mesmo em caso de falha grave. The %x installation option is only available in the FreeFileSync Donation Edition. A opção de instalação %x está disponível somente no FreeFileSync Donation Edition. +Get the Donation Edition with bonus features and help keep FreeFileSync ad-free. +Obtenha a Donation Edition com recursos bônus e ajude a manter o FreeFileSync sem anúncios. + diff --git a/FreeFileSync/Build/Languages/portuguese_br.lng b/FreeFileSync/Build/Languages/portuguese_br.lng index 8639ec65..393d94eb 100755 --- a/FreeFileSync/Build/Languages/portuguese_br.lng +++ b/FreeFileSync/Build/Languages/portuguese_br.lng @@ -112,6 +112,9 @@ Path to an alternate GlobalSettings.xml file. Caminho para um arquivo GlobalSettings.xml alternativo. +Installation files are corrupted. Please reinstall FreeFileSync. +Os arquivos de instalação estão corrompidos. Por favor, reinstale o FreeFileSync. + Cannot find the following folders: Não é possível localizar as seguintes pastas: @@ -333,12 +336,12 @@ Atual: %y bytes Cannot find %x. Não é possível encontrar %x. -Cannot open file %x. -Não é possível abrir o arquivo %x. - Cannot find device %x. Não é possível encontrar o dispositivo %x. +Cannot open file %x. +Não é possível abrir o arquivo %x. + Type of item %x is not supported: Tipo de item %x não é suportado: @@ -465,6 +468,9 @@ Atual: %y bytes Total time: Tempo total: +Cleaning up old log files... +Limpando arquivo de log antigo... + Error parsing file %x, row %y, column %z. Erro analisando o arquivo %x, linha %y, coluna %z. @@ -653,6 +659,15 @@ O comando é disparado se: Multiple... Múltiplo... +Cannot write file attributes of %x. +Não é possível escrever os atributos de arquivo de %x. + +%x and %y have different content. +%x e %y têm conteúdos diferentes. + +Data verification error: +Erro na verificação dos dados: + Moving file %x to %y Movendo arquivo %x para %y @@ -677,14 +692,8 @@ O comando é disparado se: Updating attributes of %x Atualizando atributos de %x -Cannot write file attributes of %x. -Não é possível escrever os atributos de arquivo de %x. - -%x and %y have different content. -%x e %y têm conteúdos diferentes. - -Data verification error: -Erro na verificação dos dados: +Source item %x not found +Item de origem %x não encontrado Creating a Volume Shadow Copy for %x... Criando uma Cópia de Sombra de Volume para %x... @@ -746,9 +755,6 @@ O comando é disparado se: System: Shut down Sistema: Desligar -Cleaning up old log files... -Limpando arquivo de log antigo... - Stopped Interrompido @@ -881,6 +887,12 @@ O comando é disparado se: Please select a folder on a local file system, network or an MTP device. Selecione uma pasta em um sistema de arquivos local, de rede ou de um dispositivo MTP. +Defined by context of use + + +Requires FreeFileSync Donation Edition +Requer FreeFileSync Edição do Doador + &Save &Salvar @@ -1031,6 +1043,15 @@ O comando é disparado se: Handle daylight saving time Como lidar com horário de verão +Performance improvements: +Melhorias de desempenho: + +Parallel file operations: +Operações de arquivos em paralelo: + +How to get best performance? +Como obter o melhor desempenho? + Local settings: Configurações locais: @@ -1147,15 +1168,6 @@ O comando é disparado se: Directory on server: Diretório no servidor: -Performance improvements: -Melhorias de desempenho: - -How to get best performance? -Como obter o melhor desempenho? - -Connections for directory reading: -Conexões para leitura de diretório: - SFTP channels per connection: Canais SFTP por conexão: @@ -1291,8 +1303,17 @@ Isto garante um estado consistente mesmo em caso de erro grave. &Default &Config. Padrão -Source code written in C++ using: -Código-fonte escrito em C++ utilizando: +Feedback and suggestions are welcome +Críticas e sugestões são bem-vindas + +Home page +Página web + +FreeFileSync Forum +Fórum do FreeFileSync + +Email +E-mail If you like FreeFileSync: Se você gosta do FreeFileSync: @@ -1300,20 +1321,14 @@ Isto garante um estado consistente mesmo em caso de erro grave. Support with a donation Apoie com uma doação -Donation details -Detalhes da doação - The auto updater was disabled by the administrator. A atualização automática foi desabilitada pelo administrador. -Feedback and suggestions are welcome -Críticas e sugestões são bem-vindas - -Home page -Página web +Donation details +Detalhes da doação -Email -E-mail +Source code written in C++ using: +Código-fonte escrito em C++ utilizando: Published under the GNU General Public License Publicado sob a GNU General Public License @@ -1402,9 +1417,6 @@ Isto garante um estado consistente mesmo em caso de erro grave. FreeFileSync %x is available! FreeFileSync %x está disponível! -Installation files are corrupted. Please reinstall FreeFileSync. -Os arquivos de instalação estão corrompidos. Por favor, reinstale o FreeFileSync. - Local path not available for %x. Caminho local não disponível para %x. @@ -1627,6 +1639,9 @@ Isto garante um estado consistente mesmo em caso de erro grave. Thank you, %x, for your donation and support! Obrigado, %x, pela sua doação e suporte! +Connections +Conexões + Recommended range: Intervalo recomendado: @@ -1798,9 +1813,6 @@ Isto garante um estado consistente mesmo em caso de erro grave. Automatic updates: Atualizações automáticas: -Requires FreeFileSync Donation Edition -Requer FreeFileSync Edição do Doador - Check for Program Updates Verificar se existem atualizações @@ -1990,6 +2002,9 @@ Isto garante um estado consistente mesmo em caso de erro grave. Edit with FreeFileSync Editar com FreeFileSync +Instead of an ad, here's an animal. +Ao invés de um anúncio, veja um animal. + The FreeFileSync portable version cannot install into a subfolder of %x. A versão portátil do FreeFileSync não pode ser instalada em uma subpasta de %x. @@ -1999,3 +2014,6 @@ Isto garante um estado consistente mesmo em caso de erro grave. The %x installation option is only available in the FreeFileSync Donation Edition. A opção de instalação %x está disponível apenas na Edição do Doador do FreeFileSync. +Get the Donation Edition with bonus features and help keep FreeFileSync ad-free. +Obtenha a Edição do Doador com recursos adicionais e ajude a manter o FreeFileSync livre de anúncios. + diff --git a/FreeFileSync/Build/Languages/romanian.lng b/FreeFileSync/Build/Languages/romanian.lng index aa9255ec..f36eea79 100755 --- a/FreeFileSync/Build/Languages/romanian.lng +++ b/FreeFileSync/Build/Languages/romanian.lng @@ -7,6 +7,9 @@ n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2
      +Defined by context of use + + Both sides have changed since last synchronization. Ambele părți s-au modificat de la ultima sincronizare. @@ -112,6 +115,9 @@ Path to an alternate GlobalSettings.xml file. Calea către o filă GlobalSettings.xml alternativă. +Installation files are corrupted. Please reinstall FreeFileSync. +Filele de instalare sînt stricate (corupte). Reinstalează FreeFileSync. + Cannot find the following folders: Nu pot găsi dosarele următoare: @@ -334,12 +340,12 @@ Actuală: %y baiți Cannot find %x. Nu pot găsi %x. -Cannot open file %x. -Nu pot deschide fila %x. - Cannot find device %x. Nu pot găsi dispozitivul %x. +Cannot open file %x. +Nu pot deschide fila %x. + Type of item %x is not supported: Tipul de element %x nu e suportat: @@ -470,6 +476,9 @@ Actuală: %y baiți Total time: Timp Total: +Cleaning up old log files... +Curăț filele de jurnalizare vechi... + Error parsing file %x, row %y, column %z. Eroare la parsarea filei %x, rîndul %y, coloana %z. @@ -659,6 +668,15 @@ Comanda este declanșată dacă: Multiple... Multiplu... +Cannot write file attributes of %x. +Nu pot scrie atributele de filă ale lui %x. + +%x and %y have different content. +%x și %y au conținut diferit. + +Data verification error: +Eroare de verificare a datei: + Moving file %x to %y Mut fila %x în %y @@ -683,14 +701,8 @@ Comanda este declanșată dacă: Updating attributes of %x Actualizez atributele lui %x -Cannot write file attributes of %x. -Nu pot scrie atributele de filă ale lui %x. - -%x and %y have different content. -%x și %y au conținut diferit. - -Data verification error: -Eroare de verificare a datei: +Source item %x not found +Itemul sursă %x n-a fost găsit Creating a Volume Shadow Copy for %x... Creez o Conservare a Volumului [Volume Shadow Copy] pentru %x... @@ -752,9 +764,6 @@ Comanda este declanșată dacă: System: Shut down Sistem: Închide PC-ul [Shut down] -Cleaning up old log files... -Curăț filele de jurnalizare vechi... - Stopped Oprită @@ -888,6 +897,9 @@ Comanda este declanșată dacă: Please select a folder on a local file system, network or an MTP device. Selectează un dosar de pe un sistem de file local, din rețea sau de pe un dispozitiv MTP (dispozitiv media portabil). +Requires FreeFileSync Donation Edition +Necesită FreeFileSync Ediția pentru Donatori + &Save &Salvează @@ -1038,6 +1050,15 @@ Comanda este declanșată dacă: Handle daylight saving time Gestionarea timpului de vară (DST) +Performance improvements: +Îmbunătățiri de Performanță: + +Parallel file operations: +Operațiuni paralele cu file: + +How to get best performance? +Cum obții cea mai bună performanță? + Local settings: Setări Locale: @@ -1154,15 +1175,6 @@ Comanda este declanșată dacă: Directory on server: Dosarul de pe server: -Performance improvements: -Îmbunătățiri de Performanță: - -How to get best performance? -Cum obții cea mai bună performanță? - -Connections for directory reading: -Conexiuni pentru citirea dosarelor: - SFTP channels per connection: Canale SFTP per conexiune: @@ -1298,8 +1310,17 @@ Aceasta garantează consecvența stării filelor chiar și în cazul apariției &Default Coloanele &Implicite -Source code written in C++ using: -Cod sursă scris în C++ folosind: +Feedback and suggestions are welcome +Opiniile și sugestiile despre program sînt binevenite. + +Home page +Pagina Sitului + +FreeFileSync Forum +Forumul FreeFileSync + +Email +Adresa Autorului If you like FreeFileSync: Donează pentru FreeFileSync: @@ -1307,20 +1328,14 @@ Aceasta garantează consecvența stării filelor chiar și în cazul apariției Support with a donation Sprijină cu o donație -Donation details -Detaliile Donăriii - The auto updater was disabled by the administrator. Actualizatorul automat a fost dezactivat de administrator. -Feedback and suggestions are welcome -Opiniile și sugestiile despre program sînt binevenite. - -Home page -Pagina Sitului +Donation details +Detaliile Donăriii -Email -Adresa Autorului +Source code written in C++ using: +Cod sursă scris în C++ folosind: Published under the GNU General Public License Publicat sub licența GNU GPL @@ -1409,9 +1424,6 @@ Aceasta garantează consecvența stării filelor chiar și în cazul apariției FreeFileSync %x is available! FreeFileSync %x este disponibil! -Installation files are corrupted. Please reinstall FreeFileSync. -Filele de instalare sînt stricate (corupte). Reinstalează FreeFileSync. - Local path not available for %x. Calea locală nu este disponibilă pentru %x. @@ -1638,6 +1650,9 @@ Aceasta garantează consecvența stării filelor chiar și în cazul apariției Thank you, %x, for your donation and support! Mulțumesc %x, pentru donație și sprijin! +Connections +Conexiuni + Recommended range: Interval recomandat: @@ -1812,9 +1827,6 @@ Aceasta garantează consecvența stării filelor chiar și în cazul apariției Automatic updates: Actualizări Automate: -Requires FreeFileSync Donation Edition -Necesită FreeFileSync Ediția pentru Donatori - Check for Program Updates Caută Actualizări ale Programului @@ -2006,6 +2018,9 @@ Aceasta garantează consecvența stării filelor chiar și în cazul apariției Edit with FreeFileSync Editează cu FreeFileSync +Instead of an ad, here's an animal. +În loc de o publicitate, uite un animal. + The FreeFileSync portable version cannot install into a subfolder of %x. Versiunea portabilă de FreeFileSync nu poate fi instalată într-un subdosar al %x. @@ -2015,3 +2030,6 @@ Aceasta garantează consecvența stării filelor chiar și în cazul apariției The %x installation option is only available in the FreeFileSync Donation Edition. Opțiunea de instalare %x e disponibilă doar pentru FreeFileSync Ediția pentru Donatori. +Get the Donation Edition with bonus features and help keep FreeFileSync ad-free. +Obține Ediția pentru Donatori cu funcții suplimentare și ajută la menținerea FreeFileSync fără publicitate. + diff --git a/FreeFileSync/Build/Languages/russian.lng b/FreeFileSync/Build/Languages/russian.lng index 4273a175..67606f38 100755 --- a/FreeFileSync/Build/Languages/russian.lng +++ b/FreeFileSync/Build/Languages/russian.lng @@ -112,6 +112,9 @@ Path to an alternate GlobalSettings.xml file. Путь к альтернативному файлу GlobalSettings.xml. +Installation files are corrupted. Please reinstall FreeFileSync. +Установочные файлы повреждены. Переустановите FreeFileSync. + Cannot find the following folders: Невозможно найти следующие папки: @@ -334,12 +337,12 @@ Actual: %y bytes Cannot find %x. Невозможно найти %x. -Cannot open file %x. -Невозможно открыть файл %x. - Cannot find device %x. Невозможно найти устройство %x. +Cannot open file %x. +Невозможно открыть файл %x. + Type of item %x is not supported: Тип элемента %x не поддерживается: @@ -470,6 +473,9 @@ Actual: %y bytes Total time: Общее время: +Cleaning up old log files... +Очистка старых лог-файлов (журналов)... + Error parsing file %x, row %y, column %z. Ошибка при разборе файла %x, строка %y, колонка %z. @@ -659,6 +665,15 @@ The command is triggered if: Multiple... Различные варианты синхронизации... +Cannot write file attributes of %x. +Невозможно записать атрибуты файла %x. + +%x and %y have different content. +%x и %y имеют разное содержание. + +Data verification error: +Ошибка верификации данных: + Moving file %x to %y Перемещение файла %x в %y @@ -683,14 +698,8 @@ The command is triggered if: Updating attributes of %x Обновление атрибутов %x -Cannot write file attributes of %x. -Невозможно записать атрибуты файла %x. - -%x and %y have different content. -%x и %y имеют разное содержание. - -Data verification error: -Ошибка верификации данных: +Source item %x not found +Исходный элемент %x не найден Creating a Volume Shadow Copy for %x... Создание Тома Теневого Копирования для %x... @@ -752,9 +761,6 @@ The command is triggered if: System: Shut down Система: Завершение работы -Cleaning up old log files... -Очистка старых лог-файлов (журналов)... - Stopped Остановлено @@ -888,6 +894,12 @@ The command is triggered if: Please select a folder on a local file system, network or an MTP device. Пожалуйста, выберите папку в локальной файловой системе, сети или MTP устройстве. +Defined by context of use + + +Requires FreeFileSync Donation Edition +Требуется платная версия FreeFileSync + &Save &Сохранить @@ -1038,6 +1050,15 @@ The command is triggered if: Handle daylight saving time Ручной переход на летнее время +Performance improvements: +Повышение производительности: + +Parallel file operations: +Параллельные операции с файлами: + +How to get best performance? +Как получить лучшую производительность? + Local settings: Локальные настройки: @@ -1154,15 +1175,6 @@ The command is triggered if: Directory on server: Папка на сервере: -Performance improvements: -Повышение производительности: - -How to get best performance? -Как получить лучшую производительность? - -Connections for directory reading: -Количество соединений для чтения папок: - SFTP channels per connection: Количество SFTP каналов на одно соединение: @@ -1298,8 +1310,17 @@ This guarantees a consistent state even in case of a serious error. &Default &По умолчанию -Source code written in C++ using: -Исходный код написан на C++ с использованием: +Feedback and suggestions are welcome +Замечания и предложения приветствуются + +Home page +Домашняя страница + +FreeFileSync Forum +Форум FreeFileSync + +Email +Почта If you like FreeFileSync: Если Вам понравился FreeFileSync: @@ -1307,20 +1328,14 @@ This guarantees a consistent state even in case of a serious error. Support with a donation Помочь пожертвованием -Donation details -Детали оплаты - The auto updater was disabled by the administrator. Автоматическое обновление было отключено администратором. -Feedback and suggestions are welcome -Замечания и предложения приветствуются - -Home page -Домашняя страница +Donation details +Детали оплаты -Email -Почта +Source code written in C++ using: +Исходный код написан на C++ с использованием: Published under the GNU General Public License Издается под лицензией GNU General Public License @@ -1409,9 +1424,6 @@ This guarantees a consistent state even in case of a serious error. FreeFileSync %x is available! FreeFileSync %x доступен! -Installation files are corrupted. Please reinstall FreeFileSync. -Установочные файлы повреждены. Переустановите FreeFileSync. - Local path not available for %x. Локальный путь не доступен для %x. @@ -1638,6 +1650,9 @@ This guarantees a consistent state even in case of a serious error. Thank you, %x, for your donation and support! Благодарим вас, %x, за пожертвование и поддержку! +Connections +Соединения + Recommended range: Рекомендуемый диапазон: @@ -1812,9 +1827,6 @@ This guarantees a consistent state even in case of a serious error. Automatic updates: Автоматические обновления: -Requires FreeFileSync Donation Edition -Требуется платная версия FreeFileSync - Check for Program Updates Проверка обновления программы @@ -2006,6 +2018,9 @@ This guarantees a consistent state even in case of a serious error. Edit with FreeFileSync Редактировать с помощью FreeFileSync +Instead of an ad, here's an animal. +Вот вам картинка животного, вместо рекламы. + The FreeFileSync portable version cannot install into a subfolder of %x. Портативная версия FreeFileSync не может быть установлена в подпапку %x. @@ -2015,3 +2030,6 @@ This guarantees a consistent state even in case of a serious error. The %x installation option is only available in the FreeFileSync Donation Edition. %x опция установки доступна только в платной версии FreeFileSync. +Get the Donation Edition with bonus features and help keep FreeFileSync ad-free. +Приобретите платную версию FreeFileSync с бонусными функциями и помогите сохранить FreeFileSync свободным от рекламы. + diff --git a/FreeFileSync/Build/Languages/slovak.lng b/FreeFileSync/Build/Languages/slovak.lng index 8ff29202..769d28dc 100755 --- a/FreeFileSync/Build/Languages/slovak.lng +++ b/FreeFileSync/Build/Languages/slovak.lng @@ -112,6 +112,9 @@ Path to an alternate GlobalSettings.xml file. Cesta k alternatívnemu súboru GlobalSettings.xml. +Installation files are corrupted. Please reinstall FreeFileSync. +Inštalačný súbor je poškodený. Prosím preinštalujte FreeFileSync. + Cannot find the following folders: Nie je možné nájsť následujúce priečinky: @@ -334,12 +337,12 @@ Aktuálne: %y b Cannot find %x. Nie je možné nájsť %x. -Cannot open file %x. -Nie je možné otvoriť súbor %x. - Cannot find device %x. Nie je možné nájsť zariadenie %x. +Cannot open file %x. +Nie je možné otvoriť súbor %x. + Type of item %x is not supported: Typ položky %x nie je podporovaný: @@ -470,6 +473,9 @@ Aktuálne: %y b Total time: Celkový čas: +Cleaning up old log files... +Odstráňovanie starých log súborov... + Error parsing file %x, row %y, column %z. Chyba spracovania súboru %x: na riadku %y v stĺpci %z. @@ -659,6 +665,15 @@ Príkaz bude spustení ak: Multiple... Rôzne... +Cannot write file attributes of %x. +Nie je možné zapísať atribúty súboru %x. + +%x and %y have different content. +%x a %y majú odlišný obsah. + +Data verification error: +Chyba verifikácie údajov: + Moving file %x to %y Presúvanie súboru %x do %y @@ -683,14 +698,8 @@ Príkaz bude spustení ak: Updating attributes of %x Aktualizácia atribútov súboru %x -Cannot write file attributes of %x. -Nie je možné zapísať atribúty súboru %x. - -%x and %y have different content. -%x a %y majú odlišný obsah. - -Data verification error: -Chyba verifikácie údajov: +Source item %x not found +Zdrojová položka %x sa nenašla Creating a Volume Shadow Copy for %x... Vytváranie Tieňovej kópie zväzkov pre %x... @@ -752,9 +761,6 @@ Príkaz bude spustení ak: System: Shut down Systém: Vypnúť -Cleaning up old log files... -Odstráňovanie starých log súborov... - Stopped Zastavené @@ -888,6 +894,12 @@ Príkaz bude spustení ak: Please select a folder on a local file system, network or an MTP device. Prosím vyberte priečinok v lokálnom súborovom systéme, sieti alebo multimediálnom zariadení. +Defined by context of use + + +Requires FreeFileSync Donation Edition +Je potrebná FreeFileSync Donation Edition + &Save &Uložiť @@ -1038,6 +1050,15 @@ Príkaz bude spustení ak: Handle daylight saving time Používať letný čas +Performance improvements: +Zlepšenie výkonu: + +Parallel file operations: +Paralélne operácie súborov: + +How to get best performance? +Ako získať najlepší výkon: + Local settings: Lokálne Nastavenia: @@ -1154,15 +1175,6 @@ Príkaz bude spustení ak: Directory on server: Adresár na servery: -Performance improvements: -Zlepšenie výkonu: - -How to get best performance? -Ako získať najlepší výkon: - -Connections for directory reading: -Pripojenia pri načítaní adresárov: - SFTP channels per connection: SFTP kanály na pripojenie: @@ -1295,8 +1307,17 @@ This guarantees a consistent state even in case of a serious error. &Default &Predvolené -Source code written in C++ using: -Zdrojový kód bol napísaný kompletne v C++ pomocou: +Feedback and suggestions are welcome +Komentáre a námety sú vždy vítané + +Home page +Domovská stránka + +FreeFileSync Forum +FreeFileSync fórum + +Email +Email If you like FreeFileSync: Pokiaľ sa Vám FreeFileSync páči: @@ -1304,20 +1325,14 @@ This guarantees a consistent state even in case of a serious error. Support with a donation Podporiť darom -Donation details -Detajly darovania - The auto updater was disabled by the administrator. Automatická aktualizácia bola zakázaná správcom. -Feedback and suggestions are welcome -Komentáre a námety sú vždy vítané - -Home page -Domovská stránka +Donation details +Detajly darovania -Email -Email +Source code written in C++ using: +Zdrojový kód bol napísaný kompletne v C++ pomocou: Published under the GNU General Public License Vydané pod GNU General Public License (GPL) @@ -1406,9 +1421,6 @@ This guarantees a consistent state even in case of a serious error. FreeFileSync %x is available! Je dostupná FreeFileSync verzia %x! -Installation files are corrupted. Please reinstall FreeFileSync. -Inštalačný súbor je poškodený. Prosím preinštalujte FreeFileSync. - Local path not available for %x. Lokálna cesta pre %x nie je k dispozícií. @@ -1635,6 +1647,9 @@ This guarantees a consistent state even in case of a serious error. Thank you, %x, for your donation and support! Ďakujem, %x, za dar a podporu! +Connections +Pripojenia + Recommended range: Odporúčaný rozsah: @@ -1809,9 +1824,6 @@ This guarantees a consistent state even in case of a serious error. Automatic updates: Automatická aktualizácia: -Requires FreeFileSync Donation Edition -Je potrebná FreeFileSync Donation Edition - Check for Program Updates Hľadanie aktualizácií programu @@ -2003,6 +2015,9 @@ This guarantees a consistent state even in case of a serious error. Edit with FreeFileSync Upraviť vo FreeFileSync +Instead of an ad, here's an animal. +Namiesto reklami je tu obrázok zvieraťa. + The FreeFileSync portable version cannot install into a subfolder of %x. Prenosnú verziu FreeFileSync nie je možné inštalovať do podpriečinka %x. @@ -2012,3 +2027,6 @@ This guarantees a consistent state even in case of a serious error. The %x installation option is only available in the FreeFileSync Donation Edition. Inštalačná možnosť %x je dostupná iba pri FreeFileSync Donation Edition. +Get the Donation Edition with bonus features and help keep FreeFileSync ad-free. +Získajte Donation Edition s bonusovými funkciami a pomôžte, aby ostal FreeFileSync bez reklami. + diff --git a/FreeFileSync/Build/Languages/slovenian.lng b/FreeFileSync/Build/Languages/slovenian.lng index b958ef19..63121a80 100755 --- a/FreeFileSync/Build/Languages/slovenian.lng +++ b/FreeFileSync/Build/Languages/slovenian.lng @@ -112,6 +112,9 @@ Path to an alternate GlobalSettings.xml file. Pot do alternativne datoteke GlobalSettings.xml. +Installation files are corrupted. Please reinstall FreeFileSync. +Namestitvena datoteka je poškodovana. Prosim ponovno naložite FreeFileSync. + Cannot find the following folders: Ne najdem naslednjih map: @@ -335,12 +338,12 @@ Dejansko: %y bajtov Cannot find %x. Ne najdem %x. -Cannot open file %x. -Ne morem odpreti datoteke %x. - Cannot find device %x. Ne najdem naprave %x. +Cannot open file %x. +Ne morem odpreti datoteke %x. + Type of item %x is not supported: Vrsta postavke %x ni podprta: @@ -475,6 +478,9 @@ Dejansko: %y bajtov Total time: Celoten čas: +Cleaning up old log files... +Čiščenje starih datotek dnevnika... + Error parsing file %x, row %y, column %z. Napaka pri razčlenjevanju datoteke %x, vrstica %y, stolpec %z. @@ -665,6 +671,15 @@ Ukaz se sproži če: Multiple... Večkratno... +Cannot write file attributes of %x. +Ne morem zapisati datotečnih atributov od %x. + +%x and %y have different content. +%x in %y imata različno vsebino. + +Data verification error: +Napaka pri preverjanju podatkov: + Moving file %x to %y Premikam datoteko %x v %y @@ -689,14 +704,8 @@ Ukaz se sproži če: Updating attributes of %x Posodabljam atribute od %x -Cannot write file attributes of %x. -Ne morem zapisati datotečnih atributov od %x. - -%x and %y have different content. -%x in %y imata različno vsebino. - -Data verification error: -Napaka pri preverjanju podatkov: +Source item %x not found +Izvorna postavka %x ni bila najdena Creating a Volume Shadow Copy for %x... Ustvarjam Volume Shadow Copy za %x... @@ -758,9 +767,6 @@ Ukaz se sproži če: System: Shut down Sistem: Izključi računalnik -Cleaning up old log files... -Čiščenje starih datotek dnevnika... - Stopped Ustavljeno @@ -895,6 +901,12 @@ Ukaz se sproži če: Please select a folder on a local file system, network or an MTP device. Prosim izberite mapo na lokalnem datotečnem sistemu, mreži ali na MTP napravi. +Defined by context of use + + +Requires FreeFileSync Donation Edition +Zahteva FreeFileSync Donation Edition + &Save &Shrani @@ -1045,6 +1057,15 @@ Ukaz se sproži če: Handle daylight saving time Upoštevaj poletni in zimski čas +Performance improvements: +Izboljšave zmogljivosti: + +Parallel file operations: +Vzporedne operacije datoteke: + +How to get best performance? +Kako doseči najboljšo učinkovitost? + Local settings: Lokalne nastavitve: @@ -1161,15 +1182,6 @@ Ukaz se sproži če: Directory on server: Imenik na strežniku: -Performance improvements: -Izboljšave zmogljivosti: - -How to get best performance? -Kako doseči najboljšo učinkovitost? - -Connections for directory reading: -Povezave za branje imenika: - SFTP channels per connection: SFTP kanali za povezavo: @@ -1305,8 +1317,17 @@ To zagotavlja dosledno stanje tudi v primeru resne napake. &Default &Privzeto -Source code written in C++ using: -Izvorna koda napisana v C++ z uporabo: +Feedback and suggestions are welcome +Povratne informacije in predlogi so dobrodošli + +Home page +Domača stran + +FreeFileSync Forum +FreeFileSync Forum + +Email +Elektronska pošta If you like FreeFileSync: Če vam je FreeFileSync všeč: @@ -1314,20 +1335,14 @@ To zagotavlja dosledno stanje tudi v primeru resne napake. Support with a donation Podpora z donacijo -Donation details -Podrobnosti o donaciji - The auto updater was disabled by the administrator. Skrbnik je onemogočil samodejno posodabljanje. -Feedback and suggestions are welcome -Povratne informacije in predlogi so dobrodošli - -Home page -Domača stran +Donation details +Podrobnosti o donaciji -Email -Elektronska pošta +Source code written in C++ using: +Izvorna koda napisana v C++ z uporabo: Published under the GNU General Public License Objavljeno pod licenco GNU General Public @@ -1416,9 +1431,6 @@ To zagotavlja dosledno stanje tudi v primeru resne napake. FreeFileSync %x is available! FreeFileSync %x je na voljo -Installation files are corrupted. Please reinstall FreeFileSync. -Namestitvena datoteka je poškodovana. Prosim ponovno naložite FreeFileSync. - Local path not available for %x. Lokalna pot ni na voljo za %x. @@ -1649,6 +1661,9 @@ To zagotavlja dosledno stanje tudi v primeru resne napake. Thank you, %x, for your donation and support! Najlepša hvala, %x, za vašo donacijo in podporo! +Connections +Povezave + Recommended range: Priporočeni obseg: @@ -1826,9 +1841,6 @@ To zagotavlja dosledno stanje tudi v primeru resne napake. Automatic updates: Samodejne posodobitve: -Requires FreeFileSync Donation Edition -Zahteva FreeFileSync Donation Edition - Check for Program Updates Preveri obstoj posodobitve programa @@ -2022,6 +2034,9 @@ To zagotavlja dosledno stanje tudi v primeru resne napake. Edit with FreeFileSync Uredi z FreeFileSync +Instead of an ad, here's an animal. +Namesto oglasa je tukaj žival. + The FreeFileSync portable version cannot install into a subfolder of %x. Prenosne različice FreeFileSync ni mogoče namestiti v podmapo %x. @@ -2031,3 +2046,6 @@ To zagotavlja dosledno stanje tudi v primeru resne napake. The %x installation option is only available in the FreeFileSync Donation Edition. Možnost namestitve %x je na voljo samo v FreeFileSync Donation Edition. +Get the Donation Edition with bonus features and help keep FreeFileSync ad-free. +Pridobite donacijsko izdajo s bonusnimi funkcijami in pomagajte ohraniti FreeFileSync brez oglasov. + diff --git a/FreeFileSync/Build/Languages/spanish.lng b/FreeFileSync/Build/Languages/spanish.lng index d440e564..dea4aaac 100755 --- a/FreeFileSync/Build/Languages/spanish.lng +++ b/FreeFileSync/Build/Languages/spanish.lng @@ -29,7 +29,7 @@ Creando carpeta %x Creating symbolic link %x -Creando el vínculo simbólico %x +Creando vínculo simbólico %x Moving file %x to the recycle bin Mover archivo %x a la papelera de reciclaje @@ -112,6 +112,9 @@ Path to an alternate GlobalSettings.xml file. Ruta a un archivo alternativo GlobalSettings.xml. +Installation files are corrupted. Please reinstall FreeFileSync. +Los archivos de instalación están dañados. Reinstale FreeFileSync. + Cannot find the following folders: No se pudieron encontrar las siguiente carpetas: @@ -149,7 +152,7 @@ Los elementos sólo se diferencian en los atributos Resolving symbolic link %x -Resolviendo el vínculo simbólico %x +Resolviendo vínculo simbólico %x Comparing content of files %x Comparación del contenido de los archivos %x @@ -333,17 +336,17 @@ Reales: %y bytes Cannot find %x. No se encuentra %x. -Cannot open file %x. -No se puede abrir el archivo %x. - Cannot find device %x. No se encuentra el dispositivo %x. +Cannot open file %x. +No se puede abrir el archivo %x. + Type of item %x is not supported: El tipo de objeto %x no esta soportado: Cannot delete symbolic link %x. -No se puede eliminar el el vínculo simbólico %x. +No se puede eliminar el vínculo simbólico %x. Cannot determine free disk space for %x. No se puede determinar el espacio libre en disco para %x. @@ -465,6 +468,9 @@ Reales: %y bytes Total time: Tiempo total: +Cleaning up old log files... +Limpiando antiguos archivos de registro... + Error parsing file %x, row %y, column %z. Error analizando archivo %x, fila %y, columna %z. @@ -653,6 +659,15 @@ El comando es disparado si: Multiple... Múltiple... +Cannot write file attributes of %x. +No se pueden escribir los atributos de archivo de %x. + +%x and %y have different content. +%x y %y tienen contenidos diferentes. + +Data verification error: +Error de verificación de datos: + Moving file %x to %y Mover archivo de %x a %y @@ -669,7 +684,7 @@ El comando es disparado si: Actualizando archivo %x Updating symbolic link %x -Actualizando el vínculo simbólico %x +Actualizando vínculo simbólico %x Verifying file %x Verificación del archivo %x @@ -677,14 +692,8 @@ El comando es disparado si: Updating attributes of %x Actualizar atributos de %x -Cannot write file attributes of %x. -No se pueden escribir los atributos de archivo de %x. - -%x and %y have different content. -%x y %y tienen contenidos diferentes. - -Data verification error: -Error de verificación de datos: +Source item %x not found +No se encontró el archivo de origen %x Creating a Volume Shadow Copy for %x... Creando una Instantánea de volumen para %x... @@ -696,7 +705,7 @@ El comando es disparado si: El campo de entrada de la carpeta de destino no debe estar vacío. Source folder %x not found. -El archivo de origen %x no ha sido encontrado. +No se encontró la carpeta de origen %x. Please enter a target folder for versioning. Indique una carpeta de destino para la versión. @@ -746,9 +755,6 @@ El comando es disparado si: System: Shut down Sistema: apagar -Cleaning up old log files... -Limpiando antiguos archivos de registro... - Stopped Detenido @@ -881,6 +887,12 @@ El comando es disparado si: Please select a folder on a local file system, network or an MTP device. Seleccione otra carpeta del systema de archivos local, en red o en un dispositivo MTP. +Defined by context of use + + +Requires FreeFileSync Donation Edition +Requiere FreeFileSync Donation Edition + &Save &Guardar @@ -1031,6 +1043,15 @@ El comando es disparado si: Handle daylight saving time Respetar el horario de verano +Performance improvements: +Mejoras en el rendimiento: + +Parallel file operations: +Operaciones paralelas sobre archivos: + +How to get best performance? +¿Cómo obtener el mejor rendimiento? + Local settings: Opciones locales: @@ -1147,15 +1168,6 @@ El comando es disparado si: Directory on server: Directorio del servidor: -Performance improvements: -Mejoras en el rendimiento: - -How to get best performance? -¿Cómo obtener el mejor rendimiento? - -Connections for directory reading: -Conexiones para lectura de directorios: - SFTP channels per connection: Canales SFTP por conexión: @@ -1291,8 +1303,17 @@ Esto garantiza un estado coherente incluso en caso de error grave. &Default &Configuración predeterminada -Source code written in C++ using: -Código fuente C++ con soporte de : +Feedback and suggestions are welcome +Tus comentarios y sugerencias son bienvenidos : + +Home page +Página de inicio + +FreeFileSync Forum +Foro de FreeFileSync + +Email +Correo electrónico If you like FreeFileSync: Si te resulta útil FreeFileSync: @@ -1300,20 +1321,14 @@ Esto garantiza un estado coherente incluso en caso de error grave. Support with a donation ¡Contribuye con una donación! -Donation details -Detalles para donación - The auto updater was disabled by the administrator. La actualización automática ha sido desactivada por el administrador. -Feedback and suggestions are welcome -Tus comentarios y sugerencias son bienvenidos : - -Home page -Página de inicio +Donation details +Detalles para donación -Email -Correo electrónico +Source code written in C++ using: +Código fuente C++ con soporte de : Published under the GNU General Public License Publicado bajo régimen de derechos GNU General Public License : @@ -1402,9 +1417,6 @@ Esto garantiza un estado coherente incluso en caso de error grave. FreeFileSync %x is available! ¡Ya está disponible FreeFileSync %x! -Installation files are corrupted. Please reinstall FreeFileSync. -Los archivos de instalación están dañados. Reinstale FreeFileSync. - Local path not available for %x. Ruta local no disponible para %x. @@ -1627,6 +1639,9 @@ Esto garantiza un estado coherente incluso en caso de error grave. Thank you, %x, for your donation and support! ¡Muchas gracias, %x, por su contribución y ayuda! +Connections +Conexiones + Recommended range: Rango recomendado: @@ -1798,9 +1813,6 @@ Esto garantiza un estado coherente incluso en caso de error grave. Automatic updates: Actualizaciones automáticas: -Requires FreeFileSync Donation Edition -Requiere FreeFileSync Donation Edition - Check for Program Updates Buscar actualizaciones del programa @@ -1990,6 +2002,9 @@ Esto garantiza un estado coherente incluso en caso de error grave. Edit with FreeFileSync Modificar con FreeFileSync +Instead of an ad, here's an animal. +En lugar de un anuncio, verá una imagen como ésta. + The FreeFileSync portable version cannot install into a subfolder of %x. No se puede instalar la versión portátil de FreeFileSync en una subcarpeta de %x. @@ -1999,3 +2014,6 @@ Esto garantiza un estado coherente incluso en caso de error grave. The %x installation option is only available in the FreeFileSync Donation Edition. La opción %x de instalación sólo está disponible para la versión FreeFileSync Donation Edition. +Get the Donation Edition with bonus features and help keep FreeFileSync ad-free. +Obtenga la FreeFileSync Donation Edition con sus características extra y ayude a mantener FreeFileSync libre de anuncios. + diff --git a/FreeFileSync/Build/Languages/swedish.lng b/FreeFileSync/Build/Languages/swedish.lng index 6963e231..e7879ed7 100755 --- a/FreeFileSync/Build/Languages/swedish.lng +++ b/FreeFileSync/Build/Languages/swedish.lng @@ -112,6 +112,9 @@ Path to an alternate GlobalSettings.xml file. Sökväg till en alternativ GlobalSettings.xml-fil. +Installation files are corrupted. Please reinstall FreeFileSync. +Installationsfilerna är skadade. Installera om FreeFileSync. + Cannot find the following folders: Kan inte hitta följande mappar: @@ -333,12 +336,12 @@ Aktuell: %y byte Cannot find %x. Kan inte hitta %x. -Cannot open file %x. -Kan inte öppna %x. - Cannot find device %x. Kan inte hitta enheten %x. +Cannot open file %x. +Kan inte öppna %x. + Type of item %x is not supported: Objekttyp %x stöds ej: @@ -465,6 +468,9 @@ Aktuell: %y byte Total time: Total tid: +Cleaning up old log files... +Rensar ut gamla loggfiler... + Error parsing file %x, row %y, column %z. Tolkningsfel på filen %x, rad %y, kolumn %z. @@ -653,6 +659,15 @@ Kommandot triggas om: Multiple... Flera... +Cannot write file attributes of %x. +Kan inte skriva filattribut för %x. + +%x and %y have different content. +%x och %y har olika innehåll. + +Data verification error: +Dataverifieringsfel: + Moving file %x to %y Flyttar filen %x till %y @@ -677,14 +692,8 @@ Kommandot triggas om: Updating attributes of %x Uppdaterar attribut för %x -Cannot write file attributes of %x. -Kan inte skriva filattribut för %x. - -%x and %y have different content. -%x och %y har olika innehåll. - -Data verification error: -Dataverifieringsfel: +Source item %x not found +Källobjektet %x hittades inte Creating a Volume Shadow Copy for %x... Skapar en 'Volume Shadow Copy' för %x... @@ -746,9 +755,6 @@ Kommandot triggas om: System: Shut down System: Stäng av -Cleaning up old log files... -Rensar ut gamla loggfiler... - Stopped Stoppad @@ -881,6 +887,12 @@ Kommandot triggas om: Please select a folder on a local file system, network or an MTP device. Välj en mapp på i ett lokalt filsystem, nätverk eller en MTP-enhet. +Defined by context of use + + +Requires FreeFileSync Donation Edition +Kräver FreeFileSync Donation Edition + &Save &Spara @@ -1031,6 +1043,15 @@ Kommandot triggas om: Handle daylight saving time Hantera sommartid +Performance improvements: +Prestandaförbättringar: + +Parallel file operations: +Parallella filoperationer: + +How to get best performance? +Hur får man ut bästa prestanda? + Local settings: Lokala inställningar: @@ -1147,15 +1168,6 @@ Kommandot triggas om: Directory on server: Målmapp på servern: -Performance improvements: -Prestandaförbättringar: - -How to get best performance? -Hur får man ut bästa prestanda? - -Connections for directory reading: -Anslutningar för mappläsning: - SFTP channels per connection: SFTP-kanaler per anslutning: @@ -1291,8 +1303,17 @@ Detta garanterar ett konsekvent tillstånd även vid allvarliga fel. &Default &Standard -Source code written in C++ using: -Källkod skriven i C++ med hjälp av: +Feedback and suggestions are welcome +Återkoppling och förslag är välkommna + +Home page +Hemsida + +FreeFileSync Forum +FreeFileSync Forum + +Email +E-post If you like FreeFileSync: Om du gillar FreeFileSync: @@ -1300,20 +1321,14 @@ Detta garanterar ett konsekvent tillstånd även vid allvarliga fel. Support with a donation Stöd oss med en donation -Donation details -Donationsuppgifter - The auto updater was disabled by the administrator. Automatisk uppdatering inaktiverades av administratören. -Feedback and suggestions are welcome -Återkoppling och förslag är välkommna - -Home page -Hemsida +Donation details +Donationsuppgifter -Email -E-post +Source code written in C++ using: +Källkod skriven i C++ med hjälp av: Published under the GNU General Public License Publiserad under GNU General Public License @@ -1402,9 +1417,6 @@ Detta garanterar ett konsekvent tillstånd även vid allvarliga fel. FreeFileSync %x is available! FreeFileSync %x finns tillgänglig! -Installation files are corrupted. Please reinstall FreeFileSync. -Installationsfilerna är skadade. Installera om FreeFileSync. - Local path not available for %x. Lokal sökväg ej tillgänglig för %x. @@ -1627,6 +1639,9 @@ Detta garanterar ett konsekvent tillstånd även vid allvarliga fel. Thank you, %x, for your donation and support! Tack %x, för din donation och ditt stöd! +Connections +Anslutningar + Recommended range: Rekommenderat intervall: @@ -1798,9 +1813,6 @@ Detta garanterar ett konsekvent tillstånd även vid allvarliga fel. Automatic updates: Automatiska uppdateringar: -Requires FreeFileSync Donation Edition -Kräver FreeFileSync Donation Edition - Check for Program Updates Sök efter programuppdateringar @@ -1990,6 +2002,9 @@ Detta garanterar ett konsekvent tillstånd även vid allvarliga fel. Edit with FreeFileSync Redigera med FreeFileSync +Instead of an ad, here's an animal. +Här är ett djur, istället för reklam. + The FreeFileSync portable version cannot install into a subfolder of %x. FreeFileSync portabel version kan inte installeras i en undermapp till %x. @@ -1999,3 +2014,6 @@ Detta garanterar ett konsekvent tillstånd även vid allvarliga fel. The %x installation option is only available in the FreeFileSync Donation Edition. Installationsalternativet %x är endast tillgängligt i FreeFileSync Donation Edition. +Get the Donation Edition with bonus features and help keep FreeFileSync ad-free. +Hämta donationsversionen med bonusfunktioner och hjälp till att hålla FreeFileSync reklamfri. + diff --git a/FreeFileSync/Build/Languages/turkish.lng b/FreeFileSync/Build/Languages/turkish.lng index 8a87fc93..1bdbf530 100755 --- a/FreeFileSync/Build/Languages/turkish.lng +++ b/FreeFileSync/Build/Languages/turkish.lng @@ -112,6 +112,9 @@ Path to an alternate GlobalSettings.xml file. Alternatif GlobalSettings.xml dosyasının yolu. +Installation files are corrupted. Please reinstall FreeFileSync. +Kurulum dosyaları bozulmuş. Lütfen FreeFileSync uygulamasını yeniden kurun. + Cannot find the following folders: Aşağıdaki klasörler bulunamadı: @@ -333,12 +336,12 @@ Gerçekleşen: %y bayt Cannot find %x. %x bulunamadı. -Cannot open file %x. -%x dosyası açılamadı. - Cannot find device %x. %x aygıtı bulunamadı. +Cannot open file %x. +%x dosyası açılamadı. + Type of item %x is not supported: %x ögesi türü desteklenmiyor: @@ -465,6 +468,9 @@ Gerçekleşen: %y bayt Total time: Toplam süre: +Cleaning up old log files... +Eski günlük dosyaları temizleniyor... + Error parsing file %x, row %y, column %z. %x dosyası işlenirken sorun çıktı, satır %y, sütun %z. @@ -653,6 +659,15 @@ Komut şu durumlarda yürütülür: Multiple... Çoklu... +Cannot write file attributes of %x. +%x dosya öznitelikleri yazılamadı. + +%x and %y have different content. +%x ve %y farklı içeriklere sahip. + +Data verification error: +Veri doğrulama sorunu: + Moving file %x to %y %x dosyası %y içine taşınıyor @@ -677,14 +692,8 @@ Komut şu durumlarda yürütülür: Updating attributes of %x %x öznitelikleri güncelleniyor -Cannot write file attributes of %x. -%x dosya öznitelikleri yazılamadı. - -%x and %y have different content. -%x ve %y farklı içeriklere sahip. - -Data verification error: -Veri doğrulama sorunu: +Source item %x not found +%x kaynak ögesi bulunamadı Creating a Volume Shadow Copy for %x... %x için Birim Gölge Hizmeti oluşturuluyor... @@ -746,9 +755,6 @@ Komut şu durumlarda yürütülür: System: Shut down Sistem: Kapat -Cleaning up old log files... -Eski günlük dosyaları temizleniyor... - Stopped Durduruldu @@ -881,6 +887,12 @@ Komut şu durumlarda yürütülür: Please select a folder on a local file system, network or an MTP device. Lütfen yerel dosya sistemi, ağ ya da MTP aygıtı üzerinde bulunan bir klasör seçin. +Defined by context of use + + +Requires FreeFileSync Donation Edition +FreeFileSync Donation Edition gereklidir + &Save &Kaydet @@ -1031,6 +1043,15 @@ Komut şu durumlarda yürütülür: Handle daylight saving time Yaz Saati Hakkında Bilgiler +Performance improvements: +Başarım İyileştirmeleri: + +Parallel file operations: +Paralel dosya işlemleri: + +How to get best performance? +En iyi başarım nasıl elde edilir? + Local settings: Yerel ayarlar: @@ -1147,15 +1168,6 @@ Komut şu durumlarda yürütülür: Directory on server: Sunucudaki Klasör: -Performance improvements: -Başarım İyileştirmeleri: - -How to get best performance? -En iyi başarım nasıl elde edilir? - -Connections for directory reading: -Klasör okuma bağlantıları: - SFTP channels per connection: Bir Bağlantı için SFTP Kanalı Sayısı: @@ -1291,8 +1303,17 @@ Bu yöntem, ciddi bir sorun çıkması durumunda bile işlemin tutarlı olarak y &Default &Varsayılan -Source code written in C++ using: -Kaynak kodu C++ kullanılarak yazılmıştır: +Feedback and suggestions are welcome +Öneri ve geri bildirimlerinizi bekleriz + +Home page +Ana Sayfa + +FreeFileSync Forum +FreeFileSync Forumu + +Email +E-posta If you like FreeFileSync: FreeFileSync hoşunuza gittiyse: @@ -1300,20 +1321,14 @@ Bu yöntem, ciddi bir sorun çıkması durumunda bile işlemin tutarlı olarak y Support with a donation Bağış yaparak destek olun -Donation details -Bağış Bilgileri - The auto updater was disabled by the administrator. Otomatik güncelleme yönetici tarafından devre dışı bırakılmış. -Feedback and suggestions are welcome -Öneri ve geri bildirimlerinizi bekleriz - -Home page -Ana Sayfa +Donation details +Bağış Bilgileri -Email -E-posta +Source code written in C++ using: +Kaynak kodu C++ kullanılarak yazılmıştır: Published under the GNU General Public License GNU Genel Kamu Lisansı koşulları altında yayınlanmıştır @@ -1402,9 +1417,6 @@ Bu yöntem, ciddi bir sorun çıkması durumunda bile işlemin tutarlı olarak y FreeFileSync %x is available! FreeFileSync %x sürümü yayınlanmış! -Installation files are corrupted. Please reinstall FreeFileSync. -Kurulum dosyaları bozulmuş. Lütfen FreeFileSync uygulamasını yeniden kurun. - Local path not available for %x. %x için yerel yol bulunamadı. @@ -1627,6 +1639,9 @@ Bu yöntem, ciddi bir sorun çıkması durumunda bile işlemin tutarlı olarak y Thank you, %x, for your donation and support! Sevgili %x, bağışın ve desteğin için teşekkürler! +Connections +Bağlantılar + Recommended range: Önerilen Aralık: @@ -1798,9 +1813,6 @@ Bu yöntem, ciddi bir sorun çıkması durumunda bile işlemin tutarlı olarak y Automatic updates: Otomatik güncellemeler: -Requires FreeFileSync Donation Edition -FreeFileSync Donation Edition gereklidir - Check for Program Updates Güncelleme Denetimi @@ -1958,7 +1970,7 @@ Bu yöntem, ciddi bir sorun çıkması durumunda bile işlemin tutarlı olarak y Kurulum Klasörünü Seçin: Create shortcuts: -Kısayollar Oluşturulsun: +Oluşturulacak Kısayollar: Desktop Masaüstü @@ -1990,6 +2002,9 @@ Bu yöntem, ciddi bir sorun çıkması durumunda bile işlemin tutarlı olarak y Edit with FreeFileSync FreeFileSync ile Düzenlensin +Instead of an ad, here's an animal. +Bir reklam yerine burada bir hayvan var. + The FreeFileSync portable version cannot install into a subfolder of %x. FreeFileSync taşınabilir sürümü bir %x alt klasörüne yüklenemez. @@ -1999,3 +2014,6 @@ Bu yöntem, ciddi bir sorun çıkması durumunda bile işlemin tutarlı olarak y The %x installation option is only available in the FreeFileSync Donation Edition. %x kurulumu yalnız FreeFileSync Donation Sürümü ile yapılabilir. +Get the Donation Edition with bonus features and help keep FreeFileSync ad-free. +Hediye özellikleri edinmek ve FreeFileSync yazılımını reklamsız kullanmak için Bağış Sürümünü alın. + diff --git a/FreeFileSync/Build/Languages/ukrainian.lng b/FreeFileSync/Build/Languages/ukrainian.lng index 855507d3..215d0ef7 100755 --- a/FreeFileSync/Build/Languages/ukrainian.lng +++ b/FreeFileSync/Build/Languages/ukrainian.lng @@ -7,6 +7,9 @@ n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2 +Defined by context of use + + Both sides have changed since last synchronization. З моменту останньої синхронізації з обох сторін відбулися зміни. @@ -112,6 +115,9 @@ Path to an alternate GlobalSettings.xml file. Шлях до альтернативного файлу GlobalSettings.xml. +Installation files are corrupted. Please reinstall FreeFileSync. +Файли встановлення пошкоджені. Будь ласка, перевстановіть FreeFileSync. + Cannot find the following folders: Не вдається знайти такі папки: @@ -334,12 +340,12 @@ Actual: %y bytes Cannot find %x. Не вдається знайти %x. -Cannot open file %x. -Не вдається відкрити файл %x. - Cannot find device %x. Не вдається знайти пристрій %x. +Cannot open file %x. +Не вдається відкрити файл %x. + Type of item %x is not supported: Тип елемента %x не підтримується: @@ -470,6 +476,9 @@ Actual: %y bytes Total time: Загальний час: +Cleaning up old log files... +Очистка старих журналів... + Error parsing file %x, row %y, column %z. Помилка розбору файлу %x, рядок %y, колонка %z. @@ -659,6 +668,15 @@ The command is triggered if: Multiple... Різні варіанти... +Cannot write file attributes of %x. +Не вдається записати атрибути файлу %x. + +%x and %y have different content. +%x і %y мають різний вміст. + +Data verification error: +Помилка перевірки даних: + Moving file %x to %y Переміщення файлу %x до %y @@ -683,14 +701,8 @@ The command is triggered if: Updating attributes of %x Оновлення атрибутів %x -Cannot write file attributes of %x. -Не вдається записати атрибути файлу %x. - -%x and %y have different content. -%x і %y мають різний вміст. - -Data verification error: -Помилка перевірки даних: +Source item %x not found +Вихідний елемент %x не знайдено Creating a Volume Shadow Copy for %x... Створення Тіньової Копії для %x... @@ -752,9 +764,6 @@ The command is triggered if: System: Shut down Система: Завершення роботи -Cleaning up old log files... -Очистка старих журналів... - Stopped Зупинено @@ -888,6 +897,9 @@ The command is triggered if: Please select a folder on a local file system, network or an MTP device. Будь ласка, виберіть папку на локальній файловій системі, в мережі чи на MTP пристрої. +Requires FreeFileSync Donation Edition +Потрібна FreeFileSync Donation Edition + &Save &Зберегти @@ -1038,6 +1050,15 @@ The command is triggered if: Handle daylight saving time Перехід на літній час вручну +Performance improvements: +Підвищення продуктивності: + +Parallel file operations: +Паралельні файлові операції: + +How to get best performance? +Як отримати найкращу швидкодію? + Local settings: Локальні налаштування: @@ -1154,15 +1175,6 @@ The command is triggered if: Directory on server: Папка на сервері: -Performance improvements: -Підвищення продуктивності: - -How to get best performance? -Як отримати найкращу швидкодію? - -Connections for directory reading: -З'єднань для читання папок: - SFTP channels per connection: SFTP канали на з'єднання: @@ -1298,8 +1310,17 @@ This guarantees a consistent state even in case of a serious error. &Default &За замовчуванням -Source code written in C++ using: -Код програми написаний на C++ з використанням: +Feedback and suggestions are welcome +Відгуки та пропозиції вітаються + +Home page +Домашня сторінка + +FreeFileSync Forum +Форум FreeFileSync + +Email +Пошта If you like FreeFileSync: Якщо Вам сподобався FreeFileSync: @@ -1307,20 +1328,14 @@ This guarantees a consistent state even in case of a serious error. Support with a donation Підтримати пожертвуванням. -Donation details -Докладно про пожертвування - The auto updater was disabled by the administrator. Автоматичне оновлення було відключене адміністратором. -Feedback and suggestions are welcome -Відгуки та пропозиції вітаються - -Home page -Домашня сторінка +Donation details +Докладно про пожертвування -Email -Пошта +Source code written in C++ using: +Код програми написаний на C++ з використанням: Published under the GNU General Public License Видано за ліцензією GNU General Public License @@ -1409,9 +1424,6 @@ This guarantees a consistent state even in case of a serious error. FreeFileSync %x is available! FreeFileSync %x доступний! -Installation files are corrupted. Please reinstall FreeFileSync. -Файли встановлення пошкоджені. Будь ласка, перевстановіть FreeFileSync. - Local path not available for %x. Локальний шлях не доступний для %x. @@ -1638,6 +1650,9 @@ This guarantees a consistent state even in case of a serious error. Thank you, %x, for your donation and support! Дякуємо Вам, %x, за ваше пожертвування та підтримку! +Connections +З'єднання + Recommended range: Рекомендований діапазон: @@ -1812,9 +1827,6 @@ This guarantees a consistent state even in case of a serious error. Automatic updates: Автоматичні оновлення: -Requires FreeFileSync Donation Edition -Потрібна FreeFileSync Donation Edition - Check for Program Updates Перевірка Оновлень Програми @@ -2006,6 +2018,9 @@ This guarantees a consistent state even in case of a serious error. Edit with FreeFileSync Редагувати за допомогою FreeFileSync +Instead of an ad, here's an animal. +Замість реклами, ось тварина. + The FreeFileSync portable version cannot install into a subfolder of %x. Портативна версія FreeFileSync не може бути встановлена в підпапку %x. @@ -2015,3 +2030,6 @@ This guarantees a consistent state even in case of a serious error. The %x installation option is only available in the FreeFileSync Donation Edition. Варіант установки %x доступний тільки у FreeFileSync Donation Edition. +Get the Donation Edition with bonus features and help keep FreeFileSync ad-free. +Отримайте Donation Edition з бонусними функціями та допоможіть зберегти FreeFileSync без реклами. + diff --git a/FreeFileSync/Build/Resources.zip b/FreeFileSync/Build/Resources.zip index 28e925b0..a1d93d01 100755 Binary files a/FreeFileSync/Build/Resources.zip and b/FreeFileSync/Build/Resources.zip differ diff --git a/FreeFileSync/Source/Makefile b/FreeFileSync/Source/Makefile index 33197267..dffa2308 100755 --- a/FreeFileSync/Source/Makefile +++ b/FreeFileSync/Source/Makefile @@ -5,9 +5,11 @@ SHAREDIR = $(DESTDIR)$(prefix)/share APPSHAREDIR = $(SHAREDIR)/$(APPNAME) DOCSHAREDIR = $(SHAREDIR)/doc/$(APPNAME) -CXXFLAGS = -std=c++14 -pipe -DWXINTL_NO_GETTEXT_MACRO -I../.. -I../../zenXml -include "zen/i18n.h" -Wall -O3 -DNDEBUG `wx-config --cxxflags --debug=no` -pthread +CXXFLAGS = -std=c++17 -pipe -DWXINTL_NO_GETTEXT_MACRO -I../.. -I../../zenXml -I../../boost -include "zen/i18n.h" -include "zen/warn_static.h" \ +-Wall -Wfatal-errors -Winit-self -Wmissing-include-dirs -Wswitch-enum -Wmain -Wnon-virtual-dtor -Wcast-align -Wshadow -Wno-deprecated-declarations \ +-O3 -DNDEBUG `wx-config --cxxflags --debug=no` -pthread -LINKFLAGS = -s `wx-config --libs std, aui --debug=no` -lboost_thread -lboost_chrono -lboost_system -lz -pthread +LINKFLAGS = -s -no-pie `wx-config --libs std, aui --debug=no` -pthread #Gtk - support recycler/icon loading/no button border/grid scrolling CXXFLAGS += `pkg-config --cflags gtk+-2.0` @@ -27,87 +29,86 @@ CXXFLAGS += `pkg-config --cflags unity` -DHAVE_UBUNTU_UNITY LINKFLAGS += `pkg-config --libs unity` endif -CPP_LIST= -CPP_LIST+=algorithm.cpp -CPP_LIST+=application.cpp -CPP_LIST+=comparison.cpp -CPP_LIST+=structures.cpp -CPP_LIST+=synchronization.cpp -CPP_LIST+=fs/abstract.cpp -CPP_LIST+=fs/concrete.cpp -CPP_LIST+=fs/native.cpp -CPP_LIST+=file_hierarchy.cpp -CPP_LIST+=ui/cfg_grid.cpp -CPP_LIST+=ui/file_grid.cpp -CPP_LIST+=ui/folder_history_box.cpp -CPP_LIST+=ui/command_box.cpp -CPP_LIST+=ui/folder_selector.cpp -CPP_LIST+=ui/batch_config.cpp -CPP_LIST+=ui/batch_status_handler.cpp -CPP_LIST+=ui/version_check.cpp -CPP_LIST+=ui/file_view.cpp -CPP_LIST+=ui/tree_grid.cpp -CPP_LIST+=ui/gui_generated.cpp -CPP_LIST+=ui/gui_status_handler.cpp -CPP_LIST+=ui/main_dlg.cpp -CPP_LIST+=ui/progress_indicator.cpp -CPP_LIST+=ui/search.cpp -CPP_LIST+=ui/small_dlgs.cpp -CPP_LIST+=ui/sync_cfg.cpp -CPP_LIST+=ui/taskbar.cpp -CPP_LIST+=ui/triple_splitter.cpp -CPP_LIST+=ui/tray_icon.cpp -CPP_LIST+=lib/binary.cpp -CPP_LIST+=lib/db_file.cpp -CPP_LIST+=lib/dir_lock.cpp -CPP_LIST+=lib/hard_filter.cpp -CPP_LIST+=lib/icon_buffer.cpp -CPP_LIST+=lib/icon_loader.cpp -CPP_LIST+=lib/localization.cpp -CPP_LIST+=lib/parallel_scan.cpp -CPP_LIST+=lib/process_xml.cpp -CPP_LIST+=lib/resolve_path.cpp -CPP_LIST+=lib/perf_check.cpp -CPP_LIST+=lib/status_handler.cpp -CPP_LIST+=lib/versioning.cpp -CPP_LIST+=lib/ffs_paths.cpp -CPP_LIST+=../../zen/xml_io.cpp -CPP_LIST+=../../zen/recycler.cpp -CPP_LIST+=../../zen/file_access.cpp -CPP_LIST+=../../zen/file_io.cpp -CPP_LIST+=../../zen/file_traverser.cpp -CPP_LIST+=../../zen/zstring.cpp -CPP_LIST+=../../zen/format_unit.cpp -CPP_LIST+=../../zen/process_priority.cpp -CPP_LIST+=../../zen/shutdown.cpp -CPP_LIST+=../../wx+/file_drop.cpp -CPP_LIST+=../../wx+/grid.cpp -CPP_LIST+=../../wx+/image_tools.cpp -CPP_LIST+=../../wx+/graph.cpp -CPP_LIST+=../../wx+/tooltip.cpp -CPP_LIST+=../../wx+/http.cpp -CPP_LIST+=../../wx+/image_resources.cpp -CPP_LIST+=../../wx+/popup_dlg.cpp -CPP_LIST+=../../wx+/popup_dlg_generated.cpp -CPP_LIST+=../../wx+/zlib_wrap.cpp +CPP_FILES= +CPP_FILES+=algorithm.cpp +CPP_FILES+=application.cpp +CPP_FILES+=comparison.cpp +CPP_FILES+=structures.cpp +CPP_FILES+=synchronization.cpp +CPP_FILES+=fs/abstract.cpp +CPP_FILES+=fs/concrete.cpp +CPP_FILES+=fs/native.cpp +CPP_FILES+=file_hierarchy.cpp +CPP_FILES+=ui/batch_config.cpp +CPP_FILES+=ui/batch_status_handler.cpp +CPP_FILES+=ui/cfg_grid.cpp +CPP_FILES+=ui/command_box.cpp +CPP_FILES+=ui/folder_history_box.cpp +CPP_FILES+=ui/folder_selector.cpp +CPP_FILES+=ui/file_grid.cpp +CPP_FILES+=ui/file_view.cpp +CPP_FILES+=ui/tree_grid.cpp +CPP_FILES+=ui/gui_generated.cpp +CPP_FILES+=ui/gui_status_handler.cpp +CPP_FILES+=ui/main_dlg.cpp +CPP_FILES+=ui/progress_indicator.cpp +CPP_FILES+=ui/search.cpp +CPP_FILES+=ui/small_dlgs.cpp +CPP_FILES+=ui/sync_cfg.cpp +CPP_FILES+=ui/taskbar.cpp +CPP_FILES+=ui/tray_icon.cpp +CPP_FILES+=ui/triple_splitter.cpp +CPP_FILES+=ui/version_check.cpp +CPP_FILES+=lib/binary.cpp +CPP_FILES+=lib/db_file.cpp +CPP_FILES+=lib/dir_lock.cpp +CPP_FILES+=lib/ffs_paths.cpp +CPP_FILES+=lib/generate_logfile.cpp +CPP_FILES+=lib/hard_filter.cpp +CPP_FILES+=lib/icon_buffer.cpp +CPP_FILES+=lib/icon_loader.cpp +CPP_FILES+=lib/localization.cpp +CPP_FILES+=lib/parallel_scan.cpp +CPP_FILES+=lib/process_xml.cpp +CPP_FILES+=lib/resolve_path.cpp +CPP_FILES+=lib/perf_check.cpp +CPP_FILES+=lib/status_handler.cpp +CPP_FILES+=lib/versioning.cpp +CPP_FILES+=../../zen/xml_io.cpp +CPP_FILES+=../../zen/recycler.cpp +CPP_FILES+=../../zen/file_access.cpp +CPP_FILES+=../../zen/file_io.cpp +CPP_FILES+=../../zen/file_traverser.cpp +CPP_FILES+=../../zen/zstring.cpp +CPP_FILES+=../../zen/format_unit.cpp +CPP_FILES+=../../zen/process_priority.cpp +CPP_FILES+=../../zen/shutdown.cpp +CPP_FILES+=../../wx+/file_drop.cpp +CPP_FILES+=../../wx+/grid.cpp +CPP_FILES+=../../wx+/image_tools.cpp +CPP_FILES+=../../wx+/graph.cpp +CPP_FILES+=../../wx+/http.cpp +CPP_FILES+=../../wx+/tooltip.cpp +CPP_FILES+=../../wx+/image_resources.cpp +CPP_FILES+=../../wx+/popup_dlg.cpp +CPP_FILES+=../../wx+/popup_dlg_generated.cpp +CPP_FILES+=../../wx+/zlib_wrap.cpp +CPP_FILES+=../../xBRZ/src/xbrz.cpp -OBJECT_LIST = $(CPP_LIST:%.cpp=../Obj/FFS_GCC_Make_Release/ffs/src/%.o) +OBJECT_LIST = $(CPP_FILES:%.cpp=../Obj/FFS_GCC_Make_Release/ffs/src/%.o) -all: launchpad +all: ../Build/$(APPNAME) -launchpad: FreeFileSync +../Build/$(APPNAME): $(OBJECT_LIST) + g++ -o $@ $^ $(LINKFLAGS) ../Obj/FFS_GCC_Make_Release/ffs/src/%.o : %.cpp mkdir -p $(dir $@) g++ $(CXXFLAGS) -c $< -o $@ -FreeFileSync: $(OBJECT_LIST) - g++ -o ../Build/$(APPNAME) $(OBJECT_LIST) $(LINKFLAGS) - clean: rm -rf ../Obj/FFS_GCC_Make_Release rm -f ../Build/$(APPNAME) - rm -f ../../wx+/pch.h.gch install: mkdir -p $(BINDIR) diff --git a/FreeFileSync/Source/RealTimeSync/Makefile b/FreeFileSync/Source/RealTimeSync/Makefile index baf4ef62..e4915afa 100755 --- a/FreeFileSync/Source/RealTimeSync/Makefile +++ b/FreeFileSync/Source/RealTimeSync/Makefile @@ -2,58 +2,55 @@ APPNAME = RealTimeSync prefix = /usr BINDIR = $(DESTDIR)$(prefix)/bin -CXXFLAGS = -std=c++14 -pipe -DWXINTL_NO_GETTEXT_MACRO -I../../.. -I../../../zenXml -include "zen/i18n.h" -Wall -O3 -DNDEBUG `wx-config --cxxflags --debug=no` -pthread +CXXFLAGS = -std=c++17 -pipe -DWXINTL_NO_GETTEXT_MACRO -I../../.. -I../../../zenXml -I../../../boost -include "zen/i18n.h" -include "zen/warn_static.h" \ +-Wall -Wfatal-errors -Winit-self -Wmissing-include-dirs -Wswitch-enum -Wmain -Wnon-virtual-dtor -Wcast-align -Wshadow -Wno-deprecated-declarations \ +-O3 -DNDEBUG `wx-config --cxxflags --debug=no` -pthread -LINKFLAGS = -s `wx-config --libs std, aui --debug=no` -lboost_thread -lboost_chrono -lboost_system -lz -pthread +LINKFLAGS = -s -no-pie `wx-config --libs std, aui --debug=no` -pthread #Gtk - support "no button border" CXXFLAGS += `pkg-config --cflags gtk+-2.0` LINKFLAGS += `pkg-config --libs gtk+-2.0` -CPP_LIST= -CPP_LIST+=application.cpp -CPP_LIST+=gui_generated.cpp -CPP_LIST+=main_dlg.cpp -CPP_LIST+=tray_menu.cpp -CPP_LIST+=monitor.cpp -CPP_LIST+=xml_proc.cpp -CPP_LIST+=folder_selector2.cpp -CPP_LIST+=../structures.cpp -CPP_LIST+=../lib/localization.cpp -CPP_LIST+=../lib/process_xml.cpp -CPP_LIST+=../lib/resolve_path.cpp -CPP_LIST+=../lib/ffs_paths.cpp -CPP_LIST+=../lib/hard_filter.cpp -CPP_LIST+=../../../zen/xml_io.cpp -CPP_LIST+=../../../zen/dir_watcher.cpp -CPP_LIST+=../../../zen/file_access.cpp -CPP_LIST+=../../../zen/file_io.cpp -CPP_LIST+=../../../zen/file_traverser.cpp -CPP_LIST+=../../../zen/zstring.cpp -CPP_LIST+=../../../zen/format_unit.cpp -CPP_LIST+=../../../wx+/file_drop.cpp -CPP_LIST+=../../../wx+/image_tools.cpp -CPP_LIST+=../../../wx+/image_resources.cpp -CPP_LIST+=../../../wx+/popup_dlg.cpp -CPP_LIST+=../../../wx+/popup_dlg_generated.cpp - -OBJECT_LIST=$(CPP_LIST:%.cpp=../../Obj/RTS_GCC_Make_Release/ffs/src/rts/%.o) - -all: launchpad - -launchpad: RealTimeSync +CPP_FILES= +CPP_FILES+=application.cpp +CPP_FILES+=gui_generated.cpp +CPP_FILES+=main_dlg.cpp +CPP_FILES+=tray_menu.cpp +CPP_FILES+=monitor.cpp +CPP_FILES+=xml_proc.cpp +CPP_FILES+=folder_selector2.cpp +CPP_FILES+=../lib/localization.cpp +CPP_FILES+=../lib/resolve_path.cpp +CPP_FILES+=../lib/ffs_paths.cpp +CPP_FILES+=../../../zen/xml_io.cpp +CPP_FILES+=../../../zen/dir_watcher.cpp +CPP_FILES+=../../../zen/file_access.cpp +CPP_FILES+=../../../zen/file_io.cpp +CPP_FILES+=../../../zen/file_traverser.cpp +CPP_FILES+=../../../zen/zstring.cpp +CPP_FILES+=../../../zen/format_unit.cpp +CPP_FILES+=../../../wx+/file_drop.cpp +CPP_FILES+=../../../wx+/image_tools.cpp +CPP_FILES+=../../../wx+/image_resources.cpp +CPP_FILES+=../../../wx+/popup_dlg.cpp +CPP_FILES+=../../../wx+/popup_dlg_generated.cpp +CPP_FILES+=../../../xBRZ/src/xbrz.cpp + +OBJECT_LIST=$(CPP_FILES:%.cpp=../../Obj/RTS_GCC_Make_Release/ffs/src/rts/%.o) + +all: ../../Build/$(APPNAME) + +../../Build/$(APPNAME): $(OBJECT_LIST) + g++ -o $@ $^ $(LINKFLAGS) ../../Obj/RTS_GCC_Make_Release/ffs/src/rts/%.o : %.cpp mkdir -p $(dir $@) g++ $(CXXFLAGS) -c $< -o $@ -RealTimeSync: $(OBJECT_LIST) - g++ -o ../../Build/$(APPNAME) $(OBJECT_LIST) $(LINKFLAGS) - clean: rm -rf ../../Obj/RTS_GCC_Make_Release rm -f ../../Build/$(APPNAME) - rm -f ../../../wx+/pch.h.gch install: mkdir -p $(BINDIR) diff --git a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp index 3f009a1a..67fdde0a 100755 --- a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp +++ b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp @@ -33,6 +33,14 @@ namespace static const size_t MAX_ADD_FOLDERS = 6; +std::wstring extractJobName(const Zstring& cfgFilePath) +{ + const Zstring shortName = afterLast(cfgFilePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); + const Zstring jobName = beforeLast(shortName, Zstr('.'), IF_MISSING_RETURN_ALL); + return utfTo(jobName); +} + + } @@ -207,7 +215,7 @@ void MainDialog::OnStart(wxCommandEvent& event) XmlRealConfig currentCfg = getConfiguration(); const Zstring activeCfgFilePath = !equalFilePath(activeConfigFile_, lastRunConfigPath_) ? activeConfigFile_ : Zstring(); - switch (rts::startDirectoryMonitor(currentCfg, fff::extractJobName(activeCfgFilePath))) + switch (rts::startDirectoryMonitor(currentCfg, ::extractJobName(activeCfgFilePath))) { case rts::EXIT_APP: Close(); diff --git a/FreeFileSync/Source/RealTimeSync/xml_proc.cpp b/FreeFileSync/Source/RealTimeSync/xml_proc.cpp index 9d022b5d..f4762540 100755 --- a/FreeFileSync/Source/RealTimeSync/xml_proc.cpp +++ b/FreeFileSync/Source/RealTimeSync/xml_proc.cpp @@ -6,66 +6,95 @@ #include "xml_proc.h" #include -#include -#include "../lib/process_xml.h" +#include #include "../lib/ffs_paths.h" +#include "../lib/localization.h" using namespace zen; using namespace rts; -namespace +namespace zen { -void readConfig(const XmlIn& in, XmlRealConfig& config) +template <> inline +bool readText(const std::string& input, wxLanguage& value) { - in["Directories"](config.directories); - in["Delay" ](config.delay); - in["Commandline"](config.commandline); + if (const wxLanguageInfo* lngInfo = wxLocale::FindLanguageInfo(utfTo(input))) + { + value = static_cast(lngInfo->Language); + return true; + } + return false; +} } -bool isXmlTypeRTS(const XmlDoc& doc) //throw() +namespace +{ +enum class RtsXmlType +{ + REAL, + BATCH, + GLOBAL, + OTHER +}; +RtsXmlType getXmlTypeNoThrow(const XmlDoc& doc) //throw() { if (doc.root().getNameAs() == "FreeFileSync") { std::string type; if (doc.root().getAttribute("XmlType", type)) - return type == "REAL"; + { + if (type == "REAL") + return RtsXmlType::REAL; + else if (type == "BATCH") + return RtsXmlType::BATCH; + else if (type == "GLOBAL") + return RtsXmlType::GLOBAL; + } } - return false; + return RtsXmlType::OTHER; } + + +void readConfig(const XmlIn& in, XmlRealConfig& config) +{ + in["Directories"](config.directories); + in["Delay" ](config.delay); + in["Commandline"](config.commandline); +} + +void writeConfig(const XmlRealConfig& config, XmlOut& out) +{ + out["Directories"](config.directories); + out["Delay" ](config.delay); + out["Commandline"](config.commandline); } -void rts::readConfig(const Zstring& filepath, XmlRealConfig& config, std::wstring& warningMsg) //throw FileError +template +void readConfig(const Zstring& filePath, RtsXmlType type, ConfigType& cfg, std::wstring& warningMsg) //throw FileError { - XmlDoc doc = loadXmlDocument(filepath); //throw FileError + XmlDoc doc = loadXmlDocument(filePath); //throw FileError - if (!isXmlTypeRTS(doc)) - throw FileError(replaceCpy(_("File %x does not contain a valid configuration."), L"%x", fmtPath(filepath))); + if (getXmlTypeNoThrow(doc) != type) //noexcept + throw FileError(replaceCpy(_("File %x does not contain a valid configuration."), L"%x", fmtPath(filePath))); XmlIn in(doc); - ::readConfig(in, config); + ::readConfig(in, cfg); try { - checkForMappingErrors(in, filepath); //throw FileError - } - catch (const FileError& e) - { - warningMsg = e.toString(); + checkForMappingErrors(in, filePath); //throw FileError } + catch (const FileError& e) { warningMsg = e.toString(); } +} } -namespace +void rts::readConfig(const Zstring& filePath, XmlRealConfig& config, std::wstring& warningMsg) //throw FileError { -void writeConfig(const XmlRealConfig& config, XmlOut& out) -{ - out["Directories"](config.directories); - out["Delay" ](config.delay); - out["Commandline"](config.commandline); -} + ::readConfig(filePath, RtsXmlType::REAL, config, warningMsg); //throw FileError } @@ -81,56 +110,70 @@ void rts::writeConfig(const XmlRealConfig& config, const Zstring& filepath) //th } -namespace -{ -XmlRealConfig convertBatchToReal(const fff::XmlBatchConfig& batchCfg, const Zstring& batchFilePath) +void rts::readRealOrBatchConfig(const Zstring& filePath, XmlRealConfig& config, std::wstring& warningMsg) //throw FileError { - std::set uniqueFolders; + //do NOT use zen::loadStream as it will needlessly load even huge files! + XmlDoc doc = loadXmlDocument(filePath); //throw FileError; quick exit if file is not an FFS XML - //add main folders - uniqueFolders.insert(batchCfg.mainCfg.firstPair.folderPathPhraseLeft); - uniqueFolders.insert(batchCfg.mainCfg.firstPair.folderPathPhraseRight); + const RtsXmlType xmlType = ::getXmlTypeNoThrow(doc); - //additional folders - for (const fff::LocalPairConfig& lpc : batchCfg.mainCfg.additionalPairs) + //convert batch config to RealTimeSync config + if (xmlType == RtsXmlType::BATCH) { - uniqueFolders.insert(lpc.folderPathPhraseLeft); - uniqueFolders.insert(lpc.folderPathPhraseRight); - } + XmlIn in(doc); - erase_if(uniqueFolders, [](const Zstring& str) { return trimCpy(str).empty(); }); + //read folder pairs + std::set uniqueFolders; - XmlRealConfig output; - output.directories.assign(uniqueFolders.begin(), uniqueFolders.end()); - output.commandline = Zstr("\"") + fff::getFreeFileSyncLauncherPath() + Zstr("\" \"") + batchFilePath + Zstr("\""); - return output; -} -} + for (XmlIn inPair = in["FolderPairs"]["Pair"]; inPair; inPair.next()) + { + Zstring folderPathPhraseLeft; + Zstring folderPathPhraseRight; + inPair["Left" ](folderPathPhraseLeft); + inPair["Right"](folderPathPhraseRight); + uniqueFolders.insert(folderPathPhraseLeft); + uniqueFolders.insert(folderPathPhraseRight); + } -void rts::readRealOrBatchConfig(const Zstring& filepath, XmlRealConfig& config, std::wstring& warningMsg) //throw FileError -{ - if (fff::getXmlType(filepath) != fff::XML_TYPE_BATCH) //throw FileError - return readConfig(filepath, config, warningMsg); //throw FileError + //don't consider failure a warning only: + checkForMappingErrors(in, filePath); //throw FileError - //convert batch config to RealTimeSync config - fff::XmlBatchConfig batchCfg; - readConfig(filepath, batchCfg, warningMsg); //throw FileError - //<- redirect batch config warnings + //--------------------------------------------------------------------------------------- - config = convertBatchToReal(batchCfg, filepath); + erase_if(uniqueFolders, [](const Zstring& str) { return trimCpy(str).empty(); }); + config.directories.assign(uniqueFolders.begin(), uniqueFolders.end()); + config.commandline = Zstr("\"") + fff::getFreeFileSyncLauncherPath() + Zstr("\" \"") + filePath + Zstr("\""); + } + else + return readConfig(filePath, config, warningMsg); //throw FileError } -wxLanguage rts::getProgramLanguage() +wxLanguage rts::getProgramLanguage() //throw FileError { - fff::XmlGlobalSettings settings; - std::wstring warningMsg; + const Zstring& filePath = fff::getConfigDirPathPf() + Zstr("GlobalSettings.xml"); + + XmlDoc doc; try { - fff::readConfig(fff::getGlobalConfigFile(), settings, warningMsg); //throw FileError + doc = loadXmlDocument(filePath); //throw FileError } - catch (const FileError&) {} //use default language if error occurred + catch (FileError&) + { + if (!itemNotExisting(filePath)) //existing or access error + throw; + return fff::getSystemLanguage(); + } + + if (getXmlTypeNoThrow(doc) != RtsXmlType::GLOBAL) //noexcept + throw FileError(replaceCpy(_("File %x does not contain a valid configuration."), L"%x", fmtPath(filePath))); + + XmlIn in(doc); + + wxLanguage lng = wxLANGUAGE_UNKNOWN; + in["General"]["Language"].attribute("Name", lng); - return settings.programLanguage; + checkForMappingErrors(in, filePath); //throw FileError + return lng; } diff --git a/FreeFileSync/Source/RealTimeSync/xml_proc.h b/FreeFileSync/Source/RealTimeSync/xml_proc.h index afbd010c..305ca76d 100755 --- a/FreeFileSync/Source/RealTimeSync/xml_proc.h +++ b/FreeFileSync/Source/RealTimeSync/xml_proc.h @@ -21,14 +21,14 @@ struct XmlRealConfig unsigned int delay = 10; }; -void readConfig(const Zstring& filepath, XmlRealConfig& config, std::wstring& warningMsg); //throw FileError +void readConfig(const Zstring& filePath, XmlRealConfig& config, std::wstring& warningMsg); //throw FileError void writeConfig(const XmlRealConfig& config, const Zstring& filepath); //throw FileError //reuse (some of) FreeFileSync's xml files -void readRealOrBatchConfig(const Zstring& filepath, XmlRealConfig& config, std::wstring& warningMsg); //throw FileError +void readRealOrBatchConfig(const Zstring& filePath, XmlRealConfig& config, std::wstring& warningMsg); //throw FileError -wxLanguage getProgramLanguage(); +wxLanguage getProgramLanguage(); //throw FileError } #endif //XML_PROC_H_0813748158321813490 diff --git a/FreeFileSync/Source/algorithm.cpp b/FreeFileSync/Source/algorithm.cpp index faab26af..a7d21ccf 100755 --- a/FreeFileSync/Source/algorithm.cpp +++ b/FreeFileSync/Source/algorithm.cpp @@ -1130,7 +1130,7 @@ Opt fff::getPathDependency(const AbstractPath& basePathL, const { const AFS::PathComponents compL = AFS::getPathComponents(basePathL); const AFS::PathComponents compR = AFS::getPathComponents(basePathR); - if (AFS::compareAbstractPath(compL.rootPath, compR.rootPath) == 0) + if (compL.rootPath == compR.rootPath) { const bool leftParent = compL.relPath.size() <= compR.relPath.size(); @@ -1205,7 +1205,7 @@ void copyToAlternateFolderFrom(const std::vector& rowsT const std::wstring txtCreatingFolder(_("Creating folder %x" )); const std::wstring txtCreatingLink (_("Creating symbolic link %x")); - auto copyItem = [overwriteIfExists](const AbstractPath& targetPath, //throw FileError + auto copyItem = [overwriteIfExists](const AbstractPath& targetPath, ItemStatReporter& statReporter, //throw FileError const std::function& deleteTargetItem)>& copyItemPlain) //throw FileError { //start deleting existing target as required by copyFileTransactional(): @@ -1225,27 +1225,28 @@ void copyToAlternateFolderFrom(const std::vector& rowsT } catch (FileError&) { - Opt pd; - try { pd = AFS::getPathStatus(targetPath); /*throw FileError*/ } - catch (FileError&) {} //previous exception is more relevant + const AFS::PathStatus ps = AFS::getPathStatus(targetPath); //throw FileError - if (pd) + if (ps.relPath.empty()) //already existing { - if (pd->relPath.empty()) //already existing + if (deletionError) + throw* deletionError; + } + else if (ps.relPath.size() > 1) //parent folder missing + { + AbstractPath intermediatePath = ps.existingPath; + for (const Zstring& itemName : std::vector(ps.relPath.begin(), ps.relPath.end() - 1)) { - if (deletionError) - throw* deletionError; + AFS::createFolderPlain(intermediatePath = AFS::appendRelPath(intermediatePath, itemName)); //throw FileError + statReporter.reportDelta(1, 0); } - else if (pd->relPath.size() > 1) //parent folder missing - { - AbstractPath intermediatePath = pd->existingPath; - for (const Zstring& itemName : std::vector(pd->relPath.begin(), pd->relPath.end() - 1)) - AFS::createFolderPlain(intermediatePath = AFS::appendRelPath(intermediatePath, itemName)); //throw FileError + //potential future issue when adding multithreading support: intermediate folders might already exist + //potential future issue 2: folder created by parallel thread just after failure => ps->relPath.size() == 1, but need retry! + //see abstract.cpp; AFS::createFolderIfMissingRecursion() - //retry: - copyItemPlain(nullptr /*deleteTargetItem*/); //throw FileError - return; - } + //retry: + copyItemPlain(nullptr /*deleteTargetItem*/); //throw FileError + return; } throw; } @@ -1260,7 +1261,7 @@ void copyToAlternateFolderFrom(const std::vector& rowsT visitFSObject(*fsObj, [&](const FolderPair& folder) { - StatisticsReporter statReporter(1, 0, callback); + ItemStatReporter statReporter(1, 0, callback); notifyItemCopy(txtCreatingFolder, AFS::getDisplayPath(targetPath)); try { @@ -1270,40 +1271,36 @@ void copyToAlternateFolderFrom(const std::vector& rowsT } catch (FileError&) { - Opt pd; - try { pd = AFS::getPathStatus(targetPath); /*throw FileError*/ } - catch (FileError&) {} //previous exception is more relevant + const AFS::PathStatus ps = AFS::getPathStatus(targetPath); //throw FileError + if (ps.existingType == AFS::ItemType::FILE) + throw; - if (pd) - { - if (pd->relPath.empty()) //already existing - { - if (pd->existingType != AFS::ItemType::FILE) - return; //folder might already exist: see creation of intermediate directories below - } - else if (pd->relPath.size() > 1) //parent folder missing - { - AbstractPath intermediatePath = pd->existingPath; - for (const Zstring& itemName : pd->relPath) - AFS::createFolderPlain(intermediatePath = AFS::appendRelPath(intermediatePath, itemName)); //throw FileError + if (ps.relPath.size() == 1) //don't repeat the very same createFolderPlain() call from above! + throw; - statReporter.reportDelta(1, 0); - return; - } + //folder might already exist: see creation of intermediate directories below + + AbstractPath intermediatePath = ps.existingPath; + for (const Zstring& itemName : ps.relPath) + { + AFS::createFolderPlain(intermediatePath = AFS::appendRelPath(intermediatePath, itemName)); //throw FileError + statReporter.reportDelta(1, 0); } - throw; + //potential future issue when adding multithreading support: intermediate folders might already exist + //potential future issue 2: parent folder created by parallel thread just after failure => ps->relPath.size() == 1, but need retry! + //see abstract.cpp; AFS::createFolderIfMissingRecursion() } }, [&](const FilePair& file) { - StatisticsReporter statReporter(1, file.getFileSize(), callback); + ItemStatReporter statReporter(1, file.getFileSize(), callback); notifyItemCopy(txtCreatingFile, AFS::getDisplayPath(targetPath)); const FileAttributes attr = file.getAttributes(); const AFS::StreamAttributes sourceAttr{ attr.modTime, attr.fileSize, attr.fileId }; - copyItem(targetPath, [&](const std::function& deleteTargetItem) //throw FileError + copyItem(targetPath, statReporter, [&](const std::function& deleteTargetItem) //throw FileError { auto notifyUnbufferedIO = [&](int64_t bytesDelta) { statReporter.reportDelta(0, bytesDelta); }; /*const AFS::FileCopyResult result =*/ AFS::copyFileTransactional(sourcePath, sourceAttr, targetPath, //throw FileError, ErrorFileLocked @@ -1315,17 +1312,17 @@ void copyToAlternateFolderFrom(const std::vector& rowsT [&](const SymlinkPair& symlink) { - StatisticsReporter statReporter(1, 0, callback); + ItemStatReporter statReporter(1, 0, callback); notifyItemCopy(txtCreatingLink, AFS::getDisplayPath(targetPath)); - copyItem(targetPath, [&](const std::function& deleteTargetItem) //throw FileError + copyItem(targetPath, statReporter, [&](const std::function& deleteTargetItem) //throw FileError { deleteTargetItem(); //throw FileError AFS::copySymlink(sourcePath, targetPath, false /*copyFilePermissions*/); //throw FileError }); statReporter.reportDelta(1, 0); }); - }, callback); //throw X? + }, callback); //throw X } } @@ -1399,7 +1396,7 @@ void deleteFromGridAndHDOneSide(std::vector& rowsToDelete, for (FileSystemObject* fsObj : rowsToDelete) //all pointers are required(!) to be bound tryReportingError([&] { - StatisticsReporter statReporter(1, 0, callback); + ItemStatReporter statReporter(1, 0, callback); if (!fsObj->isEmpty()) //element may be implicitly deleted, e.g. if parent folder was deleted first { @@ -1453,7 +1450,7 @@ void deleteFromGridAndHDOneSide(std::vector& rowsToDelete, fsObj->removeObject(); //if directory: removes recursively! } - }, callback); //throw X? + }, callback); //throw X } @@ -1462,7 +1459,7 @@ void categorize(const std::vector& rows, std::vector& deletePermanent, std::vector& deleteRecyler, bool useRecycleBin, - std::map& recyclerSupported, + std::map& recyclerSupported, ProcessCallback& callback) { auto hasRecycler = [&](const AbstractPath& baseFolderPath) -> bool @@ -1476,7 +1473,7 @@ void categorize(const std::vector& rows, bool recSupported = false; tryReportingError([&]{ recSupported = AFS::supportsRecycleBin(baseFolderPath, [&] { callback.reportStatus(msg); /*may throw*/ }); //throw FileError - }, callback); //throw X? + }, callback); //throw X recyclerSupported.emplace(baseFolderPath, recSupported); return recSupported; @@ -1568,7 +1565,7 @@ void fff::deleteFromGridAndHD(const std::vector& rowsToDelete std::vector deleteRecylerLeft; std::vector deleteRecylerRight; - std::map recyclerSupported; + std::map recyclerSupported; categorize< LEFT_SIDE>(deleteLeft, deletePermanentLeft, deleteRecylerLeft, useRecycleBin, recyclerSupported, callback); categorize(deleteRight, deletePermanentRight, deleteRecylerRight, useRecycleBin, recyclerSupported, callback); @@ -1608,7 +1605,7 @@ bool fff::operator<(const FileDescriptor& lhs, const FileDescriptor& rhs) if (lhs.attr.isFollowedSymlink != rhs.attr.isFollowedSymlink) return lhs.attr.isFollowedSymlink < rhs.attr.isFollowedSymlink; - return AFS::LessAbstractPath()(lhs.path, rhs.path); + return lhs.path < rhs.path; } @@ -1659,7 +1656,7 @@ void TempFileBuffer::createTempFiles(const std::set& workLoad, P createDirectoryIfMissingRecursion(tempPathTmp); //throw FileError tempFolderPath_ = tempPathTmp; - }, callback); //throw X? + }, callback); //throw X if (errMsg) return; } @@ -1687,7 +1684,7 @@ void TempFileBuffer::createTempFiles(const std::set& workLoad, P tryReportingError([&] { - StatisticsReporter statReporter(1, descr.attr.fileSize, callback); + ItemStatReporter statReporter(1, descr.attr.fileSize, callback); callback.reportInfo(replaceCpy(_("Creating file %x"), L"%x", fmtPath(tempFilePath))); @@ -1701,6 +1698,6 @@ void TempFileBuffer::createTempFiles(const std::set& workLoad, P statReporter.reportDelta(1, 0); tempFilePaths_[descr] = tempFilePath; - }, callback); //throw X? + }, callback); //throw X } } diff --git a/FreeFileSync/Source/algorithm.h b/FreeFileSync/Source/algorithm.h index 719d0f9b..f3a81e12 100755 --- a/FreeFileSync/Source/algorithm.h +++ b/FreeFileSync/Source/algorithm.h @@ -46,7 +46,7 @@ struct PathDependency { AbstractPath basePathParent; AbstractPath basePathChild; - Zstring relPath; //filled if child path is sub folder of parent path; empty if child path == parent path + Zstring relPath; //filled if child path is subfolder of parent path; empty if child path == parent path }; zen::Opt getPathDependency(const AbstractPath& basePathL, const HardFilter& filterL, const AbstractPath& basePathR, const HardFilter& filterR); diff --git a/FreeFileSync/Source/application.cpp b/FreeFileSync/Source/application.cpp index 5a6e717a..f4519639 100755 --- a/FreeFileSync/Source/application.cpp +++ b/FreeFileSync/Source/application.cpp @@ -326,9 +326,9 @@ void Application::launch(const std::vector& commandArgs) auto hasNonDefaultConfig = [](const LocalPairConfig& lpc) { - return lpc != LocalPairConfig(lpc.folderPathPhraseLeft, - lpc.folderPathPhraseRight, - NoValue(), NoValue(), FilterConfig()); + return lpc != LocalPairConfig{ lpc.folderPathPhraseLeft, + lpc.folderPathPhraseRight, + NoValue(), NoValue(), FilterConfig() }; }; auto replaceDirectories = [&](MainConfiguration& mainCfg) @@ -350,8 +350,8 @@ void Application::launch(const std::vector& commandArgs) mainCfg.firstPair.folderPathPhraseRight = dirPathPhrasePairs[0].second; } else - mainCfg.additionalPairs.emplace_back(dirPathPhrasePairs[i].first, dirPathPhrasePairs[i].second, - NoValue(), NoValue(), FilterConfig()); + mainCfg.additionalPairs.push_back({ dirPathPhrasePairs[i].first, dirPathPhrasePairs[i].second, + NoValue(), NoValue(), FilterConfig() }); } return true; }; @@ -567,11 +567,13 @@ void runBatchMode(const Zstring& globalConfigFilePath, const XmlBatchConfig& bat logNonDefaultSettings(globalCfg, statusHandler); //inform about (important) non-default global settings - const std::vector cmpConfig = extractCompareCfg(batchCfg.mainCfg); + const std::vector fpCfgList = extractCompareCfg(batchCfg.mainCfg); //batch mode: place directory locks on directories during both comparison AND synchronization std::unique_ptr dirLocks; + const std::map& deviceParallelOps = batchCfg.mainCfg.deviceParallelOps; + //COMPARE DIRECTORIES FolderComparison cmpResult = compare(globalCfg.warnDlgs, globalCfg.fileTimeTolerance, @@ -580,8 +582,9 @@ void runBatchMode(const Zstring& globalConfigFilePath, const XmlBatchConfig& bat globalCfg.folderAccessTimeout, globalCfg.createLockFile, dirLocks, - cmpConfig, - statusHandler); //throw ? + fpCfgList, + deviceParallelOps, + statusHandler); //throw X //START SYNCHRONIZATION const std::vector syncProcessCfg = extractSyncCfg(batchCfg.mainCfg); @@ -597,8 +600,9 @@ void runBatchMode(const Zstring& globalConfigFilePath, const XmlBatchConfig& bat globalCfg.folderAccessTimeout, syncProcessCfg, cmpResult, + deviceParallelOps, globalCfg.warnDlgs, - statusHandler); //throw ? + statusHandler); //throw X //not cancelled? => update last sync date for the selected cfg file for (ConfigFileItem& cfi : globalCfg.gui.mainDlg.cfgFileHistory) diff --git a/FreeFileSync/Source/comparison.cpp b/FreeFileSync/Source/comparison.cpp index 61001120..b0e7bcec 100755 --- a/FreeFileSync/Source/comparison.cpp +++ b/FreeFileSync/Source/comparison.cpp @@ -22,22 +22,26 @@ using namespace fff; std::vector fff::extractCompareCfg(const MainConfiguration& mainCfg) { //merge first and additional pairs - std::vector allPairs = { mainCfg.firstPair }; - append(allPairs, mainCfg.additionalPairs); + std::vector localCfgs = { mainCfg.firstPair }; + append(localCfgs, mainCfg.additionalPairs); std::vector output; - std::transform(allPairs.begin(), allPairs.end(), std::back_inserter(output), - [&](const LocalPairConfig& lpc) -> FolderPairCfg - { - return FolderPairCfg(lpc.folderPathPhraseLeft, lpc.folderPathPhraseRight, - lpc.localCmpCfg ? lpc.localCmpCfg->compareVar : mainCfg.cmpConfig.compareVar, - lpc.localCmpCfg ? lpc.localCmpCfg->handleSymlinks : mainCfg.cmpConfig.handleSymlinks, - lpc.localCmpCfg ? lpc.localCmpCfg->ignoreTimeShiftMinutes : mainCfg.cmpConfig.ignoreTimeShiftMinutes, - normalizeFilters(mainCfg.globalFilter, lpc.localFilter), + for (const LocalPairConfig& lpc : localCfgs) + { + const CompConfig cmpCfg = lpc.localCmpCfg ? *lpc.localCmpCfg : mainCfg.cmpCfg; + const SyncConfig syncCfg = lpc.localSyncCfg ? *lpc.localSyncCfg : mainCfg.syncCfg; - lpc.localSyncCfg ? lpc.localSyncCfg->directionCfg : mainCfg.syncCfg.directionCfg); - }); + output.push_back( + { + lpc.folderPathPhraseLeft, lpc.folderPathPhraseRight, + cmpCfg.compareVar, + cmpCfg.handleSymlinks, + cmpCfg.ignoreTimeShiftMinutes, + normalizeFilters(mainCfg.globalFilter, lpc.localFilter), + syncCfg.directionCfg + }); + } return output; } @@ -54,11 +58,11 @@ struct ResolvedFolderPair struct ResolvedBaseFolders { std::vector resolvedPairs; - std::set existingBaseFolders; + std::set existingBaseFolders; }; -ResolvedBaseFolders initializeBaseFolders(const std::vector& cfgList, +ResolvedBaseFolders initializeBaseFolders(const std::vector& fpCfgList, const std::map& deviceParallelOps, int folderAccessTimeout, bool allowUserInteraction, ProcessCallback& callback) @@ -67,11 +71,11 @@ ResolvedBaseFolders initializeBaseFolders(const std::vector& cfgL tryReportingError([&] { - std::set uniqueBaseFolders; + std::set uniqueBaseFolders; //support "retry" for environment variable and and variable driver letter resolution! output.resolvedPairs.clear(); - for (const FolderPairCfg& fpCfg : cfgList) + for (const FolderPairCfg& fpCfg : fpCfgList) { AbstractPath folderPathLeft = createAbstractPath(fpCfg.folderPathPhraseLeft_); AbstractPath folderPathRight = createAbstractPath(fpCfg.folderPathPhraseRight_); @@ -82,7 +86,8 @@ ResolvedBaseFolders initializeBaseFolders(const std::vector& cfgL output.resolvedPairs.push_back({ folderPathLeft, folderPathRight }); } - const FolderStatus status = getFolderStatusNonBlocking(uniqueBaseFolders, folderAccessTimeout, allowUserInteraction, callback); //re-check *all* directories on each try! + const FolderStatus status = getFolderStatusNonBlocking(uniqueBaseFolders, deviceParallelOps, + folderAccessTimeout, allowUserInteraction, callback); //re-check *all* directories on each try! output.existingBaseFolders = status.existing; if (!status.notExisting.empty() || !status.failedChecks.empty()) @@ -107,7 +112,7 @@ ResolvedBaseFolders initializeBaseFolders(const std::vector& cfgL throw FileError(msg); } - }, callback); //throw X? + }, callback); //throw X return output; } @@ -117,7 +122,10 @@ ResolvedBaseFolders initializeBaseFolders(const std::vector& cfgL class ComparisonBuffer { public: - ComparisonBuffer(const std::set& keysToRead, int fileTimeTolerance, ProcessCallback& callback); + ComparisonBuffer(const std::set& foldersToRead, + const std::map& deviceParallelOps, + int fileTimeTolerance, + ProcessCallback& callback); //create comparison result table and fill category except for files existing on both sides: undefinedFiles and undefinedSymlinks are appended! std::shared_ptr compareByTimeSize(const ResolvedFolderPair& fp, const FolderPairCfg& fpConfig) const; @@ -139,7 +147,10 @@ private: }; -ComparisonBuffer::ComparisonBuffer(const std::set& keysToRead, int fileTimeTolerance, ProcessCallback& callback) : +ComparisonBuffer::ComparisonBuffer(const std::set& foldersToRead, + const std::map& deviceParallelOps, + int fileTimeTolerance, + ProcessCallback& callback) : fileTimeTolerance_(fileTimeTolerance), callback_(callback) { class CbImpl : public FillBufferCallback @@ -149,7 +160,7 @@ ComparisonBuffer::ComparisonBuffer(const std::set& keysToRead, int void reportStatus(const std::wstring& statusMsg, int itemsTotal) override { - callback_.updateProcessedData(itemsTotal - itemsReported_, 0); //processed bytes are reported in subfunctions! + callback_.updateDataProcessed(itemsTotal - itemsReported_, 0); //processed bytes are reported in subfunctions! itemsReported_ = itemsTotal; callback_.reportStatus(statusMsg); //may throw @@ -177,8 +188,9 @@ ComparisonBuffer::ComparisonBuffer(const std::set& keysToRead, int int itemsReported_ = 0; } cb(callback); - fillBuffer(keysToRead, //in + fillBuffer(foldersToRead, //in directoryBuffer_, //out + deviceParallelOps, cb, UI_UPDATE_INTERVAL / 2); //every ~50 ms @@ -338,7 +350,7 @@ void categorizeSymlinkByContent(SymlinkPair& symlink, ProcessCallback& callback) callback.reportStatus(replaceCpy(_("Resolving symbolic link %x"), L"%x", fmtPath(AFS::getDisplayPath(symlink.getAbstractPath())))); binaryContentR = AFS::getSymlinkBinaryContent(symlink.getAbstractPath()); //throw FileError - }, callback); //throw X? + }, callback); //throw X if (errMsg) symlink.setCategoryConflict(*errMsg); @@ -400,6 +412,7 @@ std::shared_ptr ComparisonBuffer::compareBySize(const ResolvedFo std::list> ComparisonBuffer::compareByContent(const std::vector>& workLoad) const { + warn_static("perf: make parallel") std::list> output; if (workLoad.empty()) return output; @@ -460,14 +473,14 @@ std::list> ComparisonBuffer::compareByContent(co bool haveSameContent = false; Opt errMsg = tryReportingError([&] { - StatisticsReporter statReporter(1, file->getFileSize(), callback_); + ItemStatReporter statReporter(1, file->getFileSize(), callback_); auto notifyUnbufferedIO = [&](int64_t bytesDelta) { statReporter.reportDelta(0, bytesDelta); }; haveSameContent = filesHaveSameContent(file->getAbstractPath(), file->getAbstractPath(), notifyUnbufferedIO); //throw FileError statReporter.reportDelta(1, 0); - }, callback_); //throw X? + }, callback_); //throw X if (errMsg) file->setCategoryConflict(*errMsg); @@ -481,7 +494,7 @@ std::list> ComparisonBuffer::compareByContent(co //3. harmonize with "bool stillInSync()" in algorithm.cpp, FilePair::setSyncedTo() in file_hierarchy.h if (file->getItemName() != file->getItemName()) file->setCategoryDiffMetadata(getDescrDiffMetaShortnameCase(*file)); -#if 0 //don't synchronize modtime only see SynchronizeFolderPair::synchronizeFileInt(), SO_COPY_METADATA_TO_* +#if 0 //don't synchronize modtime only see FolderPairSyncer::synchronizeFileInt(), SO_COPY_METADATA_TO_* else if (!sameFileTime(file->getLastWriteTime(), file->getLastWriteTime(), file->base().getFileTimeTolerance(), file->base().getIgnoredTimeShift())) file->setCategoryDiffMetadata(getDescrDiffMetaDate(*file)); @@ -820,7 +833,8 @@ FolderComparison fff::compare(WarningDialogs& warnings, int folderAccessTimeout, bool createDirLocks, std::unique_ptr& dirLocks, - const std::vector& cfgList, + const std::vector& fpCfgList, + const std::map& deviceParallelOps, ProcessCallback& callback) { //PERF_START; @@ -855,17 +869,17 @@ FolderComparison fff::compare(WarningDialogs& warnings, callback.reportInfo(e.toString()); //may throw! } - const ResolvedBaseFolders& resInfo = initializeBaseFolders(cfgList, folderAccessTimeout, allowUserInteraction, callback); + const ResolvedBaseFolders& resInfo = initializeBaseFolders(fpCfgList, deviceParallelOps, folderAccessTimeout, allowUserInteraction, callback); //directory existence only checked *once* to avoid race conditions! - if (resInfo.resolvedPairs.size() != cfgList.size()) + if (resInfo.resolvedPairs.size() != fpCfgList.size()) throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo(__LINE__)); auto basefolderExisting = [&](const AbstractPath& folderPath) { return resInfo.existingBaseFolders.find(folderPath) != resInfo.existingBaseFolders.end(); }; std::vector> workLoad; - for (size_t i = 0; i < cfgList.size(); ++i) - workLoad.emplace_back(resInfo.resolvedPairs[i], cfgList[i]); + for (size_t i = 0; i < fpCfgList.size(); ++i) + workLoad.emplace_back(resInfo.resolvedPairs[i], fpCfgList[i]); //-----------execute basic checks all at once before starting comparison---------- @@ -922,14 +936,14 @@ FolderComparison fff::compare(WarningDialogs& warnings, try { //------------------- fill directory buffer --------------------------------------------------- - std::set dirsToRead; + std::set foldersToRead; for (const auto& w : workLoad) { if (basefolderExisting(w.first.folderPathLeft)) //only traverse *currently existing* folders: at this point user is aware that non-ex + empty string are seen as empty folder! - dirsToRead.insert({ w.first.folderPathLeft, w.second.filter.nameFilter, w.second.handleSymlinks }); + foldersToRead.emplace(DirectoryKey({ w.first.folderPathLeft, w.second.filter.nameFilter, w.second.handleSymlinks })); if (basefolderExisting(w.first.folderPathRight)) - dirsToRead.insert({ w.first.folderPathRight, w.second.filter.nameFilter, w.second.handleSymlinks }); + foldersToRead.emplace(DirectoryKey({ w.first.folderPathRight, w.second.filter.nameFilter, w.second.handleSymlinks })); } FolderComparison output; @@ -938,7 +952,7 @@ FolderComparison fff::compare(WarningDialogs& warnings, { //------------ traverse/read folders ----------------------------------------------------- //PERF_START; - ComparisonBuffer cmpBuff(dirsToRead, fileTimeTolerance, callback); + ComparisonBuffer cmpBuff(foldersToRead, deviceParallelOps, fileTimeTolerance, callback); //PERF_STOP; //process binary comparison as one junk @@ -969,13 +983,13 @@ FolderComparison fff::compare(WarningDialogs& warnings, break; } } - assert(output.size() == cfgList.size()); + assert(output.size() == fpCfgList.size()); //--------- set initial sync-direction -------------------------------------------------- for (auto it = begin(output); it != end(output); ++it) { - const FolderPairCfg& fpCfg = cfgList[it - output.begin()]; + const FolderPairCfg& fpCfg = fpCfgList[it - output.begin()]; callback.reportStatus(_("Calculating sync directions...")); callback.forceUiRefresh(); //throw X @@ -985,7 +999,7 @@ FolderComparison fff::compare(WarningDialogs& warnings, redetermineSyncDirection(fpCfg.directionCfg, *it, //throw FileError [&](const std::wstring& msg) { callback.reportStatus(msg); }); //throw X - }, callback); //throw X? + }, callback); //throw X } return output; @@ -993,7 +1007,7 @@ FolderComparison fff::compare(WarningDialogs& warnings, catch (const std::bad_alloc& e) { callback.reportFatalError(_("Out of memory.") + L" " + utfTo(e.what())); - //we need to maintain the "output.size() == cfgList.size()" contract in ALL cases! => abort + //we need to maintain the "output.size() == fpCfgList.size()" contract in ALL cases! => abort callback.abortProcessNow(); //throw X throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo(__LINE__)); } diff --git a/FreeFileSync/Source/comparison.h b/FreeFileSync/Source/comparison.h index 5bc5cd9d..4e9e7b5a 100755 --- a/FreeFileSync/Source/comparison.h +++ b/FreeFileSync/Source/comparison.h @@ -58,7 +58,8 @@ FolderComparison compare(WarningDialogs& warnings, int folderAccessTimeout, bool createDirLocks, std::unique_ptr& dirLocks, //out - const std::vector& cfgList, + const std::vector& fpCfgList, + const std::map& deviceParallelOps, ProcessCallback& callback); } diff --git a/FreeFileSync/Source/file_hierarchy.cpp b/FreeFileSync/Source/file_hierarchy.cpp index 7ef11e37..fd0e8beb 100755 --- a/FreeFileSync/Source/file_hierarchy.cpp +++ b/FreeFileSync/Source/file_hierarchy.cpp @@ -457,7 +457,7 @@ std::wstring fff::getSyncOpDescription(const FileSystemObject& fsObj) case SO_COPY_METADATA_TO_LEFT: case SO_COPY_METADATA_TO_RIGHT: - //harmonize with synchronization.cpp::SynchronizeFolderPair::synchronizeFileInt, ect!! + //harmonize with synchronization.cpp::FolderPairSyncer::synchronizeFileInt, ect!! { Zstring shortNameOld = fsObj.getItemName(); Zstring shortNameNew = fsObj.getItemName< LEFT_SIDE>(); diff --git a/FreeFileSync/Source/file_hierarchy.h b/FreeFileSync/Source/file_hierarchy.h index f25d7684..482cf50a 100755 --- a/FreeFileSync/Source/file_hierarchy.h +++ b/FreeFileSync/Source/file_hierarchy.h @@ -24,8 +24,6 @@ namespace fff { -using AFS = AbstractFileSystem; - struct FileAttributes { FileAttributes() {} @@ -416,6 +414,9 @@ private: static std::unordered_set& activeObjects() { + //our global ObjectMgr is not thread-safe (and currently does not need to be!) + //assert(std::this_thread::get_id() == mainThreadId); -> still, may be accessed by synchronization worker thread, one-at-a-time + static std::unordered_set inst; return inst; //external linkage (even in header file!) } diff --git a/FreeFileSync/Source/fs/abstract.cpp b/FreeFileSync/Source/fs/abstract.cpp index 6442008d..1b857161 100755 --- a/FreeFileSync/Source/fs/abstract.cpp +++ b/FreeFileSync/Source/fs/abstract.cpp @@ -47,7 +47,7 @@ int AFS::compareAbstractPath(const AbstractPath& lhs, const AbstractPath& rhs) AFS::PathComponents AFS::getPathComponents(const AbstractPath& ap) { - return { AbstractPath(ap.afs, AfsPath(Zstring())), split(ap.afsPath.value, FILE_NAME_SEPARATOR, SplitType::SKIP_EMPTY) }; + return { AbstractPath(ap.afs, AfsPath()), split(ap.afsPath.value, FILE_NAME_SEPARATOR, SplitType::SKIP_EMPTY) }; } @@ -69,6 +69,28 @@ Opt AFS::getParentAfsPath(const AfsPath& afsPath) } +void AFS::traverseFolderParallel(const AbstractPath& rootPath, const AFS::TraverserWorkload& workload, size_t parallelOps) +{ + warn_static("just glue to rootPath!") + assert(rootPath.afsPath.value.empty()); + + TraverserWorkloadImpl wlImpl; + for (const auto& item : workload) + { + AfsPath afsPath; + for (const Zstring& itemName : item.first) + { + assert(!contains(itemName, FILE_NAME_SEPARATOR)); + if (!afsPath.value.empty()) + afsPath.value += FILE_NAME_SEPARATOR; + afsPath.value += itemName; + } + wlImpl.emplace_back(afsPath, item.second); + } + rootPath.afs->traverseFolderParallel(wlImpl, parallelOps); //throw +} + + //target existing: undefined behavior! (fail/overwrite/auto-rename) AFS::FileCopyResult AFS::copyFileAsStream(const AfsPath& afsPathSource, const StreamAttributes& attrSource, //throw FileError, ErrorFileLocked const AbstractPath& apTarget, const IOCallback& notifyUnbufferedIO) const @@ -229,20 +251,28 @@ void AFS::createFolderIfMissingRecursion(const AbstractPath& ap) //throw FileErr } catch (FileError&) { - Opt pd; - try { pd = getPathStatus(ap); /*throw FileError*/ } - catch (FileError&) {} //previous exception is more relevant + const PathStatus ps = getPathStatus(ap); //throw FileError + if (ps.existingType == ItemType::FILE) + throw; - if (pd && - pd->existingType != ItemType::FILE && - pd->relPath.size() != 1) //don't repeat the very same createFolderPlain() call from above! - { - AbstractPath intermediatePath = pd->existingPath; - for (const Zstring& itemName : pd->relPath) + //ps.relPath.size() == 1 => same createFolderPlain() call from above? Maybe parent folder was created by parallel thread shortly after failure! + AbstractPath intermediatePath = ps.existingPath; + for (const Zstring& itemName : ps.relPath) + try + { createFolderPlain(intermediatePath = appendRelPath(intermediatePath, itemName)); //throw FileError - return; - } - throw; + } + catch (FileError&) + { + try //already existing => possible, if createFolderIfMissingRecursion() is run in parallel + { + if (getItemType(intermediatePath) != ItemType::FILE) //throw FileError + continue; + } + catch (FileError&) {} + + throw; + } } } @@ -254,7 +284,7 @@ struct ItemSearchCallback: public AFS::TraverserCallback ItemSearchCallback(const Zstring& itemName) : itemName_(itemName) {} void onFile (const FileInfo& fi) override { if (equalFilePath(fi.itemName, itemName_)) throw AFS::ItemType::FILE; } - std::unique_ptr onFolder (const FolderInfo& fi) override { if (equalFilePath(fi.itemName, itemName_)) throw AFS::ItemType::FOLDER; return nullptr; } + std::shared_ptr onFolder (const FolderInfo& fi) override { if (equalFilePath(fi.itemName, itemName_)) throw AFS::ItemType::FOLDER; return nullptr; } HandleLink onSymlink(const SymlinkInfo& si) override { if (equalFilePath(si.itemName, itemName_)) throw AFS::ItemType::SYMLINK; return TraverserCallback::LINK_SKIP; } HandleError reportDirError (const std::wstring& msg, size_t retryNumber) override { throw FileError(msg); } HandleError reportItemError(const std::wstring& msg, size_t retryNumber, const Zstring& itemName) override { throw FileError(msg); } @@ -289,8 +319,8 @@ AFS::PathStatusImpl AFS::getPathStatusViaFolderTraversal(const AfsPath& afsPath) ps.existingType != ItemType::FILE) //obscure, but possible (and not an error) try { - ItemSearchCallback iscb(itemName); - traverseFolder(*parentAfsPath, iscb); //throw FileError, ItemType + auto iscb = std::make_shared(itemName); + traverseFolderParallel({{ *parentAfsPath, iscb }}, 1 /*parallelOps*/); //throw FileError, ItemType } catch (const ItemType& type) { return { type, afsPath, {} }; } //yes, exceptions for control-flow are bad design... but, but... //we're not CPU-bound here and finding the item after getItemType() previously failed is exceptional (even C:\pagefile.sys should be found) @@ -302,17 +332,17 @@ AFS::PathStatusImpl AFS::getPathStatusViaFolderTraversal(const AfsPath& afsPath) Opt AFS::getItemTypeIfExists(const AbstractPath& ap) //throw FileError { - const PathStatus pd = getPathStatus(ap); //throw FileError - if (pd.relPath.empty()) - return pd.existingType; + const PathStatus ps = getPathStatus(ap); //throw FileError + if (ps.relPath.empty()) + return ps.existingType; return NoValue(); } AFS::PathStatus AFS::getPathStatus(const AbstractPath& ap) //throw FileError { - const PathStatusImpl pdi = ap.afs->getPathStatus(ap.afsPath); //throw FileError - return { pdi.existingType, AbstractPath(ap.afs, pdi.existingAfsPath), pdi.relPath }; + const PathStatusImpl psi = ap.afs->getPathStatus(ap.afsPath); //throw FileError + return { psi.existingType, AbstractPath(ap.afs, psi.existingAfsPath), psi.relPath }; } @@ -323,7 +353,7 @@ struct FlatTraverserCallback: public AFS::TraverserCallback FlatTraverserCallback(const AbstractPath& folderPath) : folderPath_(folderPath) {} void onFile (const FileInfo& fi) override { fileNames_ .push_back(fi.itemName); } - std::unique_ptr onFolder (const FolderInfo& fi) override { folderNames_ .push_back(fi.itemName); return nullptr; } + std::shared_ptr onFolder (const FolderInfo& fi) override { folderNames_ .push_back(fi.itemName); return nullptr; } HandleLink onSymlink(const SymlinkInfo& si) override { symlinkNames_.push_back(si.itemName); return TraverserCallback::LINK_SKIP; } HandleError reportDirError (const std::wstring& msg, size_t retryNumber) override { throw FileError(msg); } HandleError reportItemError(const std::wstring& msg, size_t retryNumber, const Zstring& itemName) override { throw FileError(msg); } @@ -345,10 +375,12 @@ void removeFolderIfExistsRecursionImpl(const AbstractPath& folderPath, //throw F const std::function& onBeforeFolderDeletion) //one call for each *existing* object! { - FlatTraverserCallback ft(folderPath); //deferred recursion => save stack space and allow deletion of extremely deep hierarchies! - AFS::traverseFolder(folderPath, ft); //throw FileError + //deferred recursion => save stack space and allow deletion of extremely deep hierarchies! + auto ft = std::make_shared(folderPath); + const AFS::PathComponents pc = AFS::getPathComponents(folderPath); + AFS::traverseFolderParallel(pc.rootPath, {{ pc.relPath, ft }}, 1 /*parallelOps*/); //throw FileError - for (const Zstring& fileName : ft.refFileNames()) + for (const Zstring& fileName : ft->refFileNames()) { const AbstractPath filePath = AFS::appendRelPath(folderPath, fileName); if (onBeforeFileDeletion) @@ -357,7 +389,7 @@ void removeFolderIfExistsRecursionImpl(const AbstractPath& folderPath, //throw F AFS::removeFilePlain(filePath); //throw FileError } - for (const Zstring& symlinkName : ft.refSymlinkNames()) + for (const Zstring& symlinkName : ft->refSymlinkNames()) { const AbstractPath linkPath = AFS::appendRelPath(folderPath, symlinkName); if (onBeforeFileDeletion) @@ -366,7 +398,7 @@ void removeFolderIfExistsRecursionImpl(const AbstractPath& folderPath, //throw F AFS::removeSymlinkPlain(linkPath); //throw FileError } - for (const Zstring& folderName : ft.refFolderNames()) + for (const Zstring& folderName : ft->refFolderNames()) removeFolderIfExistsRecursionImpl(AFS::appendRelPath(folderPath, folderName), //throw FileError onBeforeFileDeletion, onBeforeFolderDeletion); @@ -380,8 +412,9 @@ void removeFolderIfExistsRecursionImpl(const AbstractPath& folderPath, //throw F void AFS::removeFolderIfExistsRecursion(const AbstractPath& ap, //throw FileError const std::function& onBeforeFileDeletion, //optional - const std::function& onBeforeFolderDeletion) //one call for each *existing* object! + const std::function& onBeforeFolderDeletion) //one call for each object! { + //no error situation if directory is not existing! manual deletion relies on it! if (Opt type = AFS::getItemTypeIfExists(ap)) //throw FileError { if (*type == AFS::ItemType::SYMLINK) @@ -394,7 +427,8 @@ void AFS::removeFolderIfExistsRecursion(const AbstractPath& ap, //throw FileErro else removeFolderIfExistsRecursionImpl(ap, onBeforeFileDeletion, onBeforeFolderDeletion); //throw FileError } - //no error situation if directory is not existing! manual deletion relies on it! + else //even if the folder did not exist anymore, significant I/O work was done => report + if (onBeforeFolderDeletion) onBeforeFolderDeletion(AFS::getDisplayPath(ap)); } diff --git a/FreeFileSync/Source/fs/abstract.h b/FreeFileSync/Source/fs/abstract.h index 233d4faa..9774aead 100755 --- a/FreeFileSync/Source/fs/abstract.h +++ b/FreeFileSync/Source/fs/abstract.h @@ -11,6 +11,7 @@ #include #include #include +#include #include //InputStream/OutputStream support buffered stream concept #include //NOT a wxWidgets dependency! @@ -22,8 +23,9 @@ struct AbstractFileSystem; bool isValidRelPath(const Zstring& relPath); //============================================================================================================== -struct AfsPath //= path relative (no leading/traling separator) to the file system root folder +struct AfsPath //= path relative to the file system root folder (no leading/traling separator) { + AfsPath() {} explicit AfsPath(const Zstring& p) : value(p) { assert(isValidRelPath(value)); } Zstring value; }; @@ -42,17 +44,13 @@ private: std::shared_ptr afs; //always bound; "const AbstractFileSystem" => all accesses expected to be thread-safe!!! AfsPath afsPath; }; + //============================================================================================================== struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model thread-safe access! { static int compareAbstractPath(const AbstractPath& lhs, const AbstractPath& rhs); - struct LessAbstractPath - { - bool operator()(const AbstractPath& lhs, const AbstractPath& rhs) const { return compareAbstractPath(lhs, rhs) < 0; } - }; - static bool equalAbstractPath(const AbstractPath& lhs, const AbstractPath& rhs) { return compareAbstractPath(lhs, rhs) == 0; } static Zstring getInitPathPhrase(const AbstractPath& ap) { return ap.afs->getInitPathPhrase(ap.afsPath); } @@ -107,7 +105,7 @@ struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model t static bool removeSymlinkIfExists(const AbstractPath& ap); // static void removeFolderIfExistsRecursion(const AbstractPath& ap, //throw FileError const std::function& onBeforeFileDeletion, //optional - const std::function& onBeforeFolderDeletion); //one call for each *existing* object! + const std::function& onBeforeFolderDeletion); //one call for each object! static void removeFilePlain (const AbstractPath& ap) { ap.afs->removeFilePlain (ap.afsPath); } //throw FileError static void removeSymlinkPlain(const AbstractPath& ap) { ap.afs->removeSymlinkPlain(ap.afsPath); } //throw FileError @@ -218,15 +216,18 @@ struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model t virtual void onFile (const FileInfo& fi) = 0; // virtual HandleLink onSymlink(const SymlinkInfo& si) = 0; //throw X - virtual std::unique_ptr onFolder (const FolderInfo& fi) = 0; // + virtual std::shared_ptr onFolder (const FolderInfo& fi) = 0; // //nullptr: ignore directory, non-nullptr: traverse into, using the (new) callback virtual HandleError reportDirError (const std::wstring& msg, size_t retryNumber) = 0; //failed directory traversal -> consider directory data at current level as incomplete! virtual HandleError reportItemError(const std::wstring& msg, size_t retryNumber, const Zstring& itemName) = 0; //failed to get data for single file/dir/symlink only! }; + using TraverserWorkload = std::vector /*relPath*/, std::shared_ptr /*throw X*/>>; + //- client needs to handle duplicate file reports! (FilePlusTraverser fallback, retrying to read directory contents, ...) - static void traverseFolder(const AbstractPath& ap, TraverserCallback& sink /*throw X*/) { ap.afs->traverseFolder(ap.afsPath, sink); } //throw X + static void traverseFolderParallel(const AbstractPath& rootPath, const TraverserWorkload& workload, size_t parallelOps); + //---------------------------------------------------------------------------------------------------------------- static bool supportPermissionCopy(const AbstractPath& apSource, const AbstractPath& apTarget); //throw FileError @@ -275,7 +276,10 @@ struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model t struct RecycleSession { virtual ~RecycleSession() {} - virtual bool recycleItem(const AbstractPath& itemPath, const Zstring& logicalRelPath) = 0; //throw FileError; return true if item existed + //- return true if item existed + //- multi-threaded access: internally synchronized! + virtual bool recycleItem(const AbstractPath& itemPath, const Zstring& logicalRelPath) = 0; //throw FileError; + virtual void tryCleanup(const std::function& notifyDeletionStatus /*optional; currentItem may be empty*/) = 0; //throw FileError }; @@ -310,6 +314,8 @@ protected: //grant derived classes access to AbstractPath: FileCopyResult copyFileAsStream(const AfsPath& afsPathSource, const StreamAttributes& attrSource, //throw FileError, ErrorFileLocked const AbstractPath& apTarget, const zen::IOCallback& notifyUnbufferedIO) const; //may be nullptr; throw X! + using TraverserWorkloadImpl = std::vector /*throw X*/>>; + private: virtual zen::Opt getNativeItemPath(const AfsPath& afsPath) const { return zen::NoValue(); }; @@ -346,7 +352,8 @@ private: const uint64_t* streamSize, //optional const zen::IOCallback& notifyUnbufferedIO) const = 0; // //---------------------------------------------------------------------------------------------------------------- - virtual void traverseFolder(const AfsPath& afsPath, TraverserCallback& sink /*throw X*/) const = 0; //throw X + virtual void traverseFolderParallel(const TraverserWorkloadImpl& workload /*throw X*/, size_t parallelOps) const = 0; + //---------------------------------------------------------------------------------------------------------------- virtual bool supportsPermissions(const AfsPath& afsPath) const = 0; //throw FileError @@ -381,6 +388,11 @@ private: }; +inline bool operator< (const AbstractPath& lhs, const AbstractPath& rhs) { return AbstractFileSystem::compareAbstractPath(lhs, rhs) < 0; } +inline bool operator==(const AbstractPath& lhs, const AbstractPath& rhs) { return AbstractFileSystem::compareAbstractPath(lhs, rhs) == 0; } +inline bool operator!=(const AbstractPath& lhs, const AbstractPath& rhs) { return !(lhs == rhs); } + + //implement "retry" in a generic way: template inline //function object expecting to throw FileError if operation fails bool tryReportingDirError(Command cmd, AbstractFileSystem::TraverserCallback& callback) //throw X, return "true" on success, "false" if error was ignored @@ -426,6 +438,182 @@ bool tryReportingItemError(Command cmd, AbstractFileSystem::TraverserCallback& c } +#if __GNUC__ < 6 //support for "fold expressions" requires GCC 6.0 or later: http://en.cppreference.com/w/cpp/compiler_support + #define ZEN_LINUX_TRAVERSER_LEGACY +#else + #define ZEN_LINUX_TRAVERSER_MODERN +#endif + +#if __GNUC__ == 6 //std::apply available with GCC 7 +} +namespace std +{ +template +constexpr decltype(auto) apply(F&& f, std::tuple& t) { return std::invoke(std::forward(f), std::get<0>(t), std::get<1>(t), std::get<2>(t)); } +} +namespace fff +{ +#endif + + +#if !defined ZEN_LINUX || defined ZEN_LINUX_TRAVERSER_MODERN +template +struct WorkItem +{ + Function getResult; //throw FileError + Zstring errorItemName; //empty if all items affected + size_t errorRetryCount = 0; + std::shared_ptr cb; //call by controlling thread only! => don't require traverseFolderParallel() callbacks to be thread-safe! +}; + +template +struct ResultItem +{ + WorkItem wi; + std::exception_ptr error; //mutually exclusive + decltype(wi.getResult()) value; // +}; + + +template //avoid std::function memory alloc + virtual calls +class AsyncTraverserWorkload +{ +public: + AsyncTraverserWorkload() {} + + //context of controlling thread, non-blocking: + //NOTE: AsyncTraverserWorkload must out-live threadGroup!!! + template + void run(WorkItem&& wi, zen::ThreadGroup>& threadGroup) + { + threadGroup.run([this, wi] + { + try { this->returnResult({ wi, nullptr, wi.getResult() }); } //throw FileError + catch (...) { this->returnResult({ wi, std::current_exception(), {} }); } + }); + + std::lock_guard dummy(lockWork_); + ++workItemsInProcess_; + } + + //context of controlling thread, blocking: + bool getResults(std::tuple>...>& results) //returns false when traversal is finished + { + std::apply([](auto&... r) { (..., r.clear()); }, results); + + std::unique_lock dummy(lockWork_); + + auto resultsReady = [&] + { + bool ready = false; + std::apply([&ready](const auto&... r) { ready = (... || !r.empty()); }, results_); + return ready; + }; + + if (!resultsReady() && workItemsInProcess_ == 0) + return false; + + conditionNewResult_.wait(dummy, [&resultsReady] { return resultsReady(); }); + + results.swap(results_); //reuse memory + avoid needless item-level mutex locking + return true; + } + +private: + AsyncTraverserWorkload (const AsyncTraverserWorkload&) = delete; + AsyncTraverserWorkload& operator=(const AsyncTraverserWorkload&) = delete; + + //context of worker threads, non-blocking: + template + void returnResult(ResultItem&& r) + { + { + std::lock_guard dummy(lockWork_); + + std::get>>(results_).push_back(std::move(r)); + --workItemsInProcess_; + } + conditionNewResult_.notify_all(); + } + + std::mutex lockWork_; + size_t workItemsInProcess_ = 0; + std::tuple>...> results_; + std::condition_variable conditionNewResult_; +}; + + +template +class GenericDirTraverser +{ +public: + using Function1 = typename zen::GetFirstOf::Type; + + GenericDirTraverser(std::vector>&& initialWorkItems, size_t parallelOps, const std::string& threadGroupName) : + threadGroup_(std::max(1, parallelOps), threadGroupName) + { + //set the initial work load + for (auto& item : initialWorkItems) + asyncWorkload_.template run(std::move(item), threadGroup_); + + //run loop + std::tuple>...> results; //avoid per-getNextResults() memory allocations (=> swap instead!) + + while (asyncWorkload_.getResults(results)) + std::apply([&](auto&... r) { (..., this->evalResultList(r)); }, results); //throw X + } + +private: + GenericDirTraverser (const GenericDirTraverser&) = delete; + GenericDirTraverser& operator=(const GenericDirTraverser&) = delete; + + template + void evalResultList(std::vector>& results) //throw X + { + for (ResultItem& result : results) + evalResult(result); //throw X + } + + template + void evalResult(ResultItem& result); //throw X + + //specialize! + template + void evalResultValue(const typename Function::Result& r, std::shared_ptr& cb); //throw X + + AsyncTraverserWorkload asyncWorkload_; //ATTENTION: asyncWorkload_ must out-live threadGroup_!!! + zen::ThreadGroup> threadGroup_; // +}; + + +template +template +void GenericDirTraverser::evalResult(ResultItem& result) //throw X +{ + auto& cb = result.wi.cb; + try + { + if (result.error) + std::rethrow_exception(result.error); //throw FileError + } + catch (const zen::FileError& e) + { + switch (result.wi.errorItemName.empty() ? + cb->reportDirError (e.toString(), result.wi.errorRetryCount) : //throw X + cb->reportItemError(e.toString(), result.wi.errorRetryCount, result.wi.errorItemName)) //throw X + { + case AbstractFileSystem::TraverserCallback::ON_ERROR_RETRY: + asyncWorkload_.template run({ std::move(result.wi.getResult), result.wi.errorItemName, result.wi.errorRetryCount + 1, cb }, threadGroup_); + return; + + case AbstractFileSystem::TraverserCallback::ON_ERROR_CONTINUE: + return; + } + } + + evalResultValue(result.value, result.wi.cb); //throw X +} +#endif diff --git a/FreeFileSync/Source/fs/native.cpp b/FreeFileSync/Source/fs/native.cpp index 5ea0c9bb..864b1369 100755 --- a/FreeFileSync/Source/fs/native.cpp +++ b/FreeFileSync/Source/fs/native.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include "../lib/resolve_path.h" @@ -29,8 +30,6 @@ using AFS = AbstractFileSystem; namespace { - - void initComForThread() //throw FileError { } @@ -50,40 +49,270 @@ AFS::FileId convertToAbstractFileId(const zen::FileId& fid) } -class DirTraverser +#if !defined ZEN_LINUX || defined ZEN_LINUX_TRAVERSER_MODERN +struct FsItemRaw { -public: - static void execute(const Zstring& baseDirPath, AFS::TraverserCallback& sink) + Zstring itemName; + Zstring itemPath; +}; +std::vector getDirContentFlat(const Zstring& dirPath) //throw FileError +{ + //no need to check for endless recursion: + //1. Linux has a fixed limit on the number of symbolic links in a path + //2. fails with "too many open files" or "path too long" before reaching stack overflow + + DIR* folder = ::opendir(dirPath.c_str()); //directory must NOT end with path separator, except "/" + if (!folder) + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot open directory %x."), L"%x", fmtPath(dirPath)), L"opendir"); + ZEN_ON_SCOPE_EXIT(::closedir(folder)); //never close nullptr handles! -> crash + + std::vector output; + for (;;) + { + /* + Linux: + http://man7.org/linux/man-pages/man3/readdir_r.3.html + "It is recommended that applications use readdir(3) instead of readdir_r" + "... in modern implementations (including the glibc implementation), concurrent calls to readdir(3) that specify different directory streams are thread-safe" + + macOS: + - libc: readdir thread-safe already in code from 2000: https://opensource.apple.com/source/Libc/Libc-166/gen.subproj/readdir.c.auto.html + - and in the latest version from 2017: https://opensource.apple.com/source/Libc/Libc-1244.30.3/gen/FreeBSD/readdir.c.auto.html + */ + errno = 0; + const struct ::dirent* dirEntry = ::readdir(folder); + if (!dirEntry) + { + if (errno == 0) //errno left unchanged => no more items + return output; + + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), L"readdir"); + //don't retry but restart dir traversal on error! https://blogs.msdn.microsoft.com/oldnewthing/20140612-00/?p=753/ + } + + const char* itemNameRaw = dirEntry->d_name; //evaluate dirEntry *before* going into recursion + + //skip "." and ".." + if (itemNameRaw[0] == '.' && + (itemNameRaw[1] == 0 || (itemNameRaw[1] == '.' && itemNameRaw[2] == 0))) + continue; + const Zstring& itemName = itemNameRaw; + if (itemName.empty()) //checks result of normalizeUtfForPosix, too! + throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), L"readdir: Data corruption; item with empty name."); + + const Zstring& itemPath = appendSeparator(dirPath) + itemName; + + output.push_back({ itemName, itemPath}); + } +} + + +struct ItemDetailsRaw +{ + ItemType type; + time_t modTime; //number of seconds since Jan. 1st 1970 UTC + uint64_t fileSize; //unit: bytes! + FileId fileId; +}; +ItemDetailsRaw getItemDetails(const Zstring& itemPath) //throw FileError +{ + struct ::stat statData = {}; + if (::lstat(itemPath.c_str(), &statData) != 0) //lstat() does not resolve symlinks + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"lstat"); + + if (S_ISLNK(statData.st_mode)) //on Linux there is no distinction between file and directory symlinks! + return { ItemType::SYMLINK, statData.st_mtime, 0, extractFileId(statData) }; + + else if (S_ISDIR(statData.st_mode)) //a directory + return { ItemType::FOLDER, statData.st_mtime, 0, extractFileId(statData) }; + + else //a file or named pipe, ect. => dont't check using S_ISREG(): see comment in file_traverser.cpp + return { ItemType::FILE, statData.st_mtime, makeUnsigned(statData.st_size), extractFileId(statData) }; +} + +ItemDetailsRaw getSymlinkTargetDetails(const Zstring& linkPath) //throw FileError +{ + struct ::stat statData = {}; + if (::stat(linkPath.c_str(), &statData) != 0) + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(linkPath)), L"stat"); + + if (S_ISDIR(statData.st_mode)) //a directory + return { ItemType::FOLDER, statData.st_mtime, 0, extractFileId(statData) }; + else //a file or named pipe, ect. + return { ItemType::FILE, statData.st_mtime, makeUnsigned(statData.st_size), extractFileId(statData) }; +} + + +struct GetDirDetails +{ + GetDirDetails(const Zstring& dirPath) : dirPath_(dirPath) {} + + using Result = std::vector; + Result operator()() const { - DirTraverser(baseDirPath, sink); //throw X + return getDirContentFlat(dirPath_); //throw FileError } private: - DirTraverser(const Zstring& baseDirPath, AFS::TraverserCallback& sink) + Zstring dirPath_; +}; + + +struct GetItemDetails //details not already retrieved by raw folder traversal +{ + GetItemDetails(const FsItemRaw& rawItem) : rawItem_(rawItem) {} + + struct Result + { + FsItemRaw raw; + ItemDetailsRaw details; + }; + Result operator()() const { - /* quote: "Since POSIX.1 does not specify the size of the d_name field, and other nonstandard fields may precede - that field within the dirent structure, portable applications that use readdir_r() should allocate - the buffer whose address is passed in entry as follows: - len = offsetof(struct dirent, d_name) + pathconf(dirPath, _PC_NAME_MAX) + 1 - entryp = malloc(len); */ - const size_t nameMax = std::max(::pathconf(baseDirPath.c_str(), _PC_NAME_MAX), 10000); //::pathconf may return long(-1) - buffer_.resize(offsetof(struct ::dirent, d_name) + nameMax + 1); + return { rawItem_, getItemDetails(rawItem_.itemPath) }; //throw FileError + } + +private: + FsItemRaw rawItem_; +}; + + +struct GetLinkTargetDetails +{ + GetLinkTargetDetails(const FsItemRaw& rawItem, const ItemDetailsRaw& linkDetails) : rawItem_(rawItem), linkDetails_(linkDetails) {} - traverse(baseDirPath, sink); //throw X + struct Result + { + FsItemRaw raw; + ItemDetailsRaw link; + ItemDetailsRaw target; + }; + Result operator()() const + { + return { rawItem_, linkDetails_, getSymlinkTargetDetails(rawItem_.itemPath) }; //throw FileError } - DirTraverser (const DirTraverser&) = delete; - DirTraverser& operator=(const DirTraverser&) = delete; +private: + FsItemRaw rawItem_; + ItemDetailsRaw linkDetails_; +}; + + +void traverseFolderParallelNative(const std::vector>>& initialWorkItems, size_t parallelOps) //throw X +{ + std::vector> genItems; + + for (const auto& item : initialWorkItems) + genItems.push_back({ GetDirDetails(item.first), + Zstring() /*errorItemName*/, 0 /*errorRetryCount*/, item.second /*TraverserCallback*/ }); + + GenericDirTraverser(std::move(genItems), parallelOps, "Native Traverser"); //throw X +} +} - void traverse(const Zstring& dirPath, AFS::TraverserCallback& sink) //throw X +namespace fff //specialization in original namespace needed by GCC 6 +{ +template <> +template <> +void GenericDirTraverser::evalResultValue(const GetDirDetails::Result& r, std::shared_ptr& cb) //throw X +{ + for (const FsItemRaw& rawItem : r) + asyncWorkload_.run({ GetItemDetails(rawItem), + rawItem.itemName, 0 /*errorRetryCount*/, cb }, threadGroup_); +} + + +template <> +template <> +void GenericDirTraverser::evalResultValue(const GetItemDetails::Result& r, std::shared_ptr& cb) //throw X +{ + switch (r.details.type) { - tryReportingDirError([&] //throw X + case ItemType::FILE: + cb->onFile({ r.raw.itemName, r.details.fileSize, r.details.modTime, convertToAbstractFileId(r.details.fileId), nullptr /*symlinkInfo*/ }); //throw X + break; + + case ItemType::FOLDER: + if (std::shared_ptr cbSub = cb->onFolder({ r.raw.itemName, nullptr /*symlinkInfo*/ })) //throw X + asyncWorkload_.run({ GetDirDetails(r.raw.itemPath), + Zstring() /*errorItemName*/, 0 /*errorRetryCount*/, std::move(cbSub) }, threadGroup_); + break; + + case ItemType::SYMLINK: + switch (cb->onSymlink({ r.raw.itemName, r.details.modTime })) //throw X + { + case AFS::TraverserCallback::LINK_FOLLOW: + asyncWorkload_.run({ GetLinkTargetDetails(r.raw, r.details), + r.raw.itemName, 0 /*errorRetryCount*/, cb }, threadGroup_); + break; + + case AFS::TraverserCallback::LINK_SKIP: + break; + } + break; + } +} + + +template <> +template <> +void GenericDirTraverser::evalResultValue(const GetLinkTargetDetails::Result& r, std::shared_ptr& cb) //throw X +{ + assert(r.link.type == ItemType::SYMLINK && r.target.type != ItemType::SYMLINK); + + const AFS::TraverserCallback::SymlinkInfo linkInfo = { r.raw.itemName, r.link.modTime }; + + if (r.target.type == ItemType::FOLDER) + { + if (std::shared_ptr cbSub = cb->onFolder({ r.raw.itemName, &linkInfo })) //throw X + asyncWorkload_.run({ GetDirDetails(r.raw.itemPath), + Zstring() /*errorItemName*/, 0 /*errorRetryCount*/, std::move(cbSub) }, threadGroup_); + } + else //a file or named pipe, ect. + cb->onFile({ r.raw.itemName, r.target.fileSize, r.target.modTime, convertToAbstractFileId(r.target.fileId), &linkInfo }); //throw X +} +} + +namespace +{ +#elif defined ZEN_LINUX && defined ZEN_LINUX_TRAVERSER_LEGACY //support legacy GCC versions < 6 +static_assert(__GNUC__ < 6, ""); + +class LegacyDirTraverser +{ +public: + static void execute(const std::vector>>& initialWorkItems, size_t parallelOps) + { + assert(parallelOps == 1); + //Parallel operations > 1 are not supported because FreeFileSync was built with GCC < version 6.", + // => at least parallel_scan.h will show the correct number of threads + + for (const auto& item : initialWorkItems) + LegacyDirTraverser(item.first, *item.second); //throw X + } + +private: + LegacyDirTraverser (const LegacyDirTraverser&) = delete; + LegacyDirTraverser& operator=(const LegacyDirTraverser&) = delete; + + LegacyDirTraverser(const Zstring& baseDirPath, AFS::TraverserCallback& cb) + { + //set the initial work load + workload_.push_back({ baseDirPath, std::shared_ptr(&cb, [](AFS::TraverserCallback*) {}) }); + + while (!workload_.empty()) { - traverseWithException(dirPath, sink); //throw FileError, X - }, sink); + WorkItem wi = std::move(workload_. back()); //yes, no strong exception guarantee (std::bad_alloc) + /**/ workload_.pop_back(); // + + tryReportingDirError([&] //throw X + { + traverseWithException(wi.dirPath, *wi.cb); //throw FileError, X + }, *wi.cb); + } } - void traverseWithException(const Zstring& dirPath, AFS::TraverserCallback& sink) //throw FileError, X + void traverseWithException(const Zstring& dirPath, AFS::TraverserCallback& cb) //throw FileError, X { //no need to check for endless recursion: //1. Linux has a fixed limit on the number of symbolic links in a path @@ -96,23 +325,37 @@ private: for (;;) { - struct ::dirent* dirEntry = nullptr; - if (::readdir_r(folder, reinterpret_cast< ::dirent*>(&buffer_[0]), &dirEntry) != 0) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), L"readdir_r"); - //don't retry but restart dir traversal on error! https://blogs.msdn.microsoft.com/oldnewthing/20140612-00/?p=753/ + /* + Linux: + http://man7.org/linux/man-pages/man3/readdir_r.3.html + "It is recommended that applications use readdir(3) instead of readdir_r" + "... in modern implementations (including the glibc implementation), concurrent calls to readdir(3) that specify different directory streams are thread-safe" + + macOS: + - libc: readdir thread-safe already in code from 2000: https://opensource.apple.com/source/Libc/Libc-166/gen.subproj/readdir.c.auto.html + - and in the latest version from 2017: https://opensource.apple.com/source/Libc/Libc-1244.30.3/gen/FreeBSD/readdir.c.auto.html + */ + errno = 0; + const struct ::dirent* dirEntry = ::readdir(folder); + if (!dirEntry) + { + if (errno == 0) //errno left unchanged => no more items + return; - if (!dirEntry) //no more items - return; + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), L"readdir"); + //don't retry but restart dir traversal on error! https://blogs.msdn.microsoft.com/oldnewthing/20140612-00/?p=753/ + } - const char* itemNameRaw = dirEntry->d_name; //evaluate dirEntry *before* going into recursion => we use a single "buffer"! + const char* itemNameRaw = dirEntry->d_name; //evaluate dirEntry *before* going into recursion //skip "." and ".." if (itemNameRaw[0] == '.' && (itemNameRaw[1] == 0 || (itemNameRaw[1] == '.' && itemNameRaw[2] == 0))) continue; + const Zstring& itemName = itemNameRaw; if (itemName.empty()) //checks result of normalizeUtfForPosix, too! - throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), L"readdir_r: Data corruption; item with empty name."); + throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), L"readdir: Data corruption; item with empty name."); const Zstring& itemPath = appendSeparator(dirPath) + itemName; @@ -121,14 +364,14 @@ private: { if (::lstat(itemPath.c_str(), &statData) != 0) //lstat() does not resolve symlinks THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"lstat"); - }, sink, itemName)) + }, cb, itemName)) continue; //ignore error: skip file if (S_ISLNK(statData.st_mode)) //on Linux there is no distinction between file and directory symlinks! { const AFS::TraverserCallback::SymlinkInfo linkInfo = { itemName, statData.st_mtime }; - switch (sink.onSymlink(linkInfo)) //throw X + switch (cb.onSymlink(linkInfo)) //throw X { case AFS::TraverserCallback::LINK_FOLLOW: { @@ -139,19 +382,19 @@ private: { if (::stat(itemPath.c_str(), &statDataTrg) != 0) THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(itemPath)), L"stat"); - }, sink, itemName); + }, cb, itemName); if (validLink) { if (S_ISDIR(statDataTrg.st_mode)) //a directory { - if (std::unique_ptr trav = sink.onFolder({ itemName, &linkInfo })) //throw X - traverse(itemPath, *trav); //throw X + if (std::shared_ptr trav = cb.onFolder({ itemName, &linkInfo })) //throw X + workload_.push_back({ itemPath, std::move(trav) }); } else //a file or named pipe, ect. { AFS::TraverserCallback::FileInfo fi = { itemName, makeUnsigned(statDataTrg.st_size), statDataTrg.st_mtime, convertToAbstractFileId(extractFileId(statDataTrg)), &linkInfo }; - sink.onFile(fi); //throw X + cb.onFile(fi); //throw X } } // else //broken symlink -> ignore: it's client's responsibility to handle error! @@ -164,27 +407,34 @@ private: } else if (S_ISDIR(statData.st_mode)) //a directory { - if (std::unique_ptr trav = sink.onFolder({ itemName, nullptr })) //throw X - traverse(itemPath, *trav); //throw X + if (std::shared_ptr trav = cb.onFolder({ itemName, nullptr })) //throw X + workload_.push_back({ itemPath, std::move(trav) }); + warn_static("workload_ item order is reversed => fix!?") } - else //a file or named pipe, ect. + else //a file or named pipe, ect. => dont't check using S_ISREG(): see comment in file_traverser.cpp { AFS::TraverserCallback::FileInfo fi = { itemName, makeUnsigned(statData.st_size), statData.st_mtime, convertToAbstractFileId(extractFileId(statData)), nullptr /*symlinkInfo*/ }; - sink.onFile(fi); //throw X + cb.onFile(fi); //throw X } - /* - It may be a good idea to not check "S_ISREG(statData.st_mode)" explicitly and to not issue an error message on other types to support these scenarios: - - RTS setup watch (essentially wants to read directories only) - - removeDirectory (wants to delete everything; pipes can be deleted just like files via "unlink") - - However an "open" on a pipe will block (https://sourceforge.net/p/freefilesync/bugs/221/), so the copy routines need to be smarter!! - */ } } - std::vector buffer_; + struct WorkItem + { + Zstring dirPath; + std::shared_ptr cb; + }; + + std::vector workload_; }; + +void traverseFolderParallelNative(const std::vector>>& initialWorkItems, size_t parallelOps) //throw X +{ + LegacyDirTraverser::execute(initialWorkItems, parallelOps); //throw X +} +#endif + //==================================================================================================== //==================================================================================================== @@ -389,9 +639,15 @@ private: } //---------------------------------------------------------------------------------------------------------------- - void traverseFolder(const AfsPath& afsPath, TraverserCallback& sink /*throw X*/) const override //throw X + void traverseFolderParallel(const TraverserWorkloadImpl& workload /*throw X*/, size_t parallelOps) const override { - DirTraverser::execute(getNativePath(afsPath), sink); //throw X + //initComForThread() -> done on traverser worker threads + + std::vector>> initialWorkItems; + for (const auto& item : workload) + initialWorkItems.emplace_back(getNativePath(item.first), item.second); + + traverseFolderParallelNative(initialWorkItems, parallelOps); //throw X } //---------------------------------------------------------------------------------------------------------------- @@ -525,6 +781,8 @@ private: +//- return true if item existed +//- multi-threaded access: internally synchronized! bool RecycleSessionNative::recycleItem(const AbstractPath& itemPath, const Zstring& logicalRelPath) //throw FileError { assert(!startsWith(logicalRelPath, FILE_NAME_SEPARATOR)); @@ -572,9 +830,6 @@ AbstractPath fff::createItemPathNativeNoFormatting(const Zstring& nativePath) // { if (const Opt comp = parsePathComponents(nativePath)) return AbstractPath(std::make_shared(comp->rootPath), AfsPath(comp->relPath)); - else - { - // assert(nativePath.empty()); - return AbstractPath(std::make_shared(nativePath), AfsPath(Zstring())); - } + else //path syntax broken + return AbstractPath(std::make_shared(nativePath), AfsPath()); } diff --git a/FreeFileSync/Source/lib/dir_exist_async.h b/FreeFileSync/Source/lib/dir_exist_async.h index ea518316..4daee747 100755 --- a/FreeFileSync/Source/lib/dir_exist_async.h +++ b/FreeFileSync/Source/lib/dir_exist_async.h @@ -22,38 +22,65 @@ namespace //directory existence checking may hang for non-existent network drives => run asynchronously and update UI! //- check existence of all directories in parallel! (avoid adding up search times if multiple network drives are not reachable) //- add reasonable time-out time! -//- avoid checking duplicate entries by design: std::set +//- avoid checking duplicate entries => std::set struct FolderStatus { - std::set existing; - std::set notExisting; - std::map failedChecks; + std::set existing; + std::set notExisting; + std::map failedChecks; }; -FolderStatus getFolderStatusNonBlocking(const std::set& folderPaths, int folderAccessTimeout, - bool allowUserInteraction, ProcessCallback& procCallback) +FolderStatus getFolderStatusNonBlocking(const std::set& folderPaths, const std::map& deviceParallelOps, + int folderAccessTimeout, bool allowUserInteraction, + ProcessCallback& procCallback) { using namespace zen; - FolderStatus output; - - std::list>> futureInfo; + //aggregate folder paths that are on the same root device: see parallel_scan.h + std::map> perDevicePaths; for (const AbstractPath& folderPath : folderPaths) if (!AFS::isNullPath(folderPath)) //skip empty dirs - futureInfo.emplace_back(folderPath, runAsync([folderPath, allowUserInteraction] //AbstractPath is thread-safe like an int! :) + perDevicePaths[AFS::getPathComponents(folderPath).rootPath].insert(folderPath); + warn_static("relax for native paths? consider hanging network share!?") + + std::list>> futureInfo; + + std::list>> perDeviceThreads; + for (const auto& item : perDevicePaths) + { + const AbstractPath& rootPath = item.first; + + auto itParOps = deviceParallelOps.find(rootPath); + const size_t parallelOps = std::max(itParOps != deviceParallelOps.end() ? itParOps->second : 1, 1); + const size_t threadCount = std::min(parallelOps, item.second.size()); + + perDeviceThreads.emplace_back(threadCount, "DirExist Device: " + utfTo(AFS::getDisplayPath(rootPath))); + auto& threadGroup = perDeviceThreads.back(); + + for (const AbstractPath& folderPath : item.second) { - //1. login to network share, open FTP connection, ect. - AFS::connectNetworkFolder(folderPath, allowUserInteraction); //throw FileError + std::packaged_task pt([folderPath, allowUserInteraction] //AbstractPath is thread-safe like an int! :) + { + //1. login to network share, open FTP connection, ect. + AFS::connectNetworkFolder(folderPath, allowUserInteraction); //throw FileError - //2. check dir existence - return static_cast(AFS::getItemTypeIfExists(folderPath)); //throw FileError - //TODO: consider ItemType:FILE a failure instead? In any case: return "false" IFF nothing (of any type) exists - })); + //2. check dir existence + return static_cast(AFS::getItemTypeIfExists(folderPath)); //throw FileError + //TODO: consider ItemType:FILE a failure instead? In any case: return "false" IFF nothing (of any type) exists + }); + auto fut = pt.get_future(); + threadGroup.run(std::move(pt)); + + futureInfo.emplace_back(folderPath, std::move(fut)); + } + } //don't wait (almost) endlessly like Win32 would on non-existing network shares: const auto startTime = std::chrono::steady_clock::now(); + FolderStatus output; + for (auto& fi : futureInfo) { const std::wstring& displayPathFmt = fmtPath(AFS::getDisplayPath(fi.first)); @@ -79,7 +106,6 @@ FolderStatus getFolderStatusNonBlocking(const std::set parentDirPath = getParentFolderPath(lockFilePath_); + setCurrentThreadName(("DirLock: " + (parentDirPath ? utfTo(*parentDirPath) : "")).c_str()); + warn_static("set nice name for setCurrentThreadName in other places, too") try { for (;;) diff --git a/FreeFileSync/Source/lib/generate_logfile.cpp b/FreeFileSync/Source/lib/generate_logfile.cpp new file mode 100755 index 00000000..2ee7f309 --- /dev/null +++ b/FreeFileSync/Source/lib/generate_logfile.cpp @@ -0,0 +1,246 @@ +// ***************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * +// ***************************************************************************** + +#include "generate_logfile.h" +#include +#include + +using namespace zen; +using namespace fff; + + +namespace +{ +std::wstring generateLogHeader(const LogSummary& s) +{ + assert(s.itemsProcessed <= s.itemsTotal); + assert(s.bytesProcessed <= s.bytesTotal); + + std::wstring output; + + //write header + std::wstring headerLine = formatTime(FORMAT_DATE); + if (!s.jobName.empty()) + headerLine += L" | " + s.jobName; + headerLine += L" | " + s.finalStatus; + + //assemble results box + std::vector results; + results.push_back(headerLine); + results.push_back(L""); + + const wchar_t tabSpace[] = L" "; + + std::wstring itemsProc = tabSpace + _("Items processed:") + L" " + formatNumber(s.itemsProcessed); //show always, even if 0! + if (s.itemsProcessed != 0 || s.bytesProcessed != 0) //[!] don't show 0 bytes processed if 0 items were processed + itemsProc += + L" (" + formatFilesizeShort(s.bytesProcessed) + L")"; + results.push_back(itemsProc); + + if (s.itemsTotal != 0 || s.bytesTotal != 0) //=: sync phase was reached and there were actual items to sync + { + if (s.itemsProcessed != s.itemsTotal || + s.bytesProcessed != s.bytesTotal) + results.push_back(tabSpace + _("Items remaining:") + L" " + formatNumber(s.itemsTotal - s.itemsProcessed) + L" (" + formatFilesizeShort(s.bytesTotal - s.bytesProcessed) + L")"); + } + + results.push_back(tabSpace + _("Total time:") + L" " + copyStringTo(wxTimeSpan::Seconds(s.totalTime).Format())); + + //calculate max width, this considers UTF-16 only, not true Unicode...but maybe good idea? those 2-char-UTF16 codes are usually wider than fixed width chars anyway! + size_t sepLineLen = 0; + for (const std::wstring& str : results) sepLineLen = std::max(sepLineLen, str.size()); + + output.resize(output.size() + sepLineLen + 1, L'_'); + output += L'\n'; + + for (const std::wstring& str : results) { output += L'|'; output += str; output += L'\n'; } + + output += L'|'; + output.resize(output.size() + sepLineLen, L'_'); + output += L'\n'; + + return output; +} +} + + +void fff::streamToLogFile(const LogSummary& summary, //throw FileError + const zen::ErrorLog& log, + AFS::OutputStream& streamOut) +{ + const std::string header = replaceCpy(utfTo(generateLogHeader(summary)), '\n', LINE_BREAK); //don't replace line break any earlier + + streamOut.write(&header[0], header.size()); //throw FileError, X + + //write log items in blocks instead of creating one big string: memory allocation might fail; think 1 million entries! + std::string buffer; + buffer += LINE_BREAK; + + for (const LogEntry& entry : log) + { + buffer += replaceCpy(utfTo(formatMessage(entry)), '\n', LINE_BREAK); + buffer += LINE_BREAK; + + streamOut.write(&buffer[0], buffer.size()); //throw FileError, X + buffer.clear(); + } +} + + +void fff::saveToLastSyncsLog(const LogSummary& summary, //throw FileError + const zen::ErrorLog& log, + size_t maxBytesToWrite, //log may be *huge*, e.g. 1 million items; LastSyncs.log *must not* create performance problems! + const std::function& notifyStatus) +{ + const Zstring filePath = getConfigDirPathPf() + Zstr("LastSyncs.log"); + + Utf8String newStream = utfTo(generateLogHeader(summary)); + replace(newStream, '\n', LINE_BREAK); //don't replace line break any earlier + newStream += LINE_BREAK; + + //check size of "newStream": memory allocation might fail - think 1 million entries! + for (const LogEntry& entry : log) + { + newStream += replaceCpy(utfTo(formatMessage(entry)), '\n', LINE_BREAK); + newStream += LINE_BREAK; + + if (newStream.size() > maxBytesToWrite) + { + newStream += "[...]"; + newStream += LINE_BREAK; + break; + } + } + + auto notifyUnbufferedIOLoad = [notifyStatus, + bytesRead_ = int64_t(0), + msg_ = replaceCpy(_("Loading file %x..."), L"%x", fmtPath(filePath))] + (int64_t bytesDelta) mutable + { + if (notifyStatus) + notifyStatus(msg_ + L" (" + formatFilesizeShort(bytesRead_ += bytesDelta) + L")"); /*throw X*/ + }; + + auto notifyUnbufferedIOSave = [notifyStatus, + bytesWritten_ = int64_t(0), + msg_ = replaceCpy(_("Saving file %x..."), L"%x", fmtPath(filePath))] + (int64_t bytesDelta) mutable + { + if (notifyStatus) + notifyStatus(msg_ + L" (" + formatFilesizeShort(bytesWritten_ += bytesDelta) + L")"); /*throw X*/ + }; + + //fill up the rest of permitted space by appending old log + if (newStream.size() < maxBytesToWrite) + { + Utf8String oldStream; + try + { + oldStream = loadBinContainer(filePath, notifyUnbufferedIOLoad); //throw FileError, X + //Note: we also report the loaded bytes via onUpdateSaveStatus()! + } + catch (FileError&) {} + + if (!oldStream.empty()) + { + newStream += LINE_BREAK; + newStream += LINE_BREAK; + newStream += oldStream; //implicitly limited by "maxBytesToWrite"! + + //truncate size if required + if (newStream.size() > maxBytesToWrite) + { + //but do not cut in the middle of a row + auto it = std::search(newStream.cbegin() + maxBytesToWrite, newStream.cend(), std::begin(LINE_BREAK), std::end(LINE_BREAK) - 1); + if (it != newStream.cend()) + { + newStream.resize(it - newStream.cbegin()); + newStream += LINE_BREAK; + + newStream += "[...]"; + newStream += LINE_BREAK; + } + } + } + } + + saveBinContainer(filePath, newStream, notifyUnbufferedIOSave); //throw FileError, X +} + + +namespace +{ +struct LogTraverserCallback: public AFS::TraverserCallback +{ + LogTraverserCallback(const Zstring& prefix, const std::function& onUpdateStatus) : + prefix_(prefix), + onUpdateStatus_(onUpdateStatus) {} + + void onFile(const FileInfo& fi) override + { + if (startsWith(fi.itemName, prefix_, CmpFilePath() /*even on Linux!*/) && endsWith(fi.itemName, Zstr(".log"), CmpFilePath())) + logFileNames_.push_back(fi.itemName); + + if (onUpdateStatus_) onUpdateStatus_(); + } + std::shared_ptr onFolder (const FolderInfo& fi) override { return nullptr; } + HandleLink onSymlink(const SymlinkInfo& si) override { return TraverserCallback::LINK_SKIP; } + + HandleError reportDirError (const std::wstring& msg, size_t retryNumber ) override { setError(msg); return ON_ERROR_CONTINUE; } + HandleError reportItemError(const std::wstring& msg, size_t retryNumber, const Zstring& itemName) override { setError(msg); return ON_ERROR_CONTINUE; } + + const std::vector& refFileNames() const { return logFileNames_; } + const Opt& getLastError() const { return lastError_; } + +private: + void setError(const std::wstring& msg) //implement late failure + { + if (!lastError_) + lastError_ = FileError(msg); + } + + const Zstring prefix_; + const std::function onUpdateStatus_; + std::vector logFileNames_; //out + Opt lastError_; +}; +} + + +void fff::limitLogfileCount(const AbstractPath& logFolderPath, const std::wstring& jobname, size_t maxCount, //throw FileError + const std::function& notifyStatus) +{ + const Zstring prefix = utfTo(jobname); + const std::wstring cleaningMsg = _("Cleaning up old log files...");; + + //traverse source directory one level deep + auto lt = std::make_shared(prefix, [&] { if (notifyStatus) notifyStatus(cleaningMsg); }); + const AFS::PathComponents pc = AFS::getPathComponents(logFolderPath); + AFS::traverseFolderParallel(pc.rootPath, {{ pc.relPath, lt }}, 1 /*parallelOps*/); //throw FileError + + std::vector logFileNames = lt->refFileNames(); + Opt lastError = lt->getLastError(); + + if (logFileNames.size() > maxCount) + { + //delete oldest logfiles: take advantage of logfile naming convention to find them + std::nth_element(logFileNames.begin(), logFileNames.end() - maxCount, logFileNames.end(), LessFilePath()); + + std::for_each(logFileNames.begin(), logFileNames.end() - maxCount, [&](const Zstring& logFileName) + { + const AbstractPath filePath = AFS::appendRelPath(logFolderPath, logFileName); + if (notifyStatus) notifyStatus(cleaningMsg + L" " + fmtPath(AFS::getDisplayPath(filePath))); + + try + { + AFS::removeFilePlain(filePath); //throw FileError + } + catch (const FileError& e) { if (!lastError) lastError = e; }; + }); + } + + if (lastError) //late failure! + throw* lastError; +} diff --git a/FreeFileSync/Source/lib/generate_logfile.h b/FreeFileSync/Source/lib/generate_logfile.h index 0eb85762..ecd1db1f 100755 --- a/FreeFileSync/Source/lib/generate_logfile.h +++ b/FreeFileSync/Source/lib/generate_logfile.h @@ -8,16 +8,13 @@ #define GENERATE_LOGFILE_H_931726432167489732164 #include -#include -#include #include "ffs_paths.h" -#include "../fs/abstract.h" #include "../file_hierarchy.h" namespace fff { -struct SummaryInfo +struct LogSummary { std::wstring jobName; //may be empty std::wstring finalStatus; @@ -28,11 +25,11 @@ struct SummaryInfo int64_t totalTime = 0; //unit: [sec] }; -void streamToLogFile(const SummaryInfo& summary, //throw FileError +void streamToLogFile(const LogSummary& summary, //throw FileError const zen::ErrorLog& log, AFS::OutputStream& streamOut); -void saveToLastSyncsLog(const SummaryInfo& summary, //throw FileError +void saveToLastSyncsLog(const LogSummary& summary, //throw FileError const zen::ErrorLog& log, size_t maxBytesToWrite, const std::function& notifyStatus); @@ -40,169 +37,8 @@ void saveToLastSyncsLog(const SummaryInfo& summary, //throw FileError inline Zstring getDefaultLogFolderPath() { return getConfigDirPathPf() + Zstr("Logs") ; } - -//####################### implementation ####################### -namespace -{ -std::wstring generateLogHeader(const SummaryInfo& s) -{ - using namespace zen; - assert(s.itemsProcessed <= s.itemsTotal); - assert(s.bytesProcessed <= s.bytesTotal); - - std::wstring output; - - //write header - std::wstring headerLine = formatTime(FORMAT_DATE); - if (!s.jobName.empty()) - headerLine += L" | " + s.jobName; - headerLine += L" | " + s.finalStatus; - - //assemble results box - std::vector results; - results.push_back(headerLine); - results.push_back(L""); - - const wchar_t tabSpace[] = L" "; - - std::wstring itemsProc = tabSpace + _("Items processed:") + L" " + formatNumber(s.itemsProcessed); //show always, even if 0! - if (s.itemsProcessed != 0 || s.bytesProcessed != 0) //[!] don't show 0 bytes processed if 0 items were processed - itemsProc += + L" (" + formatFilesizeShort(s.bytesProcessed) + L")"; - results.push_back(itemsProc); - - if (s.itemsTotal != 0 || s.bytesTotal != 0) //=: sync phase was reached and there were actual items to sync - { - if (s.itemsProcessed != s.itemsTotal || - s.bytesProcessed != s.bytesTotal) - results.push_back(tabSpace + _("Items remaining:") + L" " + formatNumber(s.itemsTotal - s.itemsProcessed) + L" (" + formatFilesizeShort(s.bytesTotal - s.bytesProcessed) + L")"); - } - - results.push_back(tabSpace + _("Total time:") + L" " + copyStringTo(wxTimeSpan::Seconds(s.totalTime).Format())); - - //calculate max width, this considers UTF-16 only, not true Unicode...but maybe good idea? those 2-char-UTF16 codes are usually wider than fixed width chars anyway! - size_t sepLineLen = 0; - for (const std::wstring& str : results) sepLineLen = std::max(sepLineLen, str.size()); - - output.resize(output.size() + sepLineLen + 1, L'_'); - output += L'\n'; - - for (const std::wstring& str : results) { output += L'|'; output += str; output += L'\n'; } - - output += L'|'; - output.resize(output.size() + sepLineLen, L'_'); - output += L'\n'; - - return output; -} -} - - -inline -void streamToLogFile(const SummaryInfo& summary, //throw FileError - const zen::ErrorLog& log, - AFS::OutputStream& streamOut) -{ - using namespace zen; - const std::string header = replaceCpy(utfTo(generateLogHeader(summary)), '\n', LINE_BREAK); //don't replace line break any earlier - - streamOut.write(&header[0], header.size()); //throw FileError, X - - //write log items in blocks instead of creating one big string: memory allocation might fail; think 1 million entries! - std::string buffer; - buffer += LINE_BREAK; - - for (const LogEntry& entry : log) - { - buffer += replaceCpy(utfTo(formatMessage(entry)), '\n', LINE_BREAK); - buffer += LINE_BREAK; - - streamOut.write(&buffer[0], buffer.size()); //throw FileError, X - buffer.clear(); - } -} - - -inline -void saveToLastSyncsLog(const SummaryInfo& summary, //throw FileError - const zen::ErrorLog& log, - size_t maxBytesToWrite, //log may be *huge*, e.g. 1 million items; LastSyncs.log *must not* create performance problems! - const std::function& notifyStatus) -{ - using namespace zen; - const Zstring filePath = getConfigDirPathPf() + Zstr("LastSyncs.log"); - - Utf8String newStream = utfTo(generateLogHeader(summary)); - replace(newStream, '\n', LINE_BREAK); //don't replace line break any earlier - newStream += LINE_BREAK; - - //check size of "newStream": memory allocation might fail - think 1 million entries! - for (const LogEntry& entry : log) - { - newStream += replaceCpy(utfTo(formatMessage(entry)), '\n', LINE_BREAK); - newStream += LINE_BREAK; - - if (newStream.size() > maxBytesToWrite) - { - newStream += "[...]"; - newStream += LINE_BREAK; - break; - } - } - - auto notifyUnbufferedIOLoad = [notifyStatus, - bytesRead_ = int64_t(0), - msg_ = replaceCpy(_("Loading file %x..."), L"%x", fmtPath(filePath))] - (int64_t bytesDelta) mutable - { - if (notifyStatus) - notifyStatus(msg_ + L" (" + formatFilesizeShort(bytesRead_ += bytesDelta) + L")"); /*throw X*/ - }; - - auto notifyUnbufferedIOSave = [notifyStatus, - bytesWritten_ = int64_t(0), - msg_ = replaceCpy(_("Saving file %x..."), L"%x", fmtPath(filePath))] - (int64_t bytesDelta) mutable - { - if (notifyStatus) - notifyStatus(msg_ + L" (" + formatFilesizeShort(bytesWritten_ += bytesDelta) + L")"); /*throw X*/ - }; - - //fill up the rest of permitted space by appending old log - if (newStream.size() < maxBytesToWrite) - { - Utf8String oldStream; - try - { - oldStream = loadBinContainer(filePath, notifyUnbufferedIOLoad); //throw FileError, X - //Note: we also report the loaded bytes via onUpdateSaveStatus()! - } - catch (FileError&) {} - - if (!oldStream.empty()) - { - newStream += LINE_BREAK; - newStream += LINE_BREAK; - newStream += oldStream; //implicitly limited by "maxBytesToWrite"! - - //truncate size if required - if (newStream.size() > maxBytesToWrite) - { - //but do not cut in the middle of a row - auto it = std::search(newStream.cbegin() + maxBytesToWrite, newStream.cend(), std::begin(LINE_BREAK), std::end(LINE_BREAK) - 1); - if (it != newStream.cend()) - { - newStream.resize(it - newStream.cbegin()); - newStream += LINE_BREAK; - - newStream += "[...]"; - newStream += LINE_BREAK; - } - } - } - } - - saveBinContainer(filePath, newStream, notifyUnbufferedIOSave); //throw FileError, X -} +void limitLogfileCount(const AbstractPath& logFolderPath, const std::wstring& jobname, size_t maxCount, //throw FileError + const std::function& notifyStatus); } #endif //GENERATE_LOGFILE_H_931726432167489732164 diff --git a/FreeFileSync/Source/lib/hard_filter.cpp b/FreeFileSync/Source/lib/hard_filter.cpp index a24dc65f..3008aa24 100755 --- a/FreeFileSync/Source/lib/hard_filter.cpp +++ b/FreeFileSync/Source/lib/hard_filter.cpp @@ -291,9 +291,9 @@ bool NameFilter::passDirFilter(const Zstring& relDirPath, bool* childItemMightMa This is not a problem for folder traversal which stops at the first *childItemMightMatch == false anyway, but other code continues recursing further, e.g. the database update code in db_file.cpp recurses unconditionally without filter check! It's possible to construct edge cases with incorrect behavior if "childItemMightMatch" were not optional: - 1. two folders including a sub folder with some files are in sync with up-to-date database files - 2. deny access to this sub folder on both sides and start sync ignoring errors - 3. => database entries of this sub folder are incorrectly deleted! (if sub-folder is excluded, but child items are not!) + 1. two folders including a subfolder with some files are in sync with up-to-date database files + 2. deny access to this subfolder on both sides and start sync ignoring errors + 3. => database entries of this subfolder are incorrectly deleted! (if sub-folder is excluded, but child items are not!) */ return false; } diff --git a/FreeFileSync/Source/lib/hard_filter.h b/FreeFileSync/Source/lib/hard_filter.h index 0f312ea6..f829761e 100755 --- a/FreeFileSync/Source/lib/hard_filter.h +++ b/FreeFileSync/Source/lib/hard_filter.h @@ -42,7 +42,7 @@ public: virtual bool isNull() const = 0; //filter is equivalent to NullFilter, but may be technically slower - using FilterRef = std::shared_ptr; //always bound by design! + using FilterRef = std::shared_ptr; //always bound by design! Thread-safety: internally synchronized! virtual FilterRef copyFilterAddingExclusion(const Zstring& excludePhrase) const = 0; diff --git a/FreeFileSync/Source/lib/icon_buffer.cpp b/FreeFileSync/Source/lib/icon_buffer.cpp index 7efe713c..15bd6f93 100755 --- a/FreeFileSync/Source/lib/icon_buffer.cpp +++ b/FreeFileSync/Source/lib/icon_buffer.cpp @@ -111,16 +111,16 @@ public: interruptibleWait(conditionNewWork_, dummy, [this] { return !workLoad_.empty(); }); //throw ThreadInterruption - AbstractPath filePath = workLoad_.back(); // - workLoad_.pop_back(); //yes, no strong exception guarantee (std::bad_alloc) - return filePath; // + AbstractPath filePath = workLoad_. back(); //yes, no strong exception guarantee (std::bad_alloc) + /**/ workLoad_.pop_back(); // + return filePath; } private: //AbstractPath is thread-safe like an int! - std::vector workLoad_; //processes last elements of vector first! std::mutex lockFiles_; std::condition_variable conditionNewWork_; //signal event: data for processing available + std::vector workLoad_; //processes last elements of vector first! }; @@ -189,11 +189,11 @@ private: struct IconData; #ifdef __clang__ //workaround libc++ limitation for incomplete types: http://llvm.org/bugs/show_bug.cgi?id=17701 - using FileIconMap = std::map, AFS::LessAbstractPath>; + using FileIconMap = std::map>; static IconData& refData(FileIconMap::iterator it) { return *(it->second); } static std::unique_ptr makeValueObject() { return std::make_unique(); } #else - using FileIconMap = std::map; + using FileIconMap = std::map; IconData& refData(FileIconMap::iterator it) { return it->second; } static IconData makeValueObject() { return IconData(); } #endif diff --git a/FreeFileSync/Source/lib/parallel_scan.cpp b/FreeFileSync/Source/lib/parallel_scan.cpp index 702b7aac..954b6b07 100755 --- a/FreeFileSync/Source/lib/parallel_scan.cpp +++ b/FreeFileSync/Source/lib/parallel_scan.cpp @@ -154,56 +154,72 @@ std::vector> separateByDistinctDisk(const std::set; //thread-safe string class for UI texts - class AsyncCallback //actor pattern { public: - AsyncCallback(std::chrono::milliseconds cbInterval) : cbInterval_(cbInterval) {} + AsyncCallback(size_t threadsToFinish, std::chrono::milliseconds cbInterval) : threadsToFinish_(threadsToFinish), cbInterval_(cbInterval) {} //blocking call: context of worker thread FillBufferCallback::HandleError reportError(const std::wstring& msg, size_t retryNumber) //throw ThreadInterruption { assert(std::this_thread::get_id() != mainThreadId); - std::unique_lock dummy(lockErrorInfo_); - interruptibleWait(conditionCanReportError_, dummy, [this] { return !errorInfo_ && !errorResponse_; }); //throw ThreadInterruption + std::unique_lock dummy(lockRequest_); + interruptibleWait(conditionReadyForNewRequest_, dummy, [this] { return !errorRequest_ && !errorResponse_; }); //throw ThreadInterruption - errorInfo_ = std::make_pair(copyStringTo(msg), retryNumber); + errorRequest_ = std::make_pair(msg, retryNumber); + conditionNewRequest.notify_all(); - interruptibleWait(conditionGotResponse_, dummy, [this] { return static_cast(errorResponse_); }); //throw ThreadInterruption + interruptibleWait(conditionHaveResponse_, dummy, [this] { return static_cast(errorResponse_); }); //throw ThreadInterruption FillBufferCallback::HandleError rv = *errorResponse_; - errorInfo_ = NoValue(); + errorRequest_ = NoValue(); errorResponse_ = NoValue(); dummy.unlock(); //optimization for condition_variable::notify_all() - conditionCanReportError_.notify_all(); //instead of notify_one(); workaround bug: https://svn.boost.org/trac/boost/ticket/7796 + conditionReadyForNewRequest_.notify_all(); //instead of notify_one(); workaround bug: https://svn.boost.org/trac/boost/ticket/7796 return rv; } - //context of main thread, call repreatedly - void processErrors(FillBufferCallback& callback) + //context of main thread + void waitUntilDone(std::chrono::milliseconds duration, FillBufferCallback& callback) //throw X { assert(std::this_thread::get_id() == mainThreadId); - std::unique_lock dummy(lockErrorInfo_); - if (errorInfo_ && !errorResponse_) + for (;;) { - errorResponse_ = callback.reportError(copyStringTo(errorInfo_->first), errorInfo_->second); //throw! + const std::chrono::steady_clock::time_point callbackTime = std::chrono::steady_clock::now() + duration; + + for (std::unique_lock dummy(lockRequest_) ;;) //process all errors without delay + { + const bool rv = conditionNewRequest.wait_until(dummy, callbackTime, [this] { return (errorRequest_ && !errorResponse_) || (threadsToFinish_ == 0); }); + if (!rv) //time-out + condition not met + break; + + if (errorRequest_ && !errorResponse_) + { + assert(threadsToFinish_ != 0); + errorResponse_ = callback.reportError(errorRequest_->first, errorRequest_->second); //throw X + conditionHaveResponse_.notify_all(); //instead of notify_one(); workaround bug: https://svn.boost.org/trac/boost/ticket/7796 + } + if (threadsToFinish_ == 0) + { + dummy.unlock(); + callback.reportStatus(getCurrentStatus(), itemsScanned_); //throw X; one last call for accurate stat-reporting! + return; + } + } - dummy.unlock(); //optimization for condition_variable::notify_all() - conditionGotResponse_.notify_all(); //instead of notify_one(); workaround bug: https://svn.boost.org/trac/boost/ticket/7796 + //call member functions outside of mutex scope: + callback.reportStatus(getCurrentStatus(), itemsScanned_); //throw X } } - void incrementNotifyingThreadId() {assert(std::this_thread::get_id() == mainThreadId); ++notifyingThreadID_; } //context of main thread - - //perf optimization: comparison phase is 7% faster by avoiding needless std::wstring contstruction for reportCurrentFile() - bool mayReportCurrentFile(int threadID, std::chrono::steady_clock::time_point& lastReportTime) const + //perf optimization: comparison phase is 7% faster by avoiding needless std::wstring construction for reportCurrentFile() + bool mayReportCurrentFile(int threadIdx, std::chrono::steady_clock::time_point& lastReportTime) const { - if (threadID != notifyingThreadID_) //only one thread at a time may report status + if (threadIdx != notifyingThreadIdx_) //only one thread at a time may report status: the first in sequential order return false; const auto now = std::chrono::steady_clock::now(); //0 on error @@ -217,63 +233,88 @@ public: return false; } - void reportCurrentFile(const std::wstring& filepath) //context of worker thread + void reportCurrentFile(const std::wstring& filePath) //context of worker thread { assert(std::this_thread::get_id() != mainThreadId); std::lock_guard dummy(lockCurrentStatus_); - currentFile_ = copyStringTo(filepath); + currentFile_ = filePath; } - std::wstring getCurrentStatus() //context of main thread, call repreatedly + void incItemsScanned() { ++itemsScanned_; } //perf: irrelevant! scanning is almost entirely file I/O bound, not CPU bound! => no prob having multiple threads poking at the same variable! + + void notifyWorkBegin(int threadIdx, const size_t parallelOps) + { + std::lock_guard dummy(lockCurrentStatus_); + + const auto it = activeThreadIdxs_.emplace(threadIdx, parallelOps); + assert(it.second); + (void)it; + + notifyingThreadIdx_ = activeThreadIdxs_.begin()->first; + } + + void notifyWorkEnd(int threadIdx) { - assert(std::this_thread::get_id() == mainThreadId); - std::wstring filepath; { std::lock_guard dummy(lockCurrentStatus_); - filepath = copyStringTo(currentFile_); - } - if (filepath.empty()) - return std::wstring(); + const size_t no = activeThreadIdxs_.erase(threadIdx); + assert(no == 1); + (void)no; - std::wstring statusText = copyStringTo(textScanning_); + notifyingThreadIdx_ = activeThreadIdxs_.empty() ? 0 : activeThreadIdxs_.begin()->first; + } + { + std::lock_guard dummy(lockRequest_); + assert(threadsToFinish_ > 0); + if (--threadsToFinish_ == 0) + conditionNewRequest.notify_all(); //perf: should unlock mutex before notify!? (insignificant) + } + } - const long activeCount = activeWorker_; - if (activeCount >= 2) - statusText += L" [" + _P("1 thread", "%x threads", activeCount) + L"]"; +private: + std::wstring getCurrentStatus() //context of main thread, call repreatedly + { + assert(std::this_thread::get_id() == mainThreadId); - statusText += L" "; - statusText += filepath; - return statusText; - } + size_t parallelOpsTotal = 0; + std::wstring filePath; + { + std::lock_guard dummy(lockCurrentStatus_); - void incItemsScanned() { ++itemsScanned_; } //perf: irrelevant! scanning is almost entirely file I/O bound, not CPU bound! => no prob having multiple threads poking at the same variable! - long getItemsScanned() const { return itemsScanned_; } + for (const auto& item : activeThreadIdxs_) + parallelOpsTotal += item.second; - void incActiveWorker() { ++activeWorker_; } - void decActiveWorker() { --activeWorker_; } - long getActiveWorker() const { return activeWorker_; } + filePath = currentFile_; + } -private: - //---- error handling ---- - std::mutex lockErrorInfo_; - std::condition_variable conditionCanReportError_; - std::condition_variable conditionGotResponse_; - Opt> errorInfo_; //error message + retry number + std::wstring output = textScanning_; + if (parallelOpsTotal >= 2) + output += L"[" + _P("1 thread", "%x threads", parallelOpsTotal) + L"] "; + output += filePath; + return output; + } + + //---- main <-> worker communication channel ---- + std::mutex lockRequest_; + std::condition_variable conditionReadyForNewRequest_; + std::condition_variable conditionNewRequest; + std::condition_variable conditionHaveResponse_; + Opt> errorRequest_; //error message + retry number Opt errorResponse_; + size_t threadsToFinish_; //!= activeThreadIdxs_.size(), which may be 0 during worker thread construction! //---- status updates ---- - std::atomic notifyingThreadID_ { 0 }; //CAVEAT: do NOT use boost::thread::id: https://svn.boost.org/trac/boost/ticket/5754 - std::mutex lockCurrentStatus_; //use a different lock for current file: continue traversing while some thread may process an error - BasicWString currentFile_; - const std::chrono::milliseconds cbInterval_; + std::wstring currentFile_; + std::map activeThreadIdxs_; - const BasicWString textScanning_ { copyStringTo(_("Scanning:")) }; //this one is (currently) not shared and could be made a std::wstring, but we stay consistent and use thread-safe variables in this class only! + std::atomic notifyingThreadIdx_ { 0 }; //CAVEAT: do NOT use boost::thread::id: https://svn.boost.org/trac/boost/ticket/5754 + const std::chrono::milliseconds cbInterval_; + const std::wstring textScanning_ = _("Scanning:") + L" "; //this one is (currently) not shared - //---- status updates II (lock free) ---- + //---- status updates II (lock-free) ---- std::atomic itemsScanned_{ 0 }; //std:atomic is uninitialized by default! - std::atomic activeWorker_{ 0 }; // }; //------------------------------------------------------------------------------------------------- @@ -288,8 +329,8 @@ struct TraverserConfig std::map& failedItemReads; AsyncCallback& acb; - const int threadID; - std::chrono::steady_clock::time_point lastReportTime; + const int threadIdx; + std::chrono::steady_clock::time_point& lastReportTime; //thread-level }; @@ -303,10 +344,10 @@ public: cfg_(cfg), parentRelPathPf_(parentRelPathPf), output_(output), - level_(level) {} + level_(level) {} //MUST NOT use cfg_ during construction! see BaseDirCallback() virtual void onFile (const FileInfo& fi) override; // - virtual std::unique_ptr onFolder (const FolderInfo& fi) override; //throw ThreadInterruption + virtual std::shared_ptr onFolder (const FolderInfo& fi) override; //throw ThreadInterruption virtual HandleLink onSymlink(const SymlinkInfo& li) override; // HandleError reportDirError (const std::wstring& msg, size_t retryNumber) override; //throw ThreadInterruption @@ -320,6 +361,33 @@ private: }; +class BaseDirCallback : public DirCallback +{ +public: + BaseDirCallback(const DirectoryKey& baseFolderKey, DirectoryValue& output, + AsyncCallback& acb, int threadIdx, std::chrono::steady_clock::time_point& lastReportTime) : + DirCallback(travCfg_ /*not yet constructed!!!*/, Zstring(), output.folderCont, 0 /*level*/), + travCfg_ + { + baseFolderKey.folderPath, + baseFolderKey.filter, + baseFolderKey.handleSymlinks, + output.failedFolderReads, + output.failedItemReads, + acb, + threadIdx, + lastReportTime + } + { + if (acb.mayReportCurrentFile(threadIdx, lastReportTime)) + acb.reportCurrentFile(AFS::getDisplayPath(baseFolderKey.folderPath)); //just in case first directory access is blocking + } + +private: + TraverserConfig travCfg_; +}; + + void DirCallback::onFile(const FileInfo& fi) //throw ThreadInterruption { interruptionPoint(); //throw ThreadInterruption @@ -332,7 +400,7 @@ void DirCallback::onFile(const FileInfo& fi) //throw ThreadInterruption const Zstring fileRelPath = parentRelPathPf_ + fi.itemName; //update status information no matter whether item is excluded or not! - if (cfg_.acb.mayReportCurrentFile(cfg_.threadID, cfg_.lastReportTime)) + if (cfg_.acb.mayReportCurrentFile(cfg_.threadIdx, cfg_.lastReportTime)) cfg_.acb.reportCurrentFile(AFS::getDisplayPath(AFS::appendRelPath(cfg_.baseFolderPath, fileRelPath))); //------------------------------------------------------------------------------------ @@ -357,14 +425,14 @@ void DirCallback::onFile(const FileInfo& fi) //throw ThreadInterruption } -std::unique_ptr DirCallback::onFolder(const FolderInfo& fi) //throw ThreadInterruption +std::shared_ptr DirCallback::onFolder(const FolderInfo& fi) //throw ThreadInterruption { interruptionPoint(); //throw ThreadInterruption const Zstring& folderRelPath = parentRelPathPf_ + fi.itemName; //update status information no matter whether item is excluded or not! - if (cfg_.acb.mayReportCurrentFile(cfg_.threadID, cfg_.lastReportTime)) + if (cfg_.acb.mayReportCurrentFile(cfg_.threadIdx, cfg_.lastReportTime)) cfg_.acb.reportCurrentFile(AFS::getDisplayPath(AFS::appendRelPath(cfg_.baseFolderPath, folderRelPath))); //------------------------------------------------------------------------------------ @@ -388,7 +456,7 @@ std::unique_ptr DirCallback::onFolder(const FolderInfo& }, *this, fi.itemName)) return nullptr; - return std::make_unique(cfg_, folderRelPath + FILE_NAME_SEPARATOR, subFolder, level_ + 1); + return std::make_shared(cfg_, folderRelPath + FILE_NAME_SEPARATOR, subFolder, level_ + 1); } @@ -399,7 +467,7 @@ DirCallback::HandleLink DirCallback::onSymlink(const SymlinkInfo& si) //throw Th const Zstring& linkRelPath = parentRelPathPf_ + si.itemName; //update status information no matter whether item is excluded or not! - if (cfg_.acb.mayReportCurrentFile(cfg_.threadID, cfg_.lastReportTime)) + if (cfg_.acb.mayReportCurrentFile(cfg_.threadIdx, cfg_.lastReportTime)) cfg_.acb.reportCurrentFile(AFS::getDisplayPath(AFS::appendRelPath(cfg_.baseFolderPath, linkRelPath))); switch (cfg_.handleSymlinks) @@ -417,7 +485,7 @@ DirCallback::HandleLink DirCallback::onSymlink(const SymlinkInfo& si) //throw Th case SymLinkHandling::FOLLOW: //filter symlinks before trying to follow them: handle user-excluded broken symlinks! - //since we don't know yet what type the symlink will resolve to, only do this when both variants agree: + //since we don't know yet what type the symlink will resolve to, only do this when both filter variants agree: if (!cfg_.filter->passFileFilter(linkRelPath)) { bool childItemMightMatch = true; @@ -466,69 +534,67 @@ DirCallback::HandleError DirCallback::reportItemError(const std::wstring& msg, s } -void fff::fillBuffer(const std::set& keysToRead, //in +void fff::fillBuffer(const std::set& foldersToRead, //in std::map& buf, //out + const std::map& deviceParallelOps, FillBufferCallback& callback, std::chrono::milliseconds cbInterval) { buf.clear(); + //aggregate folder paths that are on the same root device: + // => one worker thread *per device*: avoid excessive parallelism + // => parallel folder traversal considers "parallel file operations" as specified by user + // => (S)FTP: avoid hitting connection limits inadvertently + std::map> perDeviceFolders; + + for (const DirectoryKey& key : foldersToRead) + perDeviceFolders[AFS::getPathComponents(key.folderPath).rootPath].insert(key); + //communication channel used by threads - AsyncCallback acb(cbInterval); //manage life time: enclose InterruptibleThread's!!! + AsyncCallback acb(perDeviceFolders.size() /*threadsToFinish*/, cbInterval); //manage life time: enclose InterruptibleThread's!!! FixedList worker; - - ZEN_ON_SCOPE_FAIL - ( - for (InterruptibleThread& wt : worker) - wt.interrupt(); //interrupt all first, then join - for (InterruptibleThread& wt : worker) - if (wt.joinable()) //= precondition of thread::join(), which throws an exception if violated! - wt.join(); //in this context it is possible a thread is *not* joinable anymore due to the tryJoinFor() below! - ); + ZEN_ON_SCOPE_EXIT( for (InterruptibleThread& wt : worker) wt.join(); ); + ZEN_ON_SCOPE_FAIL( for (InterruptibleThread& wt : worker) wt.interrupt(); ); //interrupt all first, then join //init worker threads - for (const DirectoryKey& key : keysToRead) + for (const auto& item : perDeviceFolders) { - DirectoryValue& dirOutput = buf[key]; - - const int threadId = static_cast(worker.size()); - worker.emplace_back([&outputContainer = dirOutput.folderCont, - travCfg = TraverserConfig //shared by all(!) instances of DirCallback while traversing a folder hierarchy - { - key.folderPath, - key.filter, - key.handleSymlinks, - dirOutput.failedFolderReads, - dirOutput.failedItemReads, - acb, - threadId, - }]() mutable + const AbstractPath& rootPath = item.first; + const int threadIdx = static_cast(worker.size()); + +#if !defined ZEN_LINUX || defined ZEN_LINUX_TRAVERSER_MODERN + auto itParOps = deviceParallelOps.find(rootPath); + const size_t parallelOps = std::max(itParOps != deviceParallelOps.end() ? itParOps->second : 1, 1); //sanitize early for correct status display +#elif defined ZEN_LINUX && defined ZEN_LINUX_TRAVERSER_LEGACY + const size_t parallelOps = 1; +#endif + std::map workload; + + for (const DirectoryKey& key : item.second) + workload.emplace(key, &buf[key]); //=> DirectoryValue* unshared for lock-free worker-thread access + + worker.emplace_back([rootPath, workload, threadIdx, &acb, parallelOps]() mutable { - setCurrentThreadName("Folder Traverser"); + setCurrentThreadName(("Traverser[" + numberTo(threadIdx) + "]").c_str()); - travCfg.acb.incActiveWorker(); - ZEN_ON_SCOPE_EXIT(travCfg.acb.decActiveWorker()); + acb.notifyWorkBegin(threadIdx, parallelOps); + ZEN_ON_SCOPE_EXIT(acb.notifyWorkEnd(threadIdx)); - if (travCfg.acb.mayReportCurrentFile(travCfg.threadID, travCfg.lastReportTime)) - travCfg.acb.reportCurrentFile(AFS::getDisplayPath(travCfg.baseFolderPath)); //just in case first directory access is blocking + std::chrono::steady_clock::time_point lastReportTime; //keep at thread-level! - DirCallback cb(travCfg, Zstring(), outputContainer, 0); + AFS::TraverserWorkload travWorkload; - AFS::traverseFolder(travCfg.baseFolderPath, cb); //throw ThreadInterruption + for (auto& wl : workload) + { + const AFS::PathComponents pc = AFS::getPathComponents(wl.first.folderPath); + assert(pc.rootPath == rootPath); + travWorkload.emplace_back(pc.relPath, std::make_shared(wl.first, *wl.second, acb, threadIdx, lastReportTime)); + } + AFS::traverseFolderParallel(rootPath, travWorkload, parallelOps); //throw ThreadInterruption }); } - //wait until done - for (InterruptibleThread& wt : worker) - { - do - { - callback.reportStatus(acb.getCurrentStatus(), acb.getItemsScanned()); //throw! - acb.processErrors(callback); - } - while (!wt.tryJoinFor(cbInterval)); - - acb.incrementNotifyingThreadId(); //process info messages of one thread at a time only - } + acb.waitUntilDone(cbInterval, callback); //throw X } diff --git a/FreeFileSync/Source/lib/parallel_scan.h b/FreeFileSync/Source/lib/parallel_scan.h index d28bb9d9..7e54428a 100755 --- a/FreeFileSync/Source/lib/parallel_scan.h +++ b/FreeFileSync/Source/lib/parallel_scan.h @@ -31,10 +31,9 @@ bool operator<(const DirectoryKey& lhs, const DirectoryKey& rhs) if (lhs.handleSymlinks != rhs.handleSymlinks) return lhs.handleSymlinks < rhs.handleSymlinks; - if (AFS::LessAbstractPath()(lhs.folderPath, rhs.folderPath)) - return true; - if (AFS::LessAbstractPath()(rhs.folderPath, lhs.folderPath)) - return false; + const int cmp = AbstractFileSystem::compareAbstractPath(lhs.folderPath, rhs.folderPath); + if (cmp != 0) + return cmp < 0; return *lhs.filter < *rhs.filter; } @@ -43,6 +42,7 @@ bool operator<(const DirectoryKey& lhs, const DirectoryKey& rhs) struct DirectoryValue { FolderContainer folderCont; + //relative names (or empty string for root) for directories that could not be read (completely), e.g. access denied, or temporal network drop std::map failedFolderReads; //with corresponding error message @@ -64,10 +64,11 @@ struct FillBufferCallback virtual void reportStatus(const std::wstring& msg, int itemsTotal ) = 0; // }; -//attention: ensure directory filtering is applied later to exclude filtered directories which have been kept as parent folders +//attention: ensure directory filtering is applied later to exclude filtered folders which have been kept as parent folders -void fillBuffer(const std::set& keysToRead, //in +void fillBuffer(const std::set& foldersToRead, //in std::map& buf, //out + const std::map& deviceParallelOps, FillBufferCallback& callback, std::chrono::milliseconds cbInterval); } diff --git a/FreeFileSync/Source/lib/parse_lng.h b/FreeFileSync/Source/lib/parse_lng.h index 86faca28..b6d486e2 100755 --- a/FreeFileSync/Source/lib/parse_lng.h +++ b/FreeFileSync/Source/lib/parse_lng.h @@ -412,7 +412,7 @@ private: translation = token().text; nextToken(); } - validateTranslation(original, translation); //throw throw ParsingError + validateTranslation(original, translation); //throw ParsingError consumeToken(Token::TK_TRG_END); out.emplace(original, translation); @@ -725,8 +725,8 @@ std::string generateLng(const TranslationUnorderedList& in, const TransHeader& h out += tokens.text(Token::TK_SRC_END) + '\n'; out += tokens.text(Token::TK_TRG_BEGIN); - if (!forms.empty()) //translators will be searching for "" - out += '\n'; + if (!forms.empty()) //translators will be searching for "" + out += '\n'; for (std::string plForm : forms) { formatMultiLineText(plForm); diff --git a/FreeFileSync/Source/lib/process_xml.cpp b/FreeFileSync/Source/lib/process_xml.cpp index d7db65df..95cac76e 100755 --- a/FreeFileSync/Source/lib/process_xml.cpp +++ b/FreeFileSync/Source/lib/process_xml.cpp @@ -12,6 +12,7 @@ #include #include #include "ffs_paths.h" +#include "../fs/concrete.h" using namespace zen; @@ -21,8 +22,8 @@ using namespace fff; //functionally needed for correct overload resolution!!! namespace { //------------------------------------------------------------------------------------------------------------------------------- -const int XML_FORMAT_VER_GLOBAL = 8; //2018-02-01 -const int XML_FORMAT_VER_FFS_CFG = 10; //2018-02-24 +const int XML_FORMAT_VER_GLOBAL = 9; //2018-03-14 +const int XML_FORMAT_VER_FFS_CFG = 11; //2018-04-14 //------------------------------------------------------------------------------------------------------------------------------- } @@ -955,12 +956,50 @@ void readConfig(const XmlIn& in, FilterConfig& filter, int formatVer) } -void readConfig(const XmlIn& in, LocalPairConfig& lpc, int formatVer) +void readConfig(const XmlIn& in, LocalPairConfig& lpc, std::map& deviceParallelOps, int formatVer) { //read folder pairs in["Left" ](lpc.folderPathPhraseLeft); in["Right"](lpc.folderPathPhraseRight); + size_t parallelOpsL = 0; + size_t parallelOpsR = 0; + + //TODO: remove old parameter after migration! 2018-04-14 + if (formatVer < 11) + { + auto getParallelOps = [&](const Zstring& folderPathPhrase, size_t& parallelOps) + { + if (startsWith(folderPathPhrase, Zstr("sftp:"), CmpAsciiNoCase()) || + startsWith(folderPathPhrase, Zstr( "ftp:"), CmpAsciiNoCase())) + { + for (const Zstring& optPhrase : split(folderPathPhrase, Zstr("|"), SplitType::SKIP_EMPTY)) + if (startsWith(optPhrase, Zstr("con="))) + parallelOps = stringTo(afterFirst(optPhrase, Zstr("con="), IF_MISSING_RETURN_NONE)); + } + }; + getParallelOps(lpc.folderPathPhraseLeft, parallelOpsL); + getParallelOps(lpc.folderPathPhraseRight, parallelOpsR); + } + else + { + if (const XmlElement* e = in["Left" ].get()) e->getAttribute("Threads", parallelOpsL); //try to get attributes: + if (const XmlElement* e = in["Right"].get()) e->getAttribute("Threads", parallelOpsR); // => *no error* if not available + //in["Left" ].attribute("Threads", parallelOpsL); + //in["Right"].attribute("Threads", parallelOpsR); + } + + auto setParallelOps = [&](const Zstring& folderPathPhrase, size_t parallelOps) + { + if (parallelOps > 1) + { + const AbstractPath& rootPath = AFS::getPathComponents(createAbstractPath(folderPathPhrase)).rootPath; + deviceParallelOps[rootPath] = std::max(deviceParallelOps[rootPath], parallelOps); + } + }; + setParallelOps(lpc.folderPathPhraseLeft, parallelOpsL); + setParallelOps(lpc.folderPathPhraseRight, parallelOpsR); + //TODO: remove after migration - 2016-07-24 auto ciReplace = [](Zstring& pathPhrase, const Zstring& oldTerm, const Zstring& newTerm) { pathPhrase = ciReplaceCpy(pathPhrase, oldTerm, newTerm); }; ciReplace(lpc.folderPathPhraseLeft, Zstr("%csidl_MyDocuments%"), Zstr("%csidl_Documents%")); @@ -1023,9 +1062,9 @@ void readConfig(const XmlIn& in, MainConfiguration& mainCfg, int formatVer) XmlIn inMain = formatVer < 10 ? in["MainConfig"] : in; //TODO: remove if parameter migration after some time! 2018-02-25 if (formatVer < 10) //TODO: remove if parameter migration after some time! 2018-02-25 - readConfig(inMain["Comparison"], mainCfg.cmpConfig); + readConfig(inMain["Comparison"], mainCfg.cmpCfg); else - readConfig(inMain["Compare"], mainCfg.cmpConfig); + readConfig(inMain["Compare"], mainCfg.cmpCfg); //########################################################### //read sync configuration @@ -1043,22 +1082,21 @@ void readConfig(const XmlIn& in, MainConfiguration& mainCfg, int formatVer) readConfig(inMain["Filter"], mainCfg.globalFilter, formatVer); //########################################################### - //read all folder pairs - mainCfg.additionalPairs.clear(); - + //read folder pairs bool firstItem = true; for (XmlIn inPair = inMain["FolderPairs"]["Pair"]; inPair; inPair.next()) { LocalPairConfig lpc; - readConfig(inPair, lpc, formatVer); + readConfig(inPair, lpc, mainCfg.deviceParallelOps, formatVer); if (firstItem) { firstItem = false; - mainCfg.firstPair = lpc; //set first folder pair + mainCfg.firstPair = lpc; + mainCfg.additionalPairs.clear(); } else - mainCfg.additionalPairs.push_back(lpc); //set additional folder pairs + mainCfg.additionalPairs.push_back(lpc); } //TODO: remove if parameter migration after some time! 2017-10-24 @@ -1470,6 +1508,20 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer) //batch specific global settings //XmlIn inBatch = in["Batch"]; + + + //TODO: remove parameter migration after some time! 2018-03-14 + if (formatVer < 9) + if (fastFromDIP(96) > 96) //high-DPI monitor => one-time migration + { + const XmlGlobalSettings defaultCfg; + cfg.gui.mainDlg.dlgSize = defaultCfg.gui.mainDlg.dlgSize; + cfg.gui.mainDlg.guiPerspectiveLast = defaultCfg.gui.mainDlg.guiPerspectiveLast; + cfg.gui.mainDlg.cfgGridColumnAttribs = defaultCfg.gui.mainDlg.cfgGridColumnAttribs; + cfg.gui.mainDlg.treeGridColumnAttribs = defaultCfg.gui.mainDlg.treeGridColumnAttribs; + cfg.gui.mainDlg.columnAttribLeft = defaultCfg.gui.mainDlg.columnAttribLeft; + cfg.gui.mainDlg.columnAttribRight = defaultCfg.gui.mainDlg.columnAttribRight; + } } @@ -1499,14 +1551,11 @@ void readConfig(const Zstring& filePath, XmlType type, ConfigType& cfg, int curr checkForMappingErrors(in, filePath); //throw FileError //(try to) migrate old configuration automatically - if (formatVer< currentXmlFormatVer) + if (formatVer < currentXmlFormatVer) try { fff::writeConfig(cfg, filePath); /*throw FileError*/ } catch (FileError&) { assert(false); } //don't bother user! } - catch (const FileError& e) - { - warningMsg = e.toString(); - } + catch (const FileError& e) { warningMsg = e.toString(); } } } @@ -1659,7 +1708,7 @@ void writeConfig(const FilterConfig& filter, XmlOut& out) } -void writeConfig(const LocalPairConfig& lpc, XmlOut& out) +void writeConfig(const LocalPairConfig& lpc, const std::map& deviceParallelOps, XmlOut& out) { XmlOut outPair = out.ref().addChild("Pair"); @@ -1667,6 +1716,21 @@ void writeConfig(const LocalPairConfig& lpc, XmlOut& out) outPair["Left" ](lpc.folderPathPhraseLeft); outPair["Right"](lpc.folderPathPhraseRight); + auto getParallelOps = [&](const Zstring& folderPathPhrase) + { + const AbstractPath& rootPath = AFS::getPathComponents(createAbstractPath(folderPathPhrase)).rootPath; + auto itParOps = deviceParallelOps.find(rootPath); + return std::max(itParOps != deviceParallelOps.end() ? itParOps->second : 1, 1); + }; + const size_t parallelOpsL = getParallelOps(lpc.folderPathPhraseLeft); + const size_t parallelOpsR = getParallelOps(lpc.folderPathPhraseRight); + + if (parallelOpsL > 1) outPair["Left" ].attribute("Threads", parallelOpsL); + if (parallelOpsR > 1) outPair["Right"].attribute("Threads", parallelOpsR); + + //avoid "fake" changed configs by only storing "real" parallel-enabled devices in deviceParallelOps + assert(std::all_of(deviceParallelOps.begin(), deviceParallelOps.end(), [](const auto& item) { return item.second > 1; })); + //########################################################### //alternate comp configuration (optional) if (lpc.localCmpCfg) @@ -1698,7 +1762,7 @@ void writeConfig(const MainConfiguration& mainCfg, XmlOut& out) XmlOut outCmp = outMain["Compare"]; - writeConfig(mainCfg.cmpConfig, outCmp); + writeConfig(mainCfg.cmpCfg, outCmp); //########################################################### XmlOut outSync = outMain["Synchronize"]; @@ -1711,16 +1775,12 @@ void writeConfig(const MainConfiguration& mainCfg, XmlOut& out) writeConfig(mainCfg.globalFilter, outFilter); //########################################################### - //write all folder pairs - XmlOut outFp = outMain["FolderPairs"]; + //write folder pairs + writeConfig(mainCfg.firstPair, mainCfg.deviceParallelOps, outFp); - //write first folder pair - writeConfig(mainCfg.firstPair, outFp); - - //write additional folder pairs for (const LocalPairConfig& lpc : mainCfg.additionalPairs) - writeConfig(lpc, outFp); + writeConfig(lpc, mainCfg.deviceParallelOps, outFp); outMain["Errors"].attribute("Ignore", mainCfg.ignoreErrors); outMain["Errors"].attribute("Retry", mainCfg.automaticRetryCount); diff --git a/FreeFileSync/Source/lib/status_handler.h b/FreeFileSync/Source/lib/status_handler.h index 779c2c60..c84396ec 100755 --- a/FreeFileSync/Source/lib/status_handler.h +++ b/FreeFileSync/Source/lib/status_handler.h @@ -77,8 +77,8 @@ public: refNumbers(numbersTotal_, currentPhase_) = { itemsTotal, bytesTotal }; } - void updateProcessedData(int itemsDelta, int64_t bytesDelta) override { updateData(numbersCurrent_, itemsDelta, bytesDelta); } //note: these methods MUST NOT throw in order - void updateTotalData (int itemsDelta, int64_t bytesDelta) override { updateData(numbersTotal_, itemsDelta, bytesDelta); } //to allow usage within destructors! + void updateDataProcessed(int itemsDelta, int64_t bytesDelta) override { updateData(numbersCurrent_, itemsDelta, bytesDelta); } //note: these methods MUST NOT throw in order + void updateDataTotal (int itemsDelta, int64_t bytesDelta) override { updateData(numbersTotal_, itemsDelta, bytesDelta); } //to allow usage within destructors! void requestUiRefresh() override final //throw X { @@ -107,20 +107,12 @@ public: void reportStatus(const std::wstring& text) override final //throw X { + warn_static("std::wstring statusText_: perf issue?") //assert(!text.empty()); -> possible: start of parallel scan - statusText_ = text; //update text *before* running operations that can throw requestUiRefresh(); //throw X } - void reportInfo(const std::wstring& text) override //throw X - { - assert(!text.empty()); - statusText_ = text; - requestUiRefresh(); //throw X - //log text in derived class - } - void abortProcessNow() override { if (!abortRequested_) abortRequested_ = AbortTrigger::PROGRAM; diff --git a/FreeFileSync/Source/lib/status_handler_impl.h b/FreeFileSync/Source/lib/status_handler_impl.h index eab46960..6404c915 100755 --- a/FreeFileSync/Source/lib/status_handler_impl.h +++ b/FreeFileSync/Source/lib/status_handler_impl.h @@ -15,7 +15,7 @@ namespace fff { template inline -zen::Opt tryReportingError(Function cmd, ProcessCallback& handler /*throw X*/) //return ignored error message if available +zen::Opt tryReportingError(Function cmd, ProcessCallback& cb /*throw X*/) //return ignored error message if available { for (size_t retryNumber = 0;; ++retryNumber) try @@ -25,7 +25,7 @@ zen::Opt tryReportingError(Function cmd, ProcessCallback& handler } catch (zen::FileError& error) { - switch (handler.reportError(error.toString(), retryNumber)) //throw X + switch (cb.reportError(error.toString(), retryNumber)) //throw X { case ProcessCallback::IGNORE_ERROR: return error.toString(); @@ -37,44 +37,46 @@ zen::Opt tryReportingError(Function cmd, ProcessCallback& handler //manage statistics reporting for a single item of work -class StatisticsReporter +class ItemStatReporter { public: - StatisticsReporter(int itemsExpected, int64_t bytesExpected, ProcessCallback& cb) : + ItemStatReporter(int itemsExpected, int64_t bytesExpected, ProcessCallback& cb) : itemsExpected_(itemsExpected), bytesExpected_(bytesExpected), cb_(cb) {} - ~StatisticsReporter() + ~ItemStatReporter() { const bool scopeFail = getUncaughtExceptionCount() > exeptionCount_; if (scopeFail) - cb_.updateTotalData(itemsReported_, bytesReported_); //=> unexpected increase of total workload + cb_.updateDataTotal(itemsReported_, bytesReported_); //=> unexpected increase of total workload else //update statistics to consider the real amount of data, e.g. more than the "file size" for ADS streams, //less for sparse and compressed files, or file changed in the meantime! - cb_.updateTotalData(itemsReported_ - itemsExpected_, bytesReported_ - bytesExpected_); //noexcept! + cb_.updateDataTotal(itemsReported_ - itemsExpected_, bytesReported_ - bytesExpected_); //noexcept! } - void reportDelta(int itemsDelta, int64_t bytesDelta) //may throw! + void reportStatus(const std::wstring& text) { cb_.reportStatus(text); } //throw X + + void reportDelta(int itemsDelta, int64_t bytesDelta) //throw X { - cb_.updateProcessedData(itemsDelta, bytesDelta); //nothrow! -> ensure client and service provider are in sync! + cb_.updateDataProcessed(itemsDelta, bytesDelta); //nothrow! itemsReported_ += itemsDelta; - bytesReported_ += bytesDelta; // + bytesReported_ += bytesDelta; //special rule: avoid temporary statistics mess up, even though they are corrected anyway below: if (itemsReported_ > itemsExpected_) { - cb_.updateTotalData(itemsReported_ - itemsExpected_, 0); + cb_.updateDataTotal(itemsReported_ - itemsExpected_, 0); itemsReported_ = itemsExpected_; } if (bytesReported_ > bytesExpected_) { - cb_.updateTotalData(0, bytesReported_ - bytesExpected_); //=> everything above "bytesExpected" adds to both "processed" and "total" data + cb_.updateDataTotal(0, bytesReported_ - bytesExpected_); //=> everything above "bytesExpected" adds to both "processed" and "total" data bytesReported_ = bytesExpected_; } - cb_.requestUiRefresh(); //may throw! + cb_.requestUiRefresh(); //throw X } private: diff --git a/FreeFileSync/Source/lib/versioning.cpp b/FreeFileSync/Source/lib/versioning.cpp index 9033bea8..9fb2804f 100755 --- a/FreeFileSync/Source/lib/versioning.cpp +++ b/FreeFileSync/Source/lib/versioning.cpp @@ -21,28 +21,28 @@ bool fff::impl::isMatchingVersion(const Zstring& shortname, const Zstring& short auto it = shortnameVersioned.begin(); auto itLast = shortnameVersioned.end(); - auto nextDigit = [&]() -> bool + auto nextDigit = [&]() { if (it == itLast || !isDigit(*it)) return false; ++it; return true; }; - auto nextDigits = [&](size_t count) -> bool + auto nextDigits = [&](size_t count) { while (count-- > 0) if (!nextDigit()) return false; return true; }; - auto nextChar = [&](Zchar c) -> bool + auto nextChar = [&](Zchar c) { if (it == itLast || *it != c) return false; ++it; return true; }; - auto nextStringI = [&](const Zstring& str) -> bool //windows: ignore case! + auto nextStringI = [&](const Zstring& str) //windows: ignore case! { if (itLast - it < static_cast(str.size()) || !equalFilePath(str, Zstring(&*it, str.size()))) return false; @@ -106,33 +106,42 @@ void moveExistingItemToVersioning(const AbstractPath& sourcePath, const Abstract catch (FileError&) { deletionError = std::current_exception(); } //probably "not existing" error, defer evaluation //overwrite AFS::ItemType::FOLDER with FILE? => highly dubious, do not allow - auto fixedTargetPathIssues = [&] //throw FileError + auto fixTargetPathIssues = [&](const FileError& prevEx) //throw FileError { - Opt pd; - try { pd = AFS::getPathStatus(targetPath); /*throw FileError*/ } - catch (FileError&) + Opt psTmp; + try { psTmp = AFS::getPathStatus(targetPath); /*throw FileError*/ } + catch (const FileError& e) { throw FileError(prevEx.toString(), e.toString()); } + const AFS::PathStatus& ps = *psTmp; + //previous exception contains context-level information, but current exception is the immediate problem => combine both + //=> e.g. prevEx might be about missing parent folder; FFS considers session faulty and tries to create a new one, + //which might fail with: LIBSSH2_ERROR_AUTHENTICATION_FAILED (due to limit on #sessions?) https://www.freefilesync.org/forum/viewtopic.php?t=4765#p16016 + + if (ps.relPath.empty()) //already existing { - //previous exception is more relevant in general - //BUT, we might be hiding a second unrelated issue: https://www.freefilesync.org/forum/viewtopic.php?t=4765#p16016 - //=> FFS considers session faulty and tries to create a new one, which might fail with: LIBSSH2_ERROR_AUTHENTICATION_FAILED + if (deletionError) + std::rethrow_exception(deletionError); + throw prevEx; //yes, slicing, but not relevant here } - if (pd) - { - if (pd->relPath.empty()) //already existing + //parent folder missing => create + retry + //parent folder existing => maybe created shortly after move attempt by parallel thread! => retry + AbstractPath intermediatePath = ps.existingPath; + for (const Zstring& itemName : std::vector(ps.relPath.begin(), ps.relPath.end() - 1)) + try { - if (deletionError) - std::rethrow_exception(deletionError); + AFS::createFolderPlain(intermediatePath = AFS::appendRelPath(intermediatePath, itemName)); //throw FileError } - else if (pd->relPath.size() > 1) //parent folder missing + catch (FileError&) { - AbstractPath intermediatePath = pd->existingPath; - for (const Zstring& itemName : std::vector(pd->relPath.begin(), pd->relPath.end() - 1)) - AFS::createFolderPlain(intermediatePath = AFS::appendRelPath(intermediatePath, itemName)); //throw FileError - return true; + try //already existing => possible, if moveExistingItemToVersioning() is run in parallel + { + if (AFS::getItemType(intermediatePath) != AFS::ItemType::FILE) //throw FileError + continue; + } + catch (FileError&) {} + + throw; } - } - return false; }; try //first try to move directly without copying @@ -146,20 +155,19 @@ void moveExistingItemToVersioning(const AbstractPath& sourcePath, const Abstract { copyNewItemPlain(); //throw FileError } - catch (FileError&) + catch (const FileError& e) { - if (!fixedTargetPathIssues()) //throw FileError - throw; + fixTargetPathIssues(e); //throw FileError + //retry copyNewItemPlain(); //throw FileError } //[!] remove source file AFTER handling target path errors! AFS::removeFilePlain(sourcePath); //throw FileError } - catch (FileError&) + catch (const FileError& e) { - if (!fixedTargetPathIssues()) //throw FileError - throw; + fixTargetPathIssues(e); //throw FileError try //retry { @@ -184,7 +192,7 @@ struct FlatTraverserCallback: public AFS::TraverserCallback private: void onFile (const FileInfo& fi) override { files_ .push_back(fi); } - std::unique_ptr onFolder (const FolderInfo& fi) override { folders_ .push_back(fi); return nullptr; } + std::shared_ptr onFolder (const FolderInfo& fi) override { folders_ .push_back(fi); return nullptr; } HandleLink onSymlink(const SymlinkInfo& si) override { symlinks_.push_back(si); return TraverserCallback::LINK_SKIP; } HandleError reportDirError (const std::wstring& msg, size_t retryNumber) override { throw FileError(msg); } @@ -198,7 +206,7 @@ private: } -bool FileVersioner::revisionFile(const FileDescriptor& fileDescr, const Zstring& relativePath, const IOCallback& notifyUnbufferedIO) //throw FileError +bool FileVersioner::revisionFile(const FileDescriptor& fileDescr, const Zstring& relativePath, const IOCallback& notifyUnbufferedIO) const //throw FileError { const AbstractPath& filePath = fileDescr.path; const AFS::StreamAttributes fileAttr{ fileDescr.attr.modTime, fileDescr.attr.fileSize, fileDescr.attr.fileId }; @@ -212,10 +220,10 @@ bool FileVersioner::revisionFile(const FileDescriptor& fileDescr, const Zstring& else moveExistingItemToVersioning(filePath, targetPath, [&] //throw FileError { - //target existing: copyFileTransactional() undefined behavior! (fail/overwrite/auto-rename) => not expected here: + //target existing: copyFileTransactional() undefined behavior! (fail/overwrite/auto-rename) => not expected, but possible if target deletion failed /*const AFS::FileCopyResult result =*/ AFS::copyFileTransactional(filePath, fileAttr, targetPath, //throw FileError, ErrorFileLocked false, //copyFilePermissions - false, //transactionalCopy: not needed for versioning! + false, //transactionalCopy: not needed for versioning! partial copy will be overwritten next time nullptr /*onDeleteTargetFile*/, notifyUnbufferedIO); //result.errorModTime? => irrelevant for versioning! }); @@ -226,7 +234,7 @@ bool FileVersioner::revisionFile(const FileDescriptor& fileDescr, const Zstring& } -bool FileVersioner::revisionSymlink(const AbstractPath& linkPath, const Zstring& relativePath) //throw FileError +bool FileVersioner::revisionSymlink(const AbstractPath& linkPath, const Zstring& relativePath) const //throw FileError { if (AFS::getItemTypeIfExists(linkPath)) //throw FileError { @@ -242,8 +250,9 @@ bool FileVersioner::revisionSymlink(const AbstractPath& linkPath, const Zstring& void FileVersioner::revisionFolder(const AbstractPath& folderPath, const Zstring& relativePath, //throw FileError const std::function& onBeforeFileMove, const std::function& onBeforeFolderMove, - const IOCallback& notifyUnbufferedIO) + const IOCallback& notifyUnbufferedIO) const { + //no error situation if directory is not existing! manual deletion relies on it! if (Opt type = AFS::getItemTypeIfExists(folderPath)) //throw FileError { if (*type == AFS::ItemType::SYMLINK) //on Linux there is just one type of symlink, and since we do revision file symlinks, we should revision dir symlinks as well! @@ -257,24 +266,26 @@ void FileVersioner::revisionFolder(const AbstractPath& folderPath, const Zstring else revisionFolderImpl(folderPath, relativePath, onBeforeFileMove, onBeforeFolderMove, notifyUnbufferedIO); //throw FileError } - //no error situation if directory is not existing! manual deletion relies on it! + else //even if the folder did not exist anymore, significant I/O work was done => report + if (onBeforeFolderMove) onBeforeFolderMove(AFS::getDisplayPath(folderPath), AFS::getDisplayPath(AFS::appendRelPath(versioningFolderPath_, relativePath))); } void FileVersioner::revisionFolderImpl(const AbstractPath& folderPath, const Zstring& relativePath, //throw FileError const std::function& onBeforeFileMove, const std::function& onBeforeFolderMove, - const IOCallback& notifyUnbufferedIO) + const IOCallback& notifyUnbufferedIO) const { //create target directories only when needed in moveFileToVersioning(): avoid empty directories! - FlatTraverserCallback ft(folderPath); //traverse source directory one level deep - AFS::traverseFolder(folderPath, ft); //throw FileError + auto ft = std::make_shared(folderPath); //traverse source directory one level deep + const AFS::PathComponents pc = AFS::getPathComponents(folderPath); + AFS::traverseFolderParallel(pc.rootPath, {{ pc.relPath, ft }}, 1 /*parallelOps*/); //throw FileError const Zstring relPathPf = appendSeparator(relativePath); - for (const auto& fileInfo: ft.refFiles()) + for (const auto& fileInfo: ft->refFiles()) { const AbstractPath sourcePath = AFS::appendRelPath(folderPath, fileInfo.itemName); const AbstractPath targetPath = generateVersionedPath(relPathPf + fileInfo.itemName); @@ -288,13 +299,13 @@ void FileVersioner::revisionFolderImpl(const AbstractPath& folderPath, const Zst //target existing: copyFileTransactional() undefined behavior! (fail/overwrite/auto-rename) => not expected here: /*const AFS::FileCopyResult result =*/ AFS::copyFileTransactional(sourcePath, sourceAttr, targetPath, //throw FileError, ErrorFileLocked false, //copyFilePermissions - false, //transactionalCopy: not needed for versioning! + false, //transactionalCopy: not needed for versioning! partial copy will be overwritten next time nullptr /*onDeleteTargetFile*/, notifyUnbufferedIO); //result.errorModTime? => irrelevant for versioning! }); } - for (const auto& linkInfo: ft.refSymlinks()) + for (const auto& linkInfo: ft->refSymlinks()) { const AbstractPath sourcePath = AFS::appendRelPath(folderPath, linkInfo.itemName); const AbstractPath targetPath = generateVersionedPath(relPathPf + linkInfo.itemName); @@ -306,7 +317,7 @@ void FileVersioner::revisionFolderImpl(const AbstractPath& folderPath, const Zst } //move folders recursively - for (const auto& folderInfo : ft.refFolders()) + for (const auto& folderInfo : ft->refFolders()) revisionFolderImpl(AFS::appendRelPath(folderPath, folderInfo.itemName), //throw FileError relPathPf + folderInfo.itemName, onBeforeFileMove, onBeforeFolderMove, notifyUnbufferedIO); @@ -319,7 +330,7 @@ void FileVersioner::revisionFolderImpl(const AbstractPath& folderPath, const Zst /* -void FileVersioner::limitVersions(std::function updateUI) //throw FileError +void FileVersioner::limitVersions(const std::function& updateUI) //throw FileError { if (versionCountLimit_ < 0) //no limit! return; diff --git a/FreeFileSync/Source/lib/versioning.h b/FreeFileSync/Source/lib/versioning.h index 4236aff2..78a031a0 100755 --- a/FreeFileSync/Source/lib/versioning.h +++ b/FreeFileSync/Source/lib/versioning.h @@ -24,9 +24,10 @@ namespace fff - creates missing intermediate directories - does not create empty directories - handles symlinks + - multi-threading: internally synchronized - replaces already existing target files/dirs (supports retry) => (unlikely) risk of data loss for naming convention "versioning": - race-condition if two FFS instances start at the very same second OR multiple folder pairs process the same filepath!! + race-condition if multiple folder pairs process the same filepath!! */ class FileVersioner @@ -48,28 +49,33 @@ public: throw FileError(_("Unable to create time stamp for versioning:") + L" \"" + utfTo(timeStamp_) + L"\""); } + //multi-threaded access: internally synchronized! bool revisionFile(const FileDescriptor& fileDescr, //throw FileError; return "false" if file is not existing const Zstring& relativePath, //called frequently if move has to revert to copy + delete => see zen::copyFile for limitations when throwing exceptions! - const zen::IOCallback& notifyUnbufferedIO); //may be nullptr + const zen::IOCallback& notifyUnbufferedIO) const; //may be nullptr - bool revisionSymlink(const AbstractPath& linkPath, const Zstring& relativePath); //throw FileError; return "false" if file is not existing + bool revisionSymlink(const AbstractPath& linkPath, const Zstring& relativePath) const; //throw FileError; return "false" if file is not existing void revisionFolder(const AbstractPath& folderPath, const Zstring& relativePath, //throw FileError //optional callbacks: may be nullptr - const std::function& onBeforeFileMove, //one call for each *existing* object! + const std::function& onBeforeFileMove, //one call for each object! const std::function& onBeforeFolderMove, // //called frequently if move has to revert to copy + delete => see zen::copyFile for limitations when throwing exceptions! - const zen::IOCallback& notifyUnbufferedIO); + const zen::IOCallback& notifyUnbufferedIO) const; + //multi-threaded access: ? //void limitVersions(std::function updateUI); //throw FileError; call when done revisioning! private: + FileVersioner (const FileVersioner&) = delete; + FileVersioner& operator=(const FileVersioner&) = delete; + void revisionFolderImpl(const AbstractPath& folderPath, const Zstring& relativePath, const std::function& onBeforeFileMove, const std::function& onBeforeFolderMove, - const zen::IOCallback& notifyUnbufferedIO); //throw FileError + const zen::IOCallback& notifyUnbufferedIO) const; //throw FileError AbstractPath generateVersionedPath(const Zstring& relativePath) const; @@ -77,7 +83,7 @@ private: const VersioningStyle versioningStyle_; const Zstring timeStamp_; - //std::vector fileRelNames; //store list of revisioned file and symlink relative names for limitVersions() + //Protected> fileRelNames_; //list of revisioned file and symlink relative names for limitVersions() }; namespace impl //declare for unit tests: diff --git a/FreeFileSync/Source/process_callback.h b/FreeFileSync/Source/process_callback.h index 7a792917..0a8f487e 100755 --- a/FreeFileSync/Source/process_callback.h +++ b/FreeFileSync/Source/process_callback.h @@ -38,8 +38,8 @@ struct ProcessCallback //note: this one must NOT throw in order to properly allow undoing setting of statistics! //it is in general paired with a call to requestUiRefresh() to compensate! - virtual void updateProcessedData(int itemsDelta, int64_t bytesDelta) = 0; //noexcept!! - virtual void updateTotalData (int itemsDelta, int64_t bytesDelta) = 0; // + virtual void updateDataProcessed(int itemsDelta, int64_t bytesDelta) = 0; //noexcept!! + virtual void updateDataTotal (int itemsDelta, int64_t bytesDelta) = 0; // /* the estimated and actual total workload may change *during* sync: 1. file cannot be moved -> fallback to copy + delete 2. file copy, actual size changed after comparison @@ -53,22 +53,29 @@ struct ProcessCallback 10. Error during file copy, retry: bytes were copied => increases total workload! */ - //opportunity to abort must be implemented in a frequently executed method like requestUiRefresh() + //opportunity to abort must be implemented in a frequently-executed method like requestUiRefresh() virtual void requestUiRefresh() = 0; //throw X virtual void forceUiRefresh () = 0; //throw X - called before starting long running tasks which don't update regularly - //called periodically after data was processed: expected(!) to request GUI update - virtual void reportStatus(const std::wstring& text) = 0; //throw X; UI info only, should not be logged! + //UI info only, should not be logged: called periodically after data was processed: expected(!) to request GUI update + virtual void reportStatus(const std::wstring& msg) = 0; //throw X - //called periodically after data was processed: expected(!) to request GUI update - virtual void reportInfo(const std::wstring& text) = 0; //throw X + //logging only, no status update! + virtual void logInfo(const std::wstring& msg) = 0; + + //called periodically after data was processed + void reportInfo(const std::wstring& msg) //throw X + { + logInfo(msg); + reportStatus(msg); //throw X + } virtual void reportWarning(const std::wstring& warningMessage, bool& warningActive) = 0; //throw X //error handling: enum Response { - IGNORE_ERROR = 10, + IGNORE_ERROR, RETRY }; virtual Response reportError (const std::wstring& errorMessage, size_t retryNumber) = 0; //throw X; recoverable error situation diff --git a/FreeFileSync/Source/structures.cpp b/FreeFileSync/Source/structures.cpp index 79c3029e..a882807f 100755 --- a/FreeFileSync/Source/structures.cpp +++ b/FreeFileSync/Source/structures.cpp @@ -187,14 +187,14 @@ std::wstring fff::getCompVariantName(const MainConfiguration& mainCfg) { const CompareVariant firstVariant = mainCfg.firstPair.localCmpCfg ? mainCfg.firstPair.localCmpCfg->compareVar : - mainCfg.cmpConfig.compareVar; //fallback to main sync cfg + mainCfg.cmpCfg.compareVar; //fallback to main sync cfg //test if there's a deviating variant within the additional folder pairs for (const LocalPairConfig& lpc : mainCfg.additionalPairs) { const CompareVariant thisVariant = lpc.localCmpCfg ? lpc.localCmpCfg->compareVar : - mainCfg.cmpConfig.compareVar; //fallback to main sync cfg + mainCfg.cmpCfg.compareVar; //fallback to main sync cfg if (thisVariant != firstVariant) return _("Multiple..."); } @@ -493,7 +493,7 @@ MainConfiguration fff::merge(const std::vector& mainCfgs) for (LocalPairConfig& lpc : tmpCfgs) { if (!lpc.localCmpCfg) - lpc.localCmpCfg = mainCfg.cmpConfig; + lpc.localCmpCfg = mainCfg.cmpCfg; if (!lpc.localSyncCfg) lpc.localSyncCfg = mainCfg.syncCfg; @@ -568,13 +568,20 @@ MainConfiguration fff::merge(const std::vector& mainCfgs) lpc.localFilter = FilterConfig(); } + std::map mergedParallelOps; + for (const MainConfiguration& mainCfg : mainCfgs) + for (const auto& item : mainCfg.deviceParallelOps) + mergedParallelOps[item.first] = std::max(mergedParallelOps[item.first], item.second); + //final assembly MainConfiguration cfgOut; - cfgOut.cmpConfig = cmpCfgHead; + cfgOut.cmpCfg = cmpCfgHead; cfgOut.syncCfg = syncCfgHead; cfgOut.globalFilter = globalFilter; cfgOut.firstPair = mergedCfgs[0]; cfgOut.additionalPairs.assign(mergedCfgs.begin() + 1, mergedCfgs.end()); + cfgOut.deviceParallelOps = mergedParallelOps; + cfgOut.ignoreErrors = std::all_of(mainCfgs.begin(), mainCfgs.end(), [](const MainConfiguration& mainCfg) { return mainCfg.ignoreErrors; }); cfgOut.automaticRetryCount = std::max_element(mainCfgs.begin(), mainCfgs.end(), diff --git a/FreeFileSync/Source/structures.h b/FreeFileSync/Source/structures.h index 298eda76..2403cf8d 100755 --- a/FreeFileSync/Source/structures.h +++ b/FreeFileSync/Source/structures.h @@ -11,10 +11,13 @@ #include #include #include +#include "fs/abstract.h" namespace fff { +using AFS = AbstractFileSystem; + enum class CompareVariant { TIME_SIZE, @@ -331,13 +334,13 @@ struct LocalPairConfig //enhanced folder pairs with (optional) alternate configu LocalPairConfig(const Zstring& phraseLeft, const Zstring& phraseRight, - const zen::Opt& cmpConfig, - const zen::Opt& syncConfig, + const zen::Opt& cmpCfg, + const zen::Opt& syncCfg, const FilterConfig& filter) : folderPathPhraseLeft (phraseLeft), folderPathPhraseRight(phraseRight), - localCmpCfg(cmpConfig), - localSyncCfg(syncConfig), + localCmpCfg(cmpCfg), + localSyncCfg(syncCfg), localFilter(filter) {} Zstring folderPathPhraseLeft; //unresolved directory names as entered by user! @@ -371,13 +374,15 @@ enum class PostSyncCondition struct MainConfiguration { - CompConfig cmpConfig; //global compare settings: may be overwritten by folder pair settings + CompConfig cmpCfg; //global compare settings: may be overwritten by folder pair settings SyncConfig syncCfg; //global synchronisation settings: may be overwritten by folder pair settings FilterConfig globalFilter; //global filter settings: combined with folder pair settings LocalPairConfig firstPair; //there needs to be at least one pair! std::vector additionalPairs; + std::map deviceParallelOps; //should only include devices with >= 2 parallel ops + bool ignoreErrors = false; //true: errors will still be logged size_t automaticRetryCount = 0; size_t automaticRetryDelay = 5; //unit: [sec] @@ -393,11 +398,12 @@ std::wstring getSyncVariantName(const MainConfiguration& mainCfg); inline bool operator==(const MainConfiguration& lhs, const MainConfiguration& rhs) { - return lhs.cmpConfig == rhs.cmpConfig && + return lhs.cmpCfg == rhs.cmpCfg && lhs.syncCfg == rhs.syncCfg && lhs.globalFilter == rhs.globalFilter && lhs.firstPair == rhs.firstPair && lhs.additionalPairs == rhs.additionalPairs && + lhs.deviceParallelOps == rhs.deviceParallelOps && lhs.ignoreErrors == rhs.ignoreErrors && lhs.automaticRetryCount == rhs.automaticRetryCount && lhs.automaticRetryDelay == rhs.automaticRetryDelay && diff --git a/FreeFileSync/Source/synchronization.cpp b/FreeFileSync/Source/synchronization.cpp index a4d064d7..89c9684c 100755 --- a/FreeFileSync/Source/synchronization.cpp +++ b/FreeFileSync/Source/synchronization.cpp @@ -253,17 +253,19 @@ std::vector fff::extractSyncCfg(const MainConfiguration& main std::vector output; - //process all pairs for (const LocalPairConfig& lpc : localCfgs) { - SyncConfig syncCfg = lpc.localSyncCfg ? *lpc.localSyncCfg : mainCfg.syncCfg; + //const CompConfig cmpCfg = lpc.localCmpCfg ? *lpc.localCmpCfg : mainCfg.cmpCfg; + const SyncConfig syncCfg = lpc.localSyncCfg ? *lpc.localSyncCfg : mainCfg.syncCfg; output.push_back( - FolderPairSyncCfg(syncCfg.directionCfg.var == DirectionConfig::TWO_WAY || detectMovedFilesEnabled(syncCfg.directionCfg), - syncCfg.handleDeletion, - syncCfg.versioningStyle, - syncCfg.versioningFolderPhrase, - syncCfg.directionCfg.var)); + { + syncCfg.directionCfg.var == DirectionConfig::TWO_WAY || detectMovedFilesEnabled(syncCfg.directionCfg), + syncCfg.handleDeletion, + syncCfg.versioningStyle, + syncCfg.versioningFolderPhrase, + syncCfg.directionCfg.var + }); } return output; } @@ -323,36 +325,466 @@ bool significantDifferenceDetected(const SyncStatistics& folderPairStat) //################################################################################################################# -class DeletionHandling //abstract deletion variants: permanently, recycle bin, user-defined directory +//--------------------- data verification ------------------------- +void flushFileBuffers(const Zstring& nativeFilePath) //throw FileError +{ + const int fileHandle = ::open(nativeFilePath.c_str(), O_WRONLY | O_APPEND); + if (fileHandle == -1) + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot open file %x."), L"%x", fmtPath(nativeFilePath)), L"open"); + ZEN_ON_SCOPE_EXIT(::close(fileHandle)); + + if (::fsync(fileHandle) != 0) + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(nativeFilePath)), L"fsync"); +} + + +void verifyFiles(const AbstractPath& sourcePath, const AbstractPath& targetPath, const IOCallback& notifyUnbufferedIO) //throw FileError +{ + try + { + //do like "copy /v": 1. flush target file buffers, 2. read again as usual (using OS buffers) + // => it seems OS buffers are not invalidated by this: snake oil??? + if (Opt nativeTargetPath = AFS::getNativeItemPath(targetPath)) + flushFileBuffers(*nativeTargetPath); //throw FileError + + if (!filesHaveSameContent(sourcePath, targetPath, notifyUnbufferedIO)) //throw FileError + throw FileError(replaceCpy(replaceCpy(_("%x and %y have different content."), + L"%x", L"\n" + fmtPath(AFS::getDisplayPath(sourcePath))), + L"%y", L"\n" + fmtPath(AFS::getDisplayPath(targetPath)))); + } + catch (const FileError& e) //add some context to error message + { + throw FileError(_("Data verification error:"), e.toString()); + } +} + +//################################################################################################################# +//################################################################################################################# + +/* ________________________________________________________________ + | | + | Multithreaded File Copy: Parallel API for expensive file I/O | + |______________________________________________________________| */ + +namespace parallel +{ +template inline +auto parallelScope(Function fun, std::mutex& singleThread) //throw X +{ + singleThread.unlock(); + ZEN_ON_SCOPE_EXIT(singleThread.lock()); + + return fun(); //throw X +} + + +inline +AFS::ItemType getItemType(const AbstractPath& ap, std::mutex& singleThread) //throw FileError +{ return parallelScope([ap] { return AFS::getItemType(ap); /*throw FileError*/ }, singleThread); } + +inline +Opt getItemTypeIfExists(const AbstractPath& ap, std::mutex& singleThread) //throw FileError +{ return parallelScope([ap] { return AFS::getItemTypeIfExists(ap); /*throw FileError*/ }, singleThread); } + +inline +bool removeFileIfExists(const AbstractPath& ap, std::mutex& singleThread) //throw FileError +{ return parallelScope([ap] { return AFS::removeFileIfExists(ap); /*throw FileError*/ }, singleThread); } + +inline +bool removeSymlinkIfExists(const AbstractPath& ap, std::mutex& singleThread) //throw FileError +{ return parallelScope([ap] { return AFS::removeSymlinkIfExists(ap); /*throw FileError*/ }, singleThread); } + +inline +void renameItem(const AbstractPath& apSource, const AbstractPath& apTarget, std::mutex& singleThread) //throw FileError, ErrorDifferentVolume +{ parallelScope([apSource, apTarget] { AFS::renameItem(apSource, apTarget); /*throw FileError, ErrorDifferentVolume*/ }, singleThread); } + +inline +AbstractPath getSymlinkResolvedPath(const AbstractPath& ap, std::mutex& singleThread) //throw FileError +{ return parallelScope([ap] { return AFS::getSymlinkResolvedPath(ap); /*throw FileError*/ }, singleThread); } + +inline +void copySymlink(const AbstractPath& apSource, const AbstractPath& apTarget, bool copyFilePermissions, std::mutex& singleThread) //throw FileError +{ parallelScope([apSource, apTarget, copyFilePermissions] { AFS::copySymlink(apSource, apTarget, copyFilePermissions); /*throw FileError*/ }, singleThread); } + +inline +void copyNewFolder(const AbstractPath& apSource, const AbstractPath& apTarget, bool copyFilePermissions, std::mutex& singleThread) //throw FileError +{ parallelScope([apSource, apTarget, copyFilePermissions] { AFS::copyNewFolder(apSource, apTarget, copyFilePermissions); /*throw FileError*/ }, singleThread); } + +inline +void removeFilePlain(const AbstractPath& ap, std::mutex& singleThread) //throw FileError +{ parallelScope([ap] { AFS::removeFilePlain(ap); /*throw FileError*/ }, singleThread); } + +//-------------------------------------------------------------- +//ATTENTION CALLBACKS: they also run asynchronously *outside* the singleThread lock! +//-------------------------------------------------------------- +inline +void removeFolderIfExistsRecursion(const AbstractPath& ap, //throw FileError + const std::function& onBeforeFileDeletion, //optional + const std::function& onBeforeFolderDeletion, //one call for each object! + std::mutex& singleThread) +{ parallelScope([ap, onBeforeFileDeletion, onBeforeFolderDeletion] { AFS::removeFolderIfExistsRecursion(ap, onBeforeFileDeletion, onBeforeFolderDeletion); /*throw FileError*/ }, singleThread); } + + +inline +AFS::FileCopyResult copyFileTransactional(const AbstractPath& apSource, const AFS::StreamAttributes& attrSource, //throw FileError, ErrorFileLocked + const AbstractPath& apTarget, + bool copyFilePermissions, + bool transactionalCopy, + const std::function& onDeleteTargetFile, + const IOCallback& notifyUnbufferedIO, + std::mutex& singleThread) +{ + return parallelScope([=] + { + return AFS::copyFileTransactional(apSource, attrSource, apTarget, copyFilePermissions, transactionalCopy, onDeleteTargetFile, notifyUnbufferedIO); //throw FileError, ErrorFileLocked + }, singleThread); +} + +inline //RecycleSession::recycleItem() is internally synchronized! +bool recycleItem(AFS::RecycleSession& recyclerSession, const AbstractPath& ap, const Zstring& logicalRelPath, std::mutex& singleThread) //throw FileError +{ return parallelScope([=, &recyclerSession] { return recyclerSession.recycleItem(ap, logicalRelPath); /*throw FileError*/ }, singleThread); } + +inline //FileVersioner::revisionFile() is internally synchronized! +bool revisionFile(FileVersioner& versioner, const FileDescriptor& fileDescr, const Zstring& relativePath, const IOCallback& notifyUnbufferedIO, std::mutex& singleThread) //throw FileError +{ return parallelScope([=, &versioner] { return versioner.revisionFile(fileDescr, relativePath, notifyUnbufferedIO); /*throw FileError*/ }, singleThread); } + +inline //FileVersioner::revisionSymlink() is internally synchronized! +bool revisionSymlink(FileVersioner& versioner, const AbstractPath& linkPath, const Zstring& relativePath, std::mutex& singleThread) //throw FileError +{ return parallelScope([=, &versioner] { return versioner.revisionSymlink(linkPath, relativePath); /*throw FileError*/ }, singleThread); } + +inline //FileVersioner::revisionFolder() is internally synchronized! +void revisionFolder(FileVersioner& versioner, + const AbstractPath& folderPath, const Zstring& relativePath, //throw FileError + const std::function& onBeforeFileMove, + const std::function& onBeforeFolderMove, + const IOCallback& notifyUnbufferedIO, + std::mutex& singleThread) +{ parallelScope([=, &versioner] { versioner.revisionFolder(folderPath, relativePath, onBeforeFileMove, onBeforeFolderMove, notifyUnbufferedIO); /*throw FileError*/ }, singleThread); } + +inline +void verifyFiles(const AbstractPath& apSource, const AbstractPath& apTarget, const IOCallback& notifyUnbufferedIO, std::mutex& singleThread) //throw FileError +{ parallelScope([=] { ::verifyFiles(apSource, apTarget, notifyUnbufferedIO); /*throw FileError*/ }, singleThread); } + +} + + +namespace +{ +class AsyncCallback //actor pattern { public: - DeletionHandling(const AbstractPath& baseFolderPath, - DeletionPolicy handleDel, //nothrow! - const Zstring& versioningFolderPhrase, - VersioningStyle versioningStyle, - const TimeComp& timeStamp, - ProcessCallback& procCallback); - ~DeletionHandling() + AsyncCallback(size_t threadCount) : threadStatus_(threadCount), totalThreadCount_(threadCount) {} + + //non-blocking: context of worker thread + void updateDataProcessed(int itemsDelta, int64_t bytesDelta) //noexcept!! { - //always (try to) clean up, even if synchronization is aborted! + itemsDeltaProcessed_ += itemsDelta; + bytesDeltaProcessed_ += bytesDelta; + } + void updateDataTotal(int itemsDelta, int64_t bytesDelta) //noexcept!! + { + itemsDeltaTotal_ += itemsDelta; + bytesDeltaTotal_ += bytesDelta; + } + + //context of main thread + void reportStats(ProcessCallback& cb) + { + assert(std::this_thread::get_id() == mainThreadId); + + const std::pair deltaProcessed(itemsDeltaProcessed_, bytesDeltaProcessed_); + if (deltaProcessed.first != 0 || deltaProcessed.second != 0) + { + updateDataProcessed (-deltaProcessed.first, -deltaProcessed.second); //careful with these atomics: don't just set to 0 + cb.updateDataProcessed( deltaProcessed.first, deltaProcessed.second); //noexcept!! + } + const std::pair deltaTotal(itemsDeltaTotal_, bytesDeltaTotal_); + if (deltaTotal.first != 0 || deltaTotal.second != 0) + { + updateDataTotal (-deltaTotal.first, -deltaTotal.second); + cb.updateDataTotal( deltaTotal.first, deltaTotal.second); //noexcept!! + } + } + + //context of worker thread + void reportStatus(const std::wstring& msg, size_t threadIdx) //throw ThreadInterruption + { + assert(std::this_thread::get_id() != mainThreadId); + std::lock_guard dummy(lockCurrentStatus_); + + assert(threadStatus_[threadIdx].active); + threadStatus_[threadIdx].statusMsg = msg; + + interruptionPoint(); //throw ThreadInterruption + } + + //context of main thread, call repreatedly + std::wstring getCurrentStatus() + { + assert(std::this_thread::get_id() == mainThreadId); + + int activeThreadCount = 0; + std::wstring statusMsg; + { + std::lock_guard dummy(lockCurrentStatus_); + + for (const ThreadStatus& ts : threadStatus_) + if (ts.active) + { + ++activeThreadCount; + if (statusMsg.empty()) + statusMsg = ts.statusMsg; + } + } + + std::wstring output; + if (activeThreadCount >= 2) + output = L"[" + _P("1 thread", "%x threads", activeThreadCount) + L"] "; + output += statusMsg; + return output; + } + + //blocking call: context of worker thread + //=> indirect support for "pause": reportInfo() is called under singleThread lock, + // so all other worker threads will wait when coming out of parallel I/O (trying to lock singleThread) + void reportInfo(const std::wstring& msg, size_t threadIdx) //throw ThreadInterruption + { + reportStatus(msg, threadIdx); //throw ThreadInterruption + logInfo (msg, threadIdx); // + } + + //blocking call: context of worker thread + void logInfo(const std::wstring& msg, size_t threadIdx) //throw ThreadInterruption + { + assert(std::this_thread::get_id() != mainThreadId); + std::unique_lock dummy(lockRequest_); + interruptibleWait(conditionReadyForNewRequest_, dummy, [this] { return !logInfoRequest_; }); //throw ThreadInterruption + + logInfoRequest_ = (totalThreadCount_ > 1 ? L"[" + numberTo(threadIdx + 1) + L"] " : L"") + msg; + + dummy.unlock(); //optimization for condition_variable::notify_all() + conditionNewRequest.notify_all(); + } + + //blocking call: context of worker thread + ProcessCallback::Response reportError(const std::wstring& msg, size_t retryNumber, size_t threadIdx) //throw ThreadInterruption + { + assert(std::this_thread::get_id() != mainThreadId); + std::unique_lock dummy(lockRequest_); + interruptibleWait(conditionReadyForNewRequest_, dummy, [this] { return !errorRequest_ && !errorResponse_; }); //throw ThreadInterruption + + errorRequest_ = ErrorInfo({ (totalThreadCount_ > 1 ? L"[" + numberTo(threadIdx + 1) + L"] " : L"") + msg, retryNumber }); + conditionNewRequest.notify_all(); + + interruptibleWait(conditionHaveResponse_, dummy, [this] { return static_cast(errorResponse_); }); //throw ThreadInterruption + + ProcessCallback::Response rv = *errorResponse_; + + errorRequest_ = NoValue(); + errorResponse_ = NoValue(); + + dummy.unlock(); //optimization for condition_variable::notify_all() + conditionReadyForNewRequest_.notify_all(); //=> spurious wake-up for AsyncCallback::logInfo() + return rv; + } + + //context of main thread + void waitUntilDone(std::chrono::milliseconds duration, ProcessCallback& cb) //throw X + { + assert(std::this_thread::get_id() == mainThreadId); + for (;;) + { + const std::chrono::steady_clock::time_point callbackTime = std::chrono::steady_clock::now() + duration; + + for (std::unique_lock dummy(lockRequest_) ;;) //process all errors without delay + { + const bool rv = conditionNewRequest.wait_until(dummy, callbackTime, [this] { return (errorRequest_ && !errorResponse_) || logInfoRequest_ || finishNowRequest_; }); + if (!rv) //time-out + condition not met + break; + + if (errorRequest_ && !errorResponse_) + { + assert(!finishNowRequest_); + errorResponse_ = cb.reportError(errorRequest_->msg, errorRequest_->retryNumber); //throw X + conditionHaveResponse_.notify_all(); //instead of notify_one(); workaround bug: https://svn.boost.org/trac/boost/ticket/7796 + } + if (logInfoRequest_) + { + cb.logInfo(*logInfoRequest_); + logInfoRequest_ = NoValue(); + conditionReadyForNewRequest_.notify_all(); //=> spurious wake-up for AsyncCallback::reportError() + } + if (finishNowRequest_) + { + dummy.unlock(); //call member functions outside of mutex scope: + reportStats(cb); //one last call for accurate stat-reporting! + return; + } + } + + //call member functions outside of mutex scope: + cb.reportStatus(getCurrentStatus()); //throw X + reportStats(cb); + } + } + + void notifyWorkBegin(size_t threadIdx) //noexcept + { + std::lock_guard dummy(lockCurrentStatus_); + assert(!threadStatus_[threadIdx].active); + threadStatus_[threadIdx].active = true; + } + + void notifyWorkEnd(size_t threadIdx) //noexcept + { + std::lock_guard dummy(lockCurrentStatus_); + assert(threadStatus_[threadIdx].active); + threadStatus_[threadIdx].active = false; + threadStatus_[threadIdx].statusMsg.clear(); + } + + void notifyAllDone() //noexcept + { + std::lock_guard dummy(lockRequest_); + assert(!finishNowRequest_); + finishNowRequest_ = true; + conditionNewRequest.notify_all(); //perf: should unlock mutex before notify!? (insignificant) + } + +private: + AsyncCallback (const AsyncCallback&) = delete; + AsyncCallback& operator=(const AsyncCallback&) = delete; + + struct ThreadStatus + { + bool active = false; + std::wstring statusMsg; + }; + struct ErrorInfo + { + std::wstring msg; + size_t retryNumber = 0; + }; + + //---- main <-> worker communication channel ---- + std::mutex lockRequest_; + std::condition_variable conditionReadyForNewRequest_; + std::condition_variable conditionNewRequest; + std::condition_variable conditionHaveResponse_; + Opt errorRequest_; + Opt errorResponse_; + Opt logInfoRequest_; + bool finishNowRequest_ = false; + + //---- status updates ---- + std::mutex lockCurrentStatus_; //use a different lock for current file: continue traversing while some thread may process an error + std::vector threadStatus_; + + const size_t totalThreadCount_; + + //---- status updates II (lock-free) ---- + std::atomic itemsDeltaProcessed_{ 0 }; // + std::atomic bytesDeltaProcessed_{ 0 }; //std:atomic is uninitialized by default! + std::atomic itemsDeltaTotal_ { 0 }; // + std::atomic bytesDeltaTotal_ { 0 }; // +}; + + +template inline //return ignored error message if available +Opt tryReportingError(Function cmd, size_t threadIdx, AsyncCallback& acb) //throw ThreadInterruption +{ + for (size_t retryNumber = 0;; ++retryNumber) try { - tryCleanup(false /*allowCallbackException*/); //throw FileError, (throw X) + cmd(); //throw FileError + return NoValue(); + } + catch (FileError& error) + { + switch (acb.reportError(error.toString(), retryNumber, threadIdx)) //throw ThreadInterruption + { + case ProcessCallback::IGNORE_ERROR: + return error.toString(); + case ProcessCallback::RETRY: + break; //continue with loop + } + } +} + + +//manage statistics reporting for a single item of work +class AsyncItemStatReporter +{ +public: + AsyncItemStatReporter(int itemsExpected, int64_t bytesExpected, size_t threadIdx, AsyncCallback& acb) : + itemsExpected_(itemsExpected), + bytesExpected_(bytesExpected), + threadIdx_(threadIdx), + acb_(acb) {} + + ~AsyncItemStatReporter() + { + const bool scopeFail = getUncaughtExceptionCount() > exeptionCount_; + if (scopeFail) + acb_.updateDataTotal(itemsReported_, bytesReported_); //=> unexpected increase of total workload + else + //update statistics to consider the real amount of data, e.g. more than the "file size" for ADS streams, + //less for sparse and compressed files, or file changed in the meantime! + acb_.updateDataTotal(itemsReported_ - itemsExpected_, bytesReported_ - bytesExpected_); //noexcept! + } + + void reportStatus(const std::wstring& text) { acb_.reportStatus(text, threadIdx_); } //throw ThreadInterruption + + void reportDelta(int itemsDelta, int64_t bytesDelta) //throw ThreadInterruption + { + acb_.updateDataProcessed(itemsDelta, bytesDelta); //nothrow! + itemsReported_ += itemsDelta; + bytesReported_ += bytesDelta; + + //special rule: avoid temporary statistics mess up, even though they are corrected anyway below: + if (itemsReported_ > itemsExpected_) + { + acb_.updateDataTotal(itemsReported_ - itemsExpected_, 0); + itemsReported_ = itemsExpected_; + } + if (bytesReported_ > bytesExpected_) + { + acb_.updateDataTotal(0, bytesReported_ - bytesExpected_); //=> everything above "bytesExpected" adds to both "processed" and "total" data + bytesReported_ = bytesExpected_; } - catch (FileError&) {} - catch (...) { assert(false); } //what is this? - /* - may block heavily, but still do not allow user callback: - -> avoid throwing user cancel exception again, leading to incomplete clean-up! - */ + + interruptionPoint(); //throw ThreadInterruption } +private: + int itemsReported_ = 0; + int64_t bytesReported_ = 0; + const int itemsExpected_; + const int64_t bytesExpected_; + const size_t threadIdx_; + AsyncCallback& acb_; + const int exeptionCount_ = getUncaughtExceptionCount(); +}; +} + +//################################################################################################################# +//################################################################################################################# + +class DeletionHandling //abstract deletion variants: permanently, recycle bin, user-defined directory +{ +public: + DeletionHandling(const AbstractPath& baseFolderPath, + DeletionPolicy handleDel, //nothrow! + const Zstring& versioningFolderPhrase, + VersioningStyle versioningStyle, + const TimeComp& timeStamp); + //clean-up temporary directory (recycle bin optimization) - void tryCleanup(bool allowCallbackException); //throw FileError; throw X -> call this in non-exceptional coding, i.e. somewhere after sync! + void tryCleanup(ProcessCallback& cb /*throw X*/, bool allowCallbackException); //throw FileError -> call this in non-exceptional code path, i.e. somewhere after sync! - template void removeFileWithCallback (const FileDescriptor& fileDescr, const Zstring& relativePath, Function onNotifyItemDeletion, const IOCallback& notifyUnbufferedIO); // - template void removeDirWithCallback (const AbstractPath& dirPath, const Zstring& relativePath, Function onNotifyItemDeletion, const IOCallback& notifyUnbufferedIO); //throw FileError - template void removeLinkWithCallback (const AbstractPath& linkPath, const Zstring& relativePath, Function onNotifyItemDeletion); // + void removeDirWithCallback (const AbstractPath& dirPath, const Zstring& relativePath, AsyncItemStatReporter& statReporter, std::mutex& singleThread); // + void removeFileWithCallback(const FileDescriptor& fileDescr, const Zstring& relativePath, AsyncItemStatReporter& statReporter, std::mutex& singleThread); //throw FileError, ThreadInterruption + void removeLinkWithCallback(const AbstractPath& linkPath, const Zstring& relativePath, AsyncItemStatReporter& statReporter, std::mutex& singleThread); // const std::wstring& getTxtRemovingFile () const { return txtRemovingFile_; } // const std::wstring& getTxtRemovingFolder () const { return txtRemovingFolder_; } //buffered status texts @@ -365,7 +797,7 @@ private: AFS::RecycleSession& getOrCreateRecyclerSession() //throw FileError => dont create in constructor!!! { assert(deletionPolicy_ == DeletionPolicy::RECYCLER); - if (!recyclerSession_.get()) + if (!recyclerSession_) recyclerSession_ = AFS::createRecyclerSession(baseFolderPath_); //throw FileError return *recyclerSession_; } @@ -373,13 +805,11 @@ private: FileVersioner& getOrCreateVersioner() //throw FileError => dont create in constructor!!! { assert(deletionPolicy_ == DeletionPolicy::VERSIONING); - if (!versioner_.get()) + if (!versioner_) versioner_ = std::make_unique(versioningFolderPath_, versioningStyle_, timeStamp_); //throw FileError return *versioner_; } - ProcessCallback& procCallback_; - const DeletionPolicy deletionPolicy_; //keep it invariant! e.g. consider getOrCreateVersioner() one-time construction! const AbstractPath baseFolderPath_; @@ -392,71 +822,84 @@ private: std::unique_ptr versioner_; //throw FileError in constructor => create on demand! //buffer status texts: - std::wstring txtRemovingFile_; - std::wstring txtRemovingSymlink_; - std::wstring txtRemovingFolder_; - - const std::wstring txtMovingFile_; - const std::wstring txtMovingFolder_; + const std::wstring txtRemovingFile_; + const std::wstring txtRemovingSymlink_; + const std::wstring txtRemovingFolder_; + const std::wstring txtMovingFileXtoY_ = _("Moving file %x to %y"); + const std::wstring txtMovingFolderXtoY_ = _("Moving folder %x to %y"); }; -DeletionHandling::DeletionHandling(const AbstractPath& baseFolderPath, - DeletionPolicy handleDel, //nothrow! +DeletionHandling::DeletionHandling(const AbstractPath& baseFolderPath, //nothrow! + DeletionPolicy handleDel, const Zstring& versioningFolderPhrase, VersioningStyle versioningStyle, - const TimeComp& timeStamp, - ProcessCallback& procCallback) : - procCallback_(procCallback), + const TimeComp& timeStamp) : deletionPolicy_(handleDel), baseFolderPath_(baseFolderPath), versioningFolderPath_(createAbstractPath(versioningFolderPhrase)), versioningStyle_(versioningStyle), timeStamp_(timeStamp), - txtMovingFile_ (_("Moving file %x to %y")), - txtMovingFolder_(_("Moving folder %x to %y")) + txtRemovingFile_([&] { - switch (deletionPolicy_) + switch (handleDel) { case DeletionPolicy::PERMANENT: - txtRemovingFile_ = _("Deleting file %x" ); - txtRemovingFolder_ = _("Deleting folder %x" ); - txtRemovingSymlink_ = _("Deleting symbolic link %x"); - break; - + return _("Deleting file %x"); case DeletionPolicy::RECYCLER: - txtRemovingFile_ = _("Moving file %x to the recycle bin" ); - txtRemovingFolder_ = _("Moving folder %x to the recycle bin" ); - txtRemovingSymlink_ = _("Moving symbolic link %x to the recycle bin"); - break; - + return _("Moving file %x to the recycle bin"); case DeletionPolicy::VERSIONING: - txtRemovingFile_ = replaceCpy(_("Moving file %x to %y" ), L"%y", fmtPath(AFS::getDisplayPath(versioningFolderPath_))); - txtRemovingFolder_ = replaceCpy(_("Moving folder %x to %y" ), L"%y", fmtPath(AFS::getDisplayPath(versioningFolderPath_))); - txtRemovingSymlink_ = replaceCpy(_("Moving symbolic link %x to %y"), L"%y", fmtPath(AFS::getDisplayPath(versioningFolderPath_))); - break; + return replaceCpy(_("Moving file %x to %y"), L"%y", fmtPath(AFS::getDisplayPath(versioningFolderPath_))); } -} + return std::wstring(); +}()), +txtRemovingSymlink_([&] +{ + switch (handleDel) + { + case DeletionPolicy::PERMANENT: + return _("Deleting symbolic link %x"); + case DeletionPolicy::RECYCLER: + return _("Moving symbolic link %x to the recycle bin"); + case DeletionPolicy::VERSIONING: + return replaceCpy(_("Moving symbolic link %x to %y"), L"%y", fmtPath(AFS::getDisplayPath(versioningFolderPath_))); + } + return std::wstring(); +}()), +txtRemovingFolder_([&] +{ + switch (handleDel) + { + case DeletionPolicy::PERMANENT: + return _("Deleting folder %x"); + case DeletionPolicy::RECYCLER: + return _("Moving folder %x to the recycle bin"); + case DeletionPolicy::VERSIONING: + return replaceCpy(_("Moving folder %x to %y"), L"%y", fmtPath(AFS::getDisplayPath(versioningFolderPath_))); + } + return std::wstring(); +}()) {} -void DeletionHandling::tryCleanup(bool allowCallbackException) //throw FileError; throw X +void DeletionHandling::tryCleanup(ProcessCallback& cb /*throw X*/, bool allowCallbackException) //throw FileError { + assert(std::this_thread::get_id() == mainThreadId); switch (deletionPolicy_) { case DeletionPolicy::PERMANENT: break; case DeletionPolicy::RECYCLER: - if (recyclerSession_.get()) + if (recyclerSession_) { auto notifyDeletionStatus = [&](const std::wstring& displayPath) { try { if (!displayPath.empty()) - procCallback_.reportStatus(replaceCpy(txtRemovingFile_, L"%x", fmtPath(displayPath))); //throw X + cb.reportStatus(replaceCpy(txtRemovingFile_, L"%x", fmtPath(displayPath))); //throw X else - procCallback_.requestUiRefresh(); //throw X + cb.requestUiRefresh(); //throw X } catch (...) { @@ -471,12 +914,12 @@ void DeletionHandling::tryCleanup(bool allowCallbackException) //throw FileError break; case DeletionPolicy::VERSIONING: - //if (versioner.get()) + //if (versioner_) //{ // if (allowUserCallback) // { - // procCallback_.reportStatus(_("Removing old versions...")); //throw ? - // versioner->limitVersions([&] { procCallback_.requestUiRefresh(); /*throw ? */ }); //throw FileError + // cb_.reportStatus(_("Removing old versions...")); //throw X + // versioner->limitVersions([&] { cb_.requestUiRefresh(); /*throw X */ }); //throw FileError // } // else // versioner->limitVersions([] {}); //throw FileError @@ -486,97 +929,104 @@ void DeletionHandling::tryCleanup(bool allowCallbackException) //throw FileError } -template -void DeletionHandling::removeDirWithCallback(const AbstractPath& folderPath, +void DeletionHandling::removeDirWithCallback(const AbstractPath& folderPath,//throw FileError, ThreadInterruption const Zstring& relativePath, - Function onNotifyItemDeletion, - const IOCallback& notifyUnbufferedIO) //throw FileError + AsyncItemStatReporter& statReporter, std::mutex& singleThread) { switch (deletionPolicy_) { case DeletionPolicy::PERMANENT: { - auto notifyDeletion = [&](const std::wstring& statusText, const std::wstring& displayPath) + //callbacks run *outside* singleThread_ lock! => fine + auto notifyDeletion = [&statReporter](const std::wstring& statusText, const std::wstring& displayPath) { - onNotifyItemDeletion(); //it would be more correct to report *after* work was done! - procCallback_.reportStatus(replaceCpy(statusText, L"%x", fmtPath(displayPath))); + statReporter.reportStatus(replaceCpy(statusText, L"%x", fmtPath(displayPath))); //throw ThreadInterruption + statReporter.reportDelta(1, 0); //throw ThreadInterruption; it would be more correct to report *after* work was done! }; + static_assert(std::is_const::value, "callbacks better be thread-safe!"); auto onBeforeFileDeletion = [&](const std::wstring& displayPath) { notifyDeletion(txtRemovingFile_, displayPath); }; auto onBeforeDirDeletion = [&](const std::wstring& displayPath) { notifyDeletion(txtRemovingFolder_, displayPath); }; - AFS::removeFolderIfExistsRecursion(folderPath, onBeforeFileDeletion, onBeforeDirDeletion); //throw FileError + parallel::removeFolderIfExistsRecursion(folderPath, onBeforeFileDeletion, onBeforeDirDeletion, singleThread); //throw FileError } break; case DeletionPolicy::RECYCLER: - if (getOrCreateRecyclerSession().recycleItem(folderPath, relativePath)) //throw FileError - onNotifyItemDeletion(); //moving to recycler is ONE logical operation, irrespective of the number of child elements! + parallel::recycleItem(getOrCreateRecyclerSession(), folderPath, relativePath, singleThread); //throw FileError + statReporter.reportDelta(1, 0); //throw ThreadInterruption; moving to recycler is ONE logical operation, irrespective of the number of child elements! break; case DeletionPolicy::VERSIONING: { - auto notifyMove = [&](const std::wstring& statusText, const std::wstring& displayPathFrom, const std::wstring& displayPathTo) + //callbacks run *outside* singleThread_ lock! => fine + auto notifyMove = [&statReporter](const std::wstring& statusText, const std::wstring& displayPathFrom, const std::wstring& displayPathTo) { - onNotifyItemDeletion(); //it would be more correct to report *after* work was done! - procCallback_.reportStatus(replaceCpy(replaceCpy(statusText, L"%x", L"\n" + fmtPath(displayPathFrom)), L"%y", L"\n" + fmtPath(displayPathTo))); + statReporter.reportStatus(replaceCpy(replaceCpy(statusText, L"%x", L"\n" + fmtPath(displayPathFrom)), L"%y", L"\n" + fmtPath(displayPathTo))); //throw ThreadInterruption + statReporter.reportDelta(1, 0); //throw ThreadInterruption; it would be more correct to report *after* work was done! }; - auto onBeforeFileMove = [&](const std::wstring& displayPathFrom, const std::wstring& displayPathTo) { notifyMove(txtMovingFile_, displayPathFrom, displayPathTo); }; - auto onBeforeFolderMove = [&](const std::wstring& displayPathFrom, const std::wstring& displayPathTo) { notifyMove(txtMovingFolder_, displayPathFrom, displayPathTo); }; + static_assert(std::is_const::value, "callbacks better be thread-safe!"); + auto onBeforeFileMove = [&](const std::wstring& displayPathFrom, const std::wstring& displayPathTo) { notifyMove(txtMovingFileXtoY_, displayPathFrom, displayPathTo); }; + auto onBeforeFolderMove = [&](const std::wstring& displayPathFrom, const std::wstring& displayPathTo) { notifyMove(txtMovingFolderXtoY_, displayPathFrom, displayPathTo); }; + auto notifyUnbufferedIO = [&](int64_t bytesDelta) { statReporter.reportDelta(0, bytesDelta); }; //throw ThreadInterruption - getOrCreateVersioner().revisionFolder(folderPath, relativePath, onBeforeFileMove, onBeforeFolderMove, notifyUnbufferedIO); //throw FileError + parallel::revisionFolder(getOrCreateVersioner(), folderPath, relativePath, onBeforeFileMove, onBeforeFolderMove, notifyUnbufferedIO, singleThread); //throw FileError, ThreadInterruption } break; } } -template -void DeletionHandling::removeFileWithCallback(const FileDescriptor& fileDescr, +void DeletionHandling::removeFileWithCallback(const FileDescriptor& fileDescr, //throw FileError, ThreadInterruption const Zstring& relativePath, - Function onNotifyItemDeletion, - const IOCallback& notifyUnbufferedIO) //throw FileError + AsyncItemStatReporter& statReporter, std::mutex& singleThread) { - bool deleted = false; if (endsWith(relativePath, AFS::TEMP_FILE_ENDING)) //special rule for .ffs_tmp files: always delete permanently! - deleted = AFS::removeFileIfExists(fileDescr.path); //throw FileError + parallel::removeFileIfExists(fileDescr.path, singleThread); //throw FileError else switch (deletionPolicy_) { case DeletionPolicy::PERMANENT: - deleted = AFS::removeFileIfExists(fileDescr.path); //throw FileError + parallel::removeFileIfExists(fileDescr.path, singleThread); //throw FileError break; case DeletionPolicy::RECYCLER: - deleted = getOrCreateRecyclerSession().recycleItem(fileDescr.path, relativePath); //throw FileError + parallel::recycleItem(getOrCreateRecyclerSession(), fileDescr.path, relativePath, singleThread); //throw FileError break; case DeletionPolicy::VERSIONING: - deleted = getOrCreateVersioner().revisionFile(fileDescr, relativePath, notifyUnbufferedIO); //throw FileError - break; + { + //callback runs *outside* singleThread_ lock! => fine + auto notifyUnbufferedIO = [&](int64_t bytesDelta) { statReporter.reportDelta(0, bytesDelta); }; //throw ThreadInterruption + + parallel::revisionFile(getOrCreateVersioner(), fileDescr, relativePath, notifyUnbufferedIO, singleThread); //throw FileError + } + break; } - if (deleted) - onNotifyItemDeletion(); + + //even if the source item does not exist anymore, significant I/O work was done => report + //-> also consider unconditional statReporter.reportDelta(-1, 0) when overwriting a file + statReporter.reportDelta(1, 0); //throw ThreadInterruption } -template inline -void DeletionHandling::removeLinkWithCallback(const AbstractPath& linkPath, const Zstring& relativePath, Function onNotifyItemDeletion) //throw FileError +void DeletionHandling::removeLinkWithCallback(const AbstractPath& linkPath, //throw FileError, throw ThreadInterruption + const Zstring& relativePath, + AsyncItemStatReporter& statReporter, std::mutex& singleThread) { - bool deleted = false; - switch (deletionPolicy_) { case DeletionPolicy::PERMANENT: - deleted = AFS::removeSymlinkIfExists(linkPath); //throw FileError + parallel::removeSymlinkIfExists(linkPath, singleThread); //throw FileError break; case DeletionPolicy::RECYCLER: - deleted = getOrCreateRecyclerSession().recycleItem(linkPath, relativePath); //throw FileError + parallel::recycleItem(getOrCreateRecyclerSession(), linkPath, relativePath, singleThread); //throw FileError break; case DeletionPolicy::VERSIONING: - deleted = getOrCreateVersioner().revisionSymlink(linkPath, relativePath); //throw FileError + parallel::revisionSymlink(getOrCreateVersioner(), linkPath, relativePath, singleThread); //throw FileError break; } - if (deleted) - onNotifyItemDeletion(); + + //report unconditionally, see removeFileWithCallback() + statReporter.reportDelta(1, 0); //throw ThreadInterruption } //------------------------------------------------------------------------------------------------------------ @@ -664,82 +1114,88 @@ private: }; //---------------------------------------------------------------------------------------- +class Workload; -class SynchronizeFolderPair +class FolderPairSyncer { public: - SynchronizeFolderPair(ProcessCallback& procCallback, - bool verifyCopiedFiles, - bool copyFilePermissions, - bool failSafeFileCopy, - std::vector& errorsModTime, - DeletionHandling& delHandlingLeft, - DeletionHandling& delHandlingRight) : - procCallback_(procCallback), - errorsModTime_(errorsModTime), - delHandlingLeft_(delHandlingLeft), - delHandlingRight_(delHandlingRight), - verifyCopiedFiles_(verifyCopiedFiles), - copyFilePermissions_(copyFilePermissions), - failSafeFileCopy_(failSafeFileCopy) {} - - void startSync(BaseFolderPair& baseFolder) - { - runZeroPass(baseFolder); //first process file moves - runPass(baseFolder); //delete files (or overwrite big ones with smaller ones) - runPass(baseFolder); //copy rest + struct SyncCtx + { + bool verifyCopiedFiles; + bool copyFilePermissions; + bool failSafeFileCopy; + std::vector& errorsModTime; + DeletionHandling& delHandlingLeft; + DeletionHandling& delHandlingRight; + size_t threadCount; + }; + + static void runSync(SyncCtx& syncCtx, BaseFolderPair& baseFolder, ProcessCallback& cb) + { + runPass(PASS_ZERO, syncCtx, baseFolder, cb); //prepare file moves + runPass(PASS_ONE, syncCtx, baseFolder, cb); //delete files (or overwrite big ones with smaller ones) + runPass(PASS_TWO, syncCtx, baseFolder, cb); //copy rest } private: + friend class Workload; enum PassNo { - PASS_ONE, //delete files - PASS_TWO, //create, modify + PASS_ZERO, //prepare file moves + PASS_ONE, //delete files + PASS_TWO, //create, modify PASS_NEVER //skip item }; + FolderPairSyncer(SyncCtx& syncCtx, Workload& workload, std::mutex& singleThread, size_t threadIdx, AsyncCallback& acb) : + errorsModTime_ (syncCtx.errorsModTime), + delHandlingLeft_ (syncCtx.delHandlingLeft), + delHandlingRight_ (syncCtx.delHandlingRight), + verifyCopiedFiles_ (syncCtx.verifyCopiedFiles), + copyFilePermissions_(syncCtx.copyFilePermissions), + failSafeFileCopy_ (syncCtx.failSafeFileCopy), + workload_(workload), + singleThread_(singleThread), + threadIdx_(threadIdx), + acb_(acb) {} + static PassNo getPass(const FilePair& file); static PassNo getPass(const SymlinkPair& link); static PassNo getPass(const FolderPair& folder); + static void runPass(PassNo pass, SyncCtx& syncCtx, BaseFolderPair& baseFolder, ProcessCallback& cb); //throw X + + static void appendFolderLevelWorkItems(PassNo pass, ContainerObject& hierObj, //in + std::vector>& workItems, //out + std::vector& foldersToProcess); // + template - void prepare2StepMove(FilePair& sourceObj, FilePair& targetObj); //throw FileError - bool createParentFolder(FileSystemObject& fsObj); //throw FileError + void setup2StepMove(FilePair& sourceObj, FilePair& targetObj); //throw FileError, ThreadInterruption + bool createParentFolder(FileSystemObject& fsObj); //throw FileError, ThreadInterruption template - void manageFileMove(FilePair& sourceObj, FilePair& targetObj); //throw FileError + void resolveMoveConflicts(FilePair& sourceObj, FilePair& targetObj); //throw FileError, ThreadInterruption + void prepareFileMove(FilePair& file); //throw ThreadInterruption - void runZeroPass(ContainerObject& hierObj); - template - void runPass(ContainerObject& hierObj); //throw X + void synchronizeFile(FilePair& file); // + template void synchronizeFileInt(FilePair& file, SyncOperation syncOp); //throw FileError, ThreadInterruption - void synchronizeFile(FilePair& file); - template void synchronizeFileInt(FilePair& file, SyncOperation syncOp); + void synchronizeLink(SymlinkPair& link); // + template void synchronizeLinkInt(SymlinkPair& link, SyncOperation syncOp); //throw FileError, ThreadInterruption - void synchronizeLink(SymlinkPair& link); - template void synchronizeLinkInt(SymlinkPair& link, SyncOperation syncOp); + void synchronizeFolder(FolderPair& folder); // + template void synchronizeFolderInt(FolderPair& folder, SyncOperation syncOp); //throw FileError, ThreadInterruption - void synchronizeFolder(FolderPair& folder); - template void synchronizeFolderInt(FolderPair& folder, SyncOperation syncOp); - - void reportStatus(const std::wstring& rawText, const std::wstring& displayPath) const { procCallback_.reportStatus(replaceCpy(rawText, L"%x", fmtPath(displayPath))); } - void reportInfo (const std::wstring& rawText, const std::wstring& displayPath) const { procCallback_.reportInfo (replaceCpy(rawText, L"%x", fmtPath(displayPath))); } - void reportInfo (const std::wstring& rawText, - const std::wstring& displayPath1, - const std::wstring& displayPath2) const + void reportInfo(const std::wstring& rawText, const std::wstring& displayPath) { acb_.reportInfo(replaceCpy(rawText, L"%x", fmtPath(displayPath)), threadIdx_); } + void reportInfo(const std::wstring& rawText, const std::wstring& displayPath1, const std::wstring& displayPath2) { - procCallback_.reportInfo(replaceCpy(replaceCpy(rawText, L"%x", L"\n" + fmtPath(displayPath1)), L"%y", L"\n" + fmtPath(displayPath2))); + acb_.reportInfo(replaceCpy(replaceCpy(rawText, L"%x", L"\n" + fmtPath(displayPath1)), L"%y", L"\n" + fmtPath(displayPath2)), threadIdx_); } //target existing after onDeleteTargetFile(): undefined behavior! (fail/overwrite/auto-rename) AFS::FileCopyResult copyFileWithCallback(const FileDescriptor& sourceDescr, //throw FileError const AbstractPath& targetPath, - const std::function& onDeleteTargetFile, - const IOCallback& notifyUnbufferedIO) const; - - template - DeletionHandling& getDelHandling(); - - ProcessCallback& procCallback_; + const std::function& onDeleteTargetFile, //optional! + AsyncItemStatReporter& statReporter); std::vector& errorsModTime_; DeletionHandling& delHandlingLeft_; @@ -749,24 +1205,218 @@ private: const bool copyFilePermissions_; const bool failSafeFileCopy_; - //preload status texts - const std::wstring txtCreatingFile {_("Creating file %x" )}; - const std::wstring txtCreatingLink {_("Creating symbolic link %x")}; - const std::wstring txtCreatingFolder {_("Creating folder %x" )}; - const std::wstring txtOverwritingFile {_("Updating file %x" )}; - const std::wstring txtOverwritingLink {_("Updating symbolic link %x")}; - const std::wstring txtVerifying {_("Verifying file %x" )}; - const std::wstring txtWritingAttributes{_("Updating attributes of %x")}; - const std::wstring txtMovingFile {_("Moving file %x to %y" )}; + Workload& workload_; + std::mutex& singleThread_; + const size_t threadIdx_; + AsyncCallback& acb_; + + //preload status texts (premature?) + const std::wstring txtCreatingFile_ {_("Creating file %x" )}; + const std::wstring txtCreatingLink_ {_("Creating symbolic link %x")}; + const std::wstring txtCreatingFolder_ {_("Creating folder %x" )}; + const std::wstring txtUpdatingFile_ {_("Updating file %x" )}; + const std::wstring txtUpdatingLink_ {_("Updating symbolic link %x")}; + const std::wstring txtVerifyingFile_ {_("Verifying file %x" )}; + const std::wstring txtUpdatingAttributes_{_("Updating attributes of %x")}; + const std::wstring txtMovingFileXtoY_ {_("Moving file %x to %y" )}; + const std::wstring txtSourceItemNotFound_{_("Source item %x not found" )}; }; //--------------------------------------------------------------------------------------------------------------- +/* ___________________________ + | | + | Multithreaded File Copy | + |_________________________| + + ---------------- ================= + |Async Callback| <-- |Worker Thread 1| + ---------------- ==================== + /|\ |Worker Thread 2| + | ================= + ============= | ... | + GUI <-- |Main Thread| \|/ \|/ +Callback ============= ------------------------------- + |Workload | folders to process| + ------------------------------- + +Notes: - All threads share a single mutex, unlocked only during file I/O => do NOT require file_hierarchy.cpp classes to be thread-safe (i.e. internally synchronized)! + - Workload holds (folder-level-) items in buckets associated with each worker thread (FTP scenario: avoid CWDs) + - If a worker is idle, its Workload bucket is empty and no more folders to anaylze: steal from other buckets (=> take half of largest bucket) + - Maximize opportunity for parallelization ASAP: Workload buckets serve folder-items *before* files/symlinks => reduce risk of work-stealing + - Memory consumption: "Folders to process" may grow indefinitely; however: test case "C:\", 100.000 folders => worst case only ~ 800kB on x64 +*/ + +class Workload +{ +public: + Workload(FolderPairSyncer::PassNo pass, BaseFolderPair& baseFolder, size_t threadCount, AsyncCallback& acb) : + pass_(pass), acb_(acb), workload_(threadCount), foldersToProcess_{ &baseFolder } { assert(threadCount > 0); } + + //blocking call: context of worker thread + std::function getNext(size_t threadIdx) //throw ThreadInterruption + { + std::unique_lock dummy(lockWork_); + for (;;) + { + for (;;) + { + if (!workload_[threadIdx].empty()) + { + auto workItem = workload_[threadIdx]. back(); //yes, no strong exception guarantee (std::bad_alloc) + /**/ workload_[threadIdx].pop_back(); // + return workItem; + } + if (!foldersToProcess_.empty()) + { + ContainerObject& hierObj = *foldersToProcess_. back(); + /**/ foldersToProcess_.pop_back(); + + //thread-safe thanks to std::mutex singleThread: + FolderPairSyncer::appendFolderLevelWorkItems(pass_, hierObj, //in + workload_[threadIdx], //out, appending + foldersToProcess_); // + } + else + break; + } + + //steal half of largest workload from other thread + WorkItems& items = *std::max_element(workload_.begin(), workload_.end(), [](const WorkItems& lhs, const WorkItems& rhs) { return lhs.size() < rhs.size(); }); + if (!items.empty()) //=> != workload_[threadIdx] + { + size_t pos = 0; + erase_if(items, [&](const WorkItem& wi) + { + if (pos++ % 2 == 0) + { + workload_[threadIdx].push_back(wi); + return true; + } + return false; + }); + auto workItem = workload_[threadIdx]. back(); //yes, no strong exception guarantee (std::bad_alloc) + /**/ workload_[threadIdx].pop_back(); // + return workItem; + } + + if (++idleThreads_ == workload_.size()) + acb_.notifyAllDone(); //noexcept + ZEN_ON_SCOPE_EXIT(--idleThreads_); + + acb_.notifyWorkEnd(threadIdx); + ZEN_ON_SCOPE_EXIT(acb_.notifyWorkBegin(threadIdx)); + + auto haveNewWork = [&] { return !foldersToProcess_.empty() || std::any_of(workload_.begin(), workload_.end(), [](const WorkItems& wi) { return !wi.empty(); }); }; + + interruptibleWait(conditionNewWork_, dummy, [&] { return haveNewWork(); }); //throw ThreadInterruption + //it's sufficient to notify condition in addFolderToProcess() only (as long as we use std::condition_variable::notify_all()) + } + } + + void addFolderToProcess(ContainerObject& folder) + { + { + std::lock_guard dummy(lockWork_); + foldersToProcess_.push_back(&folder); + } + conditionNewWork_.notify_all(); + } + +private: + Workload (const Workload&) = delete; + Workload& operator=(const Workload&) = delete; + + using WorkItem = std::function; + using WorkItems = std::vector; + + const FolderPairSyncer::PassNo pass_; + AsyncCallback& acb_; + + std::mutex lockWork_; + std::condition_variable conditionNewWork_; + + size_t idleThreads_ = 0; + + std::vector workload_; //thread-specific buckets + std::vector foldersToProcess_; +}; + + +void FolderPairSyncer::runPass(PassNo pass, SyncCtx& syncCtx, BaseFolderPair& baseFolder, ProcessCallback& cb) //throw X +{ + const size_t threadCount = std::max(syncCtx.threadCount, 1); + + std::mutex singleThread; //only a single worker thread may run at a time, except for parallel file I/O + + AsyncCallback acb(threadCount); // + Workload workload(pass, baseFolder, threadCount, acb); //manage life time: enclose InterruptibleThread's!!! + + FixedList worker; + + ZEN_ON_SCOPE_EXIT( for (InterruptibleThread& wt : worker) wt.join(); ); + ZEN_ON_SCOPE_EXIT( for (InterruptibleThread& wt : worker) wt.interrupt(); ); //interrupt all first, then join + + for (size_t threadIdx = 0; threadIdx < threadCount; ++threadIdx) + worker.emplace_back([fps = FolderPairSyncer(syncCtx, workload, singleThread, threadIdx, acb), threadIdx, &singleThread, &acb, &workload]() mutable + { + setCurrentThreadName(("Sync Worker[" + numberTo(threadIdx) + "]").c_str()); + + acb.notifyWorkBegin(threadIdx); + ZEN_ON_SCOPE_EXIT(acb.notifyWorkEnd(threadIdx)); + + while (/*blocking call:*/ std::function workItem = workload.getNext(threadIdx)) //throw ThreadInterruption + { + std::lock_guard dummy(singleThread); //protect ALL accesses to "fps" and workItem execution! + workItem(fps); //throw ThreadInterruption + } + }); + + acb.waitUntilDone(UI_UPDATE_INTERVAL / 2 /*every ~50 ms*/, cb); //throw X +} + + +void FolderPairSyncer::appendFolderLevelWorkItems(PassNo pass, ContainerObject& hierObj, //in + std::vector>& workItems, //out + std::vector& foldersToProcess) // +{ + const size_t itemCountOld = workItems.size(); + const size_t folderCountOld = foldersToProcess.size(); + + //synchronize folders: + for (FolderPair& folder : hierObj.refSubFolders()) + if (pass == getPass(folder)) + workItems.push_back([&folder](FolderPairSyncer& fps) + { + tryReportingError([&] { fps.synchronizeFolder(folder); }, fps.threadIdx_, fps.acb_); //throw ThreadInterruption + fps.workload_.addFolderToProcess(folder); + warn_static("unnatural processing order!?") + }); + else + foldersToProcess.push_back(&folder); + + //synchronize files: + for (FilePair& file : hierObj.refSubFiles()) + if (pass == PASS_ZERO) + workItems.push_back([&file](FolderPairSyncer& fps) { fps.prepareFileMove(file); /*throw ThreadInterruption*/ }); + else if (pass == getPass(file)) + workItems.push_back([&file](FolderPairSyncer& fps) + { + tryReportingError([&] { fps.synchronizeFile(file); }, fps.threadIdx_, fps.acb_); //throw ThreadInterruption + }); + + //synchronize symbolic links: + for (SymlinkPair& symlink : hierObj.refSubLinks()) + if (pass == getPass(symlink)) + workItems.push_back([&symlink](FolderPairSyncer& fps) + { + tryReportingError([&] { fps.synchronizeLink(symlink); }, fps.threadIdx_, fps.acb_); //throw ThreadInterruption + }); -template <> inline -DeletionHandling& SynchronizeFolderPair::getDelHandling() { return delHandlingLeft_; } + //ensure natural processing order despite LIFO: + std::reverse(workItems .begin() + itemCountOld, workItems .end()); + std::reverse(foldersToProcess.begin() + folderCountOld, foldersToProcess.end()); +} -template <> inline -DeletionHandling& SynchronizeFolderPair::getDelHandling() { return delHandlingRight_; } /* __________________________ @@ -806,8 +1456,8 @@ bool haveNameClash(const Zstring& shortname, List& m) template -void SynchronizeFolderPair::prepare2StepMove(FilePair& sourceObj, - FilePair& targetObj) //throw FileError +void FolderPairSyncer::setup2StepMove(FilePair& sourceObj, //throw FileError, ThreadInterruption + FilePair& targetObj) { //generate (hopefully) unique file name to avoid clashing with some remnant ffs_tmp file const Zstring shortGuid = printNumber(Zstr("%04x"), static_cast(getCrc16(generateGUID()))); @@ -822,11 +1472,11 @@ void SynchronizeFolderPair::prepare2StepMove(FilePair& sourceObj, const AbstractPath sourcePathTmp = AFS::appendRelPath(sourceObj.base().getAbstractPath(), sourceRelPathTmp); - reportInfo(txtMovingFile, + reportInfo(txtMovingFileXtoY_, //ThreadInterruption AFS::getDisplayPath(sourceObj.getAbstractPath()), AFS::getDisplayPath(sourcePathTmp)); - AFS::renameItem(sourceObj.getAbstractPath(), sourcePathTmp); //throw FileError, (ErrorDifferentVolume) + parallel::renameItem(sourceObj.getAbstractPath(), sourcePathTmp, singleThread_); //throw FileError, (ErrorDifferentVolume) //TODO: prepare2StepMove: consider ErrorDifferentVolume! e.g. symlink aliasing! @@ -844,11 +1494,12 @@ void SynchronizeFolderPair::prepare2StepMove(FilePair& sourceObj, tempFile .setMoveRef(targetObj.getId()); //NO statistics update! - procCallback_.requestUiRefresh(); //throw ? + interruptionPoint(); //throw ThreadInterruption } -bool SynchronizeFolderPair::createParentFolder(FileSystemObject& fsObj) //throw FileError, "false" on name clash +//return "false" on name clash +bool FolderPairSyncer::createParentFolder(FileSystemObject& fsObj) //throw FileError, ThreadInterruption { if (auto parentFolder = dynamic_cast(&fsObj.parent())) { @@ -866,20 +1517,20 @@ bool SynchronizeFolderPair::createParentFolder(FileSystemObject& fsObj) //throw assert(parentFolder->getSyncOperation() != SO_DELETE_LEFT && parentFolder->getSyncOperation() != SO_DELETE_RIGHT); - synchronizeFolder(*parentFolder); //throw FileError + synchronizeFolder(*parentFolder); //throw FileError, ThreadInterruption } return true; } template -void SynchronizeFolderPair::manageFileMove(FilePair& sourceFile, - FilePair& targetFile) //throw FileError +void FolderPairSyncer::resolveMoveConflicts(FilePair& sourceFile, //throw FileError, ThreadInterruption + FilePair& targetFile) { assert((sourceFile.getSyncOperation() == SO_MOVE_LEFT_FROM && targetFile.getSyncOperation() == SO_MOVE_LEFT_TO && side == LEFT_SIDE) || (sourceFile.getSyncOperation() == SO_MOVE_RIGHT_FROM && targetFile.getSyncOperation() == SO_MOVE_RIGHT_TO && side == RIGHT_SIDE)); - const bool sourceWillBeDeleted = [&]() -> bool + const bool sourceWillBeDeleted = [&] { if (auto parentFolder = dynamic_cast(&sourceFile.parent())) { @@ -917,83 +1568,76 @@ void SynchronizeFolderPair::manageFileMove(FilePair& sourceFile, { //prepare for move now: - revert to 2-step move on name clashes if (haveNameClash(targetFile) || - !createParentFolder(targetFile)) //throw FileError - return prepare2StepMove(sourceFile, targetFile); //throw FileError + !createParentFolder(targetFile)) //throw FileError, ThreadInterruption + return setup2StepMove(sourceFile, targetFile); //throw FileError, ThreadInterruption //finally start move! this should work now: - synchronizeFile(targetFile); //throw FileError - //SynchronizeFolderPair::synchronizeFileInt() is *not* expecting SO_MOVE_LEFT_FROM/SO_MOVE_RIGHT_FROM => start move from targetFile, not sourceFile! + synchronizeFile(targetFile); //throw FileError, ThreadInterruption + //FolderPairSyncer::synchronizeFileInt() is *not* expecting SO_MOVE_LEFT_FROM/SO_MOVE_RIGHT_FROM => start move from targetFile, not sourceFile! } //else: sourceFile will not be deleted, and is not standing in the way => delay to second pass //note: this case may include new "move sources" from two-step sub-routine!!! } -//search for file move-operations -void SynchronizeFolderPair::runZeroPass(ContainerObject& hierObj) +void FolderPairSyncer::prepareFileMove(FilePair& file) //throw ThreadInterruption { - for (FilePair& file : hierObj.refSubFiles()) + const SyncOperation syncOp = file.getSyncOperation(); + switch (syncOp) //evaluate comparison result and sync direction { - const SyncOperation syncOp = file.getSyncOperation(); - switch (syncOp) //evaluate comparison result and sync direction - { - case SO_MOVE_LEFT_FROM: - case SO_MOVE_RIGHT_FROM: - if (FilePair* targetObj = dynamic_cast(FileSystemObject::retrieve(file.getMoveRef()))) + case SO_MOVE_LEFT_FROM: + case SO_MOVE_RIGHT_FROM: + if (FilePair* targetObj = dynamic_cast(FileSystemObject::retrieve(file.getMoveRef()))) + { + FilePair* sourceObj = &file; + assert(dynamic_cast(FileSystemObject::retrieve(targetObj->getMoveRef())) == sourceObj); + + Opt errMsg = tryReportingError([&] //throw ThreadInterruption { - FilePair* sourceObj = &file; - assert(dynamic_cast(FileSystemObject::retrieve(targetObj->getMoveRef())) == sourceObj); + if (syncOp == SO_MOVE_LEFT_FROM) + resolveMoveConflicts(*sourceObj, *targetObj); //throw FileError, ThreadInterruption + else + resolveMoveConflicts(*sourceObj, *targetObj); // + }, threadIdx_, acb_); //throw ThreadInterruption - zen::Opt errMsg = tryReportingError([&] - { - if (syncOp == SO_MOVE_LEFT_FROM) - this->manageFileMove(*sourceObj, *targetObj); //throw FileError - else - this->manageFileMove(*sourceObj, *targetObj); // - }, procCallback_); //throw X? + if (errMsg) + { + //move operation has failed! We cannot allow to continue and have move source's parent directory deleted, messing up statistics! + // => revert to ordinary "copy + delete" - if (errMsg) + auto getStats = [&]() -> std::pair { - //move operation has failed! We cannot allow to continue and have move source's parent directory deleted, messing up statistics! - // => revert to ordinary "copy + delete" - - auto getStats = [&]() -> std::pair - { - SyncStatistics statSrc(*sourceObj); - SyncStatistics statTrg(*targetObj); - return { getCUD(statSrc) + getCUD(statTrg), statSrc.getBytesToProcess() + statTrg.getBytesToProcess() }; - }; - - const auto statBefore = getStats(); - sourceObj->setMoveRef(nullptr); - targetObj->setMoveRef(nullptr); - const auto statAfter = getStats(); - //fix statistics total to match "copy + delete" - procCallback_.updateTotalData(statAfter.first - statBefore.first, statAfter.second - statBefore.second); - } + SyncStatistics statSrc(*sourceObj); + SyncStatistics statTrg(*targetObj); + return { getCUD(statSrc) + getCUD(statTrg), statSrc.getBytesToProcess() + statTrg.getBytesToProcess() }; + }; + + const auto statBefore = getStats(); + sourceObj->setMoveRef(nullptr); + targetObj->setMoveRef(nullptr); + const auto statAfter = getStats(); + //fix statistics total to match "copy + delete" + acb_.updateDataTotal(statAfter.first - statBefore.first, statAfter.second - statBefore.second); //noexcept } - else assert(false); - break; + } + else assert(false); + break; - case SO_MOVE_LEFT_TO: //it's enough to try each move-pair *once* - case SO_MOVE_RIGHT_TO: // - case SO_DELETE_LEFT: - case SO_DELETE_RIGHT: - case SO_OVERWRITE_LEFT: - case SO_OVERWRITE_RIGHT: - case SO_CREATE_NEW_LEFT: - case SO_CREATE_NEW_RIGHT: - case SO_DO_NOTHING: - case SO_EQUAL: - case SO_UNRESOLVED_CONFLICT: - case SO_COPY_METADATA_TO_LEFT: - case SO_COPY_METADATA_TO_RIGHT: - break; - } + case SO_MOVE_LEFT_TO: //it's enough to try each move-pair *once* + case SO_MOVE_RIGHT_TO: // + case SO_DELETE_LEFT: + case SO_DELETE_RIGHT: + case SO_OVERWRITE_LEFT: + case SO_OVERWRITE_RIGHT: + case SO_CREATE_NEW_LEFT: + case SO_CREATE_NEW_RIGHT: + case SO_DO_NOTHING: + case SO_EQUAL: + case SO_UNRESOLVED_CONFLICT: + case SO_COPY_METADATA_TO_LEFT: + case SO_COPY_METADATA_TO_RIGHT: + break; } - - for (FolderPair& folder : hierObj.refSubFolders()) - runZeroPass(folder); //recurse } //--------------------------------------------------------------------------------------------------------------- @@ -1003,7 +1647,7 @@ void SynchronizeFolderPair::runZeroPass(ContainerObject& hierObj) // - support change in type: overwrite file by directory, symlink by file, ect. inline -SynchronizeFolderPair::PassNo SynchronizeFolderPair::getPass(const FilePair& file) +FolderPairSyncer::PassNo FolderPairSyncer::getPass(const FilePair& file) { switch (file.getSyncOperation()) //evaluate comparison result and sync direction { @@ -1036,12 +1680,12 @@ SynchronizeFolderPair::PassNo SynchronizeFolderPair::getPass(const FilePair& fil return PASS_NEVER; } assert(false); - return PASS_TWO; //dummy + return PASS_NEVER; //dummy } inline -SynchronizeFolderPair::PassNo SynchronizeFolderPair::getPass(const SymlinkPair& link) +FolderPairSyncer::PassNo FolderPairSyncer::getPass(const SymlinkPair& link) { switch (link.getSyncOperation()) //evaluate comparison result and sync direction { @@ -1068,12 +1712,12 @@ SynchronizeFolderPair::PassNo SynchronizeFolderPair::getPass(const SymlinkPair& return PASS_NEVER; } assert(false); - return PASS_TWO; //dummy + return PASS_NEVER; //dummy } inline -SynchronizeFolderPair::PassNo SynchronizeFolderPair::getPass(const FolderPair& folder) +FolderPairSyncer::PassNo FolderPairSyncer::getPass(const FolderPair& folder) { switch (folder.getSyncOperation()) //evaluate comparison result and sync direction { @@ -1100,37 +1744,13 @@ SynchronizeFolderPair::PassNo SynchronizeFolderPair::getPass(const FolderPair& f return PASS_NEVER; } assert(false); - return PASS_TWO; //dummy -} - - -template -void SynchronizeFolderPair::runPass(ContainerObject& hierObj) //throw X -{ - //synchronize files: - for (FilePair& file : hierObj.refSubFiles()) - if (pass == this->getPass(file)) //"this->" required by two-pass lookup as enforced by GCC 4.7 - tryReportingError([&] { synchronizeFile(file); }, procCallback_); //throw X - - //synchronize symbolic links: - for (SymlinkPair& symlink : hierObj.refSubLinks()) - if (pass == this->getPass(symlink)) - tryReportingError([&] { synchronizeLink(symlink); }, procCallback_); //throw X - - //synchronize folders: - for (FolderPair& folder : hierObj.refSubFolders()) - { - if (pass == this->getPass(folder)) - tryReportingError([&] { synchronizeFolder(folder); }, procCallback_); //throw X - - this->runPass(folder); //recurse - } + return PASS_NEVER; //dummy } //--------------------------------------------------------------------------------------------------------------- inline -void SynchronizeFolderPair::synchronizeFile(FilePair& file) +void FolderPairSyncer::synchronizeFile(FilePair& file) //throw FileError, ThreadInterruption { const SyncOperation syncOp = file.getSyncOperation(); @@ -1145,9 +1765,10 @@ void SynchronizeFolderPair::synchronizeFile(FilePair& file) template -void SynchronizeFolderPair::synchronizeFileInt(FilePair& file, SyncOperation syncOp) +void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) //throw FileError, ThreadInterruption { static const SelectedSide sideSrc = OtherSide::result; + DeletionHandling& delHandlingTrg = SelectParam::ref(delHandlingLeft_, delHandlingRight_); switch (syncOp) { @@ -1160,17 +1781,15 @@ void SynchronizeFolderPair::synchronizeFileInt(FilePair& file, SyncOperation syn //can't use "getAbstractPath()" as file name is not available! const AbstractPath targetPath = file.getAbstractPath(); - reportInfo(txtCreatingFile, AFS::getDisplayPath(targetPath)); + reportInfo(txtCreatingFile_, AFS::getDisplayPath(targetPath)); - StatisticsReporter statReporter(1, file.getFileSize(), procCallback_); + AsyncItemStatReporter statReporter(1, file.getFileSize(), threadIdx_, acb_); try { - auto notifyUnbufferedIO = [&](int64_t bytesDelta) { statReporter.reportDelta(0, bytesDelta); }; - const AFS::FileCopyResult result = copyFileWithCallback({ file.getAbstractPath(), file.getAttributes() }, targetPath, nullptr, //onDeleteTargetFile: nothing to delete; if existing: undefined behavior! (fail/overwrite/auto-rename) - notifyUnbufferedIO); //throw FileError + statReporter); //throw FileError if (result.errorModTime) errorsModTime_.push_back(*result.errorModTime); //show all warnings later as a single message @@ -1187,29 +1806,32 @@ void SynchronizeFolderPair::synchronizeFileInt(FilePair& file, SyncOperation syn catch (FileError&) { bool sourceWasDeleted = false; - try { sourceWasDeleted = !AFS::getItemTypeIfExists(file.getAbstractPath()); /*throw FileError*/ } + try { sourceWasDeleted = !parallel::getItemTypeIfExists(file.getAbstractPath(), singleThread_); /*throw FileError*/ } catch (FileError&) {} //previous exception is more relevant + //do not check on type (symlink, file, folder) -> if there is a type change, FFS should not be quiet about it! if (sourceWasDeleted) + { + //even if the source item does not exist anymore, significant I/O work was done => report + statReporter.reportDelta(1, 0); + reportInfo(txtSourceItemNotFound_, AFS::getDisplayPath(file.getAbstractPath())); + file.removeObject(); //source deleted meanwhile...nothing was done (logical point of view!) + } else - throw; //do not check on type (symlink, file, folder) -> if there is a type change, FFS should not be quiet about it! + throw; } } break; case SO_DELETE_LEFT: case SO_DELETE_RIGHT: - reportInfo(getDelHandling().getTxtRemovingFile(), AFS::getDisplayPath(file.getAbstractPath())); + reportInfo(delHandlingTrg.getTxtRemovingFile(), AFS::getDisplayPath(file.getAbstractPath())); { - StatisticsReporter statReporter(1, 0, procCallback_); - - auto onNotifyItemDeletion = [&] { statReporter.reportDelta(1, 0); }; - auto notifyUnbufferedIO = [&](int64_t bytesDelta) { statReporter.reportDelta(0, bytesDelta); }; - - getDelHandling().removeFileWithCallback({ file.getAbstractPath(), file.getAttributes() }, - file.getPairRelativePath(), onNotifyItemDeletion, notifyUnbufferedIO); //throw FileError + AsyncItemStatReporter statReporter(1, 0, threadIdx_, acb_); + delHandlingTrg.removeFileWithCallback({ file.getAbstractPath(), file.getAttributes() }, + file.getPairRelativePath(), statReporter, singleThread_); //throw FileError, X file.removeObject(); //update FilePair } break; @@ -1226,13 +1848,13 @@ void SynchronizeFolderPair::synchronizeFileInt(FilePair& file, SyncOperation syn const AbstractPath pathFrom = moveFrom->getAbstractPath(); const AbstractPath pathTo = moveTo ->getAbstractPath(); - reportInfo(txtMovingFile, AFS::getDisplayPath(pathFrom), AFS::getDisplayPath(pathTo)); + reportInfo(txtMovingFileXtoY_, AFS::getDisplayPath(pathFrom), AFS::getDisplayPath(pathTo)); - StatisticsReporter statReporter(1, 0, procCallback_); + AsyncItemStatReporter statReporter(1, 0, threadIdx_, acb_); //TODO: synchronizeFileInt: consider ErrorDifferentVolume! e.g. symlink aliasing! - AFS::renameItem(pathFrom, pathTo); //throw FileError, (ErrorDifferentVolume) + parallel::renameItem(pathFrom, pathTo, singleThread_); //throw FileError, (ErrorDifferentVolume) statReporter.reportDelta(1, 0); @@ -1259,40 +1881,38 @@ void SynchronizeFolderPair::synchronizeFileInt(FilePair& file, SyncOperation syn AbstractPath targetPathResolvedOld = file.getAbstractPath(); //support change in case when syncing to case-sensitive SFTP on Windows! AbstractPath targetPathResolvedNew = targetPathLogical; if (file.isFollowedSymlink()) //follow link when updating file rather than delete it and replace with regular file!!! - targetPathResolvedOld = targetPathResolvedNew = AFS::getSymlinkResolvedPath(file.getAbstractPath()); //throw FileError + targetPathResolvedOld = targetPathResolvedNew = parallel::getSymlinkResolvedPath(file.getAbstractPath(), singleThread_); //throw FileError - reportInfo(txtOverwritingFile, AFS::getDisplayPath(targetPathResolvedOld)); + reportInfo(txtUpdatingFile_, AFS::getDisplayPath(targetPathResolvedOld)); - StatisticsReporter statReporter(1, file.getFileSize(), procCallback_); + AsyncItemStatReporter statReporter(1, file.getFileSize(), threadIdx_, acb_); if (file.isFollowedSymlink()) //since we follow the link, we need to sync case sensitivity of the link manually! if (file.getItemName() != file.getItemName()) //have difference in case? - AFS::renameItem(file.getAbstractPath(), targetPathLogical); //throw FileError, (ErrorDifferentVolume) - - auto notifyUnbufferedIO = [&](int64_t bytesDelta) { statReporter.reportDelta(0, bytesDelta); }; + parallel::renameItem(file.getAbstractPath(), targetPathLogical, singleThread_); //throw FileError, (ErrorDifferentVolume) auto onDeleteTargetFile = [&] //delete target at appropriate time { - //reportStatus(this->getDelHandling().getTxtRemovingFile(), AFS::getDisplayPath(targetPathResolvedOld)); -> superfluous/confuses user + //reportStatus(this->delHandlingTrg.getTxtRemovingFile(), AFS::getDisplayPath(targetPathResolvedOld)); -> superfluous/confuses user FileAttributes followedTargetAttr = file.getAttributes(); followedTargetAttr.isFollowedSymlink = false; - this->getDelHandling().removeFileWithCallback({ targetPathResolvedOld, followedTargetAttr}, - file.getPairRelativePath(), [] {}, notifyUnbufferedIO); //throw FileError; - //no (logical) item count update desired - but total byte count may change, e.g. move(copy) deleted file to versioning dir + delHandlingTrg.removeFileWithCallback({ targetPathResolvedOld, followedTargetAttr }, file.getPairRelativePath(), statReporter, singleThread_); //throw FileError, X + //no (logical) item count update desired - but total byte count may change, e.g. move(copy) old file to versioning dir + statReporter.reportDelta(-1, 0); //undo item stats reporting within DeletionHandling::removeFileWithCallback() //file.removeObject(); -> doesn't make sense for isFollowedSymlink(); "file, sideTrg" evaluated below! //if fail-safe file copy is active, then the next operation will be a simple "rename" - //=> don't risk reportStatus() throwing AbortProcess() leaving the target deleted rather than updated! + //=> don't risk reportStatus() throwing ThreadInterruption() leaving the target deleted rather than updated! //=> if failSafeFileCopy_ : don't run callbacks that could throw }; const AFS::FileCopyResult result = copyFileWithCallback({ file.getAbstractPath(), file.getAttributes() }, targetPathResolvedNew, onDeleteTargetFile, - notifyUnbufferedIO); //throw FileError + statReporter); //throw FileError if (result.errorModTime) errorsModTime_.push_back(*result.errorModTime); //show all warnings later as a single message @@ -1312,21 +1932,21 @@ void SynchronizeFolderPair::synchronizeFileInt(FilePair& file, SyncOperation syn case SO_COPY_METADATA_TO_LEFT: case SO_COPY_METADATA_TO_RIGHT: //harmonize with file_hierarchy.cpp::getSyncOpDescription!! - reportInfo(txtWritingAttributes, AFS::getDisplayPath(file.getAbstractPath())); + reportInfo(txtUpdatingAttributes_, AFS::getDisplayPath(file.getAbstractPath())); { - StatisticsReporter statReporter(1, 0, procCallback_); + AsyncItemStatReporter statReporter(1, 0, threadIdx_, acb_); assert(file.getItemName() != file.getItemName()); if (file.getItemName() != file.getItemName()) //have difference in case? - AFS::renameItem(file.getAbstractPath(), //throw FileError, (ErrorDifferentVolume) - AFS::appendRelPath(file.parent().getAbstractPath(), file.getItemName())); + parallel::renameItem(file.getAbstractPath(), //throw FileError, (ErrorDifferentVolume) + AFS::appendRelPath(file.parent().getAbstractPath(), file.getItemName()), singleThread_); #if 0 //changing file time without copying content is not justified after CompareVariant::SIZE finds "equal" files! similar issue with CompareVariant::TIME_SIZE and FileTimeTolerance == -1 //Bonus: some devices don't support setting (precise) file times anyway, e.g. FAT or MTP! if (file.getLastWriteTime() != file.getLastWriteTime()) //- no need to call sameFileTime() or respect 2 second FAT/FAT32 precision in this comparison //- do NOT read *current* source file time, but use buffered value which corresponds to time of comparison! - AFS::setModTime(file.getAbstractPath(), file.getLastWriteTime()); //throw FileError + parallel::setModTime(file.getAbstractPath(), file.getLastWriteTime()); //throw FileError #endif statReporter.reportDelta(1, 0); @@ -1347,16 +1967,16 @@ void SynchronizeFolderPair::synchronizeFileInt(FilePair& file, SyncOperation syn case SO_DO_NOTHING: case SO_EQUAL: case SO_UNRESOLVED_CONFLICT: - assert(false); //should have been filtered out by SynchronizeFolderPair::getPass() + assert(false); //should have been filtered out by FolderPairSyncer::getPass() return; //no update on processed data! } - procCallback_.requestUiRefresh(); //throw ? + interruptionPoint(); //throw ThreadInterruption } inline -void SynchronizeFolderPair::synchronizeLink(SymlinkPair& link) +void FolderPairSyncer::synchronizeLink(SymlinkPair& link) //throw FileError, ThreadInterruption { const SyncOperation syncOp = link.getSyncOperation(); @@ -1371,9 +1991,11 @@ void SynchronizeFolderPair::synchronizeLink(SymlinkPair& link) template -void SynchronizeFolderPair::synchronizeLinkInt(SymlinkPair& symlink, SyncOperation syncOp) +void FolderPairSyncer::synchronizeLinkInt(SymlinkPair& symlink, SyncOperation syncOp) //throw FileError, ThreadInterruption { + warn_static("test constexpr compiler conformance") static const SelectedSide sideSrc = OtherSide::result; + DeletionHandling& delHandlingTrg = SelectParam::ref(delHandlingLeft_, delHandlingRight_); switch (syncOp) { @@ -1385,12 +2007,12 @@ void SynchronizeFolderPair::synchronizeLinkInt(SymlinkPair& symlink, SyncOperati return; //if parent directory creation failed, there's no reason to show more errors! const AbstractPath targetPath = symlink.getAbstractPath(); - reportInfo(txtCreatingLink, AFS::getDisplayPath(targetPath)); + reportInfo(txtCreatingLink_, AFS::getDisplayPath(targetPath)); - StatisticsReporter statReporter(1, 0, procCallback_); + AsyncItemStatReporter statReporter(1, 0, threadIdx_, acb_); try { - AFS::copySymlink(symlink.getAbstractPath(), targetPath, copyFilePermissions_); //throw FileError + parallel::copySymlink(symlink.getAbstractPath(), targetPath, copyFilePermissions_, singleThread_); //throw FileError statReporter.reportDelta(1, 0); @@ -1403,26 +2025,31 @@ void SynchronizeFolderPair::synchronizeLinkInt(SymlinkPair& symlink, SyncOperati catch (FileError&) { bool sourceWasDeleted = false; - try { sourceWasDeleted = !AFS::getItemTypeIfExists(symlink.getAbstractPath()); /*throw FileError*/ } + try { sourceWasDeleted = !parallel::getItemTypeIfExists(symlink.getAbstractPath(), singleThread_); /*throw FileError*/ } catch (FileError&) {} //previous exception is more relevant + //do not check on type (symlink, file, folder) -> if there is a type change, FFS should not be quiet about it! if (sourceWasDeleted) + { + //even if the source item does not exist anymore, significant I/O work was done => report + statReporter.reportDelta(1, 0); + reportInfo(txtSourceItemNotFound_, AFS::getDisplayPath(symlink.getAbstractPath())); + symlink.removeObject(); //source deleted meanwhile...nothing was done (logical point of view!) + } else - throw; //do not check on type (symlink, file, folder) -> if there is a type change, FFS should not be quiet about it! + throw; } } break; case SO_DELETE_LEFT: case SO_DELETE_RIGHT: - reportInfo(getDelHandling().getTxtRemovingSymLink(), AFS::getDisplayPath(symlink.getAbstractPath())); + reportInfo(delHandlingTrg.getTxtRemovingSymLink(), AFS::getDisplayPath(symlink.getAbstractPath())); { - StatisticsReporter statReporter(1, 0, procCallback_); + AsyncItemStatReporter statReporter(1, 0, threadIdx_, acb_); - auto onNotifyItemDeletion = [&] { statReporter.reportDelta(1, 0); }; - - getDelHandling().removeLinkWithCallback(symlink.getAbstractPath(), symlink.getPairRelativePath(), onNotifyItemDeletion); //throw FileError + delHandlingTrg.removeLinkWithCallback(symlink.getAbstractPath(), symlink.getPairRelativePath(), statReporter, singleThread_); //throw FileError, X symlink.removeObject(); //update SymlinkPair } @@ -1430,21 +2057,22 @@ void SynchronizeFolderPair::synchronizeLinkInt(SymlinkPair& symlink, SyncOperati case SO_OVERWRITE_LEFT: case SO_OVERWRITE_RIGHT: - reportInfo(txtOverwritingLink, AFS::getDisplayPath(symlink.getAbstractPath())); + reportInfo(txtUpdatingLink_, AFS::getDisplayPath(symlink.getAbstractPath())); { - StatisticsReporter statReporter(1, 0, procCallback_); + AsyncItemStatReporter statReporter(1, 0, threadIdx_, acb_); - //reportStatus(getDelHandling().getTxtRemovingSymLink(), AFS::getDisplayPath(symlink.getAbstractPath())); - getDelHandling().removeLinkWithCallback(symlink.getAbstractPath(), symlink.getPairRelativePath(), [] {}); //throw FileError + //reportStatus(delHandlingTrg.getTxtRemovingSymLink(), AFS::getDisplayPath(symlink.getAbstractPath())); + delHandlingTrg.removeLinkWithCallback(symlink.getAbstractPath(), symlink.getPairRelativePath(), statReporter, singleThread_); //throw FileError, X + statReporter.reportDelta(-1, 0); //undo item stats reporting within DeletionHandling::removeLinkWithCallback() //symlink.removeObject(); -> "symlink, sideTrg" evaluated below! - //=> don't risk reportStatus() throwing AbortProcess() leaving the target deleted rather than updated: - //reportStatus(txtOverwritingLink, AFS::getDisplayPath(symlink.getAbstractPath())); //restore status text + //=> don't risk reportStatus() throwing ThreadInterruption() leaving the target deleted rather than updated: + //reportStatus(txtUpdatingLink_, AFS::getDisplayPath(symlink.getAbstractPath())); //restore status text - AFS::copySymlink(symlink.getAbstractPath(), - AFS::appendRelPath(symlink.parent().getAbstractPath(), symlink.getItemName()), //respect differences in case of source object - copyFilePermissions_); //throw FileError + parallel::copySymlink(symlink.getAbstractPath(), + AFS::appendRelPath(symlink.parent().getAbstractPath(), symlink.getItemName()), //respect differences in case of source object + copyFilePermissions_, singleThread_); //throw FileError statReporter.reportDelta(1, 0); //we model "delete + copy" as ONE logical operation @@ -1457,18 +2085,18 @@ void SynchronizeFolderPair::synchronizeLinkInt(SymlinkPair& symlink, SyncOperati case SO_COPY_METADATA_TO_LEFT: case SO_COPY_METADATA_TO_RIGHT: - reportInfo(txtWritingAttributes, AFS::getDisplayPath(symlink.getAbstractPath())); + reportInfo(txtUpdatingAttributes_, AFS::getDisplayPath(symlink.getAbstractPath())); { - StatisticsReporter statReporter(1, 0, procCallback_); + AsyncItemStatReporter statReporter(1, 0, threadIdx_, acb_); if (symlink.getItemName() != symlink.getItemName()) //have difference in case? - AFS::renameItem(symlink.getAbstractPath(), //throw FileError, (ErrorDifferentVolume) - AFS::appendRelPath(symlink.parent().getAbstractPath(), symlink.getItemName())); + parallel::renameItem(symlink.getAbstractPath(), //throw FileError, (ErrorDifferentVolume) + AFS::appendRelPath(symlink.parent().getAbstractPath(), symlink.getItemName()), singleThread_); //if (symlink.getLastWriteTime() != symlink.getLastWriteTime()) // //- no need to call sameFileTime() or respect 2 second FAT/FAT32 precision in this comparison // //- do NOT read *current* source file time, but use buffered value which corresponds to time of comparison! - // AFS::setModTimeSymlink(symlink.getAbstractPath(), symlink.getLastWriteTime()); //throw FileError + // parallel::setModTimeSymlink(symlink.getAbstractPath(), symlink.getLastWriteTime()); //throw FileError statReporter.reportDelta(1, 0); @@ -1486,16 +2114,16 @@ void SynchronizeFolderPair::synchronizeLinkInt(SymlinkPair& symlink, SyncOperati case SO_DO_NOTHING: case SO_EQUAL: case SO_UNRESOLVED_CONFLICT: - assert(false); //should have been filtered out by SynchronizeFolderPair::getPass() + assert(false); //should have been filtered out by FolderPairSyncer::getPass() return; //no update on processed data! } - procCallback_.requestUiRefresh(); //throw ? + interruptionPoint(); //throw ThreadInterruption } inline -void SynchronizeFolderPair::synchronizeFolder(FolderPair& folder) +void FolderPairSyncer::synchronizeFolder(FolderPair& folder) //throw FileError, ThreadInterruption { const SyncOperation syncOp = folder.getSyncOperation(); @@ -1510,9 +2138,10 @@ void SynchronizeFolderPair::synchronizeFolder(FolderPair& folder) template -void SynchronizeFolderPair::synchronizeFolderInt(FolderPair& folder, SyncOperation syncOp) +void FolderPairSyncer::synchronizeFolderInt(FolderPair& folder, SyncOperation syncOp) //throw FileError, ThreadInterruption { static const SelectedSide sideSrc = OtherSide::result; + DeletionHandling& delHandlingTrg = SelectParam::ref(delHandlingLeft_, delHandlingRight_); switch (syncOp) { @@ -1524,21 +2153,22 @@ void SynchronizeFolderPair::synchronizeFolderInt(FolderPair& folder, SyncOperati return; //if parent directory creation failed, there's no reason to show more errors! const AbstractPath targetPath = folder.getAbstractPath(); - reportInfo(txtCreatingFolder, AFS::getDisplayPath(targetPath)); + reportInfo(txtCreatingFolder_, AFS::getDisplayPath(targetPath)); //shallow-"copying" a folder might not fail if source is missing, so we need to check this first: - if (AFS::getItemTypeIfExists(folder.getAbstractPath())) //throw FileError + if (parallel::getItemTypeIfExists(folder.getAbstractPath(), singleThread_)) //throw FileError { - StatisticsReporter statReporter(1, 0, procCallback_); + AsyncItemStatReporter statReporter(1, 0, threadIdx_, acb_); try { //target existing: undefined behavior! (fail/overwrite) - AFS::copyNewFolder(folder.getAbstractPath(), targetPath, copyFilePermissions_); //throw FileError + parallel::copyNewFolder(folder.getAbstractPath(), targetPath, copyFilePermissions_, singleThread_); //throw FileError } catch (FileError&) { bool folderAlreadyExists = false; - try { folderAlreadyExists = AFS::getItemType(targetPath) == AFS::ItemType::FOLDER; } /*throw FileError*/ catch (FileError&) {} //previous exception is more relevant + try { folderAlreadyExists = parallel::getItemType(targetPath, singleThread_) == AFS::ItemType::FOLDER; } /*throw FileError*/ catch (FileError&) {} + //previous exception is more relevant if (!folderAlreadyExists) throw; @@ -1551,10 +2181,14 @@ void SynchronizeFolderPair::synchronizeFolderInt(FolderPair& folder, SyncOperati false, //isSymlinkTrg folder.isFollowedSymlink()); } - else //source deleted meanwhile...nothing was done (logical point of view!) + else //source deleted meanwhile... { const SyncStatistics subStats(folder); - StatisticsReporter statReporter(1 + getCUD(subStats), subStats.getBytesToProcess(), procCallback_); + AsyncItemStatReporter statReporter(1 + getCUD(subStats), subStats.getBytesToProcess(), threadIdx_, acb_); + + //even if the source item does not exist anymore, significant I/O work was done => report + statReporter.reportDelta(1, 0); + reportInfo(txtSourceItemNotFound_, AFS::getDisplayPath(folder.getAbstractPath())); //remove only *after* evaluating folder!! folder.refSubFiles ().clear(); // @@ -1567,15 +2201,14 @@ void SynchronizeFolderPair::synchronizeFolderInt(FolderPair& folder, SyncOperati case SO_DELETE_LEFT: case SO_DELETE_RIGHT: - reportInfo(getDelHandling().getTxtRemovingFolder(), AFS::getDisplayPath(folder.getAbstractPath())); + reportInfo(delHandlingTrg.getTxtRemovingFolder(), AFS::getDisplayPath(folder.getAbstractPath())); { const SyncStatistics subStats(folder); //counts sub-objects only! - StatisticsReporter statReporter(1 + getCUD(subStats), subStats.getBytesToProcess(), procCallback_); + AsyncItemStatReporter statReporter(1 + getCUD(subStats), subStats.getBytesToProcess(), threadIdx_, acb_); - auto onNotifyItemDeletion = [&] { statReporter.reportDelta(1, 0); }; - auto notifyUnbufferedIO = [&](int64_t bytesDelta) { statReporter.reportDelta(0, bytesDelta); }; + delHandlingTrg.removeDirWithCallback(folder.getAbstractPath(), folder.getPairRelativePath(), statReporter, singleThread_); //throw FileError, X - getDelHandling().removeDirWithCallback(folder.getAbstractPath(), folder.getPairRelativePath(), onNotifyItemDeletion, notifyUnbufferedIO); //throw FileError + warn_static("perf => not parallel!") folder.refSubFiles ().clear(); // folder.refSubLinks ().clear(); //update FolderPair @@ -1588,14 +2221,14 @@ void SynchronizeFolderPair::synchronizeFolderInt(FolderPair& folder, SyncOperati case SO_OVERWRITE_RIGHT: // case SO_COPY_METADATA_TO_LEFT: case SO_COPY_METADATA_TO_RIGHT: - reportInfo(txtWritingAttributes, AFS::getDisplayPath(folder.getAbstractPath())); + reportInfo(txtUpdatingAttributes_, AFS::getDisplayPath(folder.getAbstractPath())); { - StatisticsReporter statReporter(1, 0, procCallback_); + AsyncItemStatReporter statReporter(1, 0, threadIdx_, acb_); assert(folder.getItemName() != folder.getItemName()); if (folder.getItemName() != folder.getItemName()) //have difference in case? - AFS::renameItem(folder.getAbstractPath(), //throw FileError, (ErrorDifferentVolume) - AFS::appendRelPath(folder.parent().getAbstractPath(), folder.getItemName())); + parallel::renameItem(folder.getAbstractPath(), //throw FileError, (ErrorDifferentVolume) + AFS::appendRelPath(folder.parent().getAbstractPath(), folder.getItemName()), singleThread_); //copyFileTimes -> useless: modification time changes with each child-object creation/deletion statReporter.reportDelta(1, 0); @@ -1614,70 +2247,54 @@ void SynchronizeFolderPair::synchronizeFolderInt(FolderPair& folder, SyncOperati case SO_DO_NOTHING: case SO_EQUAL: case SO_UNRESOLVED_CONFLICT: - assert(false); //should have been filtered out by SynchronizeFolderPair::getPass() + assert(false); //should have been filtered out by FolderPairSyncer::getPass() return; //no update on processed data! } - procCallback_.requestUiRefresh(); //throw ? + interruptionPoint(); //throw ThreadInterruption } //########################################################################################### -//--------------------- data verification ------------------------- -void verifyFiles(const AbstractPath& sourcePath, const AbstractPath& targetPath, const IOCallback& notifyUnbufferedIO) //throw FileError -{ - try - { - //do like "copy /v": 1. flush target file buffers, 2. read again as usual (using OS buffers) - // => it seems OS buffered are not invalidated by this: snake oil??? - if (Opt nativeTargetPath = AFS::getNativeItemPath(targetPath)) - { - const int fileHandle = ::open(nativeTargetPath->c_str(), O_WRONLY | O_APPEND); - if (fileHandle == -1) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot open file %x."), L"%x", fmtPath(*nativeTargetPath)), L"open"); - ZEN_ON_SCOPE_EXIT(::close(fileHandle)); - - if (::fsync(fileHandle) != 0) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(*nativeTargetPath)), L"fsync"); - } //close file handles! - - if (!filesHaveSameContent(sourcePath, targetPath, notifyUnbufferedIO)) //throw FileError - throw FileError(replaceCpy(replaceCpy(_("%x and %y have different content."), - L"%x", L"\n" + fmtPath(AFS::getDisplayPath(sourcePath))), - L"%y", L"\n" + fmtPath(AFS::getDisplayPath(targetPath)))); - } - catch (const FileError& e) //add some context to error message - { - throw FileError(_("Data verification error:"), e.toString()); - } -} - - -AFS::FileCopyResult SynchronizeFolderPair::copyFileWithCallback(const FileDescriptor& sourceDescr, //throw FileError - const AbstractPath& targetPath, - const std::function& onDeleteTargetFile, - const IOCallback& notifyUnbufferedIO) const //returns current attributes of source file +//returns current attributes of source file +AFS::FileCopyResult FolderPairSyncer::copyFileWithCallback(const FileDescriptor& sourceDescr, //throw FileError + const AbstractPath& targetPath, + const std::function& onDeleteTargetFile, //optional! + AsyncItemStatReporter& statReporter) { const AbstractPath& sourcePath = sourceDescr.path; const AFS::StreamAttributes sourceAttr{ sourceDescr.attr.modTime, sourceDescr.attr.fileSize, sourceDescr.attr.fileId }; - auto copyOperation = [this, &sourceAttr, &targetPath, &onDeleteTargetFile, ¬ifyUnbufferedIO](const AbstractPath& sourcePathTmp) + auto copyOperation = [this, &sourceAttr, &targetPath, &onDeleteTargetFile, &statReporter](const AbstractPath& sourcePathTmp) { //target existing after onDeleteTargetFile(): undefined behavior! (fail/overwrite/auto-rename) - const AFS::FileCopyResult result = AFS::copyFileTransactional(sourcePathTmp, sourceAttr, //throw FileError, ErrorFileLocked - targetPath, - copyFilePermissions_, - failSafeFileCopy_, - onDeleteTargetFile, - notifyUnbufferedIO); + const AFS::FileCopyResult result = parallel::copyFileTransactional(sourcePathTmp, sourceAttr, //throw FileError, ErrorFileLocked + targetPath, + copyFilePermissions_, + failSafeFileCopy_, + [&] + { + if (onDeleteTargetFile) //running *outside* singleThread_ lock! => onDeleteTargetFile-callback expects lock being held: + { + std::lock_guard dummy(singleThread_); + onDeleteTargetFile(); + } + }, + [&](int64_t bytesDelta) { statReporter.reportDelta(0, bytesDelta); }, //callback runs *outside* singleThread_ lock! => fine + singleThread_); + //#################### Verification ############################# if (verifyCopiedFiles_) { - ZEN_ON_SCOPE_FAIL(try { AFS::removeFilePlain(targetPath); } + ZEN_ON_SCOPE_FAIL(try { parallel::removeFilePlain(targetPath, singleThread_); } catch (FileError&) {}); //delete target if verification fails - procCallback_.reportInfo(replaceCpy(txtVerifying, L"%x", fmtPath(AFS::getDisplayPath(targetPath)))); - verifyFiles(sourcePathTmp, targetPath, [&](int64_t bytesDelta) { procCallback_.requestUiRefresh(); }); //throw FileError + reportInfo(txtVerifyingFile_, AFS::getDisplayPath(targetPath)); + + //callback runs *outside* singleThread_ lock! => fine + auto verifyCallback = [&](int64_t bytesDelta) { interruptionPoint(); /*throw ThreadInterruption*/ }; + + parallel::verifyFiles(sourcePathTmp, targetPath, verifyCallback, singleThread_); //throw FileError } //#################### /Verification ############################# @@ -1697,7 +2314,8 @@ bool baseFolderDrop(BaseFolderPair& baseFolder, int folderAccessTimeout, Process if (baseFolder.isAvailable()) if (Opt errMsg = tryReportingError([&] { - const FolderStatus status = getFolderStatusNonBlocking({ folderPath }, folderAccessTimeout, false /*allowUserInteraction*/, callback); + const FolderStatus status = getFolderStatusNonBlocking({ folderPath }, {} /*deviceParallelOps*/, + folderAccessTimeout, false /*allowUserInteraction*/, callback); static_assert(IsSameTypesecond), FileError>::value, ""); if (!status.failedChecks.empty()) @@ -1706,7 +2324,7 @@ bool baseFolderDrop(BaseFolderPair& baseFolder, int folderAccessTimeout, Process if (status.existing.find(folderPath) == status.existing.end()) throw FileError(replaceCpy(_("Cannot find folder %x."), L"%x", fmtPath(AFS::getDisplayPath(folderPath)))); //should really be logged as a "fatal error" if ignored by the user... - }, callback)) //throw X? + }, callback)) //throw X return true; return false; @@ -1714,7 +2332,7 @@ bool baseFolderDrop(BaseFolderPair& baseFolder, int folderAccessTimeout, Process template //create base directories first (if not yet existing) -> no symlink or attribute copying! -bool createBaseFolder(BaseFolderPair& baseFolder, int folderAccessTimeout, ProcessCallback& callback) //nothrow; return false if fatal error occurred +bool createBaseFolder(BaseFolderPair& baseFolder, int folderAccessTimeout, ProcessCallback& callback) //return false if fatal error occurred { const AbstractPath baseFolderPath = baseFolder.getAbstractPath(); @@ -1726,7 +2344,8 @@ bool createBaseFolder(BaseFolderPair& baseFolder, int folderAccessTimeout, Proce bool temporaryNetworkDrop = false; zen::Opt errMsg = tryReportingError([&] { - const FolderStatus status = getFolderStatusNonBlocking({ baseFolderPath }, folderAccessTimeout, false /*allowUserInteraction*/, callback); + const FolderStatus status = getFolderStatusNonBlocking({ baseFolderPath }, {} /*deviceParallelOps*/, + folderAccessTimeout, false /*allowUserInteraction*/, callback); static_assert(IsSameTypesecond), FileError>::value, ""); if (!status.failedChecks.empty()) @@ -1749,7 +2368,7 @@ bool createBaseFolder(BaseFolderPair& baseFolder, int folderAccessTimeout, Proce // 2. deletion handling: versioning -> " // 3. log file creates containing folder -> no, log only created in batch mode, and only *before* comparison } - }, callback); //throw X? + }, callback); //throw X return !errMsg && !temporaryNetworkDrop; } return true; @@ -1774,6 +2393,7 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime int folderAccessTimeout, const std::vector& syncConfig, FolderComparison& folderCmp, + const std::map& deviceParallelOps, WarningDialogs& warnings, ProcessCallback& callback) { @@ -1841,9 +2461,9 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime std::vector>> diskSpaceMissing; //base folder / space required / space available //status of base directories which are set to DeletionPolicy::RECYCLER (and contain actual items to be deleted) - std::map recyclerSupported; //expensive to determine on Win XP => buffer + check recycle bin existence only once per base folder! + std::map recyclerSupported; //expensive to determine on Win XP => buffer + check recycle bin existence only once per base folder! - std::set verCheckVersioningPaths; + std::set verCheckVersioningPaths; std::vector> verCheckBaseFolderPaths; //hard filter creates new logical hierarchies for otherwise equal AbstractPath... //start checking folder pairs @@ -1882,8 +2502,8 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime folderPairStat.deleteCount() > 0; //check for empty target folder paths: this only makes sense if empty field is source (and no DB files need to be created) - if ((AFS::isNullPath(baseFolder.getAbstractPath< LEFT_SIDE>()) && (writeLeft || folderPairCfg.saveSyncDB_)) || - (AFS::isNullPath(baseFolder.getAbstractPath()) && (writeRight || folderPairCfg.saveSyncDB_))) + if ((AFS::isNullPath(baseFolder.getAbstractPath< LEFT_SIDE>()) && (writeLeft || folderPairCfg.saveSyncDB)) || + (AFS::isNullPath(baseFolder.getAbstractPath()) && (writeRight || folderPairCfg.saveSyncDB))) { callback.reportFatalError(_("Target folder input field must not be empty.")); jobType[folderIndex] = FolderPairJobType::SKIP; @@ -1986,7 +2606,7 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime tryReportingError([&] { recSupported = AFS::supportsRecycleBin(baseFolderPath, [&]{ callback.requestUiRefresh(); /*may throw*/ }); //throw FileError - }, callback); //throw X? + }, callback); //throw X recyclerSupported.emplace(baseFolderPath, recSupported); } @@ -2052,7 +2672,7 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime //check if folders are used by multiple pairs in read/write access { - std::set dependentFolders; + std::set dependentFolders; //race condition := multiple accesses of which at least one is a write for (auto it = readWriteCheckBaseFolders.begin(); it != readWriteCheckBaseFolders.end(); ++it) @@ -2083,7 +2703,7 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime std::wstring msg; for (const AbstractPath& versioningFolderPath : verCheckVersioningPaths) { - std::map uniqueMsgs; //=> at most one msg per base folder (*and* per versioningFolderPath) + std::map uniqueMsgs; //=> at most one msg per base folder (*and* per versioningFolderPath) for (const auto& item : verCheckBaseFolderPaths) //may contain duplicate paths, but with *different* hard filter! if (Opt pd = getPathDependency(versioningFolderPath, NullFilter(), item.first, *item.second)) @@ -2125,7 +2745,7 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime continue; //------------------------------------------------------------------------------------------ - callback.reportInfo(_("Synchronizing folder pair:") + L" " + getVariantNameForLog(folderPairCfg.syncVariant_) + L"\n" + + callback.reportInfo(_("Synchronizing folder pair:") + L" " + getVariantNameForLog(folderPairCfg.syncVariant) + L"\n" + L" " + AFS::getDisplayPath(baseFolder.getAbstractPath< LEFT_SIDE>()) + L"\n" + L" " + AFS::getDisplayPath(baseFolder.getAbstractPath())); //------------------------------------------------------------------------------------------ @@ -2136,7 +2756,7 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime continue; //create base folders if not yet existing - if (folderPairStat.createCount() > 0 || folderPairCfg.saveSyncDB_) //else: temporary network drop leading to deletions already caught by "sourceFolderMissing" check! + if (folderPairStat.createCount() > 0 || folderPairCfg.saveSyncDB) //else: temporary network drop leading to deletions already caught by "sourceFolderMissing" check! if (!createBaseFolder< LEFT_SIDE>(baseFolder, folderAccessTimeout, callback) || //+ detect temporary network drop!! !createBaseFolder(baseFolder, folderAccessTimeout, callback)) // continue; @@ -2149,7 +2769,7 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime { try { - if (folderPairCfg.saveSyncDB_) + if (folderPairCfg.saveSyncDB) saveLastSynchronousState(baseFolder, //throw FileError [&](const std::wstring& statusMsg) { try { callback.reportStatus(statusMsg); /*throw X*/} catch (...) {}}); } @@ -2169,7 +2789,7 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime !AFS::isNullPath(baseFolder.getAbstractPath()) && // AFS::supportPermissionCopy(baseFolder.getAbstractPath(), baseFolder.getAbstractPath()); //throw FileError - }, callback); //throw X? + }, callback); //throw X auto getEffectiveDeletionPolicy = [&](const AbstractPath& baseFolderPath) -> DeletionPolicy @@ -2187,30 +2807,62 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime DeletionHandling delHandlerL(baseFolder.getAbstractPath(), getEffectiveDeletionPolicy(baseFolder.getAbstractPath()), folderPairCfg.versioningFolderPhrase, - folderPairCfg.versioningStyle_, - timeStamp, - callback); + folderPairCfg.versioningStyle, + timeStamp); DeletionHandling delHandlerR(baseFolder.getAbstractPath(), getEffectiveDeletionPolicy(baseFolder.getAbstractPath()), folderPairCfg.versioningFolderPhrase, - folderPairCfg.versioningStyle_, - timeStamp, - callback); + folderPairCfg.versioningStyle, + timeStamp); + + //always (try to) clean up, even if synchronization is aborted! + ZEN_ON_SCOPE_EXIT( + //may block heavily, but still do not allow user callback: + //-> avoid throwing user cancel exception again, leading to incomplete clean-up! + try + { + delHandlerL.tryCleanup(callback, false /*allowCallbackException*/); //throw FileError, (throw X) + } + catch (FileError&) {} + catch (...) { assert(false); } //what is this? + try + { + delHandlerR.tryCleanup(callback, false /*allowCallbackException*/); //throw FileError, (throw X) + } + catch (FileError&) {} + catch (...) { assert(false); } //what is this? + ); + + auto getParallelOps = [&](const AbstractPath& ap) + { + auto itParOps = deviceParallelOps.find(AFS::getPathComponents(ap).rootPath); + return std::max(itParOps != deviceParallelOps.end() ? itParOps->second : 1, 1); //sanitize early for correct status display + }; + const size_t parallelOps = std::max(getParallelOps(baseFolder.getAbstractPath()), + getParallelOps(baseFolder.getAbstractPath())); + //harmonize with sync_cfg.cpp: parallelOps used for versioning shown == number used for folder pair! + warn_static("TODO: warn if parallelOps is > than what versioningFolderPhrase can handle (S)FTP!") + //const AbstractPath versioningFolderPath = createAbstractPath(folderPairCfg.versioningFolderPhrase) + //getParallelOps(versioningFolderPath) - SynchronizeFolderPair syncFP(callback, verifyCopiedFiles, copyPermissionsFp, failSafeFileCopy, - errorsModTime, - delHandlerL, delHandlerR); - syncFP.startSync(baseFolder); + FolderPairSyncer::SyncCtx syncCtx = + { + verifyCopiedFiles, copyPermissionsFp, failSafeFileCopy, + errorsModTime, + delHandlerL, delHandlerR, + parallelOps + }; + FolderPairSyncer::runSync(syncCtx, baseFolder, callback); //(try to gracefully) cleanup temporary Recycle bin folders and versioning -> will be done in ~DeletionHandling anyway... - tryReportingError([&] { delHandlerL.tryCleanup(true /*allowCallbackException*/); /*throw FileError*/}, callback); //throw X? - tryReportingError([&] { delHandlerR.tryCleanup(true ); /*throw FileError*/}, callback); //throw X? + tryReportingError([&] { delHandlerL.tryCleanup(callback, true /*allowCallbackException*/); /*throw FileError*/}, callback); //throw X + tryReportingError([&] { delHandlerR.tryCleanup(callback, true ); /*throw FileError*/}, callback); //throw X } //(try to gracefully) write database file - if (folderPairCfg.saveSyncDB_) + if (folderPairCfg.saveSyncDB) { callback.reportStatus(_("Generating database...")); callback.forceUiRefresh(); //throw X diff --git a/FreeFileSync/Source/synchronization.h b/FreeFileSync/Source/synchronization.h index 076f0350..6e09709c 100755 --- a/FreeFileSync/Source/synchronization.h +++ b/FreeFileSync/Source/synchronization.h @@ -73,22 +73,11 @@ private: struct FolderPairSyncCfg { - FolderPairSyncCfg(bool saveSyncDB, - const DeletionPolicy handleDel, - VersioningStyle versioningStyle, - const Zstring& versioningPhrase, - DirectionConfig::Variant syncVariant) : - saveSyncDB_(saveSyncDB), - handleDeletion(handleDel), - versioningStyle_(versioningStyle), - versioningFolderPhrase(versioningPhrase), - syncVariant_(syncVariant) {} - - bool saveSyncDB_; //save database if in automatic mode or dection of moved files is active + bool saveSyncDB; //save database if in automatic mode or dection of moved files is active DeletionPolicy handleDeletion; - VersioningStyle versioningStyle_; + VersioningStyle versioningStyle; Zstring versioningFolderPhrase; //unresolved directory names as entered by user! - DirectionConfig::Variant syncVariant_; + DirectionConfig::Variant syncVariant; }; std::vector extractSyncCfg(const MainConfiguration& mainCfg); @@ -103,6 +92,7 @@ void synchronize(const std::chrono::system_clock::time_point& syncStartTime, int folderAccessTimeout, const std::vector& syncConfig, //CONTRACT: syncConfig and folderCmp correspond row-wise! FolderComparison& folderCmp, // + const std::map& deviceParallelOps, WarningDialogs& warnings, ProcessCallback& callback); } diff --git a/FreeFileSync/Source/ui/batch_config.cpp b/FreeFileSync/Source/ui/batch_config.cpp index fe647828..54643a5c 100755 --- a/FreeFileSync/Source/ui/batch_config.cpp +++ b/FreeFileSync/Source/ui/batch_config.cpp @@ -82,7 +82,10 @@ BatchDialog::BatchDialog(wxWindow* parent, BatchDialogConfig& dlgCfg) : m_bitmapBatchJob->SetBitmap(getResourceImage(L"file_batch")); - logfileDir_ = std::make_unique(*m_panelLogfile, *m_buttonSelectLogFolder, *m_bpButtonSelectAltLogFolder, *m_logFolderPath, nullptr /*staticText*/, nullptr /*wxWindow*/); + logfileDir_ = std::make_unique(*m_panelLogfile, *m_buttonSelectLogFolder, *m_bpButtonSelectAltLogFolder, *m_logFolderPath, nullptr /*staticText*/, nullptr /*wxWindow*/, + nullptr /*droppedPathsFilter*/, + [](const Zstring& folderPathPhrase) { return 1; } /*getDeviceParallelOps*/, + nullptr /*setDeviceParallelOps*/); logfileDir_->setBackgroundText(utfTo(getDefaultLogFolderPath())); diff --git a/FreeFileSync/Source/ui/batch_status_handler.cpp b/FreeFileSync/Source/ui/batch_status_handler.cpp index 6d49ac08..c5541cad 100755 --- a/FreeFileSync/Source/ui/batch_status_handler.cpp +++ b/FreeFileSync/Source/ui/batch_status_handler.cpp @@ -68,77 +68,6 @@ std::unique_ptr prepareNewLogfile(const AbstractPath& logFold return AFS::getOutputStream(logFilePath, nullptr, /*streamSize*/ notifyUnbufferedIO); //throw FileError } - - -struct LogTraverserCallback: public AFS::TraverserCallback -{ - LogTraverserCallback(const Zstring& prefix, const std::function& onUpdateStatus) : - prefix_(prefix), - onUpdateStatus_(onUpdateStatus) {} - - void onFile(const FileInfo& fi) override - { - if (startsWith(fi.itemName, prefix_, CmpFilePath() /*even on Linux!*/) && endsWith(fi.itemName, Zstr(".log"), CmpFilePath())) - logFileNames_.push_back(fi.itemName); - - if (onUpdateStatus_) onUpdateStatus_(); - } - std::unique_ptr onFolder (const FolderInfo& fi) override { return nullptr; } - HandleLink onSymlink(const SymlinkInfo& si) override { return TraverserCallback::LINK_SKIP; } - - HandleError reportDirError (const std::wstring& msg, size_t retryNumber ) override { setError(msg); return ON_ERROR_CONTINUE; } - HandleError reportItemError(const std::wstring& msg, size_t retryNumber, const Zstring& itemName) override { setError(msg); return ON_ERROR_CONTINUE; } - - const std::vector& refFileNames() const { return logFileNames_; } - const Opt& getLastError() const { return lastError_; } - -private: - void setError(const std::wstring& msg) //implement late failure - { - if (!lastError_) - lastError_ = FileError(msg); - } - - const Zstring prefix_; - const std::function onUpdateStatus_; - std::vector logFileNames_; //out - Opt lastError_; -}; - - -void limitLogfileCount(const AbstractPath& logFolderPath, const std::wstring& jobname, size_t maxCount, //throw FileError - const std::function& notifyStatus) -{ - const Zstring prefix = utfTo(jobname); - const std::wstring cleaningMsg = _("Cleaning up old log files...");; - - LogTraverserCallback lt(prefix, [&] { if (notifyStatus) notifyStatus(cleaningMsg); }); //traverse source directory one level deep - AFS::traverseFolder(logFolderPath, lt); - - std::vector logFileNames = lt.refFileNames(); - Opt lastError = lt.getLastError(); - - if (logFileNames.size() > maxCount) - { - //delete oldest logfiles: take advantage of logfile naming convention to find them - std::nth_element(logFileNames.begin(), logFileNames.end() - maxCount, logFileNames.end(), LessFilePath()); - - std::for_each(logFileNames.begin(), logFileNames.end() - maxCount, [&](const Zstring& logFileName) - { - const AbstractPath filePath = AFS::appendRelPath(logFolderPath, logFileName); - if (notifyStatus) notifyStatus(cleaningMsg + L" " + fmtPath(AFS::getDisplayPath(filePath))); - - try - { - AFS::removeFilePlain(filePath); //throw FileError - } - catch (const FileError& e) { if (!lastError) lastError = e; }; - }); - } - - if (lastError) //late failure! - throw* lastError; -} } //############################################################################################################################## @@ -268,7 +197,7 @@ BatchStatusHandler::~BatchStatusHandler() errorLog_.logMsg(replaceCpy(_("Executing command %x"), L"%x", fmtPath(commandLine)), MSG_TYPE_INFO); //----------------- write results into user-specified logfile ------------------------ - const SummaryInfo summary = + const LogSummary summary = { jobName_, finalStatusMsg, @@ -413,9 +342,9 @@ void BatchStatusHandler::initNewPhase(int itemsTotal, int64_t bytesTotal, Proces } -void BatchStatusHandler::updateProcessedData(int itemsDelta, int64_t bytesDelta) +void BatchStatusHandler::updateDataProcessed(int itemsDelta, int64_t bytesDelta) { - StatusHandler::updateProcessedData(itemsDelta, bytesDelta); + StatusHandler::updateDataProcessed(itemsDelta, bytesDelta); if (progressDlg_) progressDlg_->notifyProgressChange(); //noexcept @@ -423,10 +352,9 @@ void BatchStatusHandler::updateProcessedData(int itemsDelta, int64_t bytesDelta) } -void BatchStatusHandler::reportInfo(const std::wstring& text) +void BatchStatusHandler::logInfo(const std::wstring& msg) { - errorLog_.logMsg(text, MSG_TYPE_INFO); //log first! - StatusHandler::reportInfo(text); //throw X + errorLog_.logMsg(msg, MSG_TYPE_INFO); } diff --git a/FreeFileSync/Source/ui/batch_status_handler.h b/FreeFileSync/Source/ui/batch_status_handler.h index 55ceccb6..f3f3cc61 100755 --- a/FreeFileSync/Source/ui/batch_status_handler.h +++ b/FreeFileSync/Source/ui/batch_status_handler.h @@ -43,8 +43,8 @@ public: ~BatchStatusHandler(); void initNewPhase (int itemsTotal, int64_t bytesTotal, Phase phaseID) override; - void updateProcessedData(int itemsDelta, int64_t bytesDelta) override; - void reportInfo (const std::wstring& text) override; + void updateDataProcessed(int itemsDelta, int64_t bytesDelta) override; + void logInfo (const std::wstring& msg) override; void forceUiRefreshNoThrow() override; void reportWarning (const std::wstring& warningMessage, bool& warningActive) override; diff --git a/FreeFileSync/Source/ui/folder_selector.cpp b/FreeFileSync/Source/ui/folder_selector.cpp index ac501107..66e45aae 100755 --- a/FreeFileSync/Source/ui/folder_selector.cpp +++ b/FreeFileSync/Source/ui/folder_selector.cpp @@ -19,6 +19,7 @@ // #include + using namespace zen; using namespace fff; @@ -62,7 +63,13 @@ FolderSelector::FolderSelector(wxWindow& dropWindow, wxButton& selectAltFolderButton, FolderHistoryBox& folderComboBox, wxStaticText* staticText, - wxWindow* dropWindow2) : + wxWindow* dropWindow2, + const std::function& shellItemPaths)>& droppedPathsFilter, + const std::function& getDeviceParallelOps, + const std::function& setDeviceParallelOps) : + droppedPathsFilter_ (droppedPathsFilter), + getDeviceParallelOps_(getDeviceParallelOps), + setDeviceParallelOps_(setDeviceParallelOps), dropWindow_(dropWindow), dropWindow2_(dropWindow2), selectFolderButton_(selectFolderButton), @@ -70,6 +77,8 @@ FolderSelector::FolderSelector(wxWindow& dropWindow, folderComboBox_(folderComboBox), staticText_(staticText) { + assert(getDeviceParallelOps_); + auto setupDragDrop = [&](wxWindow& dropWin) { setupFileDrop(dropWin); @@ -129,7 +138,7 @@ void FolderSelector::onItemPathDropped(FileDropEvent& event) if (itemPaths.empty()) return; - if (shouldSetDroppedPaths(itemPaths)) + if (droppedPathsFilter_ && droppedPathsFilter_(itemPaths)) { auto fmtShellPath = [](const Zstring& shellItemPath) { diff --git a/FreeFileSync/Source/ui/folder_selector.h b/FreeFileSync/Source/ui/folder_selector.h index a2154ac6..732ecd72 100755 --- a/FreeFileSync/Source/ui/folder_selector.h +++ b/FreeFileSync/Source/ui/folder_selector.h @@ -12,6 +12,7 @@ #include #include #include "folder_history_box.h" +#include "../fs/abstract.h" namespace fff @@ -37,7 +38,10 @@ public: wxButton& selectAltFolderButton, FolderHistoryBox& folderComboBox, wxStaticText* staticText, //optional - wxWindow* dropWindow2); // + wxWindow* dropWindow2, // + const std::function& shellItemPaths)>& droppedPathsFilter, //optional + const std::function& getDeviceParallelOps, //mandatory + const std::function& setDeviceParallelOps); //optional ~FolderSelector(); @@ -49,20 +53,22 @@ public: void setBackgroundText(const std::wstring& text) { folderComboBox_.SetHint(text); } private: - virtual bool shouldSetDroppedPaths(const std::vector& shellItemPaths) { return true; } //return true if drop should be processed - void onMouseWheel (wxMouseEvent& event); void onItemPathDropped(zen::FileDropEvent& event); void onEditFolderPath (wxCommandEvent& event); void onSelectFolder (wxCommandEvent& event); void onSelectAltFolder(wxCommandEvent& event); + const std::function& shellItemPaths)> droppedPathsFilter_; + const std::function getDeviceParallelOps_; + const std::function setDeviceParallelOps_; + wxWindow& dropWindow_; wxWindow* dropWindow2_ = nullptr; wxButton& selectFolderButton_; wxButton& selectAltFolderButton_; FolderHistoryBox& folderComboBox_; - wxStaticText* staticText_ = nullptr; //optional + wxStaticText* staticText_ = nullptr; //optional FolderSelector* siblingSelector_ = nullptr; }; } diff --git a/FreeFileSync/Source/ui/gui_generated.cpp b/FreeFileSync/Source/ui/gui_generated.cpp index 47843381..0ee3fbd0 100755 --- a/FreeFileSync/Source/ui/gui_generated.cpp +++ b/FreeFileSync/Source/ui/gui_generated.cpp @@ -1200,6 +1200,9 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w m_panelComparisonSettings = new wxPanel( m_panelCompSettingsTab, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); m_panelComparisonSettings->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + wxBoxSizer* bSizer2561; + bSizer2561 = new wxBoxSizer( wxHORIZONTAL ); + wxBoxSizer* bSizer159; bSizer159 = new wxBoxSizer( wxVERTICAL ); @@ -1237,9 +1240,6 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer178->Add( bSizer182, 0, wxALL, 5 ); - m_staticline42 = new wxStaticLine( m_panelComparisonSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL ); - bSizer178->Add( m_staticline42, 0, wxEXPAND, 5 ); - wxBoxSizer* bSizer2371; bSizer2371 = new wxBoxSizer( wxHORIZONTAL ); @@ -1281,13 +1281,16 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer176->Add( m_radioBtnSymlinksDirect, 0, wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); - bSizer1721->Add( bSizer176, 0, wxEXPAND|wxLEFT, 18 ); + bSizer1721->Add( bSizer176, 0, wxLEFT|wxEXPAND, 18 ); + + + bSizer1721->Add( 0, 0, 1, wxEXPAND, 5 ); m_hyperlink24 = new wxHyperlinkCtrl( m_panelComparisonSettings, wxID_ANY, _("More information"), wxEmptyString, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); bSizer1721->Add( m_hyperlink24, 0, wxBOTTOM|wxRIGHT|wxLEFT, 5 ); - bSizer1734->Add( bSizer1721, 0, wxALL, 5 ); + bSizer1734->Add( bSizer1721, 0, wxALL|wxEXPAND, 5 ); m_staticline44 = new wxStaticLine( m_panelComparisonSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL ); bSizer1734->Add( m_staticline44, 0, wxEXPAND, 5 ); @@ -1322,11 +1325,14 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer1733->Add( bSizer197, 0, 0, 5 ); + + bSizer1733->Add( 0, 0, 1, wxEXPAND, 5 ); + m_hyperlink241 = new wxHyperlinkCtrl( m_panelComparisonSettings, wxID_ANY, _("Handle daylight saving time"), wxEmptyString, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); bSizer1733->Add( m_hyperlink241, 0, wxBOTTOM|wxRIGHT|wxLEFT, 5 ); - bSizer1734->Add( bSizer1733, 0, wxALL, 5 ); + bSizer1734->Add( bSizer1733, 0, wxALL|wxEXPAND, 5 ); m_staticline441 = new wxStaticLine( m_panelComparisonSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL ); bSizer1734->Add( m_staticline441, 0, wxEXPAND, 5 ); @@ -1338,10 +1344,77 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer159->Add( m_staticline331, 0, wxEXPAND, 5 ); - m_panelComparisonSettings->SetSizer( bSizer159 ); + bSizer2561->Add( bSizer159, 1, wxEXPAND, 5 ); + + m_staticlinePerformance = new wxStaticLine( m_panelComparisonSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL ); + bSizer2561->Add( m_staticlinePerformance, 0, wxEXPAND, 5 ); + + bSizerPerformance = new wxBoxSizer( wxVERTICAL ); + + m_staticTextPerfDeRequired = new wxStaticText( m_panelComparisonSettings, wxID_ANY, _("Requires FreeFileSync Donation Edition"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticTextPerfDeRequired->Wrap( -1 ); + bSizerPerformance->Add( m_staticTextPerfDeRequired, 0, wxALL, 5 ); + + m_staticlinePerfDeRequired = new wxStaticLine( m_panelComparisonSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + bSizerPerformance->Add( m_staticlinePerfDeRequired, 0, wxEXPAND, 5 ); + + m_panelPerfHeader = new wxPanel( m_panelComparisonSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); + m_panelPerfHeader->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) ); + + wxBoxSizer* bSizer2191; + bSizer2191 = new wxBoxSizer( wxHORIZONTAL ); + + m_bitmapPerf = new wxStaticBitmap( m_panelPerfHeader, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); + bSizer2191->Add( m_bitmapPerf, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); + + m_staticText13611 = new wxStaticText( m_panelPerfHeader, wxID_ANY, _("Performance improvements:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText13611->Wrap( -1 ); + bSizer2191->Add( m_staticText13611, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxRIGHT, 10 ); + + + m_panelPerfHeader->SetSizer( bSizer2191 ); + m_panelPerfHeader->Layout(); + bSizer2191->Fit( m_panelPerfHeader ); + bSizerPerformance->Add( m_panelPerfHeader, 0, wxEXPAND, 5 ); + + wxStaticLine* m_staticline75; + m_staticline75 = new wxStaticLine( m_panelComparisonSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + bSizerPerformance->Add( m_staticline75, 0, wxEXPAND, 5 ); + + bSizer260 = new wxBoxSizer( wxVERTICAL ); + + m_staticTextPerfParallelOps = new wxStaticText( m_panelComparisonSettings, wxID_ANY, _("Parallel file operations:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticTextPerfParallelOps->Wrap( -1 ); + bSizer260->Add( m_staticTextPerfParallelOps, 0, wxTOP|wxRIGHT|wxLEFT, 5 ); + + m_scrolledWindowPerf = new wxScrolledWindow( m_panelComparisonSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxVSCROLL ); + m_scrolledWindowPerf->SetScrollRate( 5, 5 ); + m_scrolledWindowPerf->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + + fgSizerPerf = new wxFlexGridSizer( 0, 2, 5, 5 ); + fgSizerPerf->SetFlexibleDirection( wxBOTH ); + fgSizerPerf->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED ); + + + m_scrolledWindowPerf->SetSizer( fgSizerPerf ); + m_scrolledWindowPerf->Layout(); + fgSizerPerf->Fit( m_scrolledWindowPerf ); + bSizer260->Add( m_scrolledWindowPerf, 1, wxALL|wxEXPAND, 5 ); + + m_hyperlink1711 = new wxHyperlinkCtrl( m_panelComparisonSettings, wxID_ANY, _("How to get best performance?"), wxEmptyString, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + bSizer260->Add( m_hyperlink1711, 0, wxALL, 5 ); + + + bSizerPerformance->Add( bSizer260, 1, wxALL|wxEXPAND, 5 ); + + + bSizer2561->Add( bSizerPerformance, 0, wxEXPAND, 5 ); + + + m_panelComparisonSettings->SetSizer( bSizer2561 ); m_panelComparisonSettings->Layout(); - bSizer159->Fit( m_panelComparisonSettings ); - bSizer275->Add( m_panelComparisonSettings, 0, wxEXPAND, 5 ); + bSizer2561->Fit( m_panelComparisonSettings ); + bSizer275->Add( m_panelComparisonSettings, 1, wxEXPAND, 5 ); m_panelCompSettingsTab->SetSizer( bSizer275 ); @@ -1404,9 +1477,6 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer166->Add( bSizer1661, 3, wxEXPAND|wxLEFT, 5 ); - m_staticline22 = new wxStaticLine( m_panelFilterSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); - bSizer166->Add( m_staticline22, 0, wxEXPAND, 5 ); - bSizer166->Add( 0, 10, 0, 0, 5 ); @@ -1444,6 +1514,12 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer166->Add( bSizer1651, 5, wxEXPAND|wxLEFT, 5 ); + m_staticTextFilterDescr = new wxStaticText( m_panelFilterSettings, wxID_ANY, _("Select filter rules to exclude certain files from synchronization. Enter file paths relative to their corresponding folder pair."), wxDefaultPosition, wxSize( -1, -1 ), 0 ); + m_staticTextFilterDescr->Wrap( -1 ); + m_staticTextFilterDescr->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) ); + + bSizer166->Add( m_staticTextFilterDescr, 0, wxALL, 10 ); + bSizer1591->Add( bSizer166, 1, wxEXPAND, 5 ); @@ -1453,36 +1529,6 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w wxBoxSizer* bSizer160; bSizer160 = new wxBoxSizer( wxVERTICAL ); - wxBoxSizer* bSizer167; - bSizer167 = new wxBoxSizer( wxHORIZONTAL ); - - m_bitmapFilterDate = new wxStaticBitmap( m_panelFilterSettings, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), 0 ); - bSizer167->Add( m_bitmapFilterDate, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 5 ); - - wxBoxSizer* bSizer165; - bSizer165 = new wxBoxSizer( wxVERTICAL ); - - m_staticText79 = new wxStaticText( m_panelFilterSettings, wxID_ANY, _("Time span:"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticText79->Wrap( -1 ); - bSizer165->Add( m_staticText79, 0, wxBOTTOM, 5 ); - - m_spinCtrlTimespan = new wxSpinCtrl( m_panelFilterSettings, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 2000000000, 0 ); - bSizer165->Add( m_spinCtrlTimespan, 0, wxEXPAND, 5 ); - - wxArrayString m_choiceUnitTimespanChoices; - m_choiceUnitTimespan = new wxChoice( m_panelFilterSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, m_choiceUnitTimespanChoices, 0 ); - m_choiceUnitTimespan->SetSelection( 0 ); - bSizer165->Add( m_choiceUnitTimespan, 0, wxEXPAND, 5 ); - - - bSizer167->Add( bSizer165, 1, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxRIGHT, 5 ); - - - bSizer160->Add( bSizer167, 1, wxEXPAND|wxALL, 5 ); - - m_staticline23 = new wxStaticLine( m_panelFilterSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); - bSizer160->Add( m_staticline23, 0, wxEXPAND, 5 ); - wxBoxSizer* bSizer168; bSizer168 = new wxBoxSizer( wxHORIZONTAL ); @@ -1536,44 +1582,58 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer168->Add( bSizer158, 1, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxRIGHT, 5 ); - bSizer160->Add( bSizer168, 0, wxEXPAND|wxALL, 5 ); + bSizer160->Add( bSizer168, 2, wxEXPAND|wxALL, 5 ); + m_staticline23 = new wxStaticLine( m_panelFilterSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + bSizer160->Add( m_staticline23, 0, wxEXPAND, 5 ); - bSizer1591->Add( bSizer160, 0, wxEXPAND, 5 ); + wxBoxSizer* bSizer167; + bSizer167 = new wxBoxSizer( wxHORIZONTAL ); + m_bitmapFilterDate = new wxStaticBitmap( m_panelFilterSettings, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), 0 ); + bSizer167->Add( m_bitmapFilterDate, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 5 ); - m_panelFilterSettings->SetSizer( bSizer1591 ); - m_panelFilterSettings->Layout(); - bSizer1591->Fit( m_panelFilterSettings ); - bSizer278->Add( m_panelFilterSettings, 1, wxEXPAND, 5 ); + wxBoxSizer* bSizer165; + bSizer165 = new wxBoxSizer( wxVERTICAL ); - m_staticline62 = new wxStaticLine( m_panelFilterSettingsTab, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); - bSizer278->Add( m_staticline62, 0, wxEXPAND, 5 ); + m_staticText79 = new wxStaticText( m_panelFilterSettings, wxID_ANY, _("Time span:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText79->Wrap( -1 ); + bSizer165->Add( m_staticText79, 0, wxBOTTOM, 5 ); - wxBoxSizer* bSizer280; - bSizer280 = new wxBoxSizer( wxHORIZONTAL ); + m_spinCtrlTimespan = new wxSpinCtrl( m_panelFilterSettings, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 2000000000, 0 ); + bSizer165->Add( m_spinCtrlTimespan, 0, wxEXPAND, 5 ); - m_staticTextFilterDescr = new wxStaticText( m_panelFilterSettingsTab, wxID_ANY, _("Select filter rules to exclude certain files from synchronization. Enter file paths relative to their corresponding folder pair."), wxDefaultPosition, wxSize( -1, -1 ), 0 ); - m_staticTextFilterDescr->Wrap( -1 ); - bSizer280->Add( m_staticTextFilterDescr, 0, wxALIGN_CENTER_VERTICAL|wxALL, 10 ); + wxArrayString m_choiceUnitTimespanChoices; + m_choiceUnitTimespan = new wxChoice( m_panelFilterSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, m_choiceUnitTimespanChoices, 0 ); + m_choiceUnitTimespan->SetSelection( 0 ); + bSizer165->Add( m_choiceUnitTimespan, 0, wxEXPAND, 5 ); - bSizer280->Add( 0, 0, 1, wxEXPAND, 5 ); + bSizer167->Add( bSizer165, 1, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxRIGHT, 5 ); - m_staticline46 = new wxStaticLine( m_panelFilterSettingsTab, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL ); - bSizer280->Add( m_staticline46, 0, wxEXPAND, 5 ); - m_buttonClear = new wxButton( m_panelFilterSettingsTab, wxID_ANY, _("C&lear"), wxDefaultPosition, wxSize( -1, -1 ), 0 ); - bSizer280->Add( m_buttonClear, 0, wxALL|wxALIGN_CENTER_VERTICAL, 10 ); + bSizer160->Add( bSizer167, 1, wxEXPAND|wxALL, 5 ); + + m_staticline231 = new wxStaticLine( m_panelFilterSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + bSizer160->Add( m_staticline231, 0, wxEXPAND, 5 ); + + m_buttonClear = new wxButton( m_panelFilterSettings, wxID_ANY, _("C&lear"), wxDefaultPosition, wxSize( -1, -1 ), 0 ); + bSizer160->Add( m_buttonClear, 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 10 ); + + + bSizer1591->Add( bSizer160, 0, wxEXPAND, 5 ); - bSizer278->Add( bSizer280, 0, wxEXPAND, 5 ); + m_panelFilterSettings->SetSizer( bSizer1591 ); + m_panelFilterSettings->Layout(); + bSizer1591->Fit( m_panelFilterSettings ); + bSizer278->Add( m_panelFilterSettings, 1, wxEXPAND, 5 ); m_panelFilterSettingsTab->SetSizer( bSizer278 ); m_panelFilterSettingsTab->Layout(); bSizer278->Fit( m_panelFilterSettingsTab ); - m_notebook->AddPage( m_panelFilterSettingsTab, _("dummy"), false ); + m_notebook->AddPage( m_panelFilterSettingsTab, _("dummy"), true ); m_panelSyncSettingsTab = new wxPanel( m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); m_panelSyncSettingsTab->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); @@ -1640,9 +1700,6 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer237->Add( bSizer235, 0, wxALL, 5 ); - m_staticline53 = new wxStaticLine( m_panelSyncSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL ); - bSizer237->Add( m_staticline53, 0, wxEXPAND, 5 ); - wxBoxSizer* bSizer238; bSizer238 = new wxBoxSizer( wxVERTICAL ); @@ -1755,16 +1812,25 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w wxBoxSizer* bSizer201; bSizer201 = new wxBoxSizer( wxHORIZONTAL ); + m_staticline72 = new wxStaticLine( m_panelSyncSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL ); + bSizer201->Add( m_staticline72, 0, wxEXPAND, 5 ); + + wxBoxSizer* bSizer249; + bSizer249 = new wxBoxSizer( wxHORIZONTAL ); + m_checkBoxDetectMove = new wxCheckBox( m_panelSyncSettings, wxID_ANY, _("Detect moved files"), wxDefaultPosition, wxDefaultSize, 0 ); m_checkBoxDetectMove->SetToolTip( _("- Not supported by all file systems\n- Requires and creates database files\n- Detection not available for first sync") ); - bSizer201->Add( m_checkBoxDetectMove, 0, wxALL|wxEXPAND, 5 ); + bSizer249->Add( m_checkBoxDetectMove, 0, wxALL|wxEXPAND, 5 ); m_hyperlink242 = new wxHyperlinkCtrl( m_panelSyncSettings, wxID_ANY, _("More information"), wxEmptyString, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); - bSizer201->Add( m_hyperlink242, 0, wxTOP|wxBOTTOM|wxRIGHT, 5 ); + bSizer249->Add( m_hyperlink242, 0, wxTOP|wxBOTTOM|wxRIGHT, 5 ); + + + bSizer201->Add( bSizer249, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); - bSizer238->Add( bSizer201, 0, wxALL, 5 ); + bSizer238->Add( bSizer201, 0, 0, 5 ); bSizer237->Add( bSizer238, 1, wxEXPAND, 5 ); @@ -1802,9 +1868,6 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer2361->Add( bSizer202, 0, wxALL, 5 ); - m_staticline531 = new wxStaticLine( m_panelSyncSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL ); - bSizer2361->Add( m_staticline531, 0, wxEXPAND, 5 ); - bSizerVersioningHolder = new wxBoxSizer( wxVERTICAL ); @@ -1942,7 +2005,7 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w m_staticTextAutoRetryDelay->Wrap( -1 ); fgSizerAutoRetry->Add( m_staticTextAutoRetryDelay, 0, wxALIGN_CENTER_VERTICAL, 5 ); - m_spinCtrlAutoRetryCount = new wxSpinCtrl( m_panelSyncSettings, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( -1, -1 ), wxSP_ARROW_KEYS, 1, 2000000000, 0 ); + m_spinCtrlAutoRetryCount = new wxSpinCtrl( m_panelSyncSettings, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( -1, -1 ), wxSP_ARROW_KEYS, 1, 2000000000, 1 ); fgSizerAutoRetry->Add( m_spinCtrlAutoRetryCount, 0, wxALIGN_CENTER_VERTICAL, 5 ); m_spinCtrlAutoRetryDelay = new wxSpinCtrl( m_panelSyncSettings, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( -1, -1 ), wxSP_ARROW_KEYS, 0, 2000000000, 0 ); @@ -1991,7 +2054,7 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w m_panelSyncSettingsTab->SetSizer( bSizer276 ); m_panelSyncSettingsTab->Layout(); bSizer276->Fit( m_panelSyncSettingsTab ); - m_notebook->AddPage( m_panelSyncSettingsTab, _("dummy"), true ); + m_notebook->AddPage( m_panelSyncSettingsTab, _("dummy"), false ); bSizer190->Add( m_notebook, 1, wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 5 ); @@ -2034,12 +2097,13 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w m_hyperlink24->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( ConfigDlgGenerated::OnHelpComparisonSettings ), NULL, this ); m_textCtrlTimeShift->Connect( wxEVT_KEY_DOWN, wxKeyEventHandler( ConfigDlgGenerated::onlTimeShiftKeyDown ), NULL, this ); m_hyperlink241->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( ConfigDlgGenerated::OnHelpTimeShift ), NULL, this ); + m_hyperlink1711->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( ConfigDlgGenerated::OnHelpPerformance ), NULL, this ); m_textCtrlInclude->Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( ConfigDlgGenerated::OnChangeFilterOption ), NULL, this ); m_hyperlink171->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( ConfigDlgGenerated::OnHelpShowExamples ), NULL, this ); m_textCtrlExclude->Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( ConfigDlgGenerated::OnChangeFilterOption ), NULL, this ); - m_choiceUnitTimespan->Connect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( ConfigDlgGenerated::OnChangeFilterOption ), NULL, this ); m_choiceUnitMinSize->Connect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( ConfigDlgGenerated::OnChangeFilterOption ), NULL, this ); m_choiceUnitMaxSize->Connect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( ConfigDlgGenerated::OnChangeFilterOption ), NULL, this ); + m_choiceUnitTimespan->Connect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( ConfigDlgGenerated::OnChangeFilterOption ), NULL, this ); m_buttonClear->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnFilterReset ), NULL, this ); m_checkBoxUseLocalSyncOptions->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnToggleLocalSyncSettings ), NULL, this ); m_toggleBtnTwoWay->Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( ConfigDlgGenerated::OnSyncTwoWayDouble ), NULL, this ); @@ -2297,20 +2361,23 @@ CloudSetupDlgGenerated::CloudSetupDlgGenerated( wxWindow* parent, wxWindowID id, bSizer185->Fit( m_panel41 ); bSizer134->Add( m_panel41, 0, wxEXPAND, 5 ); - bSizerSftpTweaks = new wxBoxSizer( wxVERTICAL ); + bSizer255 = new wxBoxSizer( wxVERTICAL ); m_staticline571 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); - bSizerSftpTweaks->Add( m_staticline571, 0, wxEXPAND, 5 ); + bSizer255->Add( m_staticline571, 0, wxEXPAND, 5 ); wxBoxSizer* bSizer219; bSizer219 = new wxBoxSizer( wxHORIZONTAL ); - m_bitmapSpeedSftp = new wxStaticBitmap( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); - bSizer219->Add( m_bitmapSpeedSftp, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT, 10 ); + + bSizer219->Add( 5, 0, 0, 0, 5 ); + + m_bitmapPerf = new wxStaticBitmap( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); + bSizer219->Add( m_bitmapPerf, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); m_staticText1361 = new wxStaticText( this, wxID_ANY, _("Performance improvements:"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText1361->Wrap( -1 ); - bSizer219->Add( m_staticText1361, 0, wxALL|wxALIGN_CENTER_VERTICAL, 10 ); + bSizer219->Add( m_staticText1361, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxRIGHT, 10 ); bSizer219->Add( 0, 0, 1, wxEXPAND, 5 ); @@ -2319,10 +2386,10 @@ CloudSetupDlgGenerated::CloudSetupDlgGenerated( wxWindow* parent, wxWindowID id, bSizer219->Add( m_hyperlink171, 0, wxALL|wxALIGN_CENTER_VERTICAL, 10 ); - bSizerSftpTweaks->Add( bSizer219, 0, wxEXPAND, 5 ); + bSizer255->Add( bSizer219, 0, wxEXPAND, 5 ); m_staticline57 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); - bSizerSftpTweaks->Add( m_staticline57, 0, wxEXPAND, 5 ); + bSizer255->Add( m_staticline57, 0, wxEXPAND, 5 ); m_panel411 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); m_panel411->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); @@ -2336,28 +2403,37 @@ CloudSetupDlgGenerated::CloudSetupDlgGenerated( wxWindow* parent, wxWindowID id, fgSizer1611->SetFlexibleDirection( wxBOTH ); fgSizer1611->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED ); - m_staticText12341 = new wxStaticText( m_panel411, wxID_ANY, _("Connections for directory reading:"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticText12341->Wrap( -1 ); - fgSizer1611->Add( m_staticText12341, 0, wxTOP|wxBOTTOM|wxLEFT|wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT, 5 ); + bSizerConnectionsLabel = new wxBoxSizer( wxVERTICAL ); + + m_staticTextConnectionsLabel = new wxStaticText( m_panel411, wxID_ANY, _("Parallel file operations:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticTextConnectionsLabel->Wrap( -1 ); + bSizerConnectionsLabel->Add( m_staticTextConnectionsLabel, 0, 0, 5 ); + + m_staticTextConnectionsLabelSub = new wxStaticText( m_panel411, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticTextConnectionsLabelSub->Wrap( -1 ); + bSizerConnectionsLabel->Add( m_staticTextConnectionsLabelSub, 0, wxALIGN_RIGHT, 5 ); + - m_spinCtrlConnectionCountSftp = new wxSpinCtrl( m_panel411, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( -1, -1 ), wxSP_ARROW_KEYS, 1, 2000000000, 1 ); - fgSizer1611->Add( m_spinCtrlConnectionCountSftp, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); + fgSizer1611->Add( bSizerConnectionsLabel, 0, wxTOP|wxBOTTOM|wxLEFT|wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL, 5 ); - m_staticTextSftpConnectionCountHint = new wxStaticText( m_panel411, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticTextSftpConnectionCountHint->Wrap( -1 ); - m_staticTextSftpConnectionCountHint->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) ); + m_spinCtrlConnectionCount = new wxSpinCtrl( m_panel411, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( -1, -1 ), wxSP_ARROW_KEYS, 1, 2000000000, 1 ); + fgSizer1611->Add( m_spinCtrlConnectionCount, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); - fgSizer1611->Add( m_staticTextSftpConnectionCountHint, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); + m_staticTextConnectionCountDescr = new wxStaticText( m_panel411, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticTextConnectionCountDescr->Wrap( -1 ); + m_staticTextConnectionCountDescr->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) ); - m_staticText1231111 = new wxStaticText( m_panel411, wxID_ANY, _("SFTP channels per connection:"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticText1231111->Wrap( -1 ); - fgSizer1611->Add( m_staticText1231111, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT|wxTOP|wxBOTTOM|wxLEFT, 5 ); + fgSizer1611->Add( m_staticTextConnectionCountDescr, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); + + m_staticTextChannelCountSftp = new wxStaticText( m_panel411, wxID_ANY, _("SFTP channels per connection:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticTextChannelCountSftp->Wrap( -1 ); + fgSizer1611->Add( m_staticTextChannelCountSftp, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT|wxTOP|wxBOTTOM|wxLEFT, 5 ); m_spinCtrlChannelCountSftp = new wxSpinCtrl( m_panel411, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( -1, -1 ), wxSP_ARROW_KEYS, 1, 2000000000, 1 ); fgSizer1611->Add( m_spinCtrlChannelCountSftp, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); - m_button42 = new wxButton( m_panel411, wxID_ANY, _("Detect server limit"), wxDefaultPosition, wxDefaultSize, 0 ); - fgSizer1611->Add( m_button42, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); + m_buttonChannelCountSftp = new wxButton( m_panel411, wxID_ANY, _("Detect server limit"), wxDefaultPosition, wxDefaultSize, 0 ); + fgSizer1611->Add( m_buttonChannelCountSftp, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); bSizer1851->Add( fgSizer1611, 0, wxALL, 5 ); @@ -2366,74 +2442,10 @@ CloudSetupDlgGenerated::CloudSetupDlgGenerated( wxWindow* parent, wxWindowID id, m_panel411->SetSizer( bSizer1851 ); m_panel411->Layout(); bSizer1851->Fit( m_panel411 ); - bSizerSftpTweaks->Add( m_panel411, 1, wxEXPAND, 5 ); - - - bSizer134->Add( bSizerSftpTweaks, 1, wxEXPAND, 5 ); - - bSizerFtpTweaks = new wxBoxSizer( wxVERTICAL ); - - m_staticline5711 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); - bSizerFtpTweaks->Add( m_staticline5711, 0, wxEXPAND, 5 ); - - wxBoxSizer* bSizer2191; - bSizer2191 = new wxBoxSizer( wxHORIZONTAL ); - - m_bitmapSpeedFtp = new wxStaticBitmap( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); - bSizer2191->Add( m_bitmapSpeedFtp, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT, 10 ); - - m_staticText13611 = new wxStaticText( this, wxID_ANY, _("Performance improvements:"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticText13611->Wrap( -1 ); - bSizer2191->Add( m_staticText13611, 0, wxALL|wxALIGN_CENTER_VERTICAL, 10 ); - - - bSizer2191->Add( 0, 0, 1, wxEXPAND, 5 ); - - m_hyperlink1711 = new wxHyperlinkCtrl( this, wxID_ANY, _("How to get best performance?"), wxEmptyString, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); - bSizer2191->Add( m_hyperlink1711, 0, wxALL|wxALIGN_CENTER_VERTICAL, 10 ); - + bSizer255->Add( m_panel411, 1, wxEXPAND, 5 ); - bSizerFtpTweaks->Add( bSizer2191, 0, wxEXPAND, 5 ); - m_staticline573 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); - bSizerFtpTweaks->Add( m_staticline573, 0, wxEXPAND, 5 ); - - m_panel4111 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); - m_panel4111->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); - - wxBoxSizer* bSizer18511; - bSizer18511 = new wxBoxSizer( wxVERTICAL ); - - wxFlexGridSizer* fgSizer16111; - fgSizer16111 = new wxFlexGridSizer( 0, 3, 0, 0 ); - fgSizer16111->AddGrowableCol( 1 ); - fgSizer16111->SetFlexibleDirection( wxBOTH ); - fgSizer16111->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED ); - - m_staticText123411 = new wxStaticText( m_panel4111, wxID_ANY, _("Connections for directory reading:"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticText123411->Wrap( -1 ); - fgSizer16111->Add( m_staticText123411, 0, wxTOP|wxBOTTOM|wxLEFT|wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT, 5 ); - - m_spinCtrlConnectionCountFtp = new wxSpinCtrl( m_panel4111, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( -1, -1 ), wxSP_ARROW_KEYS, 1, 2000000000, 1 ); - fgSizer16111->Add( m_spinCtrlConnectionCountFtp, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); - - m_staticTextFtpConnectionCountHint = new wxStaticText( m_panel4111, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticTextFtpConnectionCountHint->Wrap( -1 ); - m_staticTextFtpConnectionCountHint->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) ); - - fgSizer16111->Add( m_staticTextFtpConnectionCountHint, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); - - - bSizer18511->Add( fgSizer16111, 0, wxALL, 5 ); - - - m_panel4111->SetSizer( bSizer18511 ); - m_panel4111->Layout(); - bSizer18511->Fit( m_panel4111 ); - bSizerFtpTweaks->Add( m_panel4111, 1, wxEXPAND, 5 ); - - - bSizer134->Add( bSizerFtpTweaks, 1, wxEXPAND, 5 ); + bSizer134->Add( bSizer255, 1, wxEXPAND, 5 ); m_staticline12 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); bSizer134->Add( m_staticline12, 0, wxEXPAND, 5 ); @@ -2469,9 +2481,8 @@ CloudSetupDlgGenerated::CloudSetupDlgGenerated( wxWindow* parent, wxWindowID id, m_buttonSelectKeyfile->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( CloudSetupDlgGenerated::OnSelectKeyfile ), NULL, this ); m_checkBoxShowPassword->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( CloudSetupDlgGenerated::OnToggleShowPassword ), NULL, this ); m_buttonSelectFolder->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( CloudSetupDlgGenerated::OnBrowseCloudFolder ), NULL, this ); - m_hyperlink171->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( CloudSetupDlgGenerated::OnHelpSftpPerformance ), NULL, this ); - m_button42->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( CloudSetupDlgGenerated::OnDetectServerChannelLimit ), NULL, this ); - m_hyperlink1711->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( CloudSetupDlgGenerated::OnHelpSftpPerformance ), NULL, this ); + m_hyperlink171->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( CloudSetupDlgGenerated::OnHelpFtpPerformance ), NULL, this ); + m_buttonChannelCountSftp->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( CloudSetupDlgGenerated::OnDetectServerChannelLimit ), NULL, this ); m_buttonOkay->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( CloudSetupDlgGenerated::OnOkay ), NULL, this ); m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( CloudSetupDlgGenerated::OnCancel ), NULL, this ); } @@ -4140,99 +4151,78 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS m_staticline341 = new wxStaticLine( m_panel41, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); bSizer162->Add( m_staticline341, 0, wxEXPAND, 5 ); - wxBoxSizer* bSizer174; - bSizer174 = new wxBoxSizer( wxHORIZONTAL ); - - wxBoxSizer* bSizer181; - bSizer181 = new wxBoxSizer( wxVERTICAL ); - - wxBoxSizer* bSizer187; - bSizer187 = new wxBoxSizer( wxVERTICAL ); - - m_staticText96 = new wxStaticText( m_panel41, wxID_ANY, _("Source code written in C++ using:"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticText96->Wrap( -1 ); - bSizer187->Add( m_staticText96, 0, wxALL, 5 ); - - wxBoxSizer* bSizer171; - bSizer171 = new wxBoxSizer( wxHORIZONTAL ); + wxBoxSizer* bSizer186; + bSizer186 = new wxBoxSizer( wxVERTICAL ); - m_hyperlink11 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("MS Visual Studio"), wxT("https://www.visualstudio.com"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); - m_hyperlink11->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); - m_hyperlink11->SetToolTip( _("https://www.visualstudio.com") ); + m_staticText94 = new wxStaticText( m_panel41, wxID_ANY, _("Feedback and suggestions are welcome"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText94->Wrap( -1 ); + bSizer186->Add( m_staticText94, 0, wxALL, 5 ); - bSizer171->Add( m_hyperlink11, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + wxBoxSizer* bSizer166; + bSizer166 = new wxBoxSizer( wxHORIZONTAL ); - m_hyperlink7 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("wxWidgets"), wxT("http://www.wxwidgets.org"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); - m_hyperlink7->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); - m_hyperlink7->SetToolTip( _("http://www.wxwidgets.org") ); - bSizer171->Add( m_hyperlink7, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + bSizer166->Add( 0, 0, 1, wxEXPAND, 5 ); - m_hyperlink14 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("wxFormBuilder"), wxT("https://github.com/wxFormBuilder/wxFormBuilder"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); - m_hyperlink14->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); - m_hyperlink14->SetToolTip( _("https://github.com/wxFormBuilder/wxFormBuilder") ); + m_bitmapHomepage = new wxStaticBitmap( m_panel41, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), 0 ); + m_bitmapHomepage->SetToolTip( _("Home page") ); - bSizer171->Add( m_hyperlink14, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + bSizer166->Add( m_bitmapHomepage, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 ); - m_hyperlink16 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("Artistic Style"), wxT("http://astyle.sourceforge.net"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); - m_hyperlink16->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); - m_hyperlink16->SetToolTip( _("http://astyle.sourceforge.net") ); + m_hyperlink1 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("FreeFileSync.org"), wxT("https://www.freefilesync.org/"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink1->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, true, wxEmptyString ) ); + m_hyperlink1->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + m_hyperlink1->SetToolTip( _("https://www.freefilesync.org") ); - bSizer171->Add( m_hyperlink16, 0, wxALIGN_CENTER_VERTICAL, 5 ); + bSizer166->Add( m_hyperlink1, 0, wxALIGN_CENTER_VERTICAL, 5 ); - bSizer187->Add( bSizer171, 0, wxALIGN_CENTER_HORIZONTAL|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); + bSizer166->Add( 0, 0, 1, wxEXPAND, 5 ); - wxBoxSizer* bSizer172; - bSizer172 = new wxBoxSizer( wxHORIZONTAL ); + m_bitmapForum = new wxStaticBitmap( m_panel41, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), 0 ); + m_bitmapForum->SetToolTip( _("FreeFileSync Forum") ); - m_hyperlink15 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("zen::Xml"), wxT("http://zenxml.sourceforge.net"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); - m_hyperlink15->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); - m_hyperlink15->SetToolTip( _("http://zenxml.sourceforge.net") ); + bSizer166->Add( m_bitmapForum, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 ); - bSizer172->Add( m_hyperlink15, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + m_hyperlink21 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("FreeFileSync Forum"), wxT("https://www.freefilesync.org/forum/"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink21->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, true, wxEmptyString ) ); + m_hyperlink21->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + m_hyperlink21->SetToolTip( _("https://www.freefilesync.org/forum/") ); - m_hyperlink12 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("Google Test"), wxT("https://github.com/google/googletest"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); - m_hyperlink12->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); - m_hyperlink12->SetToolTip( _("https://github.com/google/googletest") ); + bSizer166->Add( m_hyperlink21, 0, wxALIGN_CENTER_VERTICAL, 5 ); - bSizer172->Add( m_hyperlink12, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); - m_hyperlink13 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("Boost"), wxT("http://www.boost.org"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); - m_hyperlink13->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); - m_hyperlink13->SetToolTip( _("http://www.boost.org") ); + bSizer166->Add( 0, 0, 1, wxEXPAND, 5 ); - bSizer172->Add( m_hyperlink13, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + m_bitmapEmail = new wxStaticBitmap( m_panel41, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), 0 ); + m_bitmapEmail->SetToolTip( _("Email") ); - m_hyperlink10 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("libssh2"), wxT("https://www.libssh2.org"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); - m_hyperlink10->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); - m_hyperlink10->SetToolTip( _("https://www.libssh2.org") ); + bSizer166->Add( m_bitmapEmail, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 ); - bSizer172->Add( m_hyperlink10, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + m_hyperlink2 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("zenju@freefilesync.org"), wxT("mailto:zenju@freefilesync.org"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink2->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, true, wxEmptyString ) ); + m_hyperlink2->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + m_hyperlink2->SetToolTip( _("mailto:zenju@freefilesync.org") ); - m_hyperlink101 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("libcurl"), wxT("https://curl.haxx.se/libcurl"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); - m_hyperlink101->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); - m_hyperlink101->SetToolTip( _("https://curl.haxx.se/libcurl") ); + bSizer166->Add( m_hyperlink2, 0, wxALIGN_CENTER_VERTICAL, 5 ); - bSizer172->Add( m_hyperlink101, 0, wxRIGHT|wxALIGN_CENTER_VERTICAL, 5 ); - m_hyperlink18 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("NSIS"), wxT("http://nsis.sourceforge.net"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); - m_hyperlink18->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); - m_hyperlink18->SetToolTip( _("http://nsis.sourceforge.net") ); + bSizer166->Add( 0, 0, 1, wxEXPAND, 5 ); - bSizer172->Add( m_hyperlink18, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); - m_hyperlink9 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("Inno Setup"), wxT("http://www.jrsoftware.org"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); - m_hyperlink9->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); - m_hyperlink9->SetToolTip( _("http://www.jrsoftware.org") ); + bSizer186->Add( bSizer166, 0, wxBOTTOM|wxRIGHT|wxLEFT|wxEXPAND, 5 ); - bSizer172->Add( m_hyperlink9, 0, wxALIGN_CENTER_VERTICAL, 5 ); + bSizer162->Add( bSizer186, 0, wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 5 ); - bSizer187->Add( bSizer172, 0, wxBOTTOM|wxRIGHT|wxLEFT|wxALIGN_CENTER_HORIZONTAL, 5 ); + m_staticline3412 = new wxStaticLine( m_panel41, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + bSizer162->Add( m_staticline3412, 0, wxEXPAND, 5 ); + wxBoxSizer* bSizer174; + bSizer174 = new wxBoxSizer( wxHORIZONTAL ); - bSizer181->Add( bSizer187, 0, wxALL|wxEXPAND, 5 ); + wxBoxSizer* bSizer181; + bSizer181 = new wxBoxSizer( wxVERTICAL ); m_panelDonate = new wxPanel( m_panel41, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); m_panelDonate->SetBackgroundColour( wxColour( 153, 170, 187 ) ); @@ -4284,7 +4274,7 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS m_panelDonate->SetSizer( bSizer183 ); m_panelDonate->Layout(); bSizer183->Fit( m_panelDonate ); - bSizer181->Add( m_panelDonate, 0, wxEXPAND|wxRIGHT|wxLEFT, 5 ); + bSizer181->Add( m_panelDonate, 0, wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 10 ); m_panelThankYou = new wxPanel( m_panel41, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); m_panelThankYou->SetBackgroundColour( wxColour( 153, 170, 187 ) ); @@ -4302,28 +4292,17 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS bSizer1841 = new wxBoxSizer( wxHORIZONTAL ); m_bitmapThanks = new wxStaticBitmap( m_panel391, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); - bSizer1841->Add( m_bitmapThanks, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT, 10 ); - - wxBoxSizer* bSizer1781; - bSizer1781 = new wxBoxSizer( wxVERTICAL ); + bSizer1841->Add( m_bitmapThanks, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT, 5 ); m_staticTextThanks = new wxStaticText( m_panel391, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTRE ); m_staticTextThanks->Wrap( -1 ); m_staticTextThanks->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) ); m_staticTextThanks->SetForegroundColour( wxColour( 0, 0, 0 ) ); - bSizer1781->Add( m_staticTextThanks, 0, wxALL|wxALIGN_CENTER_HORIZONTAL, 5 ); - - m_buttonShowDonationDetails = new wxButton( m_panel391, wxID_ANY, _("Donation details"), wxDefaultPosition, wxDefaultSize, 0 ); - m_buttonShowDonationDetails->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) ); + bSizer1841->Add( m_staticTextThanks, 0, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); - bSizer1781->Add( m_buttonShowDonationDetails, 0, wxBOTTOM|wxRIGHT|wxLEFT|wxALIGN_CENTER_HORIZONTAL, 5 ); - - bSizer1841->Add( bSizer1781, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); - - - bSizer243->Add( bSizer1841, 1, wxALIGN_CENTER_HORIZONTAL, 5 ); + bSizer243->Add( bSizer1841, 1, wxALIGN_CENTER_HORIZONTAL|wxRIGHT|wxLEFT, 5 ); m_staticTextNoAutoUpdate = new wxStaticText( m_panel391, wxID_ANY, _("The auto updater was disabled by the administrator."), wxDefaultPosition, wxDefaultSize, 0 ); m_staticTextNoAutoUpdate->Wrap( -1 ); @@ -4331,6 +4310,11 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS bSizer243->Add( m_staticTextNoAutoUpdate, 0, wxALIGN_CENTER_HORIZONTAL|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); + m_buttonShowDonationDetails = new wxButton( m_panel391, wxID_ANY, _("Donation details"), wxDefaultPosition, wxDefaultSize, 0 ); + m_buttonShowDonationDetails->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) ); + + bSizer243->Add( m_buttonShowDonationDetails, 0, wxBOTTOM|wxRIGHT|wxLEFT|wxEXPAND, 5 ); + m_panel391->SetSizer( bSizer243 ); m_panel391->Layout(); @@ -4341,56 +4325,95 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS m_panelThankYou->SetSizer( bSizer1831 ); m_panelThankYou->Layout(); bSizer1831->Fit( m_panelThankYou ); - bSizer181->Add( m_panelThankYou, 0, wxEXPAND|wxRIGHT|wxLEFT, 5 ); + bSizer181->Add( m_panelThankYou, 0, wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 10 ); - wxBoxSizer* bSizer186; - bSizer186 = new wxBoxSizer( wxVERTICAL ); + wxBoxSizer* bSizer187; + bSizer187 = new wxBoxSizer( wxVERTICAL ); - m_staticText94 = new wxStaticText( m_panel41, wxID_ANY, _("Feedback and suggestions are welcome"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticText94->Wrap( -1 ); - bSizer186->Add( m_staticText94, 0, wxALL, 5 ); + m_staticText96 = new wxStaticText( m_panel41, wxID_ANY, _("Source code written in C++ using:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText96->Wrap( -1 ); + bSizer187->Add( m_staticText96, 0, wxALL, 5 ); - wxBoxSizer* bSizer166; - bSizer166 = new wxBoxSizer( wxHORIZONTAL ); + wxBoxSizer* bSizer171; + bSizer171 = new wxBoxSizer( wxHORIZONTAL ); + m_hyperlink11 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("MS Visual Studio"), wxT("https://www.visualstudio.com"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink11->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + m_hyperlink11->SetToolTip( _("https://www.visualstudio.com") ); - bSizer166->Add( 0, 0, 1, wxEXPAND, 5 ); + bSizer171->Add( m_hyperlink11, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); - m_bitmapHomepage = new wxStaticBitmap( m_panel41, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), 0 ); - m_bitmapHomepage->SetToolTip( _("Home page") ); + m_hyperlink7 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("wxWidgets"), wxT("http://www.wxwidgets.org"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink7->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + m_hyperlink7->SetToolTip( _("http://www.wxwidgets.org") ); - bSizer166->Add( m_bitmapHomepage, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 ); + bSizer171->Add( m_hyperlink7, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); - m_hyperlink1 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("FreeFileSync.org"), wxT("https://www.freefilesync.org/"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); - m_hyperlink1->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, true, wxEmptyString ) ); - m_hyperlink1->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); - m_hyperlink1->SetToolTip( _("https://www.freefilesync.org") ); + m_hyperlink14 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("wxFormBuilder"), wxT("https://github.com/wxFormBuilder/wxFormBuilder"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink14->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + m_hyperlink14->SetToolTip( _("https://github.com/wxFormBuilder/wxFormBuilder") ); - bSizer166->Add( m_hyperlink1, 0, wxALIGN_CENTER_VERTICAL, 5 ); + bSizer171->Add( m_hyperlink14, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + m_hyperlink16 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("Artistic Style"), wxT("http://astyle.sourceforge.net"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink16->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + m_hyperlink16->SetToolTip( _("http://astyle.sourceforge.net") ); - bSizer166->Add( 0, 0, 1, wxEXPAND, 5 ); + bSizer171->Add( m_hyperlink16, 0, wxALIGN_CENTER_VERTICAL, 5 ); - m_bitmapEmail = new wxStaticBitmap( m_panel41, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), 0 ); - m_bitmapEmail->SetToolTip( _("Email") ); - bSizer166->Add( m_bitmapEmail, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 ); + bSizer187->Add( bSizer171, 0, wxALIGN_CENTER_HORIZONTAL|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); - m_hyperlink2 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("zenju@freefilesync.org"), wxT("mailto:zenju@freefilesync.org"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); - m_hyperlink2->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, true, wxEmptyString ) ); - m_hyperlink2->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); - m_hyperlink2->SetToolTip( _("mailto:zenju@freefilesync.org") ); + wxBoxSizer* bSizer172; + bSizer172 = new wxBoxSizer( wxHORIZONTAL ); - bSizer166->Add( m_hyperlink2, 0, wxALIGN_CENTER_VERTICAL, 5 ); + m_hyperlink15 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("zen::Xml"), wxT("http://zenxml.sourceforge.net"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink15->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + m_hyperlink15->SetToolTip( _("http://zenxml.sourceforge.net") ); + bSizer172->Add( m_hyperlink15, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); - bSizer166->Add( 0, 0, 1, wxEXPAND, 5 ); + m_hyperlink12 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("Google Test"), wxT("https://github.com/google/googletest"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink12->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + m_hyperlink12->SetToolTip( _("https://github.com/google/googletest") ); + + bSizer172->Add( m_hyperlink12, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + + m_hyperlink13 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("Boost"), wxT("http://www.boost.org"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink13->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + m_hyperlink13->SetToolTip( _("http://www.boost.org") ); + + bSizer172->Add( m_hyperlink13, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + + m_hyperlink10 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("libssh2"), wxT("https://www.libssh2.org"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink10->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + m_hyperlink10->SetToolTip( _("https://www.libssh2.org") ); + + bSizer172->Add( m_hyperlink10, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + m_hyperlink101 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("libcurl"), wxT("https://curl.haxx.se/libcurl"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink101->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + m_hyperlink101->SetToolTip( _("https://curl.haxx.se/libcurl") ); - bSizer186->Add( bSizer166, 0, wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); + bSizer172->Add( m_hyperlink101, 0, wxRIGHT|wxALIGN_CENTER_VERTICAL, 5 ); + m_hyperlink18 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("NSIS"), wxT("http://nsis.sourceforge.net"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink18->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + m_hyperlink18->SetToolTip( _("http://nsis.sourceforge.net") ); - bSizer181->Add( bSizer186, 0, wxALL|wxEXPAND, 5 ); + bSizer172->Add( m_hyperlink18, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + + m_hyperlink9 = new wxHyperlinkCtrl( m_panel41, wxID_ANY, _("Inno Setup"), wxT("http://www.jrsoftware.org"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink9->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + m_hyperlink9->SetToolTip( _("http://www.jrsoftware.org") ); + + bSizer172->Add( m_hyperlink9, 0, wxALIGN_CENTER_VERTICAL, 5 ); + + + bSizer187->Add( bSizer172, 0, wxBOTTOM|wxRIGHT|wxLEFT|wxALIGN_CENTER_HORIZONTAL, 5 ); + + + bSizer181->Add( bSizer187, 0, wxALL|wxEXPAND, 5 ); m_staticline34 = new wxStaticLine( m_panel41, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); bSizer181->Add( m_staticline34, 0, wxEXPAND, 5 ); @@ -4452,7 +4475,7 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS bSizer177->Add( m_scrolledWindowTranslators, 1, wxLEFT|wxEXPAND, 5 ); - bSizer174->Add( bSizer177, 0, wxTOP|wxLEFT|wxEXPAND, 5 ); + bSizer174->Add( bSizer177, 0, wxEXPAND|wxLEFT, 5 ); bSizer162->Add( bSizer174, 0, 0, 5 ); diff --git a/FreeFileSync/Source/ui/gui_generated.h b/FreeFileSync/Source/ui/gui_generated.h index 3b7034db..4c91d1fe 100755 --- a/FreeFileSync/Source/ui/gui_generated.h +++ b/FreeFileSync/Source/ui/gui_generated.h @@ -311,7 +311,6 @@ protected: wxToggleButton* m_toggleBtnByTimeSize; wxToggleButton* m_toggleBtnByContent; wxToggleButton* m_toggleBtnBySize; - wxStaticLine* m_staticline42; wxStaticBitmap* m_bitmapCompVariant; wxStaticText* m_staticTextCompVarDescription; wxStaticLine* m_staticline33; @@ -327,6 +326,18 @@ protected: wxHyperlinkCtrl* m_hyperlink241; wxStaticLine* m_staticline441; wxStaticLine* m_staticline331; + wxStaticLine* m_staticlinePerformance; + wxBoxSizer* bSizerPerformance; + wxStaticText* m_staticTextPerfDeRequired; + wxStaticLine* m_staticlinePerfDeRequired; + wxPanel* m_panelPerfHeader; + wxStaticBitmap* m_bitmapPerf; + wxStaticText* m_staticText13611; + wxBoxSizer* bSizer260; + wxStaticText* m_staticTextPerfParallelOps; + wxScrolledWindow* m_scrolledWindowPerf; + wxFlexGridSizer* fgSizerPerf; + wxHyperlinkCtrl* m_hyperlink1711; wxPanel* m_panelFilterSettingsTab; wxBoxSizer* bSizerHeaderFilterSettings; wxStaticText* m_staticTextMainFilterSettings; @@ -336,17 +347,11 @@ protected: wxStaticBitmap* m_bitmapInclude; wxStaticText* m_staticText78; wxTextCtrl* m_textCtrlInclude; - wxStaticLine* m_staticline22; wxStaticBitmap* m_bitmapExclude; wxStaticText* m_staticText77; wxHyperlinkCtrl* m_hyperlink171; wxTextCtrl* m_textCtrlExclude; wxStaticLine* m_staticline24; - wxStaticBitmap* m_bitmapFilterDate; - wxStaticText* m_staticText79; - wxSpinCtrl* m_spinCtrlTimespan; - wxChoice* m_choiceUnitTimespan; - wxStaticLine* m_staticline23; wxStaticBitmap* m_bitmapFilterSize; wxStaticText* m_staticText80; wxStaticText* m_staticText101; @@ -355,8 +360,12 @@ protected: wxStaticText* m_staticText102; wxSpinCtrl* m_spinCtrlMaxSize; wxChoice* m_choiceUnitMaxSize; - wxStaticLine* m_staticline62; - wxStaticLine* m_staticline46; + wxStaticLine* m_staticline23; + wxStaticBitmap* m_bitmapFilterDate; + wxStaticText* m_staticText79; + wxSpinCtrl* m_spinCtrlTimespan; + wxChoice* m_choiceUnitTimespan; + wxStaticLine* m_staticline231; wxButton* m_buttonClear; wxPanel* m_panelSyncSettingsTab; wxBoxSizer* bSizerHeaderSyncSettings; @@ -369,7 +378,6 @@ protected: wxToggleButton* m_toggleBtnMirror; wxToggleButton* m_toggleBtnUpdate; wxToggleButton* m_toggleBtnCustom; - wxStaticLine* m_staticline53; wxBoxSizer* bSizerSyncDirHolder; wxBoxSizer* bSizerSyncDirections; wxStaticText* m_staticTextCategory; @@ -392,6 +400,7 @@ protected: wxStaticText* m_staticText145; wxStaticText* m_staticTextSyncVarDescription; wxStaticLine* m_staticline431; + wxStaticLine* m_staticline72; wxCheckBox* m_checkBoxDetectMove; wxHyperlinkCtrl* m_hyperlink242; wxStaticLine* m_staticline54; @@ -400,7 +409,6 @@ protected: wxToggleButton* m_toggleBtnRecycler; wxToggleButton* m_toggleBtnPermanent; wxToggleButton* m_toggleBtnVersioning; - wxStaticLine* m_staticline531; wxBoxSizer* bSizerVersioningHolder; wxStaticBitmap* m_bitmapDeletionType; wxStaticText* m_staticTextDeletionTypeDescription; @@ -445,6 +453,7 @@ protected: virtual void OnHelpComparisonSettings( wxHyperlinkEvent& event ) { event.Skip(); } virtual void onlTimeShiftKeyDown( wxKeyEvent& event ) { event.Skip(); } virtual void OnHelpTimeShift( wxHyperlinkEvent& event ) { event.Skip(); } + virtual void OnHelpPerformance( wxHyperlinkEvent& event ) { event.Skip(); } virtual void OnChangeFilterOption( wxCommandEvent& event ) { event.Skip(); } virtual void OnHelpShowExamples( wxHyperlinkEvent& event ) { event.Skip(); } virtual void OnFilterReset( wxCommandEvent& event ) { event.Skip(); } @@ -535,29 +544,21 @@ protected: wxStaticText* m_staticText1232; wxTextCtrl* m_textCtrlServerPath; wxButton* m_buttonSelectFolder; - wxBoxSizer* bSizerSftpTweaks; + wxBoxSizer* bSizer255; wxStaticLine* m_staticline571; - wxStaticBitmap* m_bitmapSpeedSftp; + wxStaticBitmap* m_bitmapPerf; wxStaticText* m_staticText1361; wxHyperlinkCtrl* m_hyperlink171; wxStaticLine* m_staticline57; wxPanel* m_panel411; - wxStaticText* m_staticText12341; - wxSpinCtrl* m_spinCtrlConnectionCountSftp; - wxStaticText* m_staticTextSftpConnectionCountHint; - wxStaticText* m_staticText1231111; + wxBoxSizer* bSizerConnectionsLabel; + wxStaticText* m_staticTextConnectionsLabel; + wxStaticText* m_staticTextConnectionsLabelSub; + wxSpinCtrl* m_spinCtrlConnectionCount; + wxStaticText* m_staticTextConnectionCountDescr; + wxStaticText* m_staticTextChannelCountSftp; wxSpinCtrl* m_spinCtrlChannelCountSftp; - wxButton* m_button42; - wxBoxSizer* bSizerFtpTweaks; - wxStaticLine* m_staticline5711; - wxStaticBitmap* m_bitmapSpeedFtp; - wxStaticText* m_staticText13611; - wxHyperlinkCtrl* m_hyperlink1711; - wxStaticLine* m_staticline573; - wxPanel* m_panel4111; - wxStaticText* m_staticText123411; - wxSpinCtrl* m_spinCtrlConnectionCountFtp; - wxStaticText* m_staticTextFtpConnectionCountHint; + wxButton* m_buttonChannelCountSftp; wxStaticLine* m_staticline12; wxBoxSizer* bSizerStdButtons; wxButton* m_buttonOkay; @@ -573,7 +574,7 @@ protected: virtual void OnSelectKeyfile( wxCommandEvent& event ) { event.Skip(); } virtual void OnToggleShowPassword( wxCommandEvent& event ) { event.Skip(); } virtual void OnBrowseCloudFolder( wxCommandEvent& event ) { event.Skip(); } - virtual void OnHelpSftpPerformance( wxHyperlinkEvent& event ) { event.Skip(); } + virtual void OnHelpFtpPerformance( wxHyperlinkEvent& event ) { event.Skip(); } virtual void OnDetectServerChannelLimit( wxCommandEvent& event ) { event.Skip(); } virtual void OnOkay( wxCommandEvent& event ) { event.Skip(); } virtual void OnCancel( wxCommandEvent& event ) { event.Skip(); } @@ -1037,6 +1038,25 @@ protected: wxPanel* m_panel41; wxStaticBitmap* m_bitmapLogo; wxStaticLine* m_staticline341; + wxStaticText* m_staticText94; + wxStaticBitmap* m_bitmapHomepage; + wxHyperlinkCtrl* m_hyperlink1; + wxStaticBitmap* m_bitmapForum; + wxHyperlinkCtrl* m_hyperlink21; + wxStaticBitmap* m_bitmapEmail; + wxHyperlinkCtrl* m_hyperlink2; + wxStaticLine* m_staticline3412; + wxPanel* m_panelDonate; + wxPanel* m_panel39; + wxStaticBitmap* m_bitmapDonate; + wxStaticText* m_staticTextDonate; + wxButton* m_buttonDonate; + wxPanel* m_panelThankYou; + wxPanel* m_panel391; + wxStaticBitmap* m_bitmapThanks; + wxStaticText* m_staticTextThanks; + wxStaticText* m_staticTextNoAutoUpdate; + wxButton* m_buttonShowDonationDetails; wxStaticText* m_staticText96; wxHyperlinkCtrl* m_hyperlink11; wxHyperlinkCtrl* m_hyperlink7; @@ -1049,22 +1069,6 @@ protected: wxHyperlinkCtrl* m_hyperlink101; wxHyperlinkCtrl* m_hyperlink18; wxHyperlinkCtrl* m_hyperlink9; - wxPanel* m_panelDonate; - wxPanel* m_panel39; - wxStaticBitmap* m_bitmapDonate; - wxStaticText* m_staticTextDonate; - wxButton* m_buttonDonate; - wxPanel* m_panelThankYou; - wxPanel* m_panel391; - wxStaticBitmap* m_bitmapThanks; - wxStaticText* m_staticTextThanks; - wxButton* m_buttonShowDonationDetails; - wxStaticText* m_staticTextNoAutoUpdate; - wxStaticText* m_staticText94; - wxStaticBitmap* m_bitmapHomepage; - wxHyperlinkCtrl* m_hyperlink1; - wxStaticBitmap* m_bitmapEmail; - wxHyperlinkCtrl* m_hyperlink2; wxStaticLine* m_staticline34; wxStaticText* m_staticText93; wxStaticBitmap* m_bitmapGpl; diff --git a/FreeFileSync/Source/ui/gui_status_handler.cpp b/FreeFileSync/Source/ui/gui_status_handler.cpp index 4a1bebbd..198b5726 100755 --- a/FreeFileSync/Source/ui/gui_status_handler.cpp +++ b/FreeFileSync/Source/ui/gui_status_handler.cpp @@ -126,10 +126,9 @@ void StatusHandlerTemporaryPanel::initNewPhase(int itemsTotal, int64_t bytesTota } -void StatusHandlerTemporaryPanel::reportInfo(const std::wstring& text) +void StatusHandlerTemporaryPanel::logInfo(const std::wstring& msg) { - errorLog_.logMsg(text, MSG_TYPE_INFO); //log first! - StatusHandler::reportInfo(text); //throw X + errorLog_.logMsg(msg, MSG_TYPE_INFO); } @@ -318,7 +317,7 @@ StatusHandlerFloatingDialog::~StatusHandlerFloatingDialog() errorLog_.logMsg(replaceCpy(_("Executing command %x"), L"%x", fmtPath(commandLine)), MSG_TYPE_INFO); //----------------- write results into LastSyncs.log------------------------ - const SummaryInfo summary = + const LogSummary summary = { jobName_, finalStatusMsg, getItemsCurrent(PHASE_SYNCHRONIZING), getBytesCurrent(PHASE_SYNCHRONIZING), @@ -436,19 +435,18 @@ void StatusHandlerFloatingDialog::initNewPhase(int itemsTotal, int64_t bytesTota } -void StatusHandlerFloatingDialog::updateProcessedData(int itemsDelta, int64_t bytesDelta) +void StatusHandlerFloatingDialog::updateDataProcessed(int itemsDelta, int64_t bytesDelta) { - StatusHandler::updateProcessedData(itemsDelta, bytesDelta); + StatusHandler::updateDataProcessed(itemsDelta, bytesDelta); if (progressDlg_) progressDlg_->notifyProgressChange(); //noexcept //note: this method should NOT throw in order to properly allow undoing setting of statistics! } -void StatusHandlerFloatingDialog::reportInfo(const std::wstring& text) +void StatusHandlerFloatingDialog::logInfo(const std::wstring& msg) { - errorLog_.logMsg(text, MSG_TYPE_INFO); //log first! - StatusHandler::reportInfo(text); //throw X + errorLog_.logMsg(msg, MSG_TYPE_INFO); } diff --git a/FreeFileSync/Source/ui/gui_status_handler.h b/FreeFileSync/Source/ui/gui_status_handler.h index c9d9eef3..ae8125f6 100755 --- a/FreeFileSync/Source/ui/gui_status_handler.h +++ b/FreeFileSync/Source/ui/gui_status_handler.h @@ -27,7 +27,7 @@ public: void initNewPhase(int itemsTotal, int64_t bytesTotal, Phase phaseID) override; - void reportInfo (const std::wstring& text) override; + void logInfo (const std::wstring& msg) override; Response reportError (const std::wstring& text, size_t retryNumber) override; void reportFatalError(const std::wstring& errorMessage) override; void reportWarning (const std::wstring& warningMessage, bool& warningActive) override; @@ -64,9 +64,9 @@ public: ~StatusHandlerFloatingDialog(); void initNewPhase (int itemsTotal, int64_t bytesTotal, Phase phaseID) override; - void updateProcessedData(int itemsDelta, int64_t bytesDelta ) override; + void updateDataProcessed(int itemsDelta, int64_t bytesDelta ) override; - void reportInfo (const std::wstring& text ) override; + void logInfo (const std::wstring& msg ) override; Response reportError (const std::wstring& text, size_t retryNumber ) override; void reportFatalError(const std::wstring& errorMessage ) override; void reportWarning (const std::wstring& warningMessage, bool& warningActive) override; diff --git a/FreeFileSync/Source/ui/main_dlg.cpp b/FreeFileSync/Source/ui/main_dlg.cpp index 4a0d4ed1..5e40c4c4 100755 --- a/FreeFileSync/Source/ui/main_dlg.cpp +++ b/FreeFileSync/Source/ui/main_dlg.cpp @@ -47,7 +47,6 @@ #include "../lib/localization.h" #include "../version/version.h" - using namespace zen; using namespace fff; @@ -86,38 +85,6 @@ bool acceptDialogFileDrop(const std::vector& shellItemPaths) } } - -class fff::FolderSelectorImpl : public FolderSelector -{ -public: - FolderSelectorImpl(MainDialog& mainDlg, - wxPanel& dropWindow1, - wxButton& selectFolderButton, - wxButton& selectSftpButton, - FolderHistoryBox& dirpath, - wxStaticText* staticText = nullptr, - wxWindow* dropWindow2 = nullptr) : - FolderSelector(dropWindow1, selectFolderButton, selectSftpButton, dirpath, staticText, dropWindow2), - mainDlg_(mainDlg) {} - - bool shouldSetDroppedPaths(const std::vector& shellItemPaths) override - { - if (acceptDialogFileDrop(shellItemPaths)) - { - assert(!shellItemPaths.empty()); - mainDlg_.loadConfiguration(shellItemPaths); - return false; - } - return true; //=> return true: change directory selection via drag and drop - } - -private: - FolderSelectorImpl (const FolderSelectorImpl&) = delete; - FolderSelectorImpl& operator=(const FolderSelectorImpl&) = delete; - - MainDialog& mainDlg_; -}; - //------------------------------------------------------------------ /* class hierarchy: @@ -128,8 +95,8 @@ private: template<> FolderPairCallback FolderPairPanelGenerated /|\ /|\ - _________|________ ________| - | | | + _________|_________ ________| + | | | FolderPairFirst FolderPairPanel */ @@ -137,33 +104,25 @@ template class fff::FolderPairCallback : public FolderPairPanelBasic //implements callback functionality to MainDialog as imposed by FolderPairPanelBasic { public: - FolderPairCallback(GuiPanel& basicPanel, MainDialog& mainDialog) : + FolderPairCallback(GuiPanel& basicPanel, MainDialog& mainDialog, + + wxPanel& dropWindow1L, + wxButton& selectFolderButtonL, + wxButton& selectSftpButtonL, + FolderHistoryBox& dirpathL, + wxStaticText* staticTextL, + wxWindow* dropWindow2L, + + wxPanel& dropWindow1R, + wxButton& selectFolderButtonR, + wxButton& selectSftpButtonR, + FolderHistoryBox& dirpathR, + wxStaticText* staticTextR, + wxWindow* dropWindow2R) : FolderPairPanelBasic(basicPanel), //pass FolderPairPanelGenerated part... - mainDlg_(mainDialog) {} - -private: - MainConfiguration getMainConfig() const override { return mainDlg_.getConfig().mainCfg; } - wxWindow* getParentWindow() override { return &mainDlg_; } - std::unique_ptr& getFilterCfgOnClipboardRef() override { return mainDlg_.filterCfgOnClipboard_; } - - void onLocalCompCfgChange () override { mainDlg_.applyCompareConfig(false /*setDefaultViewType*/); } - void onLocalSyncCfgChange () override { mainDlg_.applySyncConfig(); } - void onLocalFilterCfgChange() override { mainDlg_.applyFilterConfig(); } //re-apply filter - - MainDialog& mainDlg_; -}; - - -class fff::FolderPairPanel : - public FolderPairPanelGenerated, //FolderPairPanel "owns" FolderPairPanelGenerated! - public FolderPairCallback -{ -public: - FolderPairPanel(wxWindow* parent, MainDialog& mainDialog) : - FolderPairPanelGenerated(parent), - FolderPairCallback(static_cast(*this), mainDialog), //pass FolderPairPanelGenerated part... - folderSelectorLeft_ (mainDialog, *m_panelLeft, *m_buttonSelectFolderLeft, *m_bpButtonSelectAltFolderLeft, *m_folderPathLeft), - folderSelectorRight_(mainDialog, *m_panelRight, *m_buttonSelectFolderRight, *m_bpButtonSelectAltFolderRight, *m_folderPathRight) + mainDlg_(mainDialog), + folderSelectorLeft_ (dropWindow1L, selectFolderButtonL, selectSftpButtonL, dirpathL, staticTextL, dropWindow2L, droppedPathsFilter_, getDeviceParallelOps_, setDeviceParallelOps_), + folderSelectorRight_(dropWindow1R, selectFolderButtonR, selectSftpButtonR, dirpathR, staticTextR, dropWindow2R, droppedPathsFilter_, getDeviceParallelOps_, setDeviceParallelOps_) { folderSelectorLeft_ .setSiblingSelector(&folderSelectorRight_); folderSelectorRight_.setSiblingSelector(&folderSelectorLeft_); @@ -173,75 +132,113 @@ public: folderSelectorLeft_ .Connect(EVENT_ON_FOLDER_MANUAL_EDIT, wxCommandEventHandler(MainDialog::onDirManualCorrection), nullptr, &mainDialog); folderSelectorRight_.Connect(EVENT_ON_FOLDER_MANUAL_EDIT, wxCommandEventHandler(MainDialog::onDirManualCorrection), nullptr, &mainDialog); - - m_bpButtonFolderPairOptions->SetBitmapLabel(getResourceImage(L"button_arrow_down")); } void setValues(const LocalPairConfig& lpc) { - setConfig(lpc.localCmpCfg, lpc.localSyncCfg, lpc.localFilter); + this->setConfig(lpc.localCmpCfg, lpc.localSyncCfg, lpc.localFilter); folderSelectorLeft_ .setPath(lpc.folderPathPhraseLeft); folderSelectorRight_.setPath(lpc.folderPathPhraseRight); } - LocalPairConfig getValues() const { return LocalPairConfig(folderSelectorLeft_.getPath(), folderSelectorRight_.getPath(), getCompConfig(), getSyncConfig(), getFilterConfig()); } + LocalPairConfig getValues() const + { + return LocalPairConfig(folderSelectorLeft_.getPath(), folderSelectorRight_.getPath(), this->getCompConfig(), this->getSyncConfig(), this->getFilterConfig()); + } private: - //support for drag and drop - FolderSelectorImpl folderSelectorLeft_; - FolderSelectorImpl folderSelectorRight_; -}; + MainConfiguration getMainConfig() const override { return mainDlg_.getConfig().mainCfg; } + wxWindow* getParentWindow() override { return &mainDlg_; } + std::unique_ptr& getFilterCfgOnClipboardRef() override { return mainDlg_.filterCfgOnClipboard_; } + void onLocalCompCfgChange () override { mainDlg_.applyCompareConfig(false /*setDefaultViewType*/); } + void onLocalSyncCfgChange () override { mainDlg_.applySyncConfig (); } + void onLocalFilterCfgChange() override { mainDlg_.applyFilterConfig(); } //re-apply filter -class fff::FolderPairFirst : public FolderPairCallback -{ -public: - FolderPairFirst(MainDialog& mainDialog) : - FolderPairCallback(mainDialog, mainDialog), - - //prepare drag & drop - folderSelectorLeft_(mainDialog, - *mainDialog.m_panelTopLeft, - *mainDialog.m_buttonSelectFolderLeft, - *mainDialog.m_bpButtonSelectAltFolderLeft, - *mainDialog.m_folderPathLeft, - mainDialog.m_staticTextResolvedPathL, - &mainDialog.m_gridMainL->getMainWin()), - folderSelectorRight_(mainDialog, - *mainDialog.m_panelTopRight, - *mainDialog.m_buttonSelectFolderRight, - *mainDialog.m_bpButtonSelectAltFolderRight, - *mainDialog.m_folderPathRight, - mainDialog.m_staticTextResolvedPathR, - &mainDialog.m_gridMainR->getMainWin()) + const std::function& shellItemPaths)> droppedPathsFilter_ = [&](const std::vector& shellItemPaths) { - folderSelectorLeft_ .setSiblingSelector(&folderSelectorRight_); - folderSelectorRight_.setSiblingSelector(&folderSelectorLeft_); - - folderSelectorLeft_ .Connect(EVENT_ON_FOLDER_SELECTED, wxCommandEventHandler(MainDialog::onDirSelected), nullptr, &mainDialog); - folderSelectorRight_.Connect(EVENT_ON_FOLDER_SELECTED, wxCommandEventHandler(MainDialog::onDirSelected), nullptr, &mainDialog); + if (acceptDialogFileDrop(shellItemPaths)) + { + assert(!shellItemPaths.empty()); + mainDlg_.loadConfiguration(shellItemPaths); + return false; //don't set dropped paths + } + return true; //do set dropped paths + }; - folderSelectorLeft_ .Connect(EVENT_ON_FOLDER_MANUAL_EDIT, wxCommandEventHandler(MainDialog::onDirManualCorrection), nullptr, &mainDialog); - folderSelectorRight_.Connect(EVENT_ON_FOLDER_MANUAL_EDIT, wxCommandEventHandler(MainDialog::onDirManualCorrection), nullptr, &mainDialog); + const std::function getDeviceParallelOps_ = [&](const Zstring& folderPathPhrase) + { + //follow deviceParallelOps editing-behavior from sync_cfg.cpp: + const auto& deviceParallelOps = mainDlg_.currentCfg_.mainCfg.deviceParallelOps; + const AbstractPath rootPath = AFS::getPathComponents(createAbstractPath(folderPathPhrase)).rootPath; - mainDialog.m_panelTopLeft ->Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(MainDialog::onTopFolderPairKeyEvent), nullptr, &mainDialog); - mainDialog.m_panelTopCenter->Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(MainDialog::onTopFolderPairKeyEvent), nullptr, &mainDialog); - mainDialog.m_panelTopRight ->Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(MainDialog::onTopFolderPairKeyEvent), nullptr, &mainDialog); - } + auto itParOps = deviceParallelOps.find(rootPath); + return std::max(itParOps != deviceParallelOps.end() ? static_cast(itParOps->second) : 1, 1); + }; - void setValues(const LocalPairConfig& lpc) + const std::function setDeviceParallelOps_ = [&](const Zstring& folderPathPhrase, size_t parallelOps) { - setConfig(lpc.localCmpCfg, lpc.localSyncCfg, lpc.localFilter); - folderSelectorLeft_ .setPath(lpc.folderPathPhraseLeft); - folderSelectorRight_.setPath(lpc.folderPathPhraseRight); - } + auto& deviceParallelOps = mainDlg_.currentCfg_.mainCfg.deviceParallelOps; + const AbstractPath rootPath = AFS::getPathComponents(createAbstractPath(folderPathPhrase)).rootPath; + if (!AFS::isNullPath(rootPath)) + { + if (parallelOps > 1) + deviceParallelOps[rootPath] = parallelOps; + else + deviceParallelOps.erase(rootPath); - LocalPairConfig getValues() const { return LocalPairConfig(folderSelectorLeft_.getPath(), folderSelectorRight_.getPath(), getCompConfig(), getSyncConfig(), getFilterConfig()); } + mainDlg_.updateUnsavedCfgStatus(); + } + }; -private: - //support for drag and drop - FolderSelectorImpl folderSelectorLeft_; - FolderSelectorImpl folderSelectorRight_; + MainDialog& mainDlg_; + FolderSelector folderSelectorLeft_; + FolderSelector folderSelectorRight_; +}; + + +class fff::FolderPairPanel : + public FolderPairPanelGenerated, //FolderPairPanel "owns" FolderPairPanelGenerated! + public FolderPairCallback +{ +public: + FolderPairPanel(wxWindow* parent, MainDialog& mainDialog) : + FolderPairPanelGenerated(parent), + FolderPairCallback(static_cast(*this), mainDialog, + + *m_panelLeft, + *m_buttonSelectFolderLeft, + *m_bpButtonSelectAltFolderLeft, + *m_folderPathLeft, + nullptr /*staticText*/, nullptr /*dropWindow2*/, + + *m_panelRight, + *m_buttonSelectFolderRight, + *m_bpButtonSelectAltFolderRight, + *m_folderPathRight, + nullptr /*staticText*/, nullptr /*dropWindow2*/) {} +}; + + +class fff::FolderPairFirst : public FolderPairCallback +{ +public: + FolderPairFirst(MainDialog& mainDialog) : + FolderPairCallback(mainDialog, mainDialog, + + *mainDialog.m_panelTopLeft, + *mainDialog.m_buttonSelectFolderLeft, + *mainDialog.m_bpButtonSelectAltFolderLeft, + *mainDialog.m_folderPathLeft, + mainDialog.m_staticTextResolvedPathL, + &mainDialog.m_gridMainL->getMainWin(), + + *mainDialog.m_panelTopRight, + *mainDialog.m_buttonSelectFolderRight, + *mainDialog.m_bpButtonSelectAltFolderRight, + *mainDialog.m_folderPathRight, + mainDialog.m_staticTextResolvedPathR, + &mainDialog.m_gridMainR->getMainWin()) {} }; @@ -675,6 +672,10 @@ MainDialog::MainDialog(const Zstring& globalConfigFilePath, //calculate witdh of folder pair manually (if scrollbars are visible) m_panelTopLeft->Connect(wxEVT_SIZE, wxEventHandler(MainDialog::OnResizeLeftFolderWidth), nullptr, this); + m_panelTopLeft ->Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(MainDialog::onTopFolderPairKeyEvent), nullptr, this); + m_panelTopCenter->Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(MainDialog::onTopFolderPairKeyEvent), nullptr, this); + m_panelTopRight ->Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(MainDialog::onTopFolderPairKeyEvent), nullptr, this); + //dynamically change sizer direction depending on size m_panelTopButtons->Connect(wxEVT_SIZE, wxEventHandler(MainDialog::OnResizeTopButtonPanel), nullptr, this); m_panelConfig ->Connect(wxEVT_SIZE, wxEventHandler(MainDialog::OnResizeConfigPanel), nullptr, this); @@ -948,7 +949,7 @@ void MainDialog::setGlobalCfgOnInit(const XmlGlobalSettings& globalSettings) filegrid::setItemPathForm(*m_gridMainL, globalSettings.gui.mainDlg.itemPathFormatLeftGrid); filegrid::setItemPathForm(*m_gridMainR, globalSettings.gui.mainDlg.itemPathFormatRightGrid); - //------------------------------------------------------------------------------------------------ + //-------------------------------------------------------------------------------- m_checkBoxMatchCase->SetValue(globalCfg_.gui.mainDlg.textSearchRespectCase); //wxAuiManager erroneously loads panel captions, we don't want that @@ -962,7 +963,7 @@ void MainDialog::setGlobalCfgOnInit(const XmlGlobalSettings& globalSettings) //restore original captions for (const auto& item : captionNameMap) auiMgr_.GetPane(item.second).Caption(item.first); - //------------------------------------------------------------------------------------------------ + //-------------------------------------------------------------------------------- //if MainDialog::onQueryEndSession() is called while comparison is active, this panel is saved and restored as "visible" auiMgr_.GetPane(compareStatus_->getAsWindow()).Hide(); @@ -978,7 +979,8 @@ void MainDialog::setGlobalCfgOnInit(const XmlGlobalSettings& globalSettings) XmlGlobalSettings MainDialog::getGlobalCfgBeforeExit() { Freeze(); //no need to Thaw() again!! - + recalcMaxFolderPairsVisible(); + //-------------------------------------------------------------------------------- XmlGlobalSettings globalSettings = globalCfg_; globalSettings.programLanguage = getLanguage(); @@ -2611,11 +2613,11 @@ void MainDialog::OnCompSettingsContext(wxEvent& event) auto setVariant = [&](CompareVariant var) { - currentCfg_.mainCfg.cmpConfig.compareVar = var; + currentCfg_.mainCfg.cmpCfg.compareVar = var; applyCompareConfig(true /*setDefaultViewType*/); }; - const CompareVariant activeCmpVar = getConfig().mainCfg.cmpConfig.compareVar; + const CompareVariant activeCmpVar = getConfig().mainCfg.cmpCfg.compareVar; auto addVariantItem = [&](CompareVariant cmpVar, const wchar_t* iconName) { @@ -3339,10 +3341,11 @@ void MainDialog::updateGuiDelayedIf(bool condition) void MainDialog::showConfigDialog(SyncConfigPanel panelToShow, int localPairIndexToShow) { GlobalPairConfig globalPairCfg; - globalPairCfg.cmpConfig = currentCfg_.mainCfg.cmpConfig; - globalPairCfg.syncCfg = currentCfg_.mainCfg.syncCfg; - globalPairCfg.filter = currentCfg_.mainCfg.globalFilter; + globalPairCfg.cmpCfg = currentCfg_.mainCfg.cmpCfg; + globalPairCfg.syncCfg = currentCfg_.mainCfg.syncCfg; + globalPairCfg.filter = currentCfg_.mainCfg.globalFilter; + globalPairCfg.miscCfg.deviceParallelOps = currentCfg_.mainCfg.deviceParallelOps; globalPairCfg.miscCfg.ignoreErrors = currentCfg_.mainCfg.ignoreErrors; globalPairCfg.miscCfg.automaticRetryCount = currentCfg_.mainCfg.automaticRetryCount; globalPairCfg.miscCfg.automaticRetryDelay = currentCfg_.mainCfg.automaticRetryDelay; @@ -3352,28 +3355,27 @@ void MainDialog::showConfigDialog(SyncConfigPanel panelToShow, int localPairInde //don't recalculate value but consider current screen status!!! //e.g. it's possible that the first folder pair local config is shown with all config initial if user just removed local config via mouse context menu! - const bool showLocalCfg = m_bpButtonLocalCompCfg->IsShown(); + const bool showMultipleCfgs = m_bpButtonLocalCompCfg->IsShown(); //harmonize with MainDialog::updateGuiForFolderPair()! + assert(showMultipleCfgs || localPairIndexToShow == -1); assert(m_bpButtonLocalCompCfg->IsShown() == m_bpButtonLocalSyncCfg->IsShown() && - m_bpButtonLocalCompCfg->IsShown() == m_bpButtonLocalFilter->IsShown()); + m_bpButtonLocalCompCfg->IsShown() == m_bpButtonLocalFilter ->IsShown()); - std::vector localCfgs; - if (showLocalCfg) - { - localCfgs.push_back(firstFolderPair_->getValues()); + std::vector localCfgs; //showSyncConfigDlg() needs *all* folder pairs for deviceParallelOps update + localCfgs.push_back(firstFolderPair_->getValues()); - for (const FolderPairPanel* panel : additionalFolderPairs_) - localCfgs.push_back(panel->getValues()); - } + for (const FolderPairPanel* panel : additionalFolderPairs_) + localCfgs.push_back(panel->getValues()); - //------------------------------------------------ + //------------------------------------------------------------------------------------ const GlobalPairConfig globalPairCfgOld = globalPairCfg; const std::vector localPairCfgOld = localCfgs; if (showSyncConfigDlg(this, panelToShow, - localPairIndexToShow, + showMultipleCfgs ? localPairIndexToShow : -1, + showMultipleCfgs, globalPairCfg, localCfgs, globalCfg_.gui.commandHistItemsMax) != ReturnSyncConfig::BUTTON_OKAY) @@ -3381,10 +3383,11 @@ void MainDialog::showConfigDialog(SyncConfigPanel panelToShow, int localPairInde assert(localCfgs.size() == localPairCfgOld.size()); - currentCfg_.mainCfg.cmpConfig = globalPairCfg.cmpConfig; + currentCfg_.mainCfg.cmpCfg = globalPairCfg.cmpCfg; currentCfg_.mainCfg.syncCfg = globalPairCfg.syncCfg; currentCfg_.mainCfg.globalFilter = globalPairCfg.filter; + currentCfg_.mainCfg.deviceParallelOps = globalPairCfg.miscCfg.deviceParallelOps; currentCfg_.mainCfg.ignoreErrors = globalPairCfg.miscCfg.ignoreErrors; currentCfg_.mainCfg.automaticRetryCount = globalPairCfg.miscCfg.automaticRetryCount; currentCfg_.mainCfg.automaticRetryDelay = globalPairCfg.miscCfg.automaticRetryDelay; @@ -3392,17 +3395,14 @@ void MainDialog::showConfigDialog(SyncConfigPanel panelToShow, int localPairInde currentCfg_.mainCfg.postSyncCondition = globalPairCfg.miscCfg.postSyncCondition; globalCfg_.gui.commandHistory = globalPairCfg.miscCfg.commandHistory; - if (showLocalCfg) - { - firstFolderPair_->setValues(localCfgs[0]); + firstFolderPair_->setValues(localCfgs[0]); - for (size_t i = 1; i < localCfgs.size(); ++i) - additionalFolderPairs_[i - 1]->setValues(localCfgs[i]); - } + for (size_t i = 1; i < localCfgs.size(); ++i) + additionalFolderPairs_[i - 1]->setValues(localCfgs[i]); - //------------------------------------------------ + //------------------------------------------------------------------------------------ - const bool cmpConfigChanged = globalPairCfg.cmpConfig != globalPairCfgOld.cmpConfig || [&] + const bool cmpConfigChanged = globalPairCfg.cmpCfg != globalPairCfgOld.cmpCfg || [&] { for (size_t i = 0; i < localCfgs.size(); ++i) if (localCfgs[i].localCmpCfg != localPairCfgOld[i].localCmpCfg) @@ -3426,18 +3426,18 @@ void MainDialog::showConfigDialog(SyncConfigPanel panelToShow, int localPairInde return false; }(); - const bool miscConfigChanged = globalPairCfg.miscCfg.ignoreErrors != globalPairCfgOld.miscCfg.ignoreErrors || + const bool miscConfigChanged = globalPairCfg.miscCfg.deviceParallelOps != globalPairCfgOld.miscCfg.deviceParallelOps || + globalPairCfg.miscCfg.ignoreErrors != globalPairCfgOld.miscCfg.ignoreErrors || globalPairCfg.miscCfg.automaticRetryCount != globalPairCfgOld.miscCfg.automaticRetryCount || globalPairCfg.miscCfg.automaticRetryDelay != globalPairCfgOld.miscCfg.automaticRetryDelay || globalPairCfg.miscCfg.postSyncCommand != globalPairCfgOld.miscCfg.postSyncCommand || globalPairCfg.miscCfg.postSyncCondition != globalPairCfgOld.miscCfg.postSyncCondition; - //globalPairCfg.miscCfg.commandHistory != globalPairCfgOld.miscCfg.commandHistory; - - //------------------------------------------------ + /**/ //globalPairCfg.miscCfg.commandHistory != globalPairCfgOld.miscCfg.commandHistory; + //------------------------------------------------------------------------------------ if (cmpConfigChanged) { - const bool setDefaultViewType = globalPairCfg.cmpConfig.compareVar != globalPairCfgOld.cmpConfig.compareVar; + const bool setDefaultViewType = globalPairCfg.cmpCfg.compareVar != globalPairCfgOld.cmpCfg.compareVar; applyCompareConfig(setDefaultViewType); } @@ -3659,12 +3659,16 @@ void MainDialog::OnCompare(wxCommandEvent& event) auto app = wxTheApp; //fix lambda/wxWigets/VC fuck up ZEN_ON_SCOPE_EXIT(app->Yield(); enableAllElements()); //ui update before enabling buttons again: prevent strange behaviour of delayed button clicks + const auto& guiCfg = getConfig(); + + const std::map& deviceParallelOps = guiCfg.mainCfg.deviceParallelOps; + try { //handle status display and error messages StatusHandlerTemporaryPanel statusHandler(*this); - const std::vector cmpConfig = extractCompareCfg(getConfig().mainCfg); + const std::vector fpCfgList = extractCompareCfg(guiCfg.mainCfg); //GUI mode: place directory locks on directories isolated(!) during both comparison and synchronization std::unique_ptr dirLocks; @@ -3677,7 +3681,8 @@ void MainDialog::OnCompare(wxCommandEvent& event) globalCfg_.folderAccessTimeout, globalCfg_.createLockFile, dirLocks, - cmpConfig, + fpCfgList, + deviceParallelOps, statusHandler); //throw AbortProcess } catch (AbortProcess&) @@ -3804,7 +3809,7 @@ void MainDialog::applyCompareConfig(bool setDefaultViewType) //convenience: change sync view if (setDefaultViewType) - switch (currentCfg_.mainCfg.cmpConfig.compareVar) + switch (currentCfg_.mainCfg.cmpCfg.compareVar) { case CompareVariant::TIME_SIZE: case CompareVariant::SIZE: @@ -3830,13 +3835,15 @@ void MainDialog::OnStartSync(wxCommandEvent& event) return; } + const auto& guiCfg = getConfig(); + //show sync preview/confirmation dialog if (globalCfg_.confirmDlgs.confirmSyncStart) { bool dontShowAgain = false; if (showSyncConfirmationDlg(this, - getSyncVariantName(getConfig().mainCfg), + getSyncVariantName(guiCfg.mainCfg), SyncStatistics(folderCmp_), dontShowAgain) != ReturnSmallDlg::BUTTON_OKAY) return; @@ -3844,6 +3851,8 @@ void MainDialog::OnStartSync(wxCommandEvent& event) globalCfg_.confirmDlgs.confirmSyncStart = !dontShowAgain; } + const std::map& deviceParallelOps = guiCfg.mainCfg.deviceParallelOps; + bool exitAfterSync = false; try { @@ -3852,8 +3861,6 @@ void MainDialog::OnStartSync(wxCommandEvent& event) //PERF_START; const Zstring activeCfgFilePath = activeConfigFiles_.size() == 1 && !equalFilePath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstring(); - const auto& guiCfg = getConfig(); - disableAllElements(false); //StatusHandlerFloatingDialog will internally process Window messages, so avoid unexpected callbacks! ZEN_ON_SCOPE_EXIT(enableAllElements()); @@ -3909,6 +3916,7 @@ void MainDialog::OnStartSync(wxCommandEvent& event) globalCfg_.folderAccessTimeout, syncProcessCfg, folderCmp_, + deviceParallelOps, globalCfg_.warnDlgs, statusHandler); @@ -4497,6 +4505,8 @@ void MainDialog::onAddFolderPairKeyEvent(wxKeyEvent& event) void MainDialog::updateGuiForFolderPair() { + recalcMaxFolderPairsVisible(); + //adapt delete top folder pair button m_bpButtonRemovePair->Show(!additionalFolderPairs_.empty()); m_panelTopLeft->Layout(); @@ -4516,24 +4526,19 @@ void MainDialog::updateGuiForFolderPair() //update sub-panel sizes for calculations below!!! m_panelTopCenter->GetSizer()->SetSizeHints(m_panelTopCenter); //~=Fit() + SetMinSize() - int addPairMinimalHeight = 0; - int addPairOptimalHeight = 0; - if (!additionalFolderPairs_.empty()) - { - const int pairHeight = additionalFolderPairs_[0]->GetSize().GetHeight(); - addPairMinimalHeight = std::min(1.5, additionalFolderPairs_.size()) * pairHeight; //have 1.5 * height indicate that more folders are there - addPairOptimalHeight = std::min(globalCfg_.gui.mainDlg.maxFolderPairsVisible - 1 + 0.5, //subtract first/main folder pair and add 0.5 to indicate additional folders - additionalFolderPairs_.size()) * pairHeight; + const int firstPairHeight = std::max(m_panelDirectoryPairs->ClientToWindowSize(m_panelTopLeft ->GetSize()).y, //include m_panelDirectoryPairs window borders! + m_panelDirectoryPairs->ClientToWindowSize(m_panelTopCenter->GetSize()).y); // + const int addPairHeight = !additionalFolderPairs_.empty() ? additionalFolderPairs_[0]->GetSize().y : 0; - addPairOptimalHeight = std::max(addPairOptimalHeight, addPairMinimalHeight); //implicitly handle corrupted values for "maxFolderPairsVisible" - } + const double addPairCountMax = std::max(globalCfg_.gui.mainDlg.maxFolderPairsVisible - 1 + 0.5, 1.5); - const int firstPairHeight = std::max(m_panelDirectoryPairs->ClientToWindowSize(m_panelTopLeft ->GetSize()).GetHeight(), //include m_panelDirectoryPairs window borders! - m_panelDirectoryPairs->ClientToWindowSize(m_panelTopCenter->GetSize()).GetHeight()); // + const double addPairCountMin = std::min(1.5, additionalFolderPairs_.size()); //add 0.5 to indicate additional folders + const double addPairCountOpt = std::min(addPairCountMax, additionalFolderPairs_.size()); // + addPairCountLast_ = addPairCountOpt; //######################################################################################################################## //wxAUI hack: set minimum height to desired value, then call wxAuiPaneInfo::Fixed() to apply it - auiMgr_.GetPane(m_panelDirectoryPairs).MinSize(-1, firstPairHeight + addPairOptimalHeight); + auiMgr_.GetPane(m_panelDirectoryPairs).MinSize(-1, firstPairHeight + addPairCountOpt * addPairHeight); auiMgr_.GetPane(m_panelDirectoryPairs).Fixed(); auiMgr_.Update(); @@ -4543,7 +4548,7 @@ void MainDialog::updateGuiForFolderPair() //######################################################################################################################## //make sure user cannot fully shrink additional folder pairs - auiMgr_.GetPane(m_panelDirectoryPairs).MinSize(-1, firstPairHeight + addPairMinimalHeight); + auiMgr_.GetPane(m_panelDirectoryPairs).MinSize(-1, firstPairHeight + addPairCountMin * addPairHeight); auiMgr_.Update(); //it seems there is no GetSizer()->SetSizeHints(this)/Fit() required due to wxAui "magic" @@ -4551,6 +4556,26 @@ void MainDialog::updateGuiForFolderPair() } +void MainDialog::recalcMaxFolderPairsVisible() +{ + const int firstPairHeight = std::max(m_panelDirectoryPairs->ClientToWindowSize(m_panelTopLeft ->GetSize()).y, //include m_panelDirectoryPairs window borders! + m_panelDirectoryPairs->ClientToWindowSize(m_panelTopCenter->GetSize()).y); // + const int addPairHeight = !additionalFolderPairs_.empty() ? additionalFolderPairs_[0]->GetSize().y : + m_bpButtonAddPair->GetSize().y; //an educated guess + assert(addPairHeight > 0); + + if (addPairCountLast_ && addPairHeight > 0) + { + const double addPairCountCurrent = (m_panelDirectoryPairs->GetSize().y - firstPairHeight) / (1.0 * addPairHeight); //include m_panelDirectoryPairs window borders! + + if (numeric::dist(addPairCountCurrent, *addPairCountLast_) > 0.4) //=> presumely changed by user! + { + globalCfg_.gui.mainDlg.maxFolderPairsVisible = numeric::round(addPairCountCurrent) + 1; + } + } +} + + void MainDialog::insertAddFolderPair(const std::vector& newPairs, size_t pos) { assert(pos <= additionalFolderPairs_.size() && additionalFolderPairs_.size() == bSizerAddFolderPairs->GetItemCount()); @@ -4564,6 +4589,8 @@ void MainDialog::insertAddFolderPair(const std::vector& newPair newPair->m_folderPathLeft ->init(folderHistoryLeft_); newPair->m_folderPathRight->init(folderHistoryRight_); + newPair->m_bpButtonFolderPairOptions->SetBitmapLabel(getResourceImage(L"button_arrow_down")); + //set width of left folder panel const int width = m_panelTopLeft->GetSize().GetWidth(); newPair->m_panelLeft->SetMinSize(wxSize(width, -1)); @@ -4650,7 +4677,6 @@ void MainDialog::setAddFolderPairs(const std::vector& newPairs) additionalFolderPairs_.clear(); bSizerAddFolderPairs->Clear(true); - //updateGuiForFolderPair(); -> already called in insertAddFolderPair() insertAddFolderPair(newPairs, 0); } diff --git a/FreeFileSync/Source/ui/main_dlg.h b/FreeFileSync/Source/ui/main_dlg.h index 40496410..e4f4be05 100755 --- a/FreeFileSync/Source/ui/main_dlg.h +++ b/FreeFileSync/Source/ui/main_dlg.h @@ -26,7 +26,6 @@ namespace fff class FolderPairFirst; class FolderPairPanel; class CompareProgressDialog; -class FolderSelectorImpl; template class FolderPairCallback; class PanelMoveWindow; @@ -64,7 +63,6 @@ private: friend class StatusHandlerFloatingDialog; friend class FolderPairFirst; friend class FolderPairPanel; - friend class FolderSelectorImpl; template friend class FolderPairCallback; friend class PanelMoveWindow; @@ -98,6 +96,7 @@ private: void setAddFolderPairs(const std::vector& newPairs); void updateGuiForFolderPair(); //helper method: add usability by showing/hiding buttons related to folder pairs + void recalcMaxFolderPairsVisible(); //main method for putting gridDataView on UI: updates data respecting current view settings void updateGui(); //kitchen-sink update @@ -307,6 +306,8 @@ private: //folder pairs: std::unique_ptr firstFolderPair_; //always bound!!! std::vector additionalFolderPairs_; //additional pairs to the first pair + + zen::Opt addPairCountLast_; //------------------------------------- //*********************************************** diff --git a/FreeFileSync/Source/ui/small_dlgs.cpp b/FreeFileSync/Source/ui/small_dlgs.cpp index 1fd6229b..29365b27 100755 --- a/FreeFileSync/Source/ui/small_dlgs.cpp +++ b/FreeFileSync/Source/ui/small_dlgs.cpp @@ -33,6 +33,7 @@ + using namespace zen; using namespace fff; @@ -57,6 +58,7 @@ AboutDlg::AboutDlg(wxWindow* parent) : AboutDlgGenerated(parent) assert(m_buttonClose->GetId() == wxID_OK); //we cannot use wxID_CLOSE else Esc key won't work: yet another wxWidgets bug?? m_bitmapHomepage->SetBitmap(getResourceImage(L"website")); + m_bitmapForum ->SetBitmap(getResourceImage(L"forum")); m_bitmapEmail ->SetBitmap(getResourceImage(L"email")); m_bitmapGpl ->SetBitmap(getResourceImage(L"gpl")); @@ -215,7 +217,10 @@ CopyToDialog::CopyToDialog(wxWindow* parent, m_bitmapCopyTo->SetBitmap(getResourceImage(L"copy_to")); - targetFolder = std::make_unique(*this, *m_buttonSelectTargetFolder, *m_bpButtonSelectAltTargetFolder, *m_targetFolderPath, nullptr /*staticText*/, nullptr /*wxWindow*/); + targetFolder = std::make_unique(*this, *m_buttonSelectTargetFolder, *m_bpButtonSelectAltTargetFolder, *m_targetFolderPath, nullptr /*staticText*/, nullptr /*wxWindow*/, + nullptr /*droppedPathsFilter*/, + [](const Zstring& folderPathPhrase) { return 1; } /*getDeviceParallelOps*/, + nullptr /*setDeviceParallelOps*/); m_targetFolderPath->init(folderHistory_); diff --git a/FreeFileSync/Source/ui/sync_cfg.cpp b/FreeFileSync/Source/ui/sync_cfg.cpp index 38aa8940..101e2b8b 100755 --- a/FreeFileSync/Source/ui/sync_cfg.cpp +++ b/FreeFileSync/Source/ui/sync_cfg.cpp @@ -25,13 +25,14 @@ #include "../fs/concrete.h" + using namespace zen; using namespace fff; namespace { -const int CFG_DESCRIPTION_WIDTH_DIP = 250; +const int CFG_DESCRIPTION_WIDTH_DIP = 230; class ConfigDialog : public ConfigDlgGenerated @@ -39,7 +40,7 @@ class ConfigDialog : public ConfigDlgGenerated public: ConfigDialog(wxWindow* parent, SyncConfigPanel panelToShow, - int localPairIndexToShow, + int localPairIndexToShow, bool showMultipleCfgs, GlobalPairConfig& globalPairCfg, std::vector& localPairConfig, size_t commandHistItemsMax); @@ -66,6 +67,8 @@ private: //------------- comparison panel ---------------------- void OnHelpComparisonSettings(wxHyperlinkEvent& event) override { displayHelpEntry(L"comparison-settings", this); } void OnHelpTimeShift (wxHyperlinkEvent& event) override { displayHelpEntry(L"daylight-saving-time", this); } + void OnHelpPerformance (wxHyperlinkEvent& event) override { displayHelpEntry(L"performance", this); } + warn_static("write performance help entry") void OnToggleLocalCompSettings(wxCommandEvent& event) override { updateCompGui(); updateSyncGui(); /*affects sync settings, too!*/ } void OnCompByTimeSize (wxCommandEvent& event) override { localCmpVar_ = CompareVariant::TIME_SIZE; updateCompGui(); updateSyncGui(); } // @@ -140,11 +143,16 @@ private: void OnToggleIgnoreErrors(wxCommandEvent& event) override { updateMiscGui(); } void OnToggleAutoRetry (wxCommandEvent& event) override { updateMiscGui(); } + size_t getParallelOpsForVersioning() const; + MiscSyncConfig getMiscSyncOptions() const; void setMiscSyncOptions(const MiscSyncConfig& miscCfg); void updateMiscGui(); + std::set devicePathsForEdit_; //helper data for deviceParallelOps + std::map deviceParallelOps_; // + //parameters with ownership NOT within GUI controls! DirectionConfig directionCfg_; DeletionPolicy handleDeletion_ = DeletionPolicy::RECYCLER; //use Recycler, delete permanently or move to user-defined location @@ -168,6 +176,9 @@ private: int selectedPairIndexToShow_ = EMPTY_PAIR_INDEX_SELECTED; static const int EMPTY_PAIR_INDEX_SELECTED = -2; + const bool showMultipleCfgs_; + const bool perfPanelActive_; + const size_t commandHistItemsMax_; }; @@ -209,17 +220,21 @@ std::wstring getSyncVariantDescription(DirectionConfig::Variant var) ConfigDialog::ConfigDialog(wxWindow* parent, SyncConfigPanel panelToShow, - int localPairIndexToShow, + int localPairIndexToShow, bool showMultipleCfgs, GlobalPairConfig& globalPairCfg, std::vector& localPairConfig, size_t commandHistItemsMax) : ConfigDlgGenerated(parent), - versioningFolder_(*m_panelVersioning, *m_buttonSelectVersioningFolder, *m_bpButtonSelectAltFolder, *m_versioningFolderPath, nullptr /*staticText*/, nullptr /*dropWindow2*/), - globalPairCfgOut_(globalPairCfg), - localPairCfgOut_(localPairConfig), - globalPairCfg_(globalPairCfg), - localPairCfg_(localPairConfig), - commandHistItemsMax_(commandHistItemsMax) + versioningFolder_(*m_panelVersioning, *m_buttonSelectVersioningFolder, *m_bpButtonSelectAltFolder, *m_versioningFolderPath, nullptr /*staticText*/, nullptr /*dropWindow2*/, + nullptr /*droppedPathsFilter*/, + [this](const Zstring& /*folderPathPhrase*/) { return getParallelOpsForVersioning(); }, nullptr /*setDeviceParallelOps*/), + globalPairCfgOut_(globalPairCfg), + localPairCfgOut_(localPairConfig), + globalPairCfg_(globalPairCfg), + localPairCfg_(localPairConfig), + showMultipleCfgs_(showMultipleCfgs), + perfPanelActive_(true), +commandHistItemsMax_(commandHistItemsMax) { setStandardButtonLayout(*bSizerStdButtons, StdButtons().setAffirmative(m_buttonOkay).setCancel(m_buttonCancel)); @@ -261,6 +276,11 @@ ConfigDialog::ConfigDialog(wxWindow* parent, m_staticTextCompVarDescription->SetMinSize(wxSize(fastFromDIP(CFG_DESCRIPTION_WIDTH_DIP), -1)); + m_scrolledWindowPerf->SetMinSize(wxSize(fastFromDIP(200), -1)); + m_bitmapPerf->SetBitmap(perfPanelActive_ ? getResourceImage(L"speed") : greyScale(getResourceImage(L"speed"))); + m_panelPerfHeader ->Enable(perfPanelActive_); + m_staticTextPerfParallelOps->Enable(perfPanelActive_); + //------------- filter panel -------------------------- m_textCtrlInclude->SetMinSize(wxSize(fastFromDIP(280), -1)); @@ -269,7 +289,7 @@ ConfigDialog::ConfigDialog(wxWindow* parent, m_textCtrlInclude->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(ConfigDialog::onFilterKeyEvent), nullptr, this); m_textCtrlExclude->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(ConfigDialog::onFilterKeyEvent), nullptr, this); - m_staticTextFilterDescr->Wrap(fastFromDIP(590)); + m_staticTextFilterDescr->Wrap(fastFromDIP(450)); enumTimeDescr_. add(UnitTime::NONE, L"(" + _("None") + L")"). //meta options should be enclosed in parentheses @@ -347,7 +367,7 @@ ConfigDialog::ConfigDialog(wxWindow* parent, m_listBoxFolderPair->Append(L" " + fpName); } - if (localPairConfig.empty()) + if (!showMultipleCfgs) { m_listBoxFolderPair->Hide(); m_staticTextFolderPairLabel->Hide(); @@ -517,6 +537,7 @@ Opt ConfigDialog::getCompConfig() const compCfg.compareVar = localCmpVar_; compCfg.handleSymlinks = !m_checkBoxSymlinksInclude->GetValue() ? SymLinkHandling::EXCLUDE : m_radioBtnSymlinksDirect->GetValue() ? SymLinkHandling::DIRECT : SymLinkHandling::FOLLOW; compCfg.ignoreTimeShiftMinutes = fromTimeShiftPhrase(copyStringTo(m_textCtrlTimeShift->GetValue())); + return compCfg; } @@ -526,7 +547,7 @@ void ConfigDialog::setCompConfig(const CompConfig* compCfg) m_checkBoxUseLocalCmpOptions->SetValue(compCfg); //when local settings are inactive, display (current) global settings instead: - const CompConfig tmpCfg = compCfg ? *compCfg : globalPairCfg_.cmpConfig; + const CompConfig tmpCfg = compCfg ? *compCfg : globalPairCfg_.cmpCfg; localCmpVar_ = tmpCfg.compareVar; @@ -943,7 +964,7 @@ void ConfigDialog::updateSyncGui() setBitmap(*m_bitmapDatabase, getResourceImage(L"database")); else { - const CompareVariant activeCmpVar = m_checkBoxUseLocalCmpOptions->GetValue() ? localCmpVar_ : globalPairCfg_.cmpConfig.compareVar; + const CompareVariant activeCmpVar = m_checkBoxUseLocalCmpOptions->GetValue() ? localCmpVar_ : globalPairCfg_.cmpCfg.compareVar; m_bitmapLeftNewer ->Show(activeCmpVar == CompareVariant::TIME_SIZE); m_bpButtonLeftNewer ->Show(activeCmpVar == CompareVariant::TIME_SIZE); @@ -1046,13 +1067,63 @@ void ConfigDialog::updateSyncGui() } +size_t ConfigDialog::getParallelOpsForVersioning() const +{ + assert(selectedPairIndexToShow_ == -1 || makeUnsigned(selectedPairIndexToShow_) < localPairCfg_.size()); + + const auto& deviceParallelOps = selectedPairIndexToShow_ < 0 ? getMiscSyncOptions().deviceParallelOps : globalPairCfg_.miscCfg.deviceParallelOps; //ternary-WTF! + + auto getParallelOps = [&](const Zstring& folderPathPhrase) + { + const AbstractPath rootPath = AFS::getPathComponents(createAbstractPath(folderPathPhrase)).rootPath; + auto itParOps = deviceParallelOps.find(rootPath); + return std::max(itParOps != deviceParallelOps.end() ? itParOps->second : 1, 1); + }; + //follow deviceParallelOps editing-behavior from synchronization.cpp: + //=> parallelOps used for versioning == number used for folder pair! + auto getParallelOpsFp = [&](const LocalPairConfig& fpCfg) + { + return std::max(getParallelOps(fpCfg.folderPathPhraseLeft), getParallelOps(fpCfg.folderPathPhraseRight)); + }; + + if (selectedPairIndexToShow_ < 0) + { + size_t parallelOps = 1; + for (const LocalPairConfig& fpCfg : localPairCfg_) + if (!fpCfg.localSyncCfg) //only consider folder pairs affected by main config's versioning folder + parallelOps = std::max(parallelOps, getParallelOpsFp(fpCfg)); + return parallelOps; + } + else + return getParallelOpsFp(localPairCfg_[selectedPairIndexToShow_]); +} + + MiscSyncConfig ConfigDialog::getMiscSyncOptions() const { assert(selectedPairIndexToShow_ == -1); - MiscSyncConfig miscCfg; - miscCfg.ignoreErrors = m_checkBoxIgnoreErrors->GetValue(); - miscCfg.automaticRetryCount = m_checkBoxAutoRetry->GetValue() ? m_spinCtrlAutoRetryCount->GetValue() : 0; + + // Avoid "fake" changed configs! => + // - don't touch items corresponding to paths not currently used + // - don't store parallel ops == 1 + miscCfg.deviceParallelOps = deviceParallelOps_; + assert(fgSizerPerf->GetItemCount() == 2 * devicePathsForEdit_.size()); + int i = 0; + for (const AbstractPath& devPath : devicePathsForEdit_) + { + wxSpinCtrl* spinCtrlParallelOps = dynamic_cast(fgSizerPerf->GetItem(i * 2)->GetWindow()); + const size_t parallelOps = spinCtrlParallelOps->GetValue(); + + if (parallelOps > 1) + miscCfg.deviceParallelOps[devPath] = parallelOps; + else + miscCfg.deviceParallelOps.erase(devPath); + ++i; + } + //---------------------------------------------------------------------------- + miscCfg.ignoreErrors = m_checkBoxIgnoreErrors ->GetValue(); + miscCfg.automaticRetryCount = m_checkBoxAutoRetry ->GetValue() ? m_spinCtrlAutoRetryCount->GetValue() : 0; miscCfg.automaticRetryDelay = m_spinCtrlAutoRetryDelay->GetValue(); miscCfg.postSyncCommand = m_comboBoxPostSyncCommand->getValue(); @@ -1064,7 +1135,60 @@ MiscSyncConfig ConfigDialog::getMiscSyncOptions() const void ConfigDialog::setMiscSyncOptions(const MiscSyncConfig& miscCfg) { - m_checkBoxIgnoreErrors->SetValue(miscCfg.ignoreErrors); + assert(selectedPairIndexToShow_ == -1); + + // Avoid "fake" changed configs! => + //- when editting, consider only the deviceParallelOps items corresponding to the currently-used folder paths + //- show parallel ops == 1 only temporarily during edit + deviceParallelOps_ = miscCfg.deviceParallelOps; + + devicePathsForEdit_.clear(); + auto addDevicePath = [&](const Zstring& folderPathPhrase) + { + const AbstractPath rootPath = AFS::getPathComponents(createAbstractPath(folderPathPhrase)).rootPath; + if (!AFS::isNullPath(rootPath)) + devicePathsForEdit_.insert(rootPath); + }; + for (const LocalPairConfig& fpCfg : localPairCfg_) + { + addDevicePath(fpCfg.folderPathPhraseLeft); + addDevicePath(fpCfg.folderPathPhraseRight); + } + + assert(fgSizerPerf->GetItemCount() % 2 == 0); + const int rowsToCreate = static_cast(devicePathsForEdit_.size()) - static_cast(fgSizerPerf->GetItemCount() / 2); + if (rowsToCreate >= 0) + for (int i = 0; i < rowsToCreate; ++i) + { + wxSpinCtrl* spinCtrlParallelOps = new wxSpinCtrl(m_scrolledWindowPerf, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 1, 2000000000, 1); + spinCtrlParallelOps->SetMinSize(wxSize(fastFromDIP(60), -1)); //Hack: set size (why does wxWindow::Size() not work?) + spinCtrlParallelOps->Enable(perfPanelActive_); + fgSizerPerf->Add(spinCtrlParallelOps, 0, wxALIGN_CENTER_VERTICAL); + + wxStaticText* staticTextDevice = new wxStaticText(m_scrolledWindowPerf, wxID_ANY, wxEmptyString); + staticTextDevice->Enable(perfPanelActive_); + fgSizerPerf->Add(staticTextDevice, 0, wxALIGN_CENTER_VERTICAL); + } + else + for (int i = 0; i < -rowsToCreate * 2; ++i) + fgSizerPerf->GetItem(size_t(0))->GetWindow()->Destroy(); + assert(fgSizerPerf->GetItemCount() == 2 * devicePathsForEdit_.size()); + + int i = 0; + for (const AbstractPath& devPath : devicePathsForEdit_) + { + wxSpinCtrl* spinCtrlParallelOps = dynamic_cast (fgSizerPerf->GetItem(i * 2 )->GetWindow()); + wxStaticText* staticTextDevice = dynamic_cast(fgSizerPerf->GetItem(i * 2 + 1)->GetWindow()); + + auto itParOps = deviceParallelOps_.find(devPath); + spinCtrlParallelOps->SetValue(std::max(itParOps != deviceParallelOps_.end() ? static_cast(itParOps->second) : 1, 1)); + staticTextDevice->SetLabel(AFS::getDisplayPath(devPath)); + ++i; + } + m_panelComparisonSettings->Layout(); //*after* setting text labels + + //---------------------------------------------------------------------------- + m_checkBoxIgnoreErrors ->SetValue(miscCfg.ignoreErrors); m_checkBoxAutoRetry ->SetValue(miscCfg.automaticRetryCount > 0); m_spinCtrlAutoRetryCount->SetValue(std::max(miscCfg.automaticRetryCount, 0)); m_spinCtrlAutoRetryDelay->SetValue(miscCfg.automaticRetryDelay); @@ -1081,6 +1205,7 @@ void ConfigDialog::updateMiscGui() { const MiscSyncConfig miscCfg = getMiscSyncOptions(); + //---------------------------------------------------------------------------- m_bitmapIgnoreErrors->SetBitmap(miscCfg.ignoreErrors ? getResourceImage(L"error_ignore_active") : greyScale(getResourceImage(L"error_ignore_inactive"))); m_bitmapRetryErrors ->SetBitmap(miscCfg.automaticRetryCount > 0 ? getResourceImage(L"error_retry") : greyScale(getResourceImage(L"error_retry"))); @@ -1102,26 +1227,32 @@ void ConfigDialog::selectFolderPairConfig(int newPairIndexToShow) //show/hide controls that are only relevant for main/local config const bool mainConfigSelected = newPairIndexToShow < 0; //comparison panel: - m_staticTextMainCompSettings->Show( mainConfigSelected && !localPairCfg_.empty()); - m_checkBoxUseLocalCmpOptions->Show(!mainConfigSelected && !localPairCfg_.empty()); - m_staticlineCompHeader->Show(!localPairCfg_.empty()); - m_panelCompSettingsTab->Layout(); //fix comp panel glitch on Win 7 125% font size + m_staticTextMainCompSettings->Show( mainConfigSelected && showMultipleCfgs_); + m_checkBoxUseLocalCmpOptions->Show(!mainConfigSelected && showMultipleCfgs_); + m_staticlineCompHeader->Show(showMultipleCfgs_); //filter panel - m_staticTextMainFilterSettings ->Show( mainConfigSelected && !localPairCfg_.empty()); - m_staticTextLocalFilterSettings->Show(!mainConfigSelected && !localPairCfg_.empty()); - m_staticlineFilterHeader->Show(!localPairCfg_.empty()); - m_panelFilterSettingsTab->Layout(); + m_staticTextMainFilterSettings ->Show( mainConfigSelected && showMultipleCfgs_); + m_staticTextLocalFilterSettings->Show(!mainConfigSelected && showMultipleCfgs_); + m_staticlineFilterHeader->Show(showMultipleCfgs_); //sync panel: - m_staticTextMainSyncSettings ->Show( mainConfigSelected && !localPairCfg_.empty()); - m_checkBoxUseLocalSyncOptions->Show(!mainConfigSelected && !localPairCfg_.empty()); - m_staticlineSyncHeader->Show(!localPairCfg_.empty()); - m_panelSyncSettingsTab->Layout(); + m_staticTextMainSyncSettings ->Show( mainConfigSelected && showMultipleCfgs_); + m_checkBoxUseLocalSyncOptions->Show(!mainConfigSelected && showMultipleCfgs_); + m_staticlineSyncHeader->Show(showMultipleCfgs_); //misc - bSizerMiscConfig->Show(mainConfigSelected); + bSizerPerformance ->Show(mainConfigSelected); //caveat: recursively shows hidden child items! + m_staticlinePerformance->Show(mainConfigSelected); + bSizerMiscConfig ->Show(mainConfigSelected); + + if (mainConfigSelected) m_staticTextPerfDeRequired->Show(!perfPanelActive_); //keep after bSizerPerformance->Show() + if (mainConfigSelected) m_staticlinePerfDeRequired->Show(!perfPanelActive_); // + + m_panelCompSettingsTab ->Layout(); //fix comp panel glitch on Win 7 125% font size + perf panel + m_panelFilterSettingsTab->Layout(); + m_panelSyncSettingsTab ->Layout(); if (mainConfigSelected) { - setCompConfig (&globalPairCfg_.cmpConfig); + setCompConfig (&globalPairCfg_.cmpCfg); setSyncConfig (&globalPairCfg_.syncCfg); setFilterConfig (globalPairCfg_.filter); setMiscSyncOptions(globalPairCfg_.miscCfg); @@ -1166,10 +1297,10 @@ bool ConfigDialog::unselectFolderPairConfig() if (selectedPairIndexToShow_ < 0) { - globalPairCfg_.cmpConfig = *compCfg; - globalPairCfg_.syncCfg = *syncCfg; - globalPairCfg_.filter = filterCfg; - globalPairCfg_.miscCfg = getMiscSyncOptions(); + globalPairCfg_.cmpCfg = *compCfg; + globalPairCfg_.syncCfg = *syncCfg; + globalPairCfg_.filter = filterCfg; + globalPairCfg_.miscCfg = getMiscSyncOptions(); } else { @@ -1189,8 +1320,8 @@ void ConfigDialog::OnOkay(wxCommandEvent& event) if (!unselectFolderPairConfig()) return; - globalPairCfgOut_ = globalPairCfg_; - localPairCfgOut_ = localPairCfg_; + globalPairCfgOut_ = globalPairCfg_; + localPairCfgOut_ = localPairCfg_; EndModal(ReturnSyncConfig::BUTTON_OKAY); } @@ -1200,16 +1331,17 @@ void ConfigDialog::OnOkay(wxCommandEvent& event) ReturnSyncConfig::ButtonPressed fff::showSyncConfigDlg(wxWindow* parent, SyncConfigPanel panelToShow, - int localPairIndexToShow, + int localPairIndexToShow, bool showMultipleCfgs, GlobalPairConfig& globalPairCfg, std::vector& localPairConfig, size_t commandHistItemsMax) { + ConfigDialog syncDlg(parent, panelToShow, - localPairIndexToShow, + localPairIndexToShow, showMultipleCfgs, globalPairCfg, localPairConfig, commandHistItemsMax); diff --git a/FreeFileSync/Source/ui/sync_cfg.h b/FreeFileSync/Source/ui/sync_cfg.h index a18207de..fd41a529 100755 --- a/FreeFileSync/Source/ui/sync_cfg.h +++ b/FreeFileSync/Source/ui/sync_cfg.h @@ -31,6 +31,7 @@ enum class SyncConfigPanel struct MiscSyncConfig { + std::map deviceParallelOps; bool ignoreErrors = false; size_t automaticRetryCount = 0; size_t automaticRetryDelay = 0; @@ -41,7 +42,7 @@ struct MiscSyncConfig struct GlobalPairConfig { - CompConfig cmpConfig; + CompConfig cmpCfg; SyncConfig syncCfg; FilterConfig filter; MiscSyncConfig miscCfg; @@ -51,6 +52,7 @@ struct GlobalPairConfig ReturnSyncConfig::ButtonPressed showSyncConfigDlg(wxWindow* parent, SyncConfigPanel panelToShow, int localPairIndexToShow, //< 0 to show global config + bool showMultipleCfgs, GlobalPairConfig& globalPairCfg, std::vector& localPairConfig, diff --git a/FreeFileSync/Source/ui/tree_grid.cpp b/FreeFileSync/Source/ui/tree_grid.cpp index d17985f7..de7561d0 100755 --- a/FreeFileSync/Source/ui/tree_grid.cpp +++ b/FreeFileSync/Source/ui/tree_grid.cpp @@ -179,9 +179,9 @@ struct TreeView::LessShortName switch (lhs.type) { case TreeView::TYPE_ROOT: - return makeSortDirection(LessNaturalSort() /*even on Linux*/, - Int2Type())(utfTo(static_cast(lhs.node)->displayName), - utfTo(static_cast(rhs.node)->displayName)); + return makeSortDirection(LessNaturalSort() /*even on Linux*/, + Int2Type())(utfTo(static_cast(lhs.node)->displayName), + utfTo(static_cast(rhs.node)->displayName)); case TreeView::TYPE_DIRECTORY: { diff --git a/FreeFileSync/Source/ui/version_check.cpp b/FreeFileSync/Source/ui/version_check.cpp index 9b241c7d..822ce230 100755 --- a/FreeFileSync/Source/ui/version_check.cpp +++ b/FreeFileSync/Source/ui/version_check.cpp @@ -21,6 +21,7 @@ #include "version_check_impl.h" + using namespace zen; using namespace fff; @@ -126,7 +127,7 @@ void showUpdateAvailableDialog(wxWindow* parent, const std::string& onlineVersio _("&Download"))) { case ConfirmationButton::ACCEPT: - wxLaunchDefaultBrowser(L"https://www.freefilesync.org/get_latest.php"); + wxLaunchDefaultBrowser(L"https://www.freefilesync.org/get_latest.php"); break; case ConfirmationButton::CANCEL: break; @@ -263,6 +264,7 @@ std::shared_ptr fff::automaticUpdateCheckRunAsync(const Updat //run on main thread: void fff::automaticUpdateCheckEval(wxWindow* parent, time_t& lastUpdateCheck, std::string& lastOnlineVersion, const UpdateCheckResult* resultAsync) { + UpdateCheckResult result; try { diff --git a/FreeFileSync/Source/version/version.h b/FreeFileSync/Source/version/version.h index 677f9556..797c1901 100755 --- a/FreeFileSync/Source/version/version.h +++ b/FreeFileSync/Source/version/version.h @@ -3,7 +3,7 @@ namespace fff { -const char ffsVersion[] = "9.9"; //internal linkage! +const char ffsVersion[] = "10.0"; //internal linkage! const char FFS_VERSION_SEPARATOR = '.'; } diff --git a/wx+/image_resources.cpp b/wx+/image_resources.cpp index e87b245c..e361515a 100755 --- a/wx+/image_resources.cpp +++ b/wx+/image_resources.cpp @@ -86,7 +86,7 @@ ImageHolder dpiScale(int width, int height, int dpiWidth, int dpiHeight, const u struct WorkItem { - Zbase name; + std::wstring name; //don't trust wxString to be thread-safe like an int int width = 0; int height = 0; int dpiWidth = 0; @@ -127,19 +127,20 @@ public: interruptibleWait(conditionNewWork_, dummy, [this] { return !workLoad_.empty() || !expectMoreWork_; }); //throw ThreadInterruption - if (workLoad_.empty()) - return NoValue(); - - WorkItem wi = workLoad_.back(); // - workLoad_.pop_back(); //yes, no strong exception guarantee (std::bad_alloc) - return wi; // + if (!workLoad_.empty()) + { + WorkItem wi = std::move(workLoad_. back()); // + /**/ workLoad_.pop_back(); //yes, no strong exception guarantee (std::bad_alloc) + return std::move(wi); // + } + return NoValue(); } private: - bool expectMoreWork_ = true; - std::vector workLoad_; std::mutex lockWork_; + std::vector workLoad_; std::condition_variable conditionNewWork_; //signal event: data for processing available + bool expectMoreWork_ = true; }; @@ -160,7 +161,7 @@ public: ImageHolder ih = dpiScale(wi->width, wi->height, wi->dpiWidth, wi->dpiHeight, wi->rgb, wi->alpha, hqScale); - result.access([&](std::vector, ImageHolder>>& r) { r.emplace_back(wi->name, std::move(ih)); }); + result.access([&](std::vector>& r) { r.emplace_back(wi->name, std::move(ih)); }); } }); } @@ -178,7 +179,7 @@ public: void add(const wxString& name, const wxImage& img) { imgKeeper_.push_back(img); //retain (ref-counted) wxImage so that the rgb/alpha pointers remain valid after passed to threads - workload_.add({ copyStringTo>(name), + workload_.add({ copyStringTo(name), img.GetWidth(), img.GetHeight(), fastFromDIP(img.GetWidth()), fastFromDIP(img.GetHeight()), //don't call fastFromDIP() from worker thread (wxWidgets function!) img.GetData(), img.GetAlpha() }); @@ -193,7 +194,7 @@ public: std::map output; - result_.access([&](std::vector, ImageHolder>>& r) + result_.access([&](std::vector>& r) { for (auto& item : r) { @@ -202,7 +203,7 @@ public: wxImage img(ih.getWidth(), ih.getHeight(), ih.releaseRgb(), false /*static_data*/); //pass ownership img.SetAlpha(ih.releaseAlpha(), false /*static_data*/); - output[utfTo(item.first)] = wxBitmap(img); + output[item.first] = wxBitmap(img); } }); return output; @@ -211,7 +212,7 @@ public: private: std::vector worker_; WorkLoad workload_; - Protected, ImageHolder>>> result_; + Protected>> result_; std::vector imgKeeper_; }; diff --git a/xBRZ/src/xbrz.cpp b/xBRZ/src/xbrz.cpp new file mode 100755 index 00000000..4d3ccd25 --- /dev/null +++ b/xBRZ/src/xbrz.cpp @@ -0,0 +1,1262 @@ +// **************************************************************************** +// * This file is part of the xBRZ project. It is distributed under * +// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// * * +// * Additionally and as a special exception, the author gives permission * +// * to link the code of this program with the following libraries * +// * (or with modified versions that use the same licenses), and distribute * +// * linked combinations including the two: MAME, FreeFileSync, Snes9x, ePSXe * +// * You must obey the GNU General Public License in all respects for all of * +// * the code used other than MAME, FreeFileSync, Snes9x, ePSXe. * +// * If you modify this file, you may extend this exception to your version * +// * of the file, but you are not obligated to do so. If you do not wish to * +// * do so, delete this exception statement from your version. * +// **************************************************************************** + +#include "xbrz.h" +#include +#include +#include +#include //std::sqrt +#include "xbrz_tools.h" + +using namespace xbrz; + + +namespace +{ +template inline +uint32_t gradientRGB(uint32_t pixFront, uint32_t pixBack) //blend front color with opacity M / N over opaque background: http://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending +{ + static_assert(0 < M && M < N && N <= 1000, ""); + + auto calcColor = [](unsigned char colFront, unsigned char colBack) -> unsigned char { return (colFront * M + colBack * (N - M)) / N; }; + + return makePixel(calcColor(getRed (pixFront), getRed (pixBack)), + calcColor(getGreen(pixFront), getGreen(pixBack)), + calcColor(getBlue (pixFront), getBlue (pixBack))); +} + + +template inline +uint32_t gradientARGB(uint32_t pixFront, uint32_t pixBack) //find intermediate color between two colors with alpha channels (=> NO alpha blending!!!) +{ + static_assert(0 < M && M < N && N <= 1000, ""); + + const unsigned int weightFront = getAlpha(pixFront) * M; + const unsigned int weightBack = getAlpha(pixBack) * (N - M); + const unsigned int weightSum = weightFront + weightBack; + if (weightSum == 0) + return 0; + + auto calcColor = [=](unsigned char colFront, unsigned char colBack) + { + return static_cast((colFront * weightFront + colBack * weightBack) / weightSum); + }; + + return makePixel(static_cast(weightSum / N), + calcColor(getRed (pixFront), getRed (pixBack)), + calcColor(getGreen(pixFront), getGreen(pixBack)), + calcColor(getBlue (pixFront), getBlue (pixBack))); +} + + +//inline +//double fastSqrt(double n) +//{ +// __asm //speeds up xBRZ by about 9% compared to std::sqrt which internally uses the same assembler instructions but adds some "fluff" +// { +// fld n +// fsqrt +// } +//} +// + + +#if defined __GNUC__ + #define FORCE_INLINE __attribute__((always_inline)) inline +#else + #define FORCE_INLINE inline +#endif + + +enum RotationDegree //clock-wise +{ + ROT_0, + ROT_90, + ROT_180, + ROT_270 +}; + +//calculate input matrix coordinates after rotation at compile time +template +struct MatrixRotation; + +template +struct MatrixRotation +{ + static const size_t I_old = I; + static const size_t J_old = J; +}; + +template //(i, j) = (row, col) indices, N = size of (square) matrix +struct MatrixRotation +{ + static const size_t I_old = N - 1 - MatrixRotation(rotDeg - 1), I, J, N>::J_old; //old coordinates before rotation! + static const size_t J_old = MatrixRotation(rotDeg - 1), I, J, N>::I_old; // +}; + + +template +class OutputMatrix +{ +public: + OutputMatrix(uint32_t* out, int outWidth) : //access matrix area, top-left at position "out" for image with given width + out_(out), + outWidth_(outWidth) {} + + template + uint32_t& ref() const + { + static const size_t I_old = MatrixRotation::I_old; + static const size_t J_old = MatrixRotation::J_old; + return *(out_ + J_old + I_old * outWidth_); + } + +private: + uint32_t* out_; + const int outWidth_; +}; + + +template inline +T square(T value) { return value * value; } + + +#if 0 +inline +double distRGB(uint32_t pix1, uint32_t pix2) +{ + const double r_diff = static_cast(getRed (pix1)) - getRed (pix2); + const double g_diff = static_cast(getGreen(pix1)) - getGreen(pix2); + const double b_diff = static_cast(getBlue (pix1)) - getBlue (pix2); + + //euklidean RGB distance + return std::sqrt(square(r_diff) + square(g_diff) + square(b_diff)); +} +#endif + + +inline +double distYCbCr(uint32_t pix1, uint32_t pix2, double lumaWeight) +{ + //http://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion + //YCbCr conversion is a matrix multiplication => take advantage of linearity by subtracting first! + const int r_diff = static_cast(getRed (pix1)) - getRed (pix2); //we may delay division by 255 to after matrix multiplication + const int g_diff = static_cast(getGreen(pix1)) - getGreen(pix2); // + const int b_diff = static_cast(getBlue (pix1)) - getBlue (pix2); //substraction for int is noticeable faster than for double! + + //const double k_b = 0.0722; //ITU-R BT.709 conversion + //const double k_r = 0.2126; // + const double k_b = 0.0593; //ITU-R BT.2020 conversion + const double k_r = 0.2627; // + const double k_g = 1 - k_b - k_r; + + const double scale_b = 0.5 / (1 - k_b); + const double scale_r = 0.5 / (1 - k_r); + + const double y = k_r * r_diff + k_g * g_diff + k_b * b_diff; //[!], analog YCbCr! + const double c_b = scale_b * (b_diff - y); + const double c_r = scale_r * (r_diff - y); + + //we skip division by 255 to have similar range like other distance functions + return std::sqrt(square(lumaWeight * y) + square(c_b) + square(c_r)); +} + + +inline +double distYCbCrBuffered(uint32_t pix1, uint32_t pix2) +{ + //30% perf boost compared to plain distYCbCr()! + //consumes 64 MB memory; using double is only 2% faster, but takes 128 MB + static const std::vector diffToDist = [] + { + std::vector tmp; + + for (uint32_t i = 0; i < 256 * 256 * 256; ++i) //startup time: 114 ms on Intel Core i5 (four cores) + { + const int r_diff = static_cast(getByte<2>(i)) * 2; + const int g_diff = static_cast(getByte<1>(i)) * 2; + const int b_diff = static_cast(getByte<0>(i)) * 2; + + const double k_b = 0.0593; //ITU-R BT.2020 conversion + const double k_r = 0.2627; // + const double k_g = 1 - k_b - k_r; + + const double scale_b = 0.5 / (1 - k_b); + const double scale_r = 0.5 / (1 - k_r); + + const double y = k_r * r_diff + k_g * g_diff + k_b * b_diff; //[!], analog YCbCr! + const double c_b = scale_b * (b_diff - y); + const double c_r = scale_r * (r_diff - y); + + tmp.push_back(static_cast(std::sqrt(square(y) + square(c_b) + square(c_r)))); + } + return tmp; + }(); + + //if (pix1 == pix2) -> 8% perf degradation! + // return 0; + //if (pix1 < pix2) + // std::swap(pix1, pix2); -> 30% perf degradation!!! + + const int r_diff = static_cast(getRed (pix1)) - getRed (pix2); + const int g_diff = static_cast(getGreen(pix1)) - getGreen(pix2); + const int b_diff = static_cast(getBlue (pix1)) - getBlue (pix2); + + const size_t index = (static_cast(r_diff / 2) << 16) | //slightly reduce precision (division by 2) to squeeze value into single byte + (static_cast(g_diff / 2) << 8) | + (static_cast(b_diff / 2)); + +#if 0 //attention: the following calculation creates an asymmetric color distance!!! (e.g. r_diff=46 will be unpacked as 45, but r_diff=-46 unpacks to -47 + const size_t index = (((r_diff + 0xFF) / 2) << 16) | //slightly reduce precision (division by 2) to squeeze value into single byte + (((g_diff + 0xFF) / 2) << 8) | + (( b_diff + 0xFF) / 2); +#endif + return diffToDist[index]; +} + + +enum BlendType +{ + BLEND_NONE = 0, + BLEND_NORMAL, //a normal indication to blend + BLEND_DOMINANT, //a strong indication to blend + //attention: BlendType must fit into the value range of 2 bit!!! +}; + +struct BlendResult +{ + BlendType + /**/blend_f, blend_g, + /**/blend_j, blend_k; +}; + + +struct Kernel_4x4 //kernel for preprocessing step +{ + uint32_t + /**/a, b, c, d, + /**/e, f, g, h, + /**/i, j, k, l, + /**/m, n, o, p; +}; + +/* +input kernel area naming convention: +----------------- +| A | B | C | D | +----|---|---|---| +| E | F | G | H | //evaluate the four corners between F, G, J, K +----|---|---|---| //input pixel is at position F +| I | J | K | L | +----|---|---|---| +| M | N | O | P | +----------------- +*/ +template +FORCE_INLINE //detect blend direction +BlendResult preProcessCorners(const Kernel_4x4& ker, const xbrz::ScalerCfg& cfg) //result: F, G, J, K corners of "GradientType" +{ + + BlendResult result = {}; + + if ((ker.f == ker.g && + ker.j == ker.k) || + (ker.f == ker.j && + ker.g == ker.k)) + return result; + + auto dist = [&](uint32_t pix1, uint32_t pix2) { return ColorDistance::dist(pix1, pix2, cfg.luminanceWeight); }; + + double jg = dist(ker.i, ker.f) + dist(ker.f, ker.c) + dist(ker.n, ker.k) + dist(ker.k, ker.h) + cfg.centerDirectionBias * dist(ker.j, ker.g); + double fk = dist(ker.e, ker.j) + dist(ker.j, ker.o) + dist(ker.b, ker.g) + dist(ker.g, ker.l) + cfg.centerDirectionBias * dist(ker.f, ker.k); + + if (jg < fk) //test sample: 70% of values max(jg, fk) / min(jg, fk) are between 1.1 and 3.7 with median being 1.8 + { + const bool dominantGradient = cfg.dominantDirectionThreshold * jg < fk; + if (ker.f != ker.g && ker.f != ker.j) + result.blend_f = dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL; + + if (ker.k != ker.j && ker.k != ker.g) + result.blend_k = dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL; + } + else if (fk < jg) + { + const bool dominantGradient = cfg.dominantDirectionThreshold * fk < jg; + if (ker.j != ker.f && ker.j != ker.k) + result.blend_j = dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL; + + if (ker.g != ker.f && ker.g != ker.k) + result.blend_g = dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL; + } + return result; +} + +struct Kernel_3x3 +{ + uint32_t + /**/a, b, c, + /**/d, e, f, + /**/g, h, i; +}; + +#define DEF_GETTER(x) template uint32_t inline get_##x(const Kernel_3x3& ker) { return ker.x; } +//we cannot and NEED NOT write "ker.##x" since ## concatenates preprocessor tokens but "." is not a token +DEF_GETTER(a) DEF_GETTER(b) DEF_GETTER(c) +DEF_GETTER(d) DEF_GETTER(e) DEF_GETTER(f) +DEF_GETTER(g) DEF_GETTER(h) DEF_GETTER(i) +#undef DEF_GETTER + +#define DEF_GETTER(x, y) template <> inline uint32_t get_##x(const Kernel_3x3& ker) { return ker.y; } +DEF_GETTER(a, g) DEF_GETTER(b, d) DEF_GETTER(c, a) +DEF_GETTER(d, h) DEF_GETTER(e, e) DEF_GETTER(f, b) +DEF_GETTER(g, i) DEF_GETTER(h, f) DEF_GETTER(i, c) +#undef DEF_GETTER + +#define DEF_GETTER(x, y) template <> inline uint32_t get_##x(const Kernel_3x3& ker) { return ker.y; } +DEF_GETTER(a, i) DEF_GETTER(b, h) DEF_GETTER(c, g) +DEF_GETTER(d, f) DEF_GETTER(e, e) DEF_GETTER(f, d) +DEF_GETTER(g, c) DEF_GETTER(h, b) DEF_GETTER(i, a) +#undef DEF_GETTER + +#define DEF_GETTER(x, y) template <> inline uint32_t get_##x(const Kernel_3x3& ker) { return ker.y; } +DEF_GETTER(a, c) DEF_GETTER(b, f) DEF_GETTER(c, i) +DEF_GETTER(d, b) DEF_GETTER(e, e) DEF_GETTER(f, h) +DEF_GETTER(g, a) DEF_GETTER(h, d) DEF_GETTER(i, g) +#undef DEF_GETTER + + +//compress four blend types into a single byte +//inline BlendType getTopL (unsigned char b) { return static_cast(0x3 & b); } +inline BlendType getTopR (unsigned char b) { return static_cast(0x3 & (b >> 2)); } +inline BlendType getBottomR(unsigned char b) { return static_cast(0x3 & (b >> 4)); } +inline BlendType getBottomL(unsigned char b) { return static_cast(0x3 & (b >> 6)); } + +inline void setTopL (unsigned char& b, BlendType bt) { b |= bt; } //buffer is assumed to be initialized before preprocessing! +inline void setTopR (unsigned char& b, BlendType bt) { b |= (bt << 2); } +inline void setBottomR(unsigned char& b, BlendType bt) { b |= (bt << 4); } +inline void setBottomL(unsigned char& b, BlendType bt) { b |= (bt << 6); } + +inline bool blendingNeeded(unsigned char b) { return b != 0; } + +template inline +unsigned char rotateBlendInfo(unsigned char b) { return b; } +template <> inline unsigned char rotateBlendInfo(unsigned char b) { return ((b << 2) | (b >> 6)) & 0xff; } +template <> inline unsigned char rotateBlendInfo(unsigned char b) { return ((b << 4) | (b >> 4)) & 0xff; } +template <> inline unsigned char rotateBlendInfo(unsigned char b) { return ((b << 6) | (b >> 2)) & 0xff; } + + + + +/* +input kernel area naming convention: +------------- +| A | B | C | +----|---|---| +| D | E | F | //input pixel is at position E +----|---|---| +| G | H | I | +------------- +*/ +template +FORCE_INLINE //perf: quite worth it! +void blendPixel(const Kernel_3x3& ker, + uint32_t* target, int trgWidth, + unsigned char blendInfo, //result of preprocessing all four corners of pixel "e" + const xbrz::ScalerCfg& cfg) +{ +#define a get_a(ker) +#define b get_b(ker) +#define c get_c(ker) +#define d get_d(ker) +#define e get_e(ker) +#define f get_f(ker) +#define g get_g(ker) +#define h get_h(ker) +#define i get_i(ker) + + + (void)a; //silence Clang's -Wunused-function + + const unsigned char blend = rotateBlendInfo(blendInfo); + + if (getBottomR(blend) >= BLEND_NORMAL) + { + auto eq = [&](uint32_t pix1, uint32_t pix2) { return ColorDistance::dist(pix1, pix2, cfg.luminanceWeight) < cfg.equalColorTolerance; }; + auto dist = [&](uint32_t pix1, uint32_t pix2) { return ColorDistance::dist(pix1, pix2, cfg.luminanceWeight); }; + + const bool doLineBlend = [&]() -> bool + { + if (getBottomR(blend) >= BLEND_DOMINANT) + return true; + + //make sure there is no second blending in an adjacent rotation for this pixel: handles insular pixels, mario eyes + if (getTopR(blend) != BLEND_NONE && !eq(e, g)) //but support double-blending for 90 corners + return false; + if (getBottomL(blend) != BLEND_NONE && !eq(e, c)) + return false; + + //no full blending for L-shapes; blend corner only (handles "mario mushroom eyes") + if (!eq(e, i) && eq(g, h) && eq(h, i) && eq(i, f) && eq(f, c)) + return false; + + return true; + }(); + + const uint32_t px = dist(e, f) <= dist(e, h) ? f : h; //choose most similar color + + OutputMatrix out(target, trgWidth); + + if (doLineBlend) + { + const double fg = dist(f, g); //test sample: 70% of values max(fg, hc) / min(fg, hc) are between 1.1 and 3.7 with median being 1.9 + const double hc = dist(h, c); // + + const bool haveShallowLine = cfg.steepDirectionThreshold * fg <= hc && e != g && d != g; + const bool haveSteepLine = cfg.steepDirectionThreshold * hc <= fg && e != c && b != c; + + if (haveShallowLine) + { + if (haveSteepLine) + Scaler::blendLineSteepAndShallow(px, out); + else + Scaler::blendLineShallow(px, out); + } + else + { + if (haveSteepLine) + Scaler::blendLineSteep(px, out); + else + Scaler::blendLineDiagonal(px, out); + } + } + else + Scaler::blendCorner(px, out); + } + +#undef a +#undef b +#undef c +#undef d +#undef e +#undef f +#undef g +#undef h +#undef i +} + + +template //scaler policy: see "Scaler2x" reference implementation +void scaleImage(const uint32_t* src, uint32_t* trg, int srcWidth, int srcHeight, const xbrz::ScalerCfg& cfg, int yFirst, int yLast) +{ + yFirst = std::max(yFirst, 0); + yLast = std::min(yLast, srcHeight); + if (yFirst >= yLast || srcWidth <= 0) + return; + + const int trgWidth = srcWidth * Scaler::scale; + + //"use" space at the end of the image as temporary buffer for "on the fly preprocessing": we even could use larger area of + //"sizeof(uint32_t) * srcWidth * (yLast - yFirst)" bytes without risk of accidental overwriting before accessing + const int bufferSize = srcWidth; + unsigned char* preProcBuffer = reinterpret_cast(trg + yLast * Scaler::scale * trgWidth) - bufferSize; + std::fill(preProcBuffer, preProcBuffer + bufferSize, '\0'); + static_assert(BLEND_NONE == 0, ""); + + //initialize preprocessing buffer for first row of current stripe: detect upper left and right corner blending + //this cannot be optimized for adjacent processing stripes; we must not allow for a memory race condition! + if (yFirst > 0) + { + const int y = yFirst - 1; + + const uint32_t* s_m1 = src + srcWidth * std::max(y - 1, 0); + const uint32_t* s_0 = src + srcWidth * y; //center line + const uint32_t* s_p1 = src + srcWidth * std::min(y + 1, srcHeight - 1); + const uint32_t* s_p2 = src + srcWidth * std::min(y + 2, srcHeight - 1); + + for (int x = 0; x < srcWidth; ++x) + { + const int x_m1 = std::max(x - 1, 0); + const int x_p1 = std::min(x + 1, srcWidth - 1); + const int x_p2 = std::min(x + 2, srcWidth - 1); + + Kernel_4x4 ker = {}; //perf: initialization is negligible + ker.a = s_m1[x_m1]; //read sequentially from memory as far as possible + ker.b = s_m1[x]; + ker.c = s_m1[x_p1]; + ker.d = s_m1[x_p2]; + + ker.e = s_0[x_m1]; + ker.f = s_0[x]; + ker.g = s_0[x_p1]; + ker.h = s_0[x_p2]; + + ker.i = s_p1[x_m1]; + ker.j = s_p1[x]; + ker.k = s_p1[x_p1]; + ker.l = s_p1[x_p2]; + + ker.m = s_p2[x_m1]; + ker.n = s_p2[x]; + ker.o = s_p2[x_p1]; + ker.p = s_p2[x_p2]; + + const BlendResult res = preProcessCorners(ker, cfg); + /* + preprocessing blend result: + --------- + | F | G | //evalute corner between F, G, J, K + ----|---| //input pixel is at position F + | J | K | + --------- + */ + setTopR(preProcBuffer[x], res.blend_j); + + if (x + 1 < bufferSize) + setTopL(preProcBuffer[x + 1], res.blend_k); + } + } + //------------------------------------------------------------------------------------ + + for (int y = yFirst; y < yLast; ++y) + { + uint32_t* out = trg + Scaler::scale * y * trgWidth; //consider MT "striped" access + + const uint32_t* s_m1 = src + srcWidth * std::max(y - 1, 0); + const uint32_t* s_0 = src + srcWidth * y; //center line + const uint32_t* s_p1 = src + srcWidth * std::min(y + 1, srcHeight - 1); + const uint32_t* s_p2 = src + srcWidth * std::min(y + 2, srcHeight - 1); + + unsigned char blend_xy1 = 0; //corner blending for current (x, y + 1) position + + for (int x = 0; x < srcWidth; ++x, out += Scaler::scale) + { + //all those bounds checks have only insignificant impact on performance! + const int x_m1 = std::max(x - 1, 0); //perf: prefer array indexing to additional pointers! + const int x_p1 = std::min(x + 1, srcWidth - 1); + const int x_p2 = std::min(x + 2, srcWidth - 1); + + Kernel_4x4 ker4 = {}; //perf: initialization is negligible + + ker4.a = s_m1[x_m1]; //read sequentially from memory as far as possible + ker4.b = s_m1[x]; + ker4.c = s_m1[x_p1]; + ker4.d = s_m1[x_p2]; + + ker4.e = s_0[x_m1]; + ker4.f = s_0[x]; + ker4.g = s_0[x_p1]; + ker4.h = s_0[x_p2]; + + ker4.i = s_p1[x_m1]; + ker4.j = s_p1[x]; + ker4.k = s_p1[x_p1]; + ker4.l = s_p1[x_p2]; + + ker4.m = s_p2[x_m1]; + ker4.n = s_p2[x]; + ker4.o = s_p2[x_p1]; + ker4.p = s_p2[x_p2]; + + //evaluate the four corners on bottom-right of current pixel + unsigned char blend_xy = 0; //for current (x, y) position + { + const BlendResult res = preProcessCorners(ker4, cfg); + /* + preprocessing blend result: + --------- + | F | G | //evalute corner between F, G, J, K + ----|---| //current input pixel is at position F + | J | K | + --------- + */ + blend_xy = preProcBuffer[x]; + setBottomR(blend_xy, res.blend_f); //all four corners of (x, y) have been determined at this point due to processing sequence! + + setTopR(blend_xy1, res.blend_j); //set 2nd known corner for (x, y + 1) + preProcBuffer[x] = blend_xy1; //store on current buffer position for use on next row + + blend_xy1 = 0; + setTopL(blend_xy1, res.blend_k); //set 1st known corner for (x + 1, y + 1) and buffer for use on next column + + if (x + 1 < bufferSize) //set 3rd known corner for (x + 1, y) + setBottomL(preProcBuffer[x + 1], res.blend_g); + } + + //fill block of size scale * scale with the given color + fillBlock(out, trgWidth * sizeof(uint32_t), ker4.f, Scaler::scale, Scaler::scale); + //place *after* preprocessing step, to not overwrite the results while processing the the last pixel! + + //blend four corners of current pixel + if (blendingNeeded(blend_xy)) //good 5% perf-improvement + { + Kernel_3x3 ker3 = {}; //perf: initialization is negligible + + ker3.a = ker4.a; + ker3.b = ker4.b; + ker3.c = ker4.c; + + ker3.d = ker4.e; + ker3.e = ker4.f; + ker3.f = ker4.g; + + ker3.g = ker4.i; + ker3.h = ker4.j; + ker3.i = ker4.k; + + blendPixel(ker3, out, trgWidth, blend_xy, cfg); + blendPixel(ker3, out, trgWidth, blend_xy, cfg); + blendPixel(ker3, out, trgWidth, blend_xy, cfg); + blendPixel(ker3, out, trgWidth, blend_xy, cfg); + } + } + } +} + +//------------------------------------------------------------------------------------ + +template +struct Scaler2x : public ColorGradient +{ + static const int scale = 2; + + template //bring template function into scope for GCC + static void alphaGrad(uint32_t& pixBack, uint32_t pixFront) { ColorGradient::template alphaGrad(pixBack, pixFront); } + + + template + static void blendLineShallow(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref(), col); + alphaGrad<3, 4>(out.template ref(), col); + } + + template + static void blendLineSteep(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref<0, scale - 1>(), col); + alphaGrad<3, 4>(out.template ref<1, scale - 1>(), col); + } + + template + static void blendLineSteepAndShallow(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref<1, 0>(), col); + alphaGrad<1, 4>(out.template ref<0, 1>(), col); + alphaGrad<5, 6>(out.template ref<1, 1>(), col); //[!] fixes 7/8 used in xBR + } + + template + static void blendLineDiagonal(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 2>(out.template ref<1, 1>(), col); + } + + template + static void blendCorner(uint32_t col, OutputMatrix& out) + { + //model a round corner + alphaGrad<21, 100>(out.template ref<1, 1>(), col); //exact: 1 - pi/4 = 0.2146018366 + } +}; + + +template +struct Scaler3x : public ColorGradient +{ + static const int scale = 3; + + template //bring template function into scope for GCC + static void alphaGrad(uint32_t& pixBack, uint32_t pixFront) { ColorGradient::template alphaGrad(pixBack, pixFront); } + + + template + static void blendLineShallow(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref(), col); + alphaGrad<1, 4>(out.template ref(), col); + + alphaGrad<3, 4>(out.template ref(), col); + out.template ref() = col; + } + + template + static void blendLineSteep(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref<0, scale - 1>(), col); + alphaGrad<1, 4>(out.template ref<2, scale - 2>(), col); + + alphaGrad<3, 4>(out.template ref<1, scale - 1>(), col); + out.template ref<2, scale - 1>() = col; + } + + template + static void blendLineSteepAndShallow(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref<2, 0>(), col); + alphaGrad<1, 4>(out.template ref<0, 2>(), col); + alphaGrad<3, 4>(out.template ref<2, 1>(), col); + alphaGrad<3, 4>(out.template ref<1, 2>(), col); + out.template ref<2, 2>() = col; + } + + template + static void blendLineDiagonal(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 8>(out.template ref<1, 2>(), col); //conflict with other rotations for this odd scale + alphaGrad<1, 8>(out.template ref<2, 1>(), col); + alphaGrad<7, 8>(out.template ref<2, 2>(), col); // + } + + template + static void blendCorner(uint32_t col, OutputMatrix& out) + { + //model a round corner + alphaGrad<45, 100>(out.template ref<2, 2>(), col); //exact: 0.4545939598 + //alphaGrad<7, 256>(out.template ref<2, 1>(), col); //0.02826017254 -> negligible + avoid conflicts with other rotations for this odd scale + //alphaGrad<7, 256>(out.template ref<1, 2>(), col); //0.02826017254 + } +}; + + +template +struct Scaler4x : public ColorGradient +{ + static const int scale = 4; + + template //bring template function into scope for GCC + static void alphaGrad(uint32_t& pixBack, uint32_t pixFront) { ColorGradient::template alphaGrad(pixBack, pixFront); } + + + template + static void blendLineShallow(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref(), col); + alphaGrad<1, 4>(out.template ref(), col); + + alphaGrad<3, 4>(out.template ref(), col); + alphaGrad<3, 4>(out.template ref(), col); + + out.template ref() = col; + out.template ref() = col; + } + + template + static void blendLineSteep(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref<0, scale - 1>(), col); + alphaGrad<1, 4>(out.template ref<2, scale - 2>(), col); + + alphaGrad<3, 4>(out.template ref<1, scale - 1>(), col); + alphaGrad<3, 4>(out.template ref<3, scale - 2>(), col); + + out.template ref<2, scale - 1>() = col; + out.template ref<3, scale - 1>() = col; + } + + template + static void blendLineSteepAndShallow(uint32_t col, OutputMatrix& out) + { + alphaGrad<3, 4>(out.template ref<3, 1>(), col); + alphaGrad<3, 4>(out.template ref<1, 3>(), col); + alphaGrad<1, 4>(out.template ref<3, 0>(), col); + alphaGrad<1, 4>(out.template ref<0, 3>(), col); + + alphaGrad<1, 3>(out.template ref<2, 2>(), col); //[!] fixes 1/4 used in xBR + + out.template ref<3, 3>() = col; + out.template ref<3, 2>() = col; + out.template ref<2, 3>() = col; + } + + template + static void blendLineDiagonal(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 2>(out.template ref(), col); + alphaGrad<1, 2>(out.template ref(), col); + out.template ref() = col; + } + + template + static void blendCorner(uint32_t col, OutputMatrix& out) + { + //model a round corner + alphaGrad<68, 100>(out.template ref<3, 3>(), col); //exact: 0.6848532563 + alphaGrad< 9, 100>(out.template ref<3, 2>(), col); //0.08677704501 + alphaGrad< 9, 100>(out.template ref<2, 3>(), col); //0.08677704501 + } +}; + + +template +struct Scaler5x : public ColorGradient +{ + static const int scale = 5; + + template //bring template function into scope for GCC + static void alphaGrad(uint32_t& pixBack, uint32_t pixFront) { ColorGradient::template alphaGrad(pixBack, pixFront); } + + + template + static void blendLineShallow(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref(), col); + alphaGrad<1, 4>(out.template ref(), col); + alphaGrad<1, 4>(out.template ref(), col); + + alphaGrad<3, 4>(out.template ref(), col); + alphaGrad<3, 4>(out.template ref(), col); + + out.template ref() = col; + out.template ref() = col; + out.template ref() = col; + out.template ref() = col; + } + + template + static void blendLineSteep(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref<0, scale - 1>(), col); + alphaGrad<1, 4>(out.template ref<2, scale - 2>(), col); + alphaGrad<1, 4>(out.template ref<4, scale - 3>(), col); + + alphaGrad<3, 4>(out.template ref<1, scale - 1>(), col); + alphaGrad<3, 4>(out.template ref<3, scale - 2>(), col); + + out.template ref<2, scale - 1>() = col; + out.template ref<3, scale - 1>() = col; + out.template ref<4, scale - 1>() = col; + out.template ref<4, scale - 2>() = col; + } + + template + static void blendLineSteepAndShallow(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref<0, scale - 1>(), col); + alphaGrad<1, 4>(out.template ref<2, scale - 2>(), col); + alphaGrad<3, 4>(out.template ref<1, scale - 1>(), col); + + alphaGrad<1, 4>(out.template ref(), col); + alphaGrad<1, 4>(out.template ref(), col); + alphaGrad<3, 4>(out.template ref(), col); + + alphaGrad<2, 3>(out.template ref<3, 3>(), col); + + out.template ref<2, scale - 1>() = col; + out.template ref<3, scale - 1>() = col; + out.template ref<4, scale - 1>() = col; + + out.template ref() = col; + out.template ref() = col; + } + + template + static void blendLineDiagonal(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 8>(out.template ref(), col); //conflict with other rotations for this odd scale + alphaGrad<1, 8>(out.template ref(), col); + alphaGrad<1, 8>(out.template ref(), col); // + + alphaGrad<7, 8>(out.template ref<4, 3>(), col); + alphaGrad<7, 8>(out.template ref<3, 4>(), col); + + out.template ref<4, 4>() = col; + } + + template + static void blendCorner(uint32_t col, OutputMatrix& out) + { + //model a round corner + alphaGrad<86, 100>(out.template ref<4, 4>(), col); //exact: 0.8631434088 + alphaGrad<23, 100>(out.template ref<4, 3>(), col); //0.2306749731 + alphaGrad<23, 100>(out.template ref<3, 4>(), col); //0.2306749731 + //alphaGrad<1, 64>(out.template ref<4, 2>(), col); //0.01676812367 -> negligible + avoid conflicts with other rotations for this odd scale + //alphaGrad<1, 64>(out.template ref<2, 4>(), col); //0.01676812367 + } +}; + + +template +struct Scaler6x : public ColorGradient +{ + static const int scale = 6; + + template //bring template function into scope for GCC + static void alphaGrad(uint32_t& pixBack, uint32_t pixFront) { ColorGradient::template alphaGrad(pixBack, pixFront); } + + + template + static void blendLineShallow(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref(), col); + alphaGrad<1, 4>(out.template ref(), col); + alphaGrad<1, 4>(out.template ref(), col); + + alphaGrad<3, 4>(out.template ref(), col); + alphaGrad<3, 4>(out.template ref(), col); + alphaGrad<3, 4>(out.template ref(), col); + + out.template ref() = col; + out.template ref() = col; + out.template ref() = col; + out.template ref() = col; + + out.template ref() = col; + out.template ref() = col; + } + + template + static void blendLineSteep(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref<0, scale - 1>(), col); + alphaGrad<1, 4>(out.template ref<2, scale - 2>(), col); + alphaGrad<1, 4>(out.template ref<4, scale - 3>(), col); + + alphaGrad<3, 4>(out.template ref<1, scale - 1>(), col); + alphaGrad<3, 4>(out.template ref<3, scale - 2>(), col); + alphaGrad<3, 4>(out.template ref<5, scale - 3>(), col); + + out.template ref<2, scale - 1>() = col; + out.template ref<3, scale - 1>() = col; + out.template ref<4, scale - 1>() = col; + out.template ref<5, scale - 1>() = col; + + out.template ref<4, scale - 2>() = col; + out.template ref<5, scale - 2>() = col; + } + + template + static void blendLineSteepAndShallow(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref<0, scale - 1>(), col); + alphaGrad<1, 4>(out.template ref<2, scale - 2>(), col); + alphaGrad<3, 4>(out.template ref<1, scale - 1>(), col); + alphaGrad<3, 4>(out.template ref<3, scale - 2>(), col); + + alphaGrad<1, 4>(out.template ref(), col); + alphaGrad<1, 4>(out.template ref(), col); + alphaGrad<3, 4>(out.template ref(), col); + alphaGrad<3, 4>(out.template ref(), col); + + out.template ref<2, scale - 1>() = col; + out.template ref<3, scale - 1>() = col; + out.template ref<4, scale - 1>() = col; + out.template ref<5, scale - 1>() = col; + + out.template ref<4, scale - 2>() = col; + out.template ref<5, scale - 2>() = col; + + out.template ref() = col; + out.template ref() = col; + } + + template + static void blendLineDiagonal(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 2>(out.template ref(), col); + alphaGrad<1, 2>(out.template ref(), col); + alphaGrad<1, 2>(out.template ref(), col); + + out.template ref() = col; + out.template ref() = col; + out.template ref() = col; + } + + template + static void blendCorner(uint32_t col, OutputMatrix& out) + { + //model a round corner + alphaGrad<97, 100>(out.template ref<5, 5>(), col); //exact: 0.9711013910 + alphaGrad<42, 100>(out.template ref<4, 5>(), col); //0.4236372243 + alphaGrad<42, 100>(out.template ref<5, 4>(), col); //0.4236372243 + alphaGrad< 6, 100>(out.template ref<5, 3>(), col); //0.05652034508 + alphaGrad< 6, 100>(out.template ref<3, 5>(), col); //0.05652034508 + } +}; + +//------------------------------------------------------------------------------------ + +struct ColorDistanceRGB +{ + static double dist(uint32_t pix1, uint32_t pix2, double luminanceWeight) + { + return distYCbCrBuffered(pix1, pix2); + + //if (pix1 == pix2) //about 4% perf boost + // return 0; + //return distYCbCr(pix1, pix2, luminanceWeight); + } +}; + +struct ColorDistanceARGB +{ + static double dist(uint32_t pix1, uint32_t pix2, double luminanceWeight) + { + const double a1 = getAlpha(pix1) / 255.0 ; + const double a2 = getAlpha(pix2) / 255.0 ; + /* + Requirements for a color distance handling alpha channel: with a1, a2 in [0, 1] + + 1. if a1 = a2, distance should be: a1 * distYCbCr() + 2. if a1 = 0, distance should be: a2 * distYCbCr(black, white) = a2 * 255 + 3. if a1 = 1, ??? maybe: 255 * (1 - a2) + a2 * distYCbCr() + */ + + //return std::min(a1, a2) * distYCbCrBuffered(pix1, pix2) + 255 * abs(a1 - a2); + //=> following code is 15% faster: + const double d = distYCbCrBuffered(pix1, pix2); + if (a1 < a2) + return a1 * d + 255 * (a2 - a1); + else + return a2 * d + 255 * (a1 - a2); + + //alternative? return std::sqrt(a1 * a2 * square(distYCbCrBuffered(pix1, pix2)) + square(255 * (a1 - a2))); + } +}; + + +struct ColorDistanceUnbufferedARGB +{ + static double dist(uint32_t pix1, uint32_t pix2, double luminanceWeight) + { + const double a1 = getAlpha(pix1) / 255.0 ; + const double a2 = getAlpha(pix2) / 255.0 ; + + const double d = distYCbCr(pix1, pix2, luminanceWeight); + if (a1 < a2) + return a1 * d + 255 * (a2 - a1); + else + return a2 * d + 255 * (a1 - a2); + } +}; + + +struct ColorGradientRGB +{ + template + static void alphaGrad(uint32_t& pixBack, uint32_t pixFront) + { + pixBack = gradientRGB(pixFront, pixBack); + } +}; + +struct ColorGradientARGB +{ + template + static void alphaGrad(uint32_t& pixBack, uint32_t pixFront) + { + pixBack = gradientARGB(pixFront, pixBack); + } +}; +} + + +void xbrz::scale(size_t factor, const uint32_t* src, uint32_t* trg, int srcWidth, int srcHeight, ColorFormat colFmt, const xbrz::ScalerCfg& cfg, int yFirst, int yLast) +{ +if (factor == 1) + { + std::copy(src + yFirst * srcWidth, src + yLast * srcWidth, trg); + return; + } + + static_assert(SCALE_FACTOR_MAX == 6, ""); + switch (colFmt) + { + case ColorFormat::RGB: + switch (factor) + { + case 2: + return scaleImage, ColorDistanceRGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 3: + return scaleImage, ColorDistanceRGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 4: + return scaleImage, ColorDistanceRGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 5: + return scaleImage, ColorDistanceRGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 6: + return scaleImage, ColorDistanceRGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + } + break; + + case ColorFormat::ARGB: + switch (factor) + { + case 2: + return scaleImage, ColorDistanceARGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 3: + return scaleImage, ColorDistanceARGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 4: + return scaleImage, ColorDistanceARGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 5: + return scaleImage, ColorDistanceARGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 6: + return scaleImage, ColorDistanceARGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + } + break; + + case ColorFormat::ARGB_UNBUFFERED: + switch (factor) + { + case 2: + return scaleImage, ColorDistanceUnbufferedARGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 3: + return scaleImage, ColorDistanceUnbufferedARGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 4: + return scaleImage, ColorDistanceUnbufferedARGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 5: + return scaleImage, ColorDistanceUnbufferedARGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 6: + return scaleImage, ColorDistanceUnbufferedARGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + } + break; + } + assert(false); +} + + +bool xbrz::equalColorTest(uint32_t col1, uint32_t col2, ColorFormat colFmt, double luminanceWeight, double equalColorTolerance) +{ + switch (colFmt) + { + case ColorFormat::RGB: + return ColorDistanceRGB::dist(col1, col2, luminanceWeight) < equalColorTolerance; + case ColorFormat::ARGB: + return ColorDistanceARGB::dist(col1, col2, luminanceWeight) < equalColorTolerance; + case ColorFormat::ARGB_UNBUFFERED: + return ColorDistanceUnbufferedARGB::dist(col1, col2, luminanceWeight) < equalColorTolerance; + } + assert(false); + return false; +} + + +void xbrz::bilinearScale(const uint32_t* src, int srcWidth, int srcHeight, + /**/ uint32_t* trg, int trgWidth, int trgHeight) +{ + bilinearScale(src, srcWidth, srcHeight, srcWidth * sizeof(uint32_t), + trg, trgWidth, trgHeight, trgWidth * sizeof(uint32_t), + 0, trgHeight, [](uint32_t pix) { return pix; }); +} + + +void xbrz::nearestNeighborScale(const uint32_t* src, int srcWidth, int srcHeight, + /**/ uint32_t* trg, int trgWidth, int trgHeight) +{ + nearestNeighborScale(src, srcWidth, srcHeight, srcWidth * sizeof(uint32_t), + trg, trgWidth, trgHeight, trgWidth * sizeof(uint32_t), + 0, trgHeight, [](uint32_t pix) { return pix; }); +} + + +#if 0 +//#include +void bilinearScaleCpu(const uint32_t* src, int srcWidth, int srcHeight, + /**/ uint32_t* trg, int trgWidth, int trgHeight) +{ + const int TASK_GRANULARITY = 16; + + concurrency::task_group tg; + + for (int i = 0; i < trgHeight; i += TASK_GRANULARITY) + tg.run([=] + { + const int iLast = std::min(i + TASK_GRANULARITY, trgHeight); + xbrz::bilinearScale(src, srcWidth, srcHeight, srcWidth * sizeof(uint32_t), + trg, trgWidth, trgHeight, trgWidth * sizeof(uint32_t), + i, iLast, [](uint32_t pix) { return pix; }); + }); + tg.wait(); +} + + +//Perf: AMP vs CPU: merely ~10% shorter runtime (scaling 1280x800 -> 1920x1080) +//#include +void bilinearScaleAmp(const uint32_t* src, int srcWidth, int srcHeight, //throw concurrency::runtime_exception + /**/ uint32_t* trg, int trgWidth, int trgHeight) +{ + //C++ AMP reference: https://msdn.microsoft.com/en-us/library/hh289390.aspx + //introduction to C++ AMP: https://msdn.microsoft.com/en-us/magazine/hh882446.aspx + using namespace concurrency; + //TODO: pitch + + if (srcHeight <= 0 || srcWidth <= 0) return; + + const float scaleX = static_cast(trgWidth ) / srcWidth; + const float scaleY = static_cast(trgHeight) / srcHeight; + + array_view srcView(srcHeight, srcWidth, src); + array_view< uint32_t, 2> trgView(trgHeight, trgWidth, trg); + trgView.discard_data(); + + parallel_for_each(trgView.extent, [=](index<2> idx) restrict(amp) //throw ? + { + const int y = idx[0]; + const int x = idx[1]; + //Perf notes: + // -> float-based calculation is (almost) 2x as fas as double! + // -> no noticeable improvement via tiling: https://msdn.microsoft.com/en-us/magazine/hh882447.aspx + // -> no noticeable improvement with restrict(amp,cpu) + // -> iterating over y-axis only is significantly slower! + // -> pre-calculating x,y-dependent variables in a buffer + array_view<> is ~ 20 % slower! + const int y1 = srcHeight * y / trgHeight; + int y2 = y1 + 1; + if (y2 == srcHeight) --y2; + + const float yy1 = y / scaleY - y1; + const float y2y = 1 - yy1; + //------------------------------------- + const int x1 = srcWidth * x / trgWidth; + int x2 = x1 + 1; + if (x2 == srcWidth) --x2; + + const float xx1 = x / scaleX - x1; + const float x2x = 1 - xx1; + //------------------------------------- + const float x2xy2y = x2x * y2y; + const float xx1y2y = xx1 * y2y; + const float x2xyy1 = x2x * yy1; + const float xx1yy1 = xx1 * yy1; + + auto interpolate = [=](int offset) + { + /* + https://en.wikipedia.org/wiki/Bilinear_interpolation + (c11(x2 - x) + c21(x - x1)) * (y2 - y ) + + (c12(x2 - x) + c22(x - x1)) * (y - y1) + */ + const auto c11 = (srcView(y1, x1) >> (8 * offset)) & 0xff; + const auto c21 = (srcView(y1, x2) >> (8 * offset)) & 0xff; + const auto c12 = (srcView(y2, x1) >> (8 * offset)) & 0xff; + const auto c22 = (srcView(y2, x2) >> (8 * offset)) & 0xff; + + return c11 * x2xy2y + c21 * xx1y2y + + c12 * x2xyy1 + c22 * xx1yy1; + }; + + const float bi = interpolate(0); + const float gi = interpolate(1); + const float ri = interpolate(2); + const float ai = interpolate(3); + + const auto b = static_cast(bi + 0.5f); + const auto g = static_cast(gi + 0.5f); + const auto r = static_cast(ri + 0.5f); + const auto a = static_cast(ai + 0.5f); + + trgView(y, x) = (a << 24) | (r << 16) | (g << 8) | b; + }); + trgView.synchronize(); //throw ? +} +#endif diff --git a/xBRZ/src/xbrz.h b/xBRZ/src/xbrz.h new file mode 100755 index 00000000..f7f7169a --- /dev/null +++ b/xBRZ/src/xbrz.h @@ -0,0 +1,80 @@ +// **************************************************************************** +// * This file is part of the xBRZ project. It is distributed under * +// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// * * +// * Additionally and as a special exception, the author gives permission * +// * to link the code of this program with the following libraries * +// * (or with modified versions that use the same licenses), and distribute * +// * linked combinations including the two: MAME, FreeFileSync, Snes9x, ePSXe * +// * You must obey the GNU General Public License in all respects for all of * +// * the code used other than MAME, FreeFileSync, Snes9x, ePSXe. * +// * If you modify this file, you may extend this exception to your version * +// * of the file, but you are not obligated to do so. If you do not wish to * +// * do so, delete this exception statement from your version. * +// **************************************************************************** + +#ifndef XBRZ_HEADER_3847894708239054 +#define XBRZ_HEADER_3847894708239054 + +#include //size_t +#include //uint32_t +#include +#include "xbrz_config.h" + + +namespace xbrz +{ +/* +------------------------------------------------------------------------- +| xBRZ: "Scale by rules" - high quality image upscaling filter by Zenju | +------------------------------------------------------------------------- +using a modified approach of xBR: +http://board.byuu.org/viewtopic.php?f=10&t=2248 +- new rule set preserving small image features +- highly optimized for performance +- support alpha channel +- support multithreading +- support 64-bit architectures +- support processing image slices +- support scaling up to 6xBRZ +*/ + +enum class ColorFormat //from high bits -> low bits, 8 bit per channel +{ + RGB, //8 bit for each red, green, blue, upper 8 bits unused + ARGB, //including alpha channel, BGRA byte order on little-endian machines + ARGB_UNBUFFERED, //like ARGB, but without the one-time buffer creation overhead (ca. 100 - 300 ms) at the expense of a slightly slower scaling time +}; + +const int SCALE_FACTOR_MAX = 6; + +/* +-> map source (srcWidth * srcHeight) to target (scale * width x scale * height) image, optionally processing a half-open slice of rows [yFirst, yLast) only +-> support for source/target pitch in bytes! +-> if your emulator changes only a few image slices during each cycle (e.g. DOSBox) then there's no need to run xBRZ on the complete image: + Just make sure you enlarge the source image slice by 2 rows on top and 2 on bottom (this is the additional range the xBRZ algorithm is using during analysis) + CAVEAT: If there are multiple changed slices, make sure they do not overlap after adding these additional rows in order to avoid a memory race condition + in the target image data if you are using multiple threads for processing each enlarged slice! + +THREAD-SAFETY: - parts of the same image may be scaled by multiple threads as long as the [yFirst, yLast) ranges do not overlap! + - there is a minor inefficiency for the first row of a slice, so avoid processing single rows only; suggestion: process at least 8-16 rows +*/ +void scale(size_t factor, //valid range: 2 - SCALE_FACTOR_MAX + const uint32_t* src, uint32_t* trg, int srcWidth, int srcHeight, + ColorFormat colFmt, + const ScalerCfg& cfg = ScalerCfg(), + int yFirst = 0, int yLast = std::numeric_limits::max()); //slice of source image + +void bilinearScale(const uint32_t* src, int srcWidth, int srcHeight, + /**/ uint32_t* trg, int trgWidth, int trgHeight); + +void nearestNeighborScale(const uint32_t* src, int srcWidth, int srcHeight, + /**/ uint32_t* trg, int trgWidth, int trgHeight); + + +//parameter tuning +bool equalColorTest(uint32_t col1, uint32_t col2, ColorFormat colFmt, double luminanceWeight, double equalColorTolerance); +} + +#endif diff --git a/xBRZ/src/xbrz_config.h b/xBRZ/src/xbrz_config.h new file mode 100755 index 00000000..fc036d17 --- /dev/null +++ b/xBRZ/src/xbrz_config.h @@ -0,0 +1,35 @@ +// **************************************************************************** +// * This file is part of the xBRZ project. It is distributed under * +// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// * * +// * Additionally and as a special exception, the author gives permission * +// * to link the code of this program with the following libraries * +// * (or with modified versions that use the same licenses), and distribute * +// * linked combinations including the two: MAME, FreeFileSync, Snes9x, ePSXe * +// * You must obey the GNU General Public License in all respects for all of * +// * the code used other than MAME, FreeFileSync, Snes9x, ePSXe. * +// * If you modify this file, you may extend this exception to your version * +// * of the file, but you are not obligated to do so. If you do not wish to * +// * do so, delete this exception statement from your version. * +// **************************************************************************** + +#ifndef XBRZ_CONFIG_HEADER_284578425345 +#define XBRZ_CONFIG_HEADER_284578425345 + +//do NOT include any headers here! used by xBRZ_dll!!! + +namespace xbrz +{ +struct ScalerCfg +{ + double luminanceWeight = 1; + double equalColorTolerance = 30; + double centerDirectionBias = 4; + double dominantDirectionThreshold = 3.6; + double steepDirectionThreshold = 2.2; + double newTestAttribute = 0; //unused; test new parameters +}; +} + +#endif diff --git a/xBRZ/src/xbrz_tools.h b/xBRZ/src/xbrz_tools.h new file mode 100755 index 00000000..aaa2b769 --- /dev/null +++ b/xBRZ/src/xbrz_tools.h @@ -0,0 +1,268 @@ +// **************************************************************************** +// * This file is part of the xBRZ project. It is distributed under * +// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// * * +// * Additionally and as a special exception, the author gives permission * +// * to link the code of this program with the following libraries * +// * (or with modified versions that use the same licenses), and distribute * +// * linked combinations including the two: MAME, FreeFileSync, Snes9x, ePSXe * +// * You must obey the GNU General Public License in all respects for all of * +// * the code used other than MAME, FreeFileSync, Snes9x, ePSXe. * +// * If you modify this file, you may extend this exception to your version * +// * of the file, but you are not obligated to do so. If you do not wish to * +// * do so, delete this exception statement from your version. * +// **************************************************************************** + +#ifndef XBRZ_TOOLS_H_825480175091875 +#define XBRZ_TOOLS_H_825480175091875 + +#include +#include +#include + + +namespace xbrz +{ +template inline +unsigned char getByte(uint32_t val) { return static_cast((val >> (8 * N)) & 0xff); } + +inline unsigned char getAlpha(uint32_t pix) { return getByte<3>(pix); } +inline unsigned char getRed (uint32_t pix) { return getByte<2>(pix); } +inline unsigned char getGreen(uint32_t pix) { return getByte<1>(pix); } +inline unsigned char getBlue (uint32_t pix) { return getByte<0>(pix); } + +inline uint32_t makePixel(unsigned char a, unsigned char r, unsigned char g, unsigned char b) { return (a << 24) | (r << 16) | (g << 8) | b; } +inline uint32_t makePixel( unsigned char r, unsigned char g, unsigned char b) { return (r << 16) | (g << 8) | b; } + +inline uint32_t rgb555to888(uint16_t pix) { return ((pix & 0x7C00) << 9) | ((pix & 0x03E0) << 6) | ((pix & 0x001F) << 3); } +inline uint32_t rgb565to888(uint16_t pix) { return ((pix & 0xF800) << 8) | ((pix & 0x07E0) << 5) | ((pix & 0x001F) << 3); } + +inline uint16_t rgb888to555(uint32_t pix) { return static_cast(((pix & 0xF80000) >> 9) | ((pix & 0x00F800) >> 6) | ((pix & 0x0000F8) >> 3)); } +inline uint16_t rgb888to565(uint32_t pix) { return static_cast(((pix & 0xF80000) >> 8) | ((pix & 0x00FC00) >> 5) | ((pix & 0x0000F8) >> 3)); } + + +template inline +Pix* byteAdvance(Pix* ptr, int bytes) +{ + using PixNonConst = typename std::remove_cv::type; + using PixByte = typename std::conditional::value, char, const char>::type; + + static_assert(std::is_integral::value, "Pix* is expected to be cast-able to char*"); + + return reinterpret_cast(reinterpret_cast(ptr) + bytes); +} + + +//fill block with the given color +template inline +void fillBlock(Pix* trg, int pitch, Pix col, int blockWidth, int blockHeight) +{ + //for (int y = 0; y < blockHeight; ++y, trg = byteAdvance(trg, pitch)) + // std::fill(trg, trg + blockWidth, col); + + for (int y = 0; y < blockHeight; ++y, trg = byteAdvance(trg, pitch)) + for (int x = 0; x < blockWidth; ++x) + trg[x] = col; +} + + +//nearest-neighbor (going over target image - slow for upscaling, since source is read multiple times missing out on cache! Fast for similar image sizes!) +template +void nearestNeighborScale(const PixSrc* src, int srcWidth, int srcHeight, int srcPitch, + /**/ PixTrg* trg, int trgWidth, int trgHeight, int trgPitch, + int yFirst, int yLast, PixConverter pixCvrt /*convert PixSrc to PixTrg*/) +{ + static_assert(std::is_integral::value, "PixSrc* is expected to be cast-able to char*"); + static_assert(std::is_integral::value, "PixTrg* is expected to be cast-able to char*"); + + static_assert(std::is_same::value, "PixConverter returning wrong pixel format"); + + if (srcPitch < srcWidth * static_cast(sizeof(PixSrc)) || + trgPitch < trgWidth * static_cast(sizeof(PixTrg))) + { + assert(false); + return; + } + + yFirst = std::max(yFirst, 0); + yLast = std::min(yLast, trgHeight); + if (yFirst >= yLast || srcHeight <= 0 || srcWidth <= 0) return; + + for (int y = yFirst; y < yLast; ++y) + { + const int ySrc = srcHeight * y / trgHeight; + const PixSrc* const srcLine = byteAdvance(src, ySrc * srcPitch); + PixTrg* const trgLine = byteAdvance(trg, y * trgPitch); + + for (int x = 0; x < trgWidth; ++x) + { + const int xSrc = srcWidth * x / trgWidth; + trgLine[x] = pixCvrt(srcLine[xSrc]); + } + } +} + + +//nearest-neighbor (going over source image - fast for upscaling, since source is read only once +template +void nearestNeighborScaleOverSource(const PixSrc* src, int srcWidth, int srcHeight, int srcPitch, + /**/ PixTrg* trg, int trgWidth, int trgHeight, int trgPitch, + int yFirst, int yLast, PixConverter pixCvrt /*convert PixSrc to PixTrg*/) +{ + static_assert(std::is_integral::value, "PixSrc* is expected to be cast-able to char*"); + static_assert(std::is_integral::value, "PixTrg* is expected to be cast-able to char*"); + + static_assert(std::is_same::value, "PixConverter returning wrong pixel format"); + + if (srcPitch < srcWidth * static_cast(sizeof(PixSrc)) || + trgPitch < trgWidth * static_cast(sizeof(PixTrg))) + { + assert(false); + return; + } + + yFirst = std::max(yFirst, 0); + yLast = std::min(yLast, srcHeight); + if (yFirst >= yLast || trgWidth <= 0 || trgHeight <= 0) return; + + for (int y = yFirst; y < yLast; ++y) + { + //mathematically: ySrc = floor(srcHeight * yTrg / trgHeight) + // => search for integers in: [ySrc, ySrc + 1) * trgHeight / srcHeight + + //keep within for loop to support MT input slices! + const int yTrgFirst = ( y * trgHeight + srcHeight - 1) / srcHeight; //=ceil(y * trgHeight / srcHeight) + const int yTrgLast = ((y + 1) * trgHeight + srcHeight - 1) / srcHeight; //=ceil(((y + 1) * trgHeight) / srcHeight) + const int blockHeight = yTrgLast - yTrgFirst; + + if (blockHeight > 0) + { + const PixSrc* srcLine = byteAdvance(src, y * srcPitch); + /**/ PixTrg* trgLine = byteAdvance(trg, yTrgFirst * trgPitch); + int xTrgFirst = 0; + + for (int x = 0; x < srcWidth; ++x) + { + const int xTrgLast = ((x + 1) * trgWidth + srcWidth - 1) / srcWidth; + const int blockWidth = xTrgLast - xTrgFirst; + if (blockWidth > 0) + { + xTrgFirst = xTrgLast; + + const auto trgPix = pixCvrt(srcLine[x]); + fillBlock(trgLine, trgPitch, trgPix, blockWidth, blockHeight); + trgLine += blockWidth; + } + } + } + } +} + + +template +void bilinearScale(const uint32_t* src, int srcWidth, int srcHeight, int srcPitch, + /**/ PixTrg* trg, int trgWidth, int trgHeight, int trgPitch, + int yFirst, int yLast, PixConverter pixCvrt /*convert uint32_t to PixTrg*/) +{ + static_assert(std::is_integral::value, "PixTrg* is expected to be cast-able to char*"); + static_assert(std::is_same::value, "PixConverter returning wrong pixel format"); + + if (srcPitch < srcWidth * static_cast(sizeof(uint32_t)) || + trgPitch < trgWidth * static_cast(sizeof(PixTrg))) + { + assert(false); + return; + } + + yFirst = std::max(yFirst, 0); + yLast = std::min(yLast, trgHeight); + if (yFirst >= yLast || srcHeight <= 0 || srcWidth <= 0) return; + + const double scaleX = static_cast(trgWidth ) / srcWidth; + const double scaleY = static_cast(trgHeight) / srcHeight; + + //perf notes: + // -> double-based calculation is (slightly) faster than float + // -> precalculation gives significant boost; std::vector<> memory allocation is negligible! + struct CoeffsX + { + int x1 = 0; + int x2 = 0; + double xx1 = 0; + double x2x = 0; + }; + std::vector buf(trgWidth); + for (int x = 0; x < trgWidth; ++x) + { + const int x1 = srcWidth * x / trgWidth; + int x2 = x1 + 1; + if (x2 == srcWidth) --x2; + + const double xx1 = x / scaleX - x1; + const double x2x = 1 - xx1; + + buf[x] = { x1, x2, xx1, x2x }; + } + + for (int y = yFirst; y < yLast; ++y) + { + const int y1 = srcHeight * y / trgHeight; + int y2 = y1 + 1; + if (y2 == srcHeight) --y2; + + const double yy1 = y / scaleY - y1; + const double y2y = 1 - yy1; + + const uint32_t* const srcLine = byteAdvance(src, y1 * srcPitch); + const uint32_t* const srcLineNext = byteAdvance(src, y2 * srcPitch); + PixTrg* const trgLine = byteAdvance(trg, y * trgPitch); + + for (int x = 0; x < trgWidth; ++x) + { + //perf: do NOT "simplify" the variable layout without measurement! + const int x1 = buf[x].x1; + const int x2 = buf[x].x2; + const double xx1 = buf[x].xx1; + const double x2x = buf[x].x2x; + + const double x2xy2y = x2x * y2y; + const double xx1y2y = xx1 * y2y; + const double x2xyy1 = x2x * yy1; + const double xx1yy1 = xx1 * yy1; + + auto interpolate = [=](int offset) + { + /* + https://en.wikipedia.org/wiki/Bilinear_interpolation + (c11(x2 - x) + c21(x - x1)) * (y2 - y ) + + (c12(x2 - x) + c22(x - x1)) * (y - y1) + */ + const auto c11 = (srcLine [x1] >> (8 * offset)) & 0xff; + const auto c21 = (srcLine [x2] >> (8 * offset)) & 0xff; + const auto c12 = (srcLineNext[x1] >> (8 * offset)) & 0xff; + const auto c22 = (srcLineNext[x2] >> (8 * offset)) & 0xff; + + return c11 * x2xy2y + c21 * xx1y2y + + c12 * x2xyy1 + c22 * xx1yy1; + }; + + const double bi = interpolate(0); + const double gi = interpolate(1); + const double ri = interpolate(2); + const double ai = interpolate(3); + + const auto b = static_cast(bi + 0.5); + const auto g = static_cast(gi + 0.5); + const auto r = static_cast(ri + 0.5); + const auto a = static_cast(ai + 0.5); + + const uint32_t trgPix = (a << 24) | (r << 16) | (g << 8) | b; + + trgLine[x] = pixCvrt(trgPix); + } + } +} +} + +#endif //XBRZ_TOOLS_H_825480175091875 diff --git a/zen/deprecate.h b/zen/deprecate.h index 2a6bcb0d..1f4e6ab4 100755 --- a/zen/deprecate.h +++ b/zen/deprecate.h @@ -8,7 +8,11 @@ #define DEPRECATE_H_234897087787348 //compiler macros: http://predef.sourceforge.net/precomp.html +#ifdef __GNUC__ #define ZEN_DEPRECATE __attribute__ ((deprecated)) +#else + #error add your platform here! +#endif #endif //DEPRECATE_H_234897087787348 diff --git a/zen/file_access.cpp b/zen/file_access.cpp index 1711e934..18c0ed26 100755 --- a/zen/file_access.cpp +++ b/zen/file_access.cpp @@ -32,13 +32,45 @@ using namespace zen; Opt zen::parsePathComponents(const Zstring& itemPath) { + auto doParse = [&](int sepCountVolumeRoot, bool rootWithSep) -> Opt + { + const Zstring itemPathFmt = appendSeparator(itemPath); //simplify analysis of root without seperator, e.g. \\server-name\share + int sepCount = 0; + for (auto it = itemPathFmt.begin(); it != itemPathFmt.end(); ++it) + if (*it == FILE_NAME_SEPARATOR) + if (++sepCount == sepCountVolumeRoot) + { + Zstring rootPath(itemPathFmt.begin(), rootWithSep ? it + 1 : it); + + Zstring relPath(it + 1, itemPathFmt.end()); + trim(relPath, true, true, [](Zchar c) { return c == FILE_NAME_SEPARATOR; }); + + return PathComponents({ rootPath, relPath }); + } + return NoValue(); + }; + if (startsWith(itemPath, "/")) { - Zstring relPath(itemPath.c_str() + 1); - if (endsWith(relPath, FILE_NAME_SEPARATOR)) - relPath.pop_back(); - return PathComponents({ "/", relPath }); + if (startsWith(itemPath, "/media/")) + { + //Ubuntu: e.g. /media/zenju/DEVICE_NAME + if (const char* username = ::getenv("USER")) + if (startsWith(itemPath, std::string("/media/") + username + "/")) + return doParse(4 /*sepCountVolumeRoot*/, false /*rootWithSep*/); + + //Ubuntu: e.g. /media/cdrom0 + return doParse(3 /*sepCountVolumeRoot*/, false /*rootWithSep*/); + } + + if (startsWith(itemPath, "/run/media/")) //Suse: e.g. /run/media/zenju/DEVICE_NAME + if (const char* username = ::getenv("USER")) + if (startsWith(itemPath, std::string("/run/media/") + username + "/")) + return doParse(5 /*sepCountVolumeRoot*/, false /*rootWithSep*/); + + return doParse(1 /*sepCountVolumeRoot*/, true /*rootWithSep*/); } + //we do NOT support relative paths! return NoValue(); } @@ -115,9 +147,9 @@ PathStatus zen::getPathStatus(const Zstring& itemPath) //throw FileError Opt zen::getItemTypeIfExists(const Zstring& itemPath) //throw FileError { - const PathStatus pd = getPathStatus(itemPath); //throw FileError - if (pd.relPath.empty()) - return pd.existingType; + const PathStatus ps = getPathStatus(itemPath); //throw FileError + if (ps.relPath.empty()) + return ps.existingType; return NoValue(); } @@ -541,23 +573,18 @@ void zen::createDirectoryIfMissingRecursion(const Zstring& dirPath) //throw File } catch (FileError&) { - Opt pd; - try { pd = getPathStatus(dirPath); /*throw FileError*/ } - catch (FileError&) {} //previous exception is more relevant + const PathStatus ps = getPathStatus(dirPath); //throw FileError + if (ps.existingType == ItemType::FILE) + throw; - if (pd && - pd->existingType != ItemType::FILE && - pd->relPath.size() != 1) //don't repeat the very same createDirectory() call from above! - { - Zstring intermediatePath = pd->existingPath; - for (const Zstring& itemName : pd->relPath) + //ps.relPath.size() == 1 => same createDirectory() call from above? Maybe parent folder was created by parallel thread shortly after failure! + Zstring intermediatePath = ps.existingPath; + for (const Zstring& itemName : ps.relPath) + try { - intermediatePath = appendSeparator(intermediatePath) + itemName; - createDirectory(intermediatePath); //throw FileError, (ErrorTargetExisting) + createDirectory(intermediatePath = appendSeparator(intermediatePath) + itemName); //throw FileError, ErrorTargetExisting } - return; - } - throw; + catch (ErrorTargetExisting&) {} //possible, if createDirectoryIfMissingRecursion() is run in parallel } } diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp index bc53f206..e342c8ec 100755 --- a/zen/file_traverser.cpp +++ b/zen/file_traverser.cpp @@ -24,14 +24,6 @@ void zen::traverseFolder(const Zstring& dirPath, { try { - /* quote: "Since POSIX.1 does not specify the size of the d_name field, and other nonstandard fields may precede - that field within the dirent structure, portable applications that use readdir_r() should allocate - the buffer whose address is passed in entry as follows: - len = offsetof(struct dirent, d_name) + pathconf(dirPath, _PC_NAME_MAX) + 1 - entryp = malloc(len); */ - const size_t nameMax = std::max(::pathconf(dirPath.c_str(), _PC_NAME_MAX), 10000); //::pathconf may return long(-1) - std::vector buffer(offsetof(struct ::dirent, d_name) + nameMax + 1); - DIR* folder = ::opendir(dirPath.c_str()); //directory must NOT end with path separator, except "/" if (!folder) THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot open directory %x."), L"%x", fmtPath(dirPath)), L"opendir"); @@ -39,13 +31,16 @@ void zen::traverseFolder(const Zstring& dirPath, for (;;) { - struct ::dirent* dirEntry = nullptr; - if (::readdir_r(folder, reinterpret_cast< ::dirent*>(&buffer[0]), &dirEntry) != 0) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), L"readdir_r"); - //don't retry but restart dir traversal on error! https://blogs.msdn.microsoft.com/oldnewthing/20140612-00/?p=753/ + errno = 0; + const struct ::dirent* dirEntry = ::readdir(folder); //don't use readdir_r(), see comment in native.cpp + if (!dirEntry) + { + if (errno == 0) //errno left unchanged => no more items + return; - if (!dirEntry) //no more items - return; + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), L"readdir"); + //don't retry but restart dir traversal on error! https://blogs.msdn.microsoft.com/oldnewthing/20140612-00/?p=753/ + } //don't return "." and ".." const char* itemNameRaw = dirEntry->d_name; @@ -56,7 +51,7 @@ void zen::traverseFolder(const Zstring& dirPath, const Zstring& itemName = itemNameRaw; if (itemName.empty()) //checks result of normalizeUtfForPosix, too! - throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), L"readdir_r: Data corruption; item with empty name."); + throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), L"readdir: Data corruption; item with empty name."); const Zstring& itemPath = appendSeparator(dirPath) + itemName; diff --git a/zen/guid.h b/zen/guid.h index b2ada48c..6cffc708 100755 --- a/zen/guid.h +++ b/zen/guid.h @@ -9,10 +9,14 @@ #include +#ifdef __GNUC__ //boost should clean this mess up #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif #include +#ifdef __GNUC__ #pragma GCC diagnostic pop +#endif namespace zen diff --git a/zen/scope_guard.h b/zen/scope_guard.h index 328e2caa..6a732c9f 100755 --- a/zen/scope_guard.h +++ b/zen/scope_guard.h @@ -13,7 +13,7 @@ //std::uncaught_exceptions() currently unsupported on GCC and Clang => clean up ASAP - static_assert(__GNUC__ < 7 || (__GNUC__ == 7 && (__GNUC_MINOR__ < 3 || (__GNUC_MINOR__ == 3 && __GNUC_PATCHLEVEL__ <= 0))), "check std::uncaught_exceptions support"); + static_assert(__GNUC__ < 7 || (__GNUC__ == 7 && (__GNUC_MINOR__ < 3 || (__GNUC_MINOR__ == 3 && __GNUC_PATCHLEVEL__ <= 1))), "check std::uncaught_exceptions support"); namespace __cxxabiv1 { diff --git a/zen/stl_tools.h b/zen/stl_tools.h index ba4a6c89..f09639e1 100755 --- a/zen/stl_tools.h +++ b/zen/stl_tools.h @@ -58,9 +58,8 @@ template bool equal(InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, InputIterator2 last2); -template size_t hashBytes (ByteIterator first, ByteIterator last); -template size_t hashBytesAppend(size_t hashVal, ByteIterator first, ByteIterator last); - +template Num hashBytes (ByteIterator first, ByteIterator last); +template Num hashBytesAppend(Num hashVal, ByteIterator first, ByteIterator last); //support for custom string classes in std::unordered_set/map struct StringHash @@ -69,7 +68,7 @@ struct StringHash size_t operator()(const String& str) const { const auto* strFirst = strBegin(str); - return hashBytes(reinterpret_cast(strFirst), + return hashBytes(reinterpret_cast(strFirst), reinterpret_cast(strFirst + strLength(str))); } }; @@ -190,34 +189,29 @@ bool equal(InputIterator1 first1, InputIterator1 last1, } + - -template inline -size_t hashBytes(ByteIterator first, ByteIterator last) +//FNV-1a: http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function +template inline +Num hashBytes(ByteIterator first, ByteIterator last) { - //FNV-1a: http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function -#ifdef ZEN_BUILD_32BIT - const size_t basis = 2166136261U; -#elif defined ZEN_BUILD_64BIT - const size_t basis = 14695981039346656037ULL; -#endif - return hashBytesAppend(basis, first, last); + static_assert(std::is_integral::value, ""); + static_assert(sizeof(Num) == 4 || sizeof(Num) == 8, ""); //macOS: size_t is "unsigned long" + const Num base = sizeof(Num) == 4 ? 2166136261U : 14695981039346656037ULL; + + return hashBytesAppend(base, first, last); } -template inline -size_t hashBytesAppend(size_t hashVal, ByteIterator first, ByteIterator last) +template inline +Num hashBytesAppend(Num hashVal, ByteIterator first, ByteIterator last) { -#ifdef ZEN_BUILD_32BIT - const size_t prime = 16777619U; -#elif defined ZEN_BUILD_64BIT - const size_t prime = 1099511628211ULL; -#endif - static_assert(sizeof(typename std::iterator_traits::value_type) == 1, ""); - - for (; first != last; ++first) + static_assert(sizeof(typename std::iterator_traits::value_type) == 1, ""); + const Num prime = sizeof(Num) == 4 ? 16777619U : 1099511628211ULL; + + for (; first != last; ++first) { - hashVal ^= static_cast(*first); + hashVal ^= static_cast(*first); hashVal *= prime; } return hashVal; diff --git a/zen/thread.h b/zen/thread.h index 3721b3c7..ed61e06b 100755 --- a/zen/thread.h +++ b/zen/thread.h @@ -71,8 +71,8 @@ std::async replacement without crappy semantics: 2. does not follow C++11 [futures.async], Paragraph 5, where std::future waits for thread in destructor Example: - Zstring dirpath = ... - auto ft = zen::runAsync([=]{ return zen::dirExists(dirpath); }); + Zstring dirPath = ... + auto ft = zen::runAsync([=]{ return zen::dirExists(dirPath); }); if (ft.wait_for(std::chrono::milliseconds(200)) == std::future_status::ready && ft.get()) //dir exising */ @@ -120,10 +120,10 @@ public: Protected(const T& value) : value_(value) {} template - void access(Function fun) + auto access(Function fun) //-> decltype(fun(std::declval())) { std::lock_guard dummy(lockValue_); - fun(value_); + return fun(value_); } private: @@ -134,6 +134,60 @@ private: T value_{}; }; +//------------------------------------------------------------------------------------------ + +template +class ThreadGroup +{ +public: + ThreadGroup(size_t threadCount, const std::string& groupName) + { + for (size_t i = 0; i < threadCount; ++i) + worker_.emplace_back([this, groupName, i, threadCount] + { + setCurrentThreadName((groupName + "[" + numberTo(i + 1) + "/" + numberTo(threadCount) + "]").c_str()); + for (;;) + getNextWorkItem()(); //throw ThreadInterruption + }); + } + ~ThreadGroup() + { + for (InterruptibleThread& w : worker_) w.interrupt(); //interrupt all first, then join + for (InterruptibleThread& w : worker_) w.join(); + } + + //context of controlling thread, non-blocking: + void run(Function&& wi) + { + assert(!worker_.empty()); + { + std::lock_guard dummy(lockWork_); + workItems_.push_back(std::move(wi)); + } + conditionNewWork_.notify_all(); + } + +private: + ThreadGroup (const ThreadGroup&) = delete; + ThreadGroup& operator=(const ThreadGroup&) = delete; + + //context of worker threads, blocking: + Function getNextWorkItem() //throw ThreadInterruption + { + std::unique_lock dummy(lockWork_); + + interruptibleWait(conditionNewWork_, dummy, [this] { return !workItems_.empty(); }); //throw ThreadInterruption + warn_static("implement FIFO!?") + + Function wi = std::move(workItems_. back()); // + /**/ workItems_.pop_back(); //noexcept thanks to move + return wi; // + } + std::vector worker_; + std::mutex lockWork_; + std::vector workItems_; + std::condition_variable conditionNewWork_; +}; @@ -222,10 +276,9 @@ public: private: bool jobDone(size_t jobsTotal) const { return result_ || (jobsFinished_ >= jobsTotal); } //call while locked! - std::mutex lockResult_; size_t jobsFinished_ = 0; // - Opt result_; //our condition is: "have result" or "jobsFinished_ == jobsTotal" + Opt result_; //our condition is: "have result" or "jobsFinished_ == jobsTotal" std::condition_variable conditionJobDone_; }; @@ -256,7 +309,11 @@ Opt GetFirstResult::get() const { return asyncResult_->getResult(jobsTotal //------------------------------------------------------------------------------------------ //thread_local with non-POD seems to cause memory leaks on VS 14 => pointer only is fine: +#if defined __GNUC__ || defined __clang__ #define ZEN_THREAD_LOCAL_SPECIFIER __thread +#else + #error "Game over!" +#endif class ThreadInterruption {}; @@ -296,7 +353,8 @@ public: setConditionVar(&cv); ZEN_ON_SCOPE_EXIT(setConditionVar(nullptr)); - //"interrupted_" is not protected by cv's mutex => signal may get lost!!! => add artifical time out to mitigate! CPU: 0.25% vs 0% for longer time out! + //"interrupted_" is not protected by cv's mutex => signal may get lost!!! e.g. after condition was checked but before the wait begins + //=> add artifical time out to mitigate! CPU: 0.25% vs 0% for longer time out! while (!cv.wait_for(lock, std::chrono::milliseconds(1), [&] { return this->interrupted_ || pred(); })) ; diff --git a/zen/type_traits.h b/zen/type_traits.h index f0f96b43..83a74d1e 100755 --- a/zen/type_traits.h +++ b/zen/type_traits.h @@ -37,6 +37,12 @@ struct ResultType using Type = T; }; +template +struct GetFirstOf +{ + using Type = T; +}; + //Herb Sutter's signedness conversion helpers: http://herbsutter.com/2013/06/13/gotw-93-solution-auto-variables-part-2/ template inline auto makeSigned (T t) { return static_cast>(t); } template inline auto makeUnsigned(T t) { return static_cast>(t); } diff --git a/zen/warn_static.h b/zen/warn_static.h index 5b9a4fee..e4931c08 100755 --- a/zen/warn_static.h +++ b/zen/warn_static.h @@ -14,11 +14,13 @@ Usage: warn_static("my message") */ +#if defined __GNUC__ #define STATIC_WARNING_CONCAT_SUB(X, Y) X ## Y #define STATIC_WARNING_CONCAT(X, Y) STATIC_WARNING_CONCAT_SUB(X, Y) #define warn_static(TXT) \ typedef int STATIC_WARNING_87903124 __attribute__ ((deprecated)); \ enum { STATIC_WARNING_CONCAT(warn_static_dummy_value, __LINE__) = sizeof(STATIC_WARNING_87903124) }; +#endif #endif //WARN_STATIC_H_08724567834560832745 diff --git a/zen/xml_io.cpp b/zen/xml_io.cpp index a618f27c..dd116ef0 100755 --- a/zen/xml_io.cpp +++ b/zen/xml_io.cpp @@ -57,7 +57,7 @@ void zen::saveXmlDocument(const XmlDoc& doc, const Zstring& filePath) //throw Fi { const std::string stream = serialize(doc); //noexcept - //only update xml file if there are real changes + //only update XML file if there are real changes try { if (getFileSize(filePath) == stream.size()) //throw FileError diff --git a/zen/xml_io.h b/zen/xml_io.h index a53a7edb..81b45aa1 100755 --- a/zen/xml_io.h +++ b/zen/xml_io.h @@ -11,7 +11,7 @@ #include "file_error.h" -//combine zen::Xml and zen file i/o +//combine zen::Xml and zen file I/O //-> loadXmlDocument vs loadStream: //1. better error reporting //2. quick exit if (potentially large) input file is not an XML diff --git a/zen/zstring.cpp b/zen/zstring.cpp index ce94fe56..afa62c93 100755 --- a/zen/zstring.cpp +++ b/zen/zstring.cpp @@ -54,7 +54,9 @@ int compareNoCaseUtf8(const char* lhs, size_t lhsLen, const char* rhs, size_t rh if (!cpL || !cpR) return static_cast(!cpR) - static_cast(!cpL); - static_assert(sizeof(wchar_t) == sizeof(impl::CodePoint), ""); +//support unit-testing on Windows: CodePoint is truncated to wchar_t +static_assert(sizeof(wchar_t) == sizeof(impl::CodePoint), ""); + const wchar_t charL = ::towlower(static_cast(*cpL)); //ordering: towlower() converts to higher code points than towupper() const wchar_t charR = ::towlower(static_cast(*cpR)); //uses LC_CTYPE category of current locale if (charL != charR) @@ -65,7 +67,7 @@ int compareNoCaseUtf8(const char* lhs, size_t lhsLen, const char* rhs, size_t rh } -int cmpStringNaturalLinux(const char* lhs, size_t lhsLen, const char* rhs, size_t rhsLen) +int cmpStringNaturalLinuxTest(const char* lhs, size_t lhsLen, const char* rhs, size_t rhsLen) { const char* const lhsEnd = lhs + lhsLen; const char* const rhsEnd = rhs + rhsLen; diff --git a/zen/zstring.h b/zen/zstring.h index 258603dc..5a6ecbdd 100755 --- a/zen/zstring.h +++ b/zen/zstring.h @@ -163,7 +163,9 @@ S ciReplaceCpy(const S& str, const T& oldTerm, const U& newTerm) } } - int cmpStringNaturalLinux(const char* lhs, size_t lhsLen, const char* rhs, size_t rhsLen); +//expose for unit tests +int cmpStringNaturalLinuxTest(const char* lhs, size_t lhsLen, const char* rhs, size_t rhsLen); +inline int cmpStringNaturalLinux(const char* lhs, size_t lhsLen, const char* rhs, size_t rhsLen) { return cmpStringNaturalLinuxTest(lhs, lhsLen, rhs, rhsLen); } //--------------------------------------------------------------------------- //ZEN macro consistency checks: -- cgit