diff options
author | Daniel Wilhelm <daniel@wili.li> | 2015-10-02 14:54:34 +0200 |
---|---|---|
committer | Daniel Wilhelm <daniel@wili.li> | 2015-10-02 14:54:34 +0200 |
commit | 96e20826f358a32e38c3f052243375982543c05b (patch) | |
tree | 691efa86265fbb35cc60a1ce816423bb2a41f17b | |
parent | 6.13 (diff) | |
download | FreeFileSync-96e20826f358a32e38c3f052243375982543c05b.tar.gz FreeFileSync-96e20826f358a32e38c3f052243375982543c05b.tar.bz2 FreeFileSync-96e20826f358a32e38c3f052243375982543c05b.zip |
6.14
40 files changed, 1064 insertions, 1138 deletions
diff --git a/FreeFileSync/Build/Changelog.txt b/FreeFileSync/Build/Changelog.txt index 2d7d9f9e..a54810c8 100644 --- a/FreeFileSync/Build/Changelog.txt +++ b/FreeFileSync/Build/Changelog.txt @@ -1,5 +1,23 @@ -FreeFileSync 6.13 ------------------ +FreeFileSync 6.14 [2015-02-10] +------------------------------ +New buttons allow changing the order of folder pairs +New keyboard shortcuts for rearranging folder pairs +Preserve comparison results when deleting a specific folder pair +Allow inserting new folder pairs into the middle of the list +Append status to log file names when warnings occur +Don't interrupt immediate comparison when starting a .ffs_gui file for slow devices +Work around wxWidgets bug eating up command keys in text boxes (Linux) +Fixed incorrect parameter error when checking recycle bin on drive mounted with Paragon ExtFS (Windows) +Use colon as time stamp seperator in log file names +Refactored basic low-level file traversal routine +Optimized file icon startup procedure +Fixed occasional failure to set modification times on Samba shares (OS X) +Transfer creation times during file copy (OS X) +Support copying file times with nano-second precision (OS X) + + +FreeFileSync 6.13 [2015-01-11] +------------------------------ Fixed crash when failing to create log file during batch run Show directory traversal errors as conflict category on grid Improved file filter behavior for certain edge cases when updating the database diff --git a/FreeFileSync/Build/Help/html/Comparison Settings.html b/FreeFileSync/Build/Help/html/Comparison Settings.html index 4b1a44d3..8be6c0a8 100644 --- a/FreeFileSync/Build/Help/html/Comparison Settings.html +++ b/FreeFileSync/Build/Help/html/Comparison Settings.html @@ -15,7 +15,7 @@ <H3>Symbolic Link Handling</H3> -<P>FreeFileSync let's you choose to include symbolic links (also called symlinks or soft links) when scanning directories rather than skipping over them. When included, you can select between two ways to handle them:</P> +<P>FreeFileSync lets you choose to include symbolic links (also called symlinks or soft links) when scanning directories rather than skipping over them. When included, you can select between two ways to handle them:</P> <OL> <LI><B>Direct:</B> diff --git a/FreeFileSync/Build/Help/html/Schedule a Batch Job.html b/FreeFileSync/Build/Help/html/Schedule a Batch Job.html index efc239e8..c7b4a3a9 100644 --- a/FreeFileSync/Build/Help/html/Schedule a Batch Job.html +++ b/FreeFileSync/Build/Help/html/Schedule a Batch Job.html @@ -108,7 +108,7 @@ <BR> <hr/> - <LI><h3>Ubuntu Linux Gnome-schedule:</h3> + <LI><h3>Ubuntu Linux Gnome Scheduled Tasks:</h3> <UL> <LI>Install Gnome-schedule if necessary: <FONT FACE="Courier New, monospace">sudo apt-get install gnome-schedule</FONT> @@ -117,7 +117,7 @@ <LI>Enter the command: <FONT FACE="Courier New, monospace"><FreeFileSync installation folder>/FreeFileSync <job name>.ffs_batch</FONT><BR> <BR> - <IMG SRC="../img/ubuntuScheduler.png"> + <IMG SRC="../img/GnomeScheduler.png"> </UL> </OL> diff --git a/FreeFileSync/Build/Help/img/GnomeScheduler.png b/FreeFileSync/Build/Help/img/GnomeScheduler.png Binary files differnew file mode 100644 index 00000000..17b78a5c --- /dev/null +++ b/FreeFileSync/Build/Help/img/GnomeScheduler.png diff --git a/FreeFileSync/Build/Help/img/ubuntuScheduler.png b/FreeFileSync/Build/Help/img/ubuntuScheduler.png Binary files differdeleted file mode 100644 index 82bf329f..00000000 --- a/FreeFileSync/Build/Help/img/ubuntuScheduler.png +++ /dev/null diff --git a/FreeFileSync/Build/Languages/german.lng b/FreeFileSync/Build/Languages/german.lng index ac9e37ed..487319f8 100644 --- a/FreeFileSync/Build/Languages/german.lng +++ b/FreeFileSync/Build/Languages/german.lng @@ -7,6 +7,24 @@ <plural_definition>n == 1 ? 0 : 1</plural_definition> </header> +<source>Cannot copy attributes from %x to %y.</source> +<target>Die Attribute können nicht von %x nach %y kopiert werden.</target> + +<source>Cannot copy permissions from %x to %y.</source> +<target>Die Berechtigungen können nicht von %x nach %y kopiert werden.</target> + +<source>Move down</source> +<target>Nach unten</target> + +<source>Move up</source> +<target>Nach oben</target> + +<source>Arrange folder pair</source> +<target>Ordnerpaar anordnen</target> + +<source>Cannot find file %x.</source> +<target>Die Datei %x wurde nicht gefunden.</target> + <source>Both sides have changed since last synchronization.</source> <target>Beide Seiten wurden seit der letzten Synchronisation verändert.</target> @@ -55,9 +73,6 @@ <source>Syntax error</source> <target>Syntaxfehler</target> -<source>Cannot open file %x.</source> -<target>Die Datei %x kann nicht geöffnet werden.</target> - <source>File %x does not contain a valid configuration.</source> <target>Die Datei %x enthält keine gültige Konfiguration.</target> @@ -1082,6 +1097,9 @@ Dadurch wird ein konsistenter Datenstand auch im schweren Fehlerfall garantiert. <source>&Preferences...</source> <target>&Einstellungen...</target> +<source>Main Bar</source> +<target>Hauptleiste</target> + <source>Folder Pairs</source> <target>Ordnerpaare</target> @@ -1097,9 +1115,6 @@ Dadurch wird ein konsistenter Datenstand auch im schweren Fehlerfall garantiert. <source>Overview</source> <target>Übersicht</target> -<source>Main Bar</source> -<target>Hauptleiste</target> - <source>Confirm</source> <target>Bestätigen</target> @@ -1286,18 +1301,15 @@ Dadurch wird ein konsistenter Datenstand auch im schweren Fehlerfall garantiert. <source>Close progress dialog</source> <target>Schließe Verlaufsdialog</target> -<source>Standby</source> -<target>Energie sparen</target> - <source>Log off</source> <target>Abmelden</target> +<source>Standby</source> +<target>Energie sparen</target> + <source>Shut down</source> <target>Herunterfahren</target> -<source>Hibernate</source> -<target>Ruhezustand</target> - <source>Scanning...</source> <target>Suche Dateien...</target> @@ -1499,6 +1511,9 @@ Dadurch wird ein konsistenter Datenstand auch im schweren Fehlerfall garantiert. <source>Type of item %x is not supported:</source> <target>Der Typ des Elements %x wird nicht unterstützt:</target> +<source>Cannot open file %x.</source> +<target>Die Datei %x kann nicht geöffnet werden.</target> + <source>Cannot resolve symbolic link %x.</source> <target>Die symbolische Verknüpfung %x kann nicht aufgelöst werden.</target> diff --git a/FreeFileSync/Build/Languages/lithuanian.lng b/FreeFileSync/Build/Languages/lithuanian.lng index 78af9c50..372e3390 100644 --- a/FreeFileSync/Build/Languages/lithuanian.lng +++ b/FreeFileSync/Build/Languages/lithuanian.lng @@ -7,6 +7,15 @@ <plural_definition>n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2</plural_definition> </header> +<source>Cannot copy attributes from %x to %y.</source> +<target></target> + +<source>Cannot copy permissions from %x to %y.</source> +<target></target> + +<source>Cannot find file %x.</source> +<target></target> + <source>Both sides have changed since last synchronization.</source> <target>Abi pusės buvo pakeistos nuo paskutinio suvienodinimo.</target> @@ -55,9 +64,6 @@ <source>Syntax error</source> <target>Sintaksės klaida</target> -<source>Cannot open file %x.</source> -<target>%x failo nepavyko atidaryti.</target> - <source>File %x does not contain a valid configuration.</source> <target>Failas %x neturi tinkamų nustatymų.</target> @@ -740,7 +746,7 @@ Komanda inicijuojama jei: <target>&Įrankiai</target> <source>&Check for new version</source> -<target>&Naujasnės versijos tikrinimas</target> +<target>&Naujesnės versijos tikrinimas</target> <source>&Check now</source> <target>&Tikrinti dabar</target> @@ -830,7 +836,7 @@ Komanda inicijuojama jei: <target>Naudoti vasaros laiką</target> <source>Include symbolic links:</source> -<target>Pridėti simbolinias nuorodas:</target> +<target>Pridėti simbolines nuorodas:</target> <source>Direct</source> <target>Tiesiogiai</target> @@ -1086,6 +1092,9 @@ Tai garantuos pastovią buseną, netgi įvykus rimtai klaidai. <source>&Preferences...</source> <target>&Pirmenybė...</target> +<source>Main Bar</source> +<target>Pagrindinė įrankinė</target> + <source>Folder Pairs</source> <target>Sugretinti aplankai</target> @@ -1101,9 +1110,6 @@ Tai garantuos pastovią buseną, netgi įvykus rimtai klaidai. <source>Overview</source> <target>Apžvalga</target> -<source>Main Bar</source> -<target>Pagrindinė įrankinė</target> - <source>Confirm</source> <target>Patvirtinti</target> @@ -1294,18 +1300,15 @@ Tai garantuos pastovią buseną, netgi įvykus rimtai klaidai. <source>Close progress dialog</source> <target>Uždaryti dialogo langą</target> -<source>Standby</source> -<target>Budėjimo režimas</target> - <source>Log off</source> <target>Atsijungti</target> +<source>Standby</source> +<target>Budėjimo režimas</target> + <source>Shut down</source> <target>Išjungti kompiuterį</target> -<source>Hibernate</source> -<target>Išjungti įrašius i pastovią atmintį</target> - <source>Scanning...</source> <target>Tikrinama...</target> @@ -1390,7 +1393,7 @@ Tai garantuos pastovią buseną, netgi įvykus rimtai klaidai. <target>&Rodyti</target> <source>Identify and propagate changes on both sides. Deletions, moves and conflicts are detected automatically using a database.</source> -<target>Nustatyti ir skatinti pokyčius apbiejose pusėse. Trinimai, perkėlimai ir konfliktai yra aptinkami automatiškai naudojant duomenų bazę.</target> +<target>Nustatyti ir vykdyti pokyčius abiejose pusėse. Trynimai, perkėlimai ir konfliktai yra aptinkami automatiškai naudojant duomenų bazę.</target> <source>Create a mirror backup of the left folder by adapting the right folder to match.</source> <target>Sukurti tikslią kairiojo aplanko atsarginę kopiją tinkinant su dešiniu aplanku.</target> @@ -1509,6 +1512,9 @@ Tai garantuos pastovią buseną, netgi įvykus rimtai klaidai. <source>Type of item %x is not supported:</source> <target>Elemento tipas %x nepalaikomas:</target> +<source>Cannot open file %x.</source> +<target>%x failo nepavyko atidaryti.</target> + <source>Cannot resolve symbolic link %x.</source> <target>Nepavyko rasti simbolinės nuorodos %x reikšmės</target> diff --git a/FreeFileSync/Build/Resources.zip b/FreeFileSync/Build/Resources.zip Binary files differindex 57a08ad2..e84b8a1a 100644 --- a/FreeFileSync/Build/Resources.zip +++ b/FreeFileSync/Build/Resources.zip diff --git a/FreeFileSync/Source/FreeFileSync.vcxproj b/FreeFileSync/Source/FreeFileSync.vcxproj index 0f469f34..d8b8b718 100644 --- a/FreeFileSync/Source/FreeFileSync.vcxproj +++ b/FreeFileSync/Source/FreeFileSync.vcxproj @@ -95,7 +95,7 @@ <PrecompiledHeader>Use</PrecompiledHeader> <WarningLevel>Level4</WarningLevel> <Optimization>Disabled</Optimization> - <PreprocessorDefinitions>__WXMSW__;ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;__WXDEBUG__;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>__WXMSW__;ZEN_WIN;__WXDEBUG__;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> <AdditionalIncludeDirectories></AdditionalIncludeDirectories> <PrecompiledHeaderFile>wx+/pch.h</PrecompiledHeaderFile> <MultiProcessorCompilation>true</MultiProcessorCompilation> @@ -109,12 +109,12 @@ <SubSystem>Windows</SubSystem> <GenerateDebugInformation>true</GenerateDebugInformation> <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> - <AdditionalDependencies>wxmsw30ud_aui.lib;wxmsw30ud_adv.lib;wxmsw30ud_core.lib;wxbase30ud.lib;wxpngd.lib;wxzlibd.lib;%(AdditionalDependencies)</AdditionalDependencies> + <AdditionalDependencies>wxmsw30ud_aui.lib;wxmsw30ud_adv.lib;wxmsw30ud_core.lib;wxbase30ud.lib;wxpngd.lib;%(AdditionalDependencies)</AdditionalDependencies> <AdditionalLibraryDirectories></AdditionalLibraryDirectories> </Link> <ResourceCompile> <AdditionalIncludeDirectories></AdditionalIncludeDirectories> - <PreprocessorDefinitions>%(PreprocessorDefinitions);ZEN_ARCHITECTURE_X86</PreprocessorDefinitions> + <PreprocessorDefinitions>%(PreprocessorDefinitions)</PreprocessorDefinitions> </ResourceCompile> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> @@ -122,7 +122,7 @@ <PrecompiledHeader>Use</PrecompiledHeader> <WarningLevel>Level4</WarningLevel> <Optimization>Disabled</Optimization> - <PreprocessorDefinitions>__WXMSW__;ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;__WXDEBUG__;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>__WXMSW__;ZEN_WIN;__WXDEBUG__;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> <AdditionalIncludeDirectories></AdditionalIncludeDirectories> <PrecompiledHeaderFile>wx+/pch.h</PrecompiledHeaderFile> <MultiProcessorCompilation>true</MultiProcessorCompilation> @@ -135,19 +135,19 @@ <SubSystem>Windows</SubSystem> <GenerateDebugInformation>true</GenerateDebugInformation> <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> - <AdditionalDependencies>wxmsw30ud_aui.lib;wxmsw30ud_adv.lib;wxmsw30ud_core.lib;wxbase30ud.lib;wxpngd.lib;wxzlibd.lib;%(AdditionalDependencies)</AdditionalDependencies> + <AdditionalDependencies>wxmsw30ud_aui.lib;wxmsw30ud_adv.lib;wxmsw30ud_core.lib;wxbase30ud.lib;wxpngd.lib;%(AdditionalDependencies)</AdditionalDependencies> <AdditionalLibraryDirectories></AdditionalLibraryDirectories> </Link> <ResourceCompile> <AdditionalIncludeDirectories></AdditionalIncludeDirectories> - <PreprocessorDefinitions>%(PreprocessorDefinitions);ZEN_ARCHITECTURE_X64</PreprocessorDefinitions> + <PreprocessorDefinitions>%(PreprocessorDefinitions)</PreprocessorDefinitions> </ResourceCompile> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> <ClCompile> <WarningLevel>Level4</WarningLevel> <Optimization>MaxSpeed</Optimization> - <PreprocessorDefinitions>__WXMSW__;ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>__WXMSW__;ZEN_WIN;%(PreprocessorDefinitions)</PreprocessorDefinitions> <AdditionalIncludeDirectories></AdditionalIncludeDirectories> <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed> <RuntimeLibrary>MultiThreaded</RuntimeLibrary> @@ -157,7 +157,7 @@ </ClCompile> <Link> <SubSystem>Windows</SubSystem> - <AdditionalDependencies>wxmsw30u_aui.lib;wxmsw30u_adv.lib;wxmsw30u_core.lib;wxbase30u.lib;wxpng.lib;wxzlib.lib;wxbase30u_net.lib;%(AdditionalDependencies)</AdditionalDependencies> + <AdditionalDependencies>wxmsw30u_aui.lib;wxmsw30u_adv.lib;wxmsw30u_core.lib;wxbase30u.lib;wxzlib.lib;%(AdditionalDependencies)</AdditionalDependencies> <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> <AdditionalLibraryDirectories></AdditionalLibraryDirectories> <OptimizeReferences>true</OptimizeReferences> @@ -165,7 +165,7 @@ </Link> <ResourceCompile> <AdditionalIncludeDirectories></AdditionalIncludeDirectories> - <PreprocessorDefinitions>%(PreprocessorDefinitions);ZEN_ARCHITECTURE_X86</PreprocessorDefinitions> + <PreprocessorDefinitions>%(PreprocessorDefinitions)</PreprocessorDefinitions> </ResourceCompile> <PostBuildEvent> </PostBuildEvent> @@ -174,7 +174,7 @@ <ClCompile> <WarningLevel>Level4</WarningLevel> <Optimization>MaxSpeed</Optimization> - <PreprocessorDefinitions>__WXMSW__;ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>__WXMSW__;ZEN_WIN;%(PreprocessorDefinitions)</PreprocessorDefinitions> <AdditionalIncludeDirectories></AdditionalIncludeDirectories> <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed> <RuntimeLibrary>MultiThreaded</RuntimeLibrary> @@ -184,7 +184,7 @@ <Link> <SubSystem>Windows</SubSystem> <GenerateDebugInformation>true</GenerateDebugInformation> - <AdditionalDependencies>wxmsw30u_aui.lib;wxmsw30u_adv.lib;wxmsw30u_core.lib;wxbase30u.lib;wxpng.lib;wxzlib.lib;wxbase30u_net.lib;%(AdditionalDependencies)</AdditionalDependencies> + <AdditionalDependencies>wxmsw30u_aui.lib;wxmsw30u_adv.lib;wxmsw30u_core.lib;wxbase30u.lib;wxzlib.lib;%(AdditionalDependencies)</AdditionalDependencies> <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> <AdditionalLibraryDirectories></AdditionalLibraryDirectories> <OptimizeReferences>true</OptimizeReferences> @@ -192,7 +192,7 @@ </Link> <ResourceCompile> <AdditionalIncludeDirectories></AdditionalIncludeDirectories> - <PreprocessorDefinitions>%(PreprocessorDefinitions);ZEN_ARCHITECTURE_X64</PreprocessorDefinitions> + <PreprocessorDefinitions>%(PreprocessorDefinitions)</PreprocessorDefinitions> </ResourceCompile> <PostBuildEvent> </PostBuildEvent> diff --git a/FreeFileSync/Source/Makefile b/FreeFileSync/Source/Makefile index f44202c6..90f56b9e 100644 --- a/FreeFileSync/Source/Makefile +++ b/FreeFileSync/Source/Makefile @@ -54,6 +54,7 @@ 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/deep_file_traverser.cpp CPP_LIST+=lib/binary.cpp CPP_LIST+=lib/db_file.cpp CPP_LIST+=lib/dir_lock.cpp diff --git a/FreeFileSync/Source/RealtimeSync/RealtimeSync.vcxproj b/FreeFileSync/Source/RealtimeSync/RealtimeSync.vcxproj index adcc161d..77b5c50e 100644 --- a/FreeFileSync/Source/RealtimeSync/RealtimeSync.vcxproj +++ b/FreeFileSync/Source/RealtimeSync/RealtimeSync.vcxproj @@ -95,7 +95,7 @@ <PrecompiledHeader>Use</PrecompiledHeader> <WarningLevel>Level4</WarningLevel> <Optimization>Disabled</Optimization> - <PreprocessorDefinitions>__WXMSW__;ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;__WXDEBUG__;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>__WXMSW__;ZEN_WIN;__WXDEBUG__;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> <AdditionalIncludeDirectories></AdditionalIncludeDirectories> <PrecompiledHeaderFile>wx+/pch.h</PrecompiledHeaderFile> <MultiProcessorCompilation>true</MultiProcessorCompilation> @@ -109,12 +109,12 @@ <SubSystem>Windows</SubSystem> <GenerateDebugInformation>true</GenerateDebugInformation> <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> - <AdditionalDependencies>wxmsw30ud_adv.lib;wxmsw30ud_core.lib;wxbase30ud.lib;wxpngd.lib;wxzlibd.lib;%(AdditionalDependencies)</AdditionalDependencies> + <AdditionalDependencies>wxmsw30ud_adv.lib;wxmsw30ud_core.lib;wxbase30ud.lib;wxpngd.lib;%(AdditionalDependencies)</AdditionalDependencies> <AdditionalLibraryDirectories></AdditionalLibraryDirectories> </Link> <ResourceCompile> <AdditionalIncludeDirectories></AdditionalIncludeDirectories> - <PreprocessorDefinitions>%(PreprocessorDefinitions);ZEN_ARCHITECTURE_X86</PreprocessorDefinitions> + <PreprocessorDefinitions>%(PreprocessorDefinitions)</PreprocessorDefinitions> </ResourceCompile> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> @@ -122,7 +122,7 @@ <PrecompiledHeader>Use</PrecompiledHeader> <WarningLevel>Level4</WarningLevel> <Optimization>Disabled</Optimization> - <PreprocessorDefinitions>__WXMSW__;ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;__WXDEBUG__;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>__WXMSW__;ZEN_WIN;__WXDEBUG__;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> <AdditionalIncludeDirectories></AdditionalIncludeDirectories> <PrecompiledHeaderFile>wx+/pch.h</PrecompiledHeaderFile> <MultiProcessorCompilation>true</MultiProcessorCompilation> @@ -136,12 +136,12 @@ <SubSystem>Windows</SubSystem> <GenerateDebugInformation>true</GenerateDebugInformation> <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> - <AdditionalDependencies>wxmsw30ud_adv.lib;wxmsw30ud_core.lib;wxbase30ud.lib;wxpngd.lib;wxzlibd.lib;%(AdditionalDependencies)</AdditionalDependencies> + <AdditionalDependencies>wxmsw30ud_adv.lib;wxmsw30ud_core.lib;wxbase30ud.lib;wxpngd.lib;%(AdditionalDependencies)</AdditionalDependencies> <AdditionalLibraryDirectories></AdditionalLibraryDirectories> </Link> <ResourceCompile> <AdditionalIncludeDirectories></AdditionalIncludeDirectories> - <PreprocessorDefinitions>%(PreprocessorDefinitions);ZEN_ARCHITECTURE_X64</PreprocessorDefinitions> + <PreprocessorDefinitions>%(PreprocessorDefinitions)</PreprocessorDefinitions> </ResourceCompile> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> @@ -150,7 +150,7 @@ <PrecompiledHeader>NotUsing</PrecompiledHeader> <Optimization>MaxSpeed</Optimization> <FunctionLevelLinking>true</FunctionLevelLinking> - <PreprocessorDefinitions>__WXMSW__;ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>__WXMSW__;ZEN_WIN;%(PreprocessorDefinitions)</PreprocessorDefinitions> <AdditionalIncludeDirectories></AdditionalIncludeDirectories> <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed> <RuntimeLibrary>MultiThreaded</RuntimeLibrary> @@ -160,7 +160,7 @@ </ClCompile> <Link> <SubSystem>Windows</SubSystem> - <AdditionalDependencies>wxmsw30u_adv.lib;wxbase30u_net.lib;wxbase30u.lib;wxmsw30u_core.lib;wxpng.lib;wxzlib.lib;%(AdditionalDependencies)</AdditionalDependencies> + <AdditionalDependencies>wxmsw30u_adv.lib;wxbase30u.lib;wxmsw30u_core.lib;wxzlib.lib;%(AdditionalDependencies)</AdditionalDependencies> <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> <AdditionalLibraryDirectories></AdditionalLibraryDirectories> <OptimizeReferences>true</OptimizeReferences> @@ -168,7 +168,7 @@ </Link> <ResourceCompile> <AdditionalIncludeDirectories></AdditionalIncludeDirectories> - <PreprocessorDefinitions>%(PreprocessorDefinitions);ZEN_ARCHITECTURE_X86</PreprocessorDefinitions> + <PreprocessorDefinitions>%(PreprocessorDefinitions)</PreprocessorDefinitions> </ResourceCompile> <PostBuildEvent> </PostBuildEvent> @@ -179,7 +179,7 @@ <PrecompiledHeader>NotUsing</PrecompiledHeader> <Optimization>MaxSpeed</Optimization> <FunctionLevelLinking>true</FunctionLevelLinking> - <PreprocessorDefinitions>__WXMSW__;ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>__WXMSW__;ZEN_WIN;%(PreprocessorDefinitions)</PreprocessorDefinitions> <AdditionalIncludeDirectories></AdditionalIncludeDirectories> <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed> <RuntimeLibrary>MultiThreaded</RuntimeLibrary> @@ -188,7 +188,7 @@ </ClCompile> <Link> <SubSystem>Windows</SubSystem> - <AdditionalDependencies>wxmsw30u_adv.lib;wxbase30u_net.lib;wxbase30u.lib;wxmsw30u_core.lib;wxpng.lib;wxzlib.lib%(AdditionalDependencies)</AdditionalDependencies> + <AdditionalDependencies>wxmsw30u_adv.lib;wxbase30u.lib;wxmsw30u_core.lib;wxzlib.lib%(AdditionalDependencies)</AdditionalDependencies> <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> <AdditionalLibraryDirectories></AdditionalLibraryDirectories> <OptimizeReferences>true</OptimizeReferences> @@ -196,7 +196,7 @@ </Link> <ResourceCompile> <AdditionalIncludeDirectories></AdditionalIncludeDirectories> - <PreprocessorDefinitions>%(PreprocessorDefinitions);ZEN_ARCHITECTURE_X64</PreprocessorDefinitions> + <PreprocessorDefinitions>%(PreprocessorDefinitions)</PreprocessorDefinitions> </ResourceCompile> <PostBuildEvent> </PostBuildEvent> diff --git a/FreeFileSync/Source/application.cpp b/FreeFileSync/Source/application.cpp index ab7051e3..567907ea 100644 --- a/FreeFileSync/Source/application.cpp +++ b/FreeFileSync/Source/application.cpp @@ -232,7 +232,7 @@ void Application::onEnterEventLoop(wxEvent& event) int Application::OnRun() -{ +{ auto processException = [](const std::wstring& msg) { //it's not always possible to display a message box, e.g. corrupted stack, however low-level file output works! diff --git a/FreeFileSync/Source/comparison.cpp b/FreeFileSync/Source/comparison.cpp index 8464ea28..cf034a50 100644 --- a/FreeFileSync/Source/comparison.cpp +++ b/FreeFileSync/Source/comparison.cpp @@ -473,6 +473,8 @@ std::list<std::shared_ptr<BaseDirPair>> ComparisonBuffer::compareByContent(const const std::wstring txtComparingContentOfFiles = _("Comparing content of files %x"); + //PERF_START; + //compare files (that have same size) bytewise... for (FilePair* fileObj : filesToCompareBytewise) { @@ -761,11 +763,11 @@ std::shared_ptr<BaseDirPair> ComparisonBuffer::performComparison(const ResolvedF } Zstring excludefilterFailedRead; - if (failedReads.find(Zstring()) != failedReads.end()) //empty path if read-error for whole base directory - excludefilterFailedRead += Zstr("*\n"); - else - for (const auto& item : failedReads) - excludefilterFailedRead += item.first + Zstr("\n"); //exclude item AND (potential) child items! + if (failedReads.find(Zstring()) != failedReads.end()) //empty path if read-error for whole base directory + excludefilterFailedRead += Zstr("*\n"); + else + for (const auto& item : failedReads) + excludefilterFailedRead += item.first + Zstr("\n"); //exclude item AND (potential) child items! std::shared_ptr<BaseDirPair> output = std::make_shared<BaseDirPair>(fp.dirpathLeft, bufValueLeft != nullptr, //dir existence must be checked only once: available iff buffer entry exists! diff --git a/FreeFileSync/Source/lib/binary.cpp b/FreeFileSync/Source/lib/binary.cpp index 0b365621..0ef1d4e1 100644 --- a/FreeFileSync/Source/lib/binary.cpp +++ b/FreeFileSync/Source/lib/binary.cpp @@ -15,12 +15,24 @@ using namespace zen; namespace { -inline -void setMinSize(std::vector<char>& buffer, size_t minSize) -{ - if (buffer.size() < minSize) //this is similar to reserve(), but we need a "properly initialized" array here - buffer.resize(minSize); -} + /* + 1. there seems to be no perf improvement possible when using file mappings instad of ::ReadFile() calls on Windows: + => buffered access: same perf + => unbuffered access: same perf on USB stick, file mapping 30% slower on local disk + + 2. Tests on Win7 x64 show that buffer size does NOT matter if files are located on different physical disks! + Impact of buffer size when files are on same disk: + + buffer MB/s + ------------ + 64 10 + 128 19 + 512 40 + 1024 48 + 2048 56 + 4096 56 + 8192 56 + */ class BufferSize { @@ -39,31 +51,25 @@ public: bufSize /= 2; } - operator size_t() const { return bufSize; } + size_t get() const { return bufSize; } private: static const size_t BUFFER_SIZE_MIN = 64 * 1024; static const size_t BUFFER_SIZE_START = 128 * 1024; //initial buffer size static const size_t BUFFER_SIZE_MAX = 16 * 1024 * 1024; - /*Tests on Win7 x64 show that buffer size does NOT matter if files are located on different physical disks! - Impact of buffer size when files are on same disk: - - buffer MB/s - ------------ - 64 10 - 128 19 - 512 40 - 1024 48 - 2048 56 - 4096 56 - 8192 56 - */ - size_t bufSize; }; +inline +void setMinSize(std::vector<char>& buffer, size_t minSize) +{ + if (buffer.size() < minSize) //this is similar to reserve(), but we need a "properly initialized" array here + buffer.resize(minSize); +} + + const std::int64_t TICKS_PER_SEC = ticksPerSec(); } @@ -89,13 +95,13 @@ bool zen::filesHaveSameContent(const Zstring& filepath1, const Zstring& filepath do { - setMinSize(memory1, bufferSize); - setMinSize(memory2, bufferSize); + setMinSize(memory1, bufferSize.get()); + setMinSize(memory2, bufferSize.get()); const TickVal startTime = getTicks(); - const size_t length1 = file1.read(&memory1[0], bufferSize); //throw FileError - const size_t length2 = file2.read(&memory2[0], bufferSize); //returns actual number of bytes read + const size_t length1 = file1.read(&memory1[0], bufferSize.get()); //throw FileError + const size_t length2 = file2.read(&memory2[0], bufferSize.get()); //returns actual number of bytes read //send progress updates immediately after reading to reliably allow speed calculations for our clients! if (onUpdateStatus) onUpdateStatus(std::max(length1, length2)); diff --git a/FreeFileSync/Source/lib/db_file.cpp b/FreeFileSync/Source/lib/db_file.cpp index 6000ebf2..feac429b 100644 --- a/FreeFileSync/Source/lib/db_file.cpp +++ b/FreeFileSync/Source/lib/db_file.cpp @@ -621,8 +621,8 @@ private: //or it was excluded via filter and the database entry should be preserved warn_static("insufficient for *.txt-include filters! -> e.g. 1. *.txt-include, both sides in sync, txt-fiels in subfolder") - warn_static("2. delete all subfolders externally ") - warn_static("3. sync => db should be updated == entries removed for .txt; mabye even for deleted subfolders!?!") + warn_static("2. delete all subfolders externally ") + warn_static("3. sync => db should be updated == entries removed for .txt; mabye even for deleted subfolders!?!") }); } diff --git a/FreeFileSync/Source/lib/deep_file_traverser.cpp b/FreeFileSync/Source/lib/deep_file_traverser.cpp new file mode 100644 index 00000000..9686bef3 --- /dev/null +++ b/FreeFileSync/Source/lib/deep_file_traverser.cpp @@ -0,0 +1,226 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#include "deep_file_traverser.h" +#include <zen/sys_error.h> +#include <zen/symlink_target.h> +#include <zen/int64.h> +#include <cstddef> //offsetof +#include <sys/stat.h> +#include <dirent.h> + +#ifdef ZEN_MAC + #include <zen/osx_string.h> +#endif + +using namespace zen; + + +namespace +{ +//implement "retry" in a generic way: + +template <class Command> inline //function object expecting to throw FileError if operation fails +bool tryReportingDirError(Command cmd, zen::TraverseCallback& callback) //return "true" on success, "false" if error was ignored +{ + for (size_t retryNumber = 0;; ++retryNumber) + try + { + cmd(); //throw FileError + return true; + } + catch (const FileError& e) + { + switch (callback.reportDirError(e.toString(), retryNumber)) + { + case TraverseCallback::ON_ERROR_RETRY: + break; + case TraverseCallback::ON_ERROR_IGNORE: + return false; + } + } +} + +template <class Command> inline //function object expecting to throw FileError if operation fails +bool tryReportingItemError(Command cmd, zen::TraverseCallback& callback, const Zchar* shortName) //return "true" on success, "false" if error was ignored +{ + for (size_t retryNumber = 0;; ++retryNumber) + try + { + cmd(); //throw FileError + return true; + } + catch (const FileError& e) + { + switch (callback.reportItemError(e.toString(), retryNumber, shortName)) + { + case TraverseCallback::ON_ERROR_RETRY: + break; + case TraverseCallback::ON_ERROR_IGNORE: + return false; + } + } +} + + +class DirTraverser +{ +public: + static void execute(const Zstring& baseDirectory, TraverseCallback& sink) + { + DirTraverser(baseDirectory, sink); + } + +private: + DirTraverser(const Zstring& baseDirectory, zen::TraverseCallback& sink) + { + const Zstring directoryFormatted = //remove trailing slash + baseDirectory.size() > 1 && endsWith(baseDirectory, FILE_NAME_SEPARATOR) ? //exception: allow '/' + beforeLast(baseDirectory, FILE_NAME_SEPARATOR) : + baseDirectory; + + /* 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<long>(::pathconf(directoryFormatted.c_str(), _PC_NAME_MAX), 10000); //::pathconf may return long(-1) + buffer.resize(offsetof(struct ::dirent, d_name) + nameMax + 1); + + traverse(directoryFormatted, sink); + } + + DirTraverser (const DirTraverser&) = delete; + DirTraverser& operator=(const DirTraverser&) = delete; + + void traverse(const Zstring& dirpath, TraverseCallback& sink) + { + tryReportingDirError([&] + { + traverseWithException(dirpath, sink); //throw FileError + }, sink); + } + + void traverseWithException(const Zstring& dirpath, TraverseCallback& sink) //throw FileError + { + //no need to check for endless recursion: Linux has a fixed limit on the number of symbolic links in a path + + DIR* dirObj = ::opendir(dirpath.c_str()); //directory must NOT end with path separator, except "/" + if (!dirObj) + throwFileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtFileName(dirpath)), L"opendir", getLastError()); + ZEN_ON_SCOPE_EXIT(::closedir(dirObj)); //never close nullptr handles! -> crash + + for (;;) + { + struct ::dirent* dirEntry = nullptr; + if (::readdir_r(dirObj, reinterpret_cast< ::dirent*>(&buffer[0]), &dirEntry) != 0) + throwFileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(dirpath)), L"readdir_r", getLastError()); + //don't retry but restart dir traversal on error! http://blogs.msdn.com/b/oldnewthing/archive/2014/06/12/10533529.aspx + + if (!dirEntry) //no more items + return; + + //don't return "." and ".." + const char* shortName = dirEntry->d_name; //evaluate dirEntry *before* going into recursion => we use a single "buffer"! + if (shortName[0] == '.' && + (shortName[1] == 0 || (shortName[1] == '.' && shortName[2] == 0))) + continue; +#ifdef ZEN_MAC + //some file system abstraction layers fail to properly return decomposed UTF8: http://developer.apple.com/library/mac/#qa/qa1173/_index.html + //so we need to do it ourselves; perf: ~600 ns per conversion + //note: it's not sufficient to apply this in z_impl::compareFilenamesNoCase: if UTF8 forms differ, FFS assumes a rename in case sensitivity and + // will try to propagate the rename => this won't work if target drive reports a particular UTF8 form only! + if (CFStringRef cfStr = osx::createCFString(shortName)) + { + ZEN_ON_SCOPE_EXIT(::CFRelease(cfStr)); + + CFIndex lenMax = ::CFStringGetMaximumSizeOfFileSystemRepresentation(cfStr); //"could be much larger than the actual space required" => don't store in Zstring + if (lenMax > 0) + { + bufferUtfDecomposed.resize(lenMax); + if (::CFStringGetFileSystemRepresentation(cfStr, &bufferUtfDecomposed[0], lenMax)) //get decomposed UTF form (verified!) despite ambiguous documentation + shortName = &bufferUtfDecomposed[0]; //attention: => don't access "shortName" after recursion in "traverse"! + } + } + //const char* sampleDecomposed = "\x6f\xcc\x81.txt"; + //const char* samplePrecomposed = "\xc3\xb3.txt"; +#endif + const Zstring& itempath = appendSeparator(dirpath) + shortName; + + struct ::stat statData = {}; + if (!tryReportingItemError([&] + { + if (::lstat(itempath.c_str(), &statData) != 0) //lstat() does not resolve symlinks + throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(itempath)), L"lstat", getLastError()); + }, sink, shortName)) + continue; //ignore error: skip file + + if (S_ISLNK(statData.st_mode)) //on Linux there is no distinction between file and directory symlinks! + { + const TraverseCallback::SymlinkInfo linkInfo = { shortName, itempath, statData.st_mtime }; + + switch (sink.onSymlink(linkInfo)) + { + case TraverseCallback::LINK_FOLLOW: + { + //try to resolve symlink (and report error on failure!!!) + struct ::stat statDataTrg = {}; + bool validLink = tryReportingItemError([&] + { + if (::stat(itempath.c_str(), &statDataTrg) != 0) + throwFileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(itempath)), L"stat", getLastError()); + }, sink, shortName); + + if (validLink) + { + if (S_ISDIR(statDataTrg.st_mode)) //a directory + { + if (TraverseCallback* trav = sink.onDir({ shortName, itempath })) + { + ZEN_ON_SCOPE_EXIT(sink.releaseDirTraverser(trav)); + traverse(itempath, *trav); + } + } + else //a file or named pipe, ect. + sink.onFile({ shortName, itempath, makeUnsigned(statDataTrg.st_size), statDataTrg.st_mtime, extractFileId(statDataTrg), &linkInfo }); + } + // else //broken symlink -> ignore: it's client's responsibility to handle error! + } + break; + + case TraverseCallback::LINK_SKIP: + break; + } + } + else if (S_ISDIR(statData.st_mode)) //a directory + { + if (TraverseCallback* trav = sink.onDir({ shortName, itempath })) + { + ZEN_ON_SCOPE_EXIT(sink.releaseDirTraverser(trav)); + traverse(itempath, *trav); + } + } + else //a file or named pipe, ect. + sink.onFile({ shortName, itempath, makeUnsigned(statData.st_size), statData.st_mtime, extractFileId(statData), nullptr }); + /* + 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<char> buffer; +#ifdef ZEN_MAC + std::vector<char> bufferUtfDecomposed; +#endif +}; +} + + +void zen::deepTraverseFolder(const Zstring& dirpath, TraverseCallback& sink) { DirTraverser::execute(dirpath, sink); } diff --git a/FreeFileSync/Source/lib/deep_file_traverser.h b/FreeFileSync/Source/lib/deep_file_traverser.h new file mode 100644 index 00000000..50740e8e --- /dev/null +++ b/FreeFileSync/Source/lib/deep_file_traverser.h @@ -0,0 +1,73 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#ifndef DEEP_FILE_TRAVERSER_H_INCLUDED_342765897342151 +#define DEEP_FILE_TRAVERSER_H_INCLUDED_342765897342151 + +#include <cstdint> +#include <zen/zstring.h> +#include <zen/file_id_def.h> + +//advanced file traverser returning metadata and hierarchical information on files and directories + +namespace zen +{ +struct TraverseCallback +{ + virtual ~TraverseCallback() {} + + struct SymlinkInfo + { + const Zchar* shortName; + const Zstring& fullPath; + std::int64_t lastWriteTime; //number of seconds since Jan. 1st 1970 UTC + }; + + struct FileInfo + { + const Zchar* shortName; + const Zstring& fullPath; + std::uint64_t fileSize; //unit: bytes! + std::int64_t lastWriteTime; //number of seconds since Jan. 1st 1970 UTC + const FileId& id; //optional: initial if not supported! + const SymlinkInfo* symlinkInfo; //only filled if file is a followed symlink + }; + + struct DirInfo + { + const Zchar* shortName; + const Zstring& fullPath; + }; + + enum HandleLink + { + LINK_FOLLOW, //dereferences link, then calls "onDir()" or "onFile()" + LINK_SKIP + }; + + enum HandleError + { + ON_ERROR_RETRY, + ON_ERROR_IGNORE + }; + + virtual void onFile (const FileInfo& fi) = 0; + virtual TraverseCallback* onDir (const DirInfo& di) = 0; + virtual HandleLink onSymlink(const SymlinkInfo& li) = 0; + //nullptr: ignore directory, non-nullptr: traverse into using the (new) callback => implement releaseDirTraverser() if necessary! + virtual void releaseDirTraverser(TraverseCallback* trav) {} + + 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 Zchar* shortName) = 0; //failed to get data for single file/dir/symlink only! +}; + +//custom traverser with detail information about files +//- client needs to handle duplicate file reports! (FilePlusTraverser fallback, retrying to read directory contents, ...) +//- directory may end with PATH_SEPARATOR +void deepTraverseFolder(const Zstring& dirpath, TraverseCallback& sink); //noexcept +} + +#endif //DEEP_FILE_TRAVERSER_H_INCLUDED_342765897342151 diff --git a/FreeFileSync/Source/lib/localization.cpp b/FreeFileSync/Source/lib/localization.cpp index 72bbcf1b..d15dfc43 100644 --- a/FreeFileSync/Source/lib/localization.cpp +++ b/FreeFileSync/Source/lib/localization.cpp @@ -111,27 +111,6 @@ FFSTranslation::FFSTranslation(const Zstring& filepath, wxLanguage languageId) : } -class FindLngfiles : public zen::TraverseCallback -{ -public: - FindLngfiles(std::vector<Zstring>& lngFiles) : lngFiles_(lngFiles) {} - - void onFile(const Zchar* shortName, const Zstring& filepath, const FileInfo& details) override - { - if (endsWith(filepath, Zstr(".lng"))) - lngFiles_.push_back(filepath); - } - - HandleLink onSymlink(const Zchar* shortName, const Zstring& linkpath, const SymlinkInfo& details) override { return LINK_SKIP; } - TraverseCallback* onDir(const Zchar* shortName, const Zstring& dirpath) override { return nullptr; } - HandleError reportDirError (const std::wstring& msg, size_t retryNumber) override { assert(false); return ON_ERROR_IGNORE; } //errors are not really critical in this context - HandleError reportItemError(const std::wstring& msg, size_t retryNumber, const Zchar* shortName) override { assert(false); return ON_ERROR_IGNORE; } // - -private: - std::vector<Zstring>& lngFiles_; -}; - - struct LessTranslation { bool operator()(const ExistingTranslations::Entry& lhs, const ExistingTranslations::Entry& rhs) const @@ -189,10 +168,12 @@ ExistingTranslations::ExistingTranslations() //search language files available std::vector<Zstring> lngFiles; - FindLngfiles traverseCallback(lngFiles); - traverseFolder(zen::getResourceDir() + Zstr("Languages"), //throw(); - traverseCallback); + traverseFolder(zen::getResourceDir() + Zstr("Languages"), [&](const FileInfo& fi) + { + if (endsWith(fi.fullPath, Zstr(".lng"))) + lngFiles.push_back(fi.fullPath); + }, nullptr, nullptr, [&](const std::wstring& errorMsg) { assert(false); }); //errors are not really critical in this context for (const Zstring& filepath : lngFiles) { diff --git a/FreeFileSync/Source/lib/parallel_scan.cpp b/FreeFileSync/Source/lib/parallel_scan.cpp index 51c02214..7a78c644 100644 --- a/FreeFileSync/Source/lib/parallel_scan.cpp +++ b/FreeFileSync/Source/lib/parallel_scan.cpp @@ -5,12 +5,12 @@ // ************************************************************************** #include "parallel_scan.h" -#include <zen/file_traverser.h> #include <zen/file_error.h> #include <zen/thread.h> //includes <boost/thread.hpp> #include <zen/scope_guard.h> #include <zen/fixed_list.h> #include <boost/detail/atomic_count.hpp> +#include "deep_file_traverser.h" #include "db_file.h" #include "lock_holder.h" @@ -312,9 +312,10 @@ public: relNameParentPf_(relNameParentPf), output_(output) {} - void onFile (const Zchar* shortName, const Zstring& filepath, const FileInfo& details) override; - HandleLink onSymlink(const Zchar* shortName, const Zstring& linkpath, const SymlinkInfo& details) override; - TraverseCallback* onDir(const Zchar* shortName, const Zstring& dirpath) override; + virtual void onFile (const FileInfo& fi) override; + virtual TraverseCallback* onDir (const DirInfo& di) override; + virtual HandleLink onSymlink(const SymlinkInfo& li) override; + void releaseDirTraverser(TraverseCallback* trav) override; HandleError reportDirError (const std::wstring& msg, size_t retryNumber) override; @@ -327,11 +328,11 @@ private: }; -void DirCallback::onFile(const Zchar* shortName, const Zstring& filepath, const FileInfo& details) +void DirCallback::onFile(const FileInfo& fi) { boost::this_thread::interruption_point(); - const Zstring fileNameShort(shortName); + const Zstring fileNameShort(fi.shortName); //do not list the database file(s) sync.ffs_db, sync.x64.ffs_db, etc. or lock files if (endsWith(fileNameShort, SYNC_DB_FILE_ENDING) || @@ -339,7 +340,7 @@ void DirCallback::onFile(const Zchar* shortName, const Zstring& filepath, const return; //update status information no matter whether object is excluded or not! - cfg.acb_.reportCurrentFile(filepath, cfg.threadID_); + cfg.acb_.reportCurrentFile(fi.fullPath, cfg.threadID_); //------------------------------------------------------------------------------------ //apply filter before processing (use relative name!) @@ -357,18 +358,43 @@ void DirCallback::onFile(const Zchar* shortName, const Zstring& filepath, const Linux: retrieveFileID takes about 50% longer in VM! (avoidable because of redundant stat() call!) */ - output_.addSubFile(fileNameShort, FileDescriptor(details.lastWriteTime, details.fileSize, details.id, details.symlinkInfo != nullptr)); + output_.addSubFile(fileNameShort, FileDescriptor(fi.lastWriteTime, fi.fileSize, fi.id, fi.symlinkInfo != nullptr)); cfg.acb_.incItemsScanned(); //add 1 element to the progress indicator } -DirCallback::HandleLink DirCallback::onSymlink(const Zchar* shortName, const Zstring& linkpath, const SymlinkInfo& details) +TraverseCallback* DirCallback::onDir(const DirInfo& di) +{ + boost::this_thread::interruption_point(); + + //update status information no matter whether object is excluded or not! + cfg.acb_.reportCurrentFile(di.fullPath, cfg.threadID_); + + //------------------------------------------------------------------------------------ + const Zstring& relPath = relNameParentPf_ + di.shortName; + + //apply filter before processing (use relative name!) + bool subObjMightMatch = true; + const bool passFilter = cfg.filterInstance->passDirFilter(relPath, &subObjMightMatch); + if (!passFilter && !subObjMightMatch) + return nullptr; //do NOT traverse subdirs + //else: attention! ensure directory filtering is applied later to exclude actually filtered directories + + DirContainer& subDir = output_.addSubDir(di.shortName); + if (passFilter) + cfg.acb_.incItemsScanned(); //add 1 element to the progress indicator + + return new DirCallback(cfg, relPath + FILE_NAME_SEPARATOR, subDir); //releaseDirTraverser() is guaranteed to be called in any case +} + + +DirCallback::HandleLink DirCallback::onSymlink(const SymlinkInfo& si) { boost::this_thread::interruption_point(); //update status information no matter whether object is excluded or not! - cfg.acb_.reportCurrentFile(linkpath, cfg.threadID_); + cfg.acb_.reportCurrentFile(si.fullPath, cfg.threadID_); switch (cfg.handleSymlinks_) { @@ -376,9 +402,9 @@ DirCallback::HandleLink DirCallback::onSymlink(const Zchar* shortName, const Zst return LINK_SKIP; case SYMLINK_DIRECT: - if (cfg.filterInstance->passFileFilter(relNameParentPf_ + shortName)) //always use file filter: Link type may not be "stable" on Linux! + if (cfg.filterInstance->passFileFilter(relNameParentPf_ + si.shortName)) //always use file filter: Link type may not be "stable" on Linux! { - output_.addSubLink(shortName, LinkDescriptor(details.lastWriteTime)); + output_.addSubLink(si.shortName, LinkDescriptor(si.lastWriteTime)); cfg.acb_.incItemsScanned(); //add 1 element to the progress indicator } return LINK_SKIP; @@ -386,10 +412,10 @@ DirCallback::HandleLink DirCallback::onSymlink(const Zchar* shortName, const Zst case SYMLINK_FOLLOW: //filter symlinks before trying to follow them: handle user-excluded broken symlinks! //since we don't know what type the symlink will resolve to, only do this when both variants agree: - if (!cfg.filterInstance->passFileFilter(relNameParentPf_ + shortName)) + if (!cfg.filterInstance->passFileFilter(relNameParentPf_ + si.shortName)) { bool subObjMightMatch = true; - if (!cfg.filterInstance->passDirFilter(relNameParentPf_ + shortName, &subObjMightMatch)) + if (!cfg.filterInstance->passDirFilter(relNameParentPf_ + si.shortName, &subObjMightMatch)) if (!subObjMightMatch) return LINK_SKIP; } @@ -401,31 +427,6 @@ DirCallback::HandleLink DirCallback::onSymlink(const Zchar* shortName, const Zst } -TraverseCallback* DirCallback::onDir(const Zchar* shortName, const Zstring& dirpath) -{ - boost::this_thread::interruption_point(); - - //update status information no matter whether object is excluded or not! - cfg.acb_.reportCurrentFile(dirpath, cfg.threadID_); - - //------------------------------------------------------------------------------------ - const Zstring& relPath = relNameParentPf_ + shortName; - - //apply filter before processing (use relative name!) - bool subObjMightMatch = true; - const bool passFilter = cfg.filterInstance->passDirFilter(relPath, &subObjMightMatch); - if (!passFilter && !subObjMightMatch) - return nullptr; //do NOT traverse subdirs - //else: attention! ensure directory filtering is applied later to exclude actually filtered directories - - DirContainer& subDir = output_.addSubDir(shortName); - if (passFilter) - cfg.acb_.incItemsScanned(); //add 1 element to the progress indicator - - return new DirCallback(cfg, relPath + FILE_NAME_SEPARATOR, subDir); //releaseDirTraverser() is guaranteed to be called in any case -} - - void DirCallback::releaseDirTraverser(TraverseCallback* trav) { TraverseCallback::releaseDirTraverser(trav); //no-op; introduce compile-time coupling @@ -499,7 +500,7 @@ public: dirOutput_.dirCont); //get all files and folders from directoryPostfixed (and subdirectories) - traverseFolder(dirKey_.dirpath_, traverser); //exceptions may be thrown! + deepTraverseFolder(dirKey_.dirpath_, traverser); //exceptions may be thrown! } private: diff --git a/FreeFileSync/Source/lib/versioning.cpp b/FreeFileSync/Source/lib/versioning.cpp index 58648771..621e9ef0 100644 --- a/FreeFileSync/Source/lib/versioning.cpp +++ b/FreeFileSync/Source/lib/versioning.cpp @@ -190,8 +190,7 @@ void moveFile(const Zstring& sourceFile, //throw FileError void moveDirSymlink(const Zstring& sourceLink, const Zstring& targetLink) //throw FileError { - moveObject(sourceLink, //throw FileError - targetLink, + moveObject(sourceLink, targetLink, //throw FileError [&] { //create target @@ -201,40 +200,6 @@ void moveDirSymlink(const Zstring& sourceLink, const Zstring& targetLink) //thro removeDirectory(sourceLink); //throw FileError; newly copied link is NOT deleted if exception is thrown here! }); } - - -class TraverseFilesOneLevel : public TraverseCallback -{ -public: - TraverseFilesOneLevel(std::vector<Zstring>& files, std::vector<Zstring>& dirs) : files_(files), dirs_(dirs) {} - -private: - void onFile(const Zchar* shortName, const Zstring& filepath, const FileInfo& details) override - { - files_.push_back(shortName); - } - - HandleLink onSymlink(const Zchar* shortName, const Zstring& linkpath, const SymlinkInfo& details) override - { - if (dirExists(linkpath)) //dir symlink - dirs_.push_back(shortName); - else //file symlink, broken symlink - files_.push_back(shortName); - return LINK_SKIP; - } - - TraverseCallback* onDir(const Zchar* shortName, const Zstring& dirpath) override - { - dirs_.push_back(shortName); - return nullptr; //DON'T traverse into subdirs; moveDirectory works recursively! - } - - HandleError reportDirError (const std::wstring& msg, size_t retryNumber) override { throw FileError(msg); } - HandleError reportItemError(const std::wstring& msg, size_t retryNumber, const Zchar* shortName) override { throw FileError(msg); } - - std::vector<Zstring>& files_; - std::vector<Zstring>& dirs_; -}; } @@ -314,10 +279,18 @@ void FileVersioner::revisionDirImpl(const Zstring& dirpath, const Zstring& relat //traverse source directory one level std::vector<Zstring> fileList; //list of *short* names std::vector<Zstring> dirList; // + + traverseFolder(dirpath, + [&](const FileInfo& fi) { fileList.push_back(fi.shortName); }, + [&](const DirInfo& di) { dirList .push_back(di.shortName); }, + [&](const SymlinkInfo& si) { - TraverseFilesOneLevel tol(fileList, dirList); //throw FileError - traverseFolder(dirpath, tol); // - } + if (dirExists(si.fullPath)) //dir symlink + dirList.push_back(si.shortName); + else //file symlink, broken symlink + fileList.push_back(si.shortName); + }, + [&](const std::wstring& errorMsg) { throw FileError(errorMsg); }); const Zstring dirpathPf = appendSeparator(dirpath); const Zstring relpathPf = appendSeparator(relativePath); diff --git a/FreeFileSync/Source/ui/batch_status_handler.cpp b/FreeFileSync/Source/ui/batch_status_handler.cpp index e4726ead..beb59d48 100644 --- a/FreeFileSync/Source/ui/batch_status_handler.cpp +++ b/FreeFileSync/Source/ui/batch_status_handler.cpp @@ -29,45 +29,28 @@ Zstring addStatusToLogfilename(const Zstring& logfilepath, const std::wstring& s size_t pos = logfilepath.rfind(Zstr('.')); if (pos != Zstring::npos) return Zstring(logfilepath.begin(), logfilepath.begin() + pos) + - utfCvrtTo<Zstring>(L" (" + status + L")") + + utfCvrtTo<Zstring>(L" [" + status + L"]") + Zstring(logfilepath.begin() + pos, logfilepath.end()); assert(false); return logfilepath; } -class FindLogfiles : public TraverseCallback -{ -public: - FindLogfiles(const Zstring& prefix, std::vector<Zstring>& logfiles, const std::function<void()>& onUpdateStatus) : prefix_(prefix), logfiles_(logfiles), onUpdateStatus_(onUpdateStatus) {} - -private: - void onFile(const Zchar* shortName, const Zstring& filePath, const FileInfo& details) override - { - const Zstring fileName(shortName); - if (startsWith(fileName, prefix_) && endsWith(fileName, Zstr(".log"))) - logfiles_.push_back(filePath); - - if (onUpdateStatus_) - onUpdateStatus_(); - } - - TraverseCallback* onDir(const Zchar* shortName, const Zstring& dirpath) override { return nullptr; } //DON'T traverse into subdirs - HandleLink onSymlink(const Zchar* shortName, const Zstring& linkpath, const SymlinkInfo& details) override { return LINK_SKIP; } - HandleError reportDirError (const std::wstring& msg, size_t retryNumber) override { assert(false); return ON_ERROR_IGNORE; } //errors are not really critical in this context - HandleError reportItemError(const std::wstring& msg, size_t retryNumber, const Zchar* shortName) override { assert(false); return ON_ERROR_IGNORE; } // - - const Zstring prefix_; - std::vector<Zstring>& logfiles_; - const std::function<void()> onUpdateStatus_; -}; - - void limitLogfileCount(const Zstring& logdir, const std::wstring& jobname, size_t maxCount, const std::function<void()>& onUpdateStatus) //noexcept { std::vector<Zstring> logFiles; - FindLogfiles traverseCallback(utfCvrtTo<Zstring>(jobname), logFiles, onUpdateStatus); //noexcept - traverseFolder(logdir, traverseCallback); + const Zstring prefix = utfCvrtTo<Zstring>(jobname); + +traverseFolder(logdir, [&](const FileInfo& fi) +{ + const Zstring fileName(fi.shortName); + if (startsWith(fileName, prefix) && endsWith(fileName, Zstr(".log"))) + logFiles.push_back(fi.fullPath); + + if (onUpdateStatus) + onUpdateStatus(); +}, + nullptr, nullptr, [&](const std::wstring& errorMsg){ assert(false); }); //errors are not really critical in this context if (logFiles.size() <= maxCount) return; @@ -95,8 +78,11 @@ std::unique_ptr<FileOutput> prepareNewLogfile(const Zstring& logfileDirectory, / //create logfile directory if required makeDirectory(logfileDir); //throw FileError + const std::string colon = "\xcb\xb8"; //="modifier letter raised colon" => regular colon is forbidden in file names on Windows and OS X + const auto format = utfCvrtTo<Zstring>("%Y-%m-%d %H" + colon + "%M" + colon + "%S"); + //assemble logfile name - const Zstring body = appendSeparator(logfileDir) + utfCvrtTo<Zstring>(jobName) + Zstr(" ") + formatTime<Zstring>(Zstr("%Y-%m-%d %H%M%S"), timeStamp); + const Zstring body = appendSeparator(logfileDir) + utfCvrtTo<Zstring>(jobName) + Zstr(" ") + formatTime<Zstring>(format, timeStamp); //ensure uniqueness Zstring filepath = body + Zstr(".log"); @@ -264,7 +250,8 @@ BatchStatusHandler::~BatchStatusHandler() renameFile(oldLogfilepath, addStatusToLogfilename(oldLogfilepath, _("Stopped"))); //throw FileError else if (totalErrors > 0) renameFile(oldLogfilepath, addStatusToLogfilename(oldLogfilepath, _("Error"))); //throw FileError - //status "warning" is not important enough to show up in log file name + else if (totalWarnings > 0) + renameFile(oldLogfilepath, addStatusToLogfilename(oldLogfilepath, _("Warning"))); //throw FileError } catch (FileError&) { assert(false); } } diff --git a/FreeFileSync/Source/ui/gui_generated.cpp b/FreeFileSync/Source/ui/gui_generated.cpp index 5a3e3615..fb9c0914 100644 --- a/FreeFileSync/Source/ui/gui_generated.cpp +++ b/FreeFileSync/Source/ui/gui_generated.cpp @@ -939,8 +939,8 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const m_bpButtonSyncConfig->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSyncSettings ), NULL, this ); m_bpButtonSyncConfig->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::OnSyncSettingsContext ), NULL, this ); m_buttonSync->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnStartSync ), NULL, this ); - m_bpButtonAddPair->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnAddFolderPair ), NULL, this ); - m_bpButtonRemovePair->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnRemoveTopFolderPair ), NULL, this ); + m_bpButtonAddPair->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnTopFolderPairAdd ), NULL, this ); + m_bpButtonRemovePair->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnTopFolderPairRemove ), NULL, this ); m_bpButtonSwapSides->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSwapSides ), NULL, this ); m_bpButtonHideSearch->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnHideSearchPanel ), NULL, this ); m_textCtrlSearchTxt->Connect( wxEVT_COMMAND_TEXT_ENTER, wxCommandEventHandler( MainDialogGenerated::OnSearchGridEnter ), NULL, this ); @@ -2041,6 +2041,11 @@ FolderPairPanelGenerated::FolderPairPanelGenerated( wxWindow* parent, wxWindowID wxBoxSizer* bSizer134; bSizer134 = new wxBoxSizer( wxHORIZONTAL ); + m_bpButtonFolderPairOptions = new wxBitmapButton( m_panelLeft, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 25,25 ), wxBU_AUTODRAW ); + m_bpButtonFolderPairOptions->SetToolTip( _("Arrange folder pair") ); + + bSizer134->Add( m_bpButtonFolderPairOptions, 0, wxALIGN_CENTER_VERTICAL, 5 ); + m_bpButtonRemovePair = new wxBitmapButton( m_panelLeft, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 25,25 ), wxBU_AUTODRAW ); m_bpButtonRemovePair->SetToolTip( _("Remove folder pair") ); diff --git a/FreeFileSync/Source/ui/gui_generated.h b/FreeFileSync/Source/ui/gui_generated.h index 3fab5c81..28f67bd8 100644 --- a/FreeFileSync/Source/ui/gui_generated.h +++ b/FreeFileSync/Source/ui/gui_generated.h @@ -214,8 +214,8 @@ protected: virtual void OnCompSettingsContext( wxMouseEvent& event ) { event.Skip(); } virtual void OnGlobalFilterContext( wxMouseEvent& event ) { event.Skip(); } virtual void OnSyncSettingsContext( wxMouseEvent& event ) { event.Skip(); } - virtual void OnAddFolderPair( wxCommandEvent& event ) { event.Skip(); } - virtual void OnRemoveTopFolderPair( wxCommandEvent& event ) { event.Skip(); } + virtual void OnTopFolderPairAdd( wxCommandEvent& event ) { event.Skip(); } + virtual void OnTopFolderPairRemove( wxCommandEvent& event ) { event.Skip(); } virtual void OnSwapSides( wxCommandEvent& event ) { event.Skip(); } virtual void OnHideSearchPanel( wxCommandEvent& event ) { event.Skip(); } virtual void OnSearchGridEnter( wxCommandEvent& event ) { event.Skip(); } @@ -480,6 +480,7 @@ protected: public: wxPanel* m_panelLeft; + wxBitmapButton* m_bpButtonFolderPairOptions; wxBitmapButton* m_bpButtonRemovePair; FolderHistoryBox* m_directoryLeft; wxPanel* m_panel20; diff --git a/FreeFileSync/Source/ui/main_dlg.cpp b/FreeFileSync/Source/ui/main_dlg.cpp index ec1d0edf..ed1b1f99 100644 --- a/FreeFileSync/Source/ui/main_dlg.cpp +++ b/FreeFileSync/Source/ui/main_dlg.cpp @@ -192,18 +192,19 @@ public: dirpathLeft .Connect(EVENT_ON_DIR_MANUAL_CORRECTION, wxCommandEventHandler(MainDialog::onDirManualCorrection), nullptr, &mainDialog); dirpathRight.Connect(EVENT_ON_DIR_MANUAL_CORRECTION, wxCommandEventHandler(MainDialog::onDirManualCorrection), nullptr, &mainDialog); + + m_bpButtonFolderPairOptions->SetBitmapLabel(getResourceImage(L"button_arrow_down")); } - void setValues(const Zstring& leftDir, - const Zstring& rightDir, - AltCompCfgPtr cmpCfg, - AltSyncCfgPtr syncCfg, - const FilterConfig& filter) + void setValues(const FolderPairEnh& fp) { - setConfig(cmpCfg, syncCfg, filter); - dirpathLeft .setPath(toWx(leftDir)); - dirpathRight.setPath(toWx(rightDir)); + setConfig(fp.altCmpConfig, fp.altSyncConfig, fp.localFilter); + dirpathLeft .setPath(toWx(fp.dirpathPhraseLeft)); + dirpathRight.setPath(toWx(fp.dirpathPhraseRight)); } + + FolderPairEnh getValues() const { return FolderPairEnh(getLeftDir(), getRightDir(), getAltCompConfig(), getAltSyncConfig(), getAltFilterConfig()); } + Zstring getLeftDir () const { return toZ(dirpathLeft .getPath()); } Zstring getRightDir() const { return toZ(dirpathRight.getPath()); } @@ -239,18 +240,21 @@ public: dirpathLeft .Connect(EVENT_ON_DIR_MANUAL_CORRECTION, wxCommandEventHandler(MainDialog::onDirManualCorrection), nullptr, &mainDialog); dirpathRight.Connect(EVENT_ON_DIR_MANUAL_CORRECTION, wxCommandEventHandler(MainDialog::onDirManualCorrection), nullptr, &mainDialog); + + mainDialog.m_panelTopLeft ->Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(MainDialog::onTopFolderPairKeyEvent), nullptr, &mainDialog); + mainDialog.m_panelTopMiddle->Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(MainDialog::onTopFolderPairKeyEvent), nullptr, &mainDialog); + mainDialog.m_panelTopRight ->Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(MainDialog::onTopFolderPairKeyEvent), nullptr, &mainDialog); } - void setValues(const Zstring& leftDir, - const Zstring& rightDir, - AltCompCfgPtr cmpCfg, - AltSyncCfgPtr syncCfg, - const FilterConfig& filter) + void setValues(const FolderPairEnh& fp) { - setConfig(cmpCfg, syncCfg, filter); - dirpathLeft .setPath(toWx(leftDir)); - dirpathRight.setPath(toWx(rightDir)); + setConfig(fp.altCmpConfig, fp.altSyncConfig, fp.localFilter); + dirpathLeft .setPath(toWx(fp.dirpathPhraseLeft)); + dirpathRight.setPath(toWx(fp.dirpathPhraseRight)); } + + FolderPairEnh getValues() const { return FolderPairEnh(getLeftDir(), getRightDir(), getAltCompConfig(), getAltSyncConfig(), getAltFilterConfig()); } + Zstring getLeftDir () const { return toZ(dirpathLeft .getPath()); } Zstring getRightDir() const { return toZ(dirpathRight.getPath()); } @@ -537,27 +541,27 @@ MainDialog::MainDialog(const Zstring& globalConfigFile, auiMgr.AddPane(m_panelCenter, wxAuiPaneInfo().Name(L"PanelCenter").CenterPane().PaneBorder(false)); - { - //set comparison button label tentatively for m_panelTopButtons to receive final height: - updateTopButton(*m_buttonCompare, getResourceImage(L"compare"), L"Dummy", false); - m_panelTopButtons->GetSizer()->SetSizeHints(m_panelTopButtons); //~=Fit() + SetMinSize() + { + //set comparison button label tentatively for m_panelTopButtons to receive final height: + updateTopButton(*m_buttonCompare, getResourceImage(L"compare"), L"Dummy", false); + m_panelTopButtons->GetSizer()->SetSizeHints(m_panelTopButtons); //~=Fit() + SetMinSize() - setBitmapTextLabel(*m_buttonCancel, wxImage(), m_buttonCancel->GetLabel()); //we can't use a wxButton for cancel: it's rendered smaller on OS X than a wxBitmapButton! - m_buttonCancel->SetMinSize(wxSize(std::max(m_buttonCancel->GetSize().x, TOP_BUTTON_OPTIMAL_WIDTH), - std::max(m_buttonCancel->GetSize().y, m_buttonCompare->GetSize().y))); + setBitmapTextLabel(*m_buttonCancel, wxImage(), m_buttonCancel->GetLabel()); //we can't use a wxButton for cancel: it's rendered smaller on OS X than a wxBitmapButton! + m_buttonCancel->SetMinSize(wxSize(std::max(m_buttonCancel->GetSize().x, TOP_BUTTON_OPTIMAL_WIDTH), + std::max(m_buttonCancel->GetSize().y, m_buttonCompare->GetSize().y))); - auiMgr.AddPane(m_panelTopButtons, - wxAuiPaneInfo().Name(L"PanelTop").Layer(2).Top().Row(1).Caption(_("Main Bar")).CaptionVisible(false).PaneBorder(false).Gripper().MinSize(TOP_BUTTON_OPTIMAL_WIDTH, m_panelTopButtons->GetSize().GetHeight())); - //note: min height is calculated incorrectly by wxAuiManager if panes with and without caption are in the same row => use smaller min-size + auiMgr.AddPane(m_panelTopButtons, + wxAuiPaneInfo().Name(L"PanelTop").Layer(2).Top().Row(1).Caption(_("Main Bar")).CaptionVisible(false).PaneBorder(false).Gripper().MinSize(TOP_BUTTON_OPTIMAL_WIDTH, m_panelTopButtons->GetSize().GetHeight())); + //note: min height is calculated incorrectly by wxAuiManager if panes with and without caption are in the same row => use smaller min-size - auiMgr.AddPane(compareStatus->getAsWindow(), - wxAuiPaneInfo().Name(L"PanelProgress").Layer(2).Top().Row(2).CaptionVisible(false).PaneBorder(false).Hide()); - } + auiMgr.AddPane(compareStatus->getAsWindow(), + wxAuiPaneInfo().Name(L"PanelProgress").Layer(2).Top().Row(2).CaptionVisible(false).PaneBorder(false).Hide()); + } auiMgr.AddPane(m_panelDirectoryPairs, wxAuiPaneInfo().Name(L"PanelFolders").Layer(2).Top().Row(3).Caption(_("Folder Pairs")).CaptionVisible(false).PaneBorder(false).Gripper()); - auiMgr.AddPane(m_panelSearch, + auiMgr.AddPane(m_panelSearch, wxAuiPaneInfo().Name(L"PanelFind").Layer(2).Bottom().Row(2).Caption(_("Find")).CaptionVisible(false).PaneBorder(false).Gripper().MinSize(200, m_bpButtonHideSearch->GetSize().GetHeight()).Hide()); auiMgr.AddPane(m_panelViewFilter, @@ -803,10 +807,10 @@ MainDialog::MainDialog(const Zstring& globalConfigFile, if (havePartialPair != haveFullPair) //either all pairs full or all half-filled -> validity check! { - //potentially slow network access: give all checks 500ms to finish - const bool allFilesExist = firstMissingDir.timedWait(boost::posix_time::milliseconds(500)) && //true: have result - !firstMissingDir.get(); //no missing - if (allFilesExist) + const bool startComparisonNow = !firstMissingDir.timedWait(boost::posix_time::milliseconds(500)) || //= no result yet => start comparison anyway! + !firstMissingDir.get(); //= all directories exist + + if (startComparisonNow) if (wxEvtHandler* evtHandler = m_buttonCompare->GetEventHandler()) { wxCommandEvent dummy2(wxEVT_COMMAND_BUTTON_CLICKED); @@ -1944,9 +1948,10 @@ void MainDialog::onLocalKeyEvent(wxKeyEvent& event) //process key events without !isComponentOf(focus, m_gridMainR ) && // !isComponentOf(focus, m_gridNavi ) && !isComponentOf(focus, m_listBoxHistory) && //don't propagate if selecting config - !isComponentOf(focus, m_directoryLeft ) && //don't propagate if changing directory field - !isComponentOf(focus, m_directoryRight) && !isComponentOf(focus, m_panelSearch ) && + !isComponentOf(focus, m_panelTopLeft ) && //don't propagate if changing directory fields + !isComponentOf(focus, m_panelTopMiddle) && + !isComponentOf(focus, m_panelTopRight ) && !isComponentOf(focus, m_scrolledWindowFolderPairs) && m_gridMainL->IsEnabled()) if (wxEvtHandler* evtHandler = m_gridMainL->getMainWin().GetEventHandler()) @@ -3220,18 +3225,13 @@ void MainDialog::setConfig(const xmlAccess::XmlGuiConfig& newGuiCfg, const std:: updateGlobalFilterButton(); //set first folder pair - firstFolderPair->setValues(currentCfg.mainCfg.firstPair.dirpathPhraseLeft, - currentCfg.mainCfg.firstPair.dirpathPhraseRight, - currentCfg.mainCfg.firstPair.altCmpConfig, - currentCfg.mainCfg.firstPair.altSyncConfig, - currentCfg.mainCfg.firstPair.localFilter); + firstFolderPair->setValues(currentCfg.mainCfg.firstPair); //folderHistoryLeft->addItem(currentCfg.mainCfg.firstPair.leftDirectory); //folderHistoryRight->addItem(currentCfg.mainCfg.firstPair.rightDirectory); setAddFolderPairs(currentCfg.mainCfg.additionalPairs); - //read GUI layout setViewTypeSyncAction(currentCfg.highlightSyncAction); clearGrid(); //+ update GUI! @@ -3240,17 +3240,6 @@ void MainDialog::setConfig(const xmlAccess::XmlGuiConfig& newGuiCfg, const std:: } -inline -FolderPairEnh getEnhancedPair(const FolderPairPanel* panel) -{ - return FolderPairEnh(panel->getLeftDir(), - panel->getRightDir(), - panel->getAltCompConfig(), - panel->getAltSyncConfig(), - panel->getAltFilterConfig()); -} - - xmlAccess::XmlGuiConfig MainDialog::getConfig() const { xmlAccess::XmlGuiConfig guiCfg = currentCfg; @@ -3258,16 +3247,13 @@ xmlAccess::XmlGuiConfig MainDialog::getConfig() const //load settings whose ownership lies not in currentCfg: //first folder pair - guiCfg.mainCfg.firstPair = FolderPairEnh(firstFolderPair->getLeftDir(), - firstFolderPair->getRightDir(), - firstFolderPair->getAltCompConfig(), - firstFolderPair->getAltSyncConfig(), - firstFolderPair->getAltFilterConfig()); + guiCfg.mainCfg.firstPair = firstFolderPair->getValues(); //add additional pairs guiCfg.mainCfg.additionalPairs.clear(); - std::transform(additionalFolderPairs.begin(), additionalFolderPairs.end(), - std::back_inserter(guiCfg.mainCfg.additionalPairs), getEnhancedPair); + + for (const FolderPairPanel* panel : additionalFolderPairs) + guiCfg.mainCfg.additionalPairs.push_back(panel->getValues()); //sync preview guiCfg.highlightSyncAction = m_bpButtonViewTypeSyncAction->isActive(); @@ -3616,12 +3602,19 @@ void MainDialog::updateGui() } -void MainDialog::clearGrid() +void MainDialog::clearGrid(ptrdiff_t pos) { - folderCmp.clear(); + if (!folderCmp.empty()) + { + assert(pos < makeSigned(folderCmp.size())); + if (pos < 0) + folderCmp.clear(); + else + folderCmp.erase(folderCmp.begin() + pos); + } + gridDataView->setData(folderCmp); treeDataView->setData(folderCmp); - updateGui(); } @@ -3837,20 +3830,17 @@ void MainDialog::onGridLabelLeftClickC(GridClickEvent& event) void MainDialog::OnSwapSides(wxCommandEvent& event) { - //swap directory names: first pair - firstFolderPair->setValues(firstFolderPair->getRightDir(), // swap directories - firstFolderPair->getLeftDir(), // - firstFolderPair->getAltCompConfig(), - firstFolderPair->getAltSyncConfig(), - firstFolderPair->getAltFilterConfig()); + //swap directory names: + FolderPairEnh fp1st = firstFolderPair->getValues(); + std::swap(fp1st.dirpathPhraseLeft, fp1st.dirpathPhraseRight); + firstFolderPair->setValues(fp1st); - //additional pairs for (FolderPairPanel* panel : additionalFolderPairs) - panel->setValues(panel->getRightDir(), // swap directories - panel->getLeftDir(), // - panel->getAltCompConfig(), - panel->getAltSyncConfig(), - panel->getAltFilterConfig()); + { + FolderPairEnh fp = panel->getValues(); + std::swap(fp.dirpathPhraseLeft, fp.dirpathPhraseRight); + panel->setValues(fp); + } //swap view filter bool tmp = m_bpButtonShowLeftOnly->isActive(); @@ -4166,47 +4156,28 @@ void MainDialog::startFindNext() //F3 or ENTER in m_textCtrlSearchTxt } -void MainDialog::OnAddFolderPair(wxCommandEvent& event) +void MainDialog::OnTopFolderPairAdd(wxCommandEvent& event) { #ifdef ZEN_WIN wxWindowUpdateLocker dummy(this); //leads to GUI corruption problems on Linux/OS X! #endif - std::vector<FolderPairEnh> newPairs; - newPairs.push_back(getConfig().mainCfg.firstPair); - - //clear first pair - const FolderPairEnh cfgEmpty; - firstFolderPair->setValues(cfgEmpty.dirpathPhraseLeft, - cfgEmpty.dirpathPhraseRight, - cfgEmpty.altCmpConfig, - cfgEmpty.altSyncConfig, - cfgEmpty.localFilter); - - //keep sequence to update GUI as last step - addAddFolderPair(newPairs, true); //add pair in front of additonal pairs + insertAddFolderPair({ FolderPairEnh() }, 0); + moveAddFolderPairUp(0); } -void MainDialog::OnRemoveTopFolderPair(wxCommandEvent& event) +void MainDialog::OnTopFolderPairRemove(wxCommandEvent& event) { - if (!additionalFolderPairs.empty()) - { #ifdef ZEN_WIN - wxWindowUpdateLocker dummy(this); //leads to GUI corruption problems on Linux/OS X! + wxWindowUpdateLocker dummy(this); //leads to GUI corruption problems on Linux/OS X! #endif - //get settings from second folder pair - const FolderPairEnh cfgSecond = getEnhancedPair(additionalFolderPairs[0]); - - //reset first pair - firstFolderPair->setValues(cfgSecond.dirpathPhraseLeft, - cfgSecond.dirpathPhraseRight, - cfgSecond.altCmpConfig, - cfgSecond.altSyncConfig, - cfgSecond.localFilter); - - removeAddFolderPair(0); //remove second folder pair (first of additional folder pairs) + assert(!additionalFolderPairs.empty()); + if (!additionalFolderPairs.empty()) + { + moveAddFolderPairUp(0); + removeAddFolderPair(0); } } @@ -4227,6 +4198,95 @@ void MainDialog::OnRemoveFolderPair(wxCommandEvent& event) } +void MainDialog::OnShowFolderPairOptions(wxCommandEvent& event) +{ +#ifdef ZEN_WIN + wxWindowUpdateLocker dummy(this); //leads to GUI corruption problems on Linux/OS X! +#endif + + const wxObject* const eventObj = event.GetEventObject(); //find folder pair originating the event + for (auto it = additionalFolderPairs.begin(); it != additionalFolderPairs.end(); ++it) + if (eventObj == (*it)->m_bpButtonFolderPairOptions) + { + const ptrdiff_t pos = it - additionalFolderPairs.begin(); + + ContextMenu menu; + menu.addItem(_("Add folder pair"), [this, pos] { insertAddFolderPair({ FolderPairEnh() }, pos); }, &getResourceImage(L"item_add_small")); + menu.addSeparator(); + menu.addItem(_("Move up" ) + L"\tAlt+Page Up" , [this, pos] { moveAddFolderPairUp(pos); }, &getResourceImage(L"move_up_small")); + menu.addItem(_("Move down") + L"\tAlt+Page Down", [this, pos] { moveAddFolderPairUp(pos + 1); }, &getResourceImage(L"move_down_small"), pos + 1 < makeSigned(additionalFolderPairs.size())); + menu.popup(*this); + + break; + } +} + + +void MainDialog::onTopFolderPairKeyEvent(wxKeyEvent& event) +{ + const int keyCode = event.GetKeyCode(); + + if (event.AltDown()) + switch (keyCode) + { + case WXK_PAGEDOWN: //Alt + Page Down + case WXK_NUMPAD_PAGEDOWN: + if (!additionalFolderPairs.empty()) + { + moveAddFolderPairUp(0); + additionalFolderPairs[0]->m_directoryLeft->SetFocus(); + } + return; + } + + event.Skip(); +} + + +void MainDialog::onAddFolderPairKeyEvent(wxKeyEvent& event) +{ + const int keyCode = event.GetKeyCode(); + + auto getAddFolderPairPos = [&]() -> ptrdiff_t //find folder pair originating the event + { + if (auto eventObj = dynamic_cast<const wxWindow*>(event.GetEventObject())) + for (auto it = additionalFolderPairs.begin(); it != additionalFolderPairs.end(); ++it) + if (isComponentOf(eventObj, *it)) + return it - additionalFolderPairs.begin(); + return -1; + }; + + if (event.AltDown()) + switch (keyCode) + { + case WXK_PAGEUP: //Alt + Page Up + case WXK_NUMPAD_PAGEUP: + { + const ptrdiff_t pos = getAddFolderPairPos(); + if (pos >= 0) + { + moveAddFolderPairUp(pos); + (pos == 0 ? m_directoryLeft : additionalFolderPairs[pos - 1]->m_directoryLeft)->SetFocus(); + } + } + return; + case WXK_PAGEDOWN: //Alt + Page Down + case WXK_NUMPAD_PAGEDOWN: + { + const ptrdiff_t pos = getAddFolderPairPos(); + if (0 <= pos && pos + 1 < makeSigned(additionalFolderPairs.size())) + { + moveAddFolderPairUp(pos + 1); + additionalFolderPairs[pos + 1]->m_directoryLeft->SetFocus(); + } + } + return; + } + + event.Skip(); +} + + void MainDialog::updateGuiForFolderPair() { #ifdef ZEN_WIN @@ -4286,18 +4346,13 @@ void MainDialog::updateGuiForFolderPair() } -void MainDialog::addAddFolderPair(const std::vector<FolderPairEnh>& newPairs, bool addFront) +void MainDialog::insertAddFolderPair(const std::vector<FolderPairEnh>& newPairs, size_t pos) { -#ifdef ZEN_WIN - wxWindowUpdateLocker dummy(m_panelDirectoryPairs); //leads to GUI corruption problems on Linux/OS X! -#endif - - std::vector<FolderPairPanel*> newEntries; + assert(pos <= additionalFolderPairs.size() && additionalFolderPairs.size() == bSizerAddFolderPairs->GetItemCount()); + pos = std::min(pos, additionalFolderPairs.size()); - std::for_each(newPairs.begin(), newPairs.end(), - [&](const FolderPairEnh& enhPair) + for (size_t i = 0; i < newPairs.size(); ++i) { - //add new folder pair FolderPairPanel* newPair = new FolderPairPanel(m_scrolledWindowFolderPairs, *this); //init dropdown history @@ -4308,58 +4363,73 @@ void MainDialog::addAddFolderPair(const std::vector<FolderPairEnh>& newPairs, bo const int width = m_panelTopLeft->GetSize().GetWidth(); newPair->m_panelLeft->SetMinSize(wxSize(width, -1)); - if (addFront) - { - bSizerAddFolderPairs->Insert(0, newPair, 0, wxEXPAND); - additionalFolderPairs.insert(additionalFolderPairs.begin(), newPair); - } - else - { - bSizerAddFolderPairs->Add(newPair, 0, wxEXPAND); - additionalFolderPairs.push_back(newPair); - } - newEntries.push_back(newPair); + bSizerAddFolderPairs->Insert(pos, newPair, 0, wxEXPAND); + additionalFolderPairs.insert(additionalFolderPairs.begin() + pos, newPair); //register events - newPair->m_bpButtonRemovePair->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MainDialog::OnRemoveFolderPair), nullptr, this); - }); + newPair->m_bpButtonFolderPairOptions->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MainDialog::OnShowFolderPairOptions), nullptr, this); + newPair->m_bpButtonFolderPairOptions->Connect(wxEVT_RIGHT_DOWN, wxCommandEventHandler(MainDialog::OnShowFolderPairOptions), nullptr, this); + newPair->m_bpButtonRemovePair ->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MainDialog::OnRemoveFolderPair ), nullptr, this); + static_cast<FolderPairPanelGenerated*>(newPair)->Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(MainDialog::onAddFolderPairKeyEvent), nullptr, this); + + } updateGuiForFolderPair(); //wxComboBox screws up miserably if width/height is smaller than the magic number 4! Problem occurs when trying to set tooltip //so we have to update window sizes before setting configuration: for (auto it = newPairs.begin(); it != newPairs.end(); ++it)//set alternate configuration - newEntries[it - newPairs.begin()]->setValues(it->dirpathPhraseLeft, - it->dirpathPhraseRight, - it->altCmpConfig, - it->altSyncConfig, - it->localFilter); + additionalFolderPairs[pos + (it - newPairs.begin())]->setValues(*it); clearGrid(); //+ GUI update } -void MainDialog::removeAddFolderPair(size_t pos) +void MainDialog::moveAddFolderPairUp(size_t pos) { -#ifdef ZEN_WIN - wxWindowUpdateLocker dummy(m_panelDirectoryPairs); //leads to GUI corruption problems on Linux/OS X! -#endif + assert(pos < additionalFolderPairs.size()); + if (pos < additionalFolderPairs.size()) + { + const FolderPairEnh cfgTmp = additionalFolderPairs[pos]->getValues(); + if (pos == 0) + { + additionalFolderPairs[pos]->setValues(firstFolderPair->getValues()); + firstFolderPair->setValues(cfgTmp); + } + else + { + additionalFolderPairs[pos]->setValues(additionalFolderPairs[pos - 1]->getValues()); + additionalFolderPairs[pos - 1]->setValues(cfgTmp); + } + + //move comparison results, too! + if (!folderCmp.empty()) + std::swap(folderCmp[pos], folderCmp[pos + 1]); //invariant: folderCmp is empty or matches number of all folder pairs + + gridDataView->setData(folderCmp); + treeDataView->setData(folderCmp); + updateGui(); + } +} + +void MainDialog::removeAddFolderPair(size_t pos) +{ + assert(pos < additionalFolderPairs.size()); if (pos < additionalFolderPairs.size()) { FolderPairPanel* panel = additionalFolderPairs[pos]; - bSizerAddFolderPairs->Detach(panel); //Remove() does not work on Window*, so do it manually + bSizerAddFolderPairs->Detach(panel); //Remove() does not work on wxWindow*, so do it manually additionalFolderPairs.erase(additionalFolderPairs.begin() + pos); //more (non-portable) wxWidgets bullshit: on OS X wxWindow::Destroy() screws up and calls "operator delete" directly rather than //the deferred deletion it is expected to do (and which is implemented correctly on Windows and Linux) //http://bb10.com/python-wxpython-devel/2012-09/msg00004.html //=> since we're in a mouse button callback of a sub-component of "panel" we need to delay deletion ourselves: processAsync2([] {}, [panel] { panel->Destroy(); }); - } - - updateGuiForFolderPair(); - clearGrid(); //+ GUI update + updateGuiForFolderPair(); + clearGrid(pos + 1); //+ GUI update + } } @@ -4372,9 +4442,8 @@ void MainDialog::setAddFolderPairs(const std::vector<zen::FolderPairEnh>& newPai additionalFolderPairs.clear(); bSizerAddFolderPairs->Clear(true); - //updateGuiForFolderPair(); -> already called in addAddFolderPair() - - addAddFolderPair(newPairs); + //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 943c76b7..7a2277c4 100644 --- a/FreeFileSync/Source/ui/main_dlg.h +++ b/FreeFileSync/Source/ui/main_dlg.h @@ -91,7 +91,8 @@ private: void removeObsoleteCfgHistoryItems(const std::vector<Zstring>& filepaths); void removeCfgHistoryItems(const std::vector<Zstring>& filepaths); - void addAddFolderPair(const std::vector<zen::FolderPairEnh>& newPairs, bool addFront = false); + void insertAddFolderPair(const std::vector<zen::FolderPairEnh>& newPairs, size_t pos); + void moveAddFolderPairUp(size_t pos); void removeAddFolderPair(size_t pos); void setAddFolderPairs(const std::vector<zen::FolderPairEnh>& newPairs); @@ -220,9 +221,13 @@ private: void filterItems(const std::vector<zen::FileSystemObject*>& selection, bool include); void filterPhrase(const Zstring& phrase, bool include, bool addNewLine); - void OnAddFolderPair (wxCommandEvent& event) override; + void OnTopFolderPairAdd (wxCommandEvent& event) override; + void OnTopFolderPairRemove(wxCommandEvent& event) override; void OnRemoveFolderPair (wxCommandEvent& event); - void OnRemoveTopFolderPair(wxCommandEvent& event) override; + void OnShowFolderPairOptions(wxCommandEvent& event); + + void onTopFolderPairKeyEvent(wxKeyEvent& event); + void onAddFolderPairKeyEvent(wxKeyEvent& event); void applyFilterConfig(); void applySyncConfig(); @@ -252,7 +257,7 @@ private: void switchProgramLanguage(int langID); - void clearGrid(); + void clearGrid(ptrdiff_t pos = -1); typedef int MenuItemID; typedef int LanguageID; diff --git a/FreeFileSync/Source/version/version.h b/FreeFileSync/Source/version/version.h index ab9ec7b2..7a3a844c 100644 --- a/FreeFileSync/Source/version/version.h +++ b/FreeFileSync/Source/version/version.h @@ -3,7 +3,7 @@ namespace zen { -const wchar_t currentVersion[] = L"6.13"; //internal linkage! +const wchar_t currentVersion[] = L"6.14"; //internal linkage! } #endif diff --git a/FreeFileSync/Source/version/version.iss b/FreeFileSync/Source/version/version.iss deleted file mode 100644 index 79ec3c65..00000000 --- a/FreeFileSync/Source/version/version.iss +++ /dev/null @@ -1 +0,0 @@ -#define FFS_Version "6.13" diff --git a/wx+/image_tools.cpp b/wx+/image_tools.cpp index c6be3852..1dbd5d46 100644 --- a/wx+/image_tools.cpp +++ b/wx+/image_tools.cpp @@ -156,7 +156,7 @@ wxImage zen::createImageFromText(const wxString& text, const wxFont& font, const dc.SetTextBackground(*wxWHITE); // dc.SetFont(font); - assert(!contains(text, L"&")); //accelerator keys not supported here; see also getTextExtent() + //assert(!contains(text, L"&")); //accelerator keys not supported here; see also getTextExtent() wxString textFmt = replaceCpy(text, L"&", L"", false); //for some reason wxDC::DrawText messes up "weak" bidi characters even when wxLayout_RightToLeft is set! (--> arrows in hebrew/arabic) diff --git a/zen/basic_math.h b/zen/basic_math.h index 9a3d195e..227d1cd4 100644 --- a/zen/basic_math.h +++ b/zen/basic_math.h @@ -88,22 +88,13 @@ const double ln2 = 0.693147180559945309417; - - - - - - - - - //################# inline implementation ######################### template <class T> inline T abs(T value) { static_assert(std::is_signed<T>::value, ""); if (value < 0) - return -value; // operator "?:" caveat: may be different type than "value" + return -value; //operator "?:" caveat: may be different type than "value" else return value; } diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp index 916f6c69..26645157 100644 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -369,29 +369,6 @@ std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void() #elif defined ZEN_LINUX -namespace -{ -class DirsOnlyTraverser : public zen::TraverseCallback -{ -public: - DirsOnlyTraverser(std::vector<Zstring>& dirs) : dirs_(dirs) {} - - void onFile (const Zchar* shortName, const Zstring& filepath, const FileInfo& details ) override {} - HandleLink onSymlink(const Zchar* shortName, const Zstring& linkpath, const SymlinkInfo& details) override { return LINK_SKIP; } - TraverseCallback* onDir (const Zchar* shortName, const Zstring& dirpath ) override - { - dirs_.push_back(dirpath); - return this; - } - HandleError reportDirError (const std::wstring& msg, size_t retryNumber) override { throw FileError(msg); } - HandleError reportItemError(const std::wstring& msg, size_t retryNumber, const Zchar* shortName) override { throw FileError(msg); } - -private: - std::vector<Zstring>& dirs_; -}; -} - - struct DirWatcher::Pimpl { Pimpl() : notifDescr() {} @@ -411,10 +388,11 @@ DirWatcher::DirWatcher(const Zstring& directory) : //throw FileError dirpathFmt.resize(dirpathFmt.size() - 1); std::vector<Zstring> fullDirList { dirpathFmt }; - { - DirsOnlyTraverser traverser(fullDirList); //throw FileError - zen::traverseFolder(dirpathFmt, traverser); //don't traverse into symlinks (analog to windows build) - } + +traverseFolder(dirpathFmt, nullptr, + [&](const DirInfo& di ){ fullDirList.push_back(di.fullPath); }, + nullptr, //don't traverse into symlinks (analog to windows build) +[&](const std::wstring& errorMsg){ throw FileError(errorMsg); }); //init pimpl_->basedirpath = directory; @@ -545,6 +523,7 @@ void eventCallback(ConstFSEventStreamRef streamRef, //events are aggregated => it's possible to see a single event with flags //kFSEventStreamEventFlagItemCreated | kFSEventStreamEventFlagItemModified | kFSEventStreamEventFlagItemRemoved + //https://developer.apple.com/library/mac/documentation/Darwin/Reference/FSEvents_Ref/index.html#//apple_ref/doc/constant_group/FSEventStreamEventFlags if (eventFlags[i] & kFSEventStreamEventFlagItemCreated || eventFlags[i] & kFSEventStreamEventFlagMount) changedFiles.emplace_back(DirWatcher::ACTION_CREATE, paths[i]); diff --git a/zen/file_access.cpp b/zen/file_access.cpp index e32a27a7..ca07e76a 100644 --- a/zen/file_access.cpp +++ b/zen/file_access.cpp @@ -206,11 +206,12 @@ Zstring getLockingProcessNames(const Zstring& filepath) //throw(), empty string const DllFun<FunType_getLockingProcesses> getLockingProcesses(getDllName(), funName_getLockingProcesses); const DllFun<FunType_freeString> freeString (getDllName(), funName_freeString); + const wchar_t* processList = nullptr; if (getLockingProcesses && freeString) - if (const wchar_t* procList = getLockingProcesses(filepath.c_str())) + if (getLockingProcesses(filepath.c_str(), processList)) { - ZEN_ON_SCOPE_EXIT(freeString(procList)); - return procList; + ZEN_ON_SCOPE_EXIT(freeString(processList)); + return processList; } } return Zstring(); @@ -249,6 +250,7 @@ std::uint64_t zen::getFilesize(const Zstring& filepath) //throw FileError throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(filepath)), L"CreateFile", getLastError()); ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile)); + //why not use ::GetFileSizeEx() instead??? BY_HANDLE_FILE_INFORMATION fileInfoHnd = {}; if (!::GetFileInformationByHandle(hFile, &fileInfoHnd)) throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(filepath)), L"GetFileInformationByHandle", getLastError()); @@ -548,42 +550,6 @@ void zen::renameFile(const Zstring& oldName, const Zstring& newName) //throw Fil namespace { -class CollectFilesFlat : public zen::TraverseCallback -{ -public: - CollectFilesFlat(std::vector<Zstring>& files, std::vector<Zstring>& dirs) : - files_(files), - dirs_(dirs) {} - - void onFile(const Zchar* shortName, const Zstring& filepath, const FileInfo& details) override - { - files_.push_back(filepath); - } - HandleLink onSymlink(const Zchar* shortName, const Zstring& linkpath, const SymlinkInfo& details) override - { - if (dirExists(linkpath)) //dir symlink - dirs_.push_back(linkpath); - else //file symlink, broken symlink - files_.push_back(linkpath); - return LINK_SKIP; - } - TraverseCallback* onDir(const Zchar* shortName, const Zstring& dirpath) override - { - dirs_.push_back(dirpath); - return nullptr; //DON'T traverse into subdirs; removeDirectory works recursively! - } - HandleError reportDirError (const std::wstring& msg, size_t retryNumber) override { throw FileError(msg); } - HandleError reportItemError(const std::wstring& msg, size_t retryNumber, const Zchar* shortName) override { throw FileError(msg); } - -private: - CollectFilesFlat (const CollectFilesFlat&) = delete; - CollectFilesFlat& operator=(const CollectFilesFlat&) = delete; - - std::vector<Zstring>& files_; - std::vector<Zstring>& dirs_; -}; - - void removeDirectoryImpl(const Zstring& directory, //throw FileError const std::function<void (const Zstring& filepath)>& onBeforeFileDeletion, const std::function<void (const Zstring& dirpath )>& onBeforeDirDeletion) @@ -615,11 +581,18 @@ void removeDirectoryImpl(const Zstring& directory, //throw FileError { std::vector<Zstring> fileList; std::vector<Zstring> dirList; - { - //get all files and directories from current directory (WITHOUT subdirectories!) - CollectFilesFlat cff(fileList, dirList); - traverseFolder(directory, cff); //don't follow symlinks - } + //get all files and directories from current directory (WITHOUT subdirectories!) +traverseFolder(directory, + [&](const FileInfo& fi){ fileList.push_back(fi.fullPath); }, + [&](const DirInfo& di){ dirList .push_back(di.fullPath); }, + [&](const SymlinkInfo& si) +{ + if (dirExists(si.fullPath)) //dir symlink + dirList.push_back(si.fullPath); + else //file symlink, broken symlink + fileList.push_back(si.fullPath); +}, +[&](const std::wstring& errorMsg){ throw FileError(errorMsg); }); //delete directories recursively for (const Zstring& dirpath : dirList) @@ -893,6 +866,74 @@ void setFileTimeRaw(const Zstring& filepath, //assert(std::abs(filetimeToTimeT(creationTimeDbg ) - filetimeToTimeT(creationTime )) <= 2); -> creation time not available for Linux-hosted Samba shares! #endif } + +#elif defined ZEN_MAC +struct AttrBufFileTimes +{ + std::uint32_t length; + struct ::timespec createTime; //keep order; see docs! + struct ::timespec writeTime; // +} __attribute__((aligned(4), packed)); + + +void setFileTimeRaw(const Zstring& filepath, + const struct ::timespec* createTime, //optional + const struct ::timespec& writeTime, + ProcSymlink procSl) //throw FileError +{ + //OS X: utime() is obsoleted by utimes()! utimensat() not yet implemented + //use ::setattrlist() instead of ::utimes() => 1. set file creation times 2. nanosecond precision + //https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/setattrlist.2.html + + struct ::attrlist attribs = {}; + attribs.bitmapcount = ATTR_BIT_MAP_COUNT; + attribs.commonattr = (createTime ? ATTR_CMN_CRTIME : 0) | ATTR_CMN_MODTIME; + + AttrBufFileTimes newTimes = {}; + if (createTime) + { + newTimes.createTime.tv_sec = createTime->tv_sec; + newTimes.createTime.tv_nsec = createTime->tv_nsec; + } + newTimes.writeTime.tv_sec = writeTime.tv_sec; + newTimes.writeTime.tv_nsec = writeTime.tv_nsec; + + const int rv = ::setattrlist(filepath.c_str(), //const char* path, + &attribs, //struct ::attrlist* attrList, + createTime ? &newTimes.createTime : &newTimes.writeTime, //void* attrBuf, + (createTime ? sizeof(newTimes.createTime) : 0) + sizeof(newTimes.writeTime), //size_t attrBufSize, + procSl == ProcSymlink::DIRECT ? FSOPT_NOFOLLOW : 0); //unsigned long options + if (rv != 0) + throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filepath)), L"setattrlist", getLastError()); +} + +/* +void getFileTimeRaw(int fd, //throw FileError + const Zstring& filepath, //for error reporting only + struct ::timespec& createTime, //out + struct ::timespec& writeTime) // +{ + //https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/getattrlist.2.html + struct ::attrlist attribs = {}; + attribs.bitmapcount = ATTR_BIT_MAP_COUNT; + attribs.commonattr = ATTR_CMN_CRTIME | ATTR_CMN_MODTIME; + + AttrBufFileTimes fileTimes = {}; + + const int rv = ::fgetattrlist(fd, //int fd, + &attribs, //struct ::attrlist* attrList, + &fileTimes, //void* attrBuf, + sizeof(fileTimes), //size_t attrBufSize, + 0); //unsigned long options + if (rv != 0) + throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(filepath)), L"getattrlist", getLastError()); + + createTime.tv_sec = fileTimes.createTime.tv_sec; + createTime.tv_nsec = fileTimes.createTime.tv_nsec; + writeTime.tv_sec = fileTimes.writeTime.tv_sec; + writeTime.tv_nsec = fileTimes.writeTime.tv_nsec; +} +*/ #endif } @@ -913,7 +954,7 @@ void zen::setFileTime(const Zstring& filepath, std::int64_t modTime, ProcSymlink #ifdef ZEN_WIN setFileTimeRaw(filepath, nullptr, timetToFileTime(modTime), procSl); //throw FileError -#elif defined ZEN_LINUX || defined ZEN_MAC +#elif defined ZEN_LINUX //sigh, we can't use utimensat on NTFS volumes on Ubuntu: silent failure!!! what morons are programming this shit??? // struct ::timespec newTimes[2] = {}; @@ -925,8 +966,6 @@ void zen::setFileTime(const Zstring& filepath, std::int64_t modTime, ProcSymlink //=> fallback to "retarded-idiot version"! -- DarkByte - //OS X: utime() is obsoleted by utimes()! utimensat() not yet implemented - struct ::timeval newTimes[2] = {}; newTimes[0].tv_sec = ::time(nullptr); //access time (seconds) newTimes[1].tv_sec = modTime; //modification time (seconds) @@ -936,6 +975,11 @@ void zen::setFileTime(const Zstring& filepath, std::int64_t modTime, ProcSymlink ::lutimes(filepath.c_str(), newTimes); if (rv != 0) throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filepath)), L"utimes", getLastError()); + +#elif defined ZEN_MAC + struct ::timespec writeTime = {}; + writeTime.tv_sec = modTime; + setFileTimeRaw(filepath, nullptr, writeTime, procSl); //throw FileError #endif } @@ -1209,7 +1253,7 @@ void copyObjectPermissions(const Zstring& source, const Zstring& target, ProcSym if (::copyfile(source.c_str(), target.c_str(), 0, flags) != 0) throwFileError(replaceCpy(replaceCpy(_("Cannot copy permissions from %x to %y."), L"%x", L"\n" + fmtFileName(source)), L"%y", L"\n" + fmtFileName(target)), L"copyfile", getLastError()); - //owner is *not* copied with ::copyfile(): + //owner is *not* copied with ::copyfile(): struct ::stat fileInfo = {}; if (procSl == ProcSymlink::FOLLOW) @@ -1359,16 +1403,16 @@ void zen::makeDirectoryPlain(const Zstring& directory, //throw FileError, ErrorT } #elif defined ZEN_LINUX || defined ZEN_MAC - mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO; //= default for newly created directory + mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO; //= default for newly created directory struct ::stat dirInfo = {}; - if (!templateDir.empty()) + if (!templateDir.empty()) if (::stat(templateDir.c_str(), &dirInfo) == 0) - { - mode = dirInfo.st_mode; //analog to "cp" which copies "mode" (considering umask) by default - mode |= S_IRWXU; //FFS only: we need full access to copy child items! "cp" seems to apply permissions *after* copying child items - } - //=> need copyObjectPermissions() only for "chown" and umask-agnostic permissions + { + mode = dirInfo.st_mode; //analog to "cp" which copies "mode" (considering umask) by default + mode |= S_IRWXU; //FFS only: we need full access to copy child items! "cp" seems to apply permissions *after* copying child items + } + //=> need copyObjectPermissions() only for "chown" and umask-agnostic permissions if (::mkdir(directory.c_str(), mode) != 0) { @@ -1518,17 +1562,19 @@ void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool setFileTimeRaw(targetLink, &sourceAttr.ftCreationTime, sourceAttr.ftLastWriteTime, ProcSymlink::DIRECT); //throw FileError #elif defined ZEN_LINUX || defined ZEN_MAC - struct ::stat srcInfo = {}; - if (::lstat(sourceLink.c_str(), &srcInfo) != 0) + struct ::stat sourceInfo = {}; + if (::lstat(sourceLink.c_str(), &sourceInfo) != 0) throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(sourceLink)), L"lstat", getLastError()); - setFileTime(targetLink, srcInfo.st_mtime, ProcSymlink::DIRECT); //throw FileError -#endif +#ifdef ZEN_LINUX + setFileTime(targetLink, sourceInfo.st_mtime, ProcSymlink::DIRECT); //throw FileError +#elif defined ZEN_MAC + setFileTimeRaw(targetLink, &sourceInfo.st_birthtimespec, sourceInfo.st_mtimespec, ProcSymlink::DIRECT); //throw FileError -#ifdef ZEN_MAC if (::copyfile(sourceLink.c_str(), targetLink.c_str(), 0, COPYFILE_XATTR | COPYFILE_NOFOLLOW) != 0) throwFileError(replaceCpy(replaceCpy(_("Cannot copy attributes from %x to %y."), L"%x", L"\n" + fmtFileName(sourceLink)), L"%y", L"\n" + fmtFileName(targetLink)), L"copyfile", getLastError()); #endif +#endif if (copyFilePermissions) copyObjectPermissions(sourceLink, targetLink, ProcSymlink::DIRECT); //throw FileError @@ -1933,8 +1979,7 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize, file time handling: ::CopyFileEx() will (only) copy file modification time over from source file AFTER the last invokation of this callback => it is possible to adapt file creation time of target in here, but NOT file modification time! - CAVEAT: if ::CopyFileEx() fails to set modification time, it silently ignores this error and returns success!!! - see procmon log in: https://sourceforge.net/tracker/?func=detail&atid=1093080&aid=3514569&group_id=234430 + CAVEAT: if ::CopyFileEx() fails to set modification time, it silently ignores this error and returns success!!! (confirmed with Process Monitor) alternate data stream handling: CopyFileEx() processes multiple streams one after another, stream 1 is the file data stream and always available! @@ -2110,10 +2155,9 @@ void copyFileWindowsDefault(const Zstring& sourceFile, newAttrib->sourceFileId = extractFileId(cbd.fileInfoSrc); newAttrib->targetFileId = extractFileId(cbd.fileInfoTrg); } - warn_static("new perf check + investigate improvements now that DST hcak is gone! =>") //caveat: - ::CopyFileEx() silently *ignores* failure to set modification time!!! => we always need to set it again but with proper error checking! - // - perf-loss on USB sticks with many small files of about 30%! + // - perf: recent measurements show no slow down at all for buffered USB sticks! setFileTimeRaw(targetFile, &cbd.fileInfoSrc.ftCreationTime, cbd.fileInfoSrc.ftLastWriteTime, ProcSymlink::FOLLOW); //throw FileError guardTarget.dismiss(); //target has been created successfully! @@ -2176,8 +2220,8 @@ void copyFileOsSpecific(const Zstring& sourceFile, try { FileOutputUnbuffered fileOut(targetFile, //throw FileError, ErrorTargetExisting - sourceInfo.st_mode); //analog to "cp" which copies "mode" (considering umask) by default - //=> need copyObjectPermissions() only for "chown" and umask-agnostic permissions + sourceInfo.st_mode); //analog to "cp" which copies "mode" (considering umask) by default + //=> need copyObjectPermissions() only for "chown" and umask-agnostic permissions std::vector<char> buffer(128 * 1024); //see comment in FileInputUnbuffered::read do @@ -2213,7 +2257,7 @@ void copyFileOsSpecific(const Zstring& sourceFile, throw; } - //we cannot set the target file times while the file descriptor is open and being written: + //we cannot set the target file times while the file descriptor is still open after a write operation: //this triggers bugs on samba shares where the modification time is set to current time instead. //http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=340236 //http://comments.gmane.org/gmane.linux.file-systems.cifs/2854 @@ -2278,78 +2322,87 @@ void copyFileOsSpecific(const Zstring& sourceFile, const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus, InSyncAttributes* newAttrib) //throw FileError, ErrorTargetExisting { + struct ::stat sourceInfo = {}; + //http://blog.plasticsfuture.org/2006/03/05/the-state-of-backup-and-cloning-tools-under-mac-os-x/ + { + auto getCopyErrorMessage = [&] { return replaceCpy(replaceCpy(_("Cannot copy file %x to %y."), L"%x", L"\n" + fmtFileName(sourceFile)), L"%y", L"\n" + fmtFileName(targetFile)); }; - auto getCopyErrorMessage = [&] { return replaceCpy(replaceCpy(_("Cannot copy file %x to %y."), L"%x", L"\n" + fmtFileName(sourceFile)), L"%y", L"\n" + fmtFileName(targetFile)); }; + copyfile_state_t copyState = ::copyfile_state_alloc(); + ZEN_ON_SCOPE_EXIT(::copyfile_state_free(copyState)); - copyfile_state_t copyState = ::copyfile_state_alloc(); - ZEN_ON_SCOPE_EXIT(::copyfile_state_free(copyState)); + CallbackData cbd(onUpdateCopyStatus, sourceFile, targetFile); - CallbackData cbd(onUpdateCopyStatus, sourceFile, targetFile); + if (::copyfile_state_set(copyState, COPYFILE_STATE_STATUS_CTX, &cbd) != 0) + throwFileError(getCopyErrorMessage(), L"copyfile_state_set, COPYFILE_STATE_STATUS_CTX", getLastError()); - if (::copyfile_state_set(copyState, COPYFILE_STATE_STATUS_CTX, &cbd) != 0) - throwFileError(getCopyErrorMessage(), L"copyfile_state_set, COPYFILE_STATE_STATUS_CTX", getLastError()); + if (::copyfile_state_set(copyState, COPYFILE_STATE_STATUS_CB, reinterpret_cast<const void*>(©FileCallback)) != 0) + throwFileError(getCopyErrorMessage(), L"copyfile_state_set, COPYFILE_STATE_STATUS_CB", getLastError()); - if (::copyfile_state_set(copyState, COPYFILE_STATE_STATUS_CB, reinterpret_cast<const void*>(©FileCallback)) != 0) - throwFileError(getCopyErrorMessage(), L"copyfile_state_set, COPYFILE_STATE_STATUS_CB", getLastError()); + zen::ScopeGuard guardTarget = zen::makeGuard([&] { try { removeFile(targetFile); } catch (FileError&) {} }); //transactional behavior: docs seem to indicate that copyfile does not clean up - zen::ScopeGuard guardTarget = zen::makeGuard([&] { try { removeFile(targetFile); } catch (FileError&) {} }); //transactional behavior: docs seem to indicate that copyfile does not clean up + //http://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/copyfile.3.html + if (::copyfile(sourceFile.c_str(), targetFile.c_str(), + copyState, + COPYFILE_XATTR | COPYFILE_DATA | COPYFILE_EXCL) != 0) + //- even though we don't use COPYFILE_STAT, "mode" (considering umask) is still copied! => harmonized with Linux file copy! + //- COPYFILE_STAT does not copy file creation time + { + //evaluate first! errno is not set for COPYFILE_QUIT! + if (cbd.exception) + std::rethrow_exception(cbd.exception); - //http://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/copyfile.3.html - if (::copyfile(sourceFile.c_str(), targetFile.c_str(), - copyState, - COPYFILE_XATTR | COPYFILE_DATA | COPYFILE_EXCL) != 0) //even though we don't use COPYFILE_STAT, "mode" (considering umask) is still copied! => harmonized with Linux file copy! - { - //evaluate first! errno is not set for COPYFILE_QUIT! - if (cbd.exception) - std::rethrow_exception(cbd.exception); + if (!cbd.errorMsg.first.empty()) + throw FileError(cbd.errorMsg.first, cbd.errorMsg.second); - if (!cbd.errorMsg.first.empty()) - throw FileError(cbd.errorMsg.first, cbd.errorMsg.second); + const int lastError = errno; + std::wstring errorDescr = formatSystemError(L"copyfile", lastError); - const int lastError = errno; - std::wstring errorDescr = formatSystemError(L"copyfile", lastError); + if (lastError == EEXIST) + { + guardTarget.dismiss(); //don't delete file that existed previously! + throw ErrorTargetExisting(getCopyErrorMessage(), errorDescr); + } - if (lastError == EEXIST) - { - guardTarget.dismiss(); //don't delete file that existed previously! - throw ErrorTargetExisting(getCopyErrorMessage(), errorDescr); + throw FileError(getCopyErrorMessage(), errorDescr); } - throw FileError(getCopyErrorMessage(), errorDescr); - } - - int fdSource = 0; - if (::copyfile_state_get(copyState, COPYFILE_STATE_SRC_FD, &fdSource) != 0) - throwFileError(getCopyErrorMessage(), L"copyfile_state_get, COPYFILE_STATE_SRC_FD", getLastError()); + int fdSource = 0; + if (::copyfile_state_get(copyState, COPYFILE_STATE_SRC_FD, &fdSource) != 0) + throwFileError(getCopyErrorMessage(), L"copyfile_state_get, COPYFILE_STATE_SRC_FD", getLastError()); - int fdTarget = 0; - if (::copyfile_state_get(copyState, COPYFILE_STATE_DST_FD, &fdTarget) != 0) - throwFileError(getCopyErrorMessage(), L"copyfile_state_get, COPYFILE_STATE_DST_FD", getLastError()); + int fdTarget = 0; + if (::copyfile_state_get(copyState, COPYFILE_STATE_DST_FD, &fdTarget) != 0) + throwFileError(getCopyErrorMessage(), L"copyfile_state_get, COPYFILE_STATE_DST_FD", getLastError()); - struct ::stat sourceInfo = {}; - if (::fstat(fdSource, &sourceInfo) != 0) - throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(sourceFile)), L"fstat", getLastError()); + if (::fstat(fdSource, &sourceInfo) != 0) + throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(sourceFile)), L"fstat", getLastError()); - struct ::stat targetInfo = {}; - if (::fstat(fdTarget, &targetInfo) != 0) - throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(targetFile)), L"fstat", getLastError()); + struct ::stat targetInfo = {}; + if (::fstat(fdTarget, &targetInfo) != 0) + throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(targetFile)), L"fstat", getLastError()); - struct ::timeval newTimes[2] = {}; - newTimes[0].tv_sec = sourceInfo.st_atime; //access time (seconds) - newTimes[1].tv_sec = sourceInfo.st_mtime; //modification time (seconds) + if (newAttrib) + { + newAttrib->fileSize = sourceInfo.st_size; + newAttrib->modificationTime = sourceInfo.st_mtimespec.tv_sec; //use same time variable as setFileTimeRaw() for consistency + //newAttrib->modificationTime = sourceInfo.st_mtime; // + newAttrib->sourceFileId = extractFileId(sourceInfo); + newAttrib->targetFileId = extractFileId(targetInfo); + } - if (::futimes(fdTarget, newTimes) != 0) - throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(targetFile)), L"futimes", getLastError()); + guardTarget.dismiss(); + } //make sure target file handle is closed before setting modification time! - if (newAttrib) - { - newAttrib->fileSize = sourceInfo.st_size; - newAttrib->modificationTime = sourceInfo.st_mtime; - newAttrib->sourceFileId = extractFileId(sourceInfo); - newAttrib->targetFileId = extractFileId(targetInfo); - } + zen::ScopeGuard guardTarget = zen::makeGuard([&] { try { removeFile(targetFile); } catch (FileError&) {} }); + //same issue like on Linux: we cannot set the target file times (::futimes) while the file descriptor is still open after a write operation: + //this triggers bugs on samba shares where the modification time is set to current time instead. + //https://sourceforge.net/p/freefilesync/discussion/help/thread/881357c0/ + //http://comments.gmane.org/gmane.linux.file-systems.cifs/2854 + setFileTimeRaw(targetFile, &sourceInfo.st_birthtimespec, sourceInfo.st_mtimespec, ProcSymlink::FOLLOW); //throw FileError + //sourceInfo.st_birthtime; -> only seconds-preicions + //sourceInfo.st_mtime; -> guardTarget.dismiss(); } #endif diff --git a/zen/file_io.cpp b/zen/file_io.cpp index c56d6ac0..00e33a60 100644 --- a/zen/file_io.cpp +++ b/zen/file_io.cpp @@ -32,11 +32,12 @@ Zstring getLockingProcessNames(const Zstring& filepath) //throw(), empty string const DllFun<FunType_getLockingProcesses> getLockingProcesses(getDllName(), funName_getLockingProcesses); const DllFun<FunType_freeString> freeString (getDllName(), funName_freeString); + const wchar_t* processList = nullptr; if (getLockingProcesses && freeString) - if (const wchar_t* procList = getLockingProcesses(filepath.c_str())) + if (getLockingProcesses(filepath.c_str(), processList)) { - ZEN_ON_SCOPE_EXIT(freeString(procList)); - return procList; + ZEN_ON_SCOPE_EXIT(freeString(processList)); + return processList; } } return Zstring(); diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp index f03cf464..2d652d2b 100644 --- a/zen/file_traverser.cpp +++ b/zen/file_traverser.cpp @@ -5,23 +5,20 @@ // ************************************************************************** #include "file_traverser.h" -#include "sys_error.h" -#include "symlink_target.h" +#include "file_error.h" #include "int64.h" #ifdef ZEN_WIN - #include "win_ver.h" #include "long_path_prefix.h" #include "file_access.h" - #include "dll.h" - #include "FindFilePlus/find_file_plus.h" - + #include "symlink_target.h" #elif defined ZEN_MAC - #include "osx_string.h" + #include "osx_string.h" #endif #if defined ZEN_LINUX || defined ZEN_MAC - #include <cstddef> //required by GCC 4.8.1 to find ptrdiff_t + #include <cstddef> //offsetof + #include <unistd.h> //::pathconf() #include <sys/stat.h> #include <dirent.h> #endif @@ -29,491 +26,105 @@ using namespace zen; -namespace -{ -//implement "retry" in a generic way: - -template <class Command> inline //function object expecting to throw FileError if operation fails -bool tryReportingDirError(Command cmd, zen::TraverseCallback& callback) //return "true" on success, "false" if error was ignored -{ - for (size_t retryNumber = 0;; ++retryNumber) - try - { - cmd(); //throw FileError - return true; - } - catch (const FileError& e) - { - switch (callback.reportDirError(e.toString(), retryNumber)) - { - case TraverseCallback::ON_ERROR_RETRY: - break; - case TraverseCallback::ON_ERROR_IGNORE: - return false; - } - } -} - -template <class Command> inline //function object expecting to throw FileError if operation fails -bool tryReportingItemError(Command cmd, zen::TraverseCallback& callback, const Zchar* shortName) //return "true" on success, "false" if error was ignored -{ - for (size_t retryNumber = 0;; ++retryNumber) - try - { - cmd(); //throw FileError - return true; - } - catch (const FileError& e) - { - switch (callback.reportItemError(e.toString(), retryNumber, shortName)) - { - case TraverseCallback::ON_ERROR_RETRY: - break; - case TraverseCallback::ON_ERROR_IGNORE: - return false; - } - } -} - - -#ifdef ZEN_WIN -TraverseCallback::FileInfo getInfoFromFileSymlink(const Zstring& linkName) //throw FileError -{ - //open handle to target of symbolic link - HANDLE hFile = ::CreateFile(zen::applyLongPathPrefix(linkName).c_str(), //_In_ LPCTSTR lpFileName, - 0, //_In_ DWORD dwDesiredAccess, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode, - nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, - OPEN_EXISTING, //_In_ DWORD dwCreationDisposition, - FILE_FLAG_BACKUP_SEMANTICS, //_In_ DWORD dwFlagsAndAttributes, - //needed to open a directory -> keep it even if we expect to open a file! See comment below - nullptr); //_In_opt_ HANDLE hTemplateFile - if (hFile == INVALID_HANDLE_VALUE) - throwFileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(linkName)), L"CreateFile", getLastError()); - ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile)); - - BY_HANDLE_FILE_INFORMATION fileInfo = {}; - if (!::GetFileInformationByHandle(hFile, &fileInfo)) - throwFileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(linkName)), L"GetFileInformationByHandle", getLastError()); - - //a file symlink may incorrectly point to a directory, but both CreateFile() and GetFileInformationByHandle() will succeed and return garbage! - //- if we did not use FILE_FLAG_BACKUP_SEMANTICS above, CreateFile() would error out with an even less helpful ERROR_ACCESS_DENIED! - //- reinterpreting the link as a directory symlink would still fail during traversal, so just show an error here - //- OTOH a directory symlink that points to a file fails immediately in ::FindFirstFile() with ERROR_DIRECTORY! -> nothing to do in this case - if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(linkName)), formatSystemError(L"GetFileInformationByHandle", static_cast<DWORD>(ERROR_FILE_INVALID))); - - TraverseCallback::FileInfo output; - output.fileSize = get64BitUInt(fileInfo.nFileSizeLow, fileInfo.nFileSizeHigh); - output.lastWriteTime = filetimeToTimeT(fileInfo.ftLastWriteTime); - output.id = extractFileId(fileInfo); //consider detection of moved files: allow for duplicate file ids, renaming affects symlink, not target, ... - //output.symlinkInfo -> not filled here - return output; -} - - -DWORD retrieveVolumeSerial(const Zstring& pathName) //returns 0 on error or if serial is not supported! -{ - //this works for: - //- root paths "C:\", "D:\" - //- network shares: \\share\dirname - //- indirection: subst S: %USERPROFILE% - // -> GetVolumePathName() + GetVolumeInformation() OTOH incorrectly resolves "S:\Desktop\somedir" to "S:\Desktop\" - nice try... - const HANDLE hDir = ::CreateFile(zen::applyLongPathPrefix(pathName).c_str(), //_In_ LPCTSTR lpFileName, - 0, //_In_ DWORD dwDesiredAccess, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode, - nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, - OPEN_EXISTING, //_In_ DWORD dwCreationDisposition, - // FILE_FLAG_OPEN_REPARSE_POINT -> no, we follow symlinks! - FILE_FLAG_BACKUP_SEMANTICS, //_In_ DWORD dwFlagsAndAttributes, - /*needed to open a directory*/ - nullptr); //_In_opt_ HANDLE hTemplateFile - if (hDir == INVALID_HANDLE_VALUE) - return 0; - ZEN_ON_SCOPE_EXIT(::CloseHandle(hDir)); - - BY_HANDLE_FILE_INFORMATION fileInfo = {}; - if (!::GetFileInformationByHandle(hDir, &fileInfo)) - return 0; - - return fileInfo.dwVolumeSerialNumber; -} - - -const bool isXpOrLater = winXpOrLater(); //VS2010 compiled DLLs are not supported on Win 2000: Popup dialog "DecodePointer not found" - -#define DEF_DLL_FUN(name) const auto name = isXpOrLater ? DllFun<findplus::FunType_##name>(findplus::getDllName(), findplus::funName_##name) : DllFun<findplus::FunType_##name>(); -DEF_DLL_FUN(openDir); // -DEF_DLL_FUN(readDir); //load at startup: avoid pre C++11 static initialization MT issues -DEF_DLL_FUN(closeDir); // - -/* -Common C-style interface for Win32 FindFirstFile(), FindNextFile() and FileFilePlus openDir(), closeDir(): -struct TraverserPolicy //see "policy based design" -{ -typedef ... DirHandle; -typedef ... FindData; - -static DirHandle create(const Zstring& directory); //throw FileError - don't follow FindFirstFile() design: open handle only, *no* return of data! -static void destroy(DirHandle hnd); //throw() - -static bool getEntry(DirHandle hnd, const Zstring& directory, FindData& fileInfo) //throw FileError, NeedFallbackToWin32Traverser -> fallback to FindFirstFile()/FindNextFile() - -//FindData "member" functions -static TraverseCallback::FileInfo extractFileInfo(const FindData& fileInfo, DWORD volumeSerial); //volumeSerial may be 0 if not available! -static std::int64_t getModTime (const FindData& fileInfo); -static const FILETIME& getModTimeRaw (const FindData& fileInfo); //yet another concession to DST hack -static const FILETIME& getCreateTimeRaw(const FindData& fileInfo); // -static const wchar_t* getItemName (const FindData& fileInfo); -static bool isDirectory (const FindData& fileInfo); -static bool isSymlink (const FindData& fileInfo); -} - -Note: Win32 FindFirstFile(), FindNextFile() is a weaker abstraction than FileFilePlus openDir(), readDir(), closeDir() and Unix opendir(), closedir(), stat() -*/ - - -struct Win32Traverser +void zen::traverseFolder(const Zstring& dirPath, + const std::function<void (const FileInfo& fi)>& onFile, + const std::function<void (const DirInfo& di)>& onDir, + const std::function<void (const SymlinkInfo& si)>& onLink, + const std::function<void (const std::wstring& errorMsg)>& onError) { - struct DirHandle - { - DirHandle(HANDLE hnd, const WIN32_FIND_DATA& d) : searchHandle(hnd), haveData(true), data(d) {} - explicit DirHandle(HANDLE hnd) : searchHandle(hnd), haveData(false) {} - - HANDLE searchHandle; - bool haveData; - WIN32_FIND_DATA data; - }; - - typedef WIN32_FIND_DATA FindData; - - static DirHandle create(const Zstring& dirpath) //throw FileError + try { - const Zstring& dirpathPf = appendSeparator(dirpath); - - WIN32_FIND_DATA fileData = {}; - HANDLE hnd = ::FindFirstFile(applyLongPathPrefix(dirpathPf + L'*').c_str(), &fileData); - //no noticable performance difference compared to FindFirstFileEx with FindExInfoBasic, FIND_FIRST_EX_CASE_SENSITIVE and/or FIND_FIRST_EX_LARGE_FETCH - if (hnd == INVALID_HANDLE_VALUE) +#ifdef ZEN_WIN + WIN32_FIND_DATA findData = {}; + HANDLE hDir = ::FindFirstFile(applyLongPathPrefix(appendSeparator(dirPath) + L'*').c_str(), &findData); + if (hDir == INVALID_HANDLE_VALUE) { const DWORD lastError = ::GetLastError(); //copy before making other system calls! if (lastError == ERROR_FILE_NOT_FOUND) { //1. directory may not exist *or* 2. it is completely empty: not all directories contain "., .." entries, e.g. a drive's root directory; NetDrive // -> FindFirstFile() is a nice example of violation of API design principle of single responsibility - if (dirExists(dirpath)) //yes, a race-condition, still the best we can do - return DirHandle(hnd); + if (dirExists(dirPath)) //yes, a race-condition, still the best we can do + return; } - throwFileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtFileName(dirpath)), L"FindFirstFile", lastError); - } - return DirHandle(hnd, fileData); - } - - static void destroy(const DirHandle& hnd) { ::FindClose(hnd.searchHandle); } //throw() - - static bool getEntry(DirHandle& hnd, const Zstring& dirpath, FindData& fileInfo) //throw FileError - { - if (hnd.searchHandle == INVALID_HANDLE_VALUE) //handle special case of "truly empty directories" - return false; - - if (hnd.haveData) - { - hnd.haveData = false; - ::memcpy(&fileInfo, &hnd.data, sizeof(fileInfo)); - return true; - } - - if (!::FindNextFile(hnd.searchHandle, &fileInfo)) - { - const DWORD lastError = ::GetLastError(); //copy before making other system calls! - if (lastError == ERROR_NO_MORE_FILES) //not an error situation - return false; - //else we have a problem... report it: - throwFileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(dirpath)), L"FindNextFile", lastError); - } - return true; - } - - static TraverseCallback::FileInfo extractFileInfo(const FindData& fileInfo, DWORD volumeSerial) - { - TraverseCallback::FileInfo output; - output.fileSize = get64BitUInt(fileInfo.nFileSizeLow, fileInfo.nFileSizeHigh); - output.lastWriteTime = getModTime(fileInfo); - //output.id = FileId(); - //output.symlinkInfo = nullptr; - return output; - } - - static std::int64_t getModTime (const FindData& fileInfo) { return filetimeToTimeT(fileInfo.ftLastWriteTime); } - static const FILETIME& getModTimeRaw (const FindData& fileInfo) { return fileInfo.ftLastWriteTime; } - static const FILETIME& getCreateTimeRaw(const FindData& fileInfo) { return fileInfo.ftCreationTime; } - static const wchar_t* getItemName (const FindData& fileInfo) { return fileInfo.cFileName; } - static bool isDirectory (const FindData& fileInfo) { return (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; } - static bool isSymlink (const FindData& fileInfo) { return zen::isSymlink(fileInfo); } //[!] keep namespace -}; - - -class NeedFallbackToWin32Traverser {}; //special exception class - - -struct FilePlusTraverser -{ - struct DirHandle - { - explicit DirHandle(findplus::FindHandle hnd) : searchHandle(hnd) {} - - findplus::FindHandle searchHandle; - }; - - typedef findplus::FileInformation FindData; - - static DirHandle create(const Zstring& dirpath) //throw FileError - { - const findplus::FindHandle hnd = ::openDir(applyLongPathPrefix(dirpath).c_str()); - if (!hnd) - throwFileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtFileName(dirpath)), L"openDir", getLastError()); - - return DirHandle(hnd); - } - - static void destroy(DirHandle hnd) { ::closeDir(hnd.searchHandle); } //throw() - - static bool getEntry(DirHandle hnd, const Zstring& dirpath, FindData& fileInfo) //throw FileError, NeedFallbackToWin32Traverser - { - if (!::readDir(hnd.searchHandle, fileInfo)) - { - const DWORD lastError = ::GetLastError(); //copy before directly or indirectly making other system calls! - if (lastError == ERROR_NO_MORE_FILES) //not an error situation - return false; - - /* - fallback to default directory query method, if FileIdBothDirectoryInformation is not properly implemented - this is required for NetDrive mounted Webdav, e.g. www.box.net and NT4, 2000 remote drives, et al. - */ - if (lastError == ERROR_NOT_SUPPORTED) - throw NeedFallbackToWin32Traverser(); - //fallback should apply to whole directory sub-tree! => client needs to handle duplicate file notifications! - - //else we have a problem... report it: - throwFileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(dirpath)), L"readDir", lastError); + throwFileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtFileName(dirPath)), L"FindFirstFile", lastError); } + ZEN_ON_SCOPE_EXIT(::FindClose(hDir)); - return true; - } - - static TraverseCallback::FileInfo extractFileInfo(const FindData& fileInfo, DWORD volumeSerial) - { - TraverseCallback::FileInfo output; - output.fileSize = fileInfo.fileSize; - output.lastWriteTime = getModTime(fileInfo); - output.id = extractFileId(volumeSerial, fileInfo.fileId); - //output.symlinkInfo = nullptr; - return output; - } - - static std::int64_t getModTime (const FindData& fileInfo) { return filetimeToTimeT(fileInfo.lastWriteTime); } - static const FILETIME& getModTimeRaw (const FindData& fileInfo) { return fileInfo.lastWriteTime; } - static const FILETIME& getCreateTimeRaw(const FindData& fileInfo) { return fileInfo.creationTime; } - static const wchar_t* getItemName (const FindData& fileInfo) { return fileInfo.shortName; } - static bool isDirectory (const FindData& fileInfo) { return (fileInfo.fileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; } - static bool isSymlink (const FindData& fileInfo) { return zen::isSymlink(fileInfo.fileAttributes, fileInfo.reparseTag); } //[!] keep namespace -}; - - -class DirTraverser -{ -public: - static void execute(const Zstring& baseDirectory, TraverseCallback& sink) - { - DirTraverser(baseDirectory, sink); - } - -private: - DirTraverser(const Zstring& baseDirectory, TraverseCallback& sink); - DirTraverser (const DirTraverser&) = delete; - DirTraverser& operator=(const DirTraverser&) = delete; - - template <class Trav> - void traverse(const Zstring& dirpath, TraverseCallback& sink, DWORD volumeSerial); - - template <class Trav> - void traverseWithException(const Zstring& dirpath, TraverseCallback& sink, DWORD volumeSerial /*may be 0!*/); //throw FileError, NeedFallbackToWin32Traverser -}; - - -template <> inline -void DirTraverser::traverse<Win32Traverser>(const Zstring& dirpath, TraverseCallback& sink, DWORD volumeSerial) -{ - tryReportingDirError([&] - { - traverseWithException<Win32Traverser>(dirpath, sink, 0); //throw FileError - }, sink); -} - - -template <> inline -void DirTraverser::traverse<FilePlusTraverser>(const Zstring& dirpath, TraverseCallback& sink, DWORD volumeSerial) -{ - try - { - tryReportingDirError([&] - { - traverseWithException<FilePlusTraverser>(dirpath, sink, volumeSerial); //throw FileError, NeedFallbackToWin32Traverser - }, sink); - } - catch (NeedFallbackToWin32Traverser&) { traverse<Win32Traverser>(dirpath, sink, 0); } -} - - -inline -DirTraverser::DirTraverser(const Zstring& baseDirectory, TraverseCallback& sink) -{ - try //traversing certain folders with restricted permissions requires this privilege! (but copying these files may still fail) - { - activatePrivilege(SE_BACKUP_NAME); //throw FileError - } - catch (FileError&) {} //don't cause issues in user mode - - if (::openDir && ::readDir && ::closeDir) - traverse<FilePlusTraverser>(baseDirectory, sink, retrieveVolumeSerial(baseDirectory)); //retrieveVolumeSerial returns 0 on error - else //fallback - traverse<Win32Traverser>(baseDirectory, sink, 0); -} - - -template <class Trav> -void DirTraverser::traverseWithException(const Zstring& dirpath, TraverseCallback& sink, DWORD volumeSerial /*may be 0!*/) //throw FileError, NeedFallbackToWin32Traverser -{ - //no need to check for endless recursion: Windows seems to have an internal path limit of about 700 chars - - typename Trav::DirHandle searchHandle = Trav::create(dirpath); //throw FileError - ZEN_ON_SCOPE_EXIT(Trav::destroy(searchHandle)); - - typename Trav::FindData findData = {}; - - while (Trav::getEntry(searchHandle, dirpath, findData)) //throw FileError, NeedFallbackToWin32Traverser - //don't retry but restart dir traversal on error! http://blogs.msdn.com/b/oldnewthing/archive/2014/06/12/10533529.aspx - { - //skip "." and ".." - const Zchar* const shortName = Trav::getItemName(findData); - if (shortName[0] == L'.' && - (shortName[1] == 0 || (shortName[1] == L'.' && shortName[2] == 0))) - continue; + bool firstIteration = true; + for (;;) + { + if (firstIteration) //keep ::FindNextFile at the start of the for-loop to support "continue"! + firstIteration = false; + else + if (!::FindNextFile(hDir, &findData)) + { + const DWORD lastError = ::GetLastError(); + if (lastError == ERROR_NO_MORE_FILES) //not an error situation + return; + //else we have a problem... report it: + throwFileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(dirPath)), L"FindNextFile", lastError); + } - const Zstring& itempath = appendSeparator(dirpath) + shortName; + //skip "." and ".." + const Zchar* const shortName = findData.cFileName; + if (shortName[0] == L'.' && + (shortName[1] == 0 || (shortName[1] == L'.' && shortName[2] == 0))) + continue; - if (Trav::isSymlink(findData)) //check first! - { - TraverseCallback::SymlinkInfo linkInfo; - linkInfo.lastWriteTime = Trav::getModTime (findData); + const Zstring& itempath = appendSeparator(dirPath) + shortName; - switch (sink.onSymlink(shortName, itempath, linkInfo)) + if (zen::isSymlink(findData)) //check first! { - case TraverseCallback::LINK_FOLLOW: - if (Trav::isDirectory(findData)) - { - if (TraverseCallback* trav = sink.onDir(shortName, itempath)) - { - ZEN_ON_SCOPE_EXIT(sink.releaseDirTraverser(trav)); - traverse<Trav>(itempath, *trav, retrieveVolumeSerial(itempath)); //symlink may link to different volume => redetermine volume serial! - } - } - else //a file - { - TraverseCallback::FileInfo targetInfo; - const bool validLink = tryReportingItemError([&] //try to resolve symlink (and report error on failure!!!) - { - targetInfo = getInfoFromFileSymlink(itempath); //throw FileError - targetInfo.symlinkInfo = &linkInfo; - }, sink, shortName); - - if (validLink) - sink.onFile(shortName, itempath, targetInfo); - // else //broken symlink -> ignore: it's client's responsibility to handle error! - } - break; - - case TraverseCallback::LINK_SKIP: - break; + if (onLink) + onLink({ shortName, itempath, filetimeToTimeT(findData.ftLastWriteTime) }); } - } - else if (Trav::isDirectory(findData)) - { - if (TraverseCallback* trav = sink.onDir(shortName, itempath)) + else if ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { - ZEN_ON_SCOPE_EXIT(sink.releaseDirTraverser(trav)); - traverse<Trav>(itempath, *trav, volumeSerial); + if (onDir) + onDir({ shortName, itempath }); + } + else //a file + { + if (onFile) + onFile({ shortName, itempath, get64BitUInt(findData.nFileSizeLow, findData.nFileSizeHigh), filetimeToTimeT(findData.ftLastWriteTime) }); } } - else //a file - { - const TraverseCallback::FileInfo fileInfo = Trav::extractFileInfo(findData, volumeSerial); - sink.onFile(shortName, itempath, fileInfo); - } - } -} - #elif defined ZEN_LINUX || defined ZEN_MAC -class DirTraverser -{ -public: - static void execute(const Zstring& baseDirectory, TraverseCallback& sink) - { - DirTraverser(baseDirectory, sink); - } - -private: - DirTraverser(const Zstring& baseDirectory, zen::TraverseCallback& sink) - { - const Zstring directoryFormatted = //remove trailing slash - baseDirectory.size() > 1 && endsWith(baseDirectory, FILE_NAME_SEPARATOR) ? //exception: allow '/' - beforeLast(baseDirectory, FILE_NAME_SEPARATOR) : - baseDirectory; + const Zstring dirPathFmt = //remove trailing slash + dirPath.size() > 1 && endsWith(dirPath, FILE_NAME_SEPARATOR) ? //exception: allow '/' + beforeLast(dirPath, FILE_NAME_SEPARATOR) : + dirPath; /* 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 + len = offsetof(struct dirent, d_name) + pathconf(dirPath, _PC_NAME_MAX) + 1 entryp = malloc(len); */ - const size_t nameMax = std::max<long>(::pathconf(directoryFormatted.c_str(), _PC_NAME_MAX), 10000); //::pathconf may return long(-1) - buffer.resize(offsetof(struct ::dirent, d_name) + nameMax + 1); - - traverse(directoryFormatted, sink); - } - - DirTraverser (const DirTraverser&) = delete; - DirTraverser& operator=(const DirTraverser&) = delete; - - void traverse(const Zstring& dirpath, TraverseCallback& sink) - { - tryReportingDirError([&] - { - traverseWithException(dirpath, sink); //throw FileError - }, sink); - } - - void traverseWithException(const Zstring& dirpath, TraverseCallback& sink) //throw FileError - { - //no need to check for endless recursion: Linux has a fixed limit on the number of symbolic links in a path + const size_t nameMax = std::max<long>(::pathconf(dirPathFmt.c_str(), _PC_NAME_MAX), 10000); //::pathconf may return long(-1) + std::vector<char> buffer(offsetof(struct ::dirent, d_name) + nameMax + 1); +#ifdef ZEN_MAC + std::vector<char> bufferUtfDecomposed; +#endif - DIR* dirObj = ::opendir(dirpath.c_str()); //directory must NOT end with path separator, except "/" + DIR* dirObj = ::opendir(dirPathFmt.c_str()); //directory must NOT end with path separator, except "/" if (!dirObj) - throwFileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtFileName(dirpath)), L"opendir", getLastError()); + throwFileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtFileName(dirPathFmt)), L"opendir", getLastError()); ZEN_ON_SCOPE_EXIT(::closedir(dirObj)); //never close nullptr handles! -> crash for (;;) { struct ::dirent* dirEntry = nullptr; if (::readdir_r(dirObj, reinterpret_cast< ::dirent*>(&buffer[0]), &dirEntry) != 0) - throwFileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(dirpath)), L"readdir_r", getLastError()); + throwFileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(dirPathFmt)), L"readdir_r", getLastError()); //don't retry but restart dir traversal on error! http://blogs.msdn.com/b/oldnewthing/archive/2014/06/12/10533529.aspx if (!dirEntry) //no more items return; //don't return "." and ".." - const char* shortName = dirEntry->d_name; //evaluate dirEntry *before* going into recursion => we use a single "buffer"! + const char* shortName = dirEntry->d_name; if (shortName[0] == '.' && (shortName[1] == 0 || (shortName[1] == '.' && shortName[2] == 0))) continue; @@ -531,83 +142,41 @@ private: { bufferUtfDecomposed.resize(lenMax); if (::CFStringGetFileSystemRepresentation(cfStr, &bufferUtfDecomposed[0], lenMax)) //get decomposed UTF form (verified!) despite ambiguous documentation - shortName = &bufferUtfDecomposed[0]; //attention: => don't access "shortName" after recursion in "traverse"! + shortName = &bufferUtfDecomposed[0]; } } //const char* sampleDecomposed = "\x6f\xcc\x81.txt"; //const char* samplePrecomposed = "\xc3\xb3.txt"; #endif - const Zstring& itempath = appendSeparator(dirpath) + shortName; + const Zstring& itempath = appendSeparator(dirPathFmt) + shortName; struct ::stat statData = {}; - if (!tryReportingItemError([&] - { - if (::lstat(itempath.c_str(), &statData) != 0) //lstat() does not resolve symlinks - throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(itempath)), L"lstat", getLastError()); - }, sink, shortName)) - continue; //ignore error: skip file + try + { + if (::lstat(itempath.c_str(), &statData) != 0) //lstat() does not resolve symlinks + throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(itempath)), L"lstat", getLastError()); + } + catch (const FileError& e) + { + if (onError) + onError(e.toString()); + continue; //ignore error: skip file + } if (S_ISLNK(statData.st_mode)) //on Linux there is no distinction between file and directory symlinks! { - TraverseCallback::SymlinkInfo linkInfo; - linkInfo.lastWriteTime = statData.st_mtime; //UTC time (ANSI C format); unit: 1 second - - switch (sink.onSymlink(shortName, itempath, linkInfo)) - { - case TraverseCallback::LINK_FOLLOW: - { - //try to resolve symlink (and report error on failure!!!) - struct ::stat statDataTrg = {}; - bool validLink = tryReportingItemError([&] - { - if (::stat(itempath.c_str(), &statDataTrg) != 0) - throwFileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(itempath)), L"stat", getLastError()); - }, sink, shortName); - - if (validLink) - { - if (S_ISDIR(statDataTrg.st_mode)) //a directory - { - if (TraverseCallback* trav = sink.onDir(shortName, itempath)) - { - ZEN_ON_SCOPE_EXIT(sink.releaseDirTraverser(trav)); - traverse(itempath, *trav); - } - } - else //a file or named pipe, ect. - { - TraverseCallback::FileInfo fileInfo; - fileInfo.fileSize = statDataTrg.st_size; - fileInfo.lastWriteTime = statDataTrg.st_mtime; //UTC time (time_t format); unit: 1 second - fileInfo.id = extractFileId(statDataTrg); - fileInfo.symlinkInfo = &linkInfo; - sink.onFile(shortName, itempath, fileInfo); - } - } - // else //broken symlink -> ignore: it's client's responsibility to handle error! - } - break; - - case TraverseCallback::LINK_SKIP: - break; - } + if (onLink) + onLink({ shortName, itempath, statData.st_mtime}); } else if (S_ISDIR(statData.st_mode)) //a directory { - if (TraverseCallback* trav = sink.onDir(shortName, itempath)) - { - ZEN_ON_SCOPE_EXIT(sink.releaseDirTraverser(trav)); - traverse(itempath, *trav); - } + if (onDir) + onDir({ shortName, itempath }); } else //a file or named pipe, ect. { - TraverseCallback::FileInfo fileInfo; - fileInfo.fileSize = statData.st_size; - fileInfo.lastWriteTime = statData.st_mtime; //UTC time (time_t format); unit: 1 second - fileInfo.id = extractFileId(statData); - - sink.onFile(shortName, itempath, fileInfo); + if (onFile) + onFile({ shortName, itempath, makeUnsigned(statData.st_size), statData.st_mtime }); } /* 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: @@ -617,15 +186,11 @@ private: 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<char> buffer; -#ifdef ZEN_MAC - std::vector<char> bufferUtfDecomposed; -#endif -}; #endif + } + catch (const FileError& e) + { + if (onError) + onError(e.toString()); + } } - - -void zen::traverseFolder(const Zstring& dirpath, TraverseCallback& sink) { DirTraverser::execute(dirpath, sink); } diff --git a/zen/file_traverser.h b/zen/file_traverser.h index 174503b5..9cba9e58 100644 --- a/zen/file_traverser.h +++ b/zen/file_traverser.h @@ -4,64 +4,43 @@ // * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * // ************************************************************************** -#ifndef FILETRAVERSER_H_INCLUDED -#define FILETRAVERSER_H_INCLUDED +#ifndef FOLDER_TRAVERSER_H_INCLUDED_3127463214871234 +#define FOLDER_TRAVERSER_H_INCLUDED_3127463214871234 #include <cstdint> +#include <functional> #include "zstring.h" -#include "file_id_def.h" - -//advanced file traverser returning metadata and hierarchical information on files and directories namespace zen { -struct TraverseCallback +struct FileInfo { - virtual ~TraverseCallback() {} - - struct SymlinkInfo - { - SymlinkInfo() : lastWriteTime() {} - - std::int64_t lastWriteTime; //number of seconds since Jan. 1st 1970 UTC - }; - - struct FileInfo - { - FileInfo() : fileSize(), lastWriteTime(), symlinkInfo() {} - - std::uint64_t fileSize; //unit: bytes! - std::int64_t lastWriteTime; //number of seconds since Jan. 1st 1970 UTC - FileId id; //optional: initial if not supported! - const SymlinkInfo* symlinkInfo; //only filled if file is a followed symlink - }; - - enum HandleLink - { - LINK_FOLLOW, //dereferences link, then calls "onDir()" or "onFile()" - LINK_SKIP - }; - - enum HandleError - { - ON_ERROR_RETRY, - ON_ERROR_IGNORE - }; + const Zchar* shortName; + const Zstring& fullPath; + std::uint64_t fileSize; //[bytes] + std::int64_t lastWriteTime; //number of seconds since Jan. 1st 1970 UTC +}; - virtual void onFile (const Zchar* shortName, const Zstring& filepath, const FileInfo& details) = 0; - virtual HandleLink onSymlink(const Zchar* shortName, const Zstring& linkpath, const SymlinkInfo& details) = 0; - virtual TraverseCallback* onDir (const Zchar* shortName, const Zstring& dirpath) = 0; - //nullptr: ignore directory, non-nullptr: traverse into using the (new) callback => implement releaseDirTraverser() if necessary! - virtual void releaseDirTraverser(TraverseCallback* trav) {} +struct DirInfo +{ + const Zchar* shortName; + const Zstring& fullPath; +}; - 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 Zchar* shortName) = 0; //failed to get data for single file/dir/symlink only! +struct SymlinkInfo +{ + const Zchar* shortName; + const Zstring& fullPath; + std::int64_t lastWriteTime; //number of seconds since Jan. 1st 1970 UTC }; -//custom traverser with detail information about files -//- client needs to handle duplicate file reports! (FilePlusTraverser fallback, retrying to read directory contents, ...) -//- directory may end with PATH_SEPARATOR -void traverseFolder(const Zstring& dirpath, TraverseCallback& sink); //noexcept +//- non-recursive +//- directory path may end with PATH_SEPARATOR +void traverseFolder(const Zstring& dirPath, //noexcept + const std::function<void (const FileInfo& fi)>& onFile, // + const std::function<void (const DirInfo& di)>& onDir, //optional + const std::function<void (const SymlinkInfo& si)>& onLink, // + const std::function<void (const std::wstring& errorMsg)>& onError); // } -#endif // FILETRAVERSER_H_INCLUDED +#endif //FOLDER_TRAVERSER_H_INCLUDED_3127463214871234 diff --git a/zen/long_path_prefix.h b/zen/long_path_prefix.h index d2289330..19b838ef 100644 --- a/zen/long_path_prefix.h +++ b/zen/long_path_prefix.h @@ -47,15 +47,6 @@ As used by GetModuleFileNameEx() and symlinks (FSCTL_GET_REPARSE_POINT): - - - - - - - - - //################## implementation ################## //there are two flavors of long path prefix: one for UNC paths, one for regular paths @@ -128,11 +119,11 @@ Zstring zen::ntPathToWin32Path(const Zstring& path) //noexcept if (bufSize > 0) { std::vector<wchar_t> buf(bufSize); - DWORD charsWritten = ::GetEnvironmentVariable(L"SystemRoot", //_In_opt_ LPCTSTR lpName, + const DWORD charsWritten = ::GetEnvironmentVariable(L"SystemRoot", //_In_opt_ LPCTSTR lpName, &buf[0], //_Out_opt_ LPTSTR lpBuffer, bufSize); //_In_ DWORD nSize - if (charsWritten != 0 && charsWritten < bufSize) + if (0 < charsWritten && charsWritten < bufSize) return replaceCpy(path, L"\\SystemRoot\\", appendSeparator(Zstring(&buf[0], charsWritten)), false); } } diff --git a/zen/notify_removal.cpp b/zen/notify_removal.cpp index 37f305c9..f528e81b 100644 --- a/zen/notify_removal.cpp +++ b/zen/notify_removal.cpp @@ -86,7 +86,7 @@ MessageProvider::MessageProvider() : if (::RegisterClass(&wc) == 0) throwFileError(_("Unable to register to receive system messages."), L"RegisterClass", getLastError()); - ScopeGuard guardClass = makeGuard([&] { ::UnregisterClass(dummyClassName, hMainModule); }); + ScopeGuard guardConstructor = zen::makeGuard([&] { this->~MessageProvider(); }); //create dummy-window windowHandle = ::CreateWindow(dummyClassName, //_In_opt_ LPCTSTR lpClassName, @@ -106,14 +106,15 @@ MessageProvider::MessageProvider() : if (::SetWindowLongPtr(windowHandle, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this)) == 0 && ::GetLastError() != ERROR_SUCCESS) throwFileError(_("Unable to register to receive system messages."), L"SetWindowLongPtr", ::GetLastError()); - guardClass.dismiss(); + guardConstructor.dismiss(); } MessageProvider::~MessageProvider() { //clean-up in reverse order - ::DestroyWindow(windowHandle); + if (windowHandle) + ::DestroyWindow(windowHandle); ::UnregisterClass(dummyClassName, //LPCTSTR lpClassName OR ATOM in low-order word! hMainModule); //HINSTANCE hInstance } diff --git a/zen/recycler.cpp b/zen/recycler.cpp index 649bbb8e..3b5ac421 100644 --- a/zen/recycler.cpp +++ b/zen/recycler.cpp @@ -143,9 +143,7 @@ bool zen::recycleOrDelete(const Zstring& itempath) //throw FileError return false; //neither file nor any other object with that name existing: no error situation, manual deletion relies on it! #ifdef ZEN_WIN - std::vector<Zstring> itempaths; - itempaths.push_back(itempath); - recycleOrDelete(itempaths, nullptr); //throw FileError + recycleOrDelete({ itempath }, nullptr); //throw FileError #elif defined ZEN_LINUX GFile* file = ::g_file_new_for_path(itempath.c_str()); //never fails according to docu @@ -231,7 +229,7 @@ bool zen::recycleOrDelete(const Zstring& itempath) //throw FileError #ifdef ZEN_WIN -bool zen::recycleBinExists(const Zstring& pathName, const std::function<void ()>& onUpdateGui) //throw FileError +bool zen::recycleBinExists(const Zstring& dirpath, const std::function<void ()>& onUpdateGui) //throw FileError { if (vistaOrLater()) { @@ -240,24 +238,24 @@ bool zen::recycleBinExists(const Zstring& pathName, const std::function<void ()> const DllFun<FunType_getLastErrorMessage> getLastErrorMessage(getDllName(), funName_getLastErrorMessage); if (!getRecycleBinStatus || !getLastErrorMessage) - throw FileError(replaceCpy(_("Checking recycle bin failed for folder %x."), L"%x", fmtFileName(pathName)), + throw FileError(replaceCpy(_("Checking recycle bin failed for folder %x."), L"%x", fmtFileName(dirpath)), replaceCpy(_("Cannot load file %x."), L"%x", fmtFileName(getDllName()))); bool hasRecycler = false; - if (!getRecycleBinStatus(pathName.c_str(), hasRecycler)) - throw FileError(replaceCpy(_("Checking recycle bin failed for folder %x."), L"%x", fmtFileName(pathName)), getLastErrorMessage()); + if (!getRecycleBinStatus(dirpath.c_str(), hasRecycler)) + throw FileError(replaceCpy(_("Checking recycle bin failed for folder %x."), L"%x", fmtFileName(dirpath)), getLastErrorMessage()); return hasRecycler; } else { //excessive runtime if recycle bin exists, is full and drive is slow: - auto ft = async([pathName]() + auto ft = async([dirpath]() { SHQUERYRBINFO recInfo = {}; recInfo.cbSize = sizeof(recInfo); - return ::SHQueryRecycleBin(pathName.c_str(), //__in_opt LPCTSTR pszRootPath, - &recInfo); //__inout LPSHQUERYRBINFO pSHQueryRBInfo + return ::SHQueryRecycleBin(dirpath.c_str(), //__in_opt LPCTSTR pszRootPath, + &recInfo); //__inout LPSHQUERYRBINFO pSHQueryRBInfo }); while (!ft.timed_wait(boost::posix_time::milliseconds(50))) @@ -278,7 +276,7 @@ bool zen::recycleBinExists(const Zstring& pathName, const std::function<void ()> // -> not upward-compatible, wrong result for subst-alias: recycler assumed existing, although it is not! //5. alternative approach a'la Raymond Chen: http://blogs.msdn.com/b/oldnewthing/archive/2008/09/18/8956382.aspx - //caveat: might not be reliable, e.g. "subst"-alias of volume contains "$Recycle.Bin" although it is not available! + //caveat: might not be reliable, e.g. "subst"-alias of volume contains "$Recycle.Bin" although recycler is not available! /* Zstring rootPathPf = appendSeparator(&buffer[0]); diff --git a/zen/recycler.h b/zen/recycler.h index d86fbb12..2319f7b6 100644 --- a/zen/recycler.h +++ b/zen/recycler.h @@ -35,8 +35,8 @@ bool recycleOrDelete(const Zstring& itempath); //throw FileError, return "true" #ifdef ZEN_WIN -//can take a long time if recycle bin is full and drive is slow!!! => buffer volume ids! -bool recycleBinExists(const Zstring& pathName, const std::function<void ()>& onUpdateGui); //throw FileError +//can take a long time if recycle bin is full and drive is slow!!! => buffer! +bool recycleBinExists(const Zstring& dirpath, const std::function<void ()>& onUpdateGui); //throw FileError void recycleOrDelete(const std::vector<Zstring>& filepaths, //throw FileError, return "true" if file/dir was actually deleted const std::function<void (const Zstring& currentItem)>& notifyDeletionStatus); //optional; currentItem may be empty diff --git a/zen/stl_tools.h b/zen/stl_tools.h index d00fc732..d2d4ee1a 100644 --- a/zen/stl_tools.h +++ b/zen/stl_tools.h @@ -80,7 +80,7 @@ void vector_append(V& vec, const W& vec2) template <class V, class W> inline void set_append(V& s, const W& s2) { - s.insert(s2.begin(), s2.end()); + s.insert(s2.begin(), s2.end()); } diff --git a/zen/win_ver.h b/zen/win_ver.h index 97b6d7e1..9cf792f2 100644 --- a/zen/win_ver.h +++ b/zen/win_ver.h @@ -125,7 +125,7 @@ inline bool running64BitWindows() //http://blogs.msdn.com/b/oldnewthing/archive/2005/02/01/364563.aspx { static_assert(zen::is32BitBuild || zen::is64BitBuild, ""); - return is64BitBuild || runningWOW64(); //should we bother to make this a compile-time check? + return is64BitBuild || runningWOW64(); //should we bother to make this a compile-time check for the first case? } } |