summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/Batch.icobin0 -> 139831 bytes
-rw-r--r--lib/FindFilePlus/FindFilePlus.vcxproj245
-rw-r--r--lib/FindFilePlus/dll_main.cpp30
-rw-r--r--lib/FindFilePlus/find_file_plus.cpp298
-rw-r--r--lib/FindFilePlus/find_file_plus.h78
-rw-r--r--lib/FindFilePlus/init_dll_binding.h16
-rw-r--r--lib/FindFilePlus/load_dll.cpp23
-rw-r--r--lib/FindFilePlus/load_dll.h47
-rw-r--r--lib/FreeFileSync.icobin0 -> 132167 bytes
-rw-r--r--lib/IFileOperation/FileOperation_Vista.vcxproj240
-rw-r--r--lib/IFileOperation/dll_main.cpp25
-rw-r--r--lib/IFileOperation/file_op.cpp240
-rw-r--r--lib/IFileOperation/file_op.h62
-rw-r--r--lib/ShadowCopy/LockFile.cpp44
-rw-r--r--lib/ShadowCopy/Shadow_2003.vcxproj244
-rw-r--r--lib/ShadowCopy/Shadow_XP.vcxproj245
-rw-r--r--lib/ShadowCopy/dll_main.cpp25
-rw-r--r--lib/ShadowCopy/shadow.cpp187
-rw-r--r--lib/ShadowCopy/shadow.h93
-rw-r--r--lib/SyncDB.icobin0 -> 145378 bytes
-rw-r--r--lib/Thumbnail/Thumbnail.vcxproj239
-rw-r--r--lib/Thumbnail/dll_main.cpp25
-rw-r--r--lib/Thumbnail/thumbnail.cpp167
-rw-r--r--lib/Thumbnail/thumbnail.h68
-rw-r--r--lib/binary.cpp125
-rw-r--r--lib/binary.h28
-rw-r--r--lib/cmp_filetime.h65
-rw-r--r--lib/custom_grid.cpp2400
-rw-r--r--lib/custom_grid.h370
-rw-r--r--lib/db_file.cpp597
-rw-r--r--lib/db_file.h31
-rw-r--r--lib/detect_renaming.cpp285
-rw-r--r--lib/detect_renaming.h26
-rw-r--r--lib/dir_exist_async.h42
-rw-r--r--lib/dir_lock.cpp600
-rw-r--r--lib/dir_lock.h35
-rw-r--r--lib/dir_name.cpp174
-rw-r--r--lib/dir_name.h51
-rw-r--r--lib/error_log.cpp99
-rw-r--r--lib/error_log.h49
-rw-r--r--lib/ffs_paths.h127
-rw-r--r--lib/folder_history_box.cpp142
-rw-r--r--lib/folder_history_box.h106
-rw-r--r--lib/hard_filter.cpp368
-rw-r--r--lib/hard_filter.h279
-rw-r--r--lib/help_provider.h59
-rw-r--r--lib/icon_buffer.cpp600
-rw-r--r--lib/icon_buffer.h50
-rw-r--r--lib/localization.cpp433
-rw-r--r--lib/localization.h41
-rw-r--r--lib/lock_holder.h61
-rw-r--r--lib/norm_filter.h85
-rw-r--r--lib/parallel_scan.cpp600
-rw-r--r--lib/parallel_scan.h74
-rw-r--r--lib/parse_lng.h608
-rw-r--r--lib/parse_plural.h412
-rw-r--r--lib/process_xml.cpp1174
-rw-r--r--lib/process_xml.h285
-rw-r--r--lib/recycler.cpp204
-rw-r--r--lib/recycler.h46
-rw-r--r--lib/resolve_path.cpp685
-rw-r--r--lib/resolve_path.h26
-rw-r--r--lib/resources.cpp99
-rw-r--r--lib/resources.h38
-rw-r--r--lib/shadow.cpp134
-rw-r--r--lib/shadow.h35
-rw-r--r--lib/soft_filter.h113
-rw-r--r--lib/statistics.cpp279
-rw-r--r--lib/statistics.h70
-rw-r--r--lib/status_handler.cpp34
-rw-r--r--lib/status_handler.h108
-rw-r--r--lib/xml_base.cpp103
-rw-r--r--lib/xml_base.h42
73 files changed, 15138 insertions, 0 deletions
diff --git a/lib/Batch.ico b/lib/Batch.ico
new file mode 100644
index 00000000..7b33067a
--- /dev/null
+++ b/lib/Batch.ico
Binary files differ
diff --git a/lib/FindFilePlus/FindFilePlus.vcxproj b/lib/FindFilePlus/FindFilePlus.vcxproj
new file mode 100644
index 00000000..2c4256a6
--- /dev/null
+++ b/lib/FindFilePlus/FindFilePlus.vcxproj
@@ -0,0 +1,245 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{70394AEF-5897-4911-AFA1-82EAF0581EFA}</ProjectGuid>
+ <RootNamespace>ShadowDll</RootNamespace>
+ <Keyword>Win32Proj</Keyword>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <PlatformToolset>Windows7.1SDK</PlatformToolset>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ <PlatformToolset>Windows7.1SDK</PlatformToolset>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <PlatformToolset>Windows7.1SDK</PlatformToolset>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ <PlatformToolset>Windows7.1SDK</PlatformToolset>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup>
+ <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">OBJ\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">OBJ\$(ProjectName)_$(Configuration)_$(Platform)\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</LinkIncremental>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">OBJ\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">OBJ\$(ProjectName)_$(Configuration)_$(Platform)\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</LinkIncremental>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">.\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">OBJ\$(ProjectName)_$(Configuration)_$(Platform)\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">OBJ\$(ProjectName)_$(Configuration)_$(Platform)\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</LinkIncremental>
+ <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">FindFilePlus_$(Platform)</TargetName>
+ <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">FindFilePlus_$(Platform)</TargetName>
+ <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">FindFilePlus_$(Platform)</TargetName>
+ <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">FindFilePlus_$(Platform)</TargetName>
+ <IncludePath Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">D:\Data\WinDDK\inc\ddk;D:\Data\WinDDK\inc\api;D:\Data\WinDDK\inc\crt;$(WindowsSdkDir)\include;$(VCInstallDir)include</IncludePath>
+ <IncludePath Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">D:\Data\WinDDK\inc\ddk;D:\Data\WinDDK\inc\api;D:\Data\WinDDK\inc\crt;$(WindowsSdkDir)\include;$(VCInstallDir)include</IncludePath>
+ <IncludePath Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">D:\Data\WinDDK\inc\ddk;D:\Data\WinDDK\inc\api;D:\Data\WinDDK\inc\crt;$(WindowsSdkDir)\include;$(VCInstallDir)include</IncludePath>
+ <IncludePath Condition="'$(Configuration)|$(Platform)'=='Release|x64'">D:\Data\WinDDK\inc\ddk;D:\Data\WinDDK\inc\api;D:\Data\WinDDK\inc\crt;$(WindowsSdkDir)\include;$(VCInstallDir)include</IncludePath>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <BuildLog>
+ <Path>$(IntDir)Build.html</Path>
+ </BuildLog>
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>_X86_;_DEBUG;_WINDOWS;_USRDLL;FIND_FILE_PLUS_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>true</MinimalRebuild>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <DebugInformationFormat>EditAndContinue</DebugInformationFormat>
+ <DisableSpecificWarnings>4100</DisableSpecificWarnings>
+ <AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <ProfileGuidedDatabase>
+ </ProfileGuidedDatabase>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <BuildLog>
+ <Path>$(IntDir)Build.html</Path>
+ </BuildLog>
+ <Midl>
+ <TargetEnvironment>X64</TargetEnvironment>
+ </Midl>
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>_AMD64_;_DEBUG;_WINDOWS;_USRDLL;FIND_FILE_PLUS_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>true</MinimalRebuild>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4100</DisableSpecificWarnings>
+ <AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <ProfileGuidedDatabase>
+ </ProfileGuidedDatabase>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX64</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <BuildLog>
+ <Path>$(IntDir)Build.html</Path>
+ </BuildLog>
+ <ClCompile>
+ <Optimization>MaxSpeed</Optimization>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>_X86_;NDEBUG;_WINDOWS;_USRDLL;FIND_FILE_PLUS_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4100</DisableSpecificWarnings>
+ <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <SubSystem>Windows</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
+ <ProfileGuidedDatabase>
+ </ProfileGuidedDatabase>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <BuildLog>
+ <Path>$(IntDir)Build.html</Path>
+ </BuildLog>
+ <Midl>
+ <TargetEnvironment>X64</TargetEnvironment>
+ </Midl>
+ <ClCompile>
+ <Optimization>MaxSpeed</Optimization>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>_AMD64_;NDEBUG;_WINDOWS;_USRDLL;FIND_FILE_PLUS_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4100</DisableSpecificWarnings>
+ <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <SubSystem>Windows</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
+ <ProfileGuidedDatabase>
+ </ProfileGuidedDatabase>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX64</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="dll_main.cpp">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</CompileAsManaged>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ </PrecompiledHeader>
+ <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</CompileAsManaged>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</CompileAsManaged>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ </PrecompiledHeader>
+ <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</CompileAsManaged>
+ </ClCompile>
+ <ClCompile Include="find_file_plus.cpp" />
+ <ClCompile Include="load_dll.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="find_file_plus.h" />
+ <ClInclude Include="load_dll.h" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/lib/FindFilePlus/dll_main.cpp b/lib/FindFilePlus/dll_main.cpp
new file mode 100644
index 00000000..aca474bc
--- /dev/null
+++ b/lib/FindFilePlus/dll_main.cpp
@@ -0,0 +1,30 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+#include "init_dll_binding.h"
+
+
+//optional: add init/teardown logic here
+BOOL APIENTRY DllMain(HINSTANCE hinstDLL,
+ DWORD fdwReason,
+ LPVOID lpvReserved)
+{
+ switch (fdwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ if (!findplus::initDllBinding())
+ return false;
+ case DLL_PROCESS_DETACH:
+ case DLL_THREAD_ATTACH:
+ case DLL_THREAD_DETACH:
+ break;
+ }
+ return true;
+}
diff --git a/lib/FindFilePlus/find_file_plus.cpp b/lib/FindFilePlus/find_file_plus.cpp
new file mode 100644
index 00000000..becfe553
--- /dev/null
+++ b/lib/FindFilePlus/find_file_plus.cpp
@@ -0,0 +1,298 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "find_file_plus.h"
+#include "init_dll_binding.h"
+//#include <windows.h> //these two don't play nice with each other
+#include "load_dll.h"
+#include <zen/scope_guard.h>
+
+using namespace dll;
+using namespace findplus;
+
+
+namespace
+{
+struct FileError
+{
+ FileError(ULONG errorCode) : win32Error(errorCode) {}
+ ULONG win32Error;
+};
+
+
+//--------------------------------------------------------------------------------------------------------------
+typedef NTSTATUS (NTAPI* NtOpenFileFunc)(PHANDLE fileHandle,
+ ACCESS_MASK desiredAccess,
+ POBJECT_ATTRIBUTES objectAttributes,
+ PIO_STATUS_BLOCK ioStatusBlock,
+ ULONG shareAccess,
+ ULONG openOptions);
+
+typedef NTSTATUS (NTAPI* NtCloseFunc)(HANDLE handle);
+
+typedef NTSTATUS (NTAPI* NtQueryDirectoryFileFunc)(HANDLE fileHandle,
+ HANDLE event,
+ PIO_APC_ROUTINE apcRoutine,
+ PVOID apcContext,
+ PIO_STATUS_BLOCK ioStatusBlock,
+ PVOID fileInformation,
+ ULONG length,
+ FILE_INFORMATION_CLASS fileInformationClass,
+ BOOLEAN ReturnSingleEntry,
+ PUNICODE_STRING fileMask,
+ BOOLEAN restartScan);
+
+typedef ULONG (NTAPI* RtlNtStatusToDosErrorFunc)(NTSTATUS /*__in status*/);
+
+typedef struct _RTL_RELATIVE_NAME_U
+{
+ UNICODE_STRING RelativeName;
+ HANDLE ContainingDirectory;
+ PVOID /*PRTLP_CURDIR_REF*/ CurDirRef;
+} RTL_RELATIVE_NAME_U, *PRTL_RELATIVE_NAME_U;
+
+typedef BOOLEAN (NTAPI* RtlDosPathNameToNtPathName_UFunc)(PCWSTR, //__in dosFileName,
+ PUNICODE_STRING, //__out ntFileName,
+ PCWSTR*, //__out_optFilePart,
+ PRTL_RELATIVE_NAME_U); //__out_opt relativeName
+
+typedef BOOLEAN (NTAPI* RtlDosPathNameToRelativeNtPathName_UFunc)(PCWSTR, //__in dosFileName,
+ PUNICODE_STRING, //__out ntFileName,
+ PCWSTR*, //__out_optFilePart,
+ PRTL_RELATIVE_NAME_U); //__out_opt relativeName
+
+typedef VOID (NTAPI* RtlFreeUnicodeStringFunc)(PUNICODE_STRING); //__inout unicodeString
+
+//--------------------------------------------------------------------------------------------------------------
+
+//it seems we cannot use any of the ntoskrnl.lib files in WinDDK as they produce access violations
+//fortunately dynamic binding works fine:
+const SysDllFun<NtOpenFileFunc> ntOpenFile (L"ntdll.dll", "NtOpenFile");
+const SysDllFun<NtCloseFunc> ntClose (L"ntdll.dll", "NtClose");
+const SysDllFun<NtQueryDirectoryFileFunc> ntQueryDirectoryFile (L"ntdll.dll", "NtQueryDirectoryFile");
+const SysDllFun<RtlNtStatusToDosErrorFunc> rtlNtStatusToDosError (L"ntdll.dll", "RtlNtStatusToDosError");
+const SysDllFun<RtlFreeUnicodeStringFunc> rtlFreeUnicodeString (L"ntdll.dll", "RtlFreeUnicodeString");
+const SysDllFun<RtlDosPathNameToNtPathName_UFunc> rtlDosPathNameToNtPathName_U(SysDllFun<RtlDosPathNameToRelativeNtPathName_UFunc>(L"ntdll.dll", "RtlDosPathNameToRelativeNtPathName_U") ?
+ SysDllFun<RtlDosPathNameToRelativeNtPathName_UFunc>(L"ntdll.dll", "RtlDosPathNameToRelativeNtPathName_U") : //use the newer version if available
+ SysDllFun<RtlDosPathNameToNtPathName_UFunc>(L"ntdll.dll", "RtlDosPathNameToNtPathName_U")); //fallback for XP
+//global constants only -> preserve thread safety!
+}
+
+
+bool findplus::initDllBinding() //evaluate in ::DllMain() when attaching process
+{
+ //NT/ZwXxx Routines
+ //http://msdn.microsoft.com/en-us/library/ff567122(v=VS.85).aspx
+
+ //Run-Time Library (RTL) Routines
+ //http://msdn.microsoft.com/en-us/library/ff563638(v=VS.85).aspx
+
+ //verify dynamic dll binding
+ return ntOpenFile &&
+ ntClose &&
+ ntQueryDirectoryFile &&
+ rtlNtStatusToDosError &&
+ rtlFreeUnicodeString &&
+ rtlDosPathNameToNtPathName_U;
+
+ //this may become handy some time: nt status code STATUS_ORDINAL_NOT_FOUND maps to win32 code ERROR_INVALID_ORDINAL
+}
+
+
+class findplus::FileSearcher
+{
+public:
+ FileSearcher(const wchar_t* dirname); //throw FileError
+ ~FileSearcher();
+
+ void readdir(FileInformation& output); //throw FileError
+
+private:
+ UNICODE_STRING ntPathName; //it seems hDir implicitly keeps a reference to this, at least ::FindFirstFile() does no cleanup before ::FindClose()!
+ HANDLE hDir;
+
+ ULONG nextEntryOffset; //!= 0 if entry is waiting in buffer
+ //::FindNextFileW() uses 0x1000 = 4096 = sizeof(FILE_BOTH_DIR_INFORMATION) + sizeof(TCHAR) * 2000
+ //=> let's use the same, even if our header is 16 byte larger; maybe there is some packet size advantage for networks? Note that larger buffers seem to degrade performance.
+ static const ULONG BUFFER_SIZE = 4096;
+ LONGLONG buffer[BUFFER_SIZE / sizeof(LONGLONG)]; //buffer needs to be aligned at LONGLONG boundary
+
+ static_assert(BUFFER_SIZE % sizeof(LONGLONG) == 0, "ups, our buffer is trimmed!");
+};
+
+
+FileSearcher::FileSearcher(const wchar_t* dirname) :
+ hDir(NULL),
+ nextEntryOffset(0)
+{
+ ntPathName.Buffer = NULL;
+ ntPathName.Length = 0;
+ ntPathName.MaximumLength = 0;
+
+ zen::ScopeGuard guardConstructor = zen::makeGuard([&]() { this->~FileSearcher(); });
+ //--------------------------------------------------------------------------------------------------------------
+
+ //convert dosFileName, e.g. C:\Users or \\?\C:\Users to ntFileName \??\C:\Users
+ //in contrast to ::FindFirstFile() we don't evaluate the relativeName, however tests indicate ntFileName is *always* filled with an absolute name, even if dosFileName is relative
+
+ //NOTE: RtlDosPathNameToNtPathName_U may be used on all XP/Win7/Win8 for compatibility
+ // RtlDosPathNameToNtPathName_U: used by Windows XP available with OS version 3.51 (Windows NT) and higher
+ // RtlDosPathNameToRelativeNtPathName_U: used by Win7/Win8 available with OS version 5.2 (Windows Server 2003) and higher
+ if (!rtlDosPathNameToNtPathName_U(dirname, //__in dosFileName,
+ &ntPathName, //__out ntFileName,
+ NULL, //__out_optFilePart,
+ NULL)) //__out_opt relativeName - empty if dosFileName is absolute
+ throw FileError(rtlNtStatusToDosError(STATUS_OBJECT_PATH_NOT_FOUND)); //translates to ERROR_PATH_NOT_FOUND, same behavior like ::FindFirstFileEx()
+
+ OBJECT_ATTRIBUTES objAttr = {};
+ InitializeObjectAttributes(&objAttr, //[out] POBJECT_ATTRIBUTES initializedAttributes,
+ &ntPathName, //[in] PUNICODE_STRING objectName,
+ OBJ_CASE_INSENSITIVE, //[in] ULONG attributes,
+ NULL, //[in] HANDLE rootDirectory,
+ NULL); //[in, optional] PSECURITY_DESCRIPTOR securityDescriptor
+
+ {
+ IO_STATUS_BLOCK status = {};
+ NTSTATUS rv = ntOpenFile(&hDir, //__out PHANDLE FileHandle,
+ FILE_LIST_DIRECTORY | SYNCHRONIZE, //__in ACCESS_MASK desiredAccess, - 100001 used by ::FindFirstFile() on all XP/Win7/Win8
+ &objAttr, //__in POBJECT_ATTRIBUTES objectAttributes,
+ &status, //__out PIO_STATUS_BLOCK ioStatusBlock,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //__in ULONG shareAccess, - 7 on Win7/Win8, 3 on XP
+ FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT); //__in ULONG openOptions - 4021 used on all XP/Win7/Win8
+ if (!NT_SUCCESS(rv))
+ throw FileError(rtlNtStatusToDosError(rv));
+ }
+
+ guardConstructor.dismiss();
+}
+
+
+inline
+FileSearcher::~FileSearcher()
+{
+ //cleanup in reverse order
+ if (hDir)
+ ntClose(hDir);
+
+ if (ntPathName.Buffer)
+ rtlFreeUnicodeString(&ntPathName); //cleanup identical to ::FindFirstFile()
+ //note that most if this function seems inlined by the linker, so that its assembly looks equivalent to "RtlFreeHeap(GetProcessHeap(), 0, ntPathName.Buffer)"
+}
+
+
+void FileSearcher::readdir(FileInformation& output)
+{
+ //although FILE_ID_FULL_DIR_INFORMATION should suffice for our purposes, there are problems on Windows XP for certain directories, e.g. "\\Vboxsvr\build"
+ //making NtQueryDirectoryFile() return with STATUS_INVALID_PARAMETER while other directories, e.g. "C:\" work fine for some reason
+ //FILE_ID_BOTH_DIR_INFORMATION on the other hand works on XP/Win7/Win8
+ //performance: there is no noticable difference between FILE_ID_BOTH_DIR_INFORMATION and FILE_ID_FULL_DIR_INFORMATION
+
+ /* corresponding first access in ::FindFirstFileW()
+
+ NTSTATUS rv = ntQueryDirectoryFile(hDir, //__in HANDLE fileHandle,
+ NULL, //__in_opt HANDLE event,
+ NULL, //__in_opt PIO_APC_ROUTINE apcRoutine,
+ NULL, //__in_opt PVOID apcContext,
+ &status, //__out PIO_STATUS_BLOCK ioStatusBlock,
+ &buffer, //__out_bcount(Length) PVOID fileInformation,
+ BUFFER_SIZE, //__in ULONG length, ::FindFirstFileW() on all XP/Win7/Win8 uses sizeof(FILE_BOTH_DIR_INFORMATION) + sizeof(TCHAR) * MAX_PATH == 0x268
+ FileIdBothDirectoryInformation, //__in FILE_INFORMATION_CLASS fileInformationClass - all XP/Win7/Win8 use "FileBothDirectoryInformation"
+ true, //__in BOOLEAN returnSingleEntry,
+ NULL, //__in_opt PUNICODE_STRING mask,
+ false); //__in BOOLEAN restartScan
+ */
+
+ //analog to ::FindNextFileW() with performance optimized access (in contrast to first access in ::FindFirstFileW())
+ if (nextEntryOffset == 0)
+ {
+ IO_STATUS_BLOCK status = {};
+ NTSTATUS rv = ntQueryDirectoryFile(hDir, //__in HANDLE fileHandle,
+ NULL, //__in_opt HANDLE event,
+ NULL, //__in_opt PIO_APC_ROUTINE apcRoutine,
+ NULL, //__in_opt PVOID apcContext,
+ &status, //__out PIO_STATUS_BLOCK ioStatusBlock,
+ &buffer, //__out_bcount(Length) PVOID fileInformation,
+ BUFFER_SIZE, //__in ULONG length, ::FindNextFileW() on all XP/Win7/Win8 uses sizeof(FILE_BOTH_DIR_INFORMATION) + sizeof(TCHAR) * 2000 == 0x1000
+ FileIdBothDirectoryInformation, //__in FILE_INFORMATION_CLASS fileInformationClass - all XP/Win7/Win8 use "FileBothDirectoryInformation"
+ false, //__in BOOLEAN returnSingleEntry,
+ NULL, //__in_opt PUNICODE_STRING mask,
+ false); //__in BOOLEAN restartScan
+ if (!NT_SUCCESS(rv))
+ throw FileError(rtlNtStatusToDosError(rv)); //throws STATUS_NO_MORE_FILES when finished
+
+ if (status.Information == 0) //except for the first call to call ::NtQueryDirectoryFile():
+ throw FileError(rtlNtStatusToDosError(STATUS_BUFFER_OVERFLOW)); //if buffer size is too small, return value is STATUS_SUCCESS and Information == 0 -> we don't expect this!
+ }
+
+ const FILE_ID_BOTH_DIR_INFORMATION& dirInfo = *reinterpret_cast<FILE_ID_BOTH_DIR_INFORMATION*>(reinterpret_cast<char*>(buffer) + nextEntryOffset);
+
+ if (dirInfo.NextEntryOffset == 0)
+ nextEntryOffset = 0; //our offset is relative to the beginning of the buffer
+ else
+ nextEntryOffset += dirInfo.NextEntryOffset;
+
+
+ auto toFileTime = [](const LARGE_INTEGER & rawTime) -> FILETIME
+ {
+ FILETIME tmp = { rawTime.LowPart, rawTime.HighPart };
+ return tmp;
+ };
+
+ output.creationTime = toFileTime(dirInfo.CreationTime);
+ output.lastWriteTime = toFileTime(dirInfo.LastWriteTime);
+ output.fileSize.QuadPart = dirInfo.EndOfFile.QuadPart;
+ output.fileId.QuadPart = dirInfo.FileId.QuadPart;
+ output.fileAttributes = dirInfo.FileAttributes;
+ output.shortNameLength = dirInfo.FileNameLength / sizeof(TCHAR); //FileNameLength is in bytes!
+
+ if (dirInfo.FileNameLength + sizeof(TCHAR) > sizeof(output.shortName)) //this may actually happen if ::NtQueryDirectoryFile() decides to return a
+ throw FileError(rtlNtStatusToDosError(STATUS_BUFFER_OVERFLOW)); //short name of length MAX_PATH + 1, 0-termination is not required!
+
+ ::memcpy(output.shortName, dirInfo.FileName, dirInfo.FileNameLength);
+ output.shortName[output.shortNameLength] = 0; //NOTE: FILE_ID_BOTH_DIR_INFORMATION::FileName in general is NOT 0-terminated! It is on XP/Win7, but NOT on Win8!
+
+ static_assert(sizeof(output.creationTime) == sizeof(dirInfo.CreationTime), "dang!");
+ static_assert(sizeof(output.lastWriteTime) == sizeof(dirInfo.LastWriteTime), "dang!");
+ static_assert(sizeof(output.fileSize) == sizeof(dirInfo.EndOfFile), "dang!");
+ static_assert(sizeof(output.fileId) == sizeof(dirInfo.FileId), "dang!");
+ static_assert(sizeof(output.fileAttributes) == sizeof(dirInfo.FileAttributes), "dang!");
+}
+
+
+FindHandle findplus::openDir(const wchar_t* dirname)
+{
+ try
+ {
+ return new FileSearcher(dirname); //throw FileError
+ }
+ catch (const FileError& err)
+ {
+ setWin32Error(err.win32Error);
+ return NULL;
+ }
+}
+
+
+bool findplus::readDir(FindHandle hnd, FileInformation& output)
+{
+ try
+ {
+ hnd->readdir(output); //throw FileError
+ return true;
+ }
+ catch (const FileError& err)
+ {
+ setWin32Error(err.win32Error);
+ return false;
+ }
+}
+
+
+void findplus::closeDir(FindHandle hnd)
+{
+ if (hnd) //play a little "nice"
+ delete hnd;
+}
diff --git a/lib/FindFilePlus/find_file_plus.h b/lib/FindFilePlus/find_file_plus.h
new file mode 100644
index 00000000..aacdf0ea
--- /dev/null
+++ b/lib/FindFilePlus/find_file_plus.h
@@ -0,0 +1,78 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef FIND_FIRST_FILE_PLUS_HEADER_087483434
+#define FIND_FIRST_FILE_PLUS_HEADER_087483434
+
+#ifdef FIND_FILE_PLUS_DLL_EXPORTS
+#define DLL_FUNCTION_DECLARATION extern "C" __declspec(dllexport)
+#else
+#define DLL_FUNCTION_DECLARATION extern "C" __declspec(dllimport)
+#endif
+
+
+#ifdef FIND_FILE_PLUS_DLL_EXPORTS
+#include <Ntifs.h> //driver level headers must be included *before* windows api headers!
+#endif
+#include <windef.h> //
+#undef min
+#undef max
+
+#include <zen/build_info.h>
+
+namespace findplus
+{
+/*--------------
+ |declarations|
+ --------------*/
+
+struct FileInformation
+{
+ FILETIME creationTime;
+ FILETIME lastWriteTime;
+ ULARGE_INTEGER fileSize;
+ ULARGE_INTEGER fileId;
+ DWORD fileAttributes;
+ DWORD shortNameLength;
+ WCHAR shortName[MAX_PATH + 1]; //shortName is 0-terminated
+}; //no need for #pragma pack -> all members already starting at 4 byte boundary!
+
+class FileSearcher;
+typedef FileSearcher* FindHandle;
+
+DLL_FUNCTION_DECLARATION
+FindHandle openDir(const wchar_t* dirname); //returns NULL on error, call ::GetLastError()
+//note: do NOT place an asterisk at end, e.g. C:\SomeDir\*, as one would do for ::FindFirstFile()
+
+DLL_FUNCTION_DECLARATION
+bool readDir(FindHandle hnd, FileInformation& output); //returns false on error or if there are no more files; ::GetLastError() returns ERROR_NO_MORE_FILES
+
+DLL_FUNCTION_DECLARATION
+void closeDir(FindHandle hnd);
+
+/*----------
+ |typedefs|
+ ----------*/
+typedef FindHandle (*OpenDirFunc )(const wchar_t* dirname);
+typedef bool (*ReadDirFunc )(FindHandle hnd, FileInformation& dirInfo);
+typedef void (*CloseDirFunc)(FindHandle hnd);
+
+/*--------------
+ |symbol names|
+ --------------*/
+//const pointers ensure internal linkage
+const char openDirFuncName [] = "openDir";
+const char readDirFuncName [] = "readDir";
+const char closeDirFuncName[] = "closeDir";
+
+/*---------------
+ |library names|
+ ---------------*/
+inline const wchar_t* getDllName() { return zen::is64BitBuild ? L"FindFilePlus_x64.dll" : L"FindFilePlus_Win32.dll"; }
+}
+
+
+#endif //FIND_FIRST_FILE_PLUS_HEADER_087483434
diff --git a/lib/FindFilePlus/init_dll_binding.h b/lib/FindFilePlus/init_dll_binding.h
new file mode 100644
index 00000000..51b32c99
--- /dev/null
+++ b/lib/FindFilePlus/init_dll_binding.h
@@ -0,0 +1,16 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef INIT_DLL_BINDING_HEADER_ß018356031467832145
+#define INIT_DLL_BINDING_HEADER_ß018356031467832145
+
+namespace findplus
+{
+//load and check dll binding at startup
+bool initDllBinding(); //evaluate in ::DllMain() when attaching process
+}
+
+#endif //INIT_DLL_BINDING_HEADER_ß018356031467832145
diff --git a/lib/FindFilePlus/load_dll.cpp b/lib/FindFilePlus/load_dll.cpp
new file mode 100644
index 00000000..7166223b
--- /dev/null
+++ b/lib/FindFilePlus/load_dll.cpp
@@ -0,0 +1,23 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "load_dll.h"
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+void* /*FARPROC*/ dll::loadSymbol(const wchar_t* libraryName, const char* functionName)
+{
+ return ::GetProcAddress(::GetModuleHandle(libraryName), functionName);
+ //cleanup neither required nor allowed (::FreeLibrary())
+
+}
+//note: void* and FARPROC function pointer have same binary size on Windows
+
+
+void dll::setWin32Error(unsigned long lastError)
+{
+ ::SetLastError(lastError);
+}
diff --git a/lib/FindFilePlus/load_dll.h b/lib/FindFilePlus/load_dll.h
new file mode 100644
index 00000000..149b6efe
--- /dev/null
+++ b/lib/FindFilePlus/load_dll.h
@@ -0,0 +1,47 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef LOAD_DLL_HEADER_0312463214872163832174
+#define LOAD_DLL_HEADER_0312463214872163832174
+
+namespace dll
+{
+void setWin32Error(unsigned long lastError);
+
+//NOTE: uses ::GetModuleHandle => call for system DLLs only!
+template <class Func>
+class SysDllFun
+{
+public:
+ SysDllFun(const wchar_t* systemLibrary, const char* functionName) :
+ fun(reinterpret_cast<Func>(loadSymbol(systemLibrary, functionName))) {}
+
+ operator Func() const { return fun; }
+
+private:
+ Func fun;
+};
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+void* /*FARPROC*/ loadSymbol(const wchar_t* libraryName, const char* functionName);
+}
+
+#endif //LOAD_DLL_HEADER_0312463214872163832174
+
diff --git a/lib/FreeFileSync.ico b/lib/FreeFileSync.ico
new file mode 100644
index 00000000..b87789a7
--- /dev/null
+++ b/lib/FreeFileSync.ico
Binary files differ
diff --git a/lib/IFileOperation/FileOperation_Vista.vcxproj b/lib/IFileOperation/FileOperation_Vista.vcxproj
new file mode 100644
index 00000000..3d454a0b
--- /dev/null
+++ b/lib/IFileOperation/FileOperation_Vista.vcxproj
@@ -0,0 +1,240 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectName>Vista IFileOperation</ProjectName>
+ <ProjectGuid>{70394AEF-5897-4911-AFA1-82EAF0581EFA}</ProjectGuid>
+ <RootNamespace>ShadowDll</RootNamespace>
+ <Keyword>Win32Proj</Keyword>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <PlatformToolset>Windows7.1SDK</PlatformToolset>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ <PlatformToolset>Windows7.1SDK</PlatformToolset>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <PlatformToolset>Windows7.1SDK</PlatformToolset>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ <PlatformToolset>Windows7.1SDK</PlatformToolset>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup>
+ <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">OBJ\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">OBJ\$(ProjectName)_$(Configuration)_$(Platform)\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</LinkIncremental>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">OBJ\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">OBJ\$(ProjectName)_$(Configuration)_$(Platform)\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</LinkIncremental>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">.\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">OBJ\$(ProjectName)_$(Configuration)_$(Platform)\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">OBJ\$(ProjectName)_$(Configuration)_$(Platform)\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</LinkIncremental>
+ <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">FileOperation_$(Platform)</TargetName>
+ <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">FileOperation_$(Platform)</TargetName>
+ <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">FileOperation_$(Platform)</TargetName>
+ <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">FileOperation_$(Platform)</TargetName>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <BuildLog>
+ <Path>$(IntDir)Build.html</Path>
+ </BuildLog>
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;FILE_OP_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>true</MinimalRebuild>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <DebugInformationFormat>EditAndContinue</DebugInformationFormat>
+ <DisableSpecificWarnings>4100;4996</DisableSpecificWarnings>
+ <AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <ProfileGuidedDatabase>
+ </ProfileGuidedDatabase>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <BuildLog>
+ <Path>$(IntDir)Build.html</Path>
+ </BuildLog>
+ <Midl>
+ <TargetEnvironment>X64</TargetEnvironment>
+ </Midl>
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;FILE_OP_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>true</MinimalRebuild>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4100;4996</DisableSpecificWarnings>
+ <AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <ProfileGuidedDatabase>
+ </ProfileGuidedDatabase>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX64</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <BuildLog>
+ <Path>$(IntDir)Build.html</Path>
+ </BuildLog>
+ <ClCompile>
+ <Optimization>MaxSpeed</Optimization>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;FILE_OP_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
+ <DisableSpecificWarnings>4100;4996</DisableSpecificWarnings>
+ <AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <SubSystem>Windows</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
+ <ProfileGuidedDatabase>
+ </ProfileGuidedDatabase>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <BuildLog>
+ <Path>$(IntDir)Build.html</Path>
+ </BuildLog>
+ <Midl>
+ <TargetEnvironment>X64</TargetEnvironment>
+ </Midl>
+ <ClCompile>
+ <Optimization>MaxSpeed</Optimization>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;FILE_OP_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
+ <DisableSpecificWarnings>4100;4996</DisableSpecificWarnings>
+ <AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <SubSystem>Windows</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
+ <ProfileGuidedDatabase>
+ </ProfileGuidedDatabase>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX64</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="dll_main.cpp">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</CompileAsManaged>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ </PrecompiledHeader>
+ <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</CompileAsManaged>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</CompileAsManaged>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ </PrecompiledHeader>
+ <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</CompileAsManaged>
+ </ClCompile>
+ <ClCompile Include="file_op.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="file_op.h" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/lib/IFileOperation/dll_main.cpp b/lib/IFileOperation/dll_main.cpp
new file mode 100644
index 00000000..3805c99d
--- /dev/null
+++ b/lib/IFileOperation/dll_main.cpp
@@ -0,0 +1,25 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+//optional: add init/teardown logic here
+BOOL APIENTRY DllMain(HINSTANCE hinstDLL,
+ DWORD fdwReason,
+ LPVOID lpvReserved)
+{
+ switch (fdwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ case DLL_PROCESS_DETACH:
+ case DLL_THREAD_ATTACH:
+ case DLL_THREAD_DETACH:
+ break;
+ }
+ return TRUE;
+}
diff --git a/lib/IFileOperation/file_op.cpp b/lib/IFileOperation/file_op.cpp
new file mode 100644
index 00000000..10eac5de
--- /dev/null
+++ b/lib/IFileOperation/file_op.cpp
@@ -0,0 +1,240 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "file_op.h"
+#define WIN32_LEAN_AND_MEAN
+#include <zen/win.h>
+#include <zen/com_ptr.h>
+#include <zen/com_error.h>
+
+#include <Shellapi.h> // Included for shell constants such as FO_* values
+#include <shobjidl.h> // Required for necessary shell dependencies
+#include <algorithm>
+#include <string>
+
+using namespace zen;
+
+
+namespace fileop
+{
+inline
+void copyString(const std::wstring& input, wchar_t* buffer, size_t bufferSize)
+{
+ if (bufferSize > 0)
+ {
+ //size_t endPos = input.copy(buffer, bufferSize - 1);
+ //buffer[endPos] = 0;
+ const size_t maxSize = std::min(input.length(), bufferSize - 1);
+ std::copy(input.begin(), input.begin() + maxSize, buffer);
+ buffer[maxSize] = 0;
+ }
+}
+
+std::wstring lastErrorMessage; //this should really be thread-local!!!
+}
+
+
+bool fileop::moveToRecycleBin(const wchar_t* fileNames[],
+ size_t fileNo) //size of fileNames array
+{
+ HRESULT hr;
+
+ // Create the IFileOperation interface
+ ComPtr<IFileOperation> fileOp;
+ hr = ::CoCreateInstance(CLSID_FileOperation,
+ NULL,
+ CLSCTX_ALL,
+ IID_PPV_ARGS(fileOp.init()));
+ if (FAILED(hr))
+ {
+ lastErrorMessage = generateErrorMsg(L"Error calling \"CoCreateInstance\".", hr);
+ return false;
+ }
+
+ // Set the operation flags. Turn off all UI
+ // from being shown to the user during the
+ // operation. This includes error, confirmation
+ // and progress dialogs.
+ hr = fileOp->SetOperationFlags(FOF_ALLOWUNDO |
+ FOF_NOCONFIRMATION |
+ FOF_SILENT |
+ FOFX_EARLYFAILURE |
+ FOF_NOERRORUI);
+ if (FAILED(hr))
+ {
+ lastErrorMessage = generateErrorMsg(L"Error calling \"SetOperationFlags\".", hr);
+ return false;
+ }
+
+ int operationCount = 0;
+
+ for (size_t i = 0; i < fileNo; ++i)
+ {
+ //create file/folder item object
+ ComPtr<IShellItem> psiFile;
+ hr = ::SHCreateItemFromParsingName(fileNames[i],
+ NULL,
+ IID_PPV_ARGS(psiFile.init()));
+ if (FAILED(hr))
+ {
+ if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) || //file not existing anymore
+ hr == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND))
+ continue;
+
+ std::wstring message(L"Error calling \"SHCreateItemFromParsingName\" for file:\n");
+ message += std::wstring(L"\"") + fileNames[i] + L"\".";
+
+ lastErrorMessage = generateErrorMsg(message, hr);
+ return false;
+ }
+
+ hr = fileOp->DeleteItem(psiFile.get(), NULL);
+ if (FAILED(hr))
+ {
+ lastErrorMessage = generateErrorMsg(L"Error calling \"DeleteItem\".", hr);
+ return false;
+ }
+
+ ++operationCount;
+ }
+
+ if (operationCount == 0) //calling PerformOperations() without anything to do results in E_UNEXPECTED
+ return true;
+
+ //perform actual operations
+ hr = fileOp->PerformOperations();
+ if (FAILED(hr))
+ {
+ lastErrorMessage = generateErrorMsg(L"Error calling \"PerformOperations\".", hr);
+ return false;
+ }
+
+ //check if errors occured: if FOFX_EARLYFAILURE is not used, PerformOperations() can return with success despite errors!
+ BOOL pfAnyOperationsAborted = FALSE;
+ hr = fileOp->GetAnyOperationsAborted(&pfAnyOperationsAborted);
+ if (FAILED(hr))
+ {
+ lastErrorMessage = generateErrorMsg(L"Error calling \"GetAnyOperationsAborted\".", hr);
+ return false;
+ }
+
+ if (pfAnyOperationsAborted == TRUE)
+ {
+ lastErrorMessage = L"Operation did not complete successfully.";
+ return false;
+ }
+
+ return true;
+}
+
+
+bool fileop::copyFile(const wchar_t* sourceFile,
+ const wchar_t* targetFile)
+{
+ HRESULT hr;
+
+ // Create the IFileOperation interface
+ ComPtr<IFileOperation> fileOp;
+ hr = ::CoCreateInstance(CLSID_FileOperation,
+ NULL,
+ CLSCTX_ALL,
+ IID_PPV_ARGS(fileOp.init()));
+ if (FAILED(hr))
+ {
+ lastErrorMessage = generateErrorMsg(L"Error calling \"CoCreateInstance\".", hr);
+ return false;
+ }
+
+ // Set the operation flags. Turn off all UI
+ // from being shown to the user during the
+ // operation. This includes error, confirmation
+ // and progress dialogs.
+ hr = fileOp->SetOperationFlags(FOF_NOCONFIRMATION |
+ FOF_SILENT |
+ FOFX_EARLYFAILURE |
+ FOF_NOERRORUI);
+ if (FAILED(hr))
+ {
+ lastErrorMessage = generateErrorMsg(L"Error calling \"SetOperationFlags\".", hr);
+ return false;
+ }
+
+ //create source object
+ ComPtr<IShellItem> psiSourceFile;
+ hr = ::SHCreateItemFromParsingName(sourceFile,
+ NULL,
+ IID_PPV_ARGS(psiSourceFile.init()));
+ if (FAILED(hr))
+ {
+ std::wstring message(L"Error calling \"SHCreateItemFromParsingName\" for file:\n");
+ message += std::wstring(L"\"") + sourceFile + L"\".";
+ lastErrorMessage = generateErrorMsg(message, hr);
+ return false;
+ }
+
+ const size_t pos = std::wstring(targetFile).find_last_of(L'\\');
+ if (pos == std::wstring::npos)
+ {
+ lastErrorMessage = L"Target filename does not contain a path separator.";
+ return false;
+ }
+
+ const std::wstring targetFolder(targetFile, pos);
+ const std::wstring targetFileNameShort = targetFile + pos + 1;
+
+ //create target folder object
+ ComPtr<IShellItem> psiTargetFolder;
+ hr = ::SHCreateItemFromParsingName(targetFolder.c_str(),
+ NULL,
+ IID_PPV_ARGS(psiTargetFolder.init()));
+ if (FAILED(hr))
+ {
+ std::wstring message(L"Error calling \"SHCreateItemFromParsingName\" for folder:\n");
+ message += std::wstring(L"\"") + targetFolder + L"\".";
+ lastErrorMessage = generateErrorMsg(message, hr);
+ return false;
+ }
+
+ //schedule file copy operation
+ hr = fileOp->CopyItem(psiSourceFile.get(), psiTargetFolder.get(), targetFileNameShort.c_str(), NULL);
+ if (FAILED(hr))
+ {
+ lastErrorMessage = generateErrorMsg(L"Error calling \"CopyItem\".", hr);
+ return false;
+ }
+
+ //perform actual operations
+ hr = fileOp->PerformOperations();
+ if (FAILED(hr))
+ {
+ lastErrorMessage = generateErrorMsg(L"Error calling \"PerformOperations\".", hr);
+ return false;
+ }
+
+ //check if errors occured: if FOFX_EARLYFAILURE is not used, PerformOperations() can return with success despite errors!
+ BOOL pfAnyOperationsAborted = FALSE;
+ hr = fileOp->GetAnyOperationsAborted(&pfAnyOperationsAborted);
+ if (FAILED(hr))
+ {
+ lastErrorMessage = generateErrorMsg(L"Error calling \"GetAnyOperationsAborted\".", hr);
+ return false;
+ }
+
+ if (pfAnyOperationsAborted == TRUE)
+ {
+ lastErrorMessage = L"Operation did not complete successfully.";
+ return false;
+ }
+
+ return true;
+}
+
+
+//if any of the functions above returns 'false', this message returns last error
+void fileop::getLastError(wchar_t* errorMessage, size_t errorBufferLen)
+{
+ copyString(lastErrorMessage, errorMessage, errorBufferLen);
+}
diff --git a/lib/IFileOperation/file_op.h b/lib/IFileOperation/file_op.h
new file mode 100644
index 00000000..c33993ad
--- /dev/null
+++ b/lib/IFileOperation/file_op.h
@@ -0,0 +1,62 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef RECYCLER_DLL_H
+#define RECYCLER_DLL_H
+
+#ifdef FILE_OP_DLL_EXPORTS
+#define FILE_OP_DLL_API extern "C" __declspec(dllexport)
+#else
+#define FILE_OP_DLL_API extern "C" __declspec(dllimport)
+#endif
+
+#include <zen/build_info.h>
+
+
+namespace fileop
+{
+/*--------------
+ |declarations|
+ --------------*/
+
+//COM needs to be initialized before calling any of these functions! CoInitializeEx/CoUninitialize
+
+FILE_OP_DLL_API
+bool moveToRecycleBin(const wchar_t* fileNames[],
+ size_t fileNo); //size of fileNames array
+
+FILE_OP_DLL_API
+bool copyFile(const wchar_t* sourceFile,
+ const wchar_t* targetFile);
+
+//if any of the functions above returns 'false', this message returns last error
+FILE_OP_DLL_API
+void getLastError(wchar_t* errorMessage, size_t errorBufferLen);
+
+/*----------
+ |typedefs|
+ ----------*/
+typedef bool (*MoveToRecycleBinFct)(const wchar_t* fileNames[], size_t fileNo);
+typedef bool (*CopyFileFct)(const wchar_t* sourceFile, const wchar_t* targetFile);
+typedef void (*GetLastErrorFct)(wchar_t* errorMessage, size_t errorBufferLen);
+
+/*--------------
+ |symbol names|
+ --------------*/
+//(use const pointers to ensure internal linkage)
+const char moveToRecycleBinFctName[] = "moveToRecycleBin";
+const char copyFileFctName[] = "copyFile";
+const char getLastErrorFctName[] = "getLastError";
+
+/*---------------
+ |library names|
+ ---------------*/
+inline const wchar_t* getDllName() { return zen::is64BitBuild ? L"FileOperation_x64.dll" : L"FileOperation_Win32.dll"; }
+}
+
+
+
+#endif //RECYCLER_DLL_H
diff --git a/lib/ShadowCopy/LockFile.cpp b/lib/ShadowCopy/LockFile.cpp
new file mode 100644
index 00000000..7df3ec66
--- /dev/null
+++ b/lib/ShadowCopy/LockFile.cpp
@@ -0,0 +1,44 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+
+#include <string>
+#include <iostream>
+#define WIN32_LEAN_AND_MEAN
+#include "windows.h"
+
+int wmain(int argc, wchar_t* argv[])
+{
+ if (argc <= 1)
+ {
+ std::wcout << "Please enter the filename to be locked as %1 parameter!" << "\n\n";
+ system("pause");
+ return -1;
+ }
+ std::wstring filename = argv[1];
+
+ //obtain exclusive lock on test file
+ HANDLE hFile = ::CreateFile(filename.c_str(),
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL,
+ OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS,
+ NULL);
+ if (hFile == INVALID_HANDLE_VALUE)
+ {
+ std::wcout << "Error obtaining exclusive lock on test file: " << filename << "\n\n";
+ system("pause");
+ return -1;
+ }
+
+ std::wcout << "File " << filename << " is locked! Press a key to unlock." << "\n\n";
+ system("pause");
+
+ ::CloseHandle(hFile);
+ return 0;
+}
+
diff --git a/lib/ShadowCopy/Shadow_2003.vcxproj b/lib/ShadowCopy/Shadow_2003.vcxproj
new file mode 100644
index 00000000..a893a389
--- /dev/null
+++ b/lib/ShadowCopy/Shadow_2003.vcxproj
@@ -0,0 +1,244 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectName>Server2003</ProjectName>
+ <ProjectGuid>{2F2994D6-FB89-4BAA-A5DF-03BAF7337FF2}</ProjectGuid>
+ <RootNamespace>ShadowDll</RootNamespace>
+ <Keyword>Win32Proj</Keyword>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <UseOfAtl>false</UseOfAtl>
+ <PlatformToolset>Windows7.1SDK</PlatformToolset>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ <PlatformToolset>Windows7.1SDK</PlatformToolset>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <PlatformToolset>Windows7.1SDK</PlatformToolset>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ <PlatformToolset>Windows7.1SDK</PlatformToolset>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup>
+ <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">OBJ\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">OBJ\$(ProjectName)_$(Configuration)_$(Platform)\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</LinkIncremental>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">OBJ\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">OBJ\$(ProjectName)_$(Configuration)_$(Platform)\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</LinkIncremental>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">.\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">OBJ\$(ProjectName)_$(Configuration)_$(Platform)\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">OBJ\$(ProjectName)_$(Configuration)_$(Platform)\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</LinkIncremental>
+ <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Shadow_$(ProjectName)_$(Platform)</TargetName>
+ <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Shadow_$(ProjectName)_$(Platform)</TargetName>
+ <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Shadow_$(ProjectName)_$(Platform)</TargetName>
+ <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Shadow_$(ProjectName)_$(Platform)</TargetName>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <BuildLog>
+ <Path>$(IntDir)Build.html</Path>
+ </BuildLog>
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;SHADOWDLL_EXPORTS;USE_SHADOW_2003;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>true</MinimalRebuild>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <DebugInformationFormat>EditAndContinue</DebugInformationFormat>
+ <DisableSpecificWarnings>4100;4996</DisableSpecificWarnings>
+ <AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <ProfileGuidedDatabase>
+ </ProfileGuidedDatabase>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <BuildLog>
+ <Path>$(IntDir)Build.html</Path>
+ </BuildLog>
+ <Midl>
+ <TargetEnvironment>X64</TargetEnvironment>
+ </Midl>
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;SHADOWDLL_EXPORTS;USE_SHADOW_2003;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>true</MinimalRebuild>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4100;4996</DisableSpecificWarnings>
+ <AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <ProfileGuidedDatabase>
+ </ProfileGuidedDatabase>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX64</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <BuildLog>
+ <Path>$(IntDir)Build.html</Path>
+ </BuildLog>
+ <ClCompile>
+ <Optimization>MaxSpeed</Optimization>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;SHADOWDLL_EXPORTS;USE_SHADOW_2003;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
+ <DisableSpecificWarnings>4100;4996</DisableSpecificWarnings>
+ <AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <SubSystem>Windows</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
+ <ProfileGuidedDatabase>
+ </ProfileGuidedDatabase>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <BuildLog>
+ <Path>$(IntDir)Build.html</Path>
+ </BuildLog>
+ <Midl>
+ <TargetEnvironment>X64</TargetEnvironment>
+ </Midl>
+ <ClCompile>
+ <Optimization>MaxSpeed</Optimization>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;SHADOWDLL_EXPORTS;USE_SHADOW_2003;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
+ <DisableSpecificWarnings>4100;4996</DisableSpecificWarnings>
+ <AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <SubSystem>Windows</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
+ <ProfileGuidedDatabase>
+ </ProfileGuidedDatabase>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX64</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="dll_main.cpp">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</CompileAsManaged>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ </PrecompiledHeader>
+ <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</CompileAsManaged>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</CompileAsManaged>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ </PrecompiledHeader>
+ <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</CompileAsManaged>
+ </ClCompile>
+ <ClCompile Include="shadow.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="shadow.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <Library Include="Server 2003\lib\$(Platform)\vssapi.lib" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/lib/ShadowCopy/Shadow_XP.vcxproj b/lib/ShadowCopy/Shadow_XP.vcxproj
new file mode 100644
index 00000000..e49e8941
--- /dev/null
+++ b/lib/ShadowCopy/Shadow_XP.vcxproj
@@ -0,0 +1,245 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectName>XP</ProjectName>
+ <ProjectGuid>{70394AEF-5897-4911-AFA1-82EAF0581EFA}</ProjectGuid>
+ <RootNamespace>ShadowDll</RootNamespace>
+ <Keyword>Win32Proj</Keyword>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <PlatformToolset>Windows7.1SDK</PlatformToolset>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ <PlatformToolset>Windows7.1SDK</PlatformToolset>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <PlatformToolset>Windows7.1SDK</PlatformToolset>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ <PlatformToolset>Windows7.1SDK</PlatformToolset>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup>
+ <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">OBJ\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">OBJ\$(ProjectName)_$(Configuration)_$(Platform)\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</LinkIncremental>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">OBJ\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">OBJ\$(ProjectName)_$(Configuration)_$(Platform)\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</LinkIncremental>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">.\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">OBJ\$(ProjectName)_$(Configuration)_$(Platform)\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">OBJ\$(ProjectName)_$(Configuration)_$(Platform)\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</LinkIncremental>
+ <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Shadow_$(ProjectName)_$(Platform)</TargetName>
+ <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Shadow_$(ProjectName)_$(Platform)</TargetName>
+ <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Shadow_$(ProjectName)_$(Platform)</TargetName>
+ <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Shadow_$(ProjectName)_$(Platform)</TargetName>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <BuildLog>
+ <Path>$(IntDir)Build.html</Path>
+ </BuildLog>
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;SHADOWDLL_EXPORTS;USE_SHADOW_XP;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>true</MinimalRebuild>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <DebugInformationFormat>EditAndContinue</DebugInformationFormat>
+ <FavorSizeOrSpeed>Neither</FavorSizeOrSpeed>
+ <DisableSpecificWarnings>4100;4996</DisableSpecificWarnings>
+ <AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <ProfileGuidedDatabase>
+ </ProfileGuidedDatabase>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <BuildLog>
+ <Path>$(IntDir)Build.html</Path>
+ </BuildLog>
+ <Midl>
+ <TargetEnvironment>X64</TargetEnvironment>
+ </Midl>
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;SHADOWDLL_EXPORTS;USE_SHADOW_XP;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>true</MinimalRebuild>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <FavorSizeOrSpeed>Neither</FavorSizeOrSpeed>
+ <DisableSpecificWarnings>4100;4996</DisableSpecificWarnings>
+ <AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <ProfileGuidedDatabase>
+ </ProfileGuidedDatabase>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX64</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <BuildLog>
+ <Path>$(IntDir)Build.html</Path>
+ </BuildLog>
+ <ClCompile>
+ <Optimization>MaxSpeed</Optimization>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;SHADOWDLL_EXPORTS;USE_SHADOW_XP;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
+ <DisableSpecificWarnings>4100;4996</DisableSpecificWarnings>
+ <AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <SubSystem>Windows</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
+ <ProfileGuidedDatabase>
+ </ProfileGuidedDatabase>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <BuildLog>
+ <Path>$(IntDir)Build.html</Path>
+ </BuildLog>
+ <Midl>
+ <TargetEnvironment>X64</TargetEnvironment>
+ </Midl>
+ <ClCompile>
+ <Optimization>MaxSpeed</Optimization>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;SHADOWDLL_EXPORTS;USE_SHADOW_XP;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
+ <DisableSpecificWarnings>4100;4996</DisableSpecificWarnings>
+ <AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <SubSystem>Windows</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
+ <ProfileGuidedDatabase>
+ </ProfileGuidedDatabase>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX64</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="dll_main.cpp">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</CompileAsManaged>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ </PrecompiledHeader>
+ <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</CompileAsManaged>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</CompileAsManaged>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ </PrecompiledHeader>
+ <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</CompileAsManaged>
+ </ClCompile>
+ <ClCompile Include="shadow.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="shadow.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <Library Include="XP\lib\$(Platform)\vssapi.lib" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/lib/ShadowCopy/dll_main.cpp b/lib/ShadowCopy/dll_main.cpp
new file mode 100644
index 00000000..3805c99d
--- /dev/null
+++ b/lib/ShadowCopy/dll_main.cpp
@@ -0,0 +1,25 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+//optional: add init/teardown logic here
+BOOL APIENTRY DllMain(HINSTANCE hinstDLL,
+ DWORD fdwReason,
+ LPVOID lpvReserved)
+{
+ switch (fdwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ case DLL_PROCESS_DETACH:
+ case DLL_THREAD_ATTACH:
+ case DLL_THREAD_DETACH:
+ break;
+ }
+ return TRUE;
+}
diff --git a/lib/ShadowCopy/shadow.cpp b/lib/ShadowCopy/shadow.cpp
new file mode 100644
index 00000000..12e9fa60
--- /dev/null
+++ b/lib/ShadowCopy/shadow.cpp
@@ -0,0 +1,187 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "shadow.h"
+#include <algorithm>
+#include <string>
+#include <comdef.h>
+#include <zen/com_ptr.h>
+#include <zen/com_error.h>
+
+#define WIN32_LEAN_AND_MEAN
+#include "windows.h"
+
+#ifdef USE_SHADOW_XP
+#include "xp/inc/vss.h"
+#include "xp/inc/vswriter.h"
+#include "xp/inc/vsbackup.h"
+
+#elif defined USE_SHADOW_2003
+#include "Server 2003/inc/vss.h"
+#include "Server 2003/inc/vswriter.h"
+#include "Server 2003/inc/vsbackup.h"
+#else
+adapt!
+#endif
+
+using namespace zen;
+
+
+namespace
+{
+inline
+void copyString(const std::wstring& input, wchar_t* buffer, size_t bufferSize)
+{
+ if (bufferSize > 0)
+ {
+ //size_t endPos = input.copy(buffer, bufferSize - 1);
+ //buffer[endPos] = 0;
+ const size_t maxSize = std::min(input.length(), bufferSize - 1);
+ std::copy(input.begin(), input.begin() + maxSize, buffer);
+ buffer[maxSize] = 0;
+ }
+}
+
+inline
+void writeErrorMsg(const wchar_t* input, HRESULT hr, wchar_t* output, unsigned int outputLen)
+{
+ copyString(generateErrorMsg(input, hr), output, outputLen);
+}
+}
+
+
+bool shadow::createShadowCopy(const wchar_t* volumeName,
+ wchar_t* shadowVolName,
+ unsigned int shadowBufferLen,
+ ShadowHandle* handle,
+ wchar_t* errorMessage,
+ unsigned int errorBufferLen)
+{
+ //MessageBox(0, L"backup err", L"", 0); */
+ *handle = 0;
+ HRESULT hr = NULL;
+
+ ComPtr<IVssBackupComponents> backupComp;
+ if (FAILED(hr = CreateVssBackupComponents(backupComp.init())))
+ {
+ if (hr == E_ACCESSDENIED)
+ writeErrorMsg(L"The caller does not have sufficient backup privileges or is not an administrator.", hr, errorMessage, errorBufferLen);
+ else
+ writeErrorMsg(L"Error calling \"CreateVssBackupComponents\".", hr, errorMessage, errorBufferLen);
+ return false;
+ }
+
+ if (FAILED(hr = backupComp->InitializeForBackup()))
+ {
+ writeErrorMsg(L"Error calling \"InitializeForBackup\".", hr, errorMessage, errorBufferLen);
+ return false;
+ }
+
+ if (FAILED(hr = backupComp->SetBackupState(false, false, VSS_BT_FULL)))
+ {
+ writeErrorMsg(L"Error calling \"SetBackupState\".", hr, errorMessage, errorBufferLen);
+ return false;
+ }
+
+ ComPtr<IVssAsync> vssWriters;
+ if (FAILED(hr = backupComp->GatherWriterMetadata(vssWriters.init())))
+ {
+ //this can happen if XP-version of VSS is used on Windows Vista (which needs at least VSS-Server2003 build)
+ writeErrorMsg(L"Error calling \"GatherWriterMetadata\".", hr, errorMessage, errorBufferLen);
+ return false;
+ }
+
+ //wait for shadow copy writers to complete
+ if (FAILED(hr = vssWriters->Wait()))
+ {
+ writeErrorMsg(L"Error calling \"vssWriters->Wait\".", hr, errorMessage, errorBufferLen);
+ return false;
+ }
+
+ vssWriters->QueryStatus(&hr, NULL); //check if the async operation succeeded...
+ if (FAILED(hr))
+ {
+ writeErrorMsg(L"Error calling \"vssWriters->QueryStatus\".", hr, errorMessage, errorBufferLen);
+ return false;
+ }
+
+ VSS_ID snapshotSetId = {0};
+ if (FAILED(hr = backupComp->StartSnapshotSet(&snapshotSetId)))
+ {
+ writeErrorMsg(L"Error calling \"StartSnapshotSet\".", hr, errorMessage, errorBufferLen);
+ return false;
+ }
+
+ VSS_ID SnapShotId = {0};
+ if (FAILED(hr = backupComp->AddToSnapshotSet(const_cast<wchar_t*>(volumeName), GUID_NULL, &SnapShotId)))
+ {
+ writeErrorMsg(L"Error calling \"AddToSnapshotSet\".", hr, errorMessage, errorBufferLen);
+ return false;
+ }
+
+ ComPtr<IVssAsync> vssPrepare;
+ if (FAILED(hr = backupComp->PrepareForBackup(vssPrepare.init())))
+ {
+ writeErrorMsg(L"Error calling \"PrepareForBackup\".", hr, errorMessage, errorBufferLen);
+ return false;
+ }
+
+ if (FAILED(hr = vssPrepare->Wait()))
+ {
+ writeErrorMsg(L"Error calling \"vssPrepare->Wait\".", hr, errorMessage, errorBufferLen);
+ return false;
+ }
+
+ vssPrepare->QueryStatus(&hr, NULL); //check if the async operation succeeded...
+ if (FAILED(hr))
+ {
+ writeErrorMsg(L"Error calling \"vssPrepare->QueryStatus\".", hr, errorMessage, errorBufferLen);
+ return false;
+ }
+
+ ComPtr<IVssAsync> vssDoShadowCopy;
+ if (FAILED(hr = backupComp->DoSnapshotSet(vssDoShadowCopy.init())))
+ {
+ writeErrorMsg(L"Error calling \"DoSnapshotSet\".", hr, errorMessage, errorBufferLen);
+ return false;
+ }
+
+ if (FAILED(hr = vssDoShadowCopy->Wait()))
+ {
+ writeErrorMsg(L"Error calling \"vssDoShadowCopy->Wait\".", hr, errorMessage, errorBufferLen);
+ return false;
+ }
+
+ vssDoShadowCopy->QueryStatus(&hr, NULL); //check if the async operation succeeded...
+ if (FAILED(hr))
+ {
+ writeErrorMsg(L"Error calling \"vssDoShadowCopy->QueryStatus\".", hr, errorMessage, errorBufferLen);
+ return false;
+ }
+
+ VSS_SNAPSHOT_PROP props;
+ if (FAILED(hr = backupComp->GetSnapshotProperties(SnapShotId, &props)))
+ {
+ writeErrorMsg(L"Error calling \"GetSnapshotProperties\".", hr, errorMessage, errorBufferLen);
+ return false;
+ }
+
+ //finally: write volume name of newly created shadow copy
+ copyString(props.m_pwszSnapshotDeviceObject, shadowVolName, shadowBufferLen);
+
+ VssFreeSnapshotProperties(&props);
+
+ *handle = backupComp.release();
+
+ return true;
+}
+
+
+void shadow::releaseShadowCopy(ShadowHandle handle)
+{
+ if (handle)
+ handle->Release();
+}
diff --git a/lib/ShadowCopy/shadow.h b/lib/ShadowCopy/shadow.h
new file mode 100644
index 00000000..0dfd39a9
--- /dev/null
+++ b/lib/ShadowCopy/shadow.h
@@ -0,0 +1,93 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef SHADOWCOPY_H
+#define SHADOWCOPY_H
+
+#ifdef SHADOWDLL_EXPORTS
+#define SHADOWDLL_API extern "C" __declspec(dllexport)
+#else
+#define SHADOWDLL_API extern "C" __declspec(dllimport)
+#endif
+
+#include <zen/build_info.h>
+#include <zen/win_ver.h>
+
+
+class IVssBackupComponents;
+
+namespace shadow
+{
+/*--------------
+ |declarations|
+ --------------*/
+
+//COM needs to be initialized before calling any of these functions! CoInitializeEx/CoUninitialize
+
+typedef IVssBackupComponents* ShadowHandle;
+
+//volumeName must end with "\", while shadowVolName does not end with "\"
+SHADOWDLL_API
+bool createShadowCopy(const wchar_t* volumeName, // in
+ wchar_t* shadowVolName, // out
+ unsigned int shadowBufferLen, // in
+ ShadowHandle* handle, // out
+ wchar_t* errorMessage, // out
+ unsigned int errorBufferLen); // in
+
+
+//don't forget to release the backupHandle after shadow copy is not needed anymore!
+SHADOWDLL_API
+void releaseShadowCopy(ShadowHandle handle);
+
+
+//##########################################################################################
+
+
+/*----------
+ |typedefs|
+ ----------*/
+typedef bool (*CreateShadowCopyFct)(const wchar_t* volumeName,
+ wchar_t* shadowVolName,
+ unsigned int shadowBufferLen,
+ ShadowHandle* handle,
+ wchar_t* errorMessage,
+ unsigned int errorBufferLen);
+
+typedef void (*ReleaseShadowCopyFct)(ShadowHandle handle);
+
+/*--------------
+ |symbol names|
+ --------------*/
+//(use const pointers to ensure internal linkage)
+const char createShadowCopyFctName[] = "createShadowCopy";
+const char releaseShadowCopyFctName[] = "releaseShadowCopy";
+
+/*---------------
+ |library names|
+ ---------------*/
+
+inline
+const wchar_t* getDllName()
+{
+ /*
+ distinguish a bunch of VSS builds: we use XP and Server 2003 implementations...
+ VSS version and compatibility overview: http://msdn.microsoft.com/en-us/library/aa384627(VS.85).aspx
+ */
+ return zen::winServer2003orLater() ?
+ (zen::is64BitBuild ?
+ L"Shadow_Server2003_x64.dll" :
+ L"Shadow_Server2003_Win32.dll") :
+
+ (zen::is64BitBuild ?
+ L"Shadow_XP_x64.dll" :
+ L"Shadow_XP_Win32.dll");
+}
+}
+
+
+
+#endif //SHADOWCOPY_H
diff --git a/lib/SyncDB.ico b/lib/SyncDB.ico
new file mode 100644
index 00000000..eee91c14
--- /dev/null
+++ b/lib/SyncDB.ico
Binary files differ
diff --git a/lib/Thumbnail/Thumbnail.vcxproj b/lib/Thumbnail/Thumbnail.vcxproj
new file mode 100644
index 00000000..e3909ff8
--- /dev/null
+++ b/lib/Thumbnail/Thumbnail.vcxproj
@@ -0,0 +1,239 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{70394AEF-5897-4911-AFA1-82EAF0581EFA}</ProjectGuid>
+ <RootNamespace>ShadowDll</RootNamespace>
+ <Keyword>Win32Proj</Keyword>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <PlatformToolset>Windows7.1SDK</PlatformToolset>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ <PlatformToolset>Windows7.1SDK</PlatformToolset>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <PlatformToolset>Windows7.1SDK</PlatformToolset>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ <PlatformToolset>Windows7.1SDK</PlatformToolset>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup>
+ <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">OBJ\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">OBJ\$(ProjectName)_$(Configuration)_$(Platform)\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</LinkIncremental>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">OBJ\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">OBJ\$(ProjectName)_$(Configuration)_$(Platform)\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</LinkIncremental>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">.\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">OBJ\$(ProjectName)_$(Configuration)_$(Platform)\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">OBJ\$(ProjectName)_$(Configuration)_$(Platform)\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</LinkIncremental>
+ <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Thumbnail_$(Platform)</TargetName>
+ <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Thumbnail_$(Platform)</TargetName>
+ <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Thumbnail_$(Platform)</TargetName>
+ <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Thumbnail_$(Platform)</TargetName>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <BuildLog>
+ <Path>$(IntDir)Build.html</Path>
+ </BuildLog>
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;THUMBNAIL_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>true</MinimalRebuild>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <DebugInformationFormat>EditAndContinue</DebugInformationFormat>
+ <DisableSpecificWarnings>4100</DisableSpecificWarnings>
+ <AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <ProfileGuidedDatabase>
+ </ProfileGuidedDatabase>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <BuildLog>
+ <Path>$(IntDir)Build.html</Path>
+ </BuildLog>
+ <Midl>
+ <TargetEnvironment>X64</TargetEnvironment>
+ </Midl>
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;THUMBNAIL_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>true</MinimalRebuild>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4100</DisableSpecificWarnings>
+ <AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <ProfileGuidedDatabase>
+ </ProfileGuidedDatabase>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX64</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <BuildLog>
+ <Path>$(IntDir)Build.html</Path>
+ </BuildLog>
+ <ClCompile>
+ <Optimization>MaxSpeed</Optimization>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;THUMBNAIL_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4100</DisableSpecificWarnings>
+ <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <SubSystem>Windows</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
+ <ProfileGuidedDatabase>
+ </ProfileGuidedDatabase>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <BuildLog>
+ <Path>$(IntDir)Build.html</Path>
+ </BuildLog>
+ <Midl>
+ <TargetEnvironment>X64</TargetEnvironment>
+ </Midl>
+ <ClCompile>
+ <Optimization>MaxSpeed</Optimization>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;THUMBNAIL_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4100</DisableSpecificWarnings>
+ <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <SubSystem>Windows</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
+ <ProfileGuidedDatabase>
+ </ProfileGuidedDatabase>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX64</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="dll_main.cpp">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</CompileAsManaged>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ </PrecompiledHeader>
+ <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</CompileAsManaged>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</CompileAsManaged>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ </PrecompiledHeader>
+ <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</CompileAsManaged>
+ </ClCompile>
+ <ClCompile Include="thumbnail.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="thumbnail.h" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/lib/Thumbnail/dll_main.cpp b/lib/Thumbnail/dll_main.cpp
new file mode 100644
index 00000000..3805c99d
--- /dev/null
+++ b/lib/Thumbnail/dll_main.cpp
@@ -0,0 +1,25 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+//optional: add init/teardown logic here
+BOOL APIENTRY DllMain(HINSTANCE hinstDLL,
+ DWORD fdwReason,
+ LPVOID lpvReserved)
+{
+ switch (fdwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ case DLL_PROCESS_DETACH:
+ case DLL_THREAD_ATTACH:
+ case DLL_THREAD_DETACH:
+ break;
+ }
+ return TRUE;
+}
diff --git a/lib/Thumbnail/thumbnail.cpp b/lib/Thumbnail/thumbnail.cpp
new file mode 100644
index 00000000..b8d00c38
--- /dev/null
+++ b/lib/Thumbnail/thumbnail.cpp
@@ -0,0 +1,167 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "thumbnail.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include "windows.h"
+
+#define STRICT_TYPED_ITEMIDS //better type safety for IDLists
+#include <Shlobj.h>
+
+#include <Shellapi.h>
+#include <CommonControls.h>
+#include <zen/com_ptr.h>
+#include <zen/string_tools.h>
+#include <string>
+#include <zen/scope_guard.h>
+
+using namespace zen;
+
+
+thumb::HICON thumb::getThumbnail(const wchar_t* filename, int requestedSize) //return 0 on failure, caller takes ownership!
+{
+ const std::wstring filenameStr(filename);
+
+ ComPtr<IShellFolder> shellFolder;
+ {
+ HRESULT hr = ::SHGetDesktopFolder(shellFolder.init());
+ if (FAILED(hr) || !shellFolder)
+ return NULL;
+ }
+
+ PIDLIST_RELATIVE pidlFolder = NULL;
+ {
+ const std::wstring& pathName = beforeLast(filenameStr, '\\');
+ HRESULT hr = shellFolder->ParseDisplayName(NULL, // [in] HWND hwnd,
+ NULL, // [in] IBindCtx *pbc,
+ const_cast<LPWSTR>(pathName.c_str()), // [in] LPWSTR pszDisplayName,
+ NULL, // [out] ULONG *pchEaten,
+ &pidlFolder, // [out] PIDLIST_RELATIVE* ppidl,
+ NULL); // [in, out] ULONG *pdwAttributes
+ if (FAILED(hr) || !pidlFolder)
+ return NULL;
+ }
+ ZEN_ON_BLOCK_EXIT(::ILFree(pidlFolder)); //older version: ::CoTaskMemFree
+
+ ComPtr<IShellFolder> imageFolder;
+ {
+ HRESULT hr = shellFolder->BindToObject(pidlFolder, // [in] PCUIDLIST_RELATIVE pidl,
+ NULL,
+ IID_PPV_ARGS(imageFolder.init()));
+ if (FAILED(hr) || !imageFolder)
+ return NULL;
+ }
+
+ PIDLIST_RELATIVE pidImage = NULL;
+ {
+ const std::wstring& shortName = afterLast(filenameStr, '\\');
+ HRESULT hr = imageFolder->ParseDisplayName(NULL, // [in] HWND hwnd,
+ NULL, // [in] IBindCtx *pbc,
+ const_cast<LPWSTR>(shortName.c_str()), // [in] LPWSTR pszDisplayName,
+ NULL, // [out] ULONG *pchEaten,
+ &pidImage, // [out] PIDLIST_RELATIVE *ppidl,
+ NULL); // [in, out] ULONG *pdwAttributes
+ if (FAILED(hr) || !pidImage)
+ return NULL;
+ }
+ ZEN_ON_BLOCK_EXIT(::ILFree(pidImage)); //older version: ::CoTaskMemFree
+
+ ComPtr<IExtractImage> extractImage;
+ {
+ PCUITEMID_CHILD_ARRAY pidlIn = reinterpret_cast<PCUITEMID_CHILD_ARRAY>(&pidImage);
+ //this is where STRICT_TYPED_ITEMIDS gets us ;)
+
+ HRESULT hr = imageFolder->GetUIObjectOf(NULL, // [in] HWND hwndOwner,
+ 1, // [in] UINT cidl,
+ pidlIn, // [in] PCUITEMID_CHILD_ARRAY apidl,
+ IID_IExtractImage, // [in] REFIID riid,
+ NULL, // [in, out] UINT *rgfReserved,
+ reinterpret_cast<void**>(extractImage.init())); // [out] void **ppv
+ if (FAILED(hr) || !extractImage)
+ return NULL;
+ }
+
+ {
+ wchar_t pathBuffer[MAX_PATH];
+ DWORD priority = 0;
+ const SIZE prgSize = { requestedSize, requestedSize }; //preferred size only!
+ DWORD clrDepth = 32;
+ DWORD flags = IEIFLAG_SCREEN | IEIFLAG_OFFLINE;
+
+ HRESULT hr = extractImage->GetLocation(pathBuffer, // [out] LPWSTR pszPathBuffer,
+ MAX_PATH, // [in] DWORD cchMax,
+ &priority, // [out] DWORD *pdwPriority,
+ &prgSize, // [in] const SIZE *prgSize,
+ clrDepth, // [in] DWORD dwRecClrDepth,
+ &flags); // [in, out] DWORD *pdwFlags
+ if (FAILED(hr))
+ return NULL;
+ }
+
+ HBITMAP bitmap = NULL;
+ {
+ HRESULT hr = extractImage->Extract(&bitmap);
+ if (FAILED(hr) || !bitmap)
+ return NULL;
+ }
+ ZEN_ON_BLOCK_EXIT(::DeleteObject(bitmap));
+
+
+ BITMAP bmpInfo = {};
+ if (::GetObject(bitmap, //__in HGDIOBJ hgdiobj,
+ sizeof(bmpInfo), //__in int cbBuffer,
+ &bmpInfo) == 0) //__out LPVOID lpvObject
+ return NULL;
+
+ HBITMAP bitmapMask = ::CreateCompatibleBitmap(::GetDC(NULL), bmpInfo.bmWidth, bmpInfo.bmHeight);
+ if (bitmapMask == 0)
+ return NULL;
+ ZEN_ON_BLOCK_EXIT(::DeleteObject(bitmapMask));
+
+ ICONINFO iconInfo = {};
+ iconInfo.fIcon = true;
+ iconInfo.hbmColor = bitmap;
+ iconInfo.hbmMask = bitmapMask;
+
+ return ::CreateIconIndirect(&iconInfo);
+}
+
+
+thumb::HICON thumb::getIconByIndex(int iconIndex, int shilIconType) //return 0 on failure, caller takes ownership!
+{
+ //Note: using IExtractIcon::Extract is *no* alternative, just as ::SHGetFileInfo(), it only supports small (16x16) and large (32x32) icons
+
+ ComPtr<IImageList> imageList; //perf: 0,12 µs only to get the image list
+ {
+ HRESULT hr = ::SHGetImageList(shilIconType, //__in int iImageList,
+ IID_PPV_ARGS(imageList.init()));
+ if (FAILED(hr) || !imageList)
+ return NULL;
+ }
+
+ bool hasAlpha = false; //perf: 0,14 µs
+ {
+ DWORD flags = 0;
+ HRESULT hr = imageList->GetItemFlags(iconIndex, //[in] int i,
+ &flags); //[out] DWORD *dwFlags
+ if (SUCCEEDED(hr))
+ hasAlpha = flags & ILIF_ALPHA;
+ }
+
+ ::HICON hIcon = NULL; //perf: 1,5 ms - the dominant block
+ {
+ HRESULT hr = imageList->GetIcon(iconIndex, // [in] int i,
+ hasAlpha ? ILD_IMAGE : ILD_NORMAL, // [in] UINT flags,
+ //ILD_IMAGE -> do not draw mask - fixes glitch with ILD_NORMAL where both mask *and* alpha channel are applied, e.g. for ffs_batch and folder icon
+ //other flags: http://msdn.microsoft.com/en-us/library/windows/desktop/bb775230(v=vs.85).aspx
+ &hIcon); // [out] HICON *picon
+ if (FAILED(hr) || !hIcon)
+ return NULL;
+ }
+
+ return hIcon;
+} \ No newline at end of file
diff --git a/lib/Thumbnail/thumbnail.h b/lib/Thumbnail/thumbnail.h
new file mode 100644
index 00000000..3feec275
--- /dev/null
+++ b/lib/Thumbnail/thumbnail.h
@@ -0,0 +1,68 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef TASKBAR_SEVEN_DLL_H
+#define TASKBAR_SEVEN_DLL_H
+
+#ifdef THUMBNAIL_DLL_EXPORTS
+#define DLL_FUNCTION_DECLARATION extern "C" __declspec(dllexport)
+#else
+#define DLL_FUNCTION_DECLARATION extern "C" __declspec(dllimport)
+#endif
+
+#include <zen/build_info.h>
+//#include <WinDef.h>
+
+namespace thumb
+{
+/*
+PREREQUISITES:
+
+1. COM must be initialized for the current thread via ::CoInitialize(NULL) or ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED),
+ but NOT ::CoInitializeEx(NULL, COINIT_MULTITHREADED) -> internal access violation crash!
+2. call ::FileIconInit() on app start to remedy obscure errors like SHELL_E_WRONG_BITDEPTH (0x80270102)
+ for certain file types, e.g. lnk, mpg - required on Windows 7 see http://msdn.microsoft.com/en-us/library/ms683212(v=VS.85).aspx
+*/
+
+/*--------------
+ |declarations|
+ --------------*/
+typedef void* HICON;
+
+DLL_FUNCTION_DECLARATION
+HICON getThumbnail(const wchar_t* filename, int requestedSize); //return 0 on failure, caller takes ownership!
+//Note: not all file types support thumbnails! make sure to implement fallback to file icon!
+
+DLL_FUNCTION_DECLARATION
+HICON getIconByIndex(int iconIndex, int shilIconType); //return 0 on failure, caller takes ownership!
+/*
+"iconType" refers to parameter "iImageList" of ::SHGetImageList(); sample values:
+ SHIL_SMALL - 16x16, but the size can be customized by the user.
+ SHIL_EXTRALARGE - 48x48, but the size can be customized by the user.
+ SHIL_JUMBO - 256x256 pixels; Vista and later only
+"iconIndex" as returned by ::SHGetFileInfo()
+*/
+
+/*----------
+ |typedefs|
+ ----------*/
+typedef HICON (*GetThumbnailFct )(const wchar_t* filename, int requestedSize);
+typedef HICON (*GetIconByIndexFct)(int iconIndex, int shilIconType);
+
+/*--------------
+ |symbol names|
+ --------------*/
+//(use const pointers to ensure internal linkage)
+const char getThumbnailFctName [] = "getThumbnail";
+const char getIconByIndexFctName [] = "getIconByIndex";
+
+/*---------------
+ |library names|
+ ---------------*/
+inline const wchar_t* getDllName() { return zen::is64BitBuild ? L"Thumbnail_x64.dll" : L"Thumbnail_Win32.dll"; }
+}
+
+#endif //TASKBAR_SEVEN_DLL_H
diff --git a/lib/binary.cpp b/lib/binary.cpp
new file mode 100644
index 00000000..4fdf8899
--- /dev/null
+++ b/lib/binary.cpp
@@ -0,0 +1,125 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "binary.h"
+#include <wx/stopwatch.h>
+#include <vector>
+#include <zen/file_io.h>
+#include <zen/int64.h>
+#include <boost/thread/tss.hpp>
+
+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);
+}
+
+
+namespace
+{
+class BufferSize
+{
+public:
+ BufferSize() : bufSize(BUFFER_SIZE_START) {}
+
+ void inc()
+ {
+ if (bufSize < BUFFER_SIZE_MAX)
+ bufSize *= 2;
+ }
+
+ void dec()
+ {
+ if (bufSize > BUFFER_SIZE_MIN)
+ bufSize /= 2;
+ }
+
+ operator size_t() const { return bufSize; }
+
+private:
+ static const size_t BUFFER_SIZE_MIN = 128 * 1024;
+ static const size_t BUFFER_SIZE_START = 512 * 1024; //512 kb seems to be a reasonable 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;
+};
+}
+
+
+bool zen::filesHaveSameContent(const Zstring& filename1, const Zstring& filename2, CompareCallback& callback)
+{
+ FileInput file1(filename1); //throw FileError
+ FileInput file2(filename2); //throw FileError
+
+ static boost::thread_specific_ptr<std::vector<char>> cpyBuf1;
+ static boost::thread_specific_ptr<std::vector<char>> cpyBuf2;
+ if (!cpyBuf1.get())
+ cpyBuf1.reset(new std::vector<char>());
+ if (!cpyBuf2.get())
+ cpyBuf2.reset(new std::vector<char>());
+
+ std::vector<char>& memory1 = *cpyBuf1;
+ std::vector<char>& memory2 = *cpyBuf2;
+
+ BufferSize bufferSize;
+ zen::UInt64 bytesCompared;
+
+ wxLongLong lastDelayViolation = wxGetLocalTimeMillis();
+
+ do
+ {
+ setMinSize(memory1, bufferSize);
+ setMinSize(memory2, bufferSize);
+
+ const wxLongLong startTime = wxGetLocalTimeMillis();
+
+ const size_t length1 = file1.read(&memory1[0], bufferSize); //returns actual number of bytes read; throw FileError()
+ const size_t length2 = file2.read(&memory2[0], bufferSize); //
+
+ const wxLongLong stopTime = wxGetLocalTimeMillis();
+
+ //-------- dynamically set buffer size to keep callback interval between 200 - 500ms ---------------------
+ const wxLongLong loopTime = stopTime - startTime;
+ if (loopTime < 200 && stopTime - lastDelayViolation > 2000) //avoid "flipping back": e.g. DVD-Roms read 32MB at once, so first read may be > 300 ms, but second one will be 0ms!
+ {
+ lastDelayViolation = stopTime;
+ bufferSize.inc(); //practically no costs!
+ }
+ else if (loopTime > 500)
+ {
+ lastDelayViolation = stopTime;
+ bufferSize.dec(); //
+ }
+ //------------------------------------------------------------------------------------------------
+
+ if (length1 != length2 || ::memcmp(&memory1[0], &memory2[0], length1) != 0)
+ return false;
+
+ bytesCompared += length1 * 2;
+ callback.updateCompareStatus(bytesCompared); //send progress updates
+ }
+ while (!file1.eof());
+
+ if (!file2.eof()) //highly unlikely, but theoretically possible! (but then again, not in this context where both files have same size...)
+ return false;
+
+ return true;
+}
diff --git a/lib/binary.h b/lib/binary.h
new file mode 100644
index 00000000..111af249
--- /dev/null
+++ b/lib/binary.h
@@ -0,0 +1,28 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef BINARY_H_INCLUDED
+#define BINARY_H_INCLUDED
+
+#include <zen/zstring.h>
+#include <zen/file_error.h>
+#include <zen/int64.h>
+
+namespace zen
+{
+
+//callback functionality for status updates while comparing
+class CompareCallback
+{
+public:
+ virtual ~CompareCallback() {}
+ virtual void updateCompareStatus(zen::UInt64 totalBytesTransferred) = 0;
+};
+
+bool filesHaveSameContent(const Zstring& filename1, const Zstring& filename2, CompareCallback& callback); //throw FileError
+}
+
+#endif // BINARY_H_INCLUDED
diff --git a/lib/cmp_filetime.h b/lib/cmp_filetime.h
new file mode 100644
index 00000000..e8cd6f50
--- /dev/null
+++ b/lib/cmp_filetime.h
@@ -0,0 +1,65 @@
+#ifndef CMP_FILETIME_H_INCLUDED
+#define CMP_FILETIME_H_INCLUDED
+
+#include <wx/stopwatch.h>
+#include <zen/int64.h>
+
+namespace zen
+{
+//---------------------------------------------------------------------------------------------------------------
+inline
+bool sameFileTime(const Int64& a, const Int64& b, size_t tolerance)
+{
+ if (a < b)
+ return b <= a + static_cast<int>(tolerance);
+ else
+ return a <= b + static_cast<int>(tolerance);
+}
+//---------------------------------------------------------------------------------------------------------------
+
+//number of seconds since Jan 1st 1970 + 1 year (needn't be too precise)
+static const long oneYearFromNow = wxGetUTCTime() + 365 * 24 * 3600; //init at program startup -> avoid MT issues
+
+
+class CmpFileTime
+{
+public:
+ CmpFileTime(size_t tolerance) : tolerance_(tolerance) {}
+
+ enum Result
+ {
+ TIME_EQUAL,
+ TIME_LEFT_NEWER,
+ TIME_RIGHT_NEWER,
+ TIME_LEFT_INVALID,
+ TIME_RIGHT_INVALID
+ };
+
+ Result getResult(const Int64& lhs, const Int64& rhs) const
+ {
+ if (lhs == rhs)
+ return TIME_EQUAL;
+
+ //check for erroneous dates (but only if dates are not (EXACTLY) the same)
+ if (lhs < 0 || lhs > oneYearFromNow) //earlier than Jan 1st 1970 or more than one year in future
+ return TIME_LEFT_INVALID;
+
+ if (rhs < 0 || rhs > oneYearFromNow)
+ return TIME_RIGHT_INVALID;
+
+ if (sameFileTime(lhs, rhs, tolerance_)) //last write time may differ by up to 2 seconds (NTFS vs FAT32)
+ return TIME_EQUAL;
+
+ //regular time comparison
+ if (lhs < rhs)
+ return TIME_RIGHT_NEWER;
+ else
+ return TIME_LEFT_NEWER;
+ }
+
+private:
+ const size_t tolerance_;
+};
+}
+
+#endif // CMP_FILETIME_H_INCLUDED
diff --git a/lib/custom_grid.cpp b/lib/custom_grid.cpp
new file mode 100644
index 00000000..6da8b275
--- /dev/null
+++ b/lib/custom_grid.cpp
@@ -0,0 +1,2400 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "custom_grid.h"
+#include "resources.h"
+#include <wx/dc.h>
+#include <wx+/format_unit.h>
+#include <wx+/string_conv.h>
+#include "resources.h"
+#include <typeinfo>
+#include "../ui/grid_view.h"
+#include "../synchronization.h"
+#include <wx/dcclient.h>
+#include <wx/icon.h>
+#include <wx/tooltip.h>
+#include <wx/settings.h>
+
+#ifdef FFS_WIN
+#include <wx/timer.h>
+#include "status_handler.h"
+#include <cmath>
+
+#elif defined FFS_LINUX
+#include <gtk/gtk.h>
+#endif
+
+using namespace zen;
+
+
+const size_t MIN_ROW_COUNT = 15;
+
+//class containing pure grid data: basically the same as wxGridStringTable, but adds cell formatting
+
+/*
+class hierarchy:
+ CustomGridTable
+ /|\
+ ________________|________________
+ | |
+ CustomGridTableRim |
+ /|\ |
+ __________|__________ |
+ | | |
+CustomGridTableLeft CustomGridTableRight CustomGridTableMiddle
+*/
+
+class CustomGridTable : public wxGridTableBase
+{
+public:
+ CustomGridTable(int initialRows = 0, int initialCols = 0) : //note: initialRows/initialCols MUST match with GetNumberRows()/GetNumberCols() at initialization!!!
+ wxGridTableBase(),
+ gridDataView(NULL),
+ lastNrRows(initialRows),
+ lastNrCols(initialCols) {}
+
+
+ virtual ~CustomGridTable() {}
+
+
+ void setGridDataTable(const GridView* view)
+ {
+ this->gridDataView = view;
+ }
+
+
+ //###########################################################################
+ //grid standard input output methods, redirected directly to gridData to improve performance
+
+ virtual int GetNumberRows()
+ {
+ if (gridDataView)
+ return static_cast<int>(std::max(gridDataView->rowsOnView(), MIN_ROW_COUNT));
+ else
+ return 0; //grid is initialized with zero number of rows
+ }
+
+
+ virtual bool IsEmptyCell(int row, int col)
+ {
+ return false; //avoid overlapping cells
+
+ //return (GetValue(row, col) == wxEmptyString);
+ }
+
+
+ virtual void SetValue(int row, int col, const wxString& value)
+ {
+ assert (false); //should not be used, since values are retrieved directly from gridDataView
+ }
+
+ //update dimensions of grid: no need for InsertRows(), AppendRows(), DeleteRows() anymore!!!
+ void updateGridSizes()
+ {
+ const int currentNrRows = GetNumberRows();
+
+ if (lastNrRows < currentNrRows)
+ {
+ if (GetView())
+ {
+ wxGridTableMessage msg(this,
+ wxGRIDTABLE_NOTIFY_ROWS_APPENDED,
+ currentNrRows - lastNrRows);
+
+ GetView()->ProcessTableMessage( msg );
+ }
+ }
+ else if (lastNrRows > currentNrRows)
+ {
+ if (GetView())
+ {
+ wxGridTableMessage msg(this,
+ wxGRIDTABLE_NOTIFY_ROWS_DELETED,
+ 0,
+ lastNrRows - currentNrRows);
+
+ GetView()->ProcessTableMessage( msg );
+ }
+ }
+ lastNrRows = currentNrRows;
+
+ const int currentNrCols = GetNumberCols();
+
+ if (lastNrCols < currentNrCols)
+ {
+ if (GetView())
+ {
+ wxGridTableMessage msg(this,
+ wxGRIDTABLE_NOTIFY_COLS_APPENDED,
+ currentNrCols - lastNrCols);
+
+ GetView()->ProcessTableMessage( msg );
+ }
+ }
+ else if (lastNrCols > currentNrCols)
+ {
+ if (GetView())
+ {
+ wxGridTableMessage msg(this,
+ wxGRIDTABLE_NOTIFY_COLS_DELETED,
+ 0,
+ lastNrCols - currentNrCols);
+
+ GetView()->ProcessTableMessage( msg );
+ }
+ }
+ lastNrCols = currentNrCols;
+ }
+ //###########################################################################
+
+
+ virtual wxGridCellAttr* GetAttr(int row, int col, wxGridCellAttr::wxAttrKind kind)
+ {
+ const std::pair<wxColour, wxColour> color = getRowColor(row);
+
+ //add color to some rows
+ wxGridCellAttr* result = wxGridTableBase::GetAttr(row, col, kind);
+ if (result)
+ {
+ if (result->GetTextColour() == color.first &&
+ result->GetBackgroundColour() == color.second)
+ {
+ return result;
+ }
+ else //grid attribute might be referenced by other elements, so clone it!
+ {
+ wxGridCellAttr* attr = result->Clone(); //attr has ref-count 1
+ result->DecRef();
+ result = attr;
+ }
+ }
+ else
+ result = new wxGridCellAttr; //created with ref-count 1
+
+ result->SetTextColour (color.first);
+ result->SetBackgroundColour(color.second);
+
+ return result;
+ }
+
+
+ const FileSystemObject* getRawData(size_t row) const
+ {
+ if (gridDataView)
+ return gridDataView->getObject(row); //returns NULL if request is not valid or not data found
+
+ return NULL;
+ }
+
+protected:
+ static const wxColour COLOR_BLUE;
+ static const wxColour COLOR_GREY;
+ static const wxColour COLOR_ORANGE;
+ static const wxColour COLOR_CMP_RED;
+ static const wxColour COLOR_CMP_BLUE;
+ static const wxColour COLOR_CMP_GREEN;
+ static const wxColour COLOR_SYNC_BLUE;
+ static const wxColour COLOR_SYNC_BLUE_LIGHT;
+ static const wxColour COLOR_SYNC_GREEN;
+ static const wxColour COLOR_SYNC_GREEN_LIGHT;
+ static const wxColour COLOR_YELLOW;
+ static const wxColour COLOR_YELLOW_LIGHT;
+
+ const GridView* gridDataView; //(very fast) access to underlying grid data :)
+
+private:
+ virtual const std::pair<wxColour, wxColour> getRowColor(int row) = 0; //rows that are filtered out are shown in different color: <foreground, background>
+
+ int lastNrRows;
+ int lastNrCols;
+};
+
+//see http://www.latiumsoftware.com/en/articles/00015.php#12 for "safe" colors
+const wxColour CustomGridTable::COLOR_ORANGE( 238, 201, 0);
+const wxColour CustomGridTable::COLOR_BLUE( 80, 110, 255);
+const wxColour CustomGridTable::COLOR_GREY( 212, 208, 200);
+const wxColour CustomGridTable::COLOR_CMP_RED( 249, 163, 165);
+const wxColour CustomGridTable::COLOR_CMP_BLUE( 144, 232, 246);
+const wxColour CustomGridTable::COLOR_CMP_GREEN( 147, 253, 159);
+const wxColour CustomGridTable::COLOR_SYNC_BLUE( 201, 203, 247);
+const wxColour CustomGridTable::COLOR_SYNC_BLUE_LIGHT(201, 225, 247);
+const wxColour CustomGridTable::COLOR_SYNC_GREEN(197, 248, 190);
+const wxColour CustomGridTable::COLOR_SYNC_GREEN_LIGHT(226, 248, 190);
+const wxColour CustomGridTable::COLOR_YELLOW( 247, 252, 62);
+const wxColour CustomGridTable::COLOR_YELLOW_LIGHT(253, 252, 169);
+
+
+class CustomGridTableRim : public CustomGridTable
+{
+public:
+ virtual ~CustomGridTableRim() {}
+
+ virtual int GetNumberCols()
+ {
+ return static_cast<int>(columnPositions.size());
+ }
+
+ virtual wxString GetColLabelValue( int col )
+ {
+ return CustomGridRim::getTypeName(getTypeAtPos(col));
+ }
+
+
+ void setupColumns(const std::vector<xmlAccess::ColumnTypes>& positions)
+ {
+ columnPositions = positions;
+ updateGridSizes(); //add or remove columns
+ }
+
+
+ xmlAccess::ColumnTypes getTypeAtPos(size_t pos) const
+ {
+ if (pos < columnPositions.size())
+ return columnPositions[pos];
+ else
+ return xmlAccess::DIRECTORY;
+ }
+
+ //get filename in order to retrieve the icon from it
+ virtual Zstring getIconFile(size_t row) const = 0; //return "folder" if row points to a folder
+
+protected:
+ template <SelectedSide side>
+ wxString GetValueSub(int row, int col)
+ {
+ const FileSystemObject* fsObj = getRawData(row);
+ if (fsObj)
+ {
+ struct GetTextValue : public FSObjectVisitor
+ {
+ GetTextValue(xmlAccess::ColumnTypes colType, const FileSystemObject& fso) : colType_(colType), fsObj_(fso) {}
+ virtual void visit(const FileMapping& fileObj)
+ {
+ switch (colType_)
+ {
+ case xmlAccess::FULL_PATH:
+ value = toWx(beforeLast(fileObj.getFullName<side>(), FILE_NAME_SEPARATOR));
+ break;
+ case xmlAccess::FILENAME: //filename
+ value = toWx(fileObj.getShortName<side>());
+ break;
+ case xmlAccess::REL_PATH: //relative path
+ value = toWx(beforeLast(fileObj.getObjRelativeName(), FILE_NAME_SEPARATOR)); //returns empty string if ch not found
+ break;
+ case xmlAccess::DIRECTORY:
+ value = toWx(fileObj.getBaseDirPf<side>());
+ break;
+ case xmlAccess::SIZE: //file size
+ if (!fsObj_.isEmpty<side>())
+ value = zen::toStringSep(fileObj.getFileSize<side>());
+ break;
+ case xmlAccess::DATE: //date
+ if (!fsObj_.isEmpty<side>())
+ value = zen::utcToLocalTimeString(fileObj.getLastWriteTime<side>());
+ break;
+ case xmlAccess::EXTENSION: //file extension
+ value = toWx(fileObj.getExtension<side>());
+ break;
+ }
+ }
+
+ virtual void visit(const SymLinkMapping& linkObj)
+ {
+ switch (colType_)
+ {
+ case xmlAccess::FULL_PATH:
+ value = toWx(beforeLast(linkObj.getFullName<side>(), FILE_NAME_SEPARATOR));
+ break;
+ case xmlAccess::FILENAME: //filename
+ value = toWx(linkObj.getShortName<side>());
+ break;
+ case xmlAccess::REL_PATH: //relative path
+ value = toWx(beforeLast(linkObj.getObjRelativeName(), FILE_NAME_SEPARATOR)); //returns empty string if ch not found
+ break;
+ case xmlAccess::DIRECTORY:
+ value = toWx(linkObj.getBaseDirPf<side>());
+ break;
+ case xmlAccess::SIZE: //file size
+ if (!fsObj_.isEmpty<side>())
+ value = _("<Symlink>");
+ break;
+ case xmlAccess::DATE: //date
+ if (!fsObj_.isEmpty<side>())
+ value = zen::utcToLocalTimeString(linkObj.getLastWriteTime<side>());
+ break;
+ case xmlAccess::EXTENSION: //file extension
+ value = wxEmptyString;
+ break;
+ }
+ }
+
+ virtual void visit(const DirMapping& dirObj)
+ {
+ switch (colType_)
+ {
+ case xmlAccess::FULL_PATH:
+ value = toWx(dirObj.getFullName<side>());
+ break;
+ case xmlAccess::FILENAME:
+ value = toWx(dirObj.getShortName<side>());
+ break;
+ case xmlAccess::REL_PATH:
+ value = toWx(beforeLast(dirObj.getObjRelativeName(), FILE_NAME_SEPARATOR)); //returns empty string if ch not found
+ break;
+ case xmlAccess::DIRECTORY:
+ value = toWx(dirObj.getBaseDirPf<side>());
+ break;
+ case xmlAccess::SIZE: //file size
+ if (!fsObj_.isEmpty<side>())
+ value = _("<Directory>");
+ break;
+ case xmlAccess::DATE: //date
+ if (!fsObj_.isEmpty<side>())
+ value = wxEmptyString;
+ break;
+ case xmlAccess::EXTENSION: //file extension
+ value = wxEmptyString;
+ break;
+ }
+ }
+ xmlAccess::ColumnTypes colType_;
+ wxString value;
+
+ const FileSystemObject& fsObj_;
+ } getVal(getTypeAtPos(col), *fsObj);
+ fsObj->accept(getVal);
+ return getVal.value;
+ }
+ //if data is not found:
+ return wxEmptyString;
+ }
+
+ template <SelectedSide side>
+ Zstring getIconFileImpl(size_t row) const //return "folder" if row points to a folder
+ {
+ const FileSystemObject* fsObj = getRawData(row);
+ if (fsObj && !fsObj->isEmpty<side>())
+ {
+ struct GetIcon : public FSObjectVisitor
+ {
+ virtual void visit(const FileMapping& fileObj)
+ {
+ //Optimization: if filename exists on both sides, always use left side's file
+ //if (!fileObj.isEmpty<LEFT_SIDE>() && !fileObj.isEmpty<RIGHT_SIDE>())
+ // iconName = fileObj.getFullName<LEFT_SIDE>();
+ //else -> now with thumbnails this isn't viable anymore
+ iconName = fileObj.getFullName<side>();
+ }
+ virtual void visit(const SymLinkMapping& linkObj)
+ {
+ iconName = linkObj.getLinkType<side>() == LinkDescriptor::TYPE_DIR ?
+ Zstr("folder") :
+ linkObj.getFullName<side>();
+ }
+ virtual void visit(const DirMapping& dirObj)
+ {
+ iconName = Zstr("folder");
+ }
+
+ Zstring iconName;
+ } getIcon;
+ fsObj->accept(getIcon);
+ return getIcon.iconName;
+ }
+
+ return Zstring();
+ }
+
+
+private:
+ virtual const std::pair<wxColour, wxColour> getRowColor(int row) //rows that are filtered out are shown in different color: <foreground, background>
+ {
+ std::pair<wxColour, wxColour> result(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT),
+ wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
+
+ const FileSystemObject* fsObj = getRawData(row);
+ if (fsObj)
+ {
+ //mark filtered rows
+ if (!fsObj->isActive())
+ {
+ result.first = *wxBLACK;
+ result.second = COLOR_BLUE;
+ }
+ else
+ {
+ //mark directories and symlinks
+ struct GetRowColor : public FSObjectVisitor
+ {
+ GetRowColor(wxColour& foreground, wxColour& background) : foreground_(foreground), background_(background) {}
+
+ virtual void visit(const FileMapping& fileObj) {}
+ virtual void visit(const SymLinkMapping& linkObj)
+ {
+ foreground_ = *wxBLACK;
+ background_ = COLOR_ORANGE;
+ }
+ virtual void visit(const DirMapping& dirObj)
+ {
+ foreground_ = *wxBLACK;
+ background_ = COLOR_GREY;
+ }
+
+ private:
+ wxColour& foreground_;
+ wxColour& background_;
+ } getCol(result.first, result.second);
+ fsObj->accept(getCol);
+ }
+ }
+
+ return result;
+ }
+
+ std::vector<xmlAccess::ColumnTypes> columnPositions;
+};
+
+
+class CustomGridTableLeft : public CustomGridTableRim
+{
+public:
+
+ virtual wxString GetValue(int row, int col)
+ {
+ return CustomGridTableRim::GetValueSub<LEFT_SIDE>(row, col);
+ }
+
+ virtual Zstring getIconFile(size_t row) const //return "folder" if row points to a folder
+ {
+ return getIconFileImpl<LEFT_SIDE>(row);
+ }
+};
+
+
+class CustomGridTableRight : public CustomGridTableRim
+{
+public:
+ virtual wxString GetValue(int row, int col)
+ {
+ return CustomGridTableRim::GetValueSub<RIGHT_SIDE>(row, col);
+ }
+
+ virtual Zstring getIconFile(size_t row) const //return "folder" if row points to a folder
+ {
+ return getIconFileImpl<RIGHT_SIDE>(row);
+ }
+};
+
+
+class CustomGridTableMiddle : public CustomGridTable
+{
+public:
+ //middle grid is created (first wxWidgets internal call to GetNumberCols()) with one column
+ CustomGridTableMiddle() :
+ CustomGridTable(0, GetNumberCols()), //attention: static binding to virtual GetNumberCols() in a Constructor!
+ syncPreviewActive(false) {}
+
+ virtual int GetNumberCols()
+ {
+ return 1;
+ }
+
+ virtual wxString GetColLabelValue( int col )
+ {
+ return wxEmptyString;
+ }
+
+ virtual wxString GetValue(int row, int col) //method used for exporting .csv file only!
+ {
+ const FileSystemObject* fsObj = getRawData(row);
+ if (fsObj)
+ {
+ if (syncPreviewActive) //synchronization preview
+ return getSymbol(fsObj->getSyncOperation());
+ else
+ return getSymbol(fsObj->getCategory());
+ }
+ return wxEmptyString;
+ }
+
+ void enableSyncPreview(bool value)
+ {
+ syncPreviewActive = value;
+ }
+
+ bool syncPreviewIsActive() const
+ {
+ return syncPreviewActive;
+ }
+
+private:
+ virtual const std::pair<wxColour, wxColour> getRowColor(int row) //rows that are filtered out are shown in different color: <foreground, background>
+ {
+ std::pair<wxColour, wxColour> result(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT),
+ wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
+
+ const FileSystemObject* fsObj = getRawData(row);
+ if (fsObj)
+ {
+ //mark filtered rows
+ if (!fsObj->isActive())
+ {
+ result.first = *wxBLACK;;
+ result.second = COLOR_BLUE;
+ }
+ else
+ {
+ if (syncPreviewActive) //synchronization preview
+ {
+ switch (fsObj->getSyncOperation()) //evaluate comparison result and sync direction
+ {
+ case SO_DO_NOTHING:
+ case SO_EQUAL:
+ break;//usually white
+ case SO_CREATE_NEW_LEFT:
+ case SO_OVERWRITE_LEFT:
+ case SO_DELETE_LEFT:
+ result.first = *wxBLACK;
+ result.second = COLOR_SYNC_BLUE;
+ break;
+ case SO_COPY_METADATA_TO_LEFT:
+ result.first = *wxBLACK;
+ result.second = COLOR_SYNC_BLUE_LIGHT;
+ break;
+ case SO_CREATE_NEW_RIGHT:
+ case SO_OVERWRITE_RIGHT:
+ case SO_DELETE_RIGHT:
+ result.first = *wxBLACK;
+ result.second = COLOR_SYNC_GREEN;
+ break;
+ case SO_COPY_METADATA_TO_RIGHT:
+ result.first = *wxBLACK;
+ result.second = COLOR_SYNC_GREEN_LIGHT;
+ break;
+ case SO_UNRESOLVED_CONFLICT:
+ result.first = *wxBLACK;
+ result.second = COLOR_YELLOW;
+ break;
+ }
+ }
+ else //comparison results view
+ {
+ switch (fsObj->getCategory())
+ {
+ case FILE_LEFT_SIDE_ONLY:
+ case FILE_LEFT_NEWER:
+ result.first = *wxBLACK;
+ result.second = COLOR_SYNC_BLUE; //COLOR_CMP_BLUE;
+ break;
+
+ case FILE_RIGHT_SIDE_ONLY:
+ case FILE_RIGHT_NEWER:
+ result.first = *wxBLACK;
+ result.second = COLOR_SYNC_GREEN; //COLOR_CMP_GREEN;
+ break;
+ case FILE_DIFFERENT:
+ result.first = *wxBLACK;
+ result.second = COLOR_CMP_RED;
+ break;
+ case FILE_EQUAL:
+ break;//usually white
+ case FILE_CONFLICT:
+ result.first = *wxBLACK;
+ result.second = COLOR_YELLOW;
+ break;
+ case FILE_DIFFERENT_METADATA:
+ result.first = *wxBLACK;
+ result.second = COLOR_YELLOW_LIGHT;
+ break;
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ bool syncPreviewActive; //determines wheter grid shall show compare result or sync preview
+};
+
+//########################################################################################################
+
+
+CustomGrid::CustomGrid(wxWindow* parent,
+ wxWindowID id,
+ const wxPoint& pos,
+ const wxSize& size,
+ long style,
+ const wxString& name) :
+ wxGrid(parent, id, pos, size, style, name),
+ m_gridLeft(NULL),
+ m_gridMiddle(NULL),
+ m_gridRight(NULL),
+ isLeading(false),
+ m_marker(-1, ASCENDING)
+{
+ //wxColour darkBlue(40, 35, 140); -> user default colors instead!
+ //SetSelectionBackground(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT));
+ //SetSelectionForeground(*wxWHITE);
+}
+
+
+void CustomGrid::initSettings(CustomGridLeft* gridLeft,
+ CustomGridMiddle* gridMiddle,
+ CustomGridRight* gridRight,
+ const GridView* gridDataView)
+{
+ assert(this == gridLeft || this == gridRight || this == gridMiddle);
+
+ //these grids will scroll together
+ m_gridLeft = gridLeft;
+ m_gridRight = gridRight;
+ m_gridMiddle = gridMiddle;
+
+ //enhance grid functionality; identify leading grid by keyboard input or scroll action
+ Connect(wxEVT_KEY_DOWN, wxEventHandler(CustomGrid::onGridAccess), NULL, this);
+ Connect(wxEVT_SCROLLWIN_TOP, wxEventHandler(CustomGrid::onGridAccess), NULL, this);
+ Connect(wxEVT_SCROLLWIN_BOTTOM, wxEventHandler(CustomGrid::onGridAccess), NULL, this);
+ Connect(wxEVT_SCROLLWIN_LINEUP, wxEventHandler(CustomGrid::onGridAccess), NULL, this);
+ Connect(wxEVT_SCROLLWIN_LINEDOWN, wxEventHandler(CustomGrid::onGridAccess), NULL, this);
+ Connect(wxEVT_SCROLLWIN_PAGEUP, wxEventHandler(CustomGrid::onGridAccess), NULL, this);
+ Connect(wxEVT_SCROLLWIN_PAGEDOWN, wxEventHandler(CustomGrid::onGridAccess), NULL, this);
+ Connect(wxEVT_SCROLLWIN_THUMBTRACK, wxEventHandler(CustomGrid::onGridAccess), NULL, this);
+ Connect(wxEVT_SCROLLWIN_THUMBRELEASE, wxEventHandler(CustomGrid::onGridAccess), NULL, this);
+ Connect(wxEVT_GRID_LABEL_LEFT_CLICK, wxEventHandler(CustomGrid::onGridAccess), NULL, this);
+ Connect(wxEVT_SET_FOCUS, wxEventHandler(CustomGrid::onGridAccess), NULL, this); //used by grid text-search
+ GetGridWindow()->Connect(wxEVT_LEFT_DOWN, wxEventHandler(CustomGrid::onGridAccess), NULL, this);
+ GetGridWindow()->Connect(wxEVT_RIGHT_DOWN, wxEventHandler(CustomGrid::onGridAccess), NULL, this);
+
+ GetGridWindow()->Connect(wxEVT_ENTER_WINDOW, wxEventHandler(CustomGrid::adjustGridHeights), NULL, this);
+
+ //parallel grid scrolling: do NOT use DoPrepareDC() to align grids! GDI resource leak! Use regular paint event instead:
+ GetGridWindow()->Connect(wxEVT_PAINT, wxEventHandler(CustomGrid::OnPaintGrid), NULL, this);
+}
+
+
+void CustomGrid::release() //release connection to zen::GridView
+{
+ assert(getGridDataTable());
+ getGridDataTable()->setGridDataTable(NULL); //kind of "disable" griddatatable; don't delete it with SetTable(NULL)! May be used by wxGridCellStringRenderer
+}
+
+
+bool CustomGrid::isLeadGrid() const
+{
+ return isLeading;
+}
+
+
+void CustomGrid::setIconManager(const std::shared_ptr<IconBuffer>& iconBuffer)
+{
+ if (iconBuffer.get())
+ SetDefaultRowSize(iconBuffer->getSize() + 1, true); //+ 1 for line between rows
+ else
+ SetDefaultRowSize(IconBuffer(IconBuffer::SIZE_SMALL).getSize() + 1, true); //currently iconBuffer is always bound, but we may use it as a "no icon" status at some time...
+
+ enableFileIcons(iconBuffer);
+ Refresh();
+}
+
+
+void CustomGrid::RefreshCell(int row, int col)
+{
+ wxRect rectScrolled(CellToRect(row, col));
+ //use: wxRect rect = CellToRect( row, col ); ?
+ CalcScrolledPosition(rectScrolled.x, rectScrolled.y, &rectScrolled.x, &rectScrolled.y);
+
+ GetGridWindow()->RefreshRect(rectScrolled); //note: CellToRect() and YToRow work on m_gridWindow NOT on the whole grid!
+}
+
+
+void CustomGrid::OnPaintGrid(wxEvent& event)
+{
+ if (isLeadGrid()) //avoid back coupling
+ alignOtherGrids(m_gridLeft, m_gridMiddle, m_gridRight); //scroll other grids
+ event.Skip();
+}
+
+
+void moveCursorWhileSelecting(int anchor, int oldPos, int newPos, wxGrid* grid)
+{
+ //note: all positions are valid in this context!
+
+ grid->SetGridCursor( newPos, grid->GetGridCursorCol());
+ grid->MakeCellVisible(newPos, grid->GetGridCursorCol());
+
+ if (oldPos < newPos)
+ {
+ for (int i = oldPos; i < std::min(anchor, newPos); ++i)
+ grid->DeselectRow(i); //remove selection
+
+ for (int i = std::max(oldPos, anchor); i <= newPos; ++i)
+ grid->SelectRow(i, true); //add to selection
+ }
+ else
+ {
+ for (int i = std::max(newPos, anchor) + 1; i <= oldPos; ++i)
+ grid->DeselectRow(i); //remove selection
+
+ for (int i = newPos; i <= std::min(oldPos, anchor); ++i)
+ grid->SelectRow(i, true); //add to selection
+ }
+}
+
+
+void execGridCommands(wxEvent& event, wxGrid* grid)
+{
+ static int anchorRow = 0;
+ if (grid->GetNumberRows() == 0 ||
+ grid->GetNumberCols() == 0)
+ return;
+
+ const wxKeyEvent* keyEvent = dynamic_cast<const wxKeyEvent*> (&event);
+ if (keyEvent)
+ {
+ //ensure cursorOldPos is always a valid row!
+ const int cursorOldPos = std::max(std::min(grid->GetGridCursorRow(), grid->GetNumberRows() - 1), 0);
+ const int cursorOldColumn = std::max(std::min(grid->GetGridCursorCol(), grid->GetNumberCols() - 1), 0);
+
+ const bool shiftPressed = keyEvent->ShiftDown();
+ const bool ctrlPressed = keyEvent->ControlDown();
+ const int keyCode = keyEvent->GetKeyCode();
+
+ //SHIFT + X
+ if (shiftPressed)
+ switch (keyCode)
+ {
+ case WXK_UP:
+ case WXK_NUMPAD_UP:
+ {
+ const int cursorNewPos = std::max(cursorOldPos - 1, 0);
+ moveCursorWhileSelecting(anchorRow, cursorOldPos, cursorNewPos, grid);
+ return; //no event.Skip()
+ }
+ case WXK_DOWN:
+ case WXK_NUMPAD_DOWN:
+ {
+ const int cursorNewPos = std::min(cursorOldPos + 1, grid->GetNumberRows() - 1);
+ moveCursorWhileSelecting(anchorRow, cursorOldPos, cursorNewPos, grid);
+ return; //no event.Skip()
+ }
+ case WXK_LEFT:
+ case WXK_NUMPAD_LEFT:
+ {
+ const int cursorColumn = std::max(cursorOldColumn - 1, 0);
+ grid->SetGridCursor(cursorOldPos, cursorColumn);
+ grid->MakeCellVisible(cursorOldPos, cursorColumn);
+ return; //no event.Skip()
+ }
+ case WXK_RIGHT:
+ case WXK_NUMPAD_RIGHT:
+ {
+ const int cursorColumn = std::min(cursorOldColumn + 1, grid->GetNumberCols() - 1);
+ grid->SetGridCursor(cursorOldPos, cursorColumn);
+ grid->MakeCellVisible(cursorOldPos, cursorColumn);
+ return; //no event.Skip()
+ }
+ case WXK_PAGEUP:
+ case WXK_NUMPAD_PAGEUP:
+ {
+ const int rowsPerPage = grid->GetGridWindow()->GetSize().GetHeight() / grid->GetDefaultRowSize();
+ const int cursorNewPos = std::max(cursorOldPos - rowsPerPage, 0);
+ moveCursorWhileSelecting(anchorRow, cursorOldPos, cursorNewPos, grid);
+ return; //no event.Skip()
+ }
+ case WXK_PAGEDOWN:
+ case WXK_NUMPAD_PAGEDOWN:
+ {
+ const int rowsPerPage = grid->GetGridWindow()->GetSize().GetHeight() / grid->GetDefaultRowSize();
+ const int cursorNewPos = std::min(cursorOldPos + rowsPerPage, grid->GetNumberRows() - 1);
+ moveCursorWhileSelecting(anchorRow, cursorOldPos, cursorNewPos, grid);
+ return; //no event.Skip()
+ }
+ case WXK_HOME:
+ case WXK_NUMPAD_HOME:
+ {
+ const int cursorNewPos = 0;
+ moveCursorWhileSelecting(anchorRow, cursorOldPos, cursorNewPos, grid);
+ return; //no event.Skip()
+ }
+ case WXK_END:
+ case WXK_NUMPAD_END:
+ {
+ const int cursorNewPos = grid->GetNumberRows() - 1;
+ moveCursorWhileSelecting(anchorRow, cursorOldPos, cursorNewPos, grid);
+ return; //no event.Skip()
+ }
+ }
+
+ //CTRL + X
+ if (ctrlPressed)
+ switch (keyCode)
+ {
+ case WXK_UP:
+ case WXK_NUMPAD_UP:
+ {
+ grid->SetGridCursor(0, grid->GetGridCursorCol());
+ grid->MakeCellVisible(0, grid->GetGridCursorCol());
+ return; //no event.Skip()
+ }
+ case WXK_DOWN:
+ case WXK_NUMPAD_DOWN:
+ {
+ grid->SetGridCursor(grid->GetNumberRows() - 1, grid->GetGridCursorCol());
+ grid->MakeCellVisible(grid->GetNumberRows() - 1, grid->GetGridCursorCol());
+ return; //no event.Skip()
+ }
+ case WXK_LEFT:
+ case WXK_NUMPAD_LEFT:
+ {
+ grid->SetGridCursor(grid->GetGridCursorRow(), 0);
+ grid->MakeCellVisible(grid->GetGridCursorRow(), 0);
+ return; //no event.Skip()
+ }
+ case WXK_RIGHT:
+ case WXK_NUMPAD_RIGHT:
+ {
+ grid->SetGridCursor(grid->GetGridCursorRow(), grid->GetNumberCols() - 1);
+ grid->MakeCellVisible(grid->GetGridCursorRow(), grid->GetNumberCols() - 1);
+ return; //no event.Skip()
+ }
+ }
+
+ //button with or without control keys pressed
+ switch (keyCode)
+ {
+ case WXK_HOME:
+ case WXK_NUMPAD_HOME:
+ {
+ grid->SetGridCursor(0, grid->GetGridCursorCol());
+ grid->MakeCellVisible(0, grid->GetGridCursorCol());
+ return; //no event.Skip()
+ }
+ case WXK_END:
+ case WXK_NUMPAD_END:
+ {
+ grid->SetGridCursor(grid->GetNumberRows() - 1, grid->GetGridCursorCol());
+ grid->MakeCellVisible(grid->GetNumberRows() - 1, grid->GetGridCursorCol());
+ return; //no event.Skip()
+ }
+
+ case WXK_PAGEUP:
+ case WXK_NUMPAD_PAGEUP:
+ {
+ const int rowsPerPage = grid->GetGridWindow()->GetSize().GetHeight() / grid->GetDefaultRowSize();
+ const int cursorNewPos = std::max(cursorOldPos - rowsPerPage, 0);
+ grid->SetGridCursor(cursorNewPos, grid->GetGridCursorCol());
+ grid->MakeCellVisible(cursorNewPos, grid->GetGridCursorCol());
+ return; //no event.Skip()
+ }
+ case WXK_PAGEDOWN:
+ case WXK_NUMPAD_PAGEDOWN:
+ {
+ const int rowsPerPage = grid->GetGridWindow()->GetSize().GetHeight() / grid->GetDefaultRowSize();
+ const int cursorNewPos = std::min(cursorOldPos + rowsPerPage, grid->GetNumberRows() - 1);
+ grid->SetGridCursor(cursorNewPos, grid->GetGridCursorCol());
+ grid->MakeCellVisible(cursorNewPos, grid->GetGridCursorCol());
+ return; //no event.Skip()
+ }
+ }
+
+ //button without additonal control keys pressed
+ if (keyEvent->GetModifiers() == wxMOD_NONE)
+ switch (keyCode)
+ {
+ case WXK_UP:
+ case WXK_NUMPAD_UP:
+ {
+ const int cursorNewPos = std::max(cursorOldPos - 1, 0);
+ grid->SetGridCursor(cursorNewPos, grid->GetGridCursorCol());
+ grid->MakeCellVisible(cursorNewPos, grid->GetGridCursorCol());
+ return; //no event.Skip()
+ }
+ case WXK_DOWN:
+ case WXK_NUMPAD_DOWN:
+ {
+ const int cursorNewPos = std::min(cursorOldPos + 1, grid->GetNumberRows() - 1);
+ grid->SetGridCursor(cursorNewPos, grid->GetGridCursorCol());
+ grid->MakeCellVisible(cursorNewPos, grid->GetGridCursorCol());
+ return; //no event.Skip()
+ }
+ case WXK_LEFT:
+ case WXK_NUMPAD_LEFT:
+ {
+ const int cursorColumn = std::max(cursorOldColumn - 1, 0);
+ grid->SetGridCursor(cursorOldPos, cursorColumn);
+ grid->MakeCellVisible(cursorOldPos, cursorColumn);
+ return; //no event.Skip()
+ }
+ case WXK_RIGHT:
+ case WXK_NUMPAD_RIGHT:
+ {
+ const int cursorColumn = std::min(cursorOldColumn + 1, grid->GetNumberCols() - 1);
+ grid->SetGridCursor(cursorOldPos, cursorColumn);
+ grid->MakeCellVisible(cursorOldPos, cursorColumn);
+ return; //no event.Skip()
+ }
+ }
+ }
+
+ anchorRow = grid->GetGridCursorRow();
+ event.Skip(); //let event delegate!
+}
+
+
+inline
+bool gridsShouldBeCleared(const wxEvent& event)
+{
+ const wxMouseEvent* mouseEvent = dynamic_cast<const wxMouseEvent*>(&event);
+ if (mouseEvent)
+ {
+ if (mouseEvent->ControlDown() || mouseEvent->ShiftDown())
+ return false;
+
+ if (mouseEvent->ButtonDown(wxMOUSE_BTN_LEFT))
+ return true;
+ }
+ else
+ {
+ const wxKeyEvent* keyEvent = dynamic_cast<const wxKeyEvent*>(&event);
+ if (keyEvent)
+ {
+ if (keyEvent->ControlDown() || keyEvent->AltDown() || keyEvent->ShiftDown())
+ return false;
+
+ switch (keyEvent->GetKeyCode())
+ {
+ //default navigation keys
+ case WXK_UP:
+ case WXK_DOWN:
+ case WXK_LEFT:
+ case WXK_RIGHT:
+ case WXK_PAGEUP:
+ case WXK_PAGEDOWN:
+ case WXK_HOME:
+ case WXK_END:
+ case WXK_NUMPAD_UP:
+ case WXK_NUMPAD_DOWN:
+ case WXK_NUMPAD_LEFT:
+ case WXK_NUMPAD_RIGHT:
+ case WXK_NUMPAD_PAGEUP:
+ case WXK_NUMPAD_PAGEDOWN:
+ case WXK_NUMPAD_HOME:
+ case WXK_NUMPAD_END:
+ //other keys
+ case WXK_TAB:
+ case WXK_RETURN:
+ case WXK_NUMPAD_ENTER:
+ case WXK_ESCAPE:
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+
+void CustomGrid::onGridAccess(wxEvent& event)
+{
+ if (!isLeading)
+ {
+ //notify other grids of new user focus
+ m_gridLeft ->isLeading = m_gridLeft == this;
+ m_gridMiddle->isLeading = m_gridMiddle == this;
+ m_gridRight ->isLeading = m_gridRight == this;
+
+ wxGrid::SetFocus();
+ }
+
+ //clear grids
+ if (gridsShouldBeCleared(event))
+ {
+ m_gridLeft ->ClearSelection();
+ m_gridMiddle->ClearSelection();
+ m_gridRight ->ClearSelection();
+ }
+
+ //update row labels NOW (needed when scrolling if buttons keep being pressed)
+ m_gridLeft ->GetGridRowLabelWindow()->Update();
+ m_gridRight->GetGridRowLabelWindow()->Update();
+
+ //support for custom short-cuts (overwriting wxWidgets functionality!)
+ execGridCommands(event, this); //event.Skip is handled here!
+}
+
+
+//workaround: ensure that all grids are properly aligned: add some extra window space to grids that have no horizontal scrollbar
+void CustomGrid::adjustGridHeights(wxEvent& event)
+{
+ //m_gridLeft, m_gridRight, m_gridMiddle not NULL because called after initSettings()
+
+ int y1 = 0;
+ int y2 = 0;
+ int y3 = 0;
+ int dummy = 0;
+
+ m_gridLeft ->GetViewStart(&dummy, &y1);
+ m_gridRight ->GetViewStart(&dummy, &y2);
+ m_gridMiddle->GetViewStart(&dummy, &y3);
+
+ if (y1 != y2 || y2 != y3)
+ {
+ int yMax = std::max(y1, std::max(y2, y3));
+
+ if (m_gridLeft->isLeadGrid()) //do not handle case (y1 == yMax) here!!! Avoid back coupling!
+ m_gridLeft->SetMargins(0, 0);
+ else if (y1 < yMax)
+ m_gridLeft->SetMargins(0, 30);
+
+ if (m_gridRight->isLeadGrid())
+ m_gridRight->SetMargins(0, 0);
+ else if (y2 < yMax)
+ m_gridRight->SetMargins(0, 30);
+
+ if (m_gridMiddle->isLeadGrid())
+ m_gridMiddle->SetMargins(0, 0);
+ else if (y3 < yMax)
+ m_gridMiddle->SetMargins(0, 30);
+
+ m_gridLeft ->ForceRefresh();
+ m_gridRight ->ForceRefresh();
+ m_gridMiddle->ForceRefresh();
+ }
+}
+
+
+void CustomGrid::updateGridSizes()
+{
+ if (getGridDataTable())
+ getGridDataTable()->updateGridSizes();
+}
+
+
+void CustomGridRim::updateGridSizes()
+{
+ CustomGrid::updateGridSizes();
+
+ //set row label size
+
+ //SetRowLabelSize(wxGRID_AUTOSIZE); -> we can do better
+ wxClientDC dc(GetGridRowLabelWindow());
+ dc.SetFont(GetLabelFont());
+
+ wxArrayString lines;
+ lines.push_back(GetRowLabelValue(GetNumberRows()));
+
+ long width = 0;
+ long dummy = 0;
+ GetTextBoxSize(dc, lines, &width, &dummy);
+
+ width += 8;
+ SetRowLabelSize(width);
+}
+
+
+void CustomGrid::setSortMarker(SortMarker marker)
+{
+ m_marker = marker;
+}
+
+
+void CustomGrid::DrawColLabel(wxDC& dc, int col)
+{
+ wxGrid::DrawColLabel(dc, col);
+
+ if (col == m_marker.first)
+ {
+ if (m_marker.second == ASCENDING)
+ dc.DrawBitmap(GlobalResources::getImage(wxT("smallUp")), GetColRight(col) - 16 - 2, 2, true); //respect 2-pixel border
+ else
+ dc.DrawBitmap(GlobalResources::getImage(wxT("smallDown")), GetColRight(col) - 16 - 2, 2, true); //respect 2-pixel border
+ }
+}
+
+
+std::pair<int, int> CustomGrid::mousePosToCell(wxPoint pos)
+{
+ int x = -1;
+ int y = -1;
+ CalcUnscrolledPosition(pos.x, pos.y, &x, &y);
+
+ std::pair<int, int> output(-1, -1);
+ if (x >= 0 && y >= 0)
+ {
+ output.first = YToRow(y);
+ output.second = XToCol(x);
+ }
+ return output;
+}
+
+
+std::set<size_t> CustomGrid::getAllSelectedRows() const
+{
+ std::set<size_t> output;
+
+ const wxArrayInt selectedRows = this->GetSelectedRows();
+ if (!selectedRows.IsEmpty())
+ {
+ for (size_t i = 0; i < selectedRows.GetCount(); ++i)
+ output.insert(selectedRows[i]);
+ }
+
+ if (!this->GetSelectedCols().IsEmpty()) //if a column is selected this is means all rows are marked for deletion
+ {
+ for (int k = 0; k < const_cast<CustomGrid*>(this)->GetNumberRows(); ++k) //messy wxGrid implementation...
+ output.insert(k);
+ }
+
+ const wxGridCellCoordsArray singlySelected = this->GetSelectedCells();
+ if (!singlySelected.IsEmpty())
+ {
+ for (size_t k = 0; k < singlySelected.GetCount(); ++k)
+ output.insert(singlySelected[k].GetRow());
+ }
+
+ const wxGridCellCoordsArray tmpArrayTop = this->GetSelectionBlockTopLeft();
+ if (!tmpArrayTop.IsEmpty())
+ {
+ wxGridCellCoordsArray tmpArrayBottom = this->GetSelectionBlockBottomRight();
+
+ size_t arrayCount = tmpArrayTop.GetCount();
+
+ if (arrayCount == tmpArrayBottom.GetCount())
+ {
+ for (size_t i = 0; i < arrayCount; ++i)
+ {
+ const int rowTop = tmpArrayTop[i].GetRow();
+ const int rowBottom = tmpArrayBottom[i].GetRow();
+
+ for (int k = rowTop; k <= rowBottom; ++k)
+ output.insert(k);
+ }
+ }
+ }
+
+ //some exception: also add current cursor row to selection if there are no others... hopefully improving usability
+ if (output.empty() && this->isLeadGrid())
+ output.insert(const_cast<CustomGrid*>(this)->GetCursorRow()); //messy wxGrid implementation...
+
+ return output;
+}
+
+
+//############################################################################################
+//CustomGrid specializations
+
+class GridCellRenderer : public wxGridCellStringRenderer
+{
+public:
+ GridCellRenderer(CustomGridRim::FailedIconLoad& failedLoads,
+ const CustomGridTableRim* gridDataTable,
+ const std::shared_ptr<zen::IconBuffer>& iconBuffer) :
+ failedLoads_(failedLoads),
+ m_gridDataTable(gridDataTable),
+ iconBuffer_(iconBuffer) {}
+
+
+ virtual void Draw(wxGrid& grid,
+ wxGridCellAttr& attr,
+ wxDC& dc,
+ const wxRect& rect, //unscrolled rect
+ int row, int col,
+ bool isSelected)
+ {
+ //############## show windows explorer file icons ######################
+
+ if (iconBuffer_.get() &&
+ m_gridDataTable->getTypeAtPos(col) == xmlAccess::FILENAME)
+ {
+ const int iconSize = iconBuffer_->getSize();
+ if (rect.GetWidth() >= iconSize)
+ {
+ // Partitioning:
+ // ____________________________
+ // | 2 pix border | icon | rest |
+ // ----------------------------
+ {
+ //clear area where icon will be placed (including border)
+ wxRect rectShrinked(rect);
+ rectShrinked.SetWidth(LEFT_BORDER + iconSize); //add 2 pixel border
+ wxGridCellRenderer::Draw(grid, attr, dc, rectShrinked, row, col, isSelected);
+ }
+
+ {
+ //draw rest
+ wxRect rest(rect); //unscrolled
+ rest.x += LEFT_BORDER + iconSize;
+ rest.width -= LEFT_BORDER + iconSize;
+ wxGridCellStringRenderer::Draw(grid, attr, dc, rest, row, col, isSelected);
+ }
+
+ wxRect rectIcon(rect);
+ rectIcon.SetWidth(iconSize); //set to icon area only
+ rectIcon.x += LEFT_BORDER; //
+
+ //try to draw icon
+ //retrieve grid data
+ const Zstring fileName = m_gridDataTable->getIconFile(row);
+ if (!fileName.empty())
+ {
+ wxIcon icon;
+
+ //first check if it is a directory icon:
+ if (fileName == Zstr("folder"))
+ icon = iconBuffer_->genericDirIcon();
+ else //retrieve file icon
+ {
+ if (!iconBuffer_->requestFileIcon(fileName, &icon)) //returns false if icon is not in buffer
+ {
+ icon = iconBuffer_->genericFileIcon(); //better than nothing
+
+ failedLoads_.insert(row); //save status of failed icon load -> used for async. icon loading
+ //falsify only! we want to avoid writing incorrect success values when only partially updating the DC, e.g. when scrolling,
+ //see repaint behavior of ::ScrollWindow() function!
+ }
+ }
+
+ if (icon.IsOk())
+ {
+ int posX = rectIcon.GetX();
+ int posY = rectIcon.GetY();
+ //center icon if it is too small
+ if (rectIcon.GetWidth() > icon.GetWidth())
+ posX += (rectIcon.GetWidth() - icon.GetWidth()) / 2;
+ if (rectIcon.GetHeight() > icon.GetHeight())
+ posY += (rectIcon.GetHeight() - icon.GetHeight()) / 2;
+
+ dc.DrawIcon(icon, posX, posY);
+ }
+ }
+ return;
+ }
+ }
+
+ //default
+ wxGridCellStringRenderer::Draw(grid, attr, dc, rect, row, col, isSelected);
+ }
+
+
+ virtual wxSize GetBestSize(wxGrid& grid, //adapt reported width if file icons are shown
+ wxGridCellAttr& attr,
+ wxDC& dc,
+ int row, int col)
+ {
+ if (iconBuffer_.get() && //evaluate at compile time
+ m_gridDataTable->getTypeAtPos(col) == xmlAccess::FILENAME)
+ {
+ wxSize rv = wxGridCellStringRenderer::GetBestSize(grid, attr, dc, row, col);
+ rv.SetWidth(rv.GetWidth() + LEFT_BORDER + iconBuffer_->getSize());
+ return rv;
+ }
+
+ //default
+ return wxGridCellStringRenderer::GetBestSize(grid, attr, dc, row, col);
+ }
+
+
+private:
+ CustomGridRim::FailedIconLoad& failedLoads_;
+ const CustomGridTableRim* const m_gridDataTable;
+ std::shared_ptr<zen::IconBuffer> iconBuffer_;
+
+ static const int LEFT_BORDER = 2;
+};
+
+//----------------------------------------------------------------------------------------
+
+CustomGridRim::CustomGridRim(wxWindow* parent,
+ wxWindowID id,
+ const wxPoint& pos,
+ const wxSize& size,
+ long style,
+ const wxString& name) :
+ CustomGrid(parent, id, pos, size, style, name), otherGrid(NULL)
+{
+ Connect(wxEVT_GRID_COL_SIZE, wxGridSizeEventHandler(CustomGridRim::OnResizeColumn), NULL, this); //row-based tooltip
+}
+
+
+void CustomGridRim::setOtherGrid(CustomGridRim* other) //call during initialization!
+{
+ otherGrid = other;
+}
+
+
+void CustomGridRim::OnResizeColumn(wxGridSizeEvent& event)
+{
+ //Resize columns on both sides in parallel
+ const int thisCol = event.GetRowOrCol();
+
+ if (!otherGrid || thisCol < 0 || thisCol >= GetNumberCols()) return;
+
+ const xmlAccess::ColumnTypes thisColType = getTypeAtPos(thisCol);
+
+ for (int i = 0; i < otherGrid->GetNumberCols(); ++i)
+ if (otherGrid->getTypeAtPos(i) == thisColType)
+ {
+ otherGrid->SetColSize(i, GetColSize(thisCol));
+ otherGrid->ForceRefresh();
+ break;
+ }
+}
+
+
+//this method is called when grid view changes: useful for parallel updating of multiple grids
+void CustomGridRim::alignOtherGrids(CustomGrid* gridLeft, CustomGrid* gridMiddle, CustomGrid* gridRight)
+{
+ if (!otherGrid) return;
+
+ int x = 0;
+ int y = 0;
+ GetViewStart(&x, &y);
+ gridMiddle->Scroll(-1, y);
+ otherGrid->Scroll(x, y);
+}
+
+
+template <SelectedSide side>
+void CustomGridRim::setTooltip(const wxMouseEvent& event)
+{
+ const int hoveredRow = mousePosToCell(event.GetPosition()).first;
+
+ wxString toolTip;
+ if (hoveredRow >= 0 && getGridDataTable() != NULL)
+ {
+ const FileSystemObject* const fsObj = getGridDataTable()->getRawData(hoveredRow);
+ if (fsObj && !fsObj->isEmpty<side>())
+ {
+ struct AssembleTooltip : public FSObjectVisitor
+ {
+ AssembleTooltip(wxString& tipMsg) : tipMsg_(tipMsg) {}
+
+ virtual void visit(const FileMapping& fileObj)
+ {
+ tipMsg_ = toWx(fileObj.getRelativeName<side>()) + "\n" +
+ _("Size") + ": " + zen::filesizeToShortString(fileObj.getFileSize<side>()) + "\n" +
+ _("Date") + ": " + zen::utcToLocalTimeString(fileObj.getLastWriteTime<side>());
+ }
+
+ virtual void visit(const SymLinkMapping& linkObj)
+ {
+ tipMsg_ = toWx(linkObj.getRelativeName<side>()) + "\n" +
+ _("Date") + ": " + zen::utcToLocalTimeString(linkObj.getLastWriteTime<side>());
+ }
+
+ virtual void visit(const DirMapping& dirObj)
+ {
+ tipMsg_ = toWx(dirObj.getRelativeName<side>());
+ }
+
+ wxString& tipMsg_;
+ } assembler(toolTip);
+ fsObj->accept(assembler);
+ }
+ }
+
+
+ wxToolTip* tt = GetGridWindow()->GetToolTip();
+
+ const wxString currentTip = tt ? tt->GetTip() : wxString();
+ if (toolTip != currentTip)
+ {
+ if (toolTip.IsEmpty())
+ GetGridWindow()->SetToolTip(NULL); //wxGTK doesn't allow wxToolTip with empty text!
+ else
+ {
+ //wxWidgets bug: tooltip multiline property is defined by first tooltip text containing newlines or not (same is true for maximum width)
+ if (!tt)
+ GetGridWindow()->SetToolTip(new wxToolTip(wxT("a b\n\
+ a b"))); //ugly, but is working (on Windows)
+ tt = GetGridWindow()->GetToolTip(); //should be bound by now
+ if (tt)
+ tt->SetTip(toolTip);
+ }
+ }
+}
+
+
+xmlAccess::ColumnAttributes CustomGridRim::getDefaultColumnAttributes()
+{
+ xmlAccess::ColumnAttributes defaultColumnSettings;
+
+ xmlAccess::ColumnAttrib newEntry;
+ newEntry.type = xmlAccess::FULL_PATH;
+ newEntry.visible = false;
+ newEntry.position = 0;
+ newEntry.width = 150;
+ defaultColumnSettings.push_back(newEntry);
+
+ newEntry.type = xmlAccess::DIRECTORY;
+ newEntry.position = 1;
+ newEntry.width = 140;
+ defaultColumnSettings.push_back(newEntry);
+
+ newEntry.type = xmlAccess::REL_PATH;
+ newEntry.visible = true;
+ newEntry.position = 2;
+ newEntry.width = 118;
+ defaultColumnSettings.push_back(newEntry);
+
+ newEntry.type = xmlAccess::FILENAME;
+ newEntry.position = 3;
+ newEntry.width = 138;
+ defaultColumnSettings.push_back(newEntry);
+
+ newEntry.type = xmlAccess::SIZE;
+ newEntry.position = 4;
+ newEntry.width = 70;
+ defaultColumnSettings.push_back(newEntry);
+
+ newEntry.type = xmlAccess::DATE;
+ newEntry.position = 5;
+ newEntry.width = 113;
+ defaultColumnSettings.push_back(newEntry);
+
+ newEntry.type = xmlAccess::EXTENSION;
+ newEntry.visible = false;
+ newEntry.position = 6;
+ newEntry.width = 60;
+ defaultColumnSettings.push_back(newEntry);
+
+ return defaultColumnSettings;
+}
+
+
+xmlAccess::ColumnAttributes CustomGridRim::getColumnAttributes()
+{
+ std::sort(columnSettings.begin(), columnSettings.end(), xmlAccess::sortByPositionAndVisibility);
+
+ xmlAccess::ColumnAttributes output;
+ xmlAccess::ColumnAttrib newEntry;
+ for (size_t i = 0; i < columnSettings.size(); ++i)
+ {
+ newEntry = columnSettings[i];
+ if (newEntry.visible)
+ newEntry.width = GetColSize(static_cast<int>(i)); //hidden columns are sorted to the end of vector!
+ output.push_back(newEntry);
+ }
+
+ return output;
+}
+
+
+void CustomGridRim::setColumnAttributes(const xmlAccess::ColumnAttributes& attr)
+{
+ //remove special alignment for column "size"
+ if (GetLayoutDirection() != wxLayout_RightToLeft) //don't change for RTL languages
+ for (int i = 0; i < GetNumberCols(); ++i)
+ if (getTypeAtPos(i) == xmlAccess::SIZE)
+ {
+ wxGridCellAttr* cellAttributes = GetOrCreateCellAttr(0, i);
+ cellAttributes->SetAlignment(wxALIGN_LEFT, wxALIGN_CENTRE);
+ SetColAttr(i, cellAttributes);
+ break;
+ }
+ //----------------------------------------------------------------------------------
+
+ columnSettings.clear();
+ if (attr.size() == 0)
+ {
+ //default settings:
+ columnSettings = getDefaultColumnAttributes();
+ }
+ else
+ {
+ for (size_t i = 0; i < xmlAccess::COLUMN_TYPE_COUNT; ++i)
+ {
+ xmlAccess::ColumnAttrib newEntry;
+
+ if (i < attr.size())
+ newEntry = attr[i];
+ else //fix corrupted data:
+ {
+ newEntry.type = static_cast<xmlAccess::ColumnTypes>(xmlAccess::COLUMN_TYPE_COUNT); //sort additional rows to the end
+ newEntry.visible = false;
+ newEntry.position = i;
+ newEntry.width = 100;
+ }
+ columnSettings.push_back(newEntry);
+ }
+
+ std::sort(columnSettings.begin(), columnSettings.end(), xmlAccess::sortByType);
+ for (size_t i = 0; i < xmlAccess::COLUMN_TYPE_COUNT; ++i) //just be sure that each type exists only once
+ columnSettings[i].type = static_cast<xmlAccess::ColumnTypes>(i);
+
+ std::sort(columnSettings.begin(), columnSettings.end(), xmlAccess::sortByPositionOnly);
+ for (size_t i = 0; i < xmlAccess::COLUMN_TYPE_COUNT; ++i) //just be sure that positions are numbered correctly
+ columnSettings[i].position = i;
+ }
+
+ std::sort(columnSettings.begin(), columnSettings.end(), xmlAccess::sortByPositionAndVisibility);
+ std::vector<xmlAccess::ColumnTypes> newPositions;
+ for (size_t i = 0; i < columnSettings.size() && columnSettings[i].visible; ++i) //hidden columns are sorted to the end of vector!
+ newPositions.push_back(columnSettings[i].type);
+
+ //set column positions
+ if (getGridDataTableRim())
+ getGridDataTableRim()->setupColumns(newPositions);
+
+ //set column width (set them after setupColumns!)
+ for (size_t i = 0; i < newPositions.size(); ++i)
+ SetColSize(static_cast<int>(i), columnSettings[i].width);
+
+ //--------------------------------------------------------------------------------------------------------
+ //set special alignment for column "size"
+ if (GetLayoutDirection() != wxLayout_RightToLeft) //don't change for RTL languages
+ for (int i = 0; i < GetNumberCols(); ++i)
+ if (getTypeAtPos(i) == xmlAccess::SIZE)
+ {
+ wxGridCellAttr* cellAttributes = GetOrCreateCellAttr(0, i);
+ cellAttributes->SetAlignment(wxALIGN_RIGHT, wxALIGN_CENTRE);
+ SetColAttr(i, cellAttributes); //make filesize right justified on grids
+ break;
+ }
+
+ ClearSelection();
+ ForceRefresh();
+}
+
+
+xmlAccess::ColumnTypes CustomGridRim::getTypeAtPos(size_t pos) const
+{
+ if (getGridDataTableRim())
+ return getGridDataTableRim()->getTypeAtPos(pos);
+ else
+ return xmlAccess::DIRECTORY;
+}
+
+
+wxString CustomGridRim::getTypeName(xmlAccess::ColumnTypes colType)
+{
+ switch (colType)
+ {
+ case xmlAccess::FULL_PATH:
+ return _("Full path");
+ case xmlAccess::FILENAME:
+ return _("Filename");
+ case xmlAccess::REL_PATH:
+ return _("Relative path");
+ case xmlAccess::DIRECTORY:
+ return _("Directory");
+ case xmlAccess::SIZE:
+ return _("Size");
+ case xmlAccess::DATE:
+ return _("Date");
+ case xmlAccess::EXTENSION:
+ return _("Extension");
+ }
+
+ return wxEmptyString; //dummy
+}
+
+
+void CustomGridRim::autoSizeColumns() //performance optimized column resizer (analog to wxGrid::AutoSizeColumns()
+{
+ for (int col = 0; col < GetNumberCols(); ++col)
+ {
+ if (col < 0)
+ return;
+
+ int rowMax = -1;
+ size_t lenMax = 0;
+ for (int row = 0; row < GetNumberRows(); ++row)
+ if (GetCellValue(row, col).size() > lenMax)
+ {
+ lenMax = GetCellValue(row, col).size();
+ rowMax = row;
+ }
+
+ wxCoord extentMax = 0;
+
+ //calculate width of (most likely) widest cell
+ wxClientDC dc(GetGridWindow());
+ if (rowMax > -1)
+ {
+ wxGridCellAttr* attr = GetCellAttr(rowMax, col);
+ if (attr)
+ {
+ wxGridCellRenderer* renderer = attr->GetRenderer(this, rowMax, col);
+ if (renderer)
+ {
+ const wxSize size = renderer->GetBestSize(*this, *attr, dc, rowMax, col);
+ extentMax = std::max(extentMax, size.x);
+ renderer->DecRef();
+ }
+ attr->DecRef();
+ }
+ }
+
+ //consider column label
+ dc.SetFont(GetLabelFont());
+ wxCoord w = 0;
+ wxCoord h = 0;
+ dc.GetMultiLineTextExtent(GetColLabelValue(col), &w, &h );
+ if (GetColLabelTextOrientation() == wxVERTICAL)
+ w = h;
+ extentMax = std::max(extentMax, w);
+
+ extentMax += 15; //leave some space around text
+
+ SetColSize(col, extentMax);
+
+ }
+ Refresh();
+}
+
+
+void CustomGridRim::enableFileIcons(const std::shared_ptr<IconBuffer>& iconBuffer)
+{
+ iconBuffer_ = iconBuffer;
+ SetDefaultRenderer(new GridCellRenderer(failedLoads, getGridDataTableRim(), iconBuffer)); //SetDefaultRenderer takes ownership!
+}
+
+
+std::pair<CustomGridRim::RowBegin, CustomGridRim::RowEnd> CustomGridRim::getVisibleRows()
+{
+ int dummy = -1;
+ int height = -1;
+ GetGridWindow()->GetClientSize(&dummy, &height);
+
+ if (height >= 0)
+ {
+ const int rowTop = mousePosToCell(wxPoint(0, 0)).first;
+ int rowEnd = mousePosToCell(wxPoint(0, height)).first;
+ if (rowEnd == -1) //when scrolling to the very end, there are a few border pixels that do not belong to any row
+ rowEnd = GetNumberRows();
+ else
+ ++rowEnd;
+
+ if (0 <= rowTop && rowTop <= rowEnd)
+ return std::make_pair(rowTop, rowEnd); //"top" means here top of the screen => smaller value
+ }
+ return std::make_pair(0, 0);
+}
+
+
+inline
+CustomGridTableRim* CustomGridRim::getGridDataTableRim() const
+{
+ return dynamic_cast<CustomGridTableRim*>(getGridDataTable()); //I'm tempted to use a static cast here...
+}
+
+
+void CustomGridRim::getIconsToBeLoaded(std::vector<Zstring>& newLoad) //loads all (not yet) drawn icons
+{
+ //don't check too often! give worker thread some time to fetch data
+
+ newLoad.clear();
+
+ if (iconBuffer_.get())
+ {
+ const CustomGridTableRim* gridDataTable = getGridDataTableRim();
+ if (!gridDataTable) return;
+
+ const int totalCols = const_cast<CustomGridTableRim*>(gridDataTable)->GetNumberCols();
+ const int totalRows = const_cast<CustomGridTableRim*>(gridDataTable)->GetNumberRows();
+
+ //determine column
+ const int colFilename = [&]() -> int
+ {
+ for (int k = 0; k < totalCols; ++k)
+ if (gridDataTable->getTypeAtPos(k) == xmlAccess::FILENAME)
+ return k;
+ return -1;
+ }();
+ if (colFilename < 0)
+ return;
+
+ const auto rowsOnScreen = getVisibleRows();
+
+ //loop over all visible rows
+ const int firstRow = static_cast<int>(rowsOnScreen.first);
+ const int rowNo = std::min(static_cast<int>(rowsOnScreen.second), totalRows) - firstRow;
+
+ for (int i = 0; i < rowNo; ++i)
+ {
+ //alternate when adding rows: first, last, first + 1, last - 1 ... -> Icon buffer will then load reversely, i.e. from inside out
+ const int currentRow = firstRow + (i % 2 == 0 ?
+ i / 2 :
+ rowNo - 1 - (i - 1) / 2);
+
+ if (failedLoads.find(currentRow) != failedLoads.end()) //find failed attempts to load icon
+ {
+ const Zstring fileName = gridDataTable->getIconFile(currentRow);
+ if (!fileName.empty())
+ {
+ //test if they are already loaded in buffer:
+ if (iconBuffer_->requestFileIcon(fileName))
+ {
+ //exists in buffer: refresh Row
+ RefreshCell(currentRow, colFilename); //do a *full* refresh for *every* failed load to update partial DC updates while scrolling
+ failedLoads.erase(currentRow); //
+ }
+ else //not yet in buffer: mark for async. loading
+ {
+ newLoad.push_back(fileName);
+ }
+ }
+ }
+ }
+ }
+}
+
+//----------------------------------------------------------------------------------------
+
+
+//update file icons periodically: use SINGLE instance to coordinate left and right grid at once
+IconUpdater::IconUpdater(CustomGridLeft* leftGrid, CustomGridRight* rightGrid) :
+ m_leftGrid(leftGrid),
+ m_rightGrid(rightGrid),
+ m_timer(new wxTimer) //connect timer event for async. icon loading
+{
+ m_timer->Connect(wxEVT_TIMER, wxEventHandler(IconUpdater::loadIconsAsynchronously), NULL, this);
+ m_timer->Start(50); //timer interval in ms
+}
+
+
+IconUpdater::~IconUpdater() {}
+
+
+void IconUpdater::loadIconsAsynchronously(wxEvent& event) //loads all (not yet) drawn icons
+{
+ std::vector<Zstring> iconsLeft;
+ m_leftGrid->getIconsToBeLoaded(iconsLeft);
+
+ std::vector<Zstring> newLoad;
+ m_rightGrid->getIconsToBeLoaded(newLoad);
+
+ //merge vectors
+ newLoad.insert(newLoad.end(), iconsLeft.begin(), iconsLeft.end());
+
+ if (m_leftGrid->iconBuffer_.get())
+ m_leftGrid->iconBuffer_->setWorkload(newLoad);
+
+ //event.Skip();
+}
+
+//----------------------------------------------------------------------------------------
+
+
+CustomGridLeft::CustomGridLeft(wxWindow* parent,
+ wxWindowID id,
+ const wxPoint& pos,
+ const wxSize& size,
+ long style,
+ const wxString& name) :
+ CustomGridRim(parent, id, pos, size, style, name)
+{
+ GetGridWindow()->Connect(wxEVT_MOTION, wxMouseEventHandler(CustomGridLeft::OnMouseMovement), NULL, this); //row-based tooltip
+}
+
+
+bool CustomGridLeft::CreateGrid(int numRows, int numCols, wxGrid::wxGridSelectionModes selmode)
+{
+ //use custom wxGridTableBase class for management of large sets of formatted data.
+ //This is done in CreateGrid instead of SetTable method since source code is generated and wxFormbuilder invokes CreatedGrid by default.
+ SetTable(new CustomGridTableLeft, true, wxGrid::wxGridSelectRows); //give ownership to wxGrid: gridDataTable is deleted automatically in wxGrid destructor
+ return true;
+}
+
+
+void CustomGridLeft::initSettings(CustomGridLeft* gridLeft, //create connection with zen::GridView
+ CustomGridMiddle* gridMiddle,
+ CustomGridRight* gridRight,
+ const zen::GridView* gridDataView)
+{
+ //set underlying grid data
+ assert(getGridDataTable());
+ getGridDataTable()->setGridDataTable(gridDataView);
+
+ CustomGridRim::setOtherGrid(gridRight);
+
+ CustomGridRim::initSettings(gridLeft, gridMiddle, gridRight, gridDataView);
+}
+
+
+void CustomGridLeft::OnMouseMovement(wxMouseEvent& event)
+{
+ CustomGridRim::setTooltip<LEFT_SIDE>(event);
+ event.Skip();
+}
+
+
+CustomGridTable* CustomGridLeft::getGridDataTable() const
+{
+ return static_cast<CustomGridTable*>(GetTable()); //one of the few cases where no dynamic_cast is required!
+}
+
+
+//----------------------------------------------------------------------------------------
+CustomGridRight::CustomGridRight(wxWindow* parent,
+ wxWindowID id,
+ const wxPoint& pos,
+ const wxSize& size,
+ long style,
+ const wxString& name) :
+ CustomGridRim(parent, id, pos, size, style, name)
+{
+ GetGridWindow()->Connect(wxEVT_MOTION, wxMouseEventHandler(CustomGridRight::OnMouseMovement), NULL, this); //row-based tooltip
+}
+
+
+bool CustomGridRight::CreateGrid(int numRows, int numCols, wxGrid::wxGridSelectionModes selmode)
+{
+ SetTable(new CustomGridTableRight, true, wxGrid::wxGridSelectRows); //give ownership to wxGrid: gridDataTable is deleted automatically in wxGrid destructor
+ return true;
+}
+
+
+void CustomGridRight::initSettings(CustomGridLeft* gridLeft, //create connection with zen::GridView
+ CustomGridMiddle* gridMiddle,
+ CustomGridRight* gridRight,
+ const zen::GridView* gridDataView)
+{
+ //set underlying grid data
+ assert(getGridDataTable());
+ getGridDataTable()->setGridDataTable(gridDataView);
+
+ CustomGridRim::setOtherGrid(gridLeft);
+
+ CustomGridRim::initSettings(gridLeft, gridMiddle, gridRight, gridDataView);
+}
+
+
+void CustomGridRight::OnMouseMovement(wxMouseEvent& event)
+{
+ CustomGridRim::setTooltip<RIGHT_SIDE>(event);
+ event.Skip();
+}
+
+
+CustomGridTable* CustomGridRight::getGridDataTable() const
+{
+ return static_cast<CustomGridTable*>(GetTable()); //one of the few cases where no dynamic_cast is required!
+}
+
+
+//----------------------------------------------------------------------------------------
+class GridCellRendererMiddle : public wxGridCellStringRenderer
+{
+public:
+ GridCellRendererMiddle(const CustomGridMiddle& middleGrid) : m_gridMiddle(middleGrid) {};
+
+ virtual void Draw(wxGrid& grid,
+ wxGridCellAttr& attr,
+ wxDC& dc,
+ const wxRect& rect,
+ int row, int col,
+ bool isSelected);
+
+private:
+ const CustomGridMiddle& m_gridMiddle;
+};
+
+
+//define new event types
+const wxEventType FFS_CHECK_ROWS_EVENT = wxNewEventType(); //attention! do NOT place in header to keep (generated) id unique!
+const wxEventType FFS_SYNC_DIRECTION_EVENT = wxNewEventType();
+
+const int CHECK_BOX_IMAGE = 11; //width of checkbox image
+const int CHECK_BOX_WIDTH = CHECK_BOX_IMAGE + 3; //width of first block
+
+// cell:
+// ----------------------------------
+// | checkbox | left | middle | right|
+// ----------------------------------
+
+
+CustomGridMiddle::CustomGridMiddle(wxWindow* parent,
+ wxWindowID id,
+ const wxPoint& pos,
+ const wxSize& size,
+ long style,
+ const wxString& name) :
+ CustomGrid(parent, id, pos, size, style, name),
+ selectionRowBegin(-1),
+ selectionPos(BLOCKPOS_CHECK_BOX),
+ highlightedRow(-1),
+ highlightedPos(BLOCKPOS_CHECK_BOX)
+{
+ SetLayoutDirection(wxLayout_LeftToRight); //
+ GetGridWindow ()->SetLayoutDirection(wxLayout_LeftToRight); //avoid mirroring this dialog in RTL languages like Hebrew or Arabic
+ GetGridColLabelWindow()->SetLayoutDirection(wxLayout_LeftToRight); //
+
+ //connect events for dynamic selection of sync direction
+ GetGridWindow()->Connect(wxEVT_MOTION, wxMouseEventHandler(CustomGridMiddle::OnMouseMovement), NULL, this);
+ GetGridWindow()->Connect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler(CustomGridMiddle::OnLeaveWindow), NULL, this);
+ GetGridWindow()->Connect(wxEVT_LEFT_UP, wxMouseEventHandler(CustomGridMiddle::OnLeftMouseUp), NULL, this);
+ GetGridWindow()->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(CustomGridMiddle::OnLeftMouseDown), NULL, this);
+}
+
+
+CustomGridMiddle::~CustomGridMiddle() {}
+
+
+bool CustomGridMiddle::CreateGrid(int numRows, int numCols, wxGrid::wxGridSelectionModes selmode)
+{
+ SetTable(new CustomGridTableMiddle, true, wxGrid::wxGridSelectRows); //give ownership to wxGrid: gridDataTable is deleted automatically in wxGrid destructor
+
+ //display checkboxes (representing bool values) if row is enabled for synchronization
+ SetDefaultRenderer(new GridCellRendererMiddle(*this)); //SetDefaultRenderer takes ownership!
+
+ return true;
+}
+
+
+void CustomGridMiddle::initSettings(CustomGridLeft* gridLeft, //create connection with zen::GridView
+ CustomGridMiddle* gridMiddle,
+ CustomGridRight* gridRight,
+ const zen::GridView* gridDataView)
+{
+ //set underlying grid data
+ assert(getGridDataTable());
+ getGridDataTable()->setGridDataTable(gridDataView);
+
+#ifdef FFS_LINUX //get rid of scrollbars; Linux: change policy for GtkScrolledWindow
+ GtkWidget* gridWidget = wxWindow::m_widget;
+ GtkScrolledWindow* scrolledWindow = GTK_SCROLLED_WINDOW(gridWidget);
+ gtk_scrolled_window_set_policy(scrolledWindow, GTK_POLICY_NEVER, GTK_POLICY_NEVER);
+#endif
+
+ CustomGrid::initSettings(gridLeft, gridMiddle, gridRight, gridDataView);
+}
+
+
+CustomGridTable* CustomGridMiddle::getGridDataTable() const
+{
+ return static_cast<CustomGridTable*>(GetTable()); //one of the few cases where no dynamic_cast is required!
+}
+
+
+inline
+CustomGridTableMiddle* CustomGridMiddle::getGridDataTableMiddle() const
+{
+ return static_cast<CustomGridTableMiddle*>(getGridDataTable());
+}
+
+
+#ifdef FFS_WIN //get rid of scrollbars; Windows: overwrite virtual method
+void CustomGridMiddle::SetScrollbar(int orientation, int position, int thumbSize, int range, bool refresh)
+{
+ wxWindow::SetScrollbar(orientation, 0, 0, 0, refresh);
+}
+#endif
+
+
+void CustomGridMiddle::OnMouseMovement(wxMouseEvent& event)
+{
+ const int rowOld = highlightedRow;
+ const BlockPosition posOld = highlightedPos;
+
+
+ if (selectionRowBegin == -1) //change highlightning only if currently not dragging mouse
+ {
+ highlightedRow = mousePosToRow(event.GetPosition(), &highlightedPos);
+
+ if (rowOld != highlightedRow)
+ {
+ RefreshCell(highlightedRow, 0);
+ RefreshCell(rowOld, 0);
+ }
+ else if (posOld != highlightedPos)
+ RefreshCell(highlightedRow, 0);
+
+ //handle tooltip
+ showToolTip(highlightedRow, GetGridWindow()->ClientToScreen(event.GetPosition()));
+ }
+
+ event.Skip();
+}
+
+
+void CustomGridMiddle::OnLeaveWindow(wxMouseEvent& event)
+{
+ highlightedRow = -1;
+ highlightedPos = BLOCKPOS_CHECK_BOX;
+ Refresh();
+
+ //handle tooltip
+ toolTip.hide();
+}
+
+
+void CustomGridMiddle::showToolTip(int rowNumber, wxPoint pos)
+{
+ if (!getGridDataTableMiddle())
+ return;
+
+ const FileSystemObject* const fsObj = getGridDataTableMiddle()->getRawData(rowNumber);
+ if (fsObj == NULL) //if invalid row...
+ {
+ toolTip.hide();
+ return;
+ }
+
+ if (getGridDataTableMiddle()->syncPreviewIsActive()) //synchronization preview
+ {
+ const SyncOperation syncOp = fsObj->getSyncOperation();
+ switch (syncOp)
+ {
+ case SO_CREATE_NEW_LEFT:
+ toolTip.show(getDescription(syncOp), pos, &GlobalResources::getImage(wxT("createLeft")));
+ break;
+ case SO_CREATE_NEW_RIGHT:
+ toolTip.show(getDescription(syncOp), pos, &GlobalResources::getImage(wxT("createRight")));
+ break;
+ case SO_DELETE_LEFT:
+ toolTip.show(getDescription(syncOp), pos, &GlobalResources::getImage(wxT("deleteLeft")));
+ break;
+ case SO_DELETE_RIGHT:
+ toolTip.show(getDescription(syncOp), pos, &GlobalResources::getImage(wxT("deleteRight")));
+ break;
+ case SO_OVERWRITE_LEFT:
+ case SO_COPY_METADATA_TO_LEFT:
+ toolTip.show(getDescription(syncOp), pos, &GlobalResources::getImage(wxT("updateLeft")));
+ break;
+ case SO_OVERWRITE_RIGHT:
+ case SO_COPY_METADATA_TO_RIGHT:
+ toolTip.show(getDescription(syncOp), pos, &GlobalResources::getImage(wxT("updateRight")));
+ break;
+ case SO_DO_NOTHING:
+ toolTip.show(getDescription(syncOp), pos, &GlobalResources::getImage(wxT("none")));
+ break;
+ case SO_EQUAL:
+ toolTip.show(getDescription(syncOp), pos, &GlobalResources::getImage(wxT("equal")));
+ break;
+ case SO_UNRESOLVED_CONFLICT:
+ toolTip.show(fsObj->getSyncOpConflict(), pos, &GlobalResources::getImage(wxT("conflict")));
+ break;
+ };
+ }
+ else
+ {
+ const CompareFilesResult cmpRes = fsObj->getCategory();
+ switch (cmpRes)
+ {
+ case FILE_LEFT_SIDE_ONLY:
+ toolTip.show(getDescription(cmpRes), pos, &GlobalResources::getImage(wxT("leftOnly")));
+ break;
+ case FILE_RIGHT_SIDE_ONLY:
+ toolTip.show(getDescription(cmpRes), pos, &GlobalResources::getImage(wxT("rightOnly")));
+ break;
+ case FILE_LEFT_NEWER:
+ toolTip.show(getDescription(cmpRes), pos, &GlobalResources::getImage(wxT("leftNewer")));
+ break;
+ case FILE_RIGHT_NEWER:
+ toolTip.show(getDescription(cmpRes), pos, &GlobalResources::getImage(wxT("rightNewer")));
+ break;
+ case FILE_DIFFERENT:
+ toolTip.show(getDescription(cmpRes), pos, &GlobalResources::getImage(wxT("different")));
+ break;
+ case FILE_EQUAL:
+ toolTip.show(getDescription(cmpRes), pos, &GlobalResources::getImage(wxT("equal")));
+ break;
+ case FILE_DIFFERENT_METADATA:
+ toolTip.show(getDescription(cmpRes), pos, &GlobalResources::getImage(wxT("conflict")));
+ break;
+ case FILE_CONFLICT:
+ toolTip.show(fsObj->getCatConflict(), pos, &GlobalResources::getImage(wxT("conflict")));
+ break;
+ }
+ }
+}
+
+
+void CustomGridMiddle::OnLeftMouseDown(wxMouseEvent& event)
+{
+ selectionRowBegin = mousePosToRow(event.GetPosition(), &selectionPos);
+ event.Skip();
+}
+
+
+void CustomGridMiddle::OnLeftMouseUp(wxMouseEvent& event)
+{
+ //int selRowEnd = mousePosToCell(event.GetPosition()).first;
+ //-> use visibly marked rows instead! with wxWidgets 2.8.12 there is no other way than IsInSelection()
+ int selRowEnd = -1;
+ if (0 <= selectionRowBegin && selectionRowBegin < GetNumberRows())
+ {
+ for (int i = selectionRowBegin; i < GetNumberRows() && IsInSelection(i, 0); ++i)
+ selRowEnd = i;
+
+ if (selRowEnd == selectionRowBegin)
+ for (int i = selectionRowBegin; i >= 0 && IsInSelection(i, 0); --i)
+ selRowEnd = i;
+ }
+
+ if (0 <= selectionRowBegin && 0 <= selRowEnd)
+ {
+ switch (selectionPos)
+ {
+ case BLOCKPOS_CHECK_BOX:
+ {
+ //create a custom event
+ FFSCheckRowsEvent evt(selectionRowBegin, selRowEnd);
+ AddPendingEvent(evt);
+ }
+ break;
+ case BLOCKPOS_LEFT:
+ {
+ //create a custom event
+ FFSSyncDirectionEvent evt(selectionRowBegin, selRowEnd, SYNC_DIR_LEFT);
+ AddPendingEvent(evt);
+ }
+ break;
+ case BLOCKPOS_MIDDLE:
+ {
+ //create a custom event
+ FFSSyncDirectionEvent evt(selectionRowBegin, selRowEnd, SYNC_DIR_NONE);
+ AddPendingEvent(evt);
+ }
+ break;
+ case BLOCKPOS_RIGHT:
+ {
+ //create a custom event
+ FFSSyncDirectionEvent evt(selectionRowBegin, selRowEnd, SYNC_DIR_RIGHT);
+ AddPendingEvent(evt);
+ }
+ break;
+ }
+ }
+ selectionRowBegin = -1;
+ selectionPos = BLOCKPOS_CHECK_BOX;
+
+ ClearSelection();
+ event.Skip();
+}
+
+
+int CustomGridMiddle::mousePosToRow(wxPoint pos, BlockPosition* block)
+{
+ if (!getGridDataTableMiddle())
+ return 0;
+
+ int row = -1;
+ int x = -1;
+ int y = -1;
+ CalcUnscrolledPosition( pos.x, pos.y, &x, &y );
+ if (x >= 0 && y >= 0)
+ {
+ row = YToRow(y);
+
+ //determine blockposition within cell (optional)
+ if (block)
+ {
+ *block = BLOCKPOS_CHECK_BOX; //default
+
+ if (row >= 0)
+ {
+ const FileSystemObject* const fsObj = getGridDataTableMiddle()->getRawData(row);
+ if (fsObj != NULL && //if valid row...
+ getGridDataTableMiddle()->syncPreviewIsActive() &&
+ fsObj->getSyncOperation() != SO_EQUAL) //in sync-preview equal files shall be treated as in cmp result, i.e. as full checkbox
+ {
+ // cell:
+ // ----------------------------------
+ // | checkbox | left | middle | right|
+ // ----------------------------------
+
+ const wxRect rect = CellToRect(row, 0);
+ if (rect.GetWidth() > CHECK_BOX_WIDTH)
+ {
+ const double blockWidth = (rect.GetWidth() - CHECK_BOX_WIDTH) / 3.0;
+ if (rect.GetX() + CHECK_BOX_WIDTH <= x && x < rect.GetX() + rect.GetWidth())
+ {
+ if (x - (rect.GetX() + CHECK_BOX_WIDTH) < blockWidth)
+ *block = BLOCKPOS_LEFT;
+ else if (x - (rect.GetX() + CHECK_BOX_WIDTH) < 2 * blockWidth)
+ *block = BLOCKPOS_MIDDLE;
+ else
+ *block = BLOCKPOS_RIGHT;
+ }
+ }
+ }
+ }
+ }
+ }
+ return row;
+}
+
+
+void CustomGridMiddle::enableSyncPreview(bool value)
+{
+ assert(getGridDataTableMiddle());
+ getGridDataTableMiddle()->enableSyncPreview(value);
+
+ if (value)
+ GetGridColLabelWindow()->SetToolTip(_("Synchronization Preview"));
+ else
+ GetGridColLabelWindow()->SetToolTip(_("Comparison Result"));
+}
+
+
+void GridCellRendererMiddle::Draw(wxGrid& grid,
+ wxGridCellAttr& attr,
+ wxDC& dc,
+ const wxRect& rect,
+ int row, int col,
+ bool isSelected)
+{
+ //retrieve grid data
+ const FileSystemObject* const fsObj = m_gridMiddle.getGridDataTableMiddle() ? m_gridMiddle.getGridDataTableMiddle()->getRawData(row) : NULL;
+ if (fsObj != NULL) //if valid row...
+ {
+ if (rect.GetWidth() > CHECK_BOX_WIDTH)
+ {
+ const bool rowIsHighlighted = row == m_gridMiddle.highlightedRow;
+
+ wxRect rectShrinked(rect);
+
+ //clean first block of rect that will receive image of checkbox
+ rectShrinked.SetWidth(CHECK_BOX_WIDTH);
+ wxGridCellRenderer::Draw(grid, attr, dc, rectShrinked, row, col, isSelected);
+
+ //print checkbox into first block
+ rectShrinked.SetX(rect.GetX() + 1);
+
+ //HIGHLIGHTNING (checkbox):
+ if (rowIsHighlighted &&
+ m_gridMiddle.highlightedPos == CustomGridMiddle::BLOCKPOS_CHECK_BOX)
+ {
+ dc.DrawLabel(wxEmptyString, GlobalResources::getImage(fsObj->isActive() ?
+ wxT("checkboxTrueFocus") :
+ wxT("checkboxFalseFocus")), rectShrinked, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
+ }
+ else //default
+ dc.DrawLabel(wxEmptyString, GlobalResources::getImage(fsObj->isActive() ?
+ wxT("checkboxTrue") :
+ wxT("checkboxFalse")), rectShrinked, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
+
+ //clean remaining block of rect that will receive image of checkbox/directions
+ rectShrinked.SetWidth(rect.GetWidth() - CHECK_BOX_WIDTH);
+ rectShrinked.SetX(rect.GetX() + CHECK_BOX_WIDTH);
+ wxGridCellRenderer::Draw(grid, attr, dc, rectShrinked, row, col, isSelected);
+
+ //print remaining block
+ if (m_gridMiddle.getGridDataTableMiddle()->syncPreviewIsActive()) //synchronization preview
+ {
+ //print sync direction into second block
+
+ //HIGHLIGHTNING (sync direction):
+ if (rowIsHighlighted &&
+ m_gridMiddle.highlightedPos != CustomGridMiddle::BLOCKPOS_CHECK_BOX) //don't allow changing direction for "=="-files
+ switch (m_gridMiddle.highlightedPos)
+ {
+ case CustomGridMiddle::BLOCKPOS_CHECK_BOX:
+ break;
+ case CustomGridMiddle::BLOCKPOS_LEFT:
+ dc.DrawLabel(wxEmptyString, getSyncOpImage(fsObj->testSyncOperation(SYNC_DIR_LEFT)), rectShrinked, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
+ break;
+ case CustomGridMiddle::BLOCKPOS_MIDDLE:
+ dc.DrawLabel(wxEmptyString, getSyncOpImage(fsObj->testSyncOperation(SYNC_DIR_NONE)), rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
+ break;
+ case CustomGridMiddle::BLOCKPOS_RIGHT:
+ dc.DrawLabel(wxEmptyString, getSyncOpImage(fsObj->testSyncOperation(SYNC_DIR_RIGHT)), rectShrinked, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL);
+ break;
+ }
+ else //default
+ {
+ const wxBitmap& syncOpIcon = getSyncOpImage(fsObj->getSyncOperation());
+ dc.DrawLabel(wxEmptyString, syncOpIcon, rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
+ }
+ }
+ else //comparison results view
+ {
+ switch (fsObj->getCategory())
+ {
+ case FILE_LEFT_SIDE_ONLY:
+ dc.DrawLabel(wxEmptyString, GlobalResources::getImage(wxT("leftOnlySmall")), rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
+ break;
+ case FILE_RIGHT_SIDE_ONLY:
+ dc.DrawLabel(wxEmptyString, GlobalResources::getImage(wxT("rightOnlySmall")), rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
+ break;
+ case FILE_LEFT_NEWER:
+ dc.DrawLabel(wxEmptyString, GlobalResources::getImage(wxT("leftNewerSmall")), rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
+ break;
+ case FILE_RIGHT_NEWER:
+ dc.DrawLabel(wxEmptyString, GlobalResources::getImage(wxT("rightNewerSmall")), rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
+ break;
+ case FILE_DIFFERENT:
+ dc.DrawLabel(wxEmptyString, GlobalResources::getImage(wxT("differentSmall")), rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
+ break;
+ case FILE_EQUAL:
+ dc.DrawLabel(wxEmptyString, GlobalResources::getImage(wxT("equalSmall")), rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
+ break;
+ case FILE_CONFLICT:
+ case FILE_DIFFERENT_METADATA:
+ dc.DrawLabel(wxEmptyString, GlobalResources::getImage(wxT("conflictSmall")), rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
+ break;
+ }
+ }
+
+ return;
+ }
+ }
+
+ //fallback
+ wxGridCellStringRenderer::Draw(grid, attr, dc, rect, row, col, isSelected);
+}
+
+
+//this method is called when grid view changes: useful for parallel updating of multiple grids
+void CustomGridMiddle::alignOtherGrids(CustomGrid* gridLeft, CustomGrid* gridMiddle, CustomGrid* gridRight)
+{
+ int x = 0;
+ int y = 0;
+ GetViewStart(&x, &y);
+ gridLeft->Scroll(-1, y);
+ gridRight->Scroll(-1, y);
+}
+
+
+void CustomGridMiddle::DrawColLabel(wxDC& dc, int col)
+{
+ CustomGrid::DrawColLabel(dc, col);
+
+ if (!getGridDataTableMiddle())
+ return;
+
+ const wxRect rect(GetColLeft(col), 0, GetColWidth(col), GetColLabelSize());
+
+ if (getGridDataTableMiddle()->syncPreviewIsActive())
+ dc.DrawLabel(wxEmptyString, GlobalResources::getImage(wxT("syncViewSmall")), rect, wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL);
+ else
+ dc.DrawLabel(wxEmptyString, GlobalResources::getImage(wxT("cmpViewSmall")), rect, wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL);
+}
+
+
+const wxBitmap& zen::getSyncOpImage(SyncOperation syncOp)
+{
+ switch (syncOp) //evaluate comparison result and sync direction
+ {
+ case SO_CREATE_NEW_LEFT:
+ return GlobalResources::getImage(wxT("createLeftSmall"));
+ case SO_CREATE_NEW_RIGHT:
+ return GlobalResources::getImage(wxT("createRightSmall"));
+ case SO_DELETE_LEFT:
+ return GlobalResources::getImage(wxT("deleteLeftSmall"));
+ case SO_DELETE_RIGHT:
+ return GlobalResources::getImage(wxT("deleteRightSmall"));
+ case SO_OVERWRITE_RIGHT:
+ case SO_COPY_METADATA_TO_RIGHT:
+ return GlobalResources::getImage(wxT("updateRightSmall"));
+ case SO_OVERWRITE_LEFT:
+ case SO_COPY_METADATA_TO_LEFT:
+ return GlobalResources::getImage(wxT("updateLeftSmall"));
+ case SO_DO_NOTHING:
+ return GlobalResources::getImage(wxT("noneSmall"));
+ case SO_EQUAL:
+ return GlobalResources::getImage(wxT("equalSmall"));
+ case SO_UNRESOLVED_CONFLICT:
+ return GlobalResources::getImage(wxT("conflictSmall"));
+ }
+
+ return wxNullBitmap; //dummy
+}
+
diff --git a/lib/custom_grid.h b/lib/custom_grid.h
new file mode 100644
index 00000000..47aad3e6
--- /dev/null
+++ b/lib/custom_grid.h
@@ -0,0 +1,370 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef CUSTOMGRID_H_INCLUDED
+#define CUSTOMGRID_H_INCLUDED
+
+#include <vector>
+#include <wx/grid.h>
+#include <wx+/tooltip.h>
+#include "process_xml.h"
+#include <memory>
+#include <set>
+#include "../file_hierarchy.h"
+#include "icon_buffer.h"
+
+
+class CustomGridTable;
+class CustomGridTableRim;
+class CustomGridTableLeft;
+class CustomGridTableRight;
+class CustomGridTableMiddle;
+class GridCellRendererMiddle;
+class wxTimer;
+class CustomGridRim;
+class CustomGridLeft;
+class CustomGridMiddle;
+class CustomGridRight;
+
+
+namespace zen
+{
+class GridView;
+
+const wxBitmap& getSyncOpImage(SyncOperation syncOp);
+}
+//##################################################################################
+
+/*
+class hierarchy:
+ CustomGrid
+ /|\
+ ____________|____________
+ | |
+ CustomGridRim |
+ /|\ |
+ ________|_______ |
+ | | |
+CustomGridLeft CustomGridRight CustomGridMiddle
+*/
+
+class CustomGrid : public wxGrid
+{
+public:
+ CustomGrid(wxWindow* parent,
+ wxWindowID id,
+ const wxPoint& pos = wxDefaultPosition,
+ const wxSize& size = wxDefaultSize,
+ long style = wxWANTS_CHARS,
+ const wxString& name = wxGridNameStr);
+
+ virtual ~CustomGrid() {}
+
+ virtual void initSettings(CustomGridLeft* gridLeft, //create connection with zen::GridView
+ CustomGridMiddle* gridMiddle,
+ CustomGridRight* gridRight,
+ const zen::GridView* gridDataView);
+
+ void release(); //release connection to zen::GridView
+
+ std::set<size_t> getAllSelectedRows() const;
+
+ //set sort direction indicator on UI
+ typedef int SortColumn;
+
+ //notify wxGrid that underlying table size has changed
+ virtual void updateGridSizes();
+
+ enum SortDirection
+ {
+ ASCENDING,
+ DESCENDING
+ };
+ typedef std::pair<SortColumn, SortDirection> SortMarker;
+ void setSortMarker(SortMarker marker);
+
+ bool isLeadGrid() const;
+
+ void setIconManager(const std::shared_ptr<zen::IconBuffer>& iconBuffer);
+
+protected:
+ void RefreshCell(int row, int col);
+ virtual void DrawColLabel(wxDC& dc, int col);
+ std::pair<int, int> mousePosToCell(wxPoint pos); //returns (row/column) pair
+
+ virtual CustomGridTable* getGridDataTable() const = 0;
+
+private:
+ void onGridAccess(wxEvent& event);
+
+ //this method is called when grid view changes: useful for parallel updating of multiple grids
+ void OnPaintGrid(wxEvent& event);
+
+ virtual void alignOtherGrids(CustomGrid* gridLeft, CustomGrid* gridMiddle, CustomGrid* gridRight) = 0;
+
+ void adjustGridHeights(wxEvent& event);
+ virtual void enableFileIcons(const std::shared_ptr<zen::IconBuffer>& iconBuffer) = 0;
+
+ CustomGrid* m_gridLeft;
+ CustomGrid* m_gridMiddle;
+ CustomGrid* m_gridRight;
+
+ bool isLeading; //identify grid that has user focus
+
+ SortMarker m_marker;
+};
+
+
+class GridCellRenderer;
+
+
+//-----------------------------------------------------------
+class IconUpdater : private wxEvtHandler //update file icons periodically: use SINGLE instance to coordinate left and right grid at once
+{
+public:
+ IconUpdater(CustomGridLeft* leftGrid, CustomGridRight* rightGrid);
+ ~IconUpdater();
+
+private:
+ void loadIconsAsynchronously(wxEvent& event); //loads all (not yet) drawn icons
+
+ CustomGridRim* m_leftGrid;
+ CustomGridRim* m_rightGrid;
+
+ std::unique_ptr<wxTimer> m_timer; //user timer event to periodically update icons: better than idle event because also active when scrolling! :)
+};
+
+
+//############## SPECIALIZATIONS ###################
+class CustomGridRim : public CustomGrid
+{
+ friend class IconUpdater;
+ friend class GridCellRenderer;
+
+public:
+ CustomGridRim(wxWindow* parent,
+ wxWindowID id,
+ const wxPoint& pos,
+ const wxSize& size,
+ long style,
+ const wxString& name);
+
+ //set visibility, position and width of columns
+ static xmlAccess::ColumnAttributes getDefaultColumnAttributes();
+ xmlAccess::ColumnAttributes getColumnAttributes();
+ void setColumnAttributes(const xmlAccess::ColumnAttributes& attr);
+
+ xmlAccess::ColumnTypes getTypeAtPos(size_t pos) const;
+ static wxString getTypeName(xmlAccess::ColumnTypes colType);
+
+ void autoSizeColumns(); //performance optimized column resizer
+
+ virtual void updateGridSizes();
+
+protected:
+ template <zen::SelectedSide side>
+ void setTooltip(const wxMouseEvent& event);
+
+ void setOtherGrid(CustomGridRim* other); //call during initialization!
+
+private:
+ CustomGridTableRim* getGridDataTableRim() const;
+ virtual void enableFileIcons(const std::shared_ptr<zen::IconBuffer>& iconBuffer);
+
+ void OnResizeColumn(wxGridSizeEvent& event);
+
+ //this method is called when grid view changes: useful for parallel updating of multiple grids
+ virtual void alignOtherGrids(CustomGrid* gridLeft, CustomGrid* gridMiddle, CustomGrid* gridRight);
+
+ //asynchronous icon loading
+ void getIconsToBeLoaded(std::vector<Zstring>& newLoad); //loads all (not yet) drawn icons
+
+ typedef size_t RowBegin;
+ typedef size_t RowEnd;
+ std::pair<RowBegin, RowEnd> getVisibleRows(); //return [first, last) number pair
+
+ typedef size_t RowNumber;
+ typedef std::set<RowNumber> FailedIconLoad;
+ FailedIconLoad failedLoads; //save status of last icon load when drawing on GUI
+
+ std::shared_ptr<zen::IconBuffer> iconBuffer_;
+
+ xmlAccess::ColumnAttributes columnSettings; //set visibility, position and width of columns
+ CustomGridRim* otherGrid; //sibling grid on other side
+};
+
+
+class CustomGridLeft : public CustomGridRim
+{
+public:
+ CustomGridLeft(wxWindow* parent,
+ wxWindowID id,
+ const wxPoint& pos = wxDefaultPosition,
+ const wxSize& size = wxDefaultSize,
+ long style = wxWANTS_CHARS,
+ const wxString& name = wxGridNameStr);
+
+ virtual bool CreateGrid(int numRows, int numCols, wxGrid::wxGridSelectionModes selmode = wxGrid::wxGridSelectCells);
+
+ virtual void initSettings(CustomGridLeft* gridLeft, //create connection with zen::GridView
+ CustomGridMiddle* gridMiddle,
+ CustomGridRight* gridRight,
+ const zen::GridView* gridDataView);
+
+private:
+ void OnMouseMovement(wxMouseEvent& event);
+ virtual CustomGridTable* getGridDataTable() const;
+};
+
+
+class CustomGridRight : public CustomGridRim
+{
+public:
+ CustomGridRight(wxWindow* parent,
+ wxWindowID id,
+ const wxPoint& pos = wxDefaultPosition,
+ const wxSize& size = wxDefaultSize,
+ long style = wxWANTS_CHARS,
+ const wxString& name = wxGridNameStr);
+
+ virtual bool CreateGrid(int numRows, int numCols, wxGrid::wxGridSelectionModes selmode = wxGrid::wxGridSelectCells);
+
+ virtual void initSettings(CustomGridLeft* gridLeft, //create connection with zen::GridView
+ CustomGridMiddle* gridMiddle,
+ CustomGridRight* gridRight,
+ const zen::GridView* gridDataView);
+
+private:
+ void OnMouseMovement(wxMouseEvent& event);
+ virtual CustomGridTable* getGridDataTable() const;
+};
+
+
+class CustomGridMiddle : public CustomGrid
+{
+ friend class GridCellRendererMiddle;
+
+public:
+ CustomGridMiddle(wxWindow* parent,
+ wxWindowID id,
+ const wxPoint& pos = wxDefaultPosition,
+ const wxSize& size = wxDefaultSize,
+ long style = wxWANTS_CHARS,
+ const wxString& name = wxGridNameStr);
+
+ ~CustomGridMiddle();
+
+ virtual bool CreateGrid(int numRows, int numCols, wxGrid::wxGridSelectionModes selmode = wxGrid::wxGridSelectCells);
+
+ virtual void initSettings(CustomGridLeft* gridLeft, //create connection with zen::GridView
+ CustomGridMiddle* gridMiddle,
+ CustomGridRight* gridRight,
+ const zen::GridView* gridDataView);
+
+ void enableSyncPreview(bool value);
+
+private:
+ virtual CustomGridTable* getGridDataTable() const;
+ CustomGridTableMiddle* getGridDataTableMiddle() const;
+
+ virtual void enableFileIcons(const std::shared_ptr<zen::IconBuffer>& iconBuffer) {};
+#ifdef FFS_WIN //get rid of scrollbars; Windows: overwrite virtual method
+ virtual void SetScrollbar(int orientation, int position, int thumbSize, int range, bool refresh = true);
+#endif
+
+ //this method is called when grid view changes: useful for parallel updating of multiple grids
+ virtual void alignOtherGrids(CustomGrid* gridLeft, CustomGrid* gridMiddle, CustomGrid* gridRight);
+
+ virtual void DrawColLabel(wxDC& dc, int col);
+
+ void OnMouseMovement(wxMouseEvent& event);
+ void OnLeaveWindow(wxMouseEvent& event);
+ void OnLeftMouseDown(wxMouseEvent& event);
+ void OnLeftMouseUp(wxMouseEvent& event);
+
+ void showToolTip(int rowNumber, wxPoint pos);
+
+ //small helper methods
+ enum BlockPosition //each cell can be divided into four blocks concerning mouse selections
+ {
+ BLOCKPOS_CHECK_BOX,
+ BLOCKPOS_LEFT,
+ BLOCKPOS_MIDDLE,
+ BLOCKPOS_RIGHT
+ };
+ int mousePosToRow(const wxPoint pos, BlockPosition* block = NULL);
+
+ //variables for selecting sync direction
+ int selectionRowBegin;
+ BlockPosition selectionPos;
+
+ //variables for highlightning on mouse-over
+ int highlightedRow;
+ BlockPosition highlightedPos;
+
+ zen::Tooltip toolTip;
+};
+
+//custom events for middle grid:
+
+//--------------------------------------------------------------------------------------------
+//(UN-)CHECKING ROWS FROM SYNCHRONIZATION
+
+extern const wxEventType FFS_CHECK_ROWS_EVENT; //define new event type
+
+class FFSCheckRowsEvent : public wxCommandEvent
+{
+public:
+ FFSCheckRowsEvent(const int from, const int to) :
+ wxCommandEvent(FFS_CHECK_ROWS_EVENT),
+ rowFrom(from),
+ rowTo(to) {}
+
+ virtual wxEvent* Clone() const
+ {
+ return new FFSCheckRowsEvent(rowFrom, rowTo);
+ }
+
+ const int rowFrom;
+ const int rowTo;
+};
+
+typedef void (wxEvtHandler::*FFSCheckRowsEventFunction)(FFSCheckRowsEvent&);
+
+#define FFSCheckRowsEventHandler(func) \
+ (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(FFSCheckRowsEventFunction, &func)
+
+//--------------------------------------------------------------------------------------------
+//SELECTING SYNC DIRECTION
+
+extern const wxEventType FFS_SYNC_DIRECTION_EVENT; //define new event type
+
+class FFSSyncDirectionEvent : public wxCommandEvent
+{
+public:
+ FFSSyncDirectionEvent(const int from, const int to, const zen::SyncDirection dir) :
+ wxCommandEvent(FFS_SYNC_DIRECTION_EVENT),
+ rowFrom(from),
+ rowTo(to),
+ direction(dir) {}
+
+ virtual wxEvent* Clone() const
+ {
+ return new FFSSyncDirectionEvent(rowFrom, rowTo, direction);
+ }
+
+ const int rowFrom;
+ const int rowTo;
+ const zen::SyncDirection direction;
+};
+
+typedef void (wxEvtHandler::*FFSSyncDirectionEventFunction)(FFSSyncDirectionEvent&);
+
+#define FFSSyncDirectionEventHandler(func) \
+ (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(FFSSyncDirectionEventFunction, &func)
+
+
+#endif // CUSTOMGRID_H_INCLUDED
diff --git a/lib/db_file.cpp b/lib/db_file.cpp
new file mode 100644
index 00000000..757a95d7
--- /dev/null
+++ b/lib/db_file.cpp
@@ -0,0 +1,597 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "db_file.h"
+#include <wx/wfstream.h>
+#include <wx/zstream.h>
+#include <wx/mstream.h>
+#include <zen/file_error.h>
+#include <wx+/string_conv.h>
+#include <zen/file_handling.h>
+#include <wx+/serialize.h>
+#include <zen/file_io.h>
+#include <zen/scope_guard.h>
+#include <zen/guid.h>
+#include <boost/bind.hpp>
+
+#ifdef FFS_WIN
+#include <zen/win.h> //includes "windows.h"
+#include <zen/long_path_prefix.h>
+#endif
+
+using namespace zen;
+
+
+namespace
+{
+//-------------------------------------------------------------------------------------------------------------------------------
+const char FILE_FORMAT_DESCR[] = "FreeFileSync";
+const int FILE_FORMAT_VER = 7;
+//-------------------------------------------------------------------------------------------------------------------------------
+
+
+template <SelectedSide side> inline
+Zstring getDBFilename(const BaseDirMapping& baseMap, bool tempfile = false)
+{
+ //Linux and Windows builds are binary incompatible: char/wchar_t case, sensitive/insensitive
+ //32 and 64 bit db files ARE designed to be binary compatible!
+ //Give db files different names.
+ //make sure they end with ".ffs_db". These files will not be included into comparison when located in base sync directories
+#ifdef FFS_WIN
+ Zstring dbname = Zstring(Zstr("sync")) + (tempfile ? Zstr(".tmp") : Zstr("")) + SYNC_DB_FILE_ENDING;
+#elif defined FFS_LINUX
+ //files beginning with dots are hidden e.g. in Nautilus
+ Zstring dbname = Zstring(Zstr(".sync")) + (tempfile ? Zstr(".tmp") : Zstr("")) + SYNC_DB_FILE_ENDING;
+#endif
+
+ return baseMap.getBaseDirPf<side>() + dbname;
+}
+
+
+
+class FileInputStreamDB : public FileInputStream
+{
+public:
+ FileInputStreamDB(const Zstring& filename) : //throw FileError
+ FileInputStream(filename)
+ {
+ //read FreeFileSync file identifier
+ char formatDescr[sizeof(FILE_FORMAT_DESCR)] = {};
+ Read(formatDescr, sizeof(formatDescr)); //throw FileError
+
+ if (!std::equal(FILE_FORMAT_DESCR, FILE_FORMAT_DESCR + sizeof(FILE_FORMAT_DESCR), formatDescr))
+ throw FileError(_("Incompatible synchronization database format:") + " \n" + "\"" + filename + "\"");
+ }
+
+private:
+};
+
+
+class FileOutputStreamDB : public FileOutputStream
+{
+public:
+ FileOutputStreamDB(const Zstring& filename) : //throw FileError
+ FileOutputStream(filename)
+ {
+ //write FreeFileSync file identifier
+ Write(FILE_FORMAT_DESCR, sizeof(FILE_FORMAT_DESCR)); //throw FileError
+ }
+
+private:
+};
+}
+//#######################################################################################################################################
+
+
+class ReadDirInfo : public zen::ReadInputStream
+{
+public:
+ ReadDirInfo(wxInputStream& stream, const wxString& errorObjName, DirInformation& dirInfo) : ReadInputStream(stream, errorObjName)
+ {
+ //|-------------------------------------------------------------------------------------
+ //| ensure 32/64 bit portability: use fixed size data types only e.g. boost::uint32_t |
+ //|-------------------------------------------------------------------------------------
+
+ //read filter settings -> currently not required, but persisting it doesn't hurt
+ dirInfo.filter = HardFilter::loadFilter(getStream());
+ check();
+
+ //start recursion
+ execute(dirInfo.baseDirContainer);
+ }
+
+private:
+ void execute(DirContainer& dirCont) const
+ {
+ while (readNumberC<bool>())
+ readSubFile(dirCont);
+
+ while (readNumberC<bool>())
+ readSubLink(dirCont);
+
+ while (readNumberC<bool>())
+ readSubDirectory(dirCont);
+ }
+
+ void readSubFile(DirContainer& dirCont) const
+ {
+ //attention: order of function argument evaluation is undefined! So do it one after the other...
+ const Zstring shortName = readStringC<Zstring>(); //file name
+
+ const std::int64_t modTime = readNumberC<std::int64_t>();
+ const std::uint64_t fileSize = readNumberC<std::uint64_t>();
+
+ //const util::FileID fileIdentifier(stream_);
+ //check();
+
+ dirCont.addSubFile(shortName,
+ FileDescriptor(modTime, fileSize));
+ }
+
+
+ void readSubLink(DirContainer& dirCont) const
+ {
+ //attention: order of function argument evaluation is undefined! So do it one after the other...
+ const Zstring shortName = readStringC<Zstring>(); //file name
+ const std::int64_t modTime = readNumberC<std::int64_t>();
+ const Zstring targetPath = readStringC<Zstring>(); //file name
+ const LinkDescriptor::LinkType linkType = static_cast<LinkDescriptor::LinkType>(readNumberC<std::int32_t>());
+
+ dirCont.addSubLink(shortName,
+ LinkDescriptor(modTime, targetPath, linkType));
+ }
+
+
+ void readSubDirectory(DirContainer& dirCont) const
+ {
+ const Zstring shortName = readStringC<Zstring>(); //directory name
+ DirContainer& subDir = dirCont.addSubDir(shortName);
+ execute(subDir); //recurse
+ }
+};
+
+namespace
+{
+typedef std::string UniqueId;
+typedef std::shared_ptr<std::vector<char> > MemoryStreamPtr; //byte stream representing DirInformation
+typedef std::map<UniqueId, MemoryStreamPtr> StreamMapping; //list of streams ordered by session UUID
+}
+
+class ReadFileStream : public zen::ReadInputStream
+{
+public:
+ ReadFileStream(wxInputStream& stream, const wxString& filename, StreamMapping& streamList) : ReadInputStream(stream, filename)
+ {
+ //|-------------------------------------------------------------------------------------
+ //| ensure 32/64 bit portability: used fixed size data types only e.g. boost::uint32_t |
+ //|-------------------------------------------------------------------------------------
+
+ std::int32_t version = readNumberC<std::int32_t>();
+
+ if (version != FILE_FORMAT_VER) //read file format version
+ throw FileError(_("Incompatible synchronization database format:") + " \n" + "\"" + filename.c_str() + "\"");
+
+ streamList.clear();
+
+ boost::uint32_t dbCount = readNumberC<boost::uint32_t>(); //number of databases: one for each sync-pair
+ while (dbCount-- != 0)
+ {
+ //DB id of partner databases
+ const CharArray tmp2 = readArrayC();
+ const std::string sessionID(tmp2->begin(), tmp2->end());
+
+ CharArray buffer = readArrayC(); //read db-entry stream (containing DirInformation)
+
+ streamList.insert(std::make_pair(sessionID, buffer));
+ }
+ }
+};
+
+namespace
+{
+StreamMapping loadStreams(const Zstring& filename) //throw FileError
+{
+ if (!zen::fileExists(filename))
+ throw FileErrorDatabaseNotExisting(_("Initial synchronization:") + " \n\n" +
+ _("One of the FreeFileSync database files is not yet existing:") + " \n" +
+ "\"" + filename + "\"");
+
+ try
+ {
+ //read format description (uncompressed)
+ FileInputStreamDB uncompressed(filename); //throw FileError
+
+ wxZlibInputStream input(uncompressed, wxZLIB_ZLIB);
+
+ StreamMapping streamList;
+ ReadFileStream(input, toWx(filename), streamList);
+ return streamList;
+ }
+ catch (const std::bad_alloc&) //this is most likely caused by a corrupted database file
+ {
+ throw FileError(_("Error reading from synchronization database:") + " (bad_alloc)");
+ }
+}
+
+
+DirInfoPtr parseStream(const std::vector<char>& stream, const Zstring& fileName) //throw FileError -> return value always bound!
+{
+ try
+ {
+ //read streams into DirInfo
+ auto dirInfo = std::make_shared<DirInformation>();
+ wxMemoryInputStream buffer(&stream[0], stream.size()); //convert char-array to inputstream: no copying, ownership not transferred
+ ReadDirInfo(buffer, toWx(fileName), *dirInfo); //throw FileError
+ return dirInfo;
+ }
+ catch (const std::bad_alloc&) //this is most likely caused by a corrupted database file
+ {
+ throw FileError(_("Error reading from synchronization database:") + " (bad_alloc)");
+ }
+}
+}
+
+
+std::pair<DirInfoPtr, DirInfoPtr> zen::loadFromDisk(const BaseDirMapping& baseMapping) //throw FileError
+{
+ const Zstring fileNameLeft = getDBFilename<LEFT_SIDE>(baseMapping);
+ const Zstring fileNameRight = getDBFilename<RIGHT_SIDE>(baseMapping);
+
+ //read file data: list of session ID + DirInfo-stream
+ const StreamMapping streamListLeft = ::loadStreams(fileNameLeft); //throw FileError
+ const StreamMapping streamListRight = ::loadStreams(fileNameRight); //throw FileError
+
+ //find associated session: there can be at most one session within intersection of left and right ids
+ StreamMapping::const_iterator streamLeft = streamListLeft .end();
+ StreamMapping::const_iterator streamRight = streamListRight.end();
+ for (auto iterLeft = streamListLeft.begin(); iterLeft != streamListLeft.end(); ++iterLeft)
+ {
+ auto iterRight = streamListRight.find(iterLeft->first);
+ if (iterRight != streamListRight.end())
+ {
+ streamLeft = iterLeft;
+ streamRight = iterRight;
+ break;
+ }
+ }
+
+ if (streamLeft == streamListLeft .end() ||
+ streamRight == streamListRight.end() ||
+ !streamLeft ->second.get() ||
+ !streamRight->second.get())
+ throw FileErrorDatabaseNotExisting(_("Initial synchronization:") + " \n\n" +
+ _("Database files do not share a common synchronization session:") + " \n" +
+ "\"" + fileNameLeft + "\"\n" +
+ "\"" + fileNameRight + "\"");
+ //read streams into DirInfo
+ DirInfoPtr dirInfoLeft = parseStream(*streamLeft ->second, fileNameLeft); //throw FileError
+ DirInfoPtr dirInfoRight = parseStream(*streamRight->second, fileNameRight); //throw FileError
+
+ return std::make_pair(dirInfoLeft, dirInfoRight);
+}
+
+
+//-------------------------------------------------------------------------------------------------------------------------
+template <SelectedSide side>
+class SaveDirInfo : public WriteOutputStream
+{
+public:
+ SaveDirInfo(const BaseDirMapping& baseMapping, const DirContainer* oldDirInfo, const wxString& errorObjName, wxOutputStream& stream) : WriteOutputStream(errorObjName, stream)
+ {
+ //save filter settings
+ baseMapping.getFilter()->saveFilter(getStream());
+ check();
+
+ //start recursion
+ execute(baseMapping, oldDirInfo);
+ }
+
+private:
+ void execute(const HierarchyObject& hierObj, const DirContainer* oldDirInfo)
+ {
+ std::for_each(hierObj.refSubFiles().begin(), hierObj.refSubFiles().end(), boost::bind(&SaveDirInfo::processFile, this, _1, oldDirInfo));
+ writeNumberC<bool>(false); //mark last entry
+ std::for_each(hierObj.refSubLinks().begin(), hierObj.refSubLinks().end(), boost::bind(&SaveDirInfo::processLink, this, _1, oldDirInfo));
+ writeNumberC<bool>(false); //mark last entry
+ std::for_each(hierObj.refSubDirs ().begin(), hierObj.refSubDirs ().end(), boost::bind(&SaveDirInfo::processDir, this, _1, oldDirInfo));
+ writeNumberC<bool>(false); //mark last entry
+ }
+
+ void processFile(const FileMapping& fileMap, const DirContainer* oldParentDir)
+ {
+ if (fileMap.getCategory() == FILE_EQUAL) //data in sync: write current state
+ {
+ if (!fileMap.isEmpty<side>())
+ {
+ writeNumberC<bool>(true); //mark beginning of entry
+ writeStringC(fileMap.getShortName<side>()); //save respecting case! (Windows)
+ writeNumberC<std:: int64_t>(to<std:: int64_t>(fileMap.getLastWriteTime<side>())); //last modification time
+ writeNumberC<std::uint64_t>(to<std::uint64_t>(fileMap.getFileSize<side>())); //filesize
+ }
+ }
+ else //not in sync: reuse last synchronous state
+ {
+ if (oldParentDir) //no data is also a "synchronous state"!
+ {
+ auto iter = oldParentDir->files.find(fileMap.getObjShortName());
+ if (iter != oldParentDir->files.end())
+ {
+ writeNumberC<bool>(true); //mark beginning of entry
+ writeStringC(iter->first); //save respecting case! (Windows)
+ writeNumberC<std:: int64_t>(to<std:: int64_t>(iter->second.lastWriteTimeRaw)); //last modification time
+ writeNumberC<std::uint64_t>(to<std::uint64_t>(iter->second.fileSize)); //filesize
+ }
+ }
+ }
+ }
+
+ void processLink(const SymLinkMapping& linkObj, const DirContainer* oldParentDir)
+ {
+ if (linkObj.getLinkCategory() == SYMLINK_EQUAL) //data in sync: write current state
+ {
+ if (!linkObj.isEmpty<side>())
+ {
+ writeNumberC<bool>(true); //mark beginning of entry
+ writeStringC(linkObj.getShortName<side>()); //save respecting case! (Windows)
+ writeNumberC<std::int64_t>(to<std::int64_t>(linkObj.getLastWriteTime<side>())); //last modification time
+ writeStringC(linkObj.getTargetPath<side>());
+ writeNumberC<std::int32_t>(linkObj.getLinkType<side>());
+ }
+ }
+ else //not in sync: reuse last synchronous state
+ {
+ if (oldParentDir) //no data is also a "synchronous state"!
+ {
+ auto iter = oldParentDir->links.find(linkObj.getObjShortName());
+ if (iter != oldParentDir->links.end())
+ {
+ writeNumberC<bool>(true); //mark beginning of entry
+ writeStringC(iter->first); //save respecting case! (Windows)
+ writeNumberC<std::int64_t>(to<std::int64_t>(iter->second.lastWriteTimeRaw)); //last modification time
+ writeStringC(iter->second.targetPath);
+ writeNumberC<std::int32_t>(iter->second.type);
+ }
+ }
+ }
+ }
+
+ void processDir(const DirMapping& dirMap, const DirContainer* oldParentDir)
+ {
+ const DirContainer* oldDir = NULL;
+ const Zstring* oldDirName = NULL;
+ if (oldParentDir) //no data is also a "synchronous state"!
+ {
+ auto iter = oldParentDir->dirs.find(dirMap.getObjShortName());
+ if (iter != oldParentDir->dirs.end())
+ {
+ oldDirName = &iter->first;
+ oldDir = &iter->second;
+ }
+ }
+
+ CompareDirResult cat = dirMap.getDirCategory();
+
+ if (cat == DIR_EQUAL) //data in sync: write current state
+ {
+ if (!dirMap.isEmpty<side>())
+ {
+ writeNumberC<bool>(true); //mark beginning of entry
+ writeStringC(dirMap.getShortName<side>()); //save respecting case! (Windows)
+ execute(dirMap, oldDir); //recurse
+ }
+ }
+ else //not in sync: reuse last synchronous state
+ {
+ if (oldDir)
+ {
+ writeNumberC<bool>(true); //mark beginning of entry
+ writeStringC(*oldDirName); //save respecting case! (Windows)
+ execute(dirMap, oldDir); //recurse
+ return;
+ }
+ //no data is also a "synchronous state"!
+
+ //else: not in sync AND no "last synchronous state"
+ //we cannot simply skip the whole directory, since sub-items might be in sync
+ //Example: directories on left and right differ in case while sub-files are equal
+ switch (cat)
+ {
+ case DIR_LEFT_SIDE_ONLY: //sub-items cannot be in sync
+ break;
+ case DIR_RIGHT_SIDE_ONLY: //sub-items cannot be in sync
+ break;
+ case DIR_EQUAL:
+ assert(false);
+ break;
+ case DIR_DIFFERENT_METADATA:
+ writeNumberC<bool>(true);
+ writeStringC(dirMap.getShortName<side>());
+ //ATTENTION: strictly this is a violation of the principle of reporting last synchronous state!
+ //however in this case this will result in "last sync unsuccessful" for this directory within <automatic> algorithm, which is fine
+ execute(dirMap, oldDir); //recurse and save sub-items which are in sync
+ break;
+ }
+ }
+ }
+};
+
+
+class WriteFileStream : public WriteOutputStream
+{
+public:
+ WriteFileStream(const StreamMapping& streamList, const wxString& filename, wxOutputStream& stream) : WriteOutputStream(filename, stream)
+ {
+ //save file format version
+ writeNumberC<std::int32_t>(FILE_FORMAT_VER);
+
+ writeNumberC<boost::uint32_t>(static_cast<boost::uint32_t>(streamList.size())); //number of database records: one for each sync-pair
+
+ for (StreamMapping::const_iterator i = streamList.begin(); i != streamList.end(); ++i)
+ {
+ //sync session id
+ writeArrayC(std::vector<char>(i->first.begin(), i->first.end()));
+
+ //write DirInformation stream
+ writeArrayC(*(i->second));
+ }
+ }
+};
+
+
+//save/load DirContainer
+void saveFile(const StreamMapping& streamList, const Zstring& filename) //throw FileError
+{
+ {
+ //write format description (uncompressed)
+ FileOutputStreamDB uncompressed(filename); //throw FileError
+
+ wxZlibOutputStream output(uncompressed, 4, wxZLIB_ZLIB);
+ /* 4 - best compromise between speed and compression: (scanning 200.000 objects)
+ 0 (uncompressed) 8,95 MB - 422 ms
+ 2 2,07 MB - 470 ms
+ 4 1,87 MB - 500 ms
+ 6 1,77 MB - 613 ms
+ 9 (maximal compression) 1,74 MB - 3330 ms */
+
+ WriteFileStream(streamList, toWx(filename), output);
+ }
+ //(try to) hide database file
+#ifdef FFS_WIN
+ ::SetFileAttributes(zen::applyLongPathPrefix(filename).c_str(), FILE_ATTRIBUTE_HIDDEN);
+#endif
+}
+
+
+bool equalEntry(const MemoryStreamPtr& lhs, const MemoryStreamPtr& rhs)
+{
+ if (!lhs.get() || !rhs.get())
+ return lhs.get() == rhs.get();
+
+ return *lhs == *rhs;
+}
+
+
+void zen::saveToDisk(const BaseDirMapping& baseMapping) //throw FileError
+{
+ //transactional behaviour! write to tmp files first
+ const Zstring dbNameLeftTmp = getDBFilename<LEFT_SIDE >(baseMapping, true);
+ const Zstring dbNameRightTmp = getDBFilename<RIGHT_SIDE>(baseMapping, true);
+
+ const Zstring dbNameLeft = getDBFilename<LEFT_SIDE >(baseMapping);
+ const Zstring dbNameRight = getDBFilename<RIGHT_SIDE>(baseMapping);
+
+ //delete old tmp file, if necessary -> throws if deletion fails!
+ removeFile(dbNameLeftTmp); //
+ removeFile(dbNameRightTmp); //throw FileError
+
+ //(try to) load old database files...
+ StreamMapping streamListLeft;
+ StreamMapping streamListRight;
+
+ try //read file data: list of session ID + DirInfo-stream
+ {
+ streamListLeft = ::loadStreams(dbNameLeft);
+ }
+ catch (FileError&) {} //if error occurs: just overwrite old file! User is already informed about issues right after comparing!
+ try
+ {
+ streamListRight = ::loadStreams(dbNameRight);
+ }
+ catch (FileError&) {}
+
+ //find associated session: there can be at most one session within intersection of left and right ids
+ StreamMapping::iterator streamLeft = streamListLeft .end();
+ StreamMapping::iterator streamRight = streamListRight.end();
+ for (auto iterLeft = streamListLeft.begin(); iterLeft != streamListLeft.end(); ++iterLeft)
+ {
+ auto iterRight = streamListRight.find(iterLeft->first);
+ if (iterRight != streamListRight.end())
+ {
+ streamLeft = iterLeft;
+ streamRight = iterRight;
+ break;
+ }
+ }
+
+ //(try to) read old DirInfo
+ DirInfoPtr oldDirInfoLeft;
+ DirInfoPtr oldDirInfoRight;
+ try
+ {
+ if (streamLeft != streamListLeft .end() &&
+ streamRight != streamListRight.end() &&
+ streamLeft ->second.get() &&
+ streamRight->second.get())
+ {
+ oldDirInfoLeft = parseStream(*streamLeft ->second, dbNameLeft); //throw FileError
+ oldDirInfoRight = parseStream(*streamRight->second, dbNameRight); //throw FileError
+ }
+ }
+ catch (FileError&)
+ {
+ //if error occurs: just overwrite old file! User is already informed about issues right after comparing!
+ oldDirInfoLeft .reset(); //read both or none!
+ oldDirInfoRight.reset(); //
+ }
+
+ //create new database entries
+ MemoryStreamPtr newStreamLeft = std::make_shared<std::vector<char>>();
+ {
+ wxMemoryOutputStream buffer;
+ const DirContainer* oldDir = oldDirInfoLeft.get() ? &oldDirInfoLeft->baseDirContainer : NULL;
+ SaveDirInfo<LEFT_SIDE>(baseMapping, oldDir, toWx(dbNameLeft), buffer);
+ newStreamLeft->resize(buffer.GetSize()); //convert output stream to char-array
+ buffer.CopyTo(&(*newStreamLeft)[0], buffer.GetSize()); //
+ }
+
+ MemoryStreamPtr newStreamRight = std::make_shared<std::vector<char>>();
+ {
+ wxMemoryOutputStream buffer;
+ const DirContainer* oldDir = oldDirInfoRight.get() ? &oldDirInfoRight->baseDirContainer : NULL;
+ SaveDirInfo<RIGHT_SIDE>(baseMapping, oldDir, toWx(dbNameRight), buffer);
+ newStreamRight->resize(buffer.GetSize()); //convert output stream to char-array
+ buffer.CopyTo(&(*newStreamRight)[0], buffer.GetSize()); //
+ }
+
+ //check if there is some work to do at all
+ {
+ const bool updateRequiredLeft = streamLeft == streamListLeft .end() || !equalEntry(newStreamLeft, streamLeft ->second);
+ const bool updateRequiredRight = streamRight == streamListRight.end() || !equalEntry(newStreamRight, streamRight->second);
+ //some users monitor the *.ffs_db file with RTS => don't touch the file if it isnt't strictly needed
+ if (!updateRequiredLeft && !updateRequiredRight)
+ return;
+ }
+
+ //create/update DirInfo-streams
+ std::string sessionID = zen::generateGUID();
+
+ //erase old session data
+ if (streamLeft != streamListLeft.end())
+ streamListLeft.erase(streamLeft);
+ if (streamRight != streamListRight.end())
+ streamListRight.erase(streamRight);
+
+ //fill in new
+ streamListLeft .insert(std::make_pair(sessionID, newStreamLeft));
+ streamListRight.insert(std::make_pair(sessionID, newStreamRight));
+
+ //write (temp-) files...
+ zen::ScopeGuard guardTempFileLeft = zen::makeGuard([&]() {zen::removeFile(dbNameLeftTmp); });
+ saveFile(streamListLeft, dbNameLeftTmp); //throw FileError
+
+ zen::ScopeGuard guardTempFileRight = zen::makeGuard([&]() {zen::removeFile(dbNameRightTmp); });
+ saveFile(streamListRight, dbNameRightTmp); //throw FileError
+
+ //operation finished: rename temp files -> this should work transactionally:
+ //if there were no write access, creation of temp files would have failed
+ removeFile(dbNameLeft);
+ removeFile(dbNameRight);
+ renameFile(dbNameLeftTmp, dbNameLeft); //throw FileError;
+ renameFile(dbNameRightTmp, dbNameRight); //throw FileError;
+
+ guardTempFileLeft. dismiss(); //no need to delete temp file anymore
+ guardTempFileRight.dismiss(); //
+}
diff --git a/lib/db_file.h b/lib/db_file.h
new file mode 100644
index 00000000..d6d765cc
--- /dev/null
+++ b/lib/db_file.h
@@ -0,0 +1,31 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef DBFILE_H_INCLUDED
+#define DBFILE_H_INCLUDED
+
+#include <zen/file_error.h>
+#include "../file_hierarchy.h"
+
+namespace zen
+{
+const Zstring SYNC_DB_FILE_ENDING = Zstr(".ffs_db");
+
+void saveToDisk(const BaseDirMapping& baseMapping); //throw FileError
+
+struct DirInformation
+{
+ HardFilter::FilterRef filter; //filter settings (used when retrieving directory data)
+ DirContainer baseDirContainer; //hierarchical directory information
+};
+typedef std::shared_ptr<const DirInformation> DirInfoPtr;
+
+DEFINE_NEW_FILE_ERROR(FileErrorDatabaseNotExisting);
+
+std::pair<DirInfoPtr, DirInfoPtr> loadFromDisk(const BaseDirMapping& baseMapping); //throw FileError, FileErrorDatabaseNotExisting -> return value always bound!
+}
+
+#endif // DBFILE_H_INCLUDED
diff --git a/lib/detect_renaming.cpp b/lib/detect_renaming.cpp
new file mode 100644
index 00000000..39e7eb4b
--- /dev/null
+++ b/lib/detect_renaming.cpp
@@ -0,0 +1,285 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "detect_renaming.h"
+#include <map>
+#include <vector>
+#include <boost/bind.hpp>
+
+using namespace FreeFileSync;
+
+/*detect renamed files:
+Example:
+ X -> |_| Create right
+|_| -> Y Delete right
+
+is detected as:
+
+Rename Y to X on right
+
+Algorithm:
+----------
+DB-file left ---filename, Metadata(=:MD)---> DB-file right
+ /|\ |
+ | fileID, MD
+ fileID, MD |
+ | \|/
+ X Y
+
+*/
+
+
+class FindDBAssoc
+{
+ /*
+ load and associate db-files by filename and metadata(size, date)
+ fileID, MD |-> fileID
+ */
+public:
+ struct AssocKey
+ {
+ AssocKey(const Utility::FileID& fileId,
+ const wxLongLong& lastWriteTimeRaw,
+ const wxULongLong& fileSize);
+
+ bool operator<(const AssocKey& other) const;
+
+ Utility::FileID fileId_;
+ wxLongLong lastWriteTimeRaw_;
+ wxULongLong fileSize_;
+ };
+
+ FindDBAssoc(const FreeFileSync::BaseDirMapping& baseMapping,
+ std::map<AssocKey, Utility::FileID>& assocDBLeftToRight);
+
+private:
+ void recurse(const DirContainer& leftSide, const DirContainer& rightSide);
+
+ std::map<AssocKey, Utility::FileID>& assocDBLeftToRight_; //-->
+};
+
+
+inline
+FindDBAssoc::AssocKey::AssocKey(const Utility::FileID& fileId,
+ const wxLongLong& lastWriteTimeRaw,
+ const wxULongLong& fileSize) :
+ fileId_(fileId),
+ lastWriteTimeRaw_(lastWriteTimeRaw),
+ fileSize_(fileSize) {}
+
+
+inline
+bool FindDBAssoc::AssocKey::operator<(const AssocKey& other) const
+{
+ if (fileId_ != other.fileId_)
+ return fileId_ < other.fileId_;
+
+ if (lastWriteTimeRaw_ != other.lastWriteTimeRaw_)
+ return lastWriteTimeRaw_ < other.lastWriteTimeRaw_;
+
+ return fileSize_ < other.fileSize_;
+}
+
+
+FindDBAssoc::FindDBAssoc(const FreeFileSync::BaseDirMapping& baseMapping,
+ std::map<AssocKey, Utility::FileID>& assocDBLeftToRight) : assocDBLeftToRight_(assocDBLeftToRight)
+{
+ try
+ {
+ std::pair<FreeFileSync::DirInfoPtr, FreeFileSync::DirInfoPtr> dbInfo =
+ FreeFileSync::loadFromDisk(baseMapping); //throw (FileError)
+
+ recurse(dbInfo.first->baseDirContainer,
+ dbInfo.second->baseDirContainer);
+ }
+ catch (...) {} //swallow...
+}
+
+
+void FindDBAssoc::recurse(const DirContainer& leftSide, const DirContainer& rightSide)
+{
+ for (DirContainer::SubFileList::const_iterator i = leftSide.getSubFiles().begin(); i != leftSide.getSubFiles().end(); ++i)
+ {
+ const FileDescriptor& fileDescrI = i->second.getData();
+ if (!fileDescrI.fileIdentifier.isNull()) //fileIdentifier may be NULL
+ {
+ const DirContainer::SubFileList::const_iterator j = rightSide.getSubFiles().find(i->first);
+
+ //find files that exist on left and right
+ if (j != rightSide.getSubFiles().end())
+ {
+ const FileDescriptor& fileDescrJ = j->second.getData();
+ if (!fileDescrJ.fileIdentifier.isNull()) //fileIdentifier may be NULL
+ {
+ if ( fileDescrI.lastWriteTimeRaw == fileDescrJ.lastWriteTimeRaw &&
+ fileDescrI.fileSize == fileDescrJ.fileSize)
+ {
+ assocDBLeftToRight_[AssocKey(fileDescrI.fileIdentifier,
+ fileDescrI.lastWriteTimeRaw,
+ fileDescrI.fileSize)] = fileDescrJ.fileIdentifier;
+ }
+ }
+ }
+ }
+ }
+
+ //-----------------------------------------------------------------------------------------------
+ for (DirContainer::SubDirList::const_iterator i = leftSide.getSubDirs().begin(); i != leftSide.getSubDirs().end(); ++i)
+ {
+ const DirContainer::SubDirList::const_iterator j = rightSide.getSubDirs().find(i->first);
+
+ //directories that exist on both sides
+ if (j != rightSide.getSubDirs().end())
+ {
+ recurse(i->second, j->second); //recurse into subdirectories
+ }
+ }
+}
+
+
+
+class FindRenameCandidates
+{
+public:
+ FindRenameCandidates(FreeFileSync::BaseDirMapping& baseMapping)
+ {
+ FindDBAssoc(baseMapping,
+ assocDBLeftToRight);
+
+ if (!assocDBLeftToRight.empty())
+ recurse(baseMapping);
+ }
+
+ void getRenameCandidates(std::vector<std::pair<FileMapping*, FileMapping*> >& renameOnLeft,
+ std::vector<std::pair<FileMapping*, FileMapping*> >& renameOnRight);
+
+private:
+ void recurse(HierarchyObject& hierObj)
+ {
+ //files
+ std::for_each(hierObj.subFiles.begin(), hierObj.subFiles.end(),
+ boost::bind(&FindRenameCandidates::processFile, this, _1));
+
+ //directories
+ std::for_each(hierObj.subDirs.begin(), hierObj.subDirs.end(),
+ boost::bind(&FindRenameCandidates::recurse, this, _1));//recursion
+ }
+
+ void processFile(FileMapping& fileObj)
+ {
+ switch (fileObj.getSyncOperation()) //evaluate comparison result and sync direction
+ {
+ case SO_CREATE_NEW_LEFT:
+ if (!fileObj.getFileID<RIGHT_SIDE>().isNull()) //fileIdentifier may be NULL
+ createLeft[FindDBAssoc::AssocKey(fileObj.getFileID<RIGHT_SIDE>(),
+ fileObj.getLastWriteTime<RIGHT_SIDE>(),
+ fileObj.getFileSize<RIGHT_SIDE>())] = &fileObj;
+ break;
+
+ case SO_CREATE_NEW_RIGHT:
+ if (!fileObj.getFileID<LEFT_SIDE>().isNull()) //fileIdentifier may be NULL
+ createRight.push_back(&fileObj);
+ break;
+
+ case SO_DELETE_LEFT:
+ if (!fileObj.getFileID<LEFT_SIDE>().isNull()) //fileIdentifier may be NULL
+ deleteLeft.push_back(&fileObj);
+ break;
+
+ case SO_DELETE_RIGHT:
+ if (!fileObj.getFileID<RIGHT_SIDE>().isNull()) //fileIdentifier may be NULL
+ deleteRight[FindDBAssoc::AssocKey(fileObj.getFileID<RIGHT_SIDE>(),
+ fileObj.getLastWriteTime<RIGHT_SIDE>(),
+ fileObj.getFileSize<RIGHT_SIDE>())] = &fileObj;
+ break;
+
+ case SO_OVERWRITE_RIGHT:
+ case SO_OVERWRITE_LEFT:
+ case SO_DO_NOTHING:
+ case SO_UNRESOLVED_CONFLICT:
+ break;
+ }
+
+ }
+
+
+ std::vector<FileMapping*> createRight; //pointer always bound!
+ std::vector<FileMapping*> deleteLeft; //
+ // |
+ // \|/
+ std::map<FindDBAssoc::AssocKey, Utility::FileID> assocDBLeftToRight;
+ // |
+ // \|/
+ std::map<FindDBAssoc::AssocKey, FileMapping*> deleteRight; //pointer always bound!
+ std::map<FindDBAssoc::AssocKey, FileMapping*> createLeft; //
+
+};
+
+
+
+void FindRenameCandidates::getRenameCandidates(
+ std::vector<std::pair<FileMapping*, FileMapping*> >& renameOnLeft,
+ std::vector<std::pair<FileMapping*, FileMapping*> >& renameOnRight)
+{
+ for (std::vector<FileMapping*>::const_iterator crRightIter = createRight.begin();
+ crRightIter != createRight.end();
+ ++crRightIter)
+ {
+ const FindDBAssoc::AssocKey assocDbKey((*crRightIter)->getFileID<LEFT_SIDE>(),
+ (*crRightIter)->getLastWriteTime<LEFT_SIDE>(),
+ (*crRightIter)->getFileSize<LEFT_SIDE>());
+
+ const std::map<FindDBAssoc::AssocKey, Utility::FileID>::const_iterator assocDBIter =
+ assocDBLeftToRight.find(assocDbKey);
+
+ if (assocDBIter != assocDBLeftToRight.end())
+ {
+ std::map<FindDBAssoc::AssocKey, FileMapping*>::const_iterator delRightIter =
+ deleteRight.find(FindDBAssoc::AssocKey(assocDBIter->second, //FileID of right side
+ assocDbKey.lastWriteTimeRaw_,
+ assocDbKey.fileSize_));
+
+ if (delRightIter != deleteRight.end())
+ {
+ renameOnRight.push_back(std::make_pair(*crRightIter, delRightIter->second));
+ }
+ }
+ }
+ //------------------------------------------------------------------------------------------------
+ for (std::vector<FileMapping*>::const_iterator delLeftIter = deleteLeft.begin();
+ delLeftIter != deleteLeft.end();
+ ++delLeftIter)
+ {
+ const FindDBAssoc::AssocKey assocDbKey((*delLeftIter)->getFileID<LEFT_SIDE>(),
+ (*delLeftIter)->getLastWriteTime<LEFT_SIDE>(),
+ (*delLeftIter)->getFileSize<LEFT_SIDE>());
+
+ const std::map<FindDBAssoc::AssocKey, Utility::FileID>::const_iterator assocDBIter =
+ assocDBLeftToRight.find(assocDbKey);
+
+ if (assocDBIter != assocDBLeftToRight.end())
+ {
+ std::map<FindDBAssoc::AssocKey, FileMapping*>::const_iterator createLeftIter =
+ createLeft.find(FindDBAssoc::AssocKey(assocDBIter->second, //FileID of right side
+ assocDbKey.lastWriteTimeRaw_,
+ assocDbKey.fileSize_));
+
+ if (createLeftIter != createLeft.end())
+ {
+ renameOnLeft.push_back(std::make_pair(createLeftIter->second, *delLeftIter));
+ }
+ }
+ }
+}
+
+
+void FreeFileSync::getRenameCandidates(FreeFileSync::BaseDirMapping& baseMapping, //in
+ std::vector<std::pair<CreateOnLeft, DeleteOnLeft> >& renameOnLeft, //out
+ std::vector<std::pair<CreateOnRight, DeleteOnRight> >& renameOnRight) //out throw()!
+{
+ FindRenameCandidates(baseMapping).getRenameCandidates(renameOnLeft, renameOnRight);
+}
+
diff --git a/lib/detect_renaming.h b/lib/detect_renaming.h
new file mode 100644
index 00000000..e94927c0
--- /dev/null
+++ b/lib/detect_renaming.h
@@ -0,0 +1,26 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef DETECTRENAMING_H_INCLUDED
+#define DETECTRENAMING_H_INCLUDED
+
+#include "../file_hierarchy.h"
+
+
+//identify a file "create and delete"-operation as a file renaming!
+
+namespace zen
+{
+typedef FileMapping* CreateOnLeft;
+typedef FileMapping* DeleteOnLeft;
+typedef FileMapping* CreateOnRight;
+typedef FileMapping* DeleteOnRight;
+void getRenameCandidates(zen::BaseDirMapping& baseMapping, //in
+ std::vector<std::pair<CreateOnLeft, DeleteOnLeft> >& renameOnLeft, //out
+ std::vector<std::pair<CreateOnRight, DeleteOnRight> >& renameOnRight); //out throw()!
+}
+
+#endif // DETECTRENAMING_H_INCLUDED
diff --git a/lib/dir_exist_async.h b/lib/dir_exist_async.h
new file mode 100644
index 00000000..72741ee2
--- /dev/null
+++ b/lib/dir_exist_async.h
@@ -0,0 +1,42 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef DIR_EXIST_HEADER_08173281673432158067342132467183267
+#define DIR_EXIST_HEADER_08173281673432158067342132467183267
+
+#include <zen/thread.h>
+#include <zen/file_handling.h>
+#include "status_handler.h"
+#include <zen/file_error.h>
+#include "resolve_path.h"
+
+//dir existence checking may hang for non-existent network drives => run asynchronously and update UI!
+namespace
+{
+bool dirExistsUpdating(const Zstring& dirname, bool allowUserInteraction, ProcessCallback& procCallback)
+{
+ using namespace zen;
+
+ std::wstring statusText = _("Searching for directory %x...");
+ replace(statusText, L"%x", std::wstring(L"\"") + dirname + L"\"", false);
+ procCallback.reportStatus(statusText);
+
+ auto ft = async([=]() -> bool
+ {
+#ifdef FFS_WIN
+ //1. login to network share, if necessary
+ loginNetworkShare(dirname, allowUserInteraction);
+#endif
+ //2. check dir existence
+ return zen::dirExists(dirname);
+ });
+ while (!ft.timed_wait(boost::posix_time::milliseconds(UI_UPDATE_INTERVAL)))
+ procCallback.requestUiRefresh(); //may throw!
+ return ft.get();
+}
+}
+
+#endif //DIR_EXIST_HEADER_08173281673432158067342132467183267
diff --git a/lib/dir_lock.cpp b/lib/dir_lock.cpp
new file mode 100644
index 00000000..29c7ffc6
--- /dev/null
+++ b/lib/dir_lock.cpp
@@ -0,0 +1,600 @@
+#include "dir_lock.h"
+#include <utility>
+#include <wx/utils.h>
+#include <wx/timer.h>
+#include <wx/log.h>
+#include <wx/msgdlg.h>
+#include <memory>
+#include <boost/weak_ptr.hpp>
+#include <wx+/string_conv.h>
+#include <zen/last_error.h>
+#include <zen/thread.h> //includes <boost/thread.hpp>
+#include <zen/scope_guard.h>
+#include <zen/guid.h>
+#include <zen/file_io.h>
+#include <zen/assert_static.h>
+#include <wx+/serialize.h>
+#include <zen/int64.h>
+#include <zen/file_handling.h>
+
+#ifdef FFS_WIN
+#include <tlhelp32.h>
+#include <zen/win.h> //includes "windows.h"
+#include <zen/long_path_prefix.h>
+
+#elif defined FFS_LINUX
+#include <sys/stat.h>
+#include <cerrno>
+#include <unistd.h>
+#endif
+
+using namespace zen;
+using namespace std::rel_ops;
+
+
+namespace
+{
+const size_t EMIT_LIFE_SIGN_INTERVAL = 5000; //show life sign; unit [ms]
+const size_t POLL_LIFE_SIGN_INTERVAL = 6000; //poll for life sign; unit [ms]
+const size_t DETECT_EXITUS_INTERVAL = 30000; //assume abandoned lock; unit [ms]
+
+const char LOCK_FORMAT_DESCR[] = "FreeFileSync";
+const int LOCK_FORMAT_VER = 1; //lock file format version
+}
+
+//worker thread
+class LifeSigns
+{
+public:
+ LifeSigns(const Zstring& lockfilename) : //throw()!!! siehe SharedDirLock()
+ lockfilename_(lockfilename) {} //thread safety: make deep copy!
+
+ void operator()() const //thread entry
+ {
+ try
+ {
+ while (true)
+ {
+ boost::thread::sleep(boost::get_system_time() + boost::posix_time::milliseconds(EMIT_LIFE_SIGN_INTERVAL)); //interruption point!
+
+ //actual work
+ emitLifeSign(); //throw ()
+ }
+ }
+ catch (const std::exception& e) //exceptions must be catched per thread
+ {
+ wxSafeShowMessage(wxString(_("An exception occurred!")) + wxT("(Dirlock)"), wxString::FromAscii(e.what())); //simple wxMessageBox won't do for threads
+ }
+ }
+
+ void emitLifeSign() const //try to append one byte...; throw()
+ {
+ const char buffer[1] = {' '};
+
+#ifdef FFS_WIN
+ //ATTENTION: setting file pointer IS required! => use CreateFile/FILE_GENERIC_WRITE + SetFilePointerEx!
+ //although CreateFile/FILE_APPEND_DATA without SetFilePointerEx works locally, it MAY NOT work on some network shares creating a 4 gig file!!!
+
+ const HANDLE fileHandle = ::CreateFile(applyLongPathPrefix(lockfilename_.c_str()).c_str(),
+ GENERIC_READ | GENERIC_WRITE, //use both when writing over network, see comment in file_io.cpp
+ FILE_SHARE_READ,
+ 0,
+ OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL,
+ NULL);
+ if (fileHandle == INVALID_HANDLE_VALUE)
+ return;
+ ZEN_ON_BLOCK_EXIT(::CloseHandle(fileHandle));
+
+ const LARGE_INTEGER moveDist = {};
+ if (!::SetFilePointerEx(fileHandle, //__in HANDLE hFile,
+ moveDist, //__in LARGE_INTEGER liDistanceToMove,
+ NULL, //__out_opt PLARGE_INTEGER lpNewFilePointer,
+ FILE_END)) //__in DWORD dwMoveMethod
+ return;
+
+ DWORD bytesWritten = 0;
+ ::WriteFile(fileHandle, //__in HANDLE hFile,
+ buffer, //__out LPVOID lpBuffer,
+ 1, //__in DWORD nNumberOfBytesToRead,
+ &bytesWritten, //__out_opt LPDWORD lpNumberOfBytesWritten,
+ NULL); //__inout_opt LPOVERLAPPED lpOverlapped
+
+#elif defined FFS_LINUX
+ const int fileHandle = ::open(lockfilename_.c_str(), O_WRONLY | O_APPEND); //O_EXCL contains a race condition on NFS file systems: http://linux.die.net/man/2/open
+ if (fileHandle == -1)
+ return;
+ ZEN_ON_BLOCK_EXIT(::close(fileHandle));
+
+ const ssize_t bytesWritten = ::write(fileHandle, buffer, 1);
+ (void)bytesWritten;
+#endif
+ }
+
+private:
+ const Zstring lockfilename_; //thread local! atomic ref-count => binary value-type semantics!
+};
+
+
+namespace
+{
+UInt64 getLockFileSize(const Zstring& filename) //throw FileError, ErrorNotExisting
+{
+#ifdef FFS_WIN
+ WIN32_FIND_DATA fileInfo = {};
+ const HANDLE searchHandle = ::FindFirstFile(applyLongPathPrefix(filename).c_str(), &fileInfo);
+ if (searchHandle == INVALID_HANDLE_VALUE)
+ {
+ const DWORD lastError = ::GetLastError();
+
+ std::wstring errorMessage = _("Error reading file attributes:") + "\n\"" + filename + "\"" + "\n\n" + getLastErrorFormatted(lastError);
+
+ if (lastError == ERROR_FILE_NOT_FOUND ||
+ lastError == ERROR_PATH_NOT_FOUND ||
+ lastError == ERROR_BAD_NETPATH ||
+ lastError == ERROR_NETNAME_DELETED)
+ throw ErrorNotExisting(errorMessage);
+ else
+ throw FileError(errorMessage);
+ }
+
+ ::FindClose(searchHandle);
+
+ return zen::UInt64(fileInfo.nFileSizeLow, fileInfo.nFileSizeHigh);
+
+#elif defined FFS_LINUX
+ struct ::stat fileInfo = {};
+ if (::stat(filename.c_str(), &fileInfo) != 0) //follow symbolic links
+ {
+ const int lastError = errno;
+
+ std::wstring errorMessage = _("Error reading file attributes:") + "\n\"" + filename + "\"" + "\n\n" + getLastErrorFormatted(lastError);
+
+ if (lastError == ENOENT)
+ throw ErrorNotExisting(errorMessage);
+ else
+ throw FileError(errorMessage);
+ }
+
+ return zen::UInt64(fileInfo.st_size);
+#endif
+}
+
+
+Zstring deleteAbandonedLockName(const Zstring& lockfilename) //make sure to NOT change file ending!
+{
+ const size_t pos = lockfilename.rfind(FILE_NAME_SEPARATOR); //search from end
+ return pos == Zstring::npos ? Zstr("Del.") + lockfilename :
+ Zstring(lockfilename.c_str(), pos + 1) + //include path separator
+ Zstr("Del.") +
+ afterLast(lockfilename, FILE_NAME_SEPARATOR); //returns the whole string if ch not found
+}
+
+
+namespace
+{
+//read string from file stream
+inline
+std::string readString(wxInputStream& stream) //throw std::exception
+{
+ const auto strLength = readPOD<std::uint32_t>(stream);
+ std::string output;
+ if (strLength > 0)
+ {
+ output.resize(strLength); //throw std::bad_alloc
+ stream.Read(&output[0], strLength);
+ }
+ return output;
+}
+
+
+inline
+void writeString(wxOutputStream& stream, const std::string& str) //write string to filestream
+{
+ writePOD(stream, static_cast<std::uint32_t>(str.length()));
+ stream.Write(str.c_str(), str.length());
+}
+
+
+std::string getComputerId() //returns empty string on error
+{
+ const wxString fhn = ::wxGetFullHostName();
+ if (fhn.empty()) return std::string();
+#ifdef FFS_WIN
+ return "Windows " + std::string(fhn.ToUTF8());
+#elif defined FFS_LINUX
+ return "Linux " + std::string(fhn.ToUTF8());
+#endif
+}
+
+
+struct LockInformation
+{
+ LockInformation()
+ {
+ lockId = zen::generateGUID();
+#ifdef FFS_WIN
+ procDescr.processId = ::GetCurrentProcessId();
+#elif defined FFS_LINUX
+ procDescr.processId = ::getpid();
+#endif
+ procDescr.computerId = getComputerId();
+ }
+
+ LockInformation(wxInputStream& stream) //read
+ {
+ char formatDescr[sizeof(LOCK_FORMAT_DESCR)] = {};
+ stream.Read(formatDescr, sizeof(LOCK_FORMAT_DESCR)); //file format header
+ const int lockFileVersion = readPOD<boost::int32_t>(stream); //
+ (void)lockFileVersion;
+
+ //some format checking here?
+
+ lockId = readString(stream);
+ procDescr.processId = static_cast<ProcessId>(readPOD<std::uint64_t>(stream)); //possible loss of precision (32/64 bit process) covered by buildId
+ procDescr.computerId = readString(stream);
+ }
+
+ void toStream(wxOutputStream& stream) const //write
+ {
+ assert_static(sizeof(ProcessId) <= sizeof(std::uint64_t)); //ensure portability
+
+ stream.Write(LOCK_FORMAT_DESCR, sizeof(LOCK_FORMAT_DESCR));
+ writePOD<boost::int32_t>(stream, LOCK_FORMAT_VER);
+
+ writeString(stream, lockId);
+ writePOD<std::uint64_t>(stream, procDescr.processId);
+ writeString(stream, procDescr.computerId);
+ }
+
+#ifdef FFS_WIN
+ typedef DWORD ProcessId; //same size on 32 and 64 bit windows!
+#elif defined FFS_LINUX
+ typedef pid_t ProcessId;
+#endif
+
+ std::string lockId; //16 byte UUID
+
+ struct ProcessDescription
+ {
+ ProcessId processId;
+ std::string computerId;
+ } procDescr;
+};
+
+
+//true: process not available, false: cannot say anything
+enum ProcessStatus
+{
+ PROC_STATUS_NOT_RUNNING,
+ PROC_STATUS_RUNNING,
+ PROC_STATUS_ITS_US,
+ PROC_STATUS_NO_IDEA
+};
+ProcessStatus getProcessStatus(const LockInformation::ProcessDescription& procDescr)
+{
+ if (procDescr.computerId != getComputerId() ||
+ procDescr.computerId.empty()) //both names are empty
+ return PROC_STATUS_NO_IDEA; //lock owned by different computer
+
+#ifdef FFS_WIN
+ if (procDescr.processId == ::GetCurrentProcessId()) //may seem obscure, but it's possible: a lock file is "stolen" and put back while the program is running
+ return PROC_STATUS_ITS_US;
+
+ //note: ::OpenProcess() is no option as it may successfully return for crashed processes!
+ HANDLE snapshot = ::CreateToolhelp32Snapshot(
+ TH32CS_SNAPPROCESS, //__in DWORD dwFlags,
+ 0); //__in DWORD th32ProcessID
+ if (snapshot == INVALID_HANDLE_VALUE)
+ return PROC_STATUS_NO_IDEA;
+ ZEN_ON_BLOCK_EXIT(::CloseHandle(snapshot));
+
+ PROCESSENTRY32 processEntry = {};
+ processEntry.dwSize = sizeof(processEntry);
+
+ if (!::Process32First(snapshot, //__in HANDLE hSnapshot,
+ &processEntry)) //__inout LPPROCESSENTRY32 lppe
+ return PROC_STATUS_NO_IDEA;
+ do
+ {
+ if (processEntry.th32ProcessID == procDescr.processId)
+ return PROC_STATUS_RUNNING; //process still running
+ }
+ while (::Process32Next(snapshot, &processEntry));
+
+ return PROC_STATUS_NOT_RUNNING;
+
+#elif defined FFS_LINUX
+ if (procDescr.processId == ::getpid()) //may seem obscure, but it's possible: a lock file is "stolen" and put back while the program is running
+ return PROC_STATUS_ITS_US;
+
+ if (procDescr.processId <= 0 || procDescr.processId >= 65536)
+ return PROC_STATUS_NO_IDEA; //invalid process id
+
+ return zen::dirExists(Zstr("/proc/") + zen::toString<Zstring>(procDescr.processId)) ? PROC_STATUS_RUNNING : PROC_STATUS_NOT_RUNNING;
+#endif
+}
+
+
+void writeLockInfo(const Zstring& lockfilename) //throw FileError
+{
+ //write GUID at the beginning of the file: this ID is a universal identifier for this lock (no matter what the path is, considering symlinks, distributed network, etc.)
+ FileOutputStream lockFile(lockfilename); //throw FileError
+ LockInformation().toStream(lockFile);
+}
+
+
+LockInformation retrieveLockInfo(const Zstring& lockfilename) //throw FileError, ErrorNotExisting
+{
+ //read GUID from beginning of file
+ FileInputStream lockFile(lockfilename); //throw FileError, ErrorNotExisting
+ return LockInformation(lockFile);
+}
+
+
+std::string retrieveLockId(const Zstring& lockfilename) //throw FileError, ErrorNotExisting
+{
+ return retrieveLockInfo(lockfilename).lockId;
+}
+}
+
+
+void waitOnDirLock(const Zstring& lockfilename, DirLockCallback* callback) //throw FileError
+{
+ std::wstring infoMsg = _("Waiting while directory is locked (%x)...");
+ replace(infoMsg, L"%x", std::wstring(L"\"") + lockfilename + "\"");
+ if (callback)
+ callback->reportInfo(infoMsg);
+ //---------------------------------------------------------------
+ try
+ {
+ const LockInformation lockInfo = retrieveLockInfo(lockfilename); //throw FileError, ErrorNotExisting
+
+ bool lockOwnderDead = false; //convenience optimization: if we know the owning process crashed, we needn't wait DETECT_EXITUS_INTERVAL sec
+ switch (getProcessStatus(lockInfo.procDescr))
+ {
+ case PROC_STATUS_ITS_US: //since we've already passed LockAdmin, the lock file seems abandoned ("stolen"?) although it's from this process
+ case PROC_STATUS_NOT_RUNNING:
+ lockOwnderDead = true;
+ break;
+ case PROC_STATUS_RUNNING:
+ case PROC_STATUS_NO_IDEA:
+ break;
+ }
+
+ zen::UInt64 fileSizeOld;
+ wxLongLong lockSilentStart = wxGetLocalTimeMillis();
+
+ while (true)
+ {
+ const zen::UInt64 fileSizeNew = ::getLockFileSize(lockfilename); //throw FileError, ErrorNotExisting
+ wxLongLong currentTime = wxGetLocalTimeMillis();
+
+ if (fileSizeNew != fileSizeOld)
+ {
+ //received life sign from lock
+ fileSizeOld = fileSizeNew;
+ lockSilentStart = currentTime;
+ }
+
+ if (lockOwnderDead || //no need to wait any longer...
+ currentTime - lockSilentStart > DETECT_EXITUS_INTERVAL)
+ {
+ DirLock dummy(deleteAbandonedLockName(lockfilename), callback); //throw FileError
+
+ //now that the lock is in place check existence again: meanwhile another process may have deleted and created a new lock!
+
+ if (retrieveLockId(lockfilename) != lockInfo.lockId) //throw FileError, ErrorNotExisting
+ return; //another process has placed a new lock, leave scope: the wait for the old lock is technically over...
+
+ if (getLockFileSize(lockfilename) != fileSizeOld) //throw FileError, ErrorNotExisting
+ continue; //belated lifesign
+
+ removeFile(lockfilename); //throw FileError
+ return;
+ }
+
+ //wait some time...
+ const size_t GUI_CALLBACK_INTERVAL = 100;
+ for (size_t i = 0; i < POLL_LIFE_SIGN_INTERVAL / GUI_CALLBACK_INTERVAL; ++i)
+ {
+ if (callback) callback->requestUiRefresh();
+ wxMilliSleep(GUI_CALLBACK_INTERVAL);
+
+ //show some countdown on abandoned locks
+ if (callback)
+ {
+ if (currentTime - lockSilentStart > EMIT_LIFE_SIGN_INTERVAL) //one signal missed: it's likely this is an abandoned lock:
+ {
+ long remainingSeconds = ((DETECT_EXITUS_INTERVAL - (wxGetLocalTimeMillis() - lockSilentStart)) / 1000).ToLong();
+ remainingSeconds = std::max(0L, remainingSeconds);
+
+ std::wstring remSecMsg = _P("1 sec", "%x sec", remainingSeconds);
+ replace(remSecMsg, L"%x", toString<std::wstring>(remainingSeconds));
+ callback->reportInfo(infoMsg + " " + remSecMsg);
+ }
+ else
+ callback->reportInfo(infoMsg); //emit a message in any case (might clear other one)
+ }
+ }
+ }
+ }
+ catch (const ErrorNotExisting&)
+ {
+ return; //what we are waiting for...
+ }
+}
+
+
+void releaseLock(const Zstring& lockfilename) //throw ()
+{
+ try
+ {
+ removeFile(lockfilename);
+ }
+ catch (...) {}
+}
+
+
+bool tryLock(const Zstring& lockfilename) //throw FileError
+{
+#ifdef FFS_WIN
+ const HANDLE fileHandle = ::CreateFile(applyLongPathPrefix(lockfilename).c_str(),
+ GENERIC_READ | GENERIC_WRITE, //use both when writing over network, see comment in file_io.cpp
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ 0,
+ CREATE_NEW,
+ FILE_ATTRIBUTE_NORMAL,
+ NULL);
+ if (fileHandle == INVALID_HANDLE_VALUE)
+ {
+#ifndef _MSC_VER
+#warning fix this problem!
+ //read-only FTP may return: ERROR_FILE_EXISTS (NetDrive @ GNU)
+#endif
+ if (::GetLastError() == ERROR_FILE_EXISTS)
+ return false;
+ else
+ throw FileError(_("Error setting directory lock:") + "\n\"" + lockfilename + "\"" + "\n\n" + getLastErrorFormatted());
+ }
+ ::CloseHandle(fileHandle);
+
+#elif defined FFS_LINUX
+ //O_EXCL contains a race condition on NFS file systems: http://linux.die.net/man/2/open
+ ::umask(0); //important!
+ const int fileHandle = ::open(lockfilename.c_str(), O_CREAT | O_WRONLY | O_EXCL, S_IRWXU | S_IRWXG | S_IRWXO);
+ if (fileHandle == -1)
+ {
+ if (errno == EEXIST)
+ return false;
+ else
+ throw FileError(_("Error setting directory lock:") + "\n\"" + lockfilename + "\"" + "\n\n" + getLastErrorFormatted());
+ }
+ ::close(fileHandle);
+#endif
+
+ zen::ScopeGuard guardLockFile = zen::makeGuard([&]() { zen::removeFile(lockfilename); });
+
+ //write UUID at the beginning of the file: this ID is a universal identifier for this lock (no matter what the path is, considering symlinks, etc.)
+ writeLockInfo(lockfilename); //throw FileError
+
+ guardLockFile.dismiss(); //lockfile created successfully
+ return true;
+}
+}
+
+
+class DirLock::SharedDirLock
+{
+public:
+ SharedDirLock(const Zstring& lockfilename, DirLockCallback* callback = NULL) : //throw FileError
+ lockfilename_(lockfilename)
+ {
+ while (!::tryLock(lockfilename)) //throw FileError
+ ::waitOnDirLock(lockfilename, callback); //
+
+ threadObj = boost::thread(LifeSigns(lockfilename));
+ }
+
+ ~SharedDirLock()
+ {
+ threadObj.interrupt(); //thread lifetime is subset of this instances's life
+ threadObj.join();
+
+ ::releaseLock(lockfilename_); //throw ()
+ }
+
+private:
+ SharedDirLock(const DirLock&);
+ SharedDirLock& operator=(const DirLock&);
+
+ const Zstring lockfilename_;
+
+ boost::thread threadObj;
+};
+
+
+class DirLock::LockAdmin //administrate all locks held by this process to avoid deadlock by recursion
+{
+public:
+ static LockAdmin& instance()
+ {
+ static LockAdmin inst;
+ return inst;
+ }
+
+ //create or retrieve a SharedDirLock
+ std::shared_ptr<SharedDirLock> retrieve(const Zstring& lockfilename, DirLockCallback* callback) //throw FileError
+ {
+ //optimization: check if there is an active(!) lock for "lockfilename"
+ FileToUuidMap::const_iterator iterUuid = fileToUuid.find(lockfilename);
+ if (iterUuid != fileToUuid.end())
+ {
+ const std::shared_ptr<SharedDirLock>& activeLock = findActive(iterUuid->second); //returns null-lock if not found
+ if (activeLock)
+ return activeLock; //SharedDirLock is still active -> enlarge circle of shared ownership
+ }
+
+ try //actual check based on lock UUID, deadlock prevention: "lockfilename" may be an alternative name for an already active lock
+ {
+ const std::string lockId = retrieveLockId(lockfilename); //throw FileError, ErrorNotExisting
+
+ const std::shared_ptr<SharedDirLock>& activeLock = findActive(lockId); //returns null-lock if not found
+ if (activeLock)
+ {
+ fileToUuid[lockfilename] = lockId; //perf-optimization: update relation
+ return activeLock;
+ }
+ }
+ catch (...) {} //catch everything, let SharedDirLock constructor deal with errors, e.g. 0-sized/corrupted lock file
+
+ //not yet in buffer, so create a new directory lock
+ std::shared_ptr<SharedDirLock> newLock(new SharedDirLock(lockfilename, callback)); //throw FileError
+ const std::string newLockId = retrieveLockId(lockfilename); //throw FileError, ErrorNotExisting
+
+ //update registry
+ fileToUuid[lockfilename] = newLockId; //throw()
+ uuidToLock[newLockId] = newLock; //
+
+ return newLock;
+ }
+
+private:
+ LockAdmin() {}
+
+ std::shared_ptr<SharedDirLock> findActive(const std::string& lockId) //returns null-lock if not found
+ {
+ UuidToLockMap::const_iterator iterLock = uuidToLock.find(lockId);
+ return iterLock != uuidToLock.end() ?
+ iterLock->second.lock() : //try to get shared_ptr; throw()
+ std::shared_ptr<SharedDirLock>();
+ }
+
+ typedef std::weak_ptr<SharedDirLock> SharedLock;
+
+ typedef std::string UniqueId;
+ typedef std::map<Zstring, UniqueId, LessFilename> FileToUuidMap; //n:1 handle uppper/lower case correctly
+ typedef std::map<UniqueId, SharedLock> UuidToLockMap; //1:1
+
+ FileToUuidMap fileToUuid; //lockname |-> UUID; locks can be referenced by a lockfilename or alternatively a UUID
+ UuidToLockMap uuidToLock; //UUID |-> "shared lock ownership"
+};
+
+
+DirLock::DirLock(const Zstring& lockfilename, DirLockCallback* callback) //throw FileError
+{
+#ifdef FFS_WIN
+ const DWORD bufferSize = std::max(lockfilename.size(), static_cast<size_t>(10000));
+ std::vector<wchar_t> volName(bufferSize);
+ if (::GetVolumePathName(lockfilename.c_str(), //__in LPCTSTR lpszFileName,
+ &volName[0], //__out LPTSTR lpszVolumePathName,
+ bufferSize)) //__in DWORD cchBufferLength
+ {
+ DWORD dt = ::GetDriveType(&volName[0]);
+ if (dt == DRIVE_CDROM)
+ return; //we don't need a lock for a CD ROM
+ }
+#endif
+
+ sharedLock = LockAdmin::instance().retrieve(lockfilename, callback);
+}
diff --git a/lib/dir_lock.h b/lib/dir_lock.h
new file mode 100644
index 00000000..c9a16c62
--- /dev/null
+++ b/lib/dir_lock.h
@@ -0,0 +1,35 @@
+#ifndef DIR_LOCK_H_INCLUDED
+#define DIR_LOCK_H_INCLUDED
+
+#include <memory>
+#include <zen/file_error.h>
+
+
+struct DirLockCallback //while waiting for the lock
+{
+ virtual ~DirLockCallback() {}
+ virtual void requestUiRefresh() = 0; //allowed to throw exceptions
+ virtual void reportInfo(const std::wstring& text) = 0;
+};
+
+/*
+RAII structure to place a directory lock against other FFS processes:
+ - recursive locking supported, even with alternate lockfile names, e.g. via symlinks, network mounts etc.
+ - ownership shared between all object instances refering to a specific lock location(= UUID)
+ - can be copied safely and efficiently! (ref-counting)
+ - detects and resolves abandoned locks (instantly if lock is associated with local pc, else after 30 seconds)
+ - temporary locks created during abandoned lock resolution keep "lockfilename"'s extension
+ - race-free (Windows, almost on Linux(NFS))
+*/
+class DirLock
+{
+public:
+ DirLock(const Zstring& lockfilename, DirLockCallback* callback = NULL); //throw FileError, callback only used during construction
+
+private:
+ class LockAdmin;
+ class SharedDirLock;
+ std::shared_ptr<SharedDirLock> sharedLock;
+};
+
+#endif // DIR_LOCK_H_INCLUDED
diff --git a/lib/dir_name.cpp b/lib/dir_name.cpp
new file mode 100644
index 00000000..010fb218
--- /dev/null
+++ b/lib/dir_name.cpp
@@ -0,0 +1,174 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "dir_name.h"
+#include <wx/dnd.h>
+#include <wx/window.h>
+#include <wx/textctrl.h>
+#include <wx/statbox.h>
+#include <zen/thread.h>
+#include <zen/file_handling.h>
+#include "resolve_path.h"
+#include <wx+/string_conv.h>
+#include "folder_history_box.h"
+
+using namespace zen;
+
+
+namespace
+{
+void setDirectoryNameImpl(const wxString& dirname, wxDirPickerCtrl* dirPicker, wxWindow& tooltipWnd, wxStaticBoxSizer* staticBox, size_t timeout)
+{
+ const wxString dirFormatted = toWx(getFormattedDirectoryName(toZ(dirname)));
+
+ tooltipWnd.SetToolTip(dirFormatted); //wxComboBox bug: the edit control is not updated... http://trac.wxwidgets.org/ticket/12659
+
+ if (staticBox)
+ {
+ //change static box label only if there is a real difference to what is shown in wxTextCtrl anyway
+ wxString dirNormalized = dirname;
+ trim(dirNormalized);
+ if (!dirNormalized.empty() && !endsWith(dirNormalized, FILE_NAME_SEPARATOR))
+ dirNormalized += FILE_NAME_SEPARATOR;
+
+ staticBox->GetStaticBox()->SetLabel(dirNormalized == dirFormatted ? wxString(_("Drag && drop")) : dirFormatted);
+ }
+
+ if (dirPicker && !dirFormatted.empty())
+ {
+ Zstring dir = toZ(dirFormatted); //convert to Zstring first: we don't want to pass wxString by value and risk MT issues!
+ auto ft = async([=]() { return zen::dirExists(dir); });
+
+ if (ft.timed_wait(boost::posix_time::milliseconds(timeout)) && ft.get()) //potentially slow network access: wait 200ms at most
+ dirPicker->SetPath(dirFormatted);
+ }
+}
+
+
+void setDirectoryName(const wxString& dirname,
+ wxTextCtrl* txtCtrl,
+ wxDirPickerCtrl* dirPicker,
+ wxWindow& tooltipWnd,
+ wxStaticBoxSizer* staticBox,
+ size_t timeout = 200) //pointers are optional
+{
+ if (txtCtrl)
+ txtCtrl->ChangeValue(dirname);
+ setDirectoryNameImpl(dirname, dirPicker, tooltipWnd, staticBox, timeout);
+}
+
+
+void setDirectoryName(const wxString& dirname,
+ FolderHistoryBox* comboBox,
+ wxDirPickerCtrl* dirPicker,
+ wxWindow& tooltipWnd,
+ wxStaticBoxSizer* staticBox,
+ size_t timeout = 200) //pointers are optional
+{
+ if (comboBox)
+ comboBox->setValue(dirname);
+ setDirectoryNameImpl(dirname, dirPicker, tooltipWnd, staticBox, timeout);
+}
+}
+//##############################################################################################################
+
+template <class NameControl>
+DirectoryName<NameControl>::DirectoryName(wxWindow& dropWindow,
+ wxDirPickerCtrl& dirPicker,
+ NameControl& dirName,
+ wxStaticBoxSizer* staticBox,
+ wxWindow* dropWindow2) :
+ dropWindow_(dropWindow),
+ dropWindow2_(dropWindow2),
+ dirPicker_(dirPicker),
+ dirName_(dirName),
+ staticBox_(staticBox)
+{
+ //prepare drag & drop
+ setupFileDrop(dropWindow);
+ if (dropWindow2)
+ setupFileDrop(*dropWindow2);
+
+ //redirect drag & drop event back to this class
+ dropWindow.Connect(FFS_DROP_FILE_EVENT, FFSFileDropEventHandler(DirectoryName::OnFilesDropped), NULL, this);
+ if (dropWindow2)
+ dropWindow2->Connect(FFS_DROP_FILE_EVENT, FFSFileDropEventHandler(DirectoryName::OnFilesDropped), NULL, this);
+
+ //keep dirPicker and dirName synchronous
+ dirName_ .Connect(wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( DirectoryName::OnWriteDirManually), NULL, this);
+ dirPicker_.Connect(wxEVT_COMMAND_DIRPICKER_CHANGED, wxFileDirPickerEventHandler(DirectoryName::OnDirSelected ), NULL, this);
+}
+
+
+template <class NameControl>
+DirectoryName<NameControl>::~DirectoryName()
+{
+ dirName_ .Disconnect(wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( DirectoryName::OnWriteDirManually), NULL, this);
+ dirPicker_.Disconnect(wxEVT_COMMAND_DIRPICKER_CHANGED, wxFileDirPickerEventHandler(DirectoryName::OnDirSelected ), NULL, this);
+}
+
+
+template <class NameControl>
+void DirectoryName<NameControl>::OnFilesDropped(FFSFileDropEvent& event)
+{
+ if (event.getFiles().empty())
+ return;
+
+ if (acceptDrop(event.getFiles()))
+ {
+ const wxString fileName = event.getFiles()[0];
+ if (dirExists(toZ(fileName)))
+ setDirectoryName(fileName, &dirName_, &dirPicker_, dirName_, staticBox_);
+ else
+ {
+ wxString parentName = beforeLast(fileName, FILE_NAME_SEPARATOR); //returns empty string if ch not found
+#ifdef FFS_WIN
+ if (endsWith(parentName, L":")) //volume name
+ parentName += FILE_NAME_SEPARATOR;
+#endif
+ if (dirExists(toZ(parentName)))
+ setDirectoryName(parentName, &dirName_, &dirPicker_, dirName_, staticBox_);
+ else //set original name unconditionally: usecase: inactive mapped network shares
+ setDirectoryName(fileName, &dirName_, &dirPicker_, dirName_, staticBox_);
+ }
+ }
+}
+
+
+template <class NameControl>
+void DirectoryName<NameControl>::OnWriteDirManually(wxCommandEvent& event)
+{
+ setDirectoryName(event.GetString(), static_cast<NameControl*>(NULL), &dirPicker_, dirName_, staticBox_, 100); //potentially slow network access: wait 100 ms at most
+ event.Skip();
+}
+
+
+template <class NameControl>
+void DirectoryName<NameControl>::OnDirSelected(wxFileDirPickerEvent& event)
+{
+ const wxString newPath = event.GetPath();
+ setDirectoryName(newPath, &dirName_, NULL, dirName_, staticBox_);
+ event.Skip();
+}
+
+
+template <class NameControl>
+wxString DirectoryName<NameControl>::getName() const
+{
+ return dirName_.GetValue();
+}
+
+
+template <class NameControl>
+void DirectoryName<NameControl>::setName(const wxString& dirname)
+{
+ setDirectoryName(dirname, &dirName_, &dirPicker_, dirName_, staticBox_);
+}
+
+
+//explicit template instantiations
+template class DirectoryName<wxTextCtrl>;
+template class DirectoryName<FolderHistoryBox>;
diff --git a/lib/dir_name.h b/lib/dir_name.h
new file mode 100644
index 00000000..43f2015d
--- /dev/null
+++ b/lib/dir_name.h
@@ -0,0 +1,51 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef DRAGANDDROP_H_INCLUDED
+#define DRAGANDDROP_H_INCLUDED
+
+#include <vector>
+#include <wx/event.h>
+#include <wx/sizer.h>
+#include <wx/filepicker.h>
+#include <wx+/file_drop.h>
+
+namespace zen
+{
+//handle drag and drop, tooltip, label and manual input, coordinating a wxWindow, wxDirPickerCtrl, and wxComboBox/wxTextCtrl
+
+template <class NameControl> //NameControl may be wxTextCtrl, FolderHistoryBox
+class DirectoryName: private wxEvtHandler
+{
+public:
+ DirectoryName(wxWindow& dropWindow,
+ wxDirPickerCtrl& dirPicker,
+ NameControl& dirName,
+ wxStaticBoxSizer* staticBox = NULL,
+ wxWindow* dropWindow2 = NULL); //optional
+
+ ~DirectoryName();
+
+ wxString getName() const;
+ void setName(const wxString& dirname);
+
+private:
+ virtual bool acceptDrop(const std::vector<wxString>& droppedFiles) { return true; }; //return true if drop should be processed
+
+ void OnFilesDropped(FFSFileDropEvent& event);
+ void OnWriteDirManually(wxCommandEvent& event);
+ void OnDirSelected(wxFileDirPickerEvent& event);
+
+ const wxWindow& dropWindow_;
+ const wxWindow* dropWindow2_;
+ wxDirPickerCtrl& dirPicker_;
+ NameControl& dirName_;
+ wxStaticBoxSizer* staticBox_; //optional
+};
+}
+
+
+#endif // DRAGANDDROP_H_INCLUDED
diff --git a/lib/error_log.cpp b/lib/error_log.cpp
new file mode 100644
index 00000000..fe423479
--- /dev/null
+++ b/lib/error_log.cpp
@@ -0,0 +1,99 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "error_log.h"
+#include <wx/datetime.h>
+#include <zen/i18n.h>
+#include <algorithm>
+
+using namespace zen;
+
+
+void ErrorLogging::logMsg(const wxString& message, zen::MessageType type)
+{
+ Entry newEntry;
+ newEntry.type = type;
+ newEntry.time = wxDateTime::GetTimeNow();
+ newEntry.message = message;
+
+ messages.push_back(newEntry);
+
+ ++statistics[type];
+}
+
+
+int ErrorLogging::typeCount(int typeFilter) const
+{
+ int count = 0;
+
+ if (typeFilter & TYPE_INFO)
+ count += statistics[TYPE_INFO];
+ if (typeFilter & TYPE_WARNING)
+ count += statistics[TYPE_WARNING];
+ if (typeFilter & TYPE_ERROR)
+ count += statistics[TYPE_ERROR];
+ if (typeFilter & TYPE_FATAL_ERROR)
+ count += statistics[TYPE_FATAL_ERROR];
+
+ return count;
+}
+
+
+std::vector<wxString> ErrorLogging::getFormattedMessages(int typeFilter) const
+{
+ std::vector<wxString> output;
+
+ std::for_each(messages.begin(), messages.end(),
+ [&](const Entry& entry)
+ {
+ if (entry.type & typeFilter)
+ output.push_back(formatMessage(entry));
+ });
+
+ return output;
+}
+
+
+wxString ErrorLogging::formatMessage(const Entry& msg)
+{
+ wxString typeName;
+ switch (msg.type)
+ {
+ case TYPE_INFO:
+ typeName = _("Info");
+ break;
+ case TYPE_WARNING:
+ typeName = _("Warning");
+ break;
+ case TYPE_ERROR:
+ typeName = _("Error");
+ break;
+ case TYPE_FATAL_ERROR:
+ typeName = _("Fatal Error");
+ break;
+ }
+
+ const wxString prefix = wxString(L"[") + wxDateTime(msg.time).FormatTime() + L"] " + typeName + L": ";
+
+ wxString formattedText = prefix;
+ for (auto i = msg.message.begin(); i != msg.message.end(); ++i)
+ if (*i == wxChar('\n'))
+ {
+ formattedText += L'\n';
+
+ wxString blanks;
+ blanks.resize(prefix.size(), L' ');
+ formattedText += blanks;
+
+ while (*++i == L'\n') //remove duplicate newlines
+ ;
+ --i;
+ }
+ else
+ formattedText += *i;
+
+ return formattedText;
+}
diff --git a/lib/error_log.h b/lib/error_log.h
new file mode 100644
index 00000000..a0b7ab90
--- /dev/null
+++ b/lib/error_log.h
@@ -0,0 +1,49 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef ERRORLOGGING_H_INCLUDED
+#define ERRORLOGGING_H_INCLUDED
+
+#include <map>
+#include <vector>
+#include <wx/string.h>
+
+namespace zen
+{
+enum MessageType
+{
+ TYPE_INFO = 1,
+ TYPE_WARNING = 2,
+ TYPE_ERROR = 4,
+ TYPE_FATAL_ERROR = 8,
+};
+
+class ErrorLogging
+{
+public:
+ void logMsg(const wxString& message, MessageType type);
+
+ int typeCount(int typeFilter = TYPE_INFO | TYPE_WARNING | TYPE_ERROR | TYPE_FATAL_ERROR) const;
+
+ std::vector<wxString> getFormattedMessages(int typeFilter = TYPE_INFO | TYPE_WARNING | TYPE_ERROR | TYPE_FATAL_ERROR) const;
+
+private:
+ struct Entry
+ {
+ MessageType type;
+ time_t time;
+ wxString message;
+ };
+
+ static wxString formatMessage(const Entry& msg);
+
+ std::vector<Entry> messages; //list of non-resolved errors and warnings
+
+ mutable std::map<MessageType, int> statistics;
+};
+}
+
+#endif // ERRORLOGGING_H_INCLUDED
diff --git a/lib/ffs_paths.h b/lib/ffs_paths.h
new file mode 100644
index 00000000..faecd2e2
--- /dev/null
+++ b/lib/ffs_paths.h
@@ -0,0 +1,127 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef STANDARDPATHS_H_INCLUDED
+#define STANDARDPATHS_H_INCLUDED
+
+#include <wx/stdpaths.h>
+#include <zen/zstring.h>
+#include <wx+/string_conv.h>
+
+namespace zen
+{
+//------------------------------------------------------------------------------
+//global program directories
+//------------------------------------------------------------------------------
+wxString getResourceDir(); //resource directory WITH path separator at end
+wxString getConfigDir(); //config directory WITH path separator at end
+//------------------------------------------------------------------------------
+
+wxString getLauncher(); //full path to application launcher C:\...\FreeFileSync.exe
+bool isPortableVersion();
+
+
+
+
+
+
+
+
+
+
+
+
+//---------------- implementation ----------------
+namespace impl
+{
+inline
+const wxString& getBinaryDir() //directory containing executable WITH path separator at end
+{
+ static wxString instance = beforeLast(wxStandardPaths::Get().GetExecutablePath(), FILE_NAME_SEPARATOR) + toWx(Zstring(FILE_NAME_SEPARATOR)); //extern linkage!
+ return instance;
+}
+
+#ifdef FFS_WIN
+inline
+wxString getInstallDir() //root install directory WITH path separator at end
+{
+ return getBinaryDir().BeforeLast(FILE_NAME_SEPARATOR).BeforeLast(FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR;
+}
+#endif
+}
+
+
+inline
+bool isPortableVersion()
+{
+#ifdef FFS_WIN
+ static const bool isPortable = !wxFileExists(impl::getInstallDir() + wxT("uninstall.exe")); //this check is a bit lame...
+#elif defined FFS_LINUX
+ static const bool isPortable = !impl::getBinaryDir().EndsWith(wxT("/bin/")); //this check is a bit lame...
+#endif
+ return isPortable;
+}
+
+
+inline
+wxString getResourceDir()
+{
+#ifdef FFS_WIN
+ return impl::getInstallDir();
+#elif defined FFS_LINUX
+ if (isPortableVersion())
+ return impl::getBinaryDir();
+ else //use OS' standard paths
+ {
+ wxString resourceDir = wxStandardPathsBase::Get().GetResourcesDir();
+
+ if (!endsWith(resourceDir, FILE_NAME_SEPARATOR))
+ resourceDir += FILE_NAME_SEPARATOR;
+
+ return resourceDir;
+ }
+#endif
+}
+
+
+inline
+wxString getConfigDir()
+{
+ if (isPortableVersion())
+#ifdef FFS_WIN
+ return impl::getInstallDir();
+#elif defined FFS_LINUX
+ //wxString(wxT(".")) + zToWx(FILE_NAME_SEPARATOR) -> don't use current working directory
+ //avoid surprises with GlobalSettings.xml being newly created in each working directory
+ return impl::getBinaryDir();
+#endif
+ else //use OS' standard paths
+ {
+ wxString userDirectory = wxStandardPathsBase::Get().GetUserDataDir();
+
+ if (!wxDirExists(userDirectory))
+ ::wxMkdir(userDirectory); //only top directory needs to be created: no recursion necessary
+
+ if (!endsWith(userDirectory, FILE_NAME_SEPARATOR))
+ userDirectory += FILE_NAME_SEPARATOR;
+
+ return userDirectory;
+ }
+}
+
+
+inline
+wxString getLauncher()
+{
+#ifdef FFS_WIN
+ return impl::getInstallDir() + wxT("FreeFileSync.exe");
+#elif defined FFS_LINUX
+ return impl::getBinaryDir() + wxT("FreeFileSync");
+#endif
+}
+}
+
+#endif // STANDARDPATHS_H_INCLUDED
diff --git a/lib/folder_history_box.cpp b/lib/folder_history_box.cpp
new file mode 100644
index 00000000..b395cbf4
--- /dev/null
+++ b/lib/folder_history_box.cpp
@@ -0,0 +1,142 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "folder_history_box.h"
+#include <list>
+#include "resolve_path.h"
+
+using namespace zen;
+
+
+FolderHistoryBox::FolderHistoryBox(wxWindow* parent,
+ wxWindowID id,
+ const wxString& value,
+ const wxPoint& pos,
+ const wxSize& size,
+ int n,
+ const wxString choices[],
+ long style,
+ const wxValidator& validator,
+ const wxString& name) :
+ wxComboBox(parent, id, value, pos, size, n, choices, style, validator, name)
+#if wxCHECK_VERSION(2, 9, 1)
+ , dropDownShown(false)
+#endif
+{
+ //#####################################
+ /*##*/ SetMinSize(wxSize(150, -1)); //## workaround yet another wxWidgets bug: default minimum size is much too large for a wxComboBox
+ //#####################################
+
+ //register key event to enable item deletion
+ Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(FolderHistoryBox::OnKeyEvent), NULL, this);
+
+ //refresh history list on mouse click
+ Connect(wxEVT_LEFT_DOWN, wxEventHandler(FolderHistoryBox::OnUpdateList), NULL, this);
+
+ Connect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(FolderHistoryBox::OnSelection), NULL, this);
+
+#if wxCHECK_VERSION(2, 9, 1)
+ Connect(wxEVT_COMMAND_COMBOBOX_DROPDOWN, wxCommandEventHandler(FolderHistoryBox::OnShowDropDown), NULL, this);
+ Connect(wxEVT_COMMAND_COMBOBOX_CLOSEUP, wxCommandEventHandler(FolderHistoryBox::OnHideDropDown), NULL, this);
+#endif
+}
+
+
+#if wxCHECK_VERSION(2, 9, 1)
+void FolderHistoryBox::OnShowDropDown(wxCommandEvent& event)
+{
+ dropDownShown = true;
+ event.Skip();
+}
+
+
+void FolderHistoryBox::OnHideDropDown(wxCommandEvent& event)
+{
+ dropDownShown = false;
+ event.Skip();
+}
+#endif
+
+
+void FolderHistoryBox::update(const wxString& dirname)
+{
+ //it may be a little lame to update the list on each mouse-button click, but it should be working and we dont't have to manipulate wxComboBox internals
+
+ std::list<Zstring> dirList;
+
+ //add some aliases to allow user changing to volume name and back, if possible
+#ifdef FFS_WIN
+ const Zstring activePath = toZ(GetValue());
+ std::vector<Zstring> aliases = getDirectoryAliases(activePath);
+ dirList.insert(dirList.end(), aliases.begin(), aliases.end());
+#endif
+
+ if (sharedHistory_.get())
+ {
+ auto tmp = sharedHistory_->getList();
+ //std::sort(tmp.begin(), tmp.end(), LessFilename());
+
+ if (!dirList.empty() && !tmp.empty())
+ dirList.push_back(FolderHistory::lineSeparator());
+
+ dirList.insert(dirList.end(), tmp.begin(), tmp.end());
+ }
+ //###########################################################################################
+
+ //attention: if the target value is not part of the dropdown list, SetValue() will look for a string that *starts with* this value:
+ //e.g. if the dropdown list contains "222" SetValue("22") will erroneously set and select "222" instead, while "111" would be set correctly!
+ // -> by design on Windows!
+ if (std::find(dirList.begin(), dirList.end(), toZ(dirname)) == dirList.end())
+ dirList.push_front(toZ(dirname));
+
+ Clear();
+ std::for_each(dirList.begin(), dirList.end(), [&](const Zstring& dir) { this->Append(toWx(dir)); });
+ //this->SetSelection(wxNOT_FOUND); //don't select anything
+ this->SetValue(dirname); //preserve main text!
+}
+
+
+void FolderHistoryBox::OnSelection(wxCommandEvent& event)
+{
+ event.Skip();
+}
+
+
+void FolderHistoryBox::OnKeyEvent(wxKeyEvent& event)
+{
+ const int keyCode = event.GetKeyCode();
+ if (keyCode == WXK_DELETE || keyCode == WXK_NUMPAD_DELETE)
+ {
+ //try to delete the currently selected config history item
+ int pos = this->GetCurrentSelection();
+ if (0 <= pos && pos < static_cast<int>(this->GetCount()) &&
+#if wxCHECK_VERSION(2, 9, 1)
+ dropDownShown)
+#else
+ //what a mess...:
+ (GetValue() != GetString(pos) || //avoid problems when a character shall be deleted instead of list item
+ GetValue() == wxEmptyString)) //exception: always allow removing empty entry
+#endif
+ {
+ //save old (selected) value: deletion seems to have influence on this
+ const wxString currentVal = this->GetValue();
+ //this->SetSelection(wxNOT_FOUND);
+
+ //delete selected row
+ if (sharedHistory_.get())
+ sharedHistory_->delItem(toZ(GetString(pos)));
+ SetString(pos, wxString()); //in contrast to Delete(), this one does not kill the drop-down list and gives a nice visual feedback!
+ //Delete(pos);
+
+ //(re-)set value
+ this->SetValue(currentVal);
+
+ //eat up key event
+ return;
+ }
+ }
+ event.Skip();
+}
diff --git a/lib/folder_history_box.h b/lib/folder_history_box.h
new file mode 100644
index 00000000..28b85c90
--- /dev/null
+++ b/lib/folder_history_box.h
@@ -0,0 +1,106 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef CUSTOMCOMBOBOX_H_INCLUDED
+#define CUSTOMCOMBOBOX_H_INCLUDED
+
+#include <wx/combobox.h>
+#include <memory>
+#include <zen/zstring.h>
+#include <wx+/string_conv.h>
+#include <zen/stl_tools.h>
+
+//combobox with history function + functionality to delete items (DEL)
+
+
+class FolderHistory
+{
+public:
+ FolderHistory() : maxSize_(0) {}
+
+ FolderHistory(const std::vector<Zstring>& dirnames, size_t maxSize) :
+ maxSize_(maxSize),
+ dirnames_(dirnames)
+ {
+ if (dirnames_.size() > maxSize_) //keep maximal size of history list
+ dirnames_.resize(maxSize_);
+ }
+
+ const std::vector<Zstring>& getList() const { return dirnames_; }
+
+ static const Zstring lineSeparator() { return Zstr("---------------------------------------------------------------------------------------------------------------"); }
+
+ void addItem(const Zstring& dirname)
+ {
+ if (dirname.empty() || dirname == lineSeparator())
+ return;
+
+ Zstring nameTmp = dirname;
+ zen::trim(nameTmp);
+
+ //insert new folder or put it to the front if already existing
+ auto iter = std::find_if(dirnames_.begin(), dirnames_.end(),
+ [&](const Zstring& entry) { return ::EqualFilename()(entry, nameTmp); });
+
+ if (iter != dirnames_.end())
+ dirnames_.erase(iter);
+ dirnames_.insert(dirnames_.begin(), nameTmp);
+
+ if (dirnames_.size() > maxSize_) //keep maximal size of history list
+ dirnames_.resize(maxSize_);
+ }
+
+ void delItem(const Zstring& dirname) { zen::vector_remove_if(dirnames_, [&](const Zstring& entry) { return ::EqualFilename()(entry, dirname); }); }
+
+private:
+
+ size_t maxSize_;
+ std::vector<Zstring> dirnames_;
+};
+
+
+class FolderHistoryBox : public wxComboBox
+{
+public:
+ FolderHistoryBox(wxWindow* parent,
+ wxWindowID id,
+ const wxString& value = wxEmptyString,
+ const wxPoint& pos = wxDefaultPosition,
+ const wxSize& size = wxDefaultSize,
+ int n = 0,
+ const wxString choices[] = NULL,
+ long style = 0,
+ const wxValidator& validator = wxDefaultValidator,
+ const wxString& name = wxComboBoxNameStr);
+
+ void init(const std::shared_ptr<FolderHistory>& sharedHistory) { sharedHistory_ = sharedHistory; }
+
+ void setValue(const wxString& dirname)
+ {
+ update(dirname); //required for setting value correctly + Linux to ensure the dropdown is shown as being populated
+ }
+
+ // GetValue
+
+private:
+ void OnKeyEvent(wxKeyEvent& event);
+ void OnSelection(wxCommandEvent& event);
+ void OnUpdateList(wxEvent& event) { update(GetValue()); event.Skip(); }
+
+ void update(const wxString& dirname);
+
+#if wxCHECK_VERSION(2, 9, 1)
+ void OnShowDropDown(wxCommandEvent& event);
+ void OnHideDropDown(wxCommandEvent& event);
+
+ bool dropDownShown;
+#endif
+
+ std::shared_ptr<FolderHistory> sharedHistory_;
+};
+
+
+#endif // CUSTOMCOMBOBOX_H_INCLUDED
diff --git a/lib/hard_filter.cpp b/lib/hard_filter.cpp
new file mode 100644
index 00000000..ee744337
--- /dev/null
+++ b/lib/hard_filter.cpp
@@ -0,0 +1,368 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "hard_filter.h"
+#include <zen/zstring.h>
+#include <wx/string.h>
+#include <set>
+#include <stdexcept>
+#include <vector>
+#include "../structures.h"
+#include <wx+/serialize.h>
+#include <typeinfo>
+
+using namespace zen;
+
+//inline bool operator<(const std::type_info& lhs, const std::type_info& rhs) { return lhs.before(rhs) != 0; } -> not working on GCC :(
+
+
+//--------------------------------------------------------------------------------------------------
+bool zen::operator<(const HardFilter& lhs, const HardFilter& rhs)
+{
+ if (typeid(lhs) != typeid(rhs))
+ return typeid(lhs).before(typeid(rhs)) != 0; //note: in worst case, order is guaranteed to be stable only during each program run
+
+ //this and other are same type:
+ return lhs.cmpLessSameType(rhs);
+}
+
+
+void HardFilter::saveFilter(wxOutputStream& stream) const //serialize derived object
+{
+ //save type information
+ writeString(stream, uniqueClassIdentifier());
+
+ //save actual object
+ save(stream);
+}
+
+
+HardFilter::FilterRef HardFilter::loadFilter(wxInputStream& stream)
+{
+ //read type information
+ const Zstring uniqueClassId = readString<Zstring>(stream);
+
+ //read actual object
+ if (uniqueClassId == Zstr("NullFilter"))
+ return NullFilter::load(stream);
+ else if (uniqueClassId == Zstr("NameFilter"))
+ return NameFilter::load(stream);
+ else if (uniqueClassId == Zstr("CombinedFilter"))
+ return CombinedFilter::load(stream);
+ else
+ throw std::logic_error("Programming Error: Unknown filter!");
+}
+
+
+//--------------------------------------------------------------------------------------------------
+void addFilterEntry(const Zstring& filtername, std::set<Zstring>& fileFilter, std::set<Zstring>& directoryFilter)
+{
+ Zstring filterFormatted = filtername;
+
+#ifdef FFS_WIN
+ //Windows does NOT distinguish between upper/lower-case
+ makeUpper(filterFormatted);
+#elif defined FFS_LINUX
+ //Linux DOES distinguish between upper/lower-case: nothing to do here
+#endif
+
+ const Zstring sepAsterisk = Zstring(FILE_NAME_SEPARATOR) + Zchar('*');
+ const Zstring sepQuestionMark = Zstring(FILE_NAME_SEPARATOR) + Zchar('?');
+ const Zstring asteriskSep = Zstring(Zstr('*')) + FILE_NAME_SEPARATOR;
+ const Zstring questionMarkSep = Zstring(Zstr('?')) + FILE_NAME_SEPARATOR;
+
+ //--------------------------------------------------------------------------------------------------
+ //add some syntactic sugar: handle beginning of filtername
+ if (startsWith(filterFormatted, FILE_NAME_SEPARATOR))
+ {
+ //remove leading separators (keep BEFORE test for Zstring::empty()!)
+ filterFormatted = afterFirst(filterFormatted, FILE_NAME_SEPARATOR);
+ }
+ else if (startsWith(filterFormatted, asteriskSep) || // *\abc
+ startsWith(filterFormatted, questionMarkSep)) // ?\abc
+ {
+ addFilterEntry(Zstring(filterFormatted.c_str() + 1), fileFilter, directoryFilter); //prevent further recursion by prefix
+ }
+
+ //--------------------------------------------------------------------------------------------------
+ //even more syntactic sugar: handle end of filtername
+ if (endsWith(filterFormatted, FILE_NAME_SEPARATOR))
+ {
+ const Zstring candidate = beforeLast(filterFormatted, FILE_NAME_SEPARATOR);
+ if (!candidate.empty())
+ directoryFilter.insert(candidate); //only relevant for directory filtering
+ }
+ else if (endsWith(filterFormatted, sepAsterisk) || // abc\*
+ endsWith(filterFormatted, sepQuestionMark)) // abc\?
+ {
+ fileFilter.insert( filterFormatted);
+ directoryFilter.insert(filterFormatted);
+
+ const Zstring candidate = beforeLast(filterFormatted, FILE_NAME_SEPARATOR);
+ if (!candidate.empty())
+ directoryFilter.insert(candidate); //only relevant for directory filtering
+ }
+ else if (!filterFormatted.empty())
+ {
+ fileFilter. insert(filterFormatted);
+ directoryFilter.insert(filterFormatted);
+ }
+}
+
+
+namespace
+{
+template <class T> inline
+const T* cStringFind(const T* str1, T ch) //strchr()
+{
+ while (*str1 != ch) //ch is allowed to be 0 by contract! must return end of string in this case
+ {
+ if (*str1 == 0)
+ return NULL;
+ ++str1;
+ }
+ return str1;
+}
+
+bool matchesMask(const Zchar* str, const Zchar* mask)
+{
+ for (Zchar ch; (ch = *mask) != 0; ++mask, ++str)
+ {
+ switch (ch)
+ {
+ case Zstr('?'):
+ if (*str == 0)
+ return false;
+ break;
+
+ case Zstr('*'):
+ //advance to next non-*/? char
+ do
+ {
+ ++mask;
+ ch = *mask;
+ }
+ while (ch == Zstr('*') || ch == Zstr('?'));
+ //if mask ends with '*':
+ if (ch == 0)
+ return true;
+
+ ++mask;
+ while ((str = cStringFind(str, ch)) != NULL)
+ {
+ ++str;
+ if (matchesMask(str, mask))
+ return true;
+ }
+ return false;
+
+ default:
+ if (*str != ch)
+ return false;
+ }
+ }
+ return *str == 0;
+}
+
+//returns true if string matches at least the beginning of mask
+inline
+bool matchesMaskBegin(const Zchar* str, const Zchar* mask)
+{
+ for (Zchar ch; (ch = *mask) != 0; ++mask, ++str)
+ {
+ if (*str == 0)
+ return true;
+
+ switch (ch)
+ {
+ case Zstr('?'):
+ break;
+
+ case Zstr('*'):
+ return true;
+
+ default:
+ if (*str != ch)
+ return false;
+ }
+ }
+ return *str == 0;
+}
+}
+
+
+class MatchFound : public std::unary_function<Zstring, bool>
+{
+public:
+ MatchFound(const Zstring& name) : name_(name) {}
+
+ bool operator()(const Zstring& mask) const
+ {
+ return matchesMask(name_.c_str(), mask.c_str());
+ }
+private:
+ const Zstring& name_;
+};
+
+
+inline
+bool matchesFilter(const Zstring& nameFormatted, const std::set<Zstring>& filter)
+{
+ return std::find_if(filter.begin(), filter.end(), MatchFound(nameFormatted)) != filter.end();
+}
+
+
+inline
+bool matchesFilterBegin(const Zstring& nameFormatted, const std::set<Zstring>& filter)
+{
+ for (std::set<Zstring>::const_iterator i = filter.begin(); i != filter.end(); ++i)
+ if (matchesMaskBegin(nameFormatted.c_str(), i->c_str()))
+ return true;
+ return false;
+
+ // return std::find_if(filter.begin(), filter.end(),
+ // boost::bind(matchesMaskBegin, nameFormatted.c_str(), _1)) != filter.end();
+}
+
+
+std::vector<Zstring> compoundStringToFilter(const Zstring& filterString)
+{
+ //delimiters may be ';' or '\n'
+ std::vector<Zstring> output;
+
+ const std::vector<Zstring> blocks = split(filterString, Zchar(';'));
+ std::for_each(blocks.begin(), blocks.end(),
+ [&](const Zstring& item)
+ {
+ const std::vector<Zstring> blocks2 = split(item, Zchar('\n'));
+
+ std::for_each(blocks2.begin(), blocks2.end(),
+ [&](Zstring entry)
+ {
+ trim(entry);
+ if (!entry.empty())
+ output.push_back(entry);
+ });
+ });
+
+ return output;
+}
+
+//#################################################################################################
+NameFilter::NameFilter(const Zstring& includeFilter, const Zstring& excludeFilter) :
+ includeFilterTmp(includeFilter), //save constructor arguments for serialization
+ excludeFilterTmp(excludeFilter)
+{
+ //no need for regular expressions! In tests wxRegex was by factor of 10 slower than wxString::Matches()!!
+
+ //load filter into vectors of strings
+ //delimiters may be ';' or '\n'
+ const std::vector<Zstring>& includeList = compoundStringToFilter(includeFilter);
+ const std::vector<Zstring>& excludeList = compoundStringToFilter(excludeFilter);
+
+ //setup include/exclude filters for files and directories
+ std::for_each(includeList.begin(), includeList.end(), [&](const Zstring& entry) { addFilterEntry(entry, filterFileIn, filterFolderIn); });
+ std::for_each(excludeList.begin(), excludeList.end(), [&](const Zstring& entry) { addFilterEntry(entry, filterFileEx, filterFolderEx); });
+}
+
+
+bool NameFilter::passFileFilter(const Zstring& relFilename) const
+{
+#ifdef FFS_WIN //Windows does NOT distinguish between upper/lower-case
+ Zstring nameFormatted = relFilename;
+ makeUpper(nameFormatted);
+#elif defined FFS_LINUX //Linux DOES distinguish between upper/lower-case
+ const Zstring& nameFormatted = relFilename; //nothing to do here
+#endif
+
+ return matchesFilter(nameFormatted, filterFileIn) && //process include filters
+ !matchesFilter(nameFormatted, filterFileEx); //process exclude filters
+}
+
+
+bool NameFilter::passDirFilter(const Zstring& relDirname, bool* subObjMightMatch) const
+{
+ assert(subObjMightMatch == NULL || *subObjMightMatch == true); //check correct usage
+
+#ifdef FFS_WIN //Windows does NOT distinguish between upper/lower-case
+ Zstring nameFormatted = relDirname;
+ makeUpper(nameFormatted);
+#elif defined FFS_LINUX //Linux DOES distinguish between upper/lower-case
+ const Zstring& nameFormatted = relDirname; //nothing to do here
+#endif
+
+ if (matchesFilter(nameFormatted, filterFolderEx)) //process exclude filters
+ {
+ if (subObjMightMatch)
+ *subObjMightMatch = false; //exclude subfolders/subfiles as well
+ return false;
+ }
+
+ if (!matchesFilter(nameFormatted, filterFolderIn)) //process include filters
+ {
+ if (subObjMightMatch)
+ {
+ const Zstring& subNameBegin = nameFormatted + FILE_NAME_SEPARATOR; //const-ref optimization
+
+ *subObjMightMatch = matchesFilterBegin(subNameBegin, filterFileIn) || //might match a file in subdirectory
+ matchesFilterBegin(subNameBegin, filterFolderIn); //or another subdirectory
+ }
+ return false;
+ }
+
+ return true;
+}
+
+
+bool NameFilter::isNull() const
+{
+ static NameFilter output(Zstr("*"), Zstring());
+ return *this == output;
+}
+
+
+bool NameFilter::cmpLessSameType(const HardFilter& other) const
+{
+ assert(typeid(*this) == typeid(other)); //always given in this context!
+
+ const NameFilter& otherNameFilt = static_cast<const NameFilter&>(other);
+
+ if (filterFileIn != otherNameFilt.filterFileIn)
+ return filterFileIn < otherNameFilt.filterFileIn;
+
+ if (filterFolderIn != otherNameFilt.filterFolderIn)
+ return filterFolderIn < otherNameFilt.filterFolderIn;
+
+ if (filterFileEx != otherNameFilt.filterFileEx)
+ return filterFileEx < otherNameFilt.filterFileEx;
+
+ if (filterFolderEx != otherNameFilt.filterFolderEx)
+ return filterFolderEx < otherNameFilt.filterFolderEx;
+
+ return false; //vectors equal
+}
+
+
+Zstring NameFilter::uniqueClassIdentifier() const
+{
+ return Zstr("NameFilter");
+}
+
+
+void NameFilter::save(wxOutputStream& stream) const
+{
+ writeString(stream, includeFilterTmp);
+ writeString(stream, excludeFilterTmp);
+}
+
+
+HardFilter::FilterRef NameFilter::load(wxInputStream& stream) //"constructor"
+{
+ const Zstring include = readString<Zstring>(stream);
+ const Zstring exclude = readString<Zstring>(stream);
+
+ return FilterRef(new NameFilter(include, exclude));
+}
diff --git a/lib/hard_filter.h b/lib/hard_filter.h
new file mode 100644
index 00000000..e128a9df
--- /dev/null
+++ b/lib/hard_filter.h
@@ -0,0 +1,279 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef FFS_FILTER_H_INCLUDED
+#define FFS_FILTER_H_INCLUDED
+
+#include <set>
+#include <memory>
+#include <wx/stream.h>
+#include <zen/zstring.h>
+
+namespace zen
+{
+//------------------------------------------------------------------
+/*
+Semantics of HardFilter:
+1. using it creates a NEW folder hierarchy! -> must be considered by <Automatic>-mode! (fortunately it turns out, doing nothing already has perfect semantics :)
+2. it applies equally to both sides => it always matches either both sides or none! => can be used while traversing a single folder!
+
+ class hierarchy:
+
+ HardFilter (interface)
+ /|\
+ _________|_____________
+ | | |
+NullFilter NameFilter CombinedFilter
+*/
+
+class HardFilter //interface for filtering
+{
+public:
+ virtual ~HardFilter() {}
+
+ //filtering
+ virtual bool passFileFilter(const Zstring& relFilename) const = 0;
+ virtual bool passDirFilter (const Zstring& relDirname, bool* subObjMightMatch) const = 0;
+ //subObjMightMatch: file/dir in subdirectories could(!) match
+ //note: variable is only set if passDirFilter returns false!
+
+ virtual bool isNull() const = 0; //filter is equivalent to NullFilter, but may be technically slower
+
+ typedef std::shared_ptr<const HardFilter> FilterRef; //always bound by design!
+
+ //serialization
+ void saveFilter(wxOutputStream& stream) const; //serialize derived object
+ static FilterRef loadFilter(wxInputStream& stream); //CAVEAT!!! adapt this method for each new derivation!!!
+
+private:
+ friend bool operator< (const HardFilter& lhs, const HardFilter& rhs);
+
+ virtual void save(wxOutputStream& stream) const = 0; //serialization
+ virtual Zstring uniqueClassIdentifier() const = 0; //get identifier, used for serialization
+ virtual bool cmpLessSameType(const HardFilter& other) const = 0; //typeid(*this) == typeid(other) in this context!
+};
+
+inline bool operator==(const HardFilter& lhs, const HardFilter& rhs) { return !(lhs < rhs) && !(rhs < lhs); }
+inline bool operator!=(const HardFilter& lhs, const HardFilter& rhs) { return !(lhs == rhs); }
+
+
+//small helper method: merge two hard filters (thereby remove Null-filters)
+HardFilter::FilterRef combineFilters(const HardFilter::FilterRef& first,
+ const HardFilter::FilterRef& second);
+
+
+class NullFilter : public HardFilter //no filtering at all
+{
+public:
+ virtual bool passFileFilter(const Zstring& relFilename) const;
+ virtual bool passDirFilter(const Zstring& relDirname, bool* subObjMightMatch) const;
+ virtual bool isNull() const;
+
+private:
+ friend class HardFilter;
+ virtual void save(wxOutputStream& stream) const {}
+ virtual Zstring uniqueClassIdentifier() const;
+ static FilterRef load(wxInputStream& stream); //"serial constructor"
+ virtual bool cmpLessSameType(const HardFilter& other) const;
+};
+
+
+class NameFilter : public HardFilter //standard filter by filename
+{
+public:
+ NameFilter(const Zstring& includeFilter, const Zstring& excludeFilter);
+
+ virtual bool passFileFilter(const Zstring& relFilename) const;
+ virtual bool passDirFilter(const Zstring& relDirname, bool* subObjMightMatch) const;
+ virtual bool isNull() const;
+
+private:
+ friend class HardFilter;
+ virtual void save(wxOutputStream& stream) const;
+ virtual Zstring uniqueClassIdentifier() const;
+ static FilterRef load(wxInputStream& stream); //"serial constructor"
+ virtual bool cmpLessSameType(const HardFilter& other) const;
+
+ std::set<Zstring> filterFileIn; //upper case (windows)
+ std::set<Zstring> filterFolderIn; //
+ std::set<Zstring> filterFileEx; //
+ std::set<Zstring> filterFolderEx; //
+
+ const Zstring includeFilterTmp; //save constructor arguments for serialization
+ const Zstring excludeFilterTmp; //
+};
+
+
+class CombinedFilter : public HardFilter //combine two filters to match if and only if both match
+{
+public:
+ CombinedFilter(const FilterRef& first, const FilterRef& second) : first_(first), second_(second) {}
+
+ virtual bool passFileFilter(const Zstring& relFilename) const;
+ virtual bool passDirFilter(const Zstring& relDirname, bool* subObjMightMatch) const;
+ virtual bool isNull() const;
+
+private:
+ friend class HardFilter;
+ virtual void save(wxOutputStream& stream) const;
+ virtual Zstring uniqueClassIdentifier() const;
+ static FilterRef load(wxInputStream& stream); //"serial constructor"
+ virtual bool cmpLessSameType(const HardFilter& other) const;
+
+ const FilterRef first_;
+ const FilterRef second_;
+};
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//---------------Inline Implementation---------------------------------------------------
+inline
+HardFilter::FilterRef NullFilter::load(wxInputStream& stream) //"serial constructor"
+{
+ return FilterRef(new NullFilter);
+}
+
+
+inline
+bool NullFilter::passFileFilter(const Zstring& relFilename) const
+{
+ return true;
+}
+
+
+inline
+bool NullFilter::passDirFilter(const Zstring& relDirname, bool* subObjMightMatch) const
+{
+ assert(subObjMightMatch == NULL || *subObjMightMatch == true); //check correct usage
+ return true;
+}
+
+
+inline
+bool NullFilter::isNull() const
+{
+ return true;
+}
+
+
+inline
+bool NullFilter::cmpLessSameType(const HardFilter& other) const
+{
+ assert(typeid(*this) == typeid(other)); //always given in this context!
+ return false;
+}
+
+
+inline
+Zstring NullFilter::uniqueClassIdentifier() const
+{
+ return Zstr("NullFilter");
+}
+
+
+inline
+bool CombinedFilter::passFileFilter(const Zstring& relFilename) const
+{
+ return first_->passFileFilter(relFilename) && //short-circuit behavior
+ second_->passFileFilter(relFilename);
+}
+
+
+inline
+bool CombinedFilter::passDirFilter(const Zstring& relDirname, bool* subObjMightMatch) const
+{
+ return first_->passDirFilter(relDirname, subObjMightMatch) && //short-circuit behavior: subObjMightMatch handled correctly!
+ second_->passDirFilter(relDirname, subObjMightMatch);
+}
+
+
+inline
+bool CombinedFilter::isNull() const
+{
+ return first_->isNull() && second_->isNull();
+}
+
+
+inline
+bool CombinedFilter::cmpLessSameType(const HardFilter& other) const
+{
+ assert(typeid(*this) == typeid(other)); //always given in this context!
+
+ const CombinedFilter& otherCombFilt = static_cast<const CombinedFilter&>(other);
+
+ if (*first_ != *otherCombFilt.first_)
+ return *first_ < *otherCombFilt.first_;
+
+ return *second_ < *otherCombFilt.second_;
+}
+
+
+inline
+Zstring CombinedFilter::uniqueClassIdentifier() const
+{
+ return Zstr("CombinedFilter");
+}
+
+
+inline
+void CombinedFilter::save(wxOutputStream& stream) const
+{
+ first_->saveFilter(stream);
+ second_->saveFilter(stream);
+}
+
+
+inline
+HardFilter::FilterRef CombinedFilter::load(wxInputStream& stream) //"constructor"
+{
+ FilterRef first = loadFilter(stream);
+ FilterRef second = loadFilter(stream);
+
+ return combineFilters(first, second);
+}
+
+
+inline
+HardFilter::FilterRef combineFilters(const HardFilter::FilterRef& first,
+ const HardFilter::FilterRef& second)
+{
+ if (first->isNull())
+ {
+ if (second->isNull())
+ return HardFilter::FilterRef(new NullFilter);
+ else
+ return second;
+ }
+ else
+ {
+ if (second->isNull())
+ return first;
+ else
+ return HardFilter::FilterRef(new CombinedFilter(first, second));
+ }
+}
+
+
+}
+
+
+#endif // FFS_FILTER_H_INCLUDED
+
diff --git a/lib/help_provider.h b/lib/help_provider.h
new file mode 100644
index 00000000..9baf9303
--- /dev/null
+++ b/lib/help_provider.h
@@ -0,0 +1,59 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef HELPPROVIDER_H_INCLUDED
+#define HELPPROVIDER_H_INCLUDED
+
+#include <wx/help.h>
+#include "ffs_paths.h"
+
+namespace zen
+{
+void displayHelpEntry(const wxString& section = wxEmptyString);
+
+
+
+
+
+
+
+
+
+
+
+
+
+//######################## implementation ########################
+inline
+wxHelpController& getHelpCtrl()
+{
+ static wxHelpController controller; //external linkage, despite inline definition!
+ static bool initialized = false;
+ if (!initialized)
+ {
+ initialized = true;
+ controller.Initialize(zen::getResourceDir() +
+#ifdef FFS_WIN
+ L"FreeFileSync.chm");
+#elif defined FFS_LINUX
+ L"Help/FreeFileSync.hhp");
+#endif
+ }
+ return controller;
+}
+
+
+inline
+void displayHelpEntry(const wxString& section)
+{
+ if (section.empty())
+ getHelpCtrl().DisplayContents();
+ else
+ getHelpCtrl().DisplaySection(section);
+}
+}
+
+#endif // HELPPROVIDER_H_INCLUDED
diff --git a/lib/icon_buffer.cpp b/lib/icon_buffer.cpp
new file mode 100644
index 00000000..bb75a538
--- /dev/null
+++ b/lib/icon_buffer.cpp
@@ -0,0 +1,600 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "icon_buffer.h"
+#include <queue>
+#include <set>
+#include <zen/thread.h> //includes <boost/thread.hpp>
+#include <zen/scope_guard.h>
+#include <boost/thread/once.hpp>
+
+#ifdef FFS_WIN
+#include <zen/win.h> //includes "windows.h"
+#include <zen/dll.h>
+#include "Thumbnail/thumbnail.h"
+#include <zen/win_ver.h>
+
+#elif defined FFS_LINUX
+#include <giomm/file.h>
+#include <gtkmm/icontheme.h>
+#include <gtkmm/main.h>
+#endif
+
+using namespace zen;
+
+
+const size_t BUFFER_SIZE_MAX = 800; //maximum number of icons to buffer
+
+
+int zen::IconBuffer::cvrtSize(IconSize sz) //get size in pixel
+{
+ switch (sz)
+ {
+ case SIZE_SMALL:
+#ifdef FFS_WIN
+ return 16;
+#elif defined FFS_LINUX
+ return 24;
+#endif
+ case SIZE_MEDIUM:
+ return 48;
+ case SIZE_LARGE:
+ return 128;
+ }
+ assert(false);
+ return 0;
+}
+
+
+class IconHolder //handle HICON/GdkPixbuf ownership WITHOUT ref-counting to allow thread-safe usage (in contrast to wxIcon)
+{
+public:
+#ifdef FFS_WIN
+ typedef HICON HandleType;
+#elif defined FFS_LINUX
+ typedef GdkPixbuf* HandleType;
+#endif
+
+ IconHolder(HandleType handle = 0) : handle_(handle) {} //take ownership!
+
+ //icon holder has value semantics!
+ IconHolder(const IconHolder& other) : handle_(other.handle_ == NULL ? NULL :
+#ifdef FFS_WIN
+ ::CopyIcon(other.handle_)
+#elif defined FFS_LINUX
+ ::gdk_pixbuf_copy(other.handle_) //create new Pix buf with reference count 1 or return 0 on error
+#endif
+ ) {}
+
+ IconHolder& operator=(const IconHolder& other)
+ {
+ IconHolder(other).swap(*this);
+ return *this;
+ }
+
+ ~IconHolder()
+ {
+ if (handle_ != NULL)
+#ifdef FFS_WIN
+ ::DestroyIcon(handle_);
+#elif defined FFS_LINUX
+ ::g_object_unref(handle_);
+#endif
+ }
+
+ void swap(IconHolder& other) { std::swap(handle_, other.handle_); } //throw()
+
+ wxIcon toWxIcon(int expectedSize) const //copy HandleType, caller needs to take ownership!
+ {
+ if (handle_ == NULL)
+ return wxNullIcon;
+
+ IconHolder clone(*this);
+
+ wxIcon newIcon; //attention: wxIcon uses reference counting!
+#ifdef FFS_WIN
+ newIcon.SetHICON(clone.handle_);
+
+ {
+ //this block costs ~0.04 ms
+ ICONINFO icoInfo = {};
+ if (::GetIconInfo(clone.handle_, &icoInfo))
+ {
+ ::DeleteObject(icoInfo.hbmMask); //nice potential for a GDI leak!
+ ZEN_ON_BLOCK_EXIT(::DeleteObject(icoInfo.hbmColor)); //
+
+ BITMAP bmpInfo = {};
+ if (::GetObject(icoInfo.hbmColor, //__in HGDIOBJ hgdiobj,
+ sizeof(BITMAP), //__in int cbBuffer,
+ &bmpInfo) != 0) // __out LPVOID lpvObject
+ {
+ const int maxExtent = std::max(bmpInfo.bmWidth, bmpInfo.bmHeight);
+ if (maxExtent > expectedSize)
+ {
+ bmpInfo.bmWidth = bmpInfo.bmWidth * expectedSize / maxExtent; //scale those Vista jumbo 256x256 icons down!
+ bmpInfo.bmHeight = bmpInfo.bmHeight * expectedSize / maxExtent; //
+ }
+ newIcon.SetSize(bmpInfo.bmWidth, bmpInfo.bmHeight); //wxIcon is stretched to this size
+ }
+ }
+ }
+ //no stretching for now
+ //newIcon.SetSize(defaultSize, defaultSize); //icon is stretched to this size if referenced HICON differs
+
+#elif defined FFS_LINUX //
+ newIcon.SetPixbuf(clone.handle_); // transfer ownership!!
+#endif //
+ clone.handle_ = NULL; //
+ return newIcon;
+ }
+
+private:
+ HandleType handle_;
+ struct ConversionToBool { int dummy; };
+
+public:
+ //use member pointer as implicit conversion to bool (C++ Templates - Vandevoorde/Josuttis; chapter 20)
+ operator int ConversionToBool::* () const { return handle_ != NULL ? &ConversionToBool::dummy : NULL; }
+};
+
+
+#ifdef FFS_WIN
+namespace
+{
+Zstring getFileExtension(const Zstring& filename)
+{
+ const Zstring shortName = afterLast(filename, Zchar('\\')); //warning: using windows file name separator!
+
+ return shortName.find(Zchar('.')) != Zstring::npos ?
+ afterLast(filename, Zchar('.')) :
+ Zstring();
+}
+
+std::set<Zstring, LessFilename> priceyExtensions; //thread-safe!
+boost::once_flag initExtensionsOnce = BOOST_ONCE_INIT; //
+
+
+//test for extension for non-thumbnail icons that physically have to be retrieved from disc
+bool isCheapExtension(const Zstring& extension)
+{
+ boost::call_once(initExtensionsOnce, []()
+ {
+ priceyExtensions.insert(L"exe");
+ priceyExtensions.insert(L"lnk");
+ priceyExtensions.insert(L"ico");
+ priceyExtensions.insert(L"ani");
+ priceyExtensions.insert(L"cur");
+ priceyExtensions.insert(L"url");
+ priceyExtensions.insert(L"msc");
+ priceyExtensions.insert(L"scr");
+ });
+ return priceyExtensions.find(extension) == priceyExtensions.end();
+}
+
+
+bool wereVistaOrLater = false;
+boost::once_flag initVistaFlagOnce = BOOST_ONCE_INIT;
+
+
+int getShilIconType(IconBuffer::IconSize sz)
+{
+ boost::call_once(initVistaFlagOnce, []()
+ {
+ wereVistaOrLater = vistaOrLater();
+ });
+
+ switch (sz)
+ {
+ case IconBuffer::SIZE_SMALL:
+ return SHIL_SMALL; //16x16, but the size can be customized by the user.
+ case IconBuffer::SIZE_MEDIUM:
+ return SHIL_EXTRALARGE; //typically 48x48, but the size can be customized by the user.
+ case IconBuffer::SIZE_LARGE:
+ return wereVistaOrLater ? SHIL_JUMBO //normally 256x256 pixels -> will be scaled down by IconHolder
+ : SHIL_EXTRALARGE; //XP doesn't have jumbo icons
+ }
+ return SHIL_SMALL;
+}
+
+
+DllFun<thumb::GetIconByIndexFct> getIconByIndex;
+boost::once_flag initGetIconByIndexOnce = BOOST_ONCE_INIT;
+
+
+IconHolder getIconByAttribute(LPCWSTR pszPath, DWORD dwFileAttributes, IconBuffer::IconSize sz)
+{
+ //NOTE: CoInitializeEx()/CoUninitialize() needs to be called for THIS thread!
+ SHFILEINFO fileInfo = {}; //initialize hIcon
+ DWORD_PTR imgList = ::SHGetFileInfo(pszPath, //Windows 7 doesn't like this parameter to be an empty string
+ dwFileAttributes,
+ &fileInfo,
+ sizeof(fileInfo),
+ SHGFI_SYSICONINDEX | SHGFI_USEFILEATTRIBUTES);
+ //no need to IUnknown::Release() imgList!
+ if (!imgList)
+ return NULL;
+
+ boost::call_once(initGetIconByIndexOnce, []() //thread-safe init
+ {
+ getIconByIndex = DllFun<thumb::GetIconByIndexFct>(thumb::getDllName(), thumb::getIconByIndexFctName);
+ });
+ return getIconByIndex ? static_cast<HICON>(getIconByIndex(fileInfo.iIcon, getShilIconType(sz))) : NULL;
+}
+
+
+IconHolder getAssociatedIconByExt(const Zstring& extension, IconBuffer::IconSize sz)
+{
+ //no read-access to disk! determine icon by extension
+ return getIconByAttribute((Zstr("dummy.") + extension).c_str(), FILE_ATTRIBUTE_NORMAL, sz);
+}
+
+
+DllFun<thumb::GetThumbnailFct> getThumbnailIcon;
+boost::once_flag initThumbnailOnce = BOOST_ONCE_INIT;
+}
+#endif
+//################################################################################################################################################
+
+
+IconHolder getThumbnail(const Zstring& filename, int requestedSize) //return 0 on failure
+{
+#ifdef FFS_WIN
+ using namespace thumb;
+
+ boost::call_once(initThumbnailOnce, []() //note: "getThumbnail" function itself is already thread-safe
+ {
+ getThumbnailIcon = DllFun<GetThumbnailFct>(getDllName(), getThumbnailFctName);
+ });
+ return getThumbnailIcon ? static_cast< ::HICON>(getThumbnailIcon(filename.c_str(), requestedSize)) : NULL;
+
+#elif defined FFS_LINUX
+ //call Gtk::Main::init_gtkmm_internals() on application startup!!
+ try
+ {
+ Glib::RefPtr<Gdk::Pixbuf> iconPixbuf = Gdk::Pixbuf::create_from_file(filename.c_str(), requestedSize, requestedSize);
+ if (iconPixbuf)
+ return IconHolder(iconPixbuf->gobj_copy()); //copy and pass icon ownership (may be 0)
+ }
+ catch (const Glib::Error&) {}
+
+ return IconHolder();
+#endif
+}
+
+
+IconHolder getAssociatedIcon(const Zstring& filename, IconBuffer::IconSize sz)
+{
+ //1. try to load thumbnails
+ switch (sz)
+ {
+ case IconBuffer::SIZE_SMALL:
+ break;
+ case IconBuffer::SIZE_MEDIUM:
+ case IconBuffer::SIZE_LARGE:
+ {
+ IconHolder ico = getThumbnail(filename, IconBuffer::cvrtSize(sz));
+ if (ico)
+ return ico;
+ //else: fallback to non-thumbnail icon
+ }
+ break;
+ }
+
+ //2. retrieve file icons
+#ifdef FFS_WIN
+ //perf: optimize fallback case for SIZE_MEDIUM and SIZE_LARGE:
+ const Zstring& extension = getFileExtension(filename);
+ if (isCheapExtension(extension)) //"pricey" extensions are stored with fullnames and are read from disk, while cheap ones require just the extension
+ return getAssociatedIconByExt(extension, sz);
+ //result will not be buffered under extension name, but full filename; this is okay, since we're in SIZE_MEDIUM or SIZE_LARGE context,
+ //which means the access to get thumbnail failed: thumbnail failure is not dependent from extension in general!
+
+ SHFILEINFO fileInfo = {};
+ DWORD_PTR imgList = ::SHGetFileInfo(filename.c_str(), //zen::removeLongPathPrefix(fileName), //::SHGetFileInfo() can't handle \\?\-prefix!
+ 0,
+ &fileInfo,
+ sizeof(fileInfo),
+ SHGFI_SYSICONINDEX);
+ //Quote: "The IImageList pointer type, such as that returned in the ppv parameter, can be cast as an HIMAGELIST as
+ // needed; for example, for use in a list view. Conversely, an HIMAGELIST can be cast as a pointer to an IImageList."
+ //http://msdn.microsoft.com/en-us/library/windows/desktop/bb762185(v=vs.85).aspx
+
+ if (!imgList)
+ return NULL;
+ //imgList->Release(); //empiric study: crash on XP if we release this! Seems we do not own it... -> also no GDI leak on Win7 -> okay
+ //another comment on http://msdn.microsoft.com/en-us/library/bb762179(v=VS.85).aspx describes exact same behavior on Win7/XP
+
+ boost::call_once(initGetIconByIndexOnce, []() //thread-safe init
+ {
+ getIconByIndex = DllFun<thumb::GetIconByIndexFct>(thumb::getDllName(), thumb::getIconByIndexFctName);
+ });
+ return getIconByIndex ? static_cast<HICON>(getIconByIndex(fileInfo.iIcon, getShilIconType(sz))) : NULL;
+
+#elif defined FFS_LINUX
+ const int requestedSize = IconBuffer::cvrtSize(sz);
+ //call Gtk::Main::init_gtkmm_internals() on application startup!!
+ try
+ {
+ Glib::RefPtr<Gio::File> fileObj = Gio::File::create_for_path(filename.c_str()); //never fails
+ Glib::RefPtr<Gio::FileInfo> fileInfo = fileObj->query_info(G_FILE_ATTRIBUTE_STANDARD_ICON);
+ if (fileInfo)
+ {
+ Glib::RefPtr<Gio::Icon> gicon = fileInfo->get_icon();
+ if (gicon)
+ {
+ Glib::RefPtr<Gtk::IconTheme> iconTheme = Gtk::IconTheme::get_default();
+ if (iconTheme)
+ {
+ Gtk::IconInfo iconInfo = iconTheme->lookup_icon(gicon, requestedSize, Gtk::ICON_LOOKUP_USE_BUILTIN); //this may fail if icon is not installed on system
+ if (iconInfo)
+ {
+ Glib::RefPtr<Gdk::Pixbuf> iconPixbuf = iconInfo.load_icon(); //render icon into Pixbuf
+ if (iconPixbuf)
+ return IconHolder(iconPixbuf->gobj_copy()); //copy and pass icon ownership (may be 0)
+ }
+ }
+ }
+ }
+ }
+ catch (const Glib::Error&) {}
+
+ try //fallback: icon lookup may fail because some icons are currently not present on system
+ {
+ Glib::RefPtr<Gtk::IconTheme> iconTheme = Gtk::IconTheme::get_default();
+ if (iconTheme)
+ {
+ Glib::RefPtr<Gdk::Pixbuf> iconPixbuf = iconTheme->load_icon("misc", requestedSize, Gtk::ICON_LOOKUP_USE_BUILTIN);
+ if (!iconPixbuf)
+ iconPixbuf = iconTheme->load_icon("text-x-generic", requestedSize, Gtk::ICON_LOOKUP_USE_BUILTIN);
+ if (iconPixbuf)
+ return IconHolder(iconPixbuf->gobj_copy()); //copy and pass icon ownership (may be 0)
+ }
+ }
+ catch (const Glib::Error&) {}
+
+ //fallback fallback
+ return IconHolder();
+#endif
+}
+
+
+IconHolder getDirectoryIcon(IconBuffer::IconSize sz)
+{
+#ifdef FFS_WIN
+ return getIconByAttribute(L"dummy", //Windows 7 doesn't like this parameter to be an empty string!
+ FILE_ATTRIBUTE_DIRECTORY, sz);
+#elif defined FFS_LINUX
+ return ::getAssociatedIcon(Zstr("/usr/"), sz); //all directories will look like "/usr/"
+#endif
+}
+
+
+IconHolder getFileIcon(IconBuffer::IconSize sz)
+{
+#ifdef FFS_WIN
+ return getIconByAttribute(L"dummy", FILE_ATTRIBUTE_NORMAL, sz);
+#elif defined FFS_LINUX
+ const int requestedSize = IconBuffer::cvrtSize(sz);
+ try
+ {
+ Glib::RefPtr<Gtk::IconTheme> iconTheme = Gtk::IconTheme::get_default();
+ if (iconTheme)
+ {
+ Glib::RefPtr<Gdk::Pixbuf> iconPixbuf = iconTheme->load_icon("misc", requestedSize, Gtk::ICON_LOOKUP_USE_BUILTIN);
+ if (!iconPixbuf)
+ iconPixbuf = iconTheme->load_icon("text-x-generic", requestedSize, Gtk::ICON_LOOKUP_USE_BUILTIN);
+ if (iconPixbuf)
+ return IconHolder(iconPixbuf->gobj_copy()); // transfer ownership!!
+ }
+ }
+ catch (const Glib::Error&) {}
+
+ return IconHolder();
+#endif
+}
+//################################################################################################################################################
+
+
+//---------------------- Shared Data -------------------------
+struct WorkLoad
+{
+public:
+ Zstring extractNextFile() //context of worker thread, blocking
+ {
+ boost::unique_lock<boost::mutex> dummy(lockFiles);
+
+ while (filesToLoad.empty())
+ conditionNewFiles.timed_wait(dummy, boost::get_system_time() + boost::posix_time::milliseconds(50)); //interruption point!
+
+ Zstring fileName = filesToLoad.back();
+ filesToLoad.pop_back();
+ return fileName;
+ }
+
+ void setWorkload(const std::vector<Zstring>& newLoad) //context of main thread
+ {
+ {
+ boost::unique_lock<boost::mutex> dummy(lockFiles);
+ filesToLoad = newLoad;
+ }
+ conditionNewFiles.notify_one();
+ //condition handling, see: http://www.boost.org/doc/libs/1_43_0/doc/html/thread/synchronization.html#thread.synchronization.condvar_ref
+ }
+
+private:
+ std::vector<Zstring> filesToLoad; //processes last elements of vector first!
+ boost::mutex lockFiles;
+ boost::condition_variable conditionNewFiles; //signal event: data for processing available
+};
+
+
+typedef std::map<Zstring, IconHolder, LessFilename> NameIconMap; //entryName/icon -> note: Zstring is thread-safe
+typedef std::queue<Zstring> IconDbSequence; //entryName
+
+class Buffer
+{
+public:
+ bool requestFileIcon(const Zstring& fileName, IconHolder* icon = NULL)
+ {
+ boost::lock_guard<boost::mutex> dummy(lockBuffer);
+
+ auto iter = iconMappping.find(fileName);
+ if (iter != iconMappping.end())
+ {
+ if (icon != NULL)
+ *icon = iter->second;
+ return true;
+ }
+ return false;
+ }
+
+ void insertIntoBuffer(const Zstring& entryName, const IconHolder& icon) //called by worker thread
+ {
+ boost::lock_guard<boost::mutex> dummy(lockBuffer);
+
+ //thread saftey: icon uses ref-counting! But is NOT shared with main thread!
+ auto rc = iconMappping.insert(std::make_pair(entryName, icon));
+ if (rc.second) //if insertion took place
+ iconSequence.push(entryName); //note: sharing Zstring with IconDB!!!
+
+ assert(iconMappping.size() == iconSequence.size());
+
+ //remove elements if buffer becomes too big:
+ if (iconMappping.size() > BUFFER_SIZE_MAX) //limit buffer size: critical because GDI resources are limited (e.g. 10000 on XP per process)
+ {
+ //remove oldest element
+ iconMappping.erase(iconSequence.front());
+ iconSequence.pop();
+ }
+ }
+
+private:
+ boost::mutex lockBuffer;
+ NameIconMap iconMappping; //use synchronisation when accessing this!
+ IconDbSequence iconSequence; //save sequence of buffer entry to delete oldest elements
+};
+//-------------------------------------------------------------
+//################################################################################################################################################
+
+class WorkerThread //lifetime is part of icon buffer
+{
+public:
+ WorkerThread(const std::shared_ptr<WorkLoad>& workload,
+ const std::shared_ptr<Buffer>& buffer,
+ IconBuffer::IconSize sz) :
+ workload_(workload),
+ buffer_(buffer),
+ icoSize(sz) {}
+
+ void operator()(); //thread entry
+
+private:
+ std::shared_ptr<WorkLoad> workload_; //main/worker thread may access different shared_ptr instances safely (even though they have the same target!)
+ std::shared_ptr<Buffer> buffer_; //http://www.boost.org/doc/libs/1_43_0/libs/smart_ptr/shared_ptr.htm?sess=8153b05b34d890e02d48730db1ff7ddc#ThreadSafety
+ const IconBuffer::IconSize icoSize;
+};
+
+
+void WorkerThread::operator()() //thread entry
+{
+ //failure to initialize COM for each thread is a source of hard to reproduce bugs: https://sourceforge.net/tracker/?func=detail&aid=3160472&group_id=234430&atid=1093080
+#ifdef FFS_WIN
+ //Prerequisites, see thumbnail.h
+
+ //1. Initialize COM
+ ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
+ ZEN_ON_BLOCK_EXIT(::CoUninitialize());
+
+ //2. Initialize system image list
+ typedef BOOL (WINAPI *FileIconInitFun)(BOOL fRestoreCache);
+ const SysDllFun<FileIconInitFun> fileIconInit(L"Shell32.dll", reinterpret_cast<LPCSTR>(660));
+ assert(fileIconInit);
+ if (fileIconInit)
+ fileIconInit(false); //TRUE to restore the system image cache from disk; FALSE otherwise.
+#endif
+
+ while (true)
+ {
+ boost::this_thread::interruption_point();
+
+ const Zstring fileName = workload_->extractNextFile(); //start work: get next icon to load
+
+ if (buffer_->requestFileIcon(fileName))
+ continue; //icon already in buffer: skip
+
+ buffer_->insertIntoBuffer(fileName, getAssociatedIcon(fileName, icoSize));
+ }
+}
+//######################### redirect to impl #####################################################
+
+struct IconBuffer::Pimpl
+{
+ Pimpl() :
+ workload(std::make_shared<WorkLoad>()),
+ buffer(std::make_shared<Buffer>()) {}
+
+ std::shared_ptr<WorkLoad> workload;
+ std::shared_ptr<Buffer> buffer;
+
+ boost::thread worker;
+};
+
+
+IconBuffer::IconBuffer(IconSize sz) :
+ pimpl(new Pimpl),
+ icoSize(sz),
+ genDirIcon(::getDirectoryIcon(sz).toWxIcon(cvrtSize(icoSize))),
+ genFileIcon(::getFileIcon(sz).toWxIcon(cvrtSize(icoSize)))
+{
+ pimpl->worker = boost::thread(WorkerThread(pimpl->workload, pimpl->buffer, sz));
+}
+
+
+IconBuffer::~IconBuffer()
+{
+ setWorkload(std::vector<Zstring>()); //make sure interruption point is always reached!
+ pimpl->worker.interrupt();
+ pimpl->worker.join();
+}
+
+
+bool IconBuffer::requestFileIcon(const Zstring& filename, wxIcon* icon)
+{
+ auto getIcon = [&](const Zstring& entryName) -> bool
+ {
+ if (!icon)
+ return pimpl->buffer->requestFileIcon(entryName);
+
+ IconHolder heldIcon;
+ if (!pimpl->buffer->requestFileIcon(entryName, &heldIcon))
+ return false;
+ *icon = heldIcon.toWxIcon(cvrtSize(icoSize));
+ return true;
+ };
+
+#ifdef FFS_WIN
+ //perf: let's read icons which don't need file access right away! No async delay justified!
+ if (icoSize == IconBuffer::SIZE_SMALL) //non-thumbnail view, we need file type icons only!
+ {
+ const Zstring& extension = getFileExtension(filename);
+ if (isCheapExtension(extension)) //"pricey" extensions are stored with fullnames and are read from disk, while cheap ones require just the extension
+ {
+ if (!getIcon(extension))
+ {
+ IconHolder heldIcon = getAssociatedIconByExt(extension, icoSize); //fast!
+ pimpl->buffer->insertIntoBuffer(extension, heldIcon);
+ if (icon)
+ *icon = heldIcon.toWxIcon(cvrtSize(icoSize));
+ }
+ return true;
+ }
+ }
+#endif
+
+ return getIcon(filename);
+}
+
+void IconBuffer::setWorkload(const std::vector<Zstring>& load) { pimpl->workload->setWorkload(load); }
diff --git a/lib/icon_buffer.h b/lib/icon_buffer.h
new file mode 100644
index 00000000..be8a02d8
--- /dev/null
+++ b/lib/icon_buffer.h
@@ -0,0 +1,50 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef ICONBUFFER_H_INCLUDED
+#define ICONBUFFER_H_INCLUDED
+
+#include <memory>
+#include <wx/icon.h>
+#include <zen/zstring.h>
+
+
+namespace zen
+{
+class IconBuffer
+{
+public:
+ enum IconSize
+ {
+ SIZE_SMALL,
+ SIZE_MEDIUM,
+ SIZE_LARGE
+ };
+
+ IconBuffer(IconSize sz);
+ ~IconBuffer();
+
+ int getSize() const { return cvrtSize(icoSize); } //*maximum* icon size in pixel
+
+ const wxIcon& genericDirIcon () { return genDirIcon; }
+ const wxIcon& genericFileIcon() { return genFileIcon; }
+
+ bool requestFileIcon(const Zstring& filename, wxIcon* icon = NULL); //returns false if icon is not in buffer
+ void setWorkload(const std::vector<Zstring>& load); //(re-)set new workload of icons to be retrieved;
+
+ static int cvrtSize(IconSize sz);
+
+private:
+ struct Pimpl;
+ std::unique_ptr<Pimpl> pimpl;
+
+ const IconSize icoSize;
+ const wxIcon genDirIcon;
+ const wxIcon genFileIcon;
+};
+}
+
+#endif // ICONBUFFER_H_INCLUDED
diff --git a/lib/localization.cpp b/lib/localization.cpp
new file mode 100644
index 00000000..774ee9ec
--- /dev/null
+++ b/lib/localization.cpp
@@ -0,0 +1,433 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "localization.h"
+#include <fstream>
+#include <map>
+#include <list>
+#include <iterator>
+#include <wx/ffile.h>
+#include <wx/intl.h>
+#include <wx/msgdlg.h>
+#include "parse_plural.h"
+#include "parse_lng.h"
+#include <wx+/format_unit.h>
+#include <zen/string_tools.h>
+#include <zen/file_traverser.h>
+#include "ffs_paths.h"
+#include <wx+/string_conv.h>
+#include <zenxml/io.h>
+#include <zen/i18n.h>
+
+using namespace zen;
+
+
+namespace
+{
+//global objects
+std::wstring THOUSANDS_SEPARATOR = L",";
+
+
+class FFSLocale : public TranslationHandler
+{
+public:
+ FFSLocale(const wxString& filename, wxLanguage languageId); //throw (lngfile::ParsingError, PluralForm::ParsingError)
+
+ wxLanguage langId() const { return langId_; }
+
+ virtual std::wstring thousandsSeparator() { return THOUSANDS_SEPARATOR; };
+
+ virtual std::wstring translate(const std::wstring& text)
+ {
+ //look for translation in buffer table
+ const Translation::const_iterator iter = transMapping.find(text);
+ if (iter != transMapping.end())
+ return iter->second;
+
+ return text; //fallback
+ }
+
+ virtual std::wstring translate(const std::wstring& singular, const std::wstring& plural, int n)
+ {
+ TranslationPlural::const_iterator iter = transMappingPl.find(std::make_pair(singular, plural));
+ if (iter != transMappingPl.end())
+ {
+ const int formNo = pluralParser->getForm(n);
+ if (0 <= formNo && formNo < static_cast<int>(iter->second.size()))
+ return iter->second[formNo];
+ }
+
+ return n == 1 ? singular : plural; //fallback
+ }
+
+private:
+ typedef std::map<std::wstring, std::wstring> Translation;
+ typedef std::map<std::pair<std::wstring, std::wstring>, std::vector<std::wstring> > TranslationPlural;
+
+ Translation transMapping; //map original text |-> translation
+ TranslationPlural transMappingPl;
+ std::unique_ptr<PluralForm> pluralParser;
+ wxLanguage langId_;
+};
+
+
+
+FFSLocale::FFSLocale(const wxString& filename, wxLanguage languageId) : langId_(languageId) //throw (lngfile::ParsingError, PluralForm::ParsingError)
+{
+ std::string inputStream;
+ try
+ {
+ inputStream = loadStream(filename);; //throw XmlFileError
+ }
+ catch (...)
+ {
+ throw lngfile::ParsingError(0, 0);
+ }
+
+ lngfile::TransHeader header;
+ lngfile::TranslationMap transInput;
+ lngfile::TranslationPluralMap transPluralInput;
+ lngfile::parseLng(inputStream, header, transInput, transPluralInput); //throw ParsingError
+
+ for (lngfile::TranslationMap::const_iterator i = transInput.begin(); i != transInput.end(); ++i)
+ {
+ const std::wstring original = utf8CvrtTo<std::wstring>(i->first);
+ const std::wstring translation = utf8CvrtTo<std::wstring>(i->second);
+ assert(!translation.empty());
+ transMapping.insert(std::make_pair(original , translation));
+ }
+
+ for (lngfile::TranslationPluralMap::const_iterator i = transPluralInput.begin(); i != transPluralInput.end(); ++i)
+ {
+ const std::wstring singular = utf8CvrtTo<std::wstring>(i->first.first);
+ const std::wstring plural = utf8CvrtTo<std::wstring>(i->first.second);
+ const lngfile::PluralForms& plForms = i->second;
+
+ std::vector<std::wstring> plFormsWide;
+ for (lngfile::PluralForms::const_iterator j = plForms.begin(); j != plForms.end(); ++j)
+ plFormsWide.push_back(utf8CvrtTo<std::wstring>(*j));
+
+ assert(!plFormsWide.empty());
+
+ transMappingPl.insert(std::make_pair(std::make_pair(singular, plural), plFormsWide));
+ }
+
+ pluralParser.reset(new PluralForm(header.pluralDefinition.c_str())); //throw PluralForm::ParsingError
+}
+}
+
+
+class FindLngfiles : public zen::TraverseCallback
+{
+public:
+ FindLngfiles(std::vector<Zstring>& lngFiles) : lngFiles_(lngFiles) {}
+
+ virtual void onFile(const Zchar* shortName, const Zstring& fullName, const FileInfo& details)
+ {
+ if (endsWith(fullName, Zstr(".lng")))
+ lngFiles_.push_back(fullName);
+ }
+
+ virtual void onSymlink(const Zchar* shortName, const Zstring& fullName, const SymlinkInfo& details) {}
+ virtual ReturnValDir onDir(const Zchar* shortName, const Zstring& fullName) { return Int2Type<ReturnValDir::TRAVERSING_DIR_IGNORE>(); }
+ virtual HandleError onError(const std::wstring& errorText) { return TRAV_ERROR_IGNORE; } //errors are not really critical in this context
+
+private:
+ std::vector<Zstring>& lngFiles_;
+};
+
+
+struct LessTranslation : public std::binary_function<ExistingTranslations::Entry, ExistingTranslations::Entry, bool>
+{
+ bool operator()(const ExistingTranslations::Entry& lhs, const ExistingTranslations::Entry& rhs) const
+ {
+#ifdef FFS_WIN
+ //use a more "natural" sort, that is ignore case and diacritics
+ const int rv = ::CompareString(LOCALE_USER_DEFAULT, //__in LCID Locale,
+ NORM_IGNORECASE, //__in DWORD dwCmpFlags,
+ lhs.languageName.c_str(), //__in LPCTSTR lpString1,
+ static_cast<int>(lhs.languageName.size()), //__in int cchCount1,
+ rhs.languageName.c_str(), //__in LPCTSTR lpString2,
+ static_cast<int>(rhs.languageName.size())); //__in int cchCount2
+ if (rv == 0)
+ throw std::runtime_error("Error comparing strings!");
+ else
+ return rv == CSTR_LESS_THAN; //convert to C-style string compare result
+#else
+ return lhs.languageName < rhs.languageName;
+#endif
+ }
+};
+
+
+ExistingTranslations::ExistingTranslations()
+{
+ {
+ //default entry:
+ ExistingTranslations::Entry newEntry;
+ newEntry.languageID = wxLANGUAGE_ENGLISH_US;
+ newEntry.languageName = wxT("English (US)");
+ newEntry.languageFile = wxT("");
+ newEntry.translatorName = wxT("ZenJu");
+ newEntry.languageFlag = wxT("usa.png");
+ locMapping.push_back(newEntry);
+ }
+
+ //search language files available
+ std::vector<Zstring> lngFiles;
+ FindLngfiles traverseCallback(lngFiles);
+
+ traverseFolder(toZ(zen::getResourceDir() + wxT("Languages")), //throw();
+ false, //don't follow symlinks
+ traverseCallback);
+
+ for (auto i = lngFiles.begin(); i != lngFiles.end(); ++i)
+ try
+ {
+ std::string stream = loadStream(*i);; //throw XmlFileError
+ try
+ {
+ lngfile::TransHeader lngHeader;
+ lngfile::parseHeader(stream, lngHeader); //throw ParsingError
+
+ const wxLanguageInfo* locInfo = wxLocale::FindLanguageInfo(utf8CvrtTo<wxString>(lngHeader.localeName));
+ if (locInfo)
+ {
+ ExistingTranslations::Entry newEntry;
+ newEntry.languageID = locInfo->Language;
+ newEntry.languageName = utf8CvrtTo<wxString>(lngHeader.languageName);
+ newEntry.languageFile = toWx(*i);
+ newEntry.translatorName = utf8CvrtTo<wxString>(lngHeader.translatorName);
+ newEntry.languageFlag = utf8CvrtTo<wxString>(lngHeader.flagFile);
+ locMapping.push_back(newEntry);
+ }
+ }
+ catch (lngfile::ParsingError&) {}
+ }
+ catch (...) {}
+
+ std::sort(locMapping.begin(), locMapping.end(), LessTranslation());
+}
+
+
+namespace
+{
+wxLanguage mapLanguageDialect(wxLanguage language)
+{
+ switch (static_cast<int>(language)) //map language dialects
+ {
+ //variants of wxLANGUAGE_GERMAN
+ case wxLANGUAGE_GERMAN_AUSTRIAN:
+ case wxLANGUAGE_GERMAN_BELGIUM:
+ case wxLANGUAGE_GERMAN_LIECHTENSTEIN:
+ case wxLANGUAGE_GERMAN_LUXEMBOURG:
+ case wxLANGUAGE_GERMAN_SWISS:
+ return wxLANGUAGE_GERMAN;
+
+ //variants of wxLANGUAGE_FRENCH
+ case wxLANGUAGE_FRENCH_BELGIAN:
+ case wxLANGUAGE_FRENCH_CANADIAN:
+ case wxLANGUAGE_FRENCH_LUXEMBOURG:
+ case wxLANGUAGE_FRENCH_MONACO:
+ case wxLANGUAGE_FRENCH_SWISS:
+ return wxLANGUAGE_FRENCH;
+
+ //variants of wxLANGUAGE_DUTCH
+ case wxLANGUAGE_DUTCH_BELGIAN:
+ return wxLANGUAGE_DUTCH;
+
+ //variants of wxLANGUAGE_ITALIAN
+ case wxLANGUAGE_ITALIAN_SWISS:
+ return wxLANGUAGE_ITALIAN;
+
+ //variants of wxLANGUAGE_CHINESE_SIMPLIFIED
+ case wxLANGUAGE_CHINESE:
+ case wxLANGUAGE_CHINESE_SINGAPORE:
+ return wxLANGUAGE_CHINESE_SIMPLIFIED;
+
+ //variants of wxLANGUAGE_CHINESE_TRADITIONAL
+ case wxLANGUAGE_CHINESE_TAIWAN:
+ case wxLANGUAGE_CHINESE_HONGKONG:
+ case wxLANGUAGE_CHINESE_MACAU:
+ return wxLANGUAGE_CHINESE_TRADITIONAL;
+
+ //variants of wxLANGUAGE_RUSSIAN
+ case wxLANGUAGE_RUSSIAN_UKRAINE:
+ return wxLANGUAGE_RUSSIAN;
+
+ //variants of wxLANGUAGE_SPANISH
+ case wxLANGUAGE_SPANISH_ARGENTINA:
+ case wxLANGUAGE_SPANISH_BOLIVIA:
+ case wxLANGUAGE_SPANISH_CHILE:
+ case wxLANGUAGE_SPANISH_COLOMBIA:
+ case wxLANGUAGE_SPANISH_COSTA_RICA:
+ case wxLANGUAGE_SPANISH_DOMINICAN_REPUBLIC:
+ case wxLANGUAGE_SPANISH_ECUADOR:
+ case wxLANGUAGE_SPANISH_EL_SALVADOR:
+ case wxLANGUAGE_SPANISH_GUATEMALA:
+ case wxLANGUAGE_SPANISH_HONDURAS:
+ case wxLANGUAGE_SPANISH_MEXICAN:
+ case wxLANGUAGE_SPANISH_MODERN:
+ case wxLANGUAGE_SPANISH_NICARAGUA:
+ case wxLANGUAGE_SPANISH_PANAMA:
+ case wxLANGUAGE_SPANISH_PARAGUAY:
+ case wxLANGUAGE_SPANISH_PERU:
+ case wxLANGUAGE_SPANISH_PUERTO_RICO:
+ case wxLANGUAGE_SPANISH_URUGUAY:
+ case wxLANGUAGE_SPANISH_US:
+ case wxLANGUAGE_SPANISH_VENEZUELA:
+ return wxLANGUAGE_SPANISH;
+
+ //variants of wxLANGUAGE_SWEDISH
+ case wxLANGUAGE_SWEDISH_FINLAND:
+ return wxLANGUAGE_SWEDISH;
+
+ //case wxLANGUAGE_CZECH:
+ //case wxLANGUAGE_DANISH:
+ //case wxLANGUAGE_FINNISH:
+ //case wxLANGUAGE_GREEK:
+ //case wxLANGUAGE_JAPANESE:
+ //case wxLANGUAGE_POLISH:
+ //case wxLANGUAGE_SLOVENIAN:
+ //case wxLANGUAGE_HUNGARIAN:
+ //case wxLANGUAGE_PORTUGUESE:
+ //case wxLANGUAGE_PORTUGUESE_BRAZILIAN:
+ //case wxLANGUAGE_KOREAN:
+ //case wxLANGUAGE_UKRAINIAN:
+
+ //variants of wxLANGUAGE_ARABIC
+ case wxLANGUAGE_ARABIC_ALGERIA:
+ case wxLANGUAGE_ARABIC_BAHRAIN:
+ case wxLANGUAGE_ARABIC_EGYPT:
+ case wxLANGUAGE_ARABIC_IRAQ:
+ case wxLANGUAGE_ARABIC_JORDAN:
+ case wxLANGUAGE_ARABIC_KUWAIT:
+ case wxLANGUAGE_ARABIC_LEBANON:
+ case wxLANGUAGE_ARABIC_LIBYA:
+ case wxLANGUAGE_ARABIC_MOROCCO:
+ case wxLANGUAGE_ARABIC_OMAN:
+ case wxLANGUAGE_ARABIC_QATAR:
+ case wxLANGUAGE_ARABIC_SAUDI_ARABIA:
+ case wxLANGUAGE_ARABIC_SUDAN:
+ case wxLANGUAGE_ARABIC_SYRIA:
+ case wxLANGUAGE_ARABIC_TUNISIA:
+ case wxLANGUAGE_ARABIC_UAE:
+ case wxLANGUAGE_ARABIC_YEMEN:
+ return wxLANGUAGE_ARABIC;
+
+ //variants of wxLANGUAGE_ENGLISH_UK
+ case wxLANGUAGE_ENGLISH_AUSTRALIA:
+ case wxLANGUAGE_ENGLISH_NEW_ZEALAND:
+ case wxLANGUAGE_ENGLISH_TRINIDAD:
+ case wxLANGUAGE_ENGLISH_CARIBBEAN:
+ case wxLANGUAGE_ENGLISH_JAMAICA:
+ case wxLANGUAGE_ENGLISH_BELIZE:
+ case wxLANGUAGE_ENGLISH_EIRE:
+ case wxLANGUAGE_ENGLISH_SOUTH_AFRICA:
+ case wxLANGUAGE_ENGLISH_ZIMBABWE:
+ case wxLANGUAGE_ENGLISH_BOTSWANA:
+ case wxLANGUAGE_ENGLISH_DENMARK:
+ return wxLANGUAGE_ENGLISH_UK;
+
+ default:
+ return language;
+ }
+}
+}
+
+
+class CustomLocale
+{
+public:
+ CustomLocale(int selectedLng)
+ {
+ const wxLanguageInfo* sysLngInfo = wxLocale::GetLanguageInfo(wxLocale::GetSystemLanguage());
+ const wxLanguageInfo* selLngInfo = wxLocale::GetLanguageInfo(selectedLng);
+
+ bool sysLangIsRTL = sysLngInfo ? sysLngInfo->LayoutDirection == wxLayout_RightToLeft : false;
+ bool selectedLangIsRTL = selLngInfo ? selLngInfo->LayoutDirection == wxLayout_RightToLeft : false;
+
+ if (sysLangIsRTL == selectedLangIsRTL)
+ loc.Init(wxLANGUAGE_DEFAULT); //use sys-lang to preserve sub-language specific rules (e.g. german swiss number punctation)
+ else
+ loc.Init(selectedLng);
+
+ //::setlocale (LC_ALL, ""); -> implicitly called by wxLocale
+ const lconv* localInfo = ::localeconv();
+
+ //actually these two parameters are language dependent, but we take system setting to handle all kinds of language derivations
+ THOUSANDS_SEPARATOR = utf8CvrtTo<wxString>(localInfo->thousands_sep);
+
+ // why not working?
+ // THOUSANDS_SEPARATOR = std::use_facet<std::numpunct<wchar_t> >(std::locale("")).thousands_sep();
+ // DECIMAL_POINT = std::use_facet<std::numpunct<wchar_t> >(std::locale("")).decimal_point();
+ }
+private:
+ wxLocale loc; //required for RTL language support (and nothing else)
+};
+
+
+void zen::setLanguage(int language)
+{
+ //(try to) retrieve language file
+ wxString languageFile;
+ for (std::vector<ExistingTranslations::Entry>::const_iterator i = ExistingTranslations::get().begin(); i != ExistingTranslations::get().end(); ++i)
+ if (i->languageID == language)
+ {
+ languageFile = i->languageFile;
+ break;
+ }
+
+
+ //handle RTL swapping: we need wxWidgets to do this
+ static std::unique_ptr<CustomLocale> dummy;
+ dummy.reset(); //avoid global locale lifetime overlap! wxWidgets cannot handle this and will crash!
+ dummy.reset(new CustomLocale(languageFile.empty() ? wxLANGUAGE_ENGLISH : language));
+
+
+ //reset to english language; in case of error show error message just once
+ zen::setTranslator();
+
+ //load language file into buffer
+ if (!languageFile.empty()) //if languageFile is empty texts will be english per default
+ {
+ try
+ {
+ zen::setTranslator(new FFSLocale(languageFile, static_cast<wxLanguage>(language))); //throw (lngfile::ParsingError, PluralForm::ParsingError)
+ }
+ catch (lngfile::ParsingError& e)
+ {
+ wxMessageBox(wxString(_("Error reading file:")) + wxT(" \"") + languageFile + wxT("\"") + wxT("\n\n") +
+ wxT("Row: ") + zen::toStringSep(e.row) + wxT("\n") +
+ wxT("Column: ") + zen::toStringSep(e.col) + wxT("\n"), _("Error"), wxOK | wxICON_ERROR);
+ }
+ catch (PluralForm::ParsingError&)
+ {
+ wxMessageBox(wxT("Invalid Plural Form"), _("Error"), wxOK | wxICON_ERROR);
+ }
+ }
+}
+
+
+
+int zen::getLanguage()
+{
+ FFSLocale* loc = dynamic_cast<FFSLocale*>(zen::getTranslator());
+ return loc ? loc->langId() : wxLANGUAGE_ENGLISH_US;
+}
+
+
+int zen::retrieveSystemLanguage()
+{
+ return mapLanguageDialect(static_cast<wxLanguage>(wxLocale::GetSystemLanguage()));
+}
+
+
+const std::vector<ExistingTranslations::Entry>& ExistingTranslations::get()
+{
+ static ExistingTranslations instance;
+ return instance.locMapping;
+}
diff --git a/lib/localization.h b/lib/localization.h
new file mode 100644
index 00000000..438ad445
--- /dev/null
+++ b/lib/localization.h
@@ -0,0 +1,41 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef MISC_H_INCLUDED
+#define MISC_H_INCLUDED
+
+#include <wx/string.h>
+#include <vector>
+
+namespace zen
+{
+class ExistingTranslations
+{
+public:
+ struct Entry
+ {
+ int languageID;
+ wxString languageName;
+ wxString languageFile;
+ wxString translatorName;
+ wxString languageFlag;
+ };
+ static const std::vector<Entry>& get();
+
+private:
+ ExistingTranslations();
+ ExistingTranslations(const ExistingTranslations&);
+ ExistingTranslations& operator=(const ExistingTranslations&);
+
+ std::vector<Entry> locMapping;
+};
+
+void setLanguage(int language);
+int getLanguage();
+int retrieveSystemLanguage();
+}
+
+#endif // MISC_H_INCLUDED
diff --git a/lib/lock_holder.h b/lib/lock_holder.h
new file mode 100644
index 00000000..a7cf3436
--- /dev/null
+++ b/lib/lock_holder.h
@@ -0,0 +1,61 @@
+#ifndef LOCK_HOLDER_H_INCLUDED
+#define LOCK_HOLDER_H_INCLUDED
+
+#include <map>
+#include <zen/zstring.h>
+#include "dir_lock.h"
+#include "status_handler.h"
+#include "dir_exist_async.h"
+
+namespace zen
+{
+const Zstring LOCK_FILE_ENDING = Zstr(".ffs_lock"); //intermediate locks created by DirLock use this extension, too!
+
+//convenience class for creating and holding locks for a number of directories
+class LockHolder
+{
+public:
+ LockHolder(bool allowUserInteraction) : allowUserInteraction_(allowUserInteraction) {}
+
+ void addDir(const Zstring& dirnameFmt, ProcessCallback& procCallback) //resolved dirname ending with path separator
+ {
+ if (dirnameFmt.empty())
+ return;
+
+ if (!dirExistsUpdating(dirnameFmt, allowUserInteraction_, procCallback))
+ return;
+
+ if (lockHolder.find(dirnameFmt) != lockHolder.end()) return;
+ assert(endsWith(dirnameFmt, FILE_NAME_SEPARATOR)); //this is really the contract, formatting does other things as well, e.g. macro substitution
+
+ class WaitOnLockHandler : public DirLockCallback
+ {
+ public:
+ WaitOnLockHandler(ProcessCallback& pc) : pc_(pc) {}
+ virtual void requestUiRefresh() { pc_.requestUiRefresh(); } //allowed to throw exceptions
+ virtual void reportInfo(const std::wstring& text) { pc_.reportStatus(text); }
+ private:
+ ProcessCallback& pc_;
+ } callback(procCallback);
+
+ try
+ {
+ lockHolder.insert(std::make_pair(dirnameFmt, DirLock(dirnameFmt + Zstr("sync") + LOCK_FILE_ENDING, &callback)));
+ }
+ catch (const FileError& e)
+ {
+ bool dummy = false; //this warning shall not be shown but logged only
+ procCallback.reportWarning(e.msg(), dummy); //may throw!
+ }
+ }
+
+private:
+ typedef std::map<Zstring, DirLock, LessFilename> DirnameLockMap;
+ DirnameLockMap lockHolder;
+ const bool allowUserInteraction_;
+};
+
+}
+
+
+#endif // LOCK_HOLDER_H_INCLUDED
diff --git a/lib/norm_filter.h b/lib/norm_filter.h
new file mode 100644
index 00000000..d95fbe35
--- /dev/null
+++ b/lib/norm_filter.h
@@ -0,0 +1,85 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef NORM_FILTER_H_INCLUDED
+#define NORM_FILTER_H_INCLUDED
+
+#include "hard_filter.h"
+#include "soft_filter.h"
+
+namespace zen
+{
+struct NormalizedFilter //grade-a filter: global/local filter settings combined, units resolved, ready for use
+{
+ NormalizedFilter(const HardFilter::FilterRef& hf, const SoftFilter& sf) : nameFilter(hf), timeSizeFilter(sf) {}
+
+ //"hard" filter: relevant during comparison, physically skips files
+ HardFilter::FilterRef nameFilter;
+ //"soft" filter: relevant after comparison; equivalent to user selection
+ SoftFilter timeSizeFilter;
+};
+
+
+//combine global and local filters via "logical and"
+NormalizedFilter normalizeFilters(const FilterConfig& global, const FilterConfig& local);
+
+inline
+bool isNullFilter(const FilterConfig& filterCfg)
+{
+ return NameFilter(filterCfg.includeFilter, filterCfg.excludeFilter).isNull() &&
+ SoftFilter(filterCfg.timeSpan, filterCfg.unitTimeSpan,
+ filterCfg.sizeMin, filterCfg.unitSizeMin,
+ filterCfg.sizeMax, filterCfg.unitSizeMax).isNull();
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+// ----------------------- implementation -----------------------
+inline
+NormalizedFilter normalizeFilters(const FilterConfig& global, const FilterConfig& local)
+{
+ HardFilter::FilterRef globalName(new NameFilter(global.includeFilter, global.excludeFilter));
+ HardFilter::FilterRef localName (new NameFilter(local .includeFilter, local .excludeFilter));
+
+ SoftFilter globalTimeSize(global.timeSpan, global.unitTimeSpan,
+ global.sizeMin, global.unitSizeMin,
+ global.sizeMax, global.unitSizeMax);
+
+ SoftFilter localTimeSize(local.timeSpan, local.unitTimeSpan,
+ local.sizeMin, local.unitSizeMin,
+ local.sizeMax, local.unitSizeMax);
+
+ return NormalizedFilter(combineFilters(globalName, localName),
+ combineFilters(globalTimeSize, localTimeSize));
+}
+}
+
+#endif //NORM_FILTER_H_INCLUDED
diff --git a/lib/parallel_scan.cpp b/lib/parallel_scan.cpp
new file mode 100644
index 00000000..29d87ee7
--- /dev/null
+++ b/lib/parallel_scan.cpp
@@ -0,0 +1,600 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "parallel_scan.h"
+#include <boost/detail/atomic_count.hpp>
+#include "db_file.h"
+#include "lock_holder.h"
+#include <zen/file_traverser.h>
+#include <zen/file_error.h>
+#include <wx+/string_conv.h>
+#include <zen/thread.h> //includes <boost/thread.hpp>
+#include <zen/scope_guard.h>
+
+using namespace zen;
+
+
+#ifndef BOOST_HAS_THREADS
+#error just some paranoia check...
+#endif
+
+
+namespace
+{
+/*
+#ifdef FFS_WIN
+
+struct DiskInfo
+{
+ DiskInfo() :
+ driveType(DRIVE_UNKNOWN),
+ diskID(-1) {}
+
+ UINT driveType;
+ int diskID; // -1 if id could not be determined, this one is filled if driveType == DRIVE_FIXED or DRIVE_REMOVABLE;
+};
+
+inline
+bool operator<(const DiskInfo& lhs, const DiskInfo& rhs)
+{
+ if (lhs.driveType != rhs.driveType)
+ return lhs.driveType < rhs.driveType;
+
+ if (lhs.diskID < 0 || rhs.diskID < 0)
+ return false;
+ //consider "same", reason: one volume may be uniquely associated with one disk, while the other volume is associated to the same disk AND another one!
+ //volume <-> disk is 0..N:1..N
+
+ return lhs.diskID < rhs.diskID ;
+}
+
+
+DiskInfo retrieveDiskInfo(const Zstring& pathName)
+{
+ std::vector<wchar_t> volName(std::max(pathName.size(), static_cast<size_t>(10000)));
+
+ DiskInfo output;
+
+ //full pathName need not yet exist!
+ if (!::GetVolumePathName(pathName.c_str(), //__in LPCTSTR lpszFileName,
+ &volName[0], //__out LPTSTR lpszVolumePathName,
+ static_cast<DWORD>(volName.size()))) //__in DWORD cchBufferLength
+ return output;
+
+ const Zstring rootPathName = &volName[0];
+
+ output.driveType = ::GetDriveType(rootPathName.c_str());
+
+ if (output.driveType == DRIVE_NO_ROOT_DIR) //these two should be the same error category
+ output.driveType = DRIVE_UNKNOWN;
+
+ if (output.driveType != DRIVE_FIXED && output.driveType != DRIVE_REMOVABLE)
+ return output; //no reason to get disk ID
+
+ //go and find disk id:
+
+ //format into form: "\\.\C:"
+ Zstring volnameFmt = rootPathName;
+ if (endsWith(volnameFmt, FILE_NAME_SEPARATOR))
+ volnameFmt.resize(volnameFmt.size() - 1);
+ volnameFmt = L"\\\\.\\" + volnameFmt;
+
+ HANDLE hVolume = ::CreateFile(volnameFmt.c_str(),
+ 0,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ 0,
+ OPEN_EXISTING,
+ 0,
+ NULL);
+ if (hVolume == INVALID_HANDLE_VALUE)
+ return output;
+ ZEN_ON_BLOCK_EXIT(::CloseHandle(hVolume));
+
+ std::vector<char> buffer(sizeof(VOLUME_DISK_EXTENTS) + sizeof(DISK_EXTENT)); //reserve buffer for at most one disk! call below will then fail if volume spans multiple disks!
+
+ DWORD bytesReturned = 0;
+ if (!::DeviceIoControl(hVolume, // handle to device
+ IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, // dwIoControlCode
+ NULL, // lpInBuffer
+ 0, // nInBufferSize
+ &buffer[0], // output buffer
+ static_cast<DWORD>(buffer.size()), // size of output buffer
+ &bytesReturned, // number of bytes returned
+ NULL)) // OVERLAPPED structure
+ return output;
+
+ const VOLUME_DISK_EXTENTS& volDisks = *reinterpret_cast<VOLUME_DISK_EXTENTS*>(&buffer[0]);
+
+ if (volDisks.NumberOfDiskExtents != 1)
+ return output;
+
+ output.diskID = volDisks.Extents[0].DiskNumber;
+
+ return output;
+}
+
+#elif defined FFS_LINUX
+#endif
+*/
+
+/*
+PERF NOTE
+
+--------------------------------------------
+|Testcase: Reading from two different disks|
+--------------------------------------------
+Windows 7:
+ 1st(unbuffered) |2nd (OS buffered)
+ ----------------------------------
+1 Thread: 57s | 8s
+2 Threads: 39s | 7s
+
+--------------------------------------------------
+|Testcase: Reading two directories from same disk|
+--------------------------------------------------
+Windows 7: Windows XP:
+ 1st(unbuffered) |2nd (OS buffered) 1st(unbuffered) |2nd (OS buffered)
+ ---------------------------------- ----------------------------------
+1 Thread: 41s | 13s 1 Thread: 45s | 13s
+2 Threads: 42s | 11s 2 Threads: 38s | 8s
+
+=> Traversing does not take any advantage of file locality so that even multiple threads operating on the same disk impose no performance overhead!
+*/
+
+
+std::vector<std::set<DirectoryKey>> separateByDistinctDisk(const std::set<DirectoryKey>& dirkeys)
+{
+ //see perf note: use one thread per dirkey:
+ typedef std::map<int, std::set<DirectoryKey>> DiskKeyMapping;
+ DiskKeyMapping tmp;
+ int index = 0;
+ std::for_each(dirkeys.begin(), dirkeys.end(),
+ [&](const DirectoryKey& key) { tmp[++index].insert(key); });
+
+ /*
+ //use one thread per physical disk:
+ typedef std::map<DiskInfo, std::set<DirectoryKey>> DiskKeyMapping;
+ DiskKeyMapping tmp;
+ std::for_each(dirkeys.begin(), dirkeys.end(),
+ [&](const DirectoryKey& key) { tmp[retrieveDiskInfo(key.dirnameFull_)].insert(key); });
+ */
+ std::vector<std::set<DirectoryKey>> buckets;
+ std::transform(tmp.begin(), tmp.end(), std::back_inserter(buckets),
+ [&](const DiskKeyMapping::value_type& diskToKey) { return diskToKey.second; });
+ return buckets;
+}
+
+//------------------------------------------------------------------------------------------
+typedef Zbase<wchar_t, StorageRefCountThreadSafe> BasicWString; //thread safe string class for UI texts
+
+
+class AsyncCallback
+{
+public:
+ AsyncCallback() :
+ notifyingThreadID(-1),
+ textScanning(_("Scanning:")),
+ itemsScanned(0),
+ activeWorker(0) {}
+
+ FillBufferCallback::HandleError reportError(const std::wstring& msg) //blocking call: context of worker thread
+ {
+ boost::unique_lock<boost::mutex> dummy(lockErrorMsg);
+ while (!errorMsg.empty() || errorResponse.get())
+ conditionCanReportError.timed_wait(dummy, boost::posix_time::milliseconds(50)); //interruption point!
+
+ errorMsg = BasicWString(msg);
+
+ while (!errorResponse.get())
+ conditionGotResponse.timed_wait(dummy, boost::posix_time::milliseconds(50)); //interruption point!
+
+ FillBufferCallback::HandleError rv = *errorResponse;
+
+ errorMsg.clear();
+ errorResponse.reset();
+
+ //dummy.unlock();
+ conditionCanReportError.notify_one();
+
+ return rv;
+ }
+
+ void processErrors(FillBufferCallback& callback) //context of main thread, call repreatedly
+ {
+ boost::lock_guard<boost::mutex> dummy(lockErrorMsg);
+ if (!errorMsg.empty() && !errorResponse.get())
+ {
+ FillBufferCallback::HandleError rv = callback.reportError(cvrtString<std::wstring>(errorMsg)); //throw!
+ errorResponse.reset(new FillBufferCallback::HandleError(rv));
+
+ //dummy.unlock();
+ conditionGotResponse.notify_one();
+ }
+ }
+
+ void setNotifyingThread(int threadID) { notifyingThreadID = threadID; } //context of main thread
+
+ void reportCurrentFile(const Zstring& filename, int threadID) //context of worker thread
+ {
+ if (threadID != notifyingThreadID) return; //only one thread may report status
+
+ boost::lock_guard<boost::mutex> dummy(lockCurrentStatus);
+ currentFile = filename;
+ currentStatus.clear();
+ }
+
+ void reportCurrentStatus(const std::wstring& status, int threadID) //context of worker thread
+ {
+ if (threadID != notifyingThreadID) return; //only one thread may report status
+
+ boost::lock_guard<boost::mutex> dummy(lockCurrentStatus);
+ currentFile.clear();
+ currentStatus = BasicWString(status); //we cannot assume std::wstring to be thread safe (yet)!
+ }
+
+ std::wstring getCurrentStatus() //context of main thread, call repreatedly
+ {
+ std::wstring filename;
+ std::wstring statusMsg;
+ {
+ boost::lock_guard<boost::mutex> dummy(lockCurrentStatus);
+ if (!currentFile.empty())
+ filename = utf8CvrtTo<std::wstring>(currentFile);
+ else if (!currentStatus.empty())
+ statusMsg = cvrtString<std::wstring>(currentStatus);
+ }
+
+ if (!filename.empty())
+ {
+ std::wstring statusText = cvrtString<std::wstring>(textScanning);
+ const long activeCount = activeWorker;
+ if (activeCount >= 2)
+ {
+ statusText += L" " + _P("[1 Thread]", "[%x Threads]", activeCount);
+ replace(statusText, L"%x", toString<std::wstring>(activeCount));
+ }
+ statusText += std::wstring(L" \n") + L'\"' + filename + L'\"';
+ return statusText;
+ }
+ else
+ return statusMsg;
+ }
+
+ void incItemsScanned() { ++itemsScanned; }
+ long getItemsScanned() const { return itemsScanned; }
+
+ void incActiveWorker() { ++activeWorker; }
+ void decActiveWorker() { --activeWorker; }
+ long getActiveWorker() const { return activeWorker; }
+
+private:
+ //---- error handling ----
+ boost::mutex lockErrorMsg;
+ boost::condition_variable conditionCanReportError;
+ boost::condition_variable conditionGotResponse;
+ BasicWString errorMsg;
+ std::unique_ptr<FillBufferCallback::HandleError> errorResponse;
+
+ //---- status updates ----
+ volatile int notifyingThreadID; //theoretically racy, but there is nothing that could go wrong...
+ //CAVEAT: do NOT use boost::thread::id as long as this showstopper exists: https://svn.boost.org/trac/boost/ticket/5754
+ boost::mutex lockCurrentStatus; //use a different lock for current file: continue traversing while some thread may process an error
+ Zstring currentFile; //only one of these two is filled at a time!
+ BasicWString currentStatus; //
+
+ const BasicWString textScanning; //this one is (currently) not shared and could be made a std::wstring, but we stay consistent and use thread-safe variables in this class only!
+
+ //---- status updates II (lock free) ----
+ boost::detail::atomic_count itemsScanned;
+ boost::detail::atomic_count activeWorker;
+};
+//-------------------------------------------------------------------------------------------------
+
+class DirCallback;
+
+struct TraverserShared
+{
+public:
+ TraverserShared(int threadID,
+ SymLinkHandling handleSymlinks,
+ const HardFilter::FilterRef& filter,
+ std::set<Zstring>& failedReads,
+ AsyncCallback& acb) :
+ handleSymlinks_(handleSymlinks),
+ filterInstance(filter),
+ failedReads_(failedReads),
+ acb_(acb),
+ threadID_(threadID) {}
+
+ typedef std::shared_ptr<DirCallback> CallbackPointer;
+ std::vector<CallbackPointer> callBackBox; //collection of callback pointers to handle ownership
+
+ const SymLinkHandling handleSymlinks_;
+ const HardFilter::FilterRef filterInstance; //always bound!
+
+ std::set<Zstring>& failedReads_; //relative postfixed names of directories that could not be read (empty for root)
+
+ AsyncCallback& acb_;
+ int threadID_;
+};
+
+
+class DirCallback : public zen::TraverseCallback
+{
+public:
+ DirCallback(TraverserShared& config,
+ const Zstring& relNameParentPf, //postfixed with FILE_NAME_SEPARATOR!
+ DirContainer& output) :
+ cfg(config),
+ relNameParentPf_(relNameParentPf),
+ output_(output) {}
+
+ virtual void onFile (const Zchar* shortName, const Zstring& fullName, const FileInfo& details);
+ virtual void onSymlink(const Zchar* shortName, const Zstring& fullName, const SymlinkInfo& details);
+ virtual ReturnValDir onDir (const Zchar* shortName, const Zstring& fullName);
+ virtual HandleError onError (const std::wstring& errorText);
+
+private:
+ TraverserShared& cfg;
+ const Zstring relNameParentPf_;
+ DirContainer& output_;
+};
+
+
+void DirCallback::onFile(const Zchar* shortName, const Zstring& fullName, const FileInfo& details)
+{
+ boost::this_thread::interruption_point();
+
+ const Zstring fileNameShort = 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) ||
+ endsWith(fileNameShort, LOCK_FILE_ENDING))
+ return;
+
+ //update status information no matter whether object is excluded or not!
+ cfg.acb_.reportCurrentFile(fullName, cfg.threadID_);
+
+ //------------------------------------------------------------------------------------
+ //apply filter before processing (use relative name!)
+ if (!cfg.filterInstance->passFileFilter(relNameParentPf_ + fileNameShort))
+ return;
+
+ // std::string fileId = details.fileSize >= 1024 * 1024U ?
+ // util::retrieveFileID(fullName) :
+ // std::string();
+ /*
+ Perf test Windows 7, SSD, 350k files, 50k dirs, files > 1MB: 7000
+ regular: 6.9s
+ ID per file: 43.9s
+ ID per file > 1MB: 7.2s
+ ID per dir: 8.4s
+
+ Linux: retrieveFileID takes about 50% longer in VM! (avoidable because of redundant stat() call!)
+ */
+
+ output_.addSubFile(fileNameShort, FileDescriptor(details.lastWriteTimeRaw, details.fileSize));
+
+ cfg.acb_.incItemsScanned(); //add 1 element to the progress indicator
+}
+
+
+void DirCallback::onSymlink(const Zchar* shortName, const Zstring& fullName, const SymlinkInfo& details)
+{
+ boost::this_thread::interruption_point();
+
+ if (cfg.handleSymlinks_ == SYMLINK_IGNORE)
+ return;
+
+ //update status information no matter whether object is excluded or not!
+ cfg.acb_.reportCurrentFile(fullName, cfg.threadID_);
+
+ //------------------------------------------------------------------------------------
+ const Zstring& relName = relNameParentPf_ + shortName;
+
+ //apply filter before processing (use relative name!)
+ if (!cfg.filterInstance->passFileFilter(relName)) //always use file filter: Link type may not be "stable" on Linux!
+ return;
+
+ output_.addSubLink(shortName, LinkDescriptor(details.lastWriteTimeRaw, details.targetPath, details.dirLink ? LinkDescriptor::TYPE_DIR : LinkDescriptor::TYPE_FILE));
+
+ cfg.acb_.incItemsScanned(); //add 1 element to the progress indicator
+}
+
+
+TraverseCallback::ReturnValDir DirCallback::onDir(const Zchar* shortName, const Zstring& fullName)
+{
+ boost::this_thread::interruption_point();
+
+ //update status information no matter whether object is excluded or not!
+ cfg.acb_.reportCurrentFile(fullName, cfg.threadID_);
+
+ //------------------------------------------------------------------------------------
+ const Zstring& relName = relNameParentPf_ + shortName;
+
+ //apply filter before processing (use relative name!)
+ bool subObjMightMatch = true;
+ const bool passFilter = cfg.filterInstance->passDirFilter(relName, &subObjMightMatch);
+ if (!passFilter && !subObjMightMatch)
+ return Int2Type<ReturnValDir::TRAVERSING_DIR_IGNORE>(); //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
+
+ TraverserShared::CallbackPointer subDirCallback = std::make_shared<DirCallback>(cfg, relName + FILE_NAME_SEPARATOR, subDir);
+ cfg.callBackBox.push_back(subDirCallback); //handle lifetime
+ return ReturnValDir(Int2Type<ReturnValDir::TRAVERSING_DIR_CONTINUE>(), *subDirCallback);
+}
+
+
+DirCallback::HandleError DirCallback::onError(const std::wstring& errorText)
+{
+ switch (cfg.acb_.reportError(errorText))
+ {
+ case FillBufferCallback::TRAV_ERROR_IGNORE:
+ cfg.failedReads_.insert(relNameParentPf_);
+ return TRAV_ERROR_IGNORE;
+
+ case FillBufferCallback::TRAV_ERROR_RETRY:
+ return TRAV_ERROR_RETRY;
+ }
+
+ assert(false);
+ return TRAV_ERROR_IGNORE;
+}
+
+
+#ifdef FFS_WIN
+class DstHackCallbackImpl : public DstHackCallback
+{
+public:
+ DstHackCallbackImpl(AsyncCallback& acb, int threadID) :
+ acb_(acb),
+ threadID_(threadID),
+ textApplyingDstHack(toZ(replaceCpy(_("Encoding extended time information: %x"), L"%x", L"\n\"%x\""))) {}
+
+private:
+ virtual void requestUiRefresh(const Zstring& filename) //applying DST hack imposes significant one-time performance drawback => callback to inform user
+ {
+ const Zstring statusText = replaceCpy(textApplyingDstHack, Zstr("%x"), filename);
+ acb_.reportCurrentStatus(utf8CvrtTo<std::wstring>(statusText), threadID_);
+ }
+
+ AsyncCallback& acb_;
+ int threadID_;
+ const Zstring textApplyingDstHack;
+};
+#endif
+//------------------------------------------------------------------------------------------
+
+
+class WorkerThread
+{
+public:
+ WorkerThread(int threadID,
+ const std::shared_ptr<AsyncCallback>& acb,
+ const std::vector<std::pair<DirectoryKey, DirectoryValue*>>& workload) :
+ threadID_(threadID),
+ acb_(acb),
+ workload_(workload) {}
+
+ void operator()() //thread entry
+ {
+ acb_->incActiveWorker();
+ ZEN_ON_BLOCK_EXIT(acb_->decActiveWorker(););
+
+ std::for_each(workload_.begin(), workload_.end(),
+ [&](std::pair<DirectoryKey, DirectoryValue*>& item)
+ {
+ const Zstring& directoryName = item.first.dirnameFull_;
+ DirectoryValue& dirVal = *item.second;
+
+ acb_->reportCurrentFile(directoryName, threadID_); //just in case first directory access is blocking
+
+ TraverserShared travCfg(threadID_,
+ item.first.handleSymlinks_, //shared by all(!) instances of DirCallback while traversing a folder hierarchy
+ item.first.filter_,
+ dirVal.failedReads,
+ *acb_);
+
+ DirCallback traverser(travCfg,
+ Zstring(),
+ dirVal.dirCont);
+
+ bool followSymlinks = false;
+ switch (item.first.handleSymlinks_)
+ {
+ case SYMLINK_IGNORE:
+ followSymlinks = false; //=> symlinks will be reported via onSymlink() where they are excluded
+ break;
+ case SYMLINK_USE_DIRECTLY:
+ followSymlinks = false;
+ break;
+ case SYMLINK_FOLLOW_LINK:
+ followSymlinks = true;
+ break;
+ }
+
+ DstHackCallback* dstCallbackPtr = NULL;
+#ifdef FFS_WIN
+ DstHackCallbackImpl dstCallback(*acb_, threadID_);
+ dstCallbackPtr = &dstCallback;
+#endif
+
+ //get all files and folders from directoryPostfixed (and subdirectories)
+ traverseFolder(directoryName, followSymlinks, traverser, dstCallbackPtr); //exceptions may be thrown!
+ });
+ }
+
+private:
+ int threadID_;
+ std::shared_ptr<AsyncCallback> acb_;
+ std::vector<std::pair<DirectoryKey, DirectoryValue*>> workload_;
+};
+}
+
+
+void zen::fillBuffer(const std::set<DirectoryKey>& keysToRead, //in
+ std::map<DirectoryKey, DirectoryValue>& buf, //out
+ FillBufferCallback& callback,
+ size_t updateInterval)
+{
+ buf.clear();
+
+ std::vector<std::set<DirectoryKey>> buckets = separateByDistinctDisk(keysToRead); //one bucket per physical device
+
+ std::vector<boost::thread> worker; //note: GCC doesn't allow to construct an array of empty threads since they would be initialized by const boost::thread&
+ worker.reserve(buckets.size());
+
+ zen::ScopeGuard guardWorker = zen::makeGuard([&]()
+ {
+ std::for_each(worker.begin(), worker.end(), std::mem_fun_ref(&boost::thread::interrupt)); //interrupt all at once, then join
+ std::for_each(worker.begin(), worker.end(), std::mem_fun_ref(&boost::thread::join));
+ });
+
+ std::shared_ptr<AsyncCallback> acb = std::make_shared<AsyncCallback>();
+
+ //init worker threads
+ for (auto iter = buckets.begin(); iter != buckets.end(); ++iter)
+ {
+ int threadID = iter - buckets.begin();
+ const std::set<DirectoryKey>& bucket = *iter;
+
+ std::vector<std::pair<DirectoryKey, DirectoryValue*>> workload;
+ std::for_each(bucket.begin(), bucket.end(),
+ [&](const DirectoryKey& key)
+ {
+ auto rv = buf.insert(std::make_pair(key, DirectoryValue()));
+ assert(rv.second);
+ workload.push_back(std::make_pair(key, &rv.first->second));
+ });
+
+ worker.push_back(boost::thread(WorkerThread(threadID, acb, workload)));
+ }
+
+ //wait until done
+ for (auto iter = worker.begin(); iter != worker.end(); ++iter)
+ {
+ boost::thread& wt = *iter;
+ int threadID = iter - worker.begin();
+
+ acb->setNotifyingThread(threadID); //process info messages of first (active) thread only
+
+ do
+ {
+ //update status
+ callback.reportStatus(acb->getCurrentStatus(), acb->getItemsScanned()); //throw!
+
+ //process errors
+ acb->processErrors(callback);
+ }
+ while (!wt.timed_join(boost::posix_time::milliseconds(updateInterval)));
+ }
+
+ guardWorker.dismiss();
+}
diff --git a/lib/parallel_scan.h b/lib/parallel_scan.h
new file mode 100644
index 00000000..99424b2a
--- /dev/null
+++ b/lib/parallel_scan.h
@@ -0,0 +1,74 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef PARALLEL_SCAN_H_INCLUDED
+#define PARALLEL_SCAN_H_INCLUDED
+
+#include <map>
+#include <set>
+#include "hard_filter.h"
+#include "../structures.h"
+#include "../file_hierarchy.h"
+
+namespace zen
+{
+struct DirectoryKey
+{
+ DirectoryKey(const Zstring& dirnameFull,
+ const HardFilter::FilterRef& filter,
+ SymLinkHandling handleSymlinks) :
+ dirnameFull_(dirnameFull),
+ filter_(filter),
+ handleSymlinks_(handleSymlinks) {}
+
+ Zstring dirnameFull_;
+ HardFilter::FilterRef filter_; //filter interface: always bound by design!
+ SymLinkHandling handleSymlinks_;
+};
+
+inline
+bool operator<(const DirectoryKey& lhs, const DirectoryKey& rhs)
+{
+ if (lhs.handleSymlinks_ != rhs.handleSymlinks_)
+ return lhs.handleSymlinks_ < rhs.handleSymlinks_;
+
+ if (!EqualFilename()(lhs.dirnameFull_, rhs.dirnameFull_))
+ return LessFilename()(lhs.dirnameFull_, rhs.dirnameFull_);
+
+ return *lhs.filter_ < *rhs.filter_;
+}
+
+
+struct DirectoryValue
+{
+ DirContainer dirCont;
+ std::set<Zstring> failedReads; //relative postfixed names of directories that could not be read (empty string for root), e.g. access denied, or temporal network drop
+};
+
+
+class FillBufferCallback
+{
+public:
+ virtual ~FillBufferCallback() {}
+
+ enum HandleError
+ {
+ TRAV_ERROR_RETRY,
+ TRAV_ERROR_IGNORE
+ };
+ virtual HandleError reportError (const std::wstring& errorText) = 0; //may throw!
+ virtual void reportStatus(const std::wstring& statusMsg, int itemTotal) = 0; //
+};
+
+//attention: ensure directory filtering is applied later to exclude filtered directories which have been kept as parent folders
+
+void fillBuffer(const std::set<DirectoryKey>& keysToRead, //in
+ std::map<DirectoryKey, DirectoryValue>& buf, //out
+ FillBufferCallback& callback,
+ size_t updateInterval); //unit: [ms]
+}
+
+#endif // PARALLEL_SCAN_H_INCLUDED
diff --git a/lib/parse_lng.h b/lib/parse_lng.h
new file mode 100644
index 00000000..811a3181
--- /dev/null
+++ b/lib/parse_lng.h
@@ -0,0 +1,608 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef PARSE_LNG_HEADER_INCLUDED
+#define PARSE_LNG_HEADER_INCLUDED
+
+#include <algorithm>
+#include <cctype>
+#include <functional>
+#include <list>
+#include <map>
+#include <set>
+#include <sstream>
+#include <stdexcept>
+#include <string>
+#include <vector>
+#include <zen/utf8.h>
+
+namespace lngfile
+{
+//singular forms
+typedef std::map <std::string, std::string> TranslationMap; //orig |-> translation
+
+//plural forms
+typedef std::pair<std::string, std::string> SingularPluralPair; //1 house| n houses
+typedef std::vector<std::string> PluralForms; //1 dom | 2 domy | 5 domów
+typedef std::map <SingularPluralPair, PluralForms> TranslationPluralMap; //(sing/plu) |-> pluralforms
+
+struct TransHeader
+{
+ std::string languageName; //display name: "English (UK)"
+ std::string translatorName; //"ZenJu"
+ std::string localeName; //ISO 639 language code + ISO 3166 country code, e.g. "en_GB", or "en_US"
+ std::string flagFile; //"england.png"
+ int pluralCount; //2
+ std::string pluralDefinition; //"n == 1 ? 0 : 1"
+};
+
+
+struct ParsingError
+{
+ ParsingError(size_t rowNo, size_t colNo) : row(rowNo), col(colNo) {}
+ size_t row;
+ size_t col;
+};
+void parseLng(const std::string& fileStream, TransHeader& header, TranslationMap& out, TranslationPluralMap& pluralOut); //throw ParsingError
+void parseHeader(const std::string& fileStream, TransHeader& header); //throw ParsingError
+
+class TranslationList; //unordered list of unique translation items
+void generateLng(const TranslationList& in, const TransHeader& header, std::string& fileStream);
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//--------------------------- implementation ---------------------------
+class TranslationList //unordered list of unique translation items
+{
+public:
+ void addItem(const std::string& orig, const std::string& trans)
+ {
+ if (!transUnique.insert(orig).second) return;
+
+ dump.push_back(RegularItem(std::make_pair(orig, trans)));
+ sequence.push_back(&dump.back());
+ }
+ void addPluralItem(const SingularPluralPair& orig, const PluralForms& trans)
+ {
+ if (!pluralUnique.insert(orig).second) return;
+
+ dumpPlural.push_back(PluralItem(std::make_pair(orig, trans)));
+ sequence.push_back(&dumpPlural.back());
+ }
+
+ bool untranslatedTextExists() const
+ {
+ for (std::list<RegularItem>::const_iterator i = dump.begin(); i != dump.end(); ++i)
+ if (i->value.second.empty())
+ return true;
+ for (std::list<PluralItem>::const_iterator i = dumpPlural.begin(); i != dumpPlural.end(); ++i)
+ if (i->value.second.empty())
+ return true;
+ return false;
+ }
+
+private:
+ friend void generateLng(const TranslationList& in, const TransHeader& header, std::string& fileStream);
+
+ struct Item {virtual ~Item() {} };
+ struct RegularItem : public Item { RegularItem(const TranslationMap ::value_type& val) : value(val) {} TranslationMap ::value_type value; };
+ struct PluralItem : public Item { PluralItem (const TranslationPluralMap::value_type& val) : value(val) {} TranslationPluralMap::value_type value; };
+
+ std::vector<Item*> sequence; //dynamic list of translation elements
+ std::list<RegularItem> dump; //manage memory
+ std::list<PluralItem> dumpPlural; //manage memory
+
+ std::set<TranslationMap ::key_type> transUnique; //check uniqueness
+ std::set<TranslationPluralMap::key_type> pluralUnique; //
+};
+
+
+struct Token
+{
+ enum Type
+ {
+ //header information
+ TK_HEADER_BEGIN,
+ TK_HEADER_END,
+ TK_LANG_NAME_BEGIN,
+ TK_LANG_NAME_END,
+ TK_TRANS_NAME_BEGIN,
+ TK_TRANS_NAME_END,
+ TK_LOCALE_NAME_BEGIN,
+ TK_LOCALE_NAME_END,
+ TK_FLAG_FILE_BEGIN,
+ TK_FLAG_FILE_END,
+ TK_PLURAL_COUNT_BEGIN,
+ TK_PLURAL_COUNT_END,
+ TK_PLURAL_DEF_BEGIN,
+ TK_PLURAL_DEF_END,
+
+ //item level
+ TK_SRC_BEGIN,
+ TK_SRC_END,
+ TK_TRG_BEGIN,
+ TK_TRG_END,
+ TK_TEXT,
+ TK_PLURAL_BEGIN,
+ TK_PLURAL_END,
+ TK_END
+ };
+
+ Token(Type t) : type(t) {}
+ Type type;
+
+ std::string text;
+};
+
+
+class KnownTokens
+{
+public:
+ typedef std::map<Token::Type, std::string> TokenMap;
+
+ static const TokenMap& asList()
+ {
+ static KnownTokens inst;
+ return inst.tokens;
+ }
+
+ static std::string text(Token::Type t)
+ {
+ TokenMap::const_iterator iter = asList().find(t);
+ return iter != asList().end() ? iter->second : std::string();
+ }
+
+private:
+ KnownTokens()
+ {
+ //header information
+ tokens.insert(std::make_pair(Token::TK_HEADER_BEGIN, "<header>"));
+ tokens.insert(std::make_pair(Token::TK_HEADER_END, "</header>"));
+ tokens.insert(std::make_pair(Token::TK_LANG_NAME_BEGIN, "<language name>"));
+ tokens.insert(std::make_pair(Token::TK_LANG_NAME_END, "</language name>"));
+ tokens.insert(std::make_pair(Token::TK_TRANS_NAME_BEGIN, "<translator>"));
+ tokens.insert(std::make_pair(Token::TK_TRANS_NAME_END, "</translator>"));
+ tokens.insert(std::make_pair(Token::TK_LOCALE_NAME_BEGIN, "<locale>"));
+ tokens.insert(std::make_pair(Token::TK_LOCALE_NAME_END, "</locale>"));
+ tokens.insert(std::make_pair(Token::TK_FLAG_FILE_BEGIN, "<flag file>"));
+ tokens.insert(std::make_pair(Token::TK_FLAG_FILE_END, "</flag file>"));
+ tokens.insert(std::make_pair(Token::TK_PLURAL_COUNT_BEGIN, "<plural forms>"));
+ tokens.insert(std::make_pair(Token::TK_PLURAL_COUNT_END, "</plural forms>"));
+ tokens.insert(std::make_pair(Token::TK_PLURAL_DEF_BEGIN, "<plural definition>"));
+ tokens.insert(std::make_pair(Token::TK_PLURAL_DEF_END, "</plural definition>"));
+
+ //item level
+ tokens.insert(std::make_pair(Token::TK_SRC_BEGIN, "<source>"));
+ tokens.insert(std::make_pair(Token::TK_SRC_END, "</source>"));
+ tokens.insert(std::make_pair(Token::TK_TRG_BEGIN, "<target>"));
+ tokens.insert(std::make_pair(Token::TK_TRG_END, "</target>"));
+ tokens.insert(std::make_pair(Token::TK_PLURAL_BEGIN, "<pluralform>"));
+ tokens.insert(std::make_pair(Token::TK_PLURAL_END, "</pluralform>"));
+ }
+ TokenMap tokens;
+};
+
+struct IsWhiteSpace : public std::unary_function<char, bool>
+{
+ bool operator()(char c) const
+ {
+ const unsigned char usc = c; //caveat 1: std::isspace() takes an int, but expects an unsigned char
+ return usc < 128 && //caveat 2: some parts of UTF-8 chars are erroneously seen as whitespace, e.g. the a0 from "\xec\x8b\a0" (MSVC)
+ std::isspace(usc) != 0; //[!]
+ }
+};
+
+class Scanner
+{
+public:
+ Scanner(const std::string& fileStream) : stream(fileStream), pos(stream.begin()) {}
+
+ Token nextToken()
+ {
+ //skip whitespace
+ pos = std::find_if(pos, stream.end(), std::not1(IsWhiteSpace()));
+
+ if (pos == stream.end())
+ return Token(Token::TK_END);
+
+ for (KnownTokens::TokenMap::const_iterator i = KnownTokens::asList().begin(); i != KnownTokens::asList().end(); ++i)
+ if (startsWith(i->second))
+ {
+ pos += i->second.size();
+ return Token(i->first);
+ }
+
+ //rest must be "text"
+ std::string::const_iterator textBegin = pos;
+ while (pos != stream.end() && !startsWithKnownTag())
+ pos = std::find(pos + 1, stream.end(), '<');
+
+ std::string text(textBegin, pos);
+
+ normalize(text); //remove whitespace from end ect.
+
+ if (text.empty() && pos == stream.end())
+ return Token(Token::TK_END);
+
+ Token out(Token::TK_TEXT);
+ out.text = text;
+ return out;
+ }
+
+ std::pair<size_t, size_t> position() const //current (row/col) beginning with 1
+ {
+ //seek last line break
+ std::string::const_iterator iter = pos;
+ while (iter != stream.begin() && *iter != '\n')
+ --iter;
+
+ return std::make_pair(std::count(stream.begin(), pos, '\n') + 1, pos - iter);
+ }
+
+private:
+ bool startsWithKnownTag() const
+ {
+ for (KnownTokens::TokenMap::const_iterator i = KnownTokens::asList().begin(); i != KnownTokens::asList().end(); ++i)
+ if (startsWith(i->second))
+ return true;
+ return false;
+ }
+
+ bool startsWith(const std::string& prefix) const
+ {
+ if (stream.end() - pos < static_cast<int>(prefix.size()))
+ return false;
+ return std::equal(prefix.begin(), prefix.end(), pos);
+ }
+
+ static void normalize(std::string& text)
+ {
+ //remmove whitespace from end
+ while (!text.empty() && IsWhiteSpace()(*text.rbegin()))
+ text.resize(text.size() - 1);
+
+ //ensure c-style line breaks
+
+ //Delimiter:
+ //----------
+ //Linux: 0xA \n
+ //Mac: 0xD \r
+ //Win: 0xD 0xA \r\n <- language files are in Windows format
+ if (text.find('\r') != std::string::npos)
+ {
+ std::string tmp;
+ for (std::string::const_iterator i = text.begin(); i != text.end(); ++i)
+ if (*i == '\r')
+ {
+ std::string::const_iterator next = i + 1;
+ if (next != text.end() && *next == '\n')
+ ++i;
+ tmp += '\n';
+ }
+ else
+ tmp += *i;
+ text = tmp;
+ }
+ }
+
+ const std::string stream;
+ std::string::const_iterator pos;
+};
+
+template <class C, class T>
+inline
+std::basic_string<C> numberToString(const T& number) //convert number to string the C++ way
+{
+ std::basic_ostringstream<C> ss;
+ ss << number;
+ return ss.str();
+}
+
+template <class T, class C>
+inline
+T stringToNumber(const std::basic_string<C>& str) //convert string to number the C++ way
+{
+ T number = 0;
+ std::basic_istringstream<C>(str) >> number;
+ return number;
+}
+
+
+class LngParser
+{
+public:
+ LngParser(const std::string& fileStream) : scn(fileStream), tk(scn.nextToken()) {}
+
+ void parse(TranslationMap& out, TranslationPluralMap& pluralOut, TransHeader& header)
+ {
+ //header
+ parseHeader(header);
+
+ //items
+ while (token().type != Token::TK_END)
+ parseRegular(out, pluralOut, header.pluralCount);
+ }
+
+ void parseHeader(TransHeader& header)
+ {
+ consumeToken(Token::TK_HEADER_BEGIN);
+
+ consumeToken(Token::TK_LANG_NAME_BEGIN);
+ header.languageName = tk.text;
+ consumeToken(Token::TK_TEXT);
+ consumeToken(Token::TK_LANG_NAME_END);
+
+ consumeToken(Token::TK_TRANS_NAME_BEGIN);
+ header.translatorName = tk.text;
+ consumeToken(Token::TK_TEXT);
+ consumeToken(Token::TK_TRANS_NAME_END);
+
+ consumeToken(Token::TK_LOCALE_NAME_BEGIN);
+ header.localeName = tk.text;
+ consumeToken(Token::TK_TEXT);
+ consumeToken(Token::TK_LOCALE_NAME_END);
+
+ consumeToken(Token::TK_FLAG_FILE_BEGIN);
+ header.flagFile = tk.text;
+ consumeToken(Token::TK_TEXT);
+ consumeToken(Token::TK_FLAG_FILE_END);
+
+ consumeToken(Token::TK_PLURAL_COUNT_BEGIN);
+ header.pluralCount = stringToNumber<int>(tk.text);
+ consumeToken(Token::TK_TEXT);
+ consumeToken(Token::TK_PLURAL_COUNT_END);
+
+ consumeToken(Token::TK_PLURAL_DEF_BEGIN);
+ header.pluralDefinition = tk.text;
+ consumeToken(Token::TK_TEXT);
+ consumeToken(Token::TK_PLURAL_DEF_END);
+
+ consumeToken(Token::TK_HEADER_END);
+ }
+
+private:
+ void parseRegular(TranslationMap& out, TranslationPluralMap& pluralOut, int formCount)
+ {
+ consumeToken(Token::TK_SRC_BEGIN);
+
+ if (token().type == Token::TK_PLURAL_BEGIN)
+ return parsePlural(pluralOut, formCount);
+
+ std::string original = tk.text;
+ consumeToken(Token::TK_TEXT);
+ consumeToken(Token::TK_SRC_END);
+
+ consumeToken(Token::TK_TRG_BEGIN);
+ std::string translation;
+ if (token().type == Token::TK_TEXT)
+ {
+ translation = token().text;
+ nextToken();
+ }
+ consumeToken(Token::TK_TRG_END);
+
+ if (!translation.empty()) //only add if translation is existing
+ out.insert(std::make_pair(original, translation));
+ }
+
+ void parsePlural(TranslationPluralMap& pluralOut, int formCount)
+ {
+ //Token::TK_SRC_BEGIN already consumed
+
+ consumeToken(Token::TK_PLURAL_BEGIN);
+ std::string engSingular = tk.text;
+ consumeToken(Token::TK_TEXT);
+ consumeToken(Token::TK_PLURAL_END);
+
+ consumeToken(Token::TK_PLURAL_BEGIN);
+ std::string engPlural = tk.text;
+ consumeToken(Token::TK_TEXT);
+ consumeToken(Token::TK_PLURAL_END);
+
+ consumeToken(Token::TK_SRC_END);
+
+ consumeToken(Token::TK_TRG_BEGIN);
+
+ PluralForms pluralList;
+ while (token().type == Token::TK_PLURAL_BEGIN)
+ {
+ consumeToken(Token::TK_PLURAL_BEGIN);
+ std::string pluralForm = tk.text;
+ consumeToken(Token::TK_TEXT);
+ consumeToken(Token::TK_PLURAL_END);
+ pluralList.push_back(pluralForm);
+
+ }
+
+ if (!pluralList.empty() && static_cast<int>(pluralList.size()) != formCount) //invalid number of plural forms
+ throw ParsingError(scn.position().first, scn.position().second);
+
+ consumeToken(Token::TK_TRG_END);
+
+ if (!pluralList.empty()) //only add if translation is existing
+ pluralOut.insert(std::make_pair(SingularPluralPair(engSingular, engPlural), pluralList));
+ }
+
+
+ void nextToken() { tk = scn.nextToken(); }
+ const Token& token() const { return tk; }
+
+ void consumeToken(Token::Type t)
+ {
+ if (token().type != t)
+ throw ParsingError(scn.position().first, scn.position().second);
+ nextToken();
+ }
+
+ Scanner scn;
+ Token tk;
+};
+
+
+inline
+void parseLng(const std::string& fileStream, TransHeader& header, TranslationMap& out, TranslationPluralMap& pluralOut) //throw ParsingError
+{
+ out.clear();
+ pluralOut.clear();
+
+ //skip UTF-8 Byte Ordering Mark
+ LngParser prs(zen::startsWith(fileStream, zen::BYTE_ORDER_MARK_UTF8) ? fileStream.substr(3) : fileStream);
+ prs.parse(out, pluralOut, header);
+}
+
+
+inline
+void parseHeader(const std::string& fileStream, TransHeader& header) //throw ParsingError
+{
+ //skip UTF-8 Byte Ordering Mark
+ LngParser prs(zen::startsWith(fileStream, zen::BYTE_ORDER_MARK_UTF8) ? fileStream.substr(3) : fileStream);
+ prs.parseHeader(header);
+}
+
+
+inline
+void formatMultiLineText(std::string& text)
+{
+ if (text.find('\n') != std::string::npos) //multiple lines
+ {
+ if (*text.begin() != '\n')
+ text = '\n' + text;
+ if (*text.rbegin() != '\n')
+ text += '\n';
+ }
+}
+
+
+const std::string LB = "\n";
+const std::string TAB = "\t";
+
+
+void generateLng(const TranslationList& in, const TransHeader& header, std::string& fileStream)
+{
+ //header
+ fileStream += KnownTokens::text(Token::TK_HEADER_BEGIN) + LB;
+
+ fileStream += TAB + KnownTokens::text(Token::TK_LANG_NAME_BEGIN);
+ fileStream += header.languageName;
+ fileStream += KnownTokens::text(Token::TK_LANG_NAME_END) + LB;
+
+ fileStream += TAB + KnownTokens::text(Token::TK_TRANS_NAME_BEGIN);
+ fileStream += header.translatorName;
+ fileStream += KnownTokens::text(Token::TK_TRANS_NAME_END) + LB;
+
+ fileStream += TAB + KnownTokens::text(Token::TK_LOCALE_NAME_BEGIN);
+ fileStream += header.localeName;
+ fileStream += KnownTokens::text(Token::TK_LOCALE_NAME_END) + LB;
+
+ fileStream += TAB + KnownTokens::text(Token::TK_FLAG_FILE_BEGIN);
+ fileStream += header.flagFile;
+ fileStream += KnownTokens::text(Token::TK_FLAG_FILE_END) + LB;
+
+ fileStream += TAB + KnownTokens::text(Token::TK_PLURAL_COUNT_BEGIN);
+ fileStream += numberToString<char>(header.pluralCount);
+ fileStream += KnownTokens::text(Token::TK_PLURAL_COUNT_END) + LB;
+
+ fileStream += TAB + KnownTokens::text(Token::TK_PLURAL_DEF_BEGIN);
+ fileStream += header.pluralDefinition;
+ fileStream += KnownTokens::text(Token::TK_PLURAL_DEF_END) + LB;
+
+ fileStream += KnownTokens::text(Token::TK_HEADER_END) + LB;
+
+ fileStream += LB;
+
+
+ //items
+ for (std::vector<TranslationList::Item*>::const_iterator i = in.sequence.begin(); i != in.sequence.end(); ++i)
+ {
+ const TranslationList::RegularItem* regular = dynamic_cast<const TranslationList::RegularItem*>(*i);
+ const TranslationList::PluralItem* plural = dynamic_cast<const TranslationList::PluralItem*>(*i);
+
+ if (regular)
+ {
+ std::string original = regular->value.first;
+ std::string translation = regular->value.second;
+
+ formatMultiLineText(original);
+ formatMultiLineText(translation);
+
+ fileStream += KnownTokens::text(Token::TK_SRC_BEGIN);
+ fileStream += original;
+ fileStream += KnownTokens::text(Token::TK_SRC_END) + LB;
+
+ fileStream += KnownTokens::text(Token::TK_TRG_BEGIN);
+ fileStream += translation;
+ fileStream += KnownTokens::text(Token::TK_TRG_END) + LB + LB;
+
+ }
+ else if (plural)
+ {
+ std::string engSingular = plural->value.first.first;
+ std::string engPlural = plural->value.first.second;
+ const PluralForms& forms = plural->value.second;
+
+ formatMultiLineText(engSingular);
+ formatMultiLineText(engPlural);
+
+ fileStream += KnownTokens::text(Token::TK_SRC_BEGIN) + LB;
+ fileStream += KnownTokens::text(Token::TK_PLURAL_BEGIN);
+ fileStream += engSingular;
+ fileStream += KnownTokens::text(Token::TK_PLURAL_END) + LB;
+ fileStream += KnownTokens::text(Token::TK_PLURAL_BEGIN);
+ fileStream += engPlural;
+ fileStream += KnownTokens::text(Token::TK_PLURAL_END) + LB;
+ fileStream += KnownTokens::text(Token::TK_SRC_END) + LB;
+
+ fileStream += KnownTokens::text(Token::TK_TRG_BEGIN);
+ if (!forms.empty()) fileStream += LB;
+
+ for (PluralForms::const_iterator j = forms.begin(); j != forms.end(); ++j)
+ {
+ std::string plForm = *j;
+ formatMultiLineText(plForm);
+
+ fileStream += KnownTokens::text(Token::TK_PLURAL_BEGIN);
+ fileStream += plForm;
+ fileStream += KnownTokens::text(Token::TK_PLURAL_END) + LB;
+ }
+ fileStream += KnownTokens::text(Token::TK_TRG_END) + LB + LB;
+ }
+ else
+ {
+ throw std::logic_error("that's what you get for brittle design ;)");
+ }
+ }
+}
+}
+
+#endif //PARSE_LNG_HEADER_INCLUDED
diff --git a/lib/parse_plural.h b/lib/parse_plural.h
new file mode 100644
index 00000000..847bec35
--- /dev/null
+++ b/lib/parse_plural.h
@@ -0,0 +1,412 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef PARSE_PLURAL_H_INCLUDED
+#define PARSE_PLURAL_H_INCLUDED
+
+#include <list>
+#include <memory>
+#include <zen/string_base.h>
+
+
+//http://www.gnu.org/software/hello/manual/gettext/Plural-forms.html
+//http://translate.sourceforge.net/wiki/l10n/pluralforms
+/*
+Grammar for Plural forms parser
+-------------------------------
+expression:
+ conditional-expression
+
+conditional-expression:
+ logical-or-expression
+ logical-or-expression ? expression : expression
+
+logical-or-expression:
+ logical-and-expression
+ logical-or-expression || logical-and-expression
+
+logical-and-expression:
+ equality-expression
+ logical-and-expression && equality-expression
+
+equality-expression:
+ relational-expression
+ relational-expression == relational-expression
+ relational-expression != relational-expression
+
+relational-expression:
+ multiplicative-expression
+ multiplicative-expression > multiplicative-expression
+ multiplicative-expression < multiplicative-expression
+ multiplicative-expression >= multiplicative-expression
+ multiplicative-expression <= multiplicative-expression
+
+multiplicative-expression:
+ pm-expression
+ multiplicative-expression % pm-expression
+
+pm-expression:
+ N
+ Number
+ ( Expression )
+*/
+
+
+
+//expression interface
+struct Expression { virtual ~Expression() {} };
+
+template <class T>
+struct Expr : public Expression
+{
+ typedef T ValueType;
+ virtual ValueType eval() const = 0;
+};
+
+//specific binary expression based on STL function objects
+template <class StlOp>
+struct BinaryExp : public Expr<typename StlOp::result_type>
+{
+ typedef const Expr<typename StlOp::first_argument_type> SourceExp;
+
+ BinaryExp(const SourceExp& lhs, const SourceExp& rhs, StlOp biop) : lhs_(lhs), rhs_(rhs), biop_(biop) {}
+ virtual typename StlOp::result_type eval() const { return biop_(lhs_.eval(), rhs_.eval()); }
+ const SourceExp& lhs_;
+ const SourceExp& rhs_;
+ StlOp biop_;
+};
+
+template <class StlOp>
+inline
+BinaryExp<StlOp> makeBiExp(const Expression& lhs, const Expression& rhs, StlOp biop) //throw (std::bad_cast)
+{
+ return BinaryExp<StlOp>(dynamic_cast<const Expr<typename StlOp::first_argument_type >&>(lhs),
+ dynamic_cast<const Expr<typename StlOp::second_argument_type>&>(rhs), biop);
+}
+
+template <class Out>
+struct TernaryExp : public Out
+{
+ TernaryExp(const Expr<bool>& ifExp, const Out& thenExp, const Out& elseExp) : ifExp_(ifExp), thenExp_(thenExp), elseExp_(elseExp) {}
+ virtual typename Out::ValueType eval() const { return ifExp_.eval() ? thenExp_.eval() : elseExp_.eval(); }
+ const Expr<bool>& ifExp_;
+ const Out& thenExp_;
+ const Out& elseExp_;
+};
+
+struct LiteralNumberEx : public Expr<int>
+{
+ LiteralNumberEx(int n) : n_(n) {}
+ virtual int eval() const { return n_; }
+ int n_;
+};
+
+struct NumberN : public Expr<int>
+{
+ NumberN(int& n) : n_(n) {}
+ virtual int eval() const { return n_; }
+ int& n_;
+};
+
+
+typedef Zbase<char> Wstring;
+
+
+class PluralForm
+{
+public:
+ struct ParsingError {};
+
+ //.po format,e.g.: (n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)
+ PluralForm(const Wstring& phrase) : n_(0)
+ {
+ Parser(phrase, //in
+ expr, n_, dump); //out
+ }
+
+ int getForm(int n) const { n_ = n ; return expr->eval(); }
+
+private:
+ typedef std::list<std::shared_ptr<Expression> > DumpList;
+
+ struct Token
+ {
+ enum Type
+ {
+ TK_TERNARY_QUEST,
+ TK_TERNARY_COLON,
+ TK_OR,
+ TK_AND,
+ TK_EQUAL,
+ TK_NOT_EQUAL,
+ TK_LESS,
+ TK_LESS_EQUAL,
+ TK_GREATER,
+ TK_GREATER_EQUAL,
+ TK_MODULUS,
+ TK_N,
+ TK_NUMBER,
+ TK_BRACKET_LEFT,
+ TK_BRACKET_RIGHT,
+ TK_END
+ };
+
+ Token(Type t) : type(t), number(0) {}
+
+ Type type;
+ int number; //if type == TK_NUMBER
+ };
+
+ class Scanner
+ {
+ public:
+ Scanner(const Wstring& phrase) : stream(phrase), pos(stream.begin())
+ {
+ tokens.push_back(std::make_pair("?" , Token::TK_TERNARY_QUEST));
+ tokens.push_back(std::make_pair(":" , Token::TK_TERNARY_COLON));
+ tokens.push_back(std::make_pair("||", Token::TK_OR ));
+ tokens.push_back(std::make_pair("&&", Token::TK_AND ));
+ tokens.push_back(std::make_pair("==", Token::TK_EQUAL ));
+ tokens.push_back(std::make_pair("!=", Token::TK_NOT_EQUAL ));
+ tokens.push_back(std::make_pair("<=", Token::TK_LESS_EQUAL ));
+ tokens.push_back(std::make_pair("<" , Token::TK_LESS ));
+ tokens.push_back(std::make_pair(">=", Token::TK_GREATER_EQUAL));
+ tokens.push_back(std::make_pair(">" , Token::TK_GREATER ));
+ tokens.push_back(std::make_pair("%" , Token::TK_MODULUS ));
+ tokens.push_back(std::make_pair("n" , Token::TK_N ));
+ tokens.push_back(std::make_pair("N" , Token::TK_N ));
+ tokens.push_back(std::make_pair("(" , Token::TK_BRACKET_LEFT ));
+ tokens.push_back(std::make_pair(")" , Token::TK_BRACKET_RIGHT));
+ }
+
+ Token nextToken()
+ {
+ //skip whitespace
+ pos = std::find_if(pos, stream.end(), std::not1(std::ptr_fun(std::iswspace)));
+
+ if (pos == stream.end()) return Token(Token::TK_END);
+
+ for (TokenList::const_iterator i = tokens.begin(); i != tokens.end(); ++i)
+ if (startsWith(i->first))
+ {
+ pos += i->first.size();
+ return Token(i->second);
+ }
+
+ Wstring::const_iterator digitEnd = std::find_if(pos, stream.end(), std::not1(std::ptr_fun(std::iswdigit)));
+ int digitCount = digitEnd - pos;
+ if (digitCount != 0)
+ {
+ Token out(Token::TK_NUMBER);
+ out.number = zen::toNumber<int>(Wstring(&*pos, digitCount));
+ pos += digitCount;
+ return out;
+ }
+
+ throw ParsingError(); //unknown token
+ }
+
+ private:
+ bool startsWith(const Wstring& prefix) const
+ {
+ if (stream.end() - pos < static_cast<int>(prefix.size()))
+ return false;
+ return std::equal(prefix.begin(), prefix.end(), pos);
+ }
+
+ typedef std::vector<std::pair<Wstring, Token::Type> > TokenList;
+ TokenList tokens;
+
+ const Wstring stream;
+ Wstring::const_iterator pos;
+ };
+
+
+ class Parser
+ {
+ public:
+ Parser(const Wstring& phrase, //in
+ const Expr<int>*& expr, int& n, PluralForm::DumpList& dump) ://out
+ scn(phrase),
+ tk(scn.nextToken()),
+ n_(n),
+ dump_(dump)
+ {
+ try
+ {
+ const Expression& e = parse();
+ expr = &dynamic_cast<const Expr<int>&>(e);
+ }
+ catch (std::bad_cast&) { throw ParsingError(); }
+
+ consumeToken(Token::TK_END);
+ }
+
+ private:
+ void nextToken() { tk = scn.nextToken(); }
+ const Token& token() const { return tk; }
+
+ void consumeToken(Token::Type t)
+ {
+ if (token().type != t)
+ throw ParsingError();
+ nextToken();
+ }
+
+ const Expression& parse() { return parseConditional(); };
+
+ const Expression& parseConditional()
+ {
+ const Expression& e = parseLogicalOr();
+
+ if (token().type == Token::TK_TERNARY_QUEST)
+ {
+ nextToken();
+ const Expression& thenEx = parse(); //associativity: <-
+ consumeToken(Token::TK_TERNARY_COLON);
+ const Expression& elseEx = parse(); //
+
+ return manageObj(TernaryExp<Expr<int> >(dynamic_cast<const Expr<bool>&>(e),
+ dynamic_cast<const Expr<int>&>(thenEx),
+ dynamic_cast<const Expr<int>&>(elseEx)));
+ }
+ return e;
+ }
+
+ const Expression& parseLogicalOr()
+ {
+ const Expression* e = &parseLogicalAnd();
+ for (;;) //associativity: ->
+ if (token().type == Token::TK_OR)
+ {
+ nextToken();
+ const Expression& rhs = parseLogicalAnd();
+ e = &manageObj(makeBiExp(*e, rhs, std::logical_or<bool>()));
+ }
+ else break;
+ return *e;
+ }
+
+ const Expression& parseLogicalAnd()
+ {
+ const Expression* e = &parseEquality();
+ for (;;) //associativity: ->
+ if (token().type == Token::TK_AND)
+ {
+ nextToken();
+ const Expression& rhs = parseEquality();
+
+ e = &manageObj(makeBiExp(*e, rhs, std::logical_and<bool>()));
+ }
+ else break;
+ return *e;
+ }
+
+ const Expression& parseEquality()
+ {
+ const Expression& e = parseRelational();
+
+ Token::Type t = token().type;
+ if (t == Token::TK_EQUAL || t == Token::TK_NOT_EQUAL) //associativity: n/a
+ {
+ nextToken();
+ const Expression& rhs = parseRelational();
+
+ if (t == Token::TK_EQUAL) return manageObj(makeBiExp(e, rhs, std::equal_to <int>()));
+ if (t == Token::TK_NOT_EQUAL) return manageObj(makeBiExp(e, rhs, std::not_equal_to<int>()));
+ }
+ return e;
+ }
+
+ const Expression& parseRelational()
+ {
+ const Expression& e = parseMultiplicative();
+
+ Token::Type t = token().type;
+ if (t == Token::TK_LESS || //associativity: n/a
+ t == Token::TK_LESS_EQUAL ||
+ t == Token::TK_GREATER ||
+ t == Token::TK_GREATER_EQUAL)
+ {
+ nextToken();
+ const Expression& rhs = parseMultiplicative();
+
+ if (t == Token::TK_LESS) return manageObj(makeBiExp(e, rhs, std::less <int>()));
+ if (t == Token::TK_LESS_EQUAL) return manageObj(makeBiExp(e, rhs, std::less_equal <int>()));
+ if (t == Token::TK_GREATER) return manageObj(makeBiExp(e, rhs, std::greater <int>()));
+ if (t == Token::TK_GREATER_EQUAL) return manageObj(makeBiExp(e, rhs, std::greater_equal<int>()));
+ }
+ return e;
+ }
+
+ const Expression& parseMultiplicative()
+ {
+ const Expression* e = &parsePrimary();
+
+ for (;;) //associativity: ->
+ if (token().type == Token::TK_MODULUS)
+ {
+ nextToken();
+ const Expression& rhs = parsePrimary();
+
+ //"compile-time" check: n % 0
+ const LiteralNumberEx* literal = dynamic_cast<const LiteralNumberEx*>(&rhs);
+ if (literal && literal->eval() == 0)
+ throw ParsingError();
+
+ e = &manageObj(makeBiExp(*e, rhs, std::modulus<int>()));
+ }
+ else break;
+ return *e;
+ }
+
+ const Expression& parsePrimary()
+ {
+ if (token().type == Token::TK_N)
+ {
+ nextToken();
+ return manageObj(NumberN(n_));
+ }
+ else if (token().type == Token::TK_NUMBER)
+ {
+ const int number = token().number;
+ nextToken();
+ return manageObj(LiteralNumberEx(number));
+ }
+ else if (token().type == Token::TK_BRACKET_LEFT)
+ {
+ nextToken();
+ const Expression& e = parse();
+
+ consumeToken(Token::TK_BRACKET_RIGHT);
+ return e;
+ }
+ else
+ throw ParsingError();
+ }
+
+ template <class T>
+ const T& manageObj(const T& obj)
+ {
+ std::shared_ptr<Expression> newEntry(new T(obj));
+ dump_.push_back(newEntry);
+ return static_cast<T&>(*dump_.back());
+ }
+
+ Scanner scn;
+ Token tk;
+
+ int& n_;
+ DumpList& dump_; //manage polymorphc object lifetimes
+ };
+
+ const Expr<int>* expr;
+ mutable int n_;
+
+ PluralForm::DumpList dump; //manage polymorphc object lifetimes
+};
+
+#endif // PARSE_PLURAL_H_INCLUDED
diff --git a/lib/process_xml.cpp b/lib/process_xml.cpp
new file mode 100644
index 00000000..777c3ed6
--- /dev/null
+++ b/lib/process_xml.cpp
@@ -0,0 +1,1174 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "process_xml.h"
+#include <zenxml/xml.h>
+#include "ffs_paths.h"
+#include <wx+/string_conv.h>
+#include <zen/file_handling.h>
+#include <zen/file_io.h>
+#include "xml_base.h"
+
+using namespace zen;
+using namespace xmlAccess; //functionally needed for correct overload resolution!!!
+
+
+XmlType getXmlType(const zen::XmlDoc& doc) //throw()
+{
+ if (doc.root().getNameAs<std::string>() == "FreeFileSync")
+ {
+ std::string type;
+ if (doc.root().getAttribute("XmlType", type))
+ {
+ if (type == "GUI")
+ return XML_TYPE_GUI;
+ else if (type == "BATCH")
+ return XML_TYPE_BATCH;
+ else if (type == "GLOBAL")
+ return XML_TYPE_GLOBAL;
+ }
+ }
+ return XML_TYPE_OTHER;
+}
+
+
+XmlType xmlAccess::getXmlType(const wxString& filename) //throw()
+{
+ XmlDoc doc;
+ try
+ {
+ //do NOT use zen::loadStream as it will superfluously load even huge files!
+ loadXmlDocument(toZ(filename), doc); //throw FfsXmlError, quick exit if file is not an FFS XML
+ }
+ catch (const FfsXmlError&)
+ {
+ return XML_TYPE_OTHER;
+ }
+ return ::getXmlType(doc);
+}
+
+
+void setXmlType(XmlDoc& doc, XmlType type) //throw()
+{
+ switch (type)
+ {
+ case XML_TYPE_GUI:
+ doc.root().setAttribute("XmlType", "GUI");
+ break;
+ case XML_TYPE_BATCH:
+ doc.root().setAttribute("XmlType", "BATCH");
+ break;
+ case XML_TYPE_GLOBAL:
+ doc.root().setAttribute("XmlType", "GLOBAL");
+ break;
+ case XML_TYPE_OTHER:
+ assert(false);
+ break;
+ }
+}
+//################################################################################################################
+
+
+wxString xmlAccess::getGlobalConfigFile()
+{
+ return zen::getConfigDir() + wxT("GlobalSettings.xml");
+}
+
+
+void xmlAccess::OptionalDialogs::resetDialogs()
+{
+ warningDependentFolders = true;
+ warningMultiFolderWriteAccess = true;
+ warningSignificantDifference = true;
+ warningNotEnoughDiskSpace = true;
+ warningUnresolvedConflicts = true;
+ warningSyncDatabase = true;
+ warningRecyclerMissing = true;
+ popupOnConfigChange = true;
+ showSummaryBeforeSync = true;
+}
+
+
+xmlAccess::XmlGuiConfig xmlAccess::convertBatchToGui(const xmlAccess::XmlBatchConfig& batchCfg)
+{
+ XmlGuiConfig output;
+ output.mainCfg = batchCfg.mainCfg;
+
+ switch (batchCfg.handleError)
+ {
+ case ON_ERROR_EXIT:
+ case ON_ERROR_POPUP:
+ output.handleError = ON_GUIERROR_POPUP;
+ break;
+ case ON_ERROR_IGNORE:
+ output.handleError = ON_GUIERROR_IGNORE;
+ break;
+ }
+ return output;
+}
+
+
+xmlAccess::XmlBatchConfig xmlAccess::convertGuiToBatch(const xmlAccess::XmlGuiConfig& guiCfg, const wxString& referenceFile)
+{
+ //try to take over batch-specific settings from reference
+ if (!referenceFile.empty() && getXmlType(referenceFile) == XML_TYPE_BATCH)
+ try
+ {
+ XmlBatchConfig output;
+
+ std::vector<wxString> filenames;
+ filenames.push_back(referenceFile);
+ convertConfig(filenames, output); //throw xmlAccess::FfsXmlError
+
+ output.mainCfg = guiCfg.mainCfg;
+ return output;
+ }
+ catch (xmlAccess::FfsXmlError&) {}
+
+ XmlBatchConfig output; //use default batch-settings
+ output.mainCfg = guiCfg.mainCfg;
+
+ switch (guiCfg.handleError)
+ {
+ case ON_GUIERROR_POPUP:
+ output.handleError = ON_ERROR_POPUP;
+ break;
+ case ON_GUIERROR_IGNORE:
+ output.handleError = ON_ERROR_IGNORE;
+ break;
+ }
+
+ return output;
+}
+
+
+xmlAccess::MergeType xmlAccess::getMergeType(const std::vector<wxString>& filenames) //throw ()
+{
+ bool guiCfgExists = false;
+ bool batchCfgExists = false;
+
+ for (auto iter = filenames.begin(); iter != filenames.end(); ++iter)
+ {
+ switch (xmlAccess::getXmlType(*iter)) //throw()
+ {
+ case XML_TYPE_GUI:
+ guiCfgExists = true;
+ break;
+
+ case XML_TYPE_BATCH:
+ batchCfgExists = true;
+ break;
+
+ case XML_TYPE_GLOBAL:
+ case XML_TYPE_OTHER:
+ return MERGE_OTHER;
+ }
+ }
+
+ if (guiCfgExists && batchCfgExists)
+ return MERGE_GUI_BATCH;
+ else if (guiCfgExists && !batchCfgExists)
+ return MERGE_GUI;
+ else if (!guiCfgExists && batchCfgExists)
+ return MERGE_BATCH;
+ else
+ return MERGE_OTHER;
+}
+
+
+namespace
+{
+template <class XmlCfg>
+XmlCfg loadCfgImpl(const wxString& filename, std::unique_ptr<xmlAccess::FfsXmlError>& exeption) //throw xmlAccess::FfsXmlError
+{
+ XmlCfg cfg;
+ try
+ {
+ xmlAccess::readConfig(filename, cfg); //throw xmlAccess::FfsXmlError
+ }
+ catch (const xmlAccess::FfsXmlError& e)
+ {
+ if (e.getSeverity() == xmlAccess::FfsXmlError::FATAL)
+ throw;
+ else
+ exeption.reset(new xmlAccess::FfsXmlError(e));
+ }
+ return cfg;
+}
+
+
+template <class XmlCfg>
+void mergeConfigFilesImpl(const std::vector<wxString>& filenames, XmlCfg& config) //throw xmlAccess::FfsXmlError
+{
+ assert(!filenames.empty());
+ if (filenames.empty())
+ return;
+
+ std::vector<zen::MainConfiguration> mainCfgs;
+ std::unique_ptr<FfsXmlError> savedException;
+
+ std::for_each(filenames.begin(), filenames.end(),
+ [&](const wxString& filename)
+ {
+ switch (getXmlType(filename))
+ {
+ case XML_TYPE_GUI:
+ mainCfgs.push_back(loadCfgImpl<XmlGuiConfig>(filename, savedException).mainCfg); //throw xmlAccess::FfsXmlError
+ break;
+
+ case XML_TYPE_BATCH:
+ mainCfgs.push_back(loadCfgImpl<XmlBatchConfig>(filename, savedException).mainCfg); //throw xmlAccess::FfsXmlError
+ break;
+
+ case XML_TYPE_GLOBAL:
+ case XML_TYPE_OTHER:
+ break;
+ }
+ });
+
+ if (mainCfgs.empty())
+ throw FfsXmlError(_("Invalid FreeFileSync config file!"));
+
+ try //...to init all non-"mainCfg" settings with first config file
+ {
+ xmlAccess::readConfig(filenames[0], config); //throw xmlAccess::FfsXmlError
+ }
+ catch (xmlAccess::FfsXmlError&) {}
+
+ config.mainCfg = merge(mainCfgs);
+
+ if (savedException.get()) //"re-throw" exception
+ throw* savedException;
+}
+}
+
+
+void xmlAccess::convertConfig(const std::vector<wxString>& filenames, XmlGuiConfig& config) //throw (xmlAccess::FfsXmlError)
+{
+ mergeConfigFilesImpl(filenames, config); //throw (xmlAccess::FfsXmlError)
+}
+
+
+void xmlAccess::convertConfig(const std::vector<wxString>& filenames, XmlBatchConfig& config) //throw (xmlAccess::FfsXmlError);
+{
+ mergeConfigFilesImpl(filenames, config); //throw (xmlAccess::FfsXmlError)
+}
+
+
+namespace zen
+{
+template <> inline
+void writeText(const CompareVariant& value, std::string& output)
+{
+ switch (value)
+ {
+ case zen::CMP_BY_TIME_SIZE:
+ output = "ByTimeAndSize";
+ break;
+ case zen::CMP_BY_CONTENT:
+ output = "ByContent";
+ break;
+ }
+}
+
+template <> inline
+bool readText(const std::string& input, CompareVariant& value)
+{
+ std::string tmp = input;
+ zen::trim(tmp);
+ if (tmp == "ByTimeAndSize")
+ value = zen::CMP_BY_TIME_SIZE;
+ else if (tmp == "ByContent")
+ value = zen::CMP_BY_CONTENT;
+ else
+ return false;
+ return true;
+}
+
+
+template <> inline
+void writeText(const SyncDirection& value, std::string& output)
+{
+ switch (value)
+ {
+ case SYNC_DIR_LEFT:
+ output = "left";
+ break;
+ case SYNC_DIR_RIGHT:
+ output = "right";
+ break;
+ case SYNC_DIR_NONE:
+ output = "none";
+ break;
+ }
+}
+
+template <> inline
+bool readText(const std::string& input, SyncDirection& value)
+{
+ std::string tmp = input;
+ zen::trim(tmp);
+ if (tmp == "left")
+ value = SYNC_DIR_LEFT;
+ else if (tmp == "right")
+ value = SYNC_DIR_RIGHT;
+ else if (tmp == "none")
+ value = SYNC_DIR_NONE;
+ else
+ return false;
+ return true;
+}
+
+
+template <> inline
+void writeText(const OnError& value, std::string& output)
+{
+ switch (value)
+ {
+ case ON_ERROR_IGNORE:
+ output = "Ignore";
+ break;
+ case ON_ERROR_EXIT:
+ output = "Exit";
+ break;
+ case ON_ERROR_POPUP:
+ output = "Popup";
+ break;
+ }
+}
+
+template <> inline
+bool readText(const std::string& input, OnError& value)
+{
+ std::string tmp = input;
+ zen::trim(tmp);
+ if (tmp == "Ignore")
+ value = ON_ERROR_IGNORE;
+ else if (tmp == "Exit")
+ value = ON_ERROR_EXIT;
+ else if (tmp == "Popup")
+ value = ON_ERROR_POPUP;
+ else
+ return false;
+ return true;
+}
+
+
+template <> inline
+void writeText(const OnGuiError& value, std::string& output)
+{
+ switch (value)
+ {
+ case ON_GUIERROR_IGNORE:
+ output = "Ignore";
+ break;
+ case ON_GUIERROR_POPUP:
+ output = "Popup";
+ break;
+ }
+}
+
+template <> inline
+bool readText(const std::string& input, OnGuiError& value)
+{
+ std::string tmp = input;
+ zen::trim(tmp);
+ if (tmp == "Ignore")
+ value = ON_GUIERROR_IGNORE;
+ else if (tmp == "Popup")
+ value = ON_GUIERROR_POPUP;
+ else
+ return false;
+ return true;
+}
+
+
+template <> inline
+void writeText(const FileIconSize& value, std::string& output)
+{
+ switch (value)
+ {
+ case ICON_SIZE_SMALL:
+ output = "Small";
+ break;
+ case ICON_SIZE_MEDIUM:
+ output = "Medium";
+ break;
+ case ICON_SIZE_LARGE:
+ output = "Large";
+ break;
+ }
+}
+
+
+template <> inline
+bool readText(const std::string& input, FileIconSize& value)
+{
+ std::string tmp = input;
+ zen::trim(tmp);
+ if (tmp == "Small")
+ value = ICON_SIZE_SMALL;
+ else if (tmp == "Medium")
+ value = ICON_SIZE_MEDIUM;
+ else if (tmp == "Large")
+ value = ICON_SIZE_LARGE;
+ else
+ return false;
+ return true;
+}
+
+
+template <> inline
+void writeText(const DeletionPolicy& value, std::string& output)
+{
+ switch (value)
+ {
+ case DELETE_PERMANENTLY:
+ output = "DeletePermanently";
+ break;
+ case MOVE_TO_RECYCLE_BIN:
+ output = "MoveToRecycleBin";
+ break;
+ case MOVE_TO_CUSTOM_DIRECTORY:
+ output = "MoveToCustomDirectory";
+ break;
+ }
+}
+
+template <> inline
+bool readText(const std::string& input, DeletionPolicy& value)
+{
+ std::string tmp = input;
+ zen::trim(tmp);
+ if (tmp == "DeletePermanently")
+ value = DELETE_PERMANENTLY;
+ else if (tmp == "MoveToRecycleBin")
+ value = MOVE_TO_RECYCLE_BIN;
+ else if (tmp == "MoveToCustomDirectory")
+ value = MOVE_TO_CUSTOM_DIRECTORY;
+ else
+ return false;
+ return true;
+}
+
+
+template <> inline
+void writeText(const SymLinkHandling& value, std::string& output)
+{
+ switch (value)
+ {
+ case SYMLINK_IGNORE:
+ output = "Ignore";
+ break;
+ case SYMLINK_USE_DIRECTLY:
+ output = "UseDirectly";
+ break;
+ case SYMLINK_FOLLOW_LINK:
+ output = "FollowLink";
+ break;
+ }
+}
+
+template <> inline
+bool readText(const std::string& input, SymLinkHandling& value)
+{
+ std::string tmp = input;
+ zen::trim(tmp);
+ if (tmp == "Ignore")
+ value = SYMLINK_IGNORE;
+ else if (tmp == "UseDirectly")
+ value = SYMLINK_USE_DIRECTLY;
+ else if (tmp == "FollowLink")
+ value = SYMLINK_FOLLOW_LINK;
+ else
+ return false;
+ return true;
+}
+
+
+template <> inline
+void writeText(const UnitTime& value, std::string& output)
+{
+ switch (value)
+ {
+ case UTIME_NONE:
+ output = "Inactive";
+ break;
+ // case UTIME_LAST_X_HOURS:
+ // output = "x-hours";
+ // break;
+ case UTIME_TODAY:
+ output = "Today";
+ break;
+ case UTIME_THIS_WEEK:
+ output = "Week";
+ break;
+ case UTIME_THIS_MONTH:
+ output = "Month";
+ break;
+ case UTIME_THIS_YEAR:
+ output = "Year";
+ break;
+ }
+}
+
+template <> inline
+bool readText(const std::string& input, UnitTime& value)
+{
+ std::string tmp = input;
+ zen::trim(tmp);
+ if (tmp == "Inactive")
+ value = UTIME_NONE;
+ // else if (tmp == "x-hours")
+ // value = UTIME_LAST_X_HOURS;
+ else if (tmp == "Today")
+ value = UTIME_TODAY;
+ else if (tmp == "Week")
+ value = UTIME_THIS_WEEK;
+ else if (tmp == "Month")
+ value = UTIME_THIS_MONTH;
+ else if (tmp == "Year")
+ value = UTIME_THIS_YEAR;
+ else
+ return false;
+ return true;
+}
+
+
+template <> inline
+void writeText(const ColumnTypes& value, std::string& output)
+{
+ output = toString<std::string>(value);
+}
+
+template <> inline
+bool readText(const std::string& input, ColumnTypes& value)
+{
+ value = static_cast<ColumnTypes>(toNumber<int>(input));
+ return true;
+}
+
+
+template <> inline
+void writeText(const UnitSize& value, std::string& output)
+{
+ switch (value)
+ {
+ case USIZE_NONE:
+ output = "Inactive";
+ break;
+ case USIZE_BYTE:
+ output = "Byte";
+ break;
+ case USIZE_KB:
+ output = "KB";
+ break;
+ case USIZE_MB:
+ output = "MB";
+ break;
+ }
+}
+
+template <> inline
+bool readText(const std::string& input, UnitSize& value)
+{
+ std::string tmp = input;
+ zen::trim(tmp);
+ if (tmp == "Inactive")
+ value = USIZE_NONE;
+ else if (tmp == "Byte")
+ value = USIZE_BYTE;
+ else if (tmp == "KB")
+ value = USIZE_KB;
+ else if (tmp == "MB")
+ value = USIZE_MB;
+ else
+ return false;
+ return true;
+}
+
+
+template <> inline
+void writeText(const DirectionConfig::Variant& value, std::string& output)
+{
+ switch (value)
+ {
+ case DirectionConfig::AUTOMATIC:
+ output = "Automatic";
+ break;
+ case DirectionConfig::MIRROR:
+ output = "Mirror";
+ break;
+ case DirectionConfig::UPDATE:
+ output = "Update";
+ break;
+ case DirectionConfig::CUSTOM:
+ output = "Custom";
+ break;
+ }
+}
+
+template <> inline
+bool readText(const std::string& input, DirectionConfig::Variant& value)
+{
+ std::string tmp = input;
+ zen::trim(tmp);
+ if (tmp == "Automatic")
+ value = DirectionConfig::AUTOMATIC;
+ else if (tmp == "Mirror")
+ value = DirectionConfig::MIRROR;
+ else if (tmp == "Update")
+ value = DirectionConfig::UPDATE;
+ else if (tmp == "Custom")
+ value = DirectionConfig::CUSTOM;
+ else
+ return false;
+ return true;
+}
+
+
+template <> inline
+bool readValue(const XmlElement& input, ColumnAttrib& value)
+{
+ XmlIn in(input);
+ bool rv1 = in.attribute("Type", value.type);
+ bool rv2 = in.attribute("Visible", value.visible);
+ bool rv3 = in.attribute("Width", value.width);
+ value.position = 0;
+ return rv1 && rv2 && rv3;
+}
+
+template <> inline
+void writeValue(const ColumnAttrib& value, XmlElement& output)
+{
+ XmlOut out(output);
+ out.attribute("Type", value.type);
+ out.attribute("Visible", value.visible);
+ out.attribute("Width", value.width);
+}
+}
+
+
+namespace
+{
+void readConfig(const XmlIn& in, CompConfig& cmpConfig)
+{
+ in["Variant" ](cmpConfig.compareVar);
+ in["HandleSymlinks"](cmpConfig.handleSymlinks);
+}
+
+
+void readConfig(const XmlIn& in, DirectionConfig& directCfg)
+{
+ in["Variant"](directCfg.var);
+
+ XmlIn inCustDir = in["CustomDirections"];
+ inCustDir["LeftOnly" ](directCfg.custom.exLeftSideOnly);
+ inCustDir["RightOnly" ](directCfg.custom.exRightSideOnly);
+ inCustDir["LeftNewer" ](directCfg.custom.leftNewer);
+ inCustDir["RightNewer"](directCfg.custom.rightNewer);
+ inCustDir["Different" ](directCfg.custom.different);
+ inCustDir["Conflict" ](directCfg.custom.conflict);
+}
+
+
+void readConfig(const XmlIn& in, SyncConfig& syncCfg)
+{
+ readConfig(in, syncCfg.directionCfg);
+
+ in["DeletionPolicy" ](syncCfg.handleDeletion);
+ in["CustomDeletionFolder"](syncCfg.customDeletionDirectory);
+}
+
+
+void readConfig(const XmlIn& in, FilterConfig& filter)
+{
+ in["Include"](filter.includeFilter);
+ in["Exclude"](filter.excludeFilter);
+
+ in["TimeSpan" ](filter.timeSpan);
+ in["UnitTimeSpan"](filter.unitTimeSpan);
+
+ in["SizeMin" ](filter.sizeMin);
+ in["UnitSizeMin"](filter.unitSizeMin);
+
+ in["SizeMax" ](filter.sizeMax);
+ in["UnitSizeMax"](filter.unitSizeMax);
+}
+
+
+void readConfig(const XmlIn& in, FolderPairEnh& enhPair)
+{
+ //read folder pairs
+ in["Left" ](enhPair.leftDirectory);
+ in["Right"](enhPair.rightDirectory);
+
+ //###########################################################
+ //alternate comp configuration (optional)
+ XmlIn inAltCmp = in["CompareConfig"];
+ if (inAltCmp)
+ {
+ CompConfig altCmpCfg;
+ readConfig(inAltCmp, altCmpCfg);
+
+ enhPair.altCmpConfig = std::make_shared<CompConfig>(altCmpCfg);;
+ }
+ //###########################################################
+ //alternate sync configuration (optional)
+ XmlIn inAltSync = in["SyncConfig"];
+ if (inAltSync)
+ {
+ SyncConfig altSyncCfg;
+ readConfig(inAltSync, altSyncCfg);
+
+ enhPair.altSyncConfig = std::make_shared<SyncConfig>(altSyncCfg);
+ }
+
+ //###########################################################
+ //alternate filter configuration
+ readConfig(in["LocalFilter"], enhPair.localFilter);
+}
+
+
+void readConfig(const XmlIn& in, MainConfiguration& mainCfg)
+{
+ //read compare settings
+ XmlIn inCmp = in["MainConfig"]["Comparison"];
+
+ readConfig(inCmp, mainCfg.cmpConfig);
+ //###########################################################
+
+ XmlIn inSync = in["MainConfig"]["SyncConfig"];
+
+ //read sync configuration
+ readConfig(inSync, mainCfg.syncCfg);
+ //###########################################################
+
+ XmlIn inFilter = in["MainConfig"]["GlobalFilter"];
+ //read filter settings
+ readConfig(inFilter, mainCfg.globalFilter);
+
+ //###########################################################
+ //read all folder pairs
+ mainCfg.additionalPairs.clear();
+
+ bool firstIter = true;
+ for (XmlIn inPair = in["MainConfig"]["FolderPairs"]["Pair"]; inPair; inPair.next())
+ {
+ FolderPairEnh newPair;
+ readConfig(inPair, newPair);
+
+ if (firstIter)
+ {
+ firstIter = false;
+ mainCfg.firstPair = newPair; //set first folder pair
+ }
+ else
+ mainCfg.additionalPairs.push_back(newPair); //set additional folder pairs
+ }
+}
+
+
+void readConfig(const XmlIn& in, xmlAccess::XmlGuiConfig& config)
+{
+ ::readConfig(in, config.mainCfg); //read main config
+
+ //read GUI specific config data
+ XmlIn inGuiCfg = in["GuiConfig"];
+
+ inGuiCfg["HideFiltered" ](config.hideFilteredElements);
+ inGuiCfg["HandleError" ](config.handleError);
+ inGuiCfg["SyncPreviewActive"](config.syncPreviewEnabled);
+}
+
+
+void readConfig(const XmlIn& in, xmlAccess::XmlBatchConfig& config)
+{
+ ::readConfig(in, config.mainCfg); //read main config
+
+ //read GUI specific config data
+ XmlIn inBatchCfg = in["BatchConfig"];
+
+ inBatchCfg["Silent" ](config.silent);
+ inBatchCfg["LogfileDirectory"](config.logFileDirectory);
+ inBatchCfg["LogfileCountMax" ](config.logFileCountMax);
+ inBatchCfg["HandleError" ](config.handleError);
+}
+
+
+void readConfig(const XmlIn& in, XmlGlobalSettings& config)
+{
+ XmlIn inShared = in["Shared"];
+
+ //try to read program language setting
+ inShared["Language"](config.programLanguage);
+
+ inShared["CopyLockedFiles" ](config.copyLockedFiles);
+ inShared["CopyFilePermissions" ](config.copyFilePermissions);
+ inShared["TransactionalFileCopy"](config.transactionalFileCopy);
+ inShared["VerifyCopiedFiles" ](config.verifyFileCopy);
+
+ //max. allowed file time deviation
+ inShared["FileTimeTolerance"](config.fileTimeTolerance);
+
+ XmlIn inOpt = inShared["ShowOptionalDialogs"];
+ inOpt["CheckForDependentFolders" ](config.optDialogs.warningDependentFolders);
+ inOpt["CheckForMultipleWriteAccess" ](config.optDialogs.warningMultiFolderWriteAccess);
+ inOpt["CheckForSignificantDifference"](config.optDialogs.warningSignificantDifference);
+ inOpt["CheckForFreeDiskSpace"](config.optDialogs.warningNotEnoughDiskSpace);
+ inOpt["CheckForUnresolvedConflicts"](config.optDialogs.warningUnresolvedConflicts);
+ inOpt["NotifyDatabaseError"](config.optDialogs.warningSyncDatabase);
+ inOpt["CheckMissingRecycleBin"](config.optDialogs.warningRecyclerMissing);
+ inOpt["PopupOnConfigChange"](config.optDialogs.popupOnConfigChange);
+ inOpt["SummaryBeforeSync" ](config.optDialogs.showSummaryBeforeSync);
+
+ //gui specific global settings (optional)
+ XmlIn inGui = in["Gui"];
+ XmlIn inWnd = inGui["Windows"]["Main"];
+
+ //read application window size and position
+ inWnd["Width" ](config.gui.dlgSize.x);
+ inWnd["Height" ](config.gui.dlgSize.y);
+ inWnd["PosX" ](config.gui.dlgPos.x);
+ inWnd["PosY" ](config.gui.dlgPos.y);
+ inWnd["Maximized"](config.gui.isMaximized);
+
+ inWnd["MaxFolderPairsVisible"](config.gui.maxFolderPairsVisible);
+
+ inWnd["ManualDeletionOnBothSides"](config.gui.deleteOnBothSides);
+ inWnd["ManualDeletionUseRecycler"](config.gui.useRecyclerForManualDeletion);
+ inWnd["RespectCaseOnSearch" ](config.gui.textSearchRespectCase);
+
+ inWnd["IconSize"](config.gui.iconSize);
+
+ //###########################################################
+ //read column attributes
+ XmlIn inColLeft = inWnd["LeftColumns"];
+ inColLeft.attribute("AutoAdjust", config.gui.autoAdjustColumnsLeft);
+
+ inColLeft(config.gui.columnAttribLeft);
+ for (size_t i = 0; i < config.gui.columnAttribLeft.size(); ++i)
+ config.gui.columnAttribLeft[i].position = i;
+
+ //###########################################################
+ XmlIn inColRight = inWnd["RightColumns"];
+ inColRight.attribute("AutoAdjust", config.gui.autoAdjustColumnsRight);
+
+ inColRight(config.gui.columnAttribRight);
+ for (size_t i = 0; i < config.gui.columnAttribRight.size(); ++i)
+ config.gui.columnAttribRight[i].position = i;
+
+ inWnd["FolderHistoryLeft" ](config.gui.folderHistoryLeft);
+ inWnd["FolderHistoryRight"](config.gui.folderHistoryRight);
+ inWnd["MaximumHistorySize"](config.gui.folderHistMax);
+ inWnd["Perspective" ](config.gui.guiPerspectiveLast);
+
+ //external applications
+ inGui["ExternalApplications"](config.gui.externelApplications);
+
+ //load config file history
+ inGui["LastConfigActive"](config.gui.lastUsedConfigFiles);
+ inGui["ConfigHistory"](config.gui.cfgFileHistory);
+
+ //last update check
+ inGui["LastUpdateCheck"](config.gui.lastUpdateCheck);
+
+ //batch specific global settings
+ //XmlIn inBatch = in["Batch"];
+}
+
+
+template <class ConfigType>
+void readConfig(const Zstring& filename, XmlType type, ConfigType& config)
+{
+ XmlDoc doc;
+ loadXmlDocument(filename, doc); //throw FfsXmlError
+
+ if (getXmlType(doc) != type) //throw()
+ throw FfsXmlError(_("Error parsing configuration file:") + "\n\"" + filename + "\"");
+
+ XmlIn in(doc);
+ ::readConfig(in, config);
+
+ if (in.errorsOccured())
+ throw FfsXmlError(_("Error parsing configuration file:") + "\n\"" + filename + "\"\n\n" +
+ getErrorMessageFormatted(in), FfsXmlError::WARNING);
+}
+}
+
+
+void xmlAccess::readConfig(const wxString& filename, xmlAccess::XmlGuiConfig& config)
+{
+ ::readConfig(toZ(filename), XML_TYPE_GUI, config);
+}
+
+
+void xmlAccess::readConfig(const wxString& filename, xmlAccess::XmlBatchConfig& config)
+{
+ ::readConfig(toZ(filename), XML_TYPE_BATCH, config);
+}
+
+
+void xmlAccess::readConfig(xmlAccess::XmlGlobalSettings& config)
+{
+ ::readConfig(toZ(getGlobalConfigFile()), XML_TYPE_GLOBAL, config);
+}
+
+
+//################################################################################################
+namespace
+{
+void writeConfig(const CompConfig& cmpConfig, XmlOut& out)
+{
+ out["Variant" ](cmpConfig.compareVar);
+ out["HandleSymlinks"](cmpConfig.handleSymlinks);
+}
+
+
+void writeConfig(const DirectionConfig& directCfg, XmlOut& out)
+{
+ out["Variant"](directCfg.var);
+
+ XmlOut outCustDir = out["CustomDirections"];
+ outCustDir["LeftOnly" ](directCfg.custom.exLeftSideOnly);
+ outCustDir["RightOnly" ](directCfg.custom.exRightSideOnly);
+ outCustDir["LeftNewer" ](directCfg.custom.leftNewer);
+ outCustDir["RightNewer"](directCfg.custom.rightNewer);
+ outCustDir["Different" ](directCfg.custom.different);
+ outCustDir["Conflict" ](directCfg.custom.conflict);
+}
+
+
+void writeConfig(const SyncConfig& syncCfg, XmlOut& out)
+{
+ writeConfig(syncCfg.directionCfg, out);
+
+ out["DeletionPolicy" ](syncCfg.handleDeletion);
+ out["CustomDeletionFolder"](syncCfg.customDeletionDirectory);
+}
+
+
+void writeConfig(const FilterConfig& filter, XmlOut& out)
+{
+ out["Include"](filter.includeFilter);
+ out["Exclude"](filter.excludeFilter);
+
+ out["TimeSpan" ](filter.timeSpan);
+ out["UnitTimeSpan"](filter.unitTimeSpan);
+
+ out["SizeMin" ](filter.sizeMin);
+ out["UnitSizeMin"](filter.unitSizeMin);
+
+ out["SizeMax" ](filter.sizeMax);
+ out["UnitSizeMax"](filter.unitSizeMax);
+}
+
+
+void writeConfigFolderPair(const FolderPairEnh& enhPair, XmlOut& out)
+{
+ XmlOut outPair = out.ref().addChild("Pair");
+
+ //read folder pairs
+ outPair["Left" ](enhPair.leftDirectory);
+ outPair["Right"](enhPair.rightDirectory);
+
+ //###########################################################
+ //alternate comp configuration (optional)
+ if (enhPair.altCmpConfig.get())
+ {
+ XmlOut outAlt = outPair["CompareConfig"];
+
+ writeConfig(*enhPair.altCmpConfig, outAlt);
+ }
+ //###########################################################
+ //alternate sync configuration (optional)
+ if (enhPair.altSyncConfig.get())
+ {
+ XmlOut outAltSync = outPair["SyncConfig"];
+
+ writeConfig(*enhPair.altSyncConfig, outAltSync);
+ }
+
+ //###########################################################
+ //alternate filter configuration
+ XmlOut outFilter = outPair["LocalFilter"];
+ writeConfig(enhPair.localFilter, outFilter);
+}
+
+
+void writeConfig(const MainConfiguration& mainCfg, XmlOut& out)
+{
+ XmlOut outCmp = out["MainConfig"]["Comparison"];
+
+ writeConfig(mainCfg.cmpConfig, outCmp);
+ //###########################################################
+
+ XmlOut outSync = out["MainConfig"]["SyncConfig"];
+
+ writeConfig(mainCfg.syncCfg, outSync);
+ //###########################################################
+
+ XmlOut outFilter = out["MainConfig"]["GlobalFilter"];
+ //write filter settings
+ writeConfig(mainCfg.globalFilter, outFilter);
+
+ //###########################################################
+ //write all folder pairs
+
+ XmlOut outFp = out["MainConfig"]["FolderPairs"];
+
+ //write first folder pair
+ writeConfigFolderPair(mainCfg.firstPair, outFp);
+
+ //write additional folder pairs
+ std::for_each(mainCfg.additionalPairs.begin(), mainCfg.additionalPairs.end(),
+ [&](const FolderPairEnh& fp) { writeConfigFolderPair(fp, outFp); });
+}
+
+
+void writeConfig(const XmlGuiConfig& config, XmlOut& out)
+{
+ writeConfig(config.mainCfg, out); //write main config
+
+ //write GUI specific config data
+ XmlOut outGuiCfg = out["GuiConfig"];
+
+ outGuiCfg["HideFiltered" ](config.hideFilteredElements);
+ outGuiCfg["HandleError" ](config.handleError);
+ outGuiCfg["SyncPreviewActive"](config.syncPreviewEnabled);
+}
+
+void writeConfig(const XmlBatchConfig& config, XmlOut& out)
+{
+
+ writeConfig(config.mainCfg, out); //write main config
+
+ //write GUI specific config data
+ XmlOut outBatchCfg = out["BatchConfig"];
+
+ outBatchCfg["Silent" ](config.silent);
+ outBatchCfg["LogfileDirectory"](config.logFileDirectory);
+ outBatchCfg["LogfileCountMax" ](config.logFileCountMax);
+ outBatchCfg["HandleError" ](config.handleError);
+}
+
+
+void writeConfig(const XmlGlobalSettings& config, XmlOut& out)
+{
+ XmlOut outShared = out["Shared"];
+
+ //write program language setting
+ outShared["Language"](config.programLanguage);
+
+ outShared["CopyLockedFiles" ](config.copyLockedFiles);
+ outShared["CopyFilePermissions" ](config.copyFilePermissions);
+ outShared["TransactionalFileCopy"](config.transactionalFileCopy);
+ outShared["VerifyCopiedFiles" ](config.verifyFileCopy);
+
+ //max. allowed file time deviation
+ outShared["FileTimeTolerance"](config.fileTimeTolerance);
+
+ XmlOut outOpt = outShared["ShowOptionalDialogs"];
+ outOpt["CheckForDependentFolders" ](config.optDialogs.warningDependentFolders);
+ outOpt["CheckForMultipleWriteAccess" ](config.optDialogs.warningMultiFolderWriteAccess);
+ outOpt["CheckForSignificantDifference"](config.optDialogs.warningSignificantDifference);
+ outOpt["CheckForFreeDiskSpace"](config.optDialogs.warningNotEnoughDiskSpace);
+ outOpt["CheckForUnresolvedConflicts"](config.optDialogs.warningUnresolvedConflicts);
+ outOpt["NotifyDatabaseError"](config.optDialogs.warningSyncDatabase);
+ outOpt["CheckMissingRecycleBin"](config.optDialogs.warningRecyclerMissing);
+ outOpt["PopupOnConfigChange"](config.optDialogs.popupOnConfigChange);
+ outOpt["SummaryBeforeSync" ](config.optDialogs.showSummaryBeforeSync);
+
+ //gui specific global settings (optional)
+ XmlOut outGui = out["Gui"];
+ XmlOut outWnd = outGui["Windows"]["Main"];
+
+ //write application window size and position
+ outWnd["Width" ](config.gui.dlgSize.x);
+ outWnd["Height" ](config.gui.dlgSize.y);
+ outWnd["PosX" ](config.gui.dlgPos.x);
+ outWnd["PosY" ](config.gui.dlgPos.y);
+ outWnd["Maximized"](config.gui.isMaximized);
+
+ outWnd["MaxFolderPairsVisible"](config.gui.maxFolderPairsVisible);
+
+ outWnd["ManualDeletionOnBothSides"](config.gui.deleteOnBothSides);
+ outWnd["ManualDeletionUseRecycler"](config.gui.useRecyclerForManualDeletion);
+ outWnd["RespectCaseOnSearch" ](config.gui.textSearchRespectCase);
+
+ outWnd["IconSize"](config.gui.iconSize);
+
+ //###########################################################
+
+ //write column attributes
+ XmlOut outColLeft = outWnd["LeftColumns"];
+ outColLeft.attribute("AutoAdjust", config.gui.autoAdjustColumnsLeft);
+
+ outColLeft(config.gui.columnAttribLeft);
+
+ //###########################################################
+ XmlOut outColRight = outWnd["RightColumns"];
+ outColRight.attribute("AutoAdjust", config.gui.autoAdjustColumnsRight);
+
+ outColRight(config.gui.columnAttribRight);
+
+ outWnd["FolderHistoryLeft" ](config.gui.folderHistoryLeft);
+ outWnd["FolderHistoryRight"](config.gui.folderHistoryRight);
+ outWnd["MaximumHistorySize"](config.gui.folderHistMax);
+ outWnd["Perspective" ](config.gui.guiPerspectiveLast);
+
+ //external applications
+ outGui["ExternalApplications"](config.gui.externelApplications);
+
+ //load config file history
+ outGui["LastConfigActive"](config.gui.lastUsedConfigFiles);
+ outGui["ConfigHistory"](config.gui.cfgFileHistory);
+
+ //last update check
+ outGui["LastUpdateCheck"](config.gui.lastUpdateCheck);
+
+ //batch specific global settings
+ //XmlOut outBatch = out["Batch"];
+}
+
+
+template <class ConfigType>
+void writeConfig(const ConfigType& config, XmlType type, const wxString& filename)
+{
+ XmlDoc doc("FreeFileSync");
+ setXmlType(doc, type); //throw()
+
+ XmlOut out(doc);
+ writeConfig(config, out);
+
+ saveXmlDocument(doc, toZ(filename)); //throw FfsXmlError
+}
+}
+
+void xmlAccess::writeConfig(const XmlGuiConfig& config, const wxString& filename)
+{
+ ::writeConfig(config, XML_TYPE_GUI, filename); //throw FfsXmlError
+}
+
+
+void xmlAccess::writeConfig(const XmlBatchConfig& config, const wxString& filename)
+{
+ ::writeConfig(config, XML_TYPE_BATCH, filename); //throw FfsXmlError
+}
+
+
+void xmlAccess::writeConfig(const XmlGlobalSettings& config)
+{
+ ::writeConfig(config, XML_TYPE_GLOBAL, getGlobalConfigFile()); //throw FfsXmlError
+}
+
+
+wxString xmlAccess::extractJobName(const wxString& configFilename)
+{
+ const wxString shortName = configFilename.AfterLast(FILE_NAME_SEPARATOR); //returns the whole string if seperator not found
+ const wxString jobName = shortName.BeforeLast(wxChar('.')); //returns empty string if seperator not found
+ return jobName.IsEmpty() ? shortName : jobName;
+}
diff --git a/lib/process_xml.h b/lib/process_xml.h
new file mode 100644
index 00000000..5f1dfb93
--- /dev/null
+++ b/lib/process_xml.h
@@ -0,0 +1,285 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef PROCESSXML_H_INCLUDED
+#define PROCESSXML_H_INCLUDED
+
+#include <wx/gdicmn.h>
+#include "../structures.h"
+#include "xml_base.h"
+#include "localization.h"
+
+namespace xmlAccess
+{
+enum XmlType
+{
+ XML_TYPE_GUI,
+ XML_TYPE_BATCH,
+ XML_TYPE_GLOBAL,
+ XML_TYPE_OTHER
+};
+
+XmlType getXmlType(const wxString& filename); //throw()
+
+
+enum OnError
+{
+ ON_ERROR_POPUP,
+ ON_ERROR_IGNORE,
+ ON_ERROR_EXIT
+};
+
+enum OnGuiError
+{
+ ON_GUIERROR_POPUP,
+ ON_GUIERROR_IGNORE
+};
+
+enum ColumnTypes
+{
+ DIRECTORY, //this needs to begin with 0 and be continuous (some code relies on it)
+ FULL_PATH,
+ REL_PATH,
+ FILENAME,
+ SIZE,
+ DATE,
+ EXTENSION
+};
+const size_t COLUMN_TYPE_COUNT = 7;
+
+struct ColumnAttrib
+{
+ ColumnTypes type;
+ bool visible;
+ size_t position;
+ int width;
+};
+typedef std::vector<ColumnAttrib> ColumnAttributes;
+
+
+typedef wxString Description;
+typedef wxString Commandline;
+typedef std::vector<std::pair<Description, Commandline> > ExternalApps;
+
+//---------------------------------------------------------------------
+struct XmlGuiConfig
+{
+ XmlGuiConfig() :
+ hideFilteredElements(false),
+ handleError(ON_GUIERROR_POPUP),
+ syncPreviewEnabled(true) {} //initialize values
+
+ zen::MainConfiguration mainCfg;
+
+ bool hideFilteredElements;
+ OnGuiError handleError; //reaction on error situation during synchronization
+ bool syncPreviewEnabled;
+
+ bool operator==(const XmlGuiConfig& other) const
+ {
+ return mainCfg == other.mainCfg &&
+ hideFilteredElements == other.hideFilteredElements &&
+ handleError == other.handleError &&
+ syncPreviewEnabled == other.syncPreviewEnabled;
+ }
+
+ bool operator!=(const XmlGuiConfig& other) const { return !(*this == other); }
+};
+
+
+struct XmlBatchConfig
+{
+ XmlBatchConfig() :
+ silent(false),
+ logFileCountMax(200),
+ handleError(ON_ERROR_POPUP) {}
+
+ zen::MainConfiguration mainCfg;
+
+ bool silent;
+ wxString logFileDirectory;
+ size_t logFileCountMax;
+ OnError handleError; //reaction on error situation during synchronization
+};
+
+
+struct OptionalDialogs
+{
+ OptionalDialogs() { resetDialogs();}
+
+ void resetDialogs();
+
+ bool warningDependentFolders;
+ bool warningMultiFolderWriteAccess;
+ bool warningSignificantDifference;
+ bool warningNotEnoughDiskSpace;
+ bool warningUnresolvedConflicts;
+ bool warningSyncDatabase;
+ bool warningRecyclerMissing;
+ bool popupOnConfigChange;
+ bool showSummaryBeforeSync;
+};
+
+
+enum FileIconSize
+{
+ ICON_SIZE_SMALL,
+ ICON_SIZE_MEDIUM,
+ ICON_SIZE_LARGE
+};
+
+
+wxString getGlobalConfigFile();
+
+struct XmlGlobalSettings
+{
+ //---------------------------------------------------------------------
+ //Shared (GUI/BATCH) settings
+ XmlGlobalSettings() :
+ programLanguage(zen::retrieveSystemLanguage()),
+ copyLockedFiles(true),
+ copyFilePermissions(false),
+ fileTimeTolerance(2), //default 2s: FAT vs NTFS
+ verifyFileCopy(false),
+ transactionalFileCopy(true) {}
+
+ int programLanguage;
+ bool copyLockedFiles; //VSS usage
+ bool copyFilePermissions;
+
+ size_t fileTimeTolerance; //max. allowed file time deviation
+ bool verifyFileCopy; //verify copied files
+ bool transactionalFileCopy;
+
+ OptionalDialogs optDialogs;
+
+ //---------------------------------------------------------------------
+ struct _Gui
+ {
+ _Gui() :
+ dlgPos(wxDefaultCoord, wxDefaultCoord),
+ dlgSize(wxDefaultCoord, wxDefaultCoord),
+ isMaximized(false),
+ maxFolderPairsVisible(6),
+ autoAdjustColumnsLeft(false),
+ autoAdjustColumnsRight(false),
+ folderHistMax(15),
+ deleteOnBothSides(false),
+ useRecyclerForManualDeletion(true), //enable if OS supports it; else user will have to activate first and then get an error message
+#ifdef FFS_WIN
+ textSearchRespectCase(false),
+#elif defined FFS_LINUX
+ textSearchRespectCase(true),
+#endif
+ iconSize(ICON_SIZE_MEDIUM),
+ lastUpdateCheck(0)
+ {
+ //default external apps will be translated "on the fly"!!!
+#ifdef FFS_WIN
+ externelApplications.push_back(std::make_pair(wxT("Show in Explorer"), //mark for extraction: _("Show in Explorer")
+ wxT("explorer /select, \"%name\"")));
+ externelApplications.push_back(std::make_pair(wxT("Open with default application"), //mark for extraction: _("Open with default application")
+ wxT("\"%name\"")));
+#elif defined FFS_LINUX
+ externelApplications.push_back(std::make_pair(wxT("Browse directory"), //mark for extraction: _("Browse directory")
+ wxT("xdg-open \"%dir\"")));
+ externelApplications.push_back(std::make_pair(wxT("Open with default application"), //mark for extraction: _("Open with default application")
+ wxT("xdg-open \"%name\"")));
+#endif
+ }
+
+ wxPoint dlgPos;
+ wxSize dlgSize;
+ bool isMaximized;
+
+ int maxFolderPairsVisible;
+
+ ColumnAttributes columnAttribLeft;
+ ColumnAttributes columnAttribRight;
+
+ bool autoAdjustColumnsLeft;
+ bool autoAdjustColumnsRight;
+
+ ExternalApps externelApplications;
+
+ std::vector<wxString> cfgFileHistory;
+ std::vector<wxString> lastUsedConfigFiles;
+
+ std::vector<Zstring> folderHistoryLeft;
+ std::vector<Zstring> folderHistoryRight;
+ unsigned int folderHistMax;
+
+ bool deleteOnBothSides;
+ bool useRecyclerForManualDeletion;
+ bool textSearchRespectCase;
+
+ FileIconSize iconSize;
+
+ long lastUpdateCheck; //time of last update check
+
+ wxString guiPerspectiveLast; //used by wxAuiManager
+ } gui;
+
+ //---------------------------------------------------------------------
+ //struct _Batch
+};
+
+
+inline
+bool sortByType(const ColumnAttrib& a, const ColumnAttrib& b)
+{
+ return a.type < b.type;
+}
+
+
+inline
+bool sortByPositionOnly(const ColumnAttrib& a, const ColumnAttrib& b)
+{
+ return a.position < b.position;
+}
+
+
+inline
+bool sortByPositionAndVisibility(const ColumnAttrib& a, const ColumnAttrib& b)
+{
+ if (a.visible == false) //hidden elements shall appear at end of vector
+ return false;
+ if (b.visible == false)
+ return true;
+ return a.position < b.position;
+}
+
+void readConfig(const wxString& filename, XmlGuiConfig& config); //throw xmlAccess::FfsXmlError
+void readConfig(const wxString& filename, XmlBatchConfig& config); //throw xmlAccess::FfsXmlError
+void readConfig( XmlGlobalSettings& config); //throw xmlAccess::FfsXmlError
+
+void writeConfig(const XmlGuiConfig& config, const wxString& filename); //throw xmlAccess::FfsXmlError
+void writeConfig(const XmlBatchConfig& config, const wxString& filename); //throw xmlAccess::FfsXmlError
+void writeConfig(const XmlGlobalSettings& config); //throw xmlAccess::FfsXmlError
+
+//config conversion utilities
+XmlGuiConfig convertBatchToGui(const XmlBatchConfig& batchCfg);
+XmlBatchConfig convertGuiToBatch(const XmlGuiConfig& guiCfg, const wxString& referenceFile);
+
+
+//convert (multiple) *.ffs_gui, *.ffs_batch files or combinations of both into target config structure:
+enum MergeType
+{
+ MERGE_GUI, //pure gui config files
+ MERGE_BATCH, // " batch "
+ MERGE_GUI_BATCH, //gui and batch files
+ MERGE_OTHER
+};
+MergeType getMergeType(const std::vector<wxString>& filenames); //throw ()
+
+void convertConfig(const std::vector<wxString>& filenames, XmlGuiConfig& config); //throw xmlAccess::FfsXmlError
+void convertConfig(const std::vector<wxString>& filenames, XmlBatchConfig& config); //throw xmlAccess::FfsXmlError
+
+wxString extractJobName(const wxString& configFilename);
+}
+
+
+#endif // PROCESSXML_H_INCLUDED
diff --git a/lib/recycler.cpp b/lib/recycler.cpp
new file mode 100644
index 00000000..30083701
--- /dev/null
+++ b/lib/recycler.cpp
@@ -0,0 +1,204 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "recycler.h"
+#include <stdexcept>
+#include <iterator>
+#include <zen/file_handling.h>
+
+#ifdef FFS_WIN
+#include <algorithm>
+#include <functional>
+#include <vector>
+#include <boost/thread/once.hpp>
+#include <zen/dll.h>
+#include <zen/win.h> //includes "windows.h"
+#include <zen/assert_static.h>
+#include <zen/win_ver.h>
+#include <zen/long_path_prefix.h>
+#include "IFileOperation/file_op.h"
+
+#elif defined FFS_LINUX
+#include <sys/stat.h>
+#include <giomm/file.h>
+#endif
+
+using namespace zen;
+
+
+namespace
+{
+#ifdef FFS_WIN
+/*
+Performance test: delete 1000 files
+------------------------------------
+SHFileOperation - single file 33s
+SHFileOperation - multiple files 2,1s
+IFileOperation - single file 33s
+IFileOperation - multiple files 2,1s
+
+=> SHFileOperation and IFileOperation have nearly IDENTICAL performance characteristics!
+
+Nevertheless, let's use IFileOperation for better error reporting!
+*/
+
+void moveToWindowsRecycler(const std::vector<Zstring>& filesToDelete) //throw FileError
+{
+ if (filesToDelete.empty())
+ return;
+
+ static bool useIFileOperation = false;
+ static boost::once_flag once = BOOST_ONCE_INIT; //caveat: function scope static initialization is not thread-safe in VS 2010!
+ boost::call_once(once, []() { useIFileOperation = vistaOrLater(); });
+
+ if (useIFileOperation) //new recycle bin usage: available since Vista
+ {
+ std::vector<const wchar_t*> fileNames;
+ std::transform(filesToDelete.begin(), filesToDelete.end(),
+ std::back_inserter(fileNames), std::mem_fun_ref(&Zstring::c_str));
+
+ using namespace fileop;
+ const DllFun<MoveToRecycleBinFct> moveToRecycler(getDllName(), moveToRecycleBinFctName);
+ const DllFun<GetLastErrorFct> getLastError (getDllName(), getLastErrorFctName);
+
+ if (!moveToRecycler || !getLastError)
+ throw FileError(_("Error moving to Recycle Bin:") + "\n\"" + fileNames[0] + "\"" + //report first file only... better than nothing
+ "\n\n" + _("Could not load a required DLL:") + " \"" + getDllName() + "\"");
+
+ //#warning moving long file paths to recycler does not work! clarify!
+ // std::vector<Zstring> temp;
+ // std::transform(filesToDelete.begin(), filesToDelete.end(),
+ // std::back_inserter(temp), std::ptr_fun(zen::removeLongPathPrefix)); //::IFileOperation() can't handle \\?\-prefix!
+
+ if (!moveToRecycler(&fileNames[0], //array must not be empty
+ fileNames.size()))
+ {
+ wchar_t errorMessage[2000];
+ getLastError(errorMessage, 2000);
+ throw FileError(_("Error moving to Recycle Bin:") + "\n\"" + fileNames[0] + "\"" + //report first file only... better than nothing
+ "\n\n" + "(" + errorMessage + ")");
+ }
+ }
+ else //regular recycle bin usage: available since XP
+ {
+ Zstring filenameDoubleNull;
+ for (std::vector<Zstring>::const_iterator i = filesToDelete.begin(); i != filesToDelete.end(); ++i)
+ {
+ //#warning moving long file paths to recycler does not work! clarify!
+ //filenameDoubleNull += removeLongPathPrefix(*i); //::SHFileOperation() can't handle \\?\-prefix!
+ //You should use fully-qualified path names with this function. Using it with relative path names is not thread safe.
+ filenameDoubleNull += *i; //::SHFileOperation() can't handle \\?\-prefix!
+ filenameDoubleNull += L'\0';
+ }
+
+ SHFILEOPSTRUCT fileOp = {};
+ fileOp.hwnd = NULL;
+ fileOp.wFunc = FO_DELETE;
+ fileOp.pFrom = filenameDoubleNull.c_str();
+ fileOp.pTo = NULL;
+ fileOp.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_SILENT | FOF_NOERRORUI;
+ fileOp.fAnyOperationsAborted = false;
+ fileOp.hNameMappings = NULL;
+ fileOp.lpszProgressTitle = NULL;
+
+ if (::SHFileOperation(&fileOp) != 0 || fileOp.fAnyOperationsAborted)
+ {
+ throw FileError(_("Error moving to Recycle Bin:") + "\n\"" + filenameDoubleNull + "\""); //report first file only... better than nothing
+ }
+ }
+}
+#endif
+}
+
+
+bool zen::moveToRecycleBin(const Zstring& filename) //throw FileError
+{
+ if (!somethingExists(filename))
+ return false; //neither file nor any other object with that name existing: no error situation, manual deletion relies on it!
+
+#ifdef FFS_WIN
+ //::SetFileAttributes(applyLongPathPrefix(filename).c_str(), FILE_ATTRIBUTE_NORMAL);
+ //both SHFileOperation and IFileOperation are not able to delete a folder named "System Volume Information" with normal attributes but shamelessly report success
+
+ std::vector<Zstring> fileNames;
+ fileNames.push_back(filename);
+ ::moveToWindowsRecycler(fileNames); //throw FileError
+
+#elif defined FFS_LINUX
+ Glib::RefPtr<Gio::File> fileObj = Gio::File::create_for_path(filename.c_str()); //never fails
+ try
+ {
+ if (!fileObj->trash())
+ throw FileError(_("Error moving to Recycle Bin:") + "\n\"" + filename + "\"" +
+ "\n\n" + "(unknown error)");
+ }
+ catch (const Glib::Error& errorObj)
+ {
+ //implement same behavior as in Windows: if recycler is not existing, delete permanently
+ if (errorObj.code() == G_IO_ERROR_NOT_SUPPORTED)
+ {
+ struct stat fileInfo = {};
+ if (::lstat(filename.c_str(), &fileInfo) != 0)
+ return false;
+
+ if (S_ISLNK(fileInfo.st_mode) || S_ISREG(fileInfo.st_mode))
+ removeFile(filename); //throw FileError
+ else if (S_ISDIR(fileInfo.st_mode))
+ removeDirectory(filename); //throw FileError
+ return true;
+ }
+
+ //assemble error message
+ const std::wstring errorMessage = L"Glib Error Code " + toString<std::wstring>(errorObj.code()) + /* ", " +
+ g_quark_to_string(errorObj.domain()) + */ ": " + errorObj.what();
+
+ throw FileError(_("Error moving to Recycle Bin:") + "\n\"" + filename + "\"" +
+ "\n\n" + "(" + errorMessage + ")");
+ }
+#endif
+ return true;
+}
+
+
+#ifdef FFS_WIN
+zen::StatusRecycler zen::recycleBinStatus(const Zstring& pathName)
+{
+ std::vector<wchar_t> buffer(MAX_PATH + 1);
+ if (::GetVolumePathName(applyLongPathPrefix(pathName).c_str(), //__in LPCTSTR lpszFileName,
+ &buffer[0], //__out LPTSTR lpszVolumePathName,
+ static_cast<DWORD>(buffer.size()))) //__in DWORD cchBufferLength
+ {
+ Zstring rootPath = &buffer[0];
+ if (!endsWith(rootPath, FILE_NAME_SEPARATOR)) //a trailing backslash is required
+ rootPath += FILE_NAME_SEPARATOR;
+
+ SHQUERYRBINFO recInfo = {};
+ recInfo.cbSize = sizeof(recInfo);
+ HRESULT rv = ::SHQueryRecycleBin(rootPath.c_str(), //__in_opt LPCTSTR pszRootPath,
+ &recInfo); //__inout LPSHQUERYRBINFO pSHQueryRBInfo
+ return rv == S_OK ? STATUS_REC_EXISTS : STATUS_REC_MISSING;
+ }
+ return STATUS_REC_UNKNOWN;
+}
+#elif defined FFS_LINUX
+/*
+We really need access to a similar function to check whether a directory supports trashing and emit a warning if it does not!
+
+The following function looks perfect, alas it is restricted to local files and to the implementation of GIO only:
+
+ gboolean _g_local_file_has_trash_dir(const char* dirname, dev_t dir_dev);
+ See: http://www.netmite.com/android/mydroid/2.0/external/bluetooth/glib/gio/glocalfileinfo.h
+
+ Just checking for "G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH" is not correct, since we find in
+ http://www.netmite.com/android/mydroid/2.0/external/bluetooth/glib/gio/glocalfileinfo.c
+
+ g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH,
+ writable && parent_info->has_trash_dir);
+
+ => We're NOT interested in whether the specified folder can be trashed, but whether it supports thrashing its child elements! (Only support, not actual write access!)
+ This renders G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH useless for this purpose.
+*/
+#endif
diff --git a/lib/recycler.h b/lib/recycler.h
new file mode 100644
index 00000000..4607199c
--- /dev/null
+++ b/lib/recycler.h
@@ -0,0 +1,46 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef RECYCLER_H_INCLUDED
+#define RECYCLER_H_INCLUDED
+
+#include <zen/file_error.h>
+#include <zen/zstring.h>
+
+namespace zen
+{
+/*
+--------------------
+|Recycle Bin Access|
+--------------------
+
+Windows
+-------
+Recycler always available: during runtime either SHFileOperation or (since Vista) IFileOperation will be dynamically selected
+
+Linux
+-----
+Compiler flag: `pkg-config --cflags gtkmm-2.4`
+Linker flag: `pkg-config --libs gtkmm-2.4`
+*/
+
+//move a file or folder to Recycle Bin (deletes permanently if recycle is not available)
+bool moveToRecycleBin(const Zstring& filename); //return "true" if file/dir was actually deleted; throw (FileError)
+
+
+#ifdef FFS_WIN
+enum StatusRecycler
+{
+ STATUS_REC_EXISTS,
+ STATUS_REC_MISSING,
+ STATUS_REC_UNKNOWN
+};
+
+StatusRecycler recycleBinStatus(const Zstring& pathName); //test existence of Recycle Bin API for certain path
+#endif
+}
+
+#endif // RECYCLER_H_INCLUDED
diff --git a/lib/resolve_path.cpp b/lib/resolve_path.cpp
new file mode 100644
index 00000000..14ad690e
--- /dev/null
+++ b/lib/resolve_path.cpp
@@ -0,0 +1,685 @@
+#include "resolve_path.h"
+#include <wx/utils.h>
+#include <wx/datetime.h>
+#include <wx+/string_conv.h>
+#include <map>
+#include <set>
+#include <zen/scope_guard.h>
+
+#ifdef FFS_WIN
+#include <zen/dll.h>
+#include <Shlobj.h>
+#include <zen/win.h> //includes "windows.h"
+#include <zen/long_path_prefix.h>
+#ifdef _MSC_VER
+#pragma comment(lib, "Mpr.lib")
+#endif
+
+#elif defined FFS_LINUX
+#include <zen/file_traverser.h>
+#include <unistd.h>
+#endif
+
+using namespace zen;
+
+
+namespace
+{
+#ifdef FFS_WIN
+Zstring resolveRelativePath(Zstring relativeName) //note: ::GetFullPathName() is documented not threadsafe!
+{
+ const DWORD bufferSize = 10000;
+ std::vector<Zchar> fullPath(bufferSize);
+
+ const DWORD rv = ::GetFullPathName(applyLongPathPrefix(relativeName).c_str(), //__in LPCTSTR lpFileName,
+ bufferSize, //__in DWORD nBufferLength,
+ &fullPath[0], //__out LPTSTR lpBuffer,
+ NULL); //__out LPTSTR *lpFilePart
+ if (rv == 0 || rv >= bufferSize) //theoretically, rv can never be == bufferSize
+ //ERROR! Don't do anything
+ return relativeName;
+
+ return Zstring(&fullPath[0], rv);
+}
+
+#elif defined FFS_LINUX
+Zstring resolveRelativePath(const Zstring& relativeName)
+{
+ //unfortunately ::realpath only resolves *existing* relative paths, so we have resolve to absolute by ourselves
+
+ //http://linux.die.net/man/2/path_resolution
+ if (!startsWith(relativeName, FILE_NAME_SEPARATOR)) //absolute names are exactly those starting with a '/'
+ {
+ std::vector<char> buffer(10000);
+ if (::getcwd(&buffer[0], buffer.size()) != NULL)
+ {
+ Zstring workingDir = &buffer[0];
+ if (!endsWith(workingDir, FILE_NAME_SEPARATOR))
+ workingDir += FILE_NAME_SEPARATOR;
+
+ return workingDir + relativeName;
+ }
+ }
+ return relativeName;
+
+ /*
+ char* absPath = ::realpath(relativeName.c_str(), NULL);
+ if (!absPath)
+ return relativeName; //ERROR! Don't do anything
+ ZEN_ON_BLOCK_EXIT(::free(absPath));
+
+ return Zstring(absPath);
+ */
+}
+#endif
+
+
+#ifdef FFS_WIN
+class CsidlConstants
+{
+public:
+ typedef std::map<Zstring, Zstring, LessFilename> CsidlToDirMap; //case-insensitive comparison
+
+ static const CsidlToDirMap& get()
+ {
+ static CsidlConstants inst; //potential MT solved by intializing at startup, see below
+ return inst.csidlToDir;
+ }
+
+private:
+ CsidlConstants()
+ {
+ auto addCsidl = [&](int csidl, const Zstring& paramName)
+ {
+ wchar_t buffer[MAX_PATH];
+ if (SUCCEEDED(::SHGetFolderPath(NULL, //__in HWND hwndOwner,
+ csidl | CSIDL_FLAG_DONT_VERIFY, //__in int nFolder,
+ NULL, //__in HANDLE hToken,
+ 0 /* == SHGFP_TYPE_CURRENT*/, //__in DWORD dwFlags,
+ buffer))) //__out LPTSTR pszPath
+ {
+ Zstring dirname = buffer;
+ if (!dirname.empty())
+ csidlToDir.insert(std::make_pair(paramName, dirname));
+ }
+ };
+
+ addCsidl(CSIDL_DESKTOPDIRECTORY, L"csidl_Desktop"); // C:\Users\username\Desktop
+ addCsidl(CSIDL_COMMON_DESKTOPDIRECTORY, L"csidl_PublicDesktop"); // C:\Users\All Users\Desktop
+
+ addCsidl(CSIDL_MYMUSIC, L"csidl_MyMusic"); // C:\Users\username\My Documents\My Music
+ addCsidl(CSIDL_COMMON_MUSIC, L"csidl_PublicMusic"); // C:\Users\All Users\Documents\My Music
+
+ addCsidl(CSIDL_MYPICTURES, L"csidl_MyPictures"); // C:\Users\username\My Documents\My Pictures
+ addCsidl(CSIDL_COMMON_PICTURES, L"csidl_PublicPictures"); // C:\Users\All Users\Documents\My Pictures
+
+ addCsidl(CSIDL_MYVIDEO, L"csidl_MyVideo"); // C:\Users\username\My Documents\My Videos
+ addCsidl(CSIDL_COMMON_VIDEO, L"csidl_PublicVideo"); // C:\Users\All Users\Documents\My Videos
+
+ addCsidl(CSIDL_PERSONAL, L"csidl_MyDocuments"); // C:\Users\username\My Documents
+ addCsidl(CSIDL_COMMON_DOCUMENTS, L"csidl_PublicDocuments"); // C:\Users\All Users\Documents
+
+ addCsidl(CSIDL_STARTMENU, L"csidl_StartMenu"); // C:\Users\username\Start Menu
+ addCsidl(CSIDL_COMMON_STARTMENU, L"csidl_PublicStartMenu"); // C:\Users\All Users\Start Menu
+
+ addCsidl(CSIDL_FAVORITES, L"csidl_Favorites"); // C:\Users\username\Favorites
+ addCsidl(CSIDL_COMMON_FAVORITES, L"csidl_PublicFavorites"); // C:\Users\All Users\Favoriten
+
+ addCsidl(CSIDL_TEMPLATES, L"csidl_Templates"); // C:\Users\username\Templates
+ addCsidl(CSIDL_COMMON_TEMPLATES, L"csidl_PublicTemplates"); // C:\Users\All Users\Templates
+
+ addCsidl(CSIDL_RESOURCES, L"csidl_Resources"); // C:\Windows\Resources
+
+ //CSIDL_APPDATA covered by %AppData%
+ //CSIDL_LOCAL_APPDATA covered by %LocalAppData%
+ //CSIDL_COMMON_APPDATA covered by %ProgramData%
+
+ //CSIDL_PROFILE covered by %UserProfile%
+ }
+
+ CsidlConstants(const CsidlConstants&);
+ CsidlConstants& operator=(const CsidlConstants&);
+
+ CsidlToDirMap csidlToDir;
+};
+
+//caveat: function scope static initialization is not thread-safe in VS 2010! => make sure to call at app start!
+namespace
+{
+struct Dummy { Dummy() { CsidlConstants::get(); }} blah;
+}
+#endif
+
+
+wxString getEnvValue(const wxString& envName) //return empty on error
+{
+ //try to apply environment variables
+ wxString envValue;
+ if (wxGetEnv(envName, &envValue))
+ {
+ //some postprocessing:
+ trim(envValue); //remove leading, trailing blanks
+
+ //remove leading, trailing double-quotes
+ if (startsWith(envValue, L"\"") &&
+ endsWith(envValue, L"\"") &&
+ envValue.length() >= 2)
+ envValue = wxString(envValue.c_str() + 1, envValue.length() - 2);
+ }
+ return envValue;
+}
+
+
+bool replaceMacro(wxString& macro) //macro without %-characters, return true if replaced successfully
+{
+ if (macro.IsEmpty())
+ return false;
+
+ //there are equally named environment variables %TIME%, %DATE% existing, so replace these first!
+ if (macro.CmpNoCase(wxT("time")) == 0)
+ {
+ macro = wxDateTime::Now().FormatISOTime();
+ macro.Replace(wxT(":"), wxT(""));
+ return true;
+ }
+
+ if (macro.CmpNoCase(wxT("date")) == 0)
+ {
+ macro = wxDateTime::Now().FormatISODate();
+ return true;
+ }
+
+ auto processPhrase = [&](const wchar_t* phrase, const wchar_t* format) -> bool
+ {
+ if (macro.CmpNoCase(phrase) != 0)
+ return false;
+ macro = wxDateTime::Now().Format(format);
+ return true;
+ };
+
+ if (processPhrase(L"weekday", L"%A")) return true;
+ if (processPhrase(L"day" , L"%d")) return true;
+ if (processPhrase(L"month" , L"%B")) return true;
+ if (processPhrase(L"week" , L"%U")) return true;
+ if (processPhrase(L"year" , L"%Y")) return true;
+ if (processPhrase(L"hour" , L"%H")) return true;
+ if (processPhrase(L"min" , L"%M")) return true;
+ if (processPhrase(L"sec" , L"%S")) return true;
+
+ //try to apply environment variables
+ {
+ wxString envValue = getEnvValue(macro);
+ if (!envValue.empty())
+ {
+ macro = envValue;
+ return true;
+ }
+ }
+
+#ifdef FFS_WIN
+ //try to resolve CSIDL values
+ {
+ auto csidlMap = CsidlConstants::get();
+ auto iter = csidlMap.find(toZ(macro));
+ if (iter != csidlMap.end())
+ {
+ macro = toWx(iter->second);
+ return true;
+ }
+ }
+#endif
+
+ return false;
+}
+
+
+void expandMacros(wxString& text)
+{
+ const wxChar SEPARATOR = '%';
+
+ if (text.Find(SEPARATOR) != wxNOT_FOUND)
+ {
+ wxString prefix = text.BeforeFirst(SEPARATOR);
+ wxString postfix = text.AfterFirst(SEPARATOR);
+ if (postfix.Find(SEPARATOR) != wxNOT_FOUND)
+ {
+ wxString potentialMacro = postfix.BeforeFirst(SEPARATOR);
+ wxString rest = postfix.AfterFirst(SEPARATOR); //text == prefix + SEPARATOR + potentialMacro + SEPARATOR + rest
+
+ if (replaceMacro(potentialMacro))
+ {
+ expandMacros(rest);
+ text = prefix + potentialMacro + rest;
+ }
+ else
+ {
+ rest = SEPARATOR + rest;
+ expandMacros(rest);
+ text = prefix + SEPARATOR + potentialMacro + rest;
+ }
+ }
+ }
+}
+
+
+#ifdef FFS_LINUX
+class TraverseMedia : public zen::TraverseCallback
+{
+public:
+ typedef std::map<Zstring, Zstring> DeviceList; //device name -> device path mapping
+
+ TraverseMedia(DeviceList& devices) : devices_(devices) {}
+
+ virtual void onFile(const Zchar* shortName, const Zstring& fullName, const FileInfo& details) {}
+ virtual void onSymlink(const Zchar* shortName, const Zstring& fullName, const SymlinkInfo& details) {}
+ virtual ReturnValDir onDir(const Zchar* shortName, const Zstring& fullName)
+ {
+ devices_.insert(std::make_pair(shortName, fullName));
+ return Int2Type<ReturnValDir::TRAVERSING_DIR_IGNORE>(); //DON'T traverse into subdirs
+ }
+ virtual HandleError onError(const std::wstring& errorText) { return TRAV_ERROR_IGNORE; }
+
+private:
+ DeviceList& devices_;
+};
+#endif
+
+
+Zstring volumenNameToPath(const Zstring& volumeName) //return empty string on error
+{
+#ifdef FFS_WIN
+ std::vector<wchar_t> volGuid(10000);
+
+ HANDLE hVol = ::FindFirstVolume(&volGuid[0], static_cast<DWORD>(volGuid.size()));
+ if (hVol != INVALID_HANDLE_VALUE)
+ {
+ ZEN_ON_BLOCK_EXIT(::FindVolumeClose(hVol));
+
+ do
+ {
+ std::vector<wchar_t> volName(MAX_PATH + 1);
+
+ if (::GetVolumeInformation(&volGuid[0], //__in_opt LPCTSTR lpRootPathName,
+ &volName[0], //__out LPTSTR lpVolumeNameBuffer,
+ static_cast<DWORD>(volName.size()), //__in DWORD nVolumeNameSize,
+ NULL, //__out_opt LPDWORD lpVolumeSerialNumber,
+ NULL, //__out_opt LPDWORD lpMaximumComponentLength,
+ NULL, //__out_opt LPDWORD lpFileSystemFlags,
+ NULL, //__out LPTSTR lpFileSystemNameBuffer,
+ 0)) //__in DWORD nFileSystemNameSize
+ {
+ if (EqualFilename()(volumeName, Zstring(&volName[0])))
+ {
+ //GetVolumePathNamesForVolumeName is not available for Windows 2000!
+ typedef BOOL (WINAPI *GetVolumePathNamesForVolumeNameWFunc)(LPCWSTR lpszVolumeName,
+ LPWCH lpszVolumePathNames,
+ DWORD cchBufferLength,
+ PDWORD lpcchReturnLength);
+
+ const SysDllFun<GetVolumePathNamesForVolumeNameWFunc> getVolumePathNamesForVolumeName(L"kernel32.dll", "GetVolumePathNamesForVolumeNameW");
+ if (getVolumePathNamesForVolumeName)
+ {
+ std::vector<wchar_t> volPath(10000);
+
+ DWORD returnedLen = 0;
+ if (getVolumePathNamesForVolumeName(&volGuid[0], //__in LPCTSTR lpszVolumeName,
+ &volPath[0], //__out LPTSTR lpszVolumePathNames,
+ static_cast<DWORD>(volPath.size()), //__in DWORD cchBufferLength,
+ &returnedLen)) //__out PDWORD lpcchReturnLength
+ {
+ return &volPath[0]; //return first path name in double-null terminated list!
+ }
+ }
+ return &volGuid[0]; //GUID looks ugly, but should be working correctly
+ }
+ }
+ }
+ while (::FindNextVolume(hVol, &volGuid[0], static_cast<DWORD>(volGuid.size())));
+ }
+
+#elif defined FFS_LINUX
+ //due to the naming convention on Linux /media/<volume name> this function is not that useful, but...
+
+ TraverseMedia::DeviceList deviceList;
+
+ TraverseMedia traverser(deviceList);
+ traverseFolder("/media", false, traverser); //traverse one level
+
+ TraverseMedia::DeviceList::const_iterator iter = deviceList.find(volumeName);
+ if (iter != deviceList.end())
+ return iter->second;
+#endif
+ return Zstring();
+}
+
+
+#ifdef FFS_WIN
+Zstring volumePathToName(const Zstring& volumePath) //return empty string on error
+{
+ const DWORD bufferSize = MAX_PATH + 1;
+ std::vector<wchar_t> volName(bufferSize);
+
+ if (::GetVolumeInformation(volumePath.c_str(), //__in_opt LPCTSTR lpRootPathName,
+ &volName[0], //__out LPTSTR lpVolumeNameBuffer,
+ bufferSize, //__in DWORD nVolumeNameSize,
+ NULL, //__out_opt LPDWORD lpVolumeSerialNumber,
+ NULL, //__out_opt LPDWORD lpMaximumComponentLength,
+ NULL, //__out_opt LPDWORD lpFileSystemFlags,
+ NULL, //__out LPTSTR lpFileSystemNameBuffer,
+ 0)) //__in DWORD nFileSystemNameSize
+ {
+ return &volName[0];
+ }
+ return Zstring();
+}
+#endif
+
+
+void expandVolumeName(Zstring& text) // [volname]:\folder [volname]\folder [volname]folder -> C:\folder
+{
+ //this would be a nice job for a C++11 regex...
+
+ size_t posStart = text.find(Zstr("["));
+ if (posStart != Zstring::npos)
+ {
+ size_t posEnd = text.find(Zstr("]"), posStart);
+ if (posEnd != Zstring::npos)
+ {
+ Zstring before = Zstring(text.c_str(), posStart);
+ Zstring volname = Zstring(text.c_str() + posStart + 1, posEnd - posStart - 1);
+ Zstring after = Zstring(text.c_str() + posEnd + 1);
+
+ if (startsWith(after, ':'))
+ after = afterFirst(after, ':');
+ if (startsWith(after, FILE_NAME_SEPARATOR))
+ after = afterFirst(after, FILE_NAME_SEPARATOR);
+
+ //[.*] pattern was found...
+ if (!volname.empty())
+ {
+ Zstring volPath = volumenNameToPath(volname); //return empty string on error
+ if (!volPath.empty())
+ {
+ if (!endsWith(volPath, FILE_NAME_SEPARATOR))
+ volPath += FILE_NAME_SEPARATOR;
+
+ text = before + volPath + after;
+ //successfully replaced pattern
+ return;
+ }
+ }
+ //error: did not find corresponding volume name:
+
+ /*make sure directory creation will fail later if attempted, instead of inconveniently interpreting this string as a relative name!
+ [FFS USB]\FreeFileSync will be resolved as
+ ?:\[FFS USB]\FreeFileSync\ - Windows
+ /.../[FFS USB]/FreeFileSync/ - Linux
+ instead of:
+ C:\Program Files\FreeFileSync\[FFS USB]\FreeFileSync\
+ */
+#ifdef FFS_WIN
+ text = L"?:\\[" + volname + L"]\\" + after;
+#elif defined FFS_LINUX
+ text = "/.../[" + volname + "]/" + after;
+#endif
+ return;
+ }
+ }
+}
+}
+
+
+#ifdef FFS_WIN
+void getDirectoryAliasesRecursive(const Zstring& dirname, std::set<Zstring, LessFilename>& output)
+{
+ //1. replace volume path by volume name: c:\dirname -> [SYSTEM]\dirname
+ if (dirname.size() >= 3 &&
+ std::iswalpha(dirname[0]) &&
+ dirname[1] == L':' &&
+ dirname[2] == L'\\')
+ {
+ Zstring volname = volumePathToName(Zstring(dirname.c_str(), 3));
+ if (!volname.empty())
+ output.insert(L"[" + volname + L"]" + Zstring(dirname.c_str() + 2));
+ }
+
+ //2. replace volume name by volume path: [SYSTEM]\dirname -> c:\dirname
+ {
+ Zstring testVolname = dirname;
+ expandVolumeName(testVolname);
+ if (testVolname != dirname)
+ if (output.insert(testVolname).second)
+ getDirectoryAliasesRecursive(testVolname, output); //recurse!
+ }
+
+ //3. environment variables: C:\Users\username -> %USERPROFILE%
+ {
+ std::map<Zstring, Zstring> envToDir;
+
+ //get list of useful variables
+ auto addEnvVar = [&](const wxString& envName)
+ {
+ wxString envVal = getEnvValue(envName); //return empty on error
+ if (!envVal.empty())
+ envToDir.insert(std::make_pair(toZ(envName), toZ(envVal)));
+ };
+ addEnvVar(L"AllUsersProfile"); // C:\ProgramData
+ addEnvVar(L"AppData"); // C:\Users\username\AppData\Roaming
+ addEnvVar(L"LocalAppData"); // C:\Users\username\AppData\Local
+ addEnvVar(L"ProgramData"); // C:\ProgramData
+ addEnvVar(L"ProgramFiles"); // C:\Program Files
+ addEnvVar(L"ProgramFiles(x86)");// C:\Program Files (x86)
+ addEnvVar(L"Public"); // C:\Users\Public
+ addEnvVar(L"UserProfile"); // C:\Users\username
+ addEnvVar(L"WinDir"); // C:\Windows
+ addEnvVar(L"Temp"); // C:\Windows\Temp
+
+ //add CSIDL values: http://msdn.microsoft.com/en-us/library/bb762494(v=vs.85).aspx
+ auto csidlMap = CsidlConstants::get();
+ envToDir.insert(csidlMap.begin(), csidlMap.end());
+
+ Zstring tmp = dirname;
+ ::makeUpper(tmp);
+ std::for_each(envToDir.begin(), envToDir.end(),
+ [&](const std::pair<Zstring, Zstring>& entry)
+ {
+ Zstring tmp2 = entry.second; //case-insensitive "startsWith()"
+ ::makeUpper(tmp2); //
+ if (startsWith(tmp, tmp2))
+ output.insert(L"%" + entry.first + L"%" + (dirname.c_str() + tmp2.size()));
+ });
+ }
+
+ //4. replace (all) macros: %USERPROFILE% -> C:\Users\username
+ {
+ wxString testMacros = toWx(dirname);
+ expandMacros(testMacros);
+ if (toZ(testMacros) != dirname)
+ if (output.insert(toZ(testMacros)).second)
+ getDirectoryAliasesRecursive(toZ(testMacros), output); //recurse!
+ }
+}
+
+
+std::vector<Zstring> zen::getDirectoryAliases(const Zstring& dirString)
+{
+ Zstring dirname = dirString;
+ trim(dirname, true, false);
+ if (dirname.empty())
+ return std::vector<Zstring>();
+
+ std::set<Zstring, LessFilename> tmp;
+ getDirectoryAliasesRecursive(dirname, tmp);
+
+ tmp.erase(dirname);
+ tmp.erase(Zstring());
+
+ return std::vector<Zstring>(tmp.begin(), tmp.end());
+}
+#endif
+
+
+Zstring zen::getFormattedDirectoryName(const Zstring& dirString) // throw()
+{
+ //Formatting is needed since functions expect the directory to end with '\' to be able to split the relative names.
+
+ wxString dirnameTmp = toWx(dirString);
+ expandMacros(dirnameTmp);
+
+ Zstring output = toZ(dirnameTmp);
+
+ expandVolumeName(output);
+
+ //remove leading/trailing whitespace
+ trim(output, true, false);
+ while (endsWith(output, " ")) //don't remove all whitespace from right, e.g. 0xa0 may be used as part of dir name
+ output.resize(output.size() - 1);
+
+ if (output.empty()) //an empty string would later be resolved as "\"; this is not desired
+ return Zstring();
+
+ /*
+ resolve relative names; required by:
+ WINDOWS:
+ - \\?\-prefix which needs absolute names
+ - Volume Shadow Copy: volume name needs to be part of each filename
+ - file icon buffer (at least for extensions that are actually read from disk, like "exe")
+ - ::SHFileOperation(): Using relative path names is not thread safe
+ WINDOWS/LINUX:
+ - detection of dependent directories, e.g. "\" and "C:\test"
+ */
+ output = resolveRelativePath(output);
+
+ if (!endsWith(output, FILE_NAME_SEPARATOR))
+ output += FILE_NAME_SEPARATOR;
+
+ return output;
+}
+
+
+#ifdef FFS_WIN
+void zen::loginNetworkShare(const Zstring& dirnameOrig, bool allowUserInteraction) //throw() - user interaction: show OS password prompt
+{
+ /*
+ ATTENTION: it is not safe to retrieve UNC path via ::WNetGetConnection() for every type of network share:
+
+ network type |::WNetGetConnection rv | lpRemoteName | existing UNC path
+ -----------------------------|-------------------------|---------------------------------|----------------
+ inactive local network share | ERROR_CONNECTION_UNAVAIL| \\192.168.1.27\new2 | YES
+ WebDrive | NO_ERROR | \\Webdrive-ZenJu\GNU | NO
+ Box.net (WebDav) | NO_ERROR | \\www.box.net\DavWWWRoot\dav | YES
+ NetDrive | ERROR_NOT_CONNECTED | <empty> | NO
+ */
+
+ //if (::GetFileAttributes((driveLetter + L'\\').c_str()) == INVALID_FILE_ATTRIBUTES) <- this will seriously block if network is not reachable!!!
+
+ const Zstring dirname = removeLongPathPrefix(dirnameOrig);
+
+ //1. local path
+ if (dirname.size() >= 2 && iswalpha(dirname[0]) && dirname[1] == L':')
+ {
+ Zstring driveLetter(dirname.c_str(), 2); //e.g.: "Q:"
+ {
+ DWORD bufferSize = 10000;
+ std::vector<wchar_t> remoteNameBuffer(bufferSize);
+
+ //map local -> remote
+
+ //note: following function call does NOT block!
+ DWORD rv = ::WNetGetConnection(driveLetter.c_str(), //__in LPCTSTR lpLocalName in the form "<driveletter>:"
+ &remoteNameBuffer[0], //__out LPTSTR lpRemoteName,
+ &bufferSize); //__inout LPDWORD lpnLength
+ if (rv == ERROR_CONNECTION_UNAVAIL) //remoteNameBuffer will be filled nevertheless!
+ {
+ //ERROR_CONNECTION_UNAVAIL: network mapping is existing, but not connected
+
+ Zstring networkShare = &remoteNameBuffer[0];
+ if (!networkShare.empty())
+ {
+ NETRESOURCE trgRes = {};
+ trgRes.dwType = RESOURCETYPE_DISK;
+ trgRes.lpLocalName = const_cast<LPWSTR>(driveLetter.c_str()); //lpNetResource is marked "__in", seems WNetAddConnection2 is not const correct!
+ trgRes.lpRemoteName = const_cast<LPWSTR>(networkShare.c_str()); //
+
+ //note: following function call may block heavily if network is not reachable!!!
+ DWORD rv2 = ::WNetAddConnection2(&trgRes, // __in LPNETRESOURCE lpNetResource,
+ NULL, // __in LPCTSTR lpPassword,
+ NULL, // __in LPCTSTR lpUsername,
+ allowUserInteraction ? CONNECT_INTERACTIVE : 0); //__in DWORD dwFlags
+ if (rv2 == NO_ERROR)
+ return; //mapping reestablished
+
+ //restoring connection failed for some reason...
+ //we could use full UNC path instead: networkShare + (dirname.c_str() + 2); //replace "Q:\subdir" by "\\server\share\subdir"
+ }
+ }
+ }
+ }
+ //2. UNC path
+ else if (startsWith(dirname, L"\\\\"))
+ {
+ const Zstring networkShare = [&]() -> Zstring //extract prefix "\\server\share"
+ {
+ size_t pos = dirname.find('\\', 2);
+ if (pos == Zstring::npos)
+ return Zstring();
+ pos = dirname.find('\\', pos + 1);
+ return pos == Zstring::npos ? dirname : Zstring(dirname.c_str(), pos);
+ }();
+
+ if (!networkShare.empty())
+ {
+ //::WNetGetResourceInformation seems to fail with ERROR_BAD_NET_NAME even for existing unconnected network shares!
+ // => unconditionally try to connect to network share, seems we cannot reliably detect connection status otherwise
+
+ NETRESOURCE trgRes = {};
+ trgRes.dwType = RESOURCETYPE_DISK;
+ trgRes.lpRemoteName = const_cast<LPWSTR>(networkShare.c_str()); //trgRes is "__in"
+
+ //note: following function call may block heavily if network is not reachable!!!
+ DWORD rv2 = ::WNetAddConnection2(&trgRes, // __in LPNETRESOURCE lpNetResource,
+ NULL, // __in LPCTSTR lpPassword,
+ NULL, // __in LPCTSTR lpUsername,
+ allowUserInteraction ? CONNECT_INTERACTIVE : 0); //__in DWORD dwFlags
+ if (rv2 == NO_ERROR)
+ return; //mapping reestablished
+
+ /*
+ NETRESOURCE nr = {};
+ nr.dwType = RESOURCETYPE_DISK;
+ nr.lpRemoteName = const_cast<LPWSTR>(networkShare.c_str()); //nr is "__in"
+
+ DWORD bufferSize = sizeof(NETRESOURCE) + 20000;
+ std::vector<char> buffer(bufferSize);
+
+ LPTSTR relPath = NULL;
+
+ //note: following function call may block heavily if network is not reachable!!!
+ const DWORD rv = WNetGetResourceInformation(&nr, // __in LPNETRESOURCE lpNetResource,
+ &buffer[0], // __out LPVOID lpBuffer,
+ &bufferSize, // __inout LPDWORD lpcbBuffer,
+ &relPath); // __out LPTSTR *lplpSystem
+ if (rv == NO_ERROR)
+ {
+ //NO_ERROR: network share is existing, *either* connected or disconnected
+
+ //we have no way to check if network is already connected, so let's try to connect anyway:
+
+ NETRESOURCE& trgRes = reinterpret_cast<NETRESOURCE&>(buffer[0]);
+
+ if (trgRes.dwUsage & RESOURCEUSAGE_CONNECTABLE)
+ {
+ //note: following function call may block heavily if network is not reachable!!!
+ DWORD rv2 = ::WNetAddConnection2(&trgRes, // __in LPNETRESOURCE lpNetResource,
+ NULL, // __in LPCTSTR lpPassword,
+ NULL, // __in LPCTSTR lpUsername,
+ allowUserInteraction ? CONNECT_INTERACTIVE : 0); //__in DWORD dwFlags
+ if (rv2 == NO_ERROR)
+ return; //mapping reestablished
+ }
+ }
+ */
+ }
+ }
+}
+#endif
diff --git a/lib/resolve_path.h b/lib/resolve_path.h
new file mode 100644
index 00000000..7005d0ea
--- /dev/null
+++ b/lib/resolve_path.h
@@ -0,0 +1,26 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef RESOLVE_PATH_H_INCLUDED
+#define RESOLVE_PATH_H_INCLUDED
+
+#include <zen/zstring.h>
+
+
+namespace zen
+{
+Zstring getFormattedDirectoryName(const Zstring& dirString); //throw() - non-blocking! no I/O!
+
+#ifdef FFS_WIN
+std::vector<Zstring> getDirectoryAliases(const Zstring& dirString);
+
+//this call may block if network is not reachable or when showing login prompt!
+void loginNetworkShare(const Zstring& dirname, bool allowUserInteraction); //throw() - user interaction: show OS password prompt
+#endif
+}
+
+
+#endif // RESOLVE_PATH_H_INCLUDED
diff --git a/lib/resources.cpp b/lib/resources.cpp
new file mode 100644
index 00000000..da407920
--- /dev/null
+++ b/lib/resources.cpp
@@ -0,0 +1,99 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "resources.h"
+#include <memory>
+#include <wx/wfstream.h>
+#include <wx/zipstrm.h>
+#include <wx/image.h>
+#include <wx/mstream.h>
+#include <wx+/string_conv.h>
+#include "ffs_paths.h"
+
+using namespace zen;
+
+
+const GlobalResources& GlobalResources::instance()
+{
+ static GlobalResources inst;
+ return inst;
+}
+
+
+namespace
+{
+void loadAnimFromZip(wxZipInputStream& zipInput, wxAnimation& anim)
+{
+ //Workaround for wxWidgets:
+ //construct seekable input stream (zip-input stream is non-seekable) for wxAnimation::Load()
+ //luckily this method call is very fast: below measurement precision!
+ std::vector<char> data;
+ data.reserve(10000);
+
+ int newValue = 0;
+ while ((newValue = zipInput.GetC()) != wxEOF)
+ data.push_back(newValue);
+
+ wxMemoryInputStream seekAbleStream(&data.front(), data.size()); //stream does not take ownership of data
+
+ anim.Load(seekAbleStream, wxANIMATION_TYPE_GIF);
+}
+}
+
+
+GlobalResources::GlobalResources()
+{
+ wxFFileInputStream input(zen::getResourceDir() + wxT("Resources.zip"));
+ if (input.IsOk()) //if not... we don't want to react too harsh here
+ {
+ //activate support for .png files
+ wxImage::AddHandler(new wxPNGHandler); //ownership passed
+
+ wxZipInputStream resourceFile(input);
+
+ while (true)
+ {
+ std::unique_ptr<wxZipEntry> entry(resourceFile.GetNextEntry()); //take ownership!
+ if (entry.get() == NULL)
+ break;
+
+ const wxString name = entry->GetName();
+
+ //generic image loading
+ if (name.EndsWith(wxT(".png")))
+ bitmaps.insert(std::make_pair(name, wxImage(resourceFile, wxBITMAP_TYPE_PNG)));
+ else if (name == wxT("money.gif"))
+ loadAnimFromZip(resourceFile, animationMoney);
+ else if (name == wxT("working.gif"))
+ loadAnimFromZip(resourceFile, animationSync);
+ }
+ }
+
+#ifdef FFS_WIN
+ //for compatibility it seems we need to stick with a "real" icon
+ programIcon = wxIcon(wxT("A_PROGRAM_ICON"));
+#else
+ //use big logo bitmap for better quality
+ programIcon.CopyFromBitmap(getImageInt(wxT("FreeFileSync.png")));
+ //attention: this is the reason we need a member getImage -> it must not implicitly create static object instance!!!
+ //erroneously calling static object constructor twice will deadlock on Linux!!
+#endif
+}
+
+
+const wxBitmap& GlobalResources::getImageInt(const wxString& imageName) const
+{
+ auto iter = bitmaps.find(imageName.Find(L'.') == wxNOT_FOUND ? //assume .png ending if nothing else specified
+ imageName + wxT(".png") :
+ imageName);
+ if (iter != bitmaps.end())
+ return iter->second;
+ else
+ {
+ assert(false);
+ return wxNullBitmap;
+ }
+}
diff --git a/lib/resources.h b/lib/resources.h
new file mode 100644
index 00000000..d8b08b72
--- /dev/null
+++ b/lib/resources.h
@@ -0,0 +1,38 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef RESOURCES_H_INCLUDED
+#define RESOURCES_H_INCLUDED
+
+#include <map>
+#include <wx/bitmap.h>
+#include <wx/animate.h>
+#include <wx/icon.h>
+
+
+class GlobalResources
+{
+public:
+ static const GlobalResources& instance();
+
+ static const wxBitmap& getImage(const wxString& name) { return instance().getImageInt(name); }
+
+ //global image resource objects
+ wxAnimation animationMoney;
+ wxAnimation animationSync;
+ wxIcon programIcon;
+
+private:
+ GlobalResources();
+ GlobalResources(const GlobalResources&);
+ GlobalResources& operator=(const GlobalResources&);
+
+ const wxBitmap& getImageInt(const wxString& name) const;
+
+ std::map<wxString, wxBitmap> bitmaps;
+};
+
+#endif // RESOURCES_H_INCLUDED
diff --git a/lib/shadow.cpp b/lib/shadow.cpp
new file mode 100644
index 00000000..73a2e8f7
--- /dev/null
+++ b/lib/shadow.cpp
@@ -0,0 +1,134 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "shadow.h"
+#include <stdexcept>
+#include <zen/win.h> //includes "windows.h"
+#include <zen/dll.h>
+#include <zen/assert_static.h>
+#include "ShadowCopy/shadow.h"
+#include <zen/scope_guard.h>
+
+using namespace zen;
+using namespace shadow;
+
+
+namespace
+{
+bool runningWOW64() //test if process is running under WOW64 (reference http://msdn.microsoft.com/en-us/library/ms684139(VS.85).aspx)
+{
+ //dynamically load windows API function
+ typedef BOOL (WINAPI *IsWow64ProcessFun)(HANDLE hProcess, PBOOL Wow64Process);
+
+ const SysDllFun<IsWow64ProcessFun> isWow64Process(L"kernel32.dll", "IsWow64Process");
+ if (isWow64Process)
+ {
+ BOOL isWow64 = FALSE;
+ if (isWow64Process(::GetCurrentProcess(), &isWow64))
+ return isWow64 == TRUE;
+ }
+
+ return false;
+}
+}
+
+//#############################################################################################################
+
+
+class ShadowCopy::ShadowVolume
+{
+public:
+ ShadowVolume(const Zstring& volumeNameFormatted) : //throw(FileError)
+ createShadowCopy (getDllName(), createShadowCopyFctName),
+ releaseShadowCopy(getDllName(), releaseShadowCopyFctName),
+ backupHandle(0)
+ {
+ //check if shadow copy dll was loaded correctly
+ if (!createShadowCopy || !releaseShadowCopy)
+ throw FileError(_("Error accessing Volume Shadow Copy Service!") + "\n" +
+ _("Could not load a required DLL:") + " \"" + getDllName() + "\"");
+
+ //VSS does not support running under WOW64 except for Windows XP and Windows Server 2003
+ //(Reference: http://msdn.microsoft.com/en-us/library/aa384627(VS.85).aspx)
+ if (runningWOW64())
+ throw FileError(_("Error accessing Volume Shadow Copy Service!") + "\n" +
+ _("Making shadow copies on WOW64 is not supported. Please use FreeFileSync 64-bit version."));
+
+ //---------------------------------------------------------------------------------------------------------
+ //start shadow volume copy service:
+ wchar_t shadowVolName[1000];
+ wchar_t errorMessage[1000];
+
+ if (!createShadowCopy(volumeNameFormatted.c_str(),
+ shadowVolName,
+ 1000,
+ &backupHandle,
+ errorMessage,
+ 1000))
+ throw FileError(_("Error accessing Volume Shadow Copy Service!") + "\n" +
+ "(" + errorMessage + " Volume: \"" + volumeNameFormatted + "\")");
+
+ shadowVol = Zstring(shadowVolName) + FILE_NAME_SEPARATOR; //shadowVolName NEVER has a trailing backslash
+ }
+
+ ~ShadowVolume()
+ {
+ releaseShadowCopy(backupHandle); //fast! no performance optimization necessary
+ }
+
+ Zstring getShadowVolume() const //trailing path separator
+ {
+ return shadowVol;
+ }
+
+private:
+ ShadowVolume(const ShadowVolume&);
+ ShadowVolume& operator=(const ShadowVolume&);
+
+ const DllFun<CreateShadowCopyFct> createShadowCopy;
+ const DllFun<ReleaseShadowCopyFct> releaseShadowCopy;
+
+ Zstring shadowVol;
+
+ ShadowHandle backupHandle;
+};
+//#############################################################################################################
+
+
+Zstring ShadowCopy::makeShadowCopy(const Zstring& inputFile)
+{
+ wchar_t volumeNameRaw[1000];
+
+ if (!::GetVolumePathName(inputFile.c_str(), //__in LPCTSTR lpszFileName,
+ volumeNameRaw, //__out LPTSTR lpszVolumePathName,
+ 1000)) //__in DWORD cchBufferLength
+ throw FileError(_("Could not determine volume name for file:") + "\n\"" + inputFile + "\"");
+
+ Zstring volumeNameFormatted = volumeNameRaw;
+ if (!endsWith(volumeNameFormatted, FILE_NAME_SEPARATOR))
+ volumeNameFormatted += FILE_NAME_SEPARATOR;
+
+ //input file is always absolute! directory formatting takes care of this! Therefore volume name can always be found.
+ const size_t pos = inputFile.find(volumeNameFormatted); //inputFile needs NOT to begin with volumeNameFormatted: consider for example \\?\ prefix!
+ if (pos == Zstring::npos)
+ {
+ std::wstring msg = _("Volume name %x not part of filename %y!");
+ replace(msg, L"%x", std::wstring(L"\"") + volumeNameFormatted + "\"", false);
+ replace(msg, L"%y", std::wstring(L"\"") + inputFile + "\"", false);
+ throw FileError(msg);
+ }
+
+ //get or create instance of shadow volume
+ VolNameShadowMap::const_iterator iter = shadowVol.find(volumeNameFormatted);
+ if (iter == shadowVol.end())
+ {
+ std::shared_ptr<ShadowVolume> newEntry(new ShadowVolume(volumeNameFormatted));
+ iter = shadowVol.insert(std::make_pair(volumeNameFormatted, newEntry)).first;
+ }
+
+ //return filename alias on shadow copy volume
+ return iter->second->getShadowVolume() + Zstring(inputFile.c_str() + pos + volumeNameFormatted.length());
+}
diff --git a/lib/shadow.h b/lib/shadow.h
new file mode 100644
index 00000000..bfb1e84a
--- /dev/null
+++ b/lib/shadow.h
@@ -0,0 +1,35 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef SHADOW_H_INCLUDED
+#define SHADOW_H_INCLUDED
+
+#include <map>
+#include <memory>
+#include <zen/zstring.h>
+#include <zen/file_error.h>
+
+
+namespace shadow
+{
+class ShadowCopy //buffer access to Windows Volume Shadow Copy Service
+{
+public:
+ ShadowCopy() {}
+
+ Zstring makeShadowCopy(const Zstring& inputFile); //throw(FileError); returns filename on shadow copy
+
+private:
+ ShadowCopy(const ShadowCopy&);
+ ShadowCopy& operator=(const ShadowCopy&);
+
+ class ShadowVolume;
+ typedef std::map<Zstring, std::shared_ptr<ShadowVolume>, LessFilename> VolNameShadowMap;
+ VolNameShadowMap shadowVol;
+};
+}
+
+#endif // SHADOW_H_INCLUDED
diff --git a/lib/soft_filter.h b/lib/soft_filter.h
new file mode 100644
index 00000000..6763d6b7
--- /dev/null
+++ b/lib/soft_filter.h
@@ -0,0 +1,113 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef SOFT_FILTER_H_INCLUDED
+#define SOFT_FILTER_H_INCLUDED
+
+#include <algorithm>
+#include <limits>
+#include "../structures.h"
+#include <wx/stopwatch.h>
+
+namespace zen
+{
+/*
+Semantics of SoftFilter:
+1. It potentially may match only one side => it MUST NOT be applied while traversing a single folder to avoid mismatches
+2. => it is applied after traversing and just marks rows, (NO deletions after comparison are allowed)
+3. => equivalent to a user temporarily (de-)selecting rows -> not relevant for <Automatic>-mode! ;)
+*/
+
+class SoftFilter
+{
+public:
+ SoftFilter(size_t timeSpan, UnitTime unitTimeSpan,
+ size_t sizeMin, UnitSize unitSizeMin,
+ size_t sizeMax, UnitSize unitSizeMax);
+
+ bool matchTime(Int64 writeTime) const { return timeFrom_ <= writeTime; }
+ bool matchSize(UInt64 fileSize) const { return sizeMin_ <= fileSize && fileSize <= sizeMax_; }
+ bool matchFolder() const { return timeFrom_ == std::numeric_limits<Int64>::min(); }
+ //if date filter is active we deactivate all folders: effectively gets rid of empty folders!
+
+ bool isNull() const; //filter is equivalent to NullFilter, but may be technically slower
+
+ //small helper method: merge two soft filters
+ friend SoftFilter combineFilters(const SoftFilter& first, const SoftFilter& second);
+
+private:
+ SoftFilter(const Int64& timeFrom,
+ const UInt64& sizeMin,
+ const UInt64& sizeMax);
+
+ Int64 timeFrom_; //unit: UTC, seconds
+ UInt64 sizeMin_; //unit: bytes
+ UInt64 sizeMax_; //unit: bytes
+};
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+// ----------------------- implementation -----------------------
+namespace zen
+{
+inline
+SoftFilter::SoftFilter(size_t timeSpan, UnitTime unitTimeSpan,
+ size_t sizeMin, UnitSize unitSizeMin,
+ size_t sizeMax, UnitSize unitSizeMax)
+{
+ resolveUnits(timeSpan, unitTimeSpan,
+ sizeMin, unitSizeMin,
+ sizeMax, unitSizeMax,
+ timeFrom_,
+ sizeMin_,
+ sizeMax_);
+}
+
+inline
+SoftFilter::SoftFilter(const Int64& timeFrom,
+ const UInt64& sizeMin,
+ const UInt64& sizeMax) :
+ timeFrom_(timeFrom),
+ sizeMin_ (sizeMin),
+ sizeMax_ (sizeMax) {}
+
+inline
+SoftFilter combineFilters(const SoftFilter& first, const SoftFilter& second)
+{
+ return SoftFilter(std::max(first.timeFrom_, second.timeFrom_),
+ std::max(first.sizeMin_, second.sizeMin_),
+ std::min(first.sizeMax_, second.sizeMax_));
+}
+
+inline
+bool SoftFilter::isNull() const //filter is equivalent to NullFilter, but may be technically slower
+{
+ return timeFrom_ == std::numeric_limits<Int64>::min() &&
+ sizeMin_ == 0U &&
+ sizeMax_ == std::numeric_limits<UInt64>::max();
+}
+}
+
+#endif // SOFT_FILTER_H_INCLUDED
diff --git a/lib/statistics.cpp b/lib/statistics.cpp
new file mode 100644
index 00000000..9924091c
--- /dev/null
+++ b/lib/statistics.cpp
@@ -0,0 +1,279 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "statistics.h"
+
+#include <wx/ffile.h>
+#include <zen/basic_math.h>
+#include "status_handler.h"
+#include <wx+/format_unit.h>
+#include <limits>
+#include <wx/stopwatch.h>
+#include <zen/assert_static.h>
+#include <zen/i18n.h>
+
+
+using namespace zen;
+
+
+RetrieveStatistics::~RetrieveStatistics()
+{
+ //write statistics to a file
+ wxFFile outputFile(wxT("statistics.dat"), wxT("w"));
+
+ outputFile.Write(wxT("Time(ms);Objects;Data\n"));
+
+ std::for_each(data.begin(), data.end(),
+ [&](const StatEntry& entry)
+ {
+ outputFile.Write(toString<wxString>(entry.time));
+ outputFile.Write(wxT(";"));
+ outputFile.Write(toString<wxString>(entry.objects));
+ outputFile.Write(wxT(";"));
+ outputFile.Write(toString<wxString>(entry.value));
+ outputFile.Write(wxT("\n"));
+ });
+}
+
+
+void RetrieveStatistics::writeEntry(double value, int objects)
+{
+ StatEntry newEntry;
+ newEntry.value = value;
+ newEntry.objects = objects;
+ newEntry.time = timer.Time();
+ data.push_back(newEntry);
+}
+
+
+//########################################################################################
+Statistics::Statistics(int totalObjectCount,
+ double totalDataAmount,
+ unsigned windowSizeRemainingTime,
+ unsigned windowSizeBytesPerSecond) :
+ objectsTotal(totalObjectCount),
+ dataTotal(totalDataAmount),
+ windowSizeRemTime(windowSizeRemainingTime),
+ windowSizeBPS(windowSizeBytesPerSecond),
+ windowMax(std::max(windowSizeRemainingTime, windowSizeBytesPerSecond)) {}
+
+
+void Statistics::addMeasurement(int objectsCurrent, double dataCurrent)
+{
+ Record newRecord;
+ newRecord.objects = objectsCurrent;
+ newRecord.data = dataCurrent;
+
+ const long now = timer.Time();
+
+ measurements.insert(measurements.end(), std::make_pair(now, newRecord)); //use fact that time is monotonously ascending
+
+ //remove all records earlier than "now - windowMax"
+ const long newBegin = now - windowMax;
+ TimeRecordMap::iterator windowBegin = measurements.upper_bound(newBegin);
+ if (windowBegin != measurements.begin())
+ measurements.erase(measurements.begin(), --windowBegin); //retain one point before newBegin in order to handle "measurement holes"
+}
+
+
+wxString Statistics::getRemainingTime() const
+{
+ if (!measurements.empty())
+ {
+ const TimeRecordMap::value_type& backRecord = *measurements.rbegin();
+ //find start of records "window"
+ const long frontTime = backRecord.first - windowSizeRemTime;
+ TimeRecordMap::const_iterator windowBegin = measurements.upper_bound(frontTime);
+ if (windowBegin != measurements.begin())
+ --windowBegin; //one point before window begin in order to handle "measurement holes"
+
+ const TimeRecordMap::value_type& frontRecord = *windowBegin;
+ //-----------------------------------------------------------------------------------------------
+ const double timeDelta = backRecord.first - frontRecord.first;
+ const double dataDelta = backRecord.second.data - frontRecord.second.data;
+
+ const double dataRemaining = dataTotal - backRecord.second.data;
+
+ if (!numeric::isNull(dataDelta))
+ {
+ int remTimeSec = dataRemaining * timeDelta / (1000.0 * dataDelta);
+ return zen::remainingTimeToShortString(remTimeSec);
+ }
+ }
+
+ return wxT("-"); //fallback
+}
+
+
+wxString Statistics::getBytesPerSecond() const
+{
+ if (!measurements.empty())
+ {
+ const TimeRecordMap::value_type& backRecord = *measurements.rbegin();
+ //find start of records "window"
+ const long frontTime = backRecord.first - windowSizeBPS;
+ TimeRecordMap::const_iterator windowBegin = measurements.upper_bound(frontTime);
+ if (windowBegin != measurements.begin())
+ --windowBegin; //one point before window begin in order to handle "measurement holes"
+
+ const TimeRecordMap::value_type& frontRecord = *windowBegin;
+ //-----------------------------------------------------------------------------------------------
+ const double timeDelta = backRecord.first - frontRecord.first;
+ const double dataDelta = backRecord.second.data - frontRecord.second.data;
+
+ if (!numeric::isNull(timeDelta))
+ if (dataDelta > 0) //may be negative if user cancels copying
+ return zen::filesizeToShortString(zen::UInt64(dataDelta * 1000 / timeDelta)) + _("/sec");
+ }
+
+ return wxT("-"); //fallback
+}
+
+
+void Statistics::pauseTimer()
+{
+ timer.Pause();
+}
+
+
+void Statistics::resumeTimer()
+{
+ timer.Resume();
+}
+
+/*
+class for calculation of remaining time:
+----------------------------------------
+"filesize |-> time" is an affine linear function f(x) = z_1 + z_2 x
+
+For given n measurements, sizes x_0, ..., x_n and times f_0, ..., f_n, the function f (as a polynom of degree 1) can be lineary approximated by
+
+z_1 = (r - s * q / p) / ((n + 1) - s * s / p)
+z_2 = (q - s * z_1) / p = (r - (n + 1) z_1) / s
+
+with
+p := x_0^2 + ... + x_n^2
+q := f_0 x_0 + ... + f_n x_n
+r := f_0 + ... + f_n
+s := x_0 + ... + x_n
+
+=> the time to process N files with amount of data D is: N * z_1 + D * z_2
+
+Problem:
+--------
+Times f_0, ..., f_n can be very small so that precision of the PC clock is poor.
+=> Times have to be accumulated to enhance precision:
+Copying of m files with sizes x_i and times f_i (i = 1, ..., m) takes sum_i f(x_i) := m * z_1 + z_2 * sum x_i = sum f_i
+With X defined as the accumulated sizes and F the accumulated times this gives: (in theory...)
+m * z_1 + z_2 * X = F <=>
+z_1 + z_2 * X / m = F / m
+
+=> we obtain a new (artificial) measurement with size X / m and time F / m to be used in the linear approximation above
+
+
+Statistics::Statistics(const int totalObjectCount, const double totalDataAmount, const unsigned recordCount) :
+ objectsTotal(totalObjectCount),
+ dataTotal(totalDataAmount),
+ recordsMax(recordCount),
+ objectsLast(0),
+ dataLast(0),
+ timeLast(wxGetLocalTimeMillis()),
+ z1_current(0),
+ z2_current(0),
+ dummyRecordPresent(false) {}
+
+
+wxString Statistics::getRemainingTime(const int objectsCurrent, const double dataCurrent)
+{
+ //add new measurement point
+ const int m = objectsCurrent - objectsLast;
+ if (m != 0)
+ {
+ objectsLast = objectsCurrent;
+
+ const double X = dataCurrent - dataLast;
+ dataLast = dataCurrent;
+
+ const zen::Int64 timeCurrent = wxGetLocalTimeMillis();
+ const double F = (timeCurrent - timeLast).ToDouble();
+ timeLast = timeCurrent;
+
+ record newEntry;
+ newEntry.x_i = X / m;
+ newEntry.f_i = F / m;
+
+ //remove dummy record
+ if (dummyRecordPresent)
+ {
+ measurements.pop_back();
+ dummyRecordPresent = false;
+ }
+
+ //insert new record
+ measurements.push_back(newEntry);
+ if (measurements.size() > recordsMax)
+ measurements.pop_front();
+ }
+ else //dataCurrent increased without processing new objects:
+ { //modify last measurement until m != 0
+ const double X = dataCurrent - dataLast; //do not set dataLast, timeLast variables here, but write dummy record instead
+ if (!isNull(X))
+ {
+ const zen::Int64 timeCurrent = wxGetLocalTimeMillis();
+ const double F = (timeCurrent - timeLast).ToDouble();
+
+ record modifyEntry;
+ modifyEntry.x_i = X;
+ modifyEntry.f_i = F;
+
+ //insert dummy record
+ if (!dummyRecordPresent)
+ {
+ measurements.push_back(modifyEntry);
+ if (measurements.size() > recordsMax)
+ measurements.pop_front();
+ dummyRecordPresent = true;
+ }
+ else //modify dummy record
+ measurements.back() = modifyEntry;
+ }
+ }
+
+ //calculate remaining time based on stored measurement points
+ double p = 0;
+ double q = 0;
+ double r = 0;
+ double s = 0;
+ for (std::list<record>::const_iterator i = measurements.begin(); i != measurements.end(); ++i)
+ {
+ const double x_i = i->x_i;
+ const double f_i = i->f_i;
+ p += x_i * x_i;
+ q += f_i * x_i;
+ r += f_i;
+ s += x_i;
+ }
+
+ if (!isNull(p))
+ {
+ const double n = measurements.size();
+ const double tmp = (n - s * s / p);
+
+ if (!isNull(tmp) && !isNull(s))
+ {
+ const double z1 = (r - s * q / p) / tmp;
+ const double z2 = (r - n * z1) / s; //not (n + 1) here, since n already is the number of measurements
+
+ //refresh current values for z1, z2
+ z1_current = z1;
+ z2_current = z2;
+ }
+ }
+
+ return formatRemainingTime((objectsTotal - objectsCurrent) * z1_current + (dataTotal - dataCurrent) * z2_current);
+}
+
+*/
diff --git a/lib/statistics.h b/lib/statistics.h
new file mode 100644
index 00000000..8f974aae
--- /dev/null
+++ b/lib/statistics.h
@@ -0,0 +1,70 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef STATISTICS_H_INCLUDED
+#define STATISTICS_H_INCLUDED
+
+#include <vector>
+#include <map>
+#include <memory>
+#include <wx/defs.h>
+#include <wx/string.h>
+#include <wx/stopwatch.h>
+
+class RetrieveStatistics
+{
+public:
+ wxDEPRECATED(~RetrieveStatistics()); //generate compiler warnings as a reminder to remove code after measurements
+ void writeEntry(double value, int objects);
+
+private:
+ struct StatEntry
+ {
+ long time;
+ int objects;
+ double value;
+ };
+ std::vector<StatEntry> data;
+ wxStopWatch timer;
+};
+
+
+class Statistics
+{
+public:
+ Statistics(int totalObjectCount,
+ double totalDataAmount,
+ unsigned windowSizeRemainingTime, //time in ms
+ unsigned windowSizeBytesPerSecond); //time in ms
+
+ void addMeasurement(int objectsCurrent, double dataCurrent);
+ wxString getRemainingTime() const; //returns the remaining time in milliseconds
+ wxString getBytesPerSecond() const;
+
+ void pauseTimer();
+ void resumeTimer();
+
+private:
+ const int objectsTotal;
+ const double dataTotal;
+
+ const unsigned windowSizeRemTime; //"window width" of statistics used for calculation of remaining time in ms
+ const unsigned windowSizeBPS; //
+ const unsigned windowMax;
+
+ struct Record
+ {
+ int objects; //object count
+ double data; //unit: bytes
+ };
+
+ typedef std::multimap<long, Record> TimeRecordMap; //time, unit: milliseconds
+ TimeRecordMap measurements; //
+
+ wxStopWatch timer;
+};
+
+#endif // STATISTICS_H_INCLUDED
diff --git a/lib/status_handler.cpp b/lib/status_handler.cpp
new file mode 100644
index 00000000..55f82c64
--- /dev/null
+++ b/lib/status_handler.cpp
@@ -0,0 +1,34 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "status_handler.h"
+#include <wx/app.h>
+#include <ctime>
+
+void updateUiNow()
+{
+ //process UI events and prevent application from "not responding" -> NO performance issue!
+ wxTheApp->Yield();
+
+ // while (wxTheApp->Pending())
+ // wxTheApp->Dispatch();
+}
+
+
+bool updateUiIsAllowed()
+{
+ const std::clock_t CLOCK_UPDATE_INTERVAL = UI_UPDATE_INTERVAL * CLOCKS_PER_SEC / 1000;
+
+ static std::clock_t lastExec = 0;
+ const std::clock_t now = std::clock(); //this is quite fast: 2 * 10^-5
+
+ if (now - lastExec >= CLOCK_UPDATE_INTERVAL) //perform ui updates not more often than necessary
+ {
+ lastExec = now;
+ return true;
+ }
+ return false;
+}
diff --git a/lib/status_handler.h b/lib/status_handler.h
new file mode 100644
index 00000000..390b4f0a
--- /dev/null
+++ b/lib/status_handler.h
@@ -0,0 +1,108 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef STATUSHANDLER_H_INCLUDED
+#define STATUSHANDLER_H_INCLUDED
+
+#include <wx/string.h>
+#include <string>
+#include <zen/int64.h>
+
+const int UI_UPDATE_INTERVAL = 100; //unit: [ms]; perform ui updates not more often than necessary, 100 seems to be a good value with only a minimal performance loss
+
+bool updateUiIsAllowed(); //test if a specific amount of time is over
+void updateUiNow(); //do the updating
+
+/*
+Updating GUI is fast!
+ time per single call to ProcessCallback::forceUiRefresh()
+ - Comparison 25 µs
+ - Synchronization 0.6 ms (despite complex graph!)
+*/
+
+//interfaces for status updates (can be implemented by GUI or Batch mode)
+
+
+//report status during comparison and synchronization
+struct ProcessCallback
+{
+ virtual ~ProcessCallback() {}
+
+ //identifiers of different phases
+ enum Process
+ {
+ PROCESS_NONE = 10,
+ PROCESS_SCANNING,
+ PROCESS_COMPARING_CONTENT,
+ PROCESS_SYNCHRONIZING
+ };
+
+ //these methods have to be implemented in the derived classes to handle error and status information
+ virtual void initNewProcess(int objectsTotal, zen::Int64 dataTotal, Process processID) = 0; //informs about the total amount of data that will be processed from now on
+
+ //note: this one must NOT throw in order to properly allow undoing setting of statistics!
+ //it is in general paired with a call to requestUiRefresh() to compensate!
+ virtual void updateProcessedData(int objectsProcessed, zen::Int64 dataProcessed) = 0; //throw()
+
+ //opportunity to abort must be implemented in a frequently executed method like requestUiRefresh()
+ virtual void requestUiRefresh() = 0; //throw ?
+
+ //this method is triggered repeatedly by requestUiRefresh() and can be used to refresh the ui by dispatching pending events
+ virtual void forceUiRefresh() = 0;
+
+ //called periodically after data was processed: expected(!) to request GUI update
+ virtual void reportStatus(const wxString& text) = 0; //status info only, should not be logged!
+
+ //called periodically after data was processed: expected(!) to request GUI update
+ virtual void reportInfo(const wxString& text) = 0;
+
+ virtual void reportWarning(const wxString& warningMessage, bool& warningActive) = 0;
+
+ //error handling:
+ enum Response
+ {
+ IGNORE_ERROR = 10,
+ RETRY
+ };
+ virtual Response reportError (const wxString& errorMessage) = 0; //recoverable error situation
+ virtual void reportFatalError(const wxString& errorMessage) = 0; //non-recoverable error situation
+};
+
+
+//gui may want to abort process
+struct AbortCallback
+{
+ virtual ~AbortCallback() {}
+ virtual void requestAbortion() = 0;
+};
+
+
+//actual callback implementation will have to satisfy "process" and "gui"
+class StatusHandler : public ProcessCallback, public AbortCallback
+{
+public:
+ StatusHandler() : abortRequested(false) {}
+
+ virtual void requestUiRefresh()
+ {
+ if (updateUiIsAllowed()) //test if specific time span between ui updates is over
+ forceUiRefresh();
+
+ if (abortRequested)
+ abortThisProcess(); //abort can be triggered by requestAbortion()
+ }
+
+ virtual void abortThisProcess() = 0;
+ virtual void requestAbortion() { abortRequested = true; } //this does NOT call abortThisProcess immediately, but when appropriate (e.g. async. processes finished)
+ bool abortIsRequested() { return abortRequested; }
+
+private:
+ bool abortRequested;
+};
+
+
+
+#endif // STATUSHANDLER_H_INCLUDED
diff --git a/lib/xml_base.cpp b/lib/xml_base.cpp
new file mode 100644
index 00000000..b4887dc0
--- /dev/null
+++ b/lib/xml_base.cpp
@@ -0,0 +1,103 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "xml_base.h"
+#include <zen/file_handling.h>
+#include <zen/file_io.h>
+
+using namespace zen;
+
+
+//loadXmlDocument vs loadStream:
+//1. better error reporting
+//2. quick exit if (potentially large) input file is not an XML
+void xmlAccess::loadXmlDocument(const Zstring& filename, XmlDoc& doc) //throw FfsXmlError
+{
+ std::string stream;
+ try
+ {
+ {
+ //quick test whether input is an XML: avoid loading large binary files up front!
+ const std::string xmlBegin = "<?xml version=";
+ std::vector<char> buffer(xmlBegin.size() + sizeof(zen::BYTE_ORDER_MARK_UTF8));
+
+ FileInput inputFile(filename); //throw FileError;
+ const size_t bytesRead = inputFile.read(&buffer[0], buffer.size()); //throw FileError
+
+ const std::string fileBegin(&buffer[0], bytesRead);
+
+ if (!startsWith(fileBegin, xmlBegin) &&
+ !startsWith(fileBegin, zen::BYTE_ORDER_MARK_UTF8 + xmlBegin)) //respect BOM!
+ throw FfsXmlError(_("Error parsing configuration file:") + "\n\"" + filename + "\"");
+ }
+
+ const zen::UInt64 fs = zen::getFilesize(filename); //throw FileError
+ stream.resize(to<size_t>(fs));
+
+ FileInput inputFile(filename); //throw FileError
+ const size_t bytesRead = inputFile.read(&stream[0], stream.size()); //throw FileError
+ if (bytesRead < to<size_t>(fs))
+ throw FfsXmlError(_("Error reading file:") + "\n\"" + filename + "\"");
+ }
+ catch (const FileError& error)
+ {
+ if (!fileExists(filename))
+ throw FfsXmlError(_("File does not exist:") + "\n\"" + filename+ "\"");
+
+ throw FfsXmlError(error.msg());
+ }
+
+ try
+ {
+ zen::parse(stream, doc); //throw XmlParsingError
+ }
+ catch (const XmlParsingError&)
+ {
+ throw FfsXmlError(_("Error parsing configuration file:") + "\n\"" + filename + "\"");
+ }
+}
+
+
+const std::wstring xmlAccess::getErrorMessageFormatted(const XmlIn& in)
+{
+ std::wstring errorMessage = _("Could not read values for the following XML nodes:") + "\n";
+
+ std::vector<std::wstring> failedNodes = in.getErrorsAs<std::wstring>();
+ std::for_each(failedNodes.begin(), failedNodes.end(),
+ [&](const std::wstring& str) { errorMessage += str + L'\n'; });
+
+ return errorMessage;
+}
+
+
+void xmlAccess::saveXmlDocument(const zen::XmlDoc& doc, const Zstring& filename) //throw (FfsXmlError)
+{
+ std::string stream = serialize(doc); //throw ()
+
+ bool saveNecessary = true;
+ try
+ {
+ if (zen::getFilesize(filename) == stream.size()) //throw FileError
+ try
+ {
+ if (zen::loadStream(filename) == stream) //throw XmlFileError
+ saveNecessary = false;
+ }
+ catch (const zen::XmlFileError&) {}
+ }
+ catch (FileError&) {}
+
+ if (saveNecessary)
+ try
+ {
+ FileOutput outputFile(filename, FileOutput::ACC_OVERWRITE); //throw FileError
+ outputFile.write(stream.c_str(), stream.length()); //
+ }
+ catch (const FileError& error) //more detailed error messages than with wxWidgets
+ {
+ throw FfsXmlError(error.msg());
+ }
+}
diff --git a/lib/xml_base.h b/lib/xml_base.h
new file mode 100644
index 00000000..4614615e
--- /dev/null
+++ b/lib/xml_base.h
@@ -0,0 +1,42 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef XMLBASE_H_INCLUDED
+#define XMLBASE_H_INCLUDED
+
+#include <zenxml/xml.h>
+#include <zen/zstring.h>
+#include <zen/file_error.h>
+
+//bind zenxml and zen file handling together
+
+namespace xmlAccess
+{
+class FfsXmlError //Exception class
+{
+public:
+ enum Severity
+ {
+ WARNING = 77,
+ FATAL
+ };
+
+ FfsXmlError(const std::wstring& message, Severity sev = FATAL) : errorMessage(message), m_severity(sev) {}
+
+ const std::wstring& msg() const { return errorMessage; }
+ Severity getSeverity() const { return m_severity; }
+private:
+ const std::wstring errorMessage;
+ const Severity m_severity;
+};
+
+void saveXmlDocument(const zen::XmlDoc& doc, const Zstring& filename); //throw FfsXmlError
+void loadXmlDocument(const Zstring& filename, zen::XmlDoc& doc); //throw FfsXmlError
+
+const std::wstring getErrorMessageFormatted(const zen::XmlIn& in);
+}
+
+#endif // XMLBASE_H_INCLUDED
bgstack15