diff options
author | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 17:15:16 +0200 |
---|---|---|
committer | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 17:15:16 +0200 |
commit | bd6336c629841c6db3a6ca53a936d629d34db53b (patch) | |
tree | 3721ef997864108df175ce677a8a7d4342a6f1d2 /lib | |
parent | 4.0 (diff) | |
download | FreeFileSync-bd6336c629841c6db3a6ca53a936d629d34db53b.tar.gz FreeFileSync-bd6336c629841c6db3a6ca53a936d629d34db53b.tar.bz2 FreeFileSync-bd6336c629841c6db3a6ca53a936d629d34db53b.zip |
4.1
Diffstat (limited to 'lib')
73 files changed, 15138 insertions, 0 deletions
diff --git a/lib/Batch.ico b/lib/Batch.ico Binary files differnew file mode 100644 index 00000000..7b33067a --- /dev/null +++ b/lib/Batch.ico 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 Binary files differnew file mode 100644 index 00000000..b87789a7 --- /dev/null +++ b/lib/FreeFileSync.ico 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 Binary files differnew file mode 100644 index 00000000..eee91c14 --- /dev/null +++ b/lib/SyncDB.ico 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 |