diff options
80 files changed, 1870 insertions, 1052 deletions
diff --git a/Application.cpp b/Application.cpp index ee616271..327681bf 100644 --- a/Application.cpp +++ b/Application.cpp @@ -8,7 +8,6 @@ #include <memory> #include "ui/main_dlg.h" #include <wx/msgdlg.h> -#include <wx/sound.h> #include <wx/tooltip.h> //wxWidgets v2.9 #include <wx/log.h> #include <wx+/app_main.h> @@ -19,7 +18,6 @@ #include "ui/check_version.h" #include "ui/switch_to_gui.h" #include "lib/resources.h" -#include "lib/ffs_paths.h" #include "lib/lock_holder.h" #include "lib/process_xml.h" #include "lib/error_log.h" @@ -68,10 +66,10 @@ bool Application::OnInit() { #ifdef FFS_WIN std::set_terminate(onTerminationRequested); //unlike wxWidgets uncaught exception handling, this works for all worker threads + assert(!win8OrLater()); //another breadcrumb: test and add new OS entry to "compatibility" in application manifest #ifdef _MSC_VER _set_invalid_parameter_handler(crtInvalidParameterHandler); //see comment in <zen/time.h> #endif - assert(!win8OrLater()); //another breadcrumb: test and add new OS entry to "compatibility" in application manifest #endif returnCode = FFS_RC_SUCCESS; @@ -446,14 +444,6 @@ void runBatchMode(const Zstring& filename, FfsReturnCode& returnCode) syncProcessCfg, folderCmp, statusHandler); - - //play (optional) sound notification after sync has completed -> don't play in silent mode, consider RealtimeSync! - if (batchCfg.showProgress) - { - const wxString soundFile = toWx(getResourceDir()) + L"Sync_Complete.wav"; - if (fileExists(toZ(soundFile))) - wxSound::Play(soundFile, wxSOUND_ASYNC); //warning: this may fail and show a wxWidgets error message! => must not play when running FFS as a service! - } } catch (BatchAbortProcess&) {} //exit used by statusHandler diff --git a/BUILD/Changelog.txt b/BUILD/Changelog.txt index 8e9bdc27..6696584b 100644 --- a/BUILD/Changelog.txt +++ b/BUILD/Changelog.txt @@ -2,6 +2,23 @@ |FreeFileSync| -------------- +Changelog v5.9 +-------------- +Scroll grid directly under mouse cursor +Move files directly to recycle bin without parent "FFS 2012-05-15 131513" temporary folders +Offer $HOME directory alias in directory dropdown list (Linux) +Support for tilde (~) character in input folder paths (Linux) +New environment variables for RealtimeSync: %change_action%, "%change_path% +Use Internet Explorer proxy settings for new version check (Windows) +Show proper error message after failed symlink creation +Start comparison directly when double-clicking config list +New batch return code: "Synchronization completed with warnings" +Hide files that won't be copied by default if direction "none" is part of the rule set (e.g. update variant) +New sync completion sound +Remember save config and folder picker dialog positions separately +Fixed sync completion sound not playing (Ubuntu) + + Changelog v5.8 -------------- New icon theme diff --git a/BUILD/FreeFileSync.chm b/BUILD/FreeFileSync.chm Binary files differindex f680d349..e4eba632 100644 --- a/BUILD/FreeFileSync.chm +++ b/BUILD/FreeFileSync.chm diff --git a/BUILD/Help/html/Backup Strategies.html b/BUILD/Help/html/Backup Strategies.html index 95f33a47..4cc4dd46 100644 --- a/BUILD/Help/html/Backup Strategies.html +++ b/BUILD/Help/html/Backup Strategies.html @@ -5,7 +5,7 @@ <TITLE></TITLE> <META NAME="GENERATOR" CONTENT="OpenOffice.org 3.2 (Win32)"> <META NAME="CREATED" CONTENT="20091206;16574000"> - <META NAME="CHANGED" CONTENT="20120511;23131500"> + <META NAME="CHANGED" CONTENT="20121007;13361000"> <META NAME="Info 1" CONTENT=""> <META NAME="Info 2" CONTENT=""> <META NAME="Info 3" CONTENT=""> @@ -28,12 +28,12 @@ Strategies</FONT></FONT></H2> <P STYLE="margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif"><B>1. Full backup with versioning of old files</B></FONT></P> <P STYLE="margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif">In -synchronization settings select and specify "Versioning" -for deletion handling. FreeFileSync will place files that have been -deleted or overwritten with newer versions into corresponding -time-stamped sub directories. This provides a space-optimized way to -save all older versions of files separately while the most recent -ones are available in main source and target directories.</FONT></P> +synchronization settings select "Versioning" for deletion +handling. FreeFileSync will move files that have been deleted or +overwritten with newer versions into the provided directory and +append a time-stamp. This is effectively a space-optimized way to +save all older versions of files separately while the recent ones are +available in main source and target directories.</FONT></P> <P STYLE="margin-bottom: 0cm"><BR> </P> <P STYLE="margin-bottom: 0cm"><BR> @@ -58,7 +58,7 @@ Base directories are set up accordingly:</FONT></P> </SPAN><BR CLEAR=LEFT><BR> </P> <P STYLE="margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif">Latter -will be interactively replaced with the current date during +will be dynamically replaced with the current date during synchronization. In order to further automate this process, you can create a *.ffs_batch file with this configuration and choose "<I>ignore errors</I>" to avoid the popup message that target directory is diff --git a/BUILD/Help/html/Batch Scripting.html b/BUILD/Help/html/Batch Scripting.html index 526d3d79..b812ee1e 100644 --- a/BUILD/Help/html/Batch Scripting.html +++ b/BUILD/Help/html/Batch Scripting.html @@ -5,7 +5,7 @@ <TITLE></TITLE> <META NAME="GENERATOR" CONTENT="OpenOffice.org 3.2 (Win32)"> <META NAME="CREATED" CONTENT="20091206;16574000"> - <META NAME="CHANGED" CONTENT="20120511;23115700"> + <META NAME="CHANGED" CONTENT="20121005;17531200"> <META NAME="Info 1" CONTENT=""> <META NAME="Info 2" CONTENT=""> <META NAME="Info 3" CONTENT=""> @@ -39,7 +39,8 @@ as argument) it returns one of the following status codes:</FONT></P> <P ALIGN=LEFT STYLE="margin-right: 0.98cm; margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif"><SPAN STYLE="font-style: normal"><B>Return Codes</B></SPAN><SPAN STYLE="font-style: normal"><BR>0 - Synchronization completed successfully<BR>1 - Synchronization - completed with errors<BR>2 - Synchronization was aborted</SPAN></FONT></P> + completed with warnings<BR>2 - Synchronization completed with + errors<BR>3 - Synchronization was aborted</SPAN></FONT></P> </UL> </SPAN><BR CLEAR=LEFT><BR> </P> diff --git a/BUILD/Help/html/Macros.html b/BUILD/Help/html/Macros.html index 33d16757..c491c22c 100644 --- a/BUILD/Help/html/Macros.html +++ b/BUILD/Help/html/Macros.html @@ -5,7 +5,7 @@ <TITLE></TITLE> <META NAME="GENERATOR" CONTENT="OpenOffice.org 3.2 (Win32)"> <META NAME="CREATED" CONTENT="20091206;16574000"> - <META NAME="CHANGED" CONTENT="20120907;12344300"> + <META NAME="CHANGED" CONTENT="20121007;19184600"> <META NAME="Info 1" CONTENT=""> <META NAME="Info 2" CONTENT=""> <META NAME="Info 3" CONTENT=""> @@ -40,10 +40,10 @@ macros:</B></FONT></P> </FONT>current time, format [hhmmss], e. g. "<FONT FACE="Courier New, monospace">201340</FONT>"<BR><FONT FACE="Courier New, monospace">%date% - </FONT>current date, e. g. "<FONT FACE="Courier New, monospace">2010-07-13</FONT>"<BR><BR><FONT FACE="Courier New, monospace">%weekday% - </FONT>day of the week, e. g. "<FONT FACE="Courier New, monospace">Monday</FONT>"<BR><FONT FACE="Courier New, monospace">%week% - - </FONT>calendar week, e. g. "<FONT FACE="Courier New, monospace">28</FONT>"<BR><BR><FONT FACE="Courier New, monospace">%day% - - </FONT>current day, e. g. "<FONT FACE="Courier New, monospace">21</FONT>"<BR><FONT FACE="Courier New, monospace">%month% - - </FONT>current month, e. g. "<FONT FACE="Courier New, monospace">July</FONT>"<BR><FONT FACE="Courier New, monospace">%year% - - </FONT>current year, e. g. "<FONT FACE="Courier New, monospace">2010</FONT>"<BR><BR><FONT FACE="Courier New, monospace">%hour% - + </FONT>calendar week, e. g. "<FONT FACE="Courier New, monospace">28</FONT>"<BR><BR><FONT FACE="Courier New, monospace">%year% - + </FONT>current year, e. g. "<FONT FACE="Courier New, monospace">2010</FONT>"<BR><FONT FACE="Courier New, monospace">%month% - + </FONT>current month, e. g. "<FONT FACE="Courier New, monospace">July</FONT>"<BR><FONT FACE="Courier New, monospace">%day% - + </FONT>current day, e. g. "<FONT FACE="Courier New, monospace">21</FONT>"<BR><BR><FONT FACE="Courier New, monospace">%hour% - </FONT>current hour, e. g. "<FONT FACE="Courier New, monospace">20</FONT>"<BR><FONT FACE="Courier New, monospace">%min% - </FONT>current minute, e. g. "<FONT FACE="Courier New, monospace">13</FONT>"<BR><FONT FACE="Courier New, monospace">%sec% - </FONT>current second, e. g. "<FONT FACE="Courier New, monospace">40</FONT>"</FONT></P> diff --git a/BUILD/Help/html/RealtimeSync.html b/BUILD/Help/html/RealtimeSync.html index 49187f40..73f9dd60 100644 --- a/BUILD/Help/html/RealtimeSync.html +++ b/BUILD/Help/html/RealtimeSync.html @@ -5,7 +5,7 @@ <TITLE></TITLE> <META NAME="GENERATOR" CONTENT="OpenOffice.org 3.2 (Win32)"> <META NAME="CREATED" CONTENT="20091206;16574000"> - <META NAME="CHANGED" CONTENT="20120511;21275200"> + <META NAME="CHANGED" CONTENT="20121017;18372400"> <META NAME="Info 1" CONTENT=""> <META NAME="Info 2" CONTENT=""> <META NAME="Info 3" CONTENT=""> @@ -50,7 +50,7 @@ but also sets up the command line to execute the </FONT><FONT FACE="Courier New, Now press "</FONT><FONT FACE="Tahoma, sans-serif"><I>Start</I></FONT><FONT FACE="Tahoma, sans-serif">" to begin monitoring.</FONT></P> <UL> - <P STYLE="margin-bottom: 0cm"><IMG SRC="../img/RealtimeSync.png" NAME="Grafik3" ALIGN=MIDDLE BORDER=0></P> + <P STYLE="margin-bottom: 0cm"><IMG SRC="../img/RealtimeSync.png" NAME="Grafik3" ALIGN=MIDDLE WIDTH=469 HEIGHT=437 BORDER=0></P> </UL> <P STYLE="margin-bottom: 0cm"><BR> </P> @@ -58,10 +58,10 @@ to begin monitoring.</FONT></P> <UL> <P ALIGN=LEFT STYLE="margin-right: 0.98cm; margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif"><B>Note</B></FONT></P> <LI><P ALIGN=LEFT STYLE="margin-right: 0.98cm; margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif">The - command should </FONT><FONT FACE="Tahoma, sans-serif"><B>not</B></FONT><FONT FACE="Tahoma, sans-serif"> - </FONT><FONT FACE="Tahoma, sans-serif"><B>block</B></FONT><FONT FACE="Tahoma, sans-serif"> - progress. Make sure the FreeFileSync batch job does not show any - popup dialogs. See notes in <A HREF="Batch%20Scripting.html">Batch + command should </FONT><FONT FACE="Tahoma, sans-serif"><B>not</B></FONT> + <FONT FACE="Tahoma, sans-serif"><B>block</B></FONT> <FONT FACE="Tahoma, sans-serif">progress. + Make sure the FreeFileSync batch job does not show any popup + dialogs. See notes in <A HREF="Batch%20Scripting.html">Batch Scripting</A>.</FONT><SPAN STYLE="text-decoration: none"><FONT FACE="Tahoma, sans-serif"><BR> </FONT></SPAN></P> <LI><P ALIGN=LEFT STYLE="margin-right: 0.98cm; margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif">The settings dialog can be skipped by passing a RealtimeSync @@ -90,7 +90,7 @@ ffs_batch configuration into the USB stick's root directory and have it called when the stick is mounted. Then configure RealtimeSync as shown in the following:</FONT></P> <UL> - <P><IMG SRC="../img/WatchUsbInsert.png" NAME="Grafik2" ALIGN=BOTTOM BORDER=0></P> + <P><IMG SRC="../img/WatchUsbInsert.png" NAME="Grafik2" ALIGN=BOTTOM WIDTH=446 HEIGHT=411 BORDER=0></P> </UL> <P STYLE="margin-bottom: 0cm"><BR> </P> @@ -106,9 +106,10 @@ files are modified in "</FONT><FONT FACE="Courier New, monospace">H:\Data</ <P STYLE="margin-left: 1.46cm; margin-bottom: 0cm"><SPAN ID="Rahmen2" DIR="LTR" STYLE="float: left; width: 80%; height: 0.14cm; border: 1px solid #000080; padding: 0.05cm; background: #ccccff"> <UL> <P ALIGN=LEFT STYLE="margin-right: 0.98cm; margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif"><B>Note<BR></B></FONT><FONT FACE="Tahoma, sans-serif">The - n</FONT><SPAN STYLE="text-decoration: none"><FONT FACE="Tahoma, sans-serif">ame - of the last changed file is written to an environment variable - named </FONT></SPAN><SPAN STYLE="text-decoration: none"><FONT FACE="Courier New, monospace">"changed_file".</FONT></SPAN></P> + full path of the last changed file and the action that triggered + the change notification (create, update or delete) are </FONT><SPAN STYLE="text-decoration: none"><FONT FACE="Tahoma, sans-serif">written + to the environment variables </FONT></SPAN><SPAN STYLE="text-decoration: none"><FONT FACE="Courier New, monospace">"change_path"</FONT></SPAN><SPAN STYLE="text-decoration: none"><FONT FACE="Tahoma, sans-serif"> + and </FONT></SPAN><SPAN STYLE="text-decoration: none"><FONT FACE="Courier New, monospace">"change_action".</FONT></SPAN></P> </UL> </SPAN><BR CLEAR=LEFT><BR> </P> @@ -121,9 +122,10 @@ directories. (Windows)</FONT></P> <P ALIGN=LEFT STYLE="margin-right: 0.98cm; margin-bottom: 0cm"><FONT FACE="Courier New, monospace"><FONT FACE="Tahoma, sans-serif">Show which file or directory has triggered a change. Enter command line:</FONT><BR> cmd /c echo - "%changed_file%" & pause<BR><BR><FONT FACE="Tahoma, sans-serif">Write + %change_action% "%change_path%" & pause<BR><BR><FONT FACE="Tahoma, sans-serif">Write a list of all changes to a logfile:</FONT><BR> cmd - /c echo "%changed_file%" >> c:\log.txt</FONT></P> + /c echo %change_action% "%change_path%" >> + c:\log.txt</FONT></P> </UL> </SPAN><BR CLEAR=LEFT> </P> diff --git a/BUILD/Languages/english_uk.lng b/BUILD/Languages/english_uk.lng index 840e9431..4d6a5611 100644 --- a/BUILD/Languages/english_uk.lng +++ b/BUILD/Languages/english_uk.lng @@ -355,10 +355,10 @@ The command is triggered if: <target>Synchronisation completed successfully!</target> <source>Press "Switch" to resolve issues in FreeFileSync main dialog.</source> -<target>Press "Switch" to resolve issues in FreeFileSync main dialog.</target> +<target>Press "Switch" to resolve issues in FreeFileSync main dialogue.</target> <source>Switching to FreeFileSync main dialog...</source> -<target>Switching to FreeFileSync main dialog...</target> +<target>Switching to FreeFileSync main dialogue...</target> <source>Unable to connect to sourceforge.net!</source> <target>Unable to connect to sourceforge.net!</target> @@ -424,7 +424,7 @@ The command is triggered if: <target>Drag && drop</target> <source>Close progress dialog</source> -<target>Close progress dialog</target> +<target>Close progress dialogue</target> <source>Standby</source> <target>Standby</target> @@ -574,7 +574,7 @@ The command is triggered if: <target>On completion:</target> <source>Show progress dialog</source> -<target>Show progress dialog</target> +<target>Show progress dialogue</target> <source>Generate log file</source> <target>Generate log file</target> @@ -772,7 +772,7 @@ Note: File names must be relative to base directories! <target>Transfer file and folder permissions (Requires Administrator rights)</target> <source>Restore hidden dialogs</source> -<target>Restore hidden dialogs</target> +<target>Restore hidden dialogues</target> <source>External applications</source> <target>External applications</target> @@ -787,7 +787,7 @@ Note: File names must be relative to base directories! <target>Statistics</target> <source>Don't show this dialog again</source> -<target>Don't show this dialog again</target> +<target>Don't show this dialogue again</target> <source>Find what:</source> <target>Find what:</target> @@ -1114,7 +1114,7 @@ Note: File names must be relative to base directories! <target>- Other side's counterpart to %dir</target> <source>Make hidden dialogs and warning messages visible again?</source> -<target>Make hidden dialogs and warning messages visible again?</target> +<target>Make hidden dialogues and warning messages visible again?</target> <source> <pluralform>Do you really want to move the following object to the Recycle Bin?</pluralform> diff --git a/BUILD/Languages/german.lng b/BUILD/Languages/german.lng index 10f19f6d..cdb0622f 100644 --- a/BUILD/Languages/german.lng +++ b/BUILD/Languages/german.lng @@ -351,6 +351,9 @@ Die Befehlszeile wird ausgelöst wenn: <source>Synchronization completed with errors!</source> <target>Synchronisation mit Fehlern abgeschlossen!</target> +<source>Synchronization completed with warnings!</source> +<target>Synchronisation mit Warnungen abgeschlossen!</target> + <source>Nothing to synchronize!</source> <target>Es gibt nichts zu synchronisieren!</target> @@ -444,18 +447,18 @@ Die Befehlszeile wird ausgelöst wenn: <source>Hibernate</source> <target>Ruhezustand</target> -<source>1. &Compare</source> -<target>1. &Vergleichen</target> - -<source>2. &Synchronize</source> -<target>2. &Synchronisieren</target> - <source>&New</source> <target>&Neu</target> <source>&Save</source> <target>&Speichern</target> +<source>1. &Compare</source> +<target>1. &Vergleichen</target> + +<source>2. &Synchronize</source> +<target>2. &Synchronisieren</target> + <source>&Language</source> <target>&Sprache</target> diff --git a/BUILD/Resources.zip b/BUILD/Resources.zip Binary files differindex 676a8d0d..1f602205 100644 --- a/BUILD/Resources.zip +++ b/BUILD/Resources.zip diff --git a/BUILD/Sync_Complete.wav b/BUILD/Sync_Complete.wav Binary files differindex 3fe95e5d..96dd2a15 100644 --- a/BUILD/Sync_Complete.wav +++ b/BUILD/Sync_Complete.wav diff --git a/FreeFileSync.cbp b/FreeFileSync.cbp index becb6722..f2abfcc6 100644 --- a/FreeFileSync.cbp +++ b/FreeFileSync.cbp @@ -8,9 +8,9 @@ <Option compiler="gcc" /> <Build> <Target title="Release"> - <Option output="BUILD\Bin\FreeFileSync_Win32" prefix_auto="1" extension_auto="1" /> - <Option working_dir="BUILD\" /> - <Option object_output="OBJ\FFS_Release_32_MinGW\" /> + <Option output="BUILD/Bin/FreeFileSync_Win32" prefix_auto="1" extension_auto="1" /> + <Option working_dir="BUILD/" /> + <Option object_output="OBJ/FFS_Release_32_MinGW/" /> <Option type="0" /> <Option compiler="gcc" /> <Option projectCompilerOptionsRelation="2" /> @@ -18,14 +18,13 @@ <Compiler> <Add option="-O3" /> <Add option="-DNDEBUG" /> - <Add directory="C:\Programme\C++\wxWidgets\lib\mingw_release_lib\mswu" /> + <Add directory="C:/Programme/C++/wxWidgets/lib/mingw_release_lib/mswu" /> </Compiler> <Linker> <Add option="-s" /> <Add option="-static" /> - <Add library="libboost_thread-mgw47-mt-s-1_51.a" /> - <Add library="libboost_system-mgw47-mt-s-1_51.a" /> - <Add library="libboost_chrono-mgw47-mt-s-1_51.a" /> + <Add library="libboost_thread-mgw47-mt-s-1_52.a" /> + <Add library="libboost_system-mgw47-mt-s-1_52.a" /> <Add library="libwxmsw28u_aui.a" /> <Add library="libwxmsw28u_adv.a" /> <Add library="libwxmsw28u_core.a" /> @@ -33,16 +32,16 @@ <Add library="libwxbase28u_net.a" /> <Add library="libwxpng.a" /> <Add library="libwxzlib.a" /> - <Add directory="C:\Programme\C++\wxWidgets\lib\mingw_release_lib" /> + <Add directory="C:/Programme/C++/wxWidgets/lib/mingw_release_lib" /> </Linker> <ExtraCommands> <Add after='"C:\Program Files\C++\CodeSigning\SignCode.cmd" "$(PROJECT_DIR)$(TARGET_OUTPUT_FILE)"' /> </ExtraCommands> </Target> <Target title="Debug-DLL"> - <Option output="BUILD\Bin\FreeFileSync_Debug" prefix_auto="1" extension_auto="1" /> + <Option output="BUILD/Bin/FreeFileSync_Debug" prefix_auto="1" extension_auto="1" /> <Option working_dir="BUILD" /> - <Option object_output="OBJ\FFS_Debug_32_MinGW\" /> + <Option object_output="OBJ/FFS_Debug_32_MinGW/" /> <Option type="0" /> <Option compiler="gcc" /> <Option projectCompilerOptionsRelation="2" /> @@ -52,25 +51,24 @@ <Add option="-Winvalid-pch" /> <Add option='-include "wx+/pch.h"' /> <Add option="-D__WXDEBUG__" /> - <Add directory="C:\Programme\C++\wxWidgets\lib\mingw_debug_dll\mswud" /> + <Add directory="C:/Programme/C++/wxWidgets/lib/mingw_debug_dll/mswud" /> </Compiler> <Linker> - <Add library="libboost_thread-mgw47-mt-sd-1_51.a" /> - <Add library="libboost_system-mgw47-mt-sd-1_51.a" /> - <Add library="libboost_chrono-mgw47-mt-sd-1_51.a" /> + <Add library="libboost_thread-mgw47-mt-sd-1_52.a" /> + <Add library="libboost_system-mgw47-mt-sd-1_52.a" /> <Add library="libwxmsw28ud_aui.a" /> <Add library="libwxmsw28ud_adv.a" /> <Add library="libwxmsw28ud_core.a" /> <Add library="libwxbase28ud.a" /> <Add library="libwxbase28ud_net.a" /> <Add library="libwxzlibd.a" /> - <Add directory="C:\Programme\C++\wxWidgets\lib\mingw_debug_dll" /> + <Add directory="C:/Programme/C++/wxWidgets/lib/mingw_debug_dll" /> </Linker> </Target> <Target title="Unit Test"> - <Option output="OBJ\Unit_Test_MinGW\Unit Test" prefix_auto="1" extension_auto="1" /> - <Option working_dir="OBJ\Unit_Test_MinGW\" /> - <Option object_output="OBJ\Unit_Test_MinGW\" /> + <Option output="OBJ/Unit_Test_MinGW/Unit Test" prefix_auto="1" extension_auto="1" /> + <Option working_dir="OBJ/Unit_Test_MinGW/" /> + <Option object_output="OBJ/Unit_Test_MinGW/" /> <Option type="1" /> <Option compiler="gcc" /> <Option projectCompilerOptionsRelation="2" /> @@ -80,20 +78,19 @@ <Add option="-Winvalid-pch" /> <Add option='-include "wx+/pch.h"' /> <Add option="-D__WXDEBUG__" /> - <Add directory="C:\Programme\C++\wxWidgets\lib\mingw_debug_dll\mswud" /> - <Add directory="lib\gtest" /> - <Add directory="lib\gtest\include" /> + <Add directory="C:/Programme/C++/wxWidgets/lib/mingw_debug_dll/mswud" /> + <Add directory="lib/gtest" /> + <Add directory="lib/gtest/include" /> </Compiler> <Linker> - <Add library="libboost_thread-mgw47-mt-sd-1_51.a" /> - <Add library="libboost_system-mgw47-mt-sd-1_51.a" /> - <Add library="libboost_chrono-mgw47-mt-sd-1_51.a" /> + <Add library="libboost_thread-mgw47-mt-sd-1_52.a" /> + <Add library="libboost_system-mgw47-mt-sd-1_52.a" /> <Add library="libwxmsw28ud_adv.a" /> <Add library="libwxmsw28ud_core.a" /> <Add library="libwxbase28ud.a" /> <Add library="libwxpngd.a" /> <Add library="libwxzlibd.a" /> - <Add directory="C:\Programme\C++\wxWidgets\lib\mingw_debug_dll" /> + <Add directory="C:/Programme/C++/wxWidgets/lib/mingw_debug_dll" /> </Linker> </Target> </Build> @@ -120,12 +117,12 @@ <Add option="-DBOOST_THREAD_USE_LIB" /> <Add option="-DWXINTL_NO_GETTEXT_MACRO" /> <Add option="-DZEN_PLATFORM_WINDOWS" /> - <Add directory="C:\Programme\C++\wxWidgets\include" /> - <Add directory="C:\Program Files\C++\Boost" /> + <Add directory="C:/Programme/C++/wxWidgets/include" /> + <Add directory="C:/Program Files/C++/Boost" /> <Add directory="." /> </Compiler> <ResourceCompiler> - <Add directory="C:\Programme\C++\wxWidgets\include" /> + <Add directory="C:/Programme/C++/wxWidgets/include" /> </ResourceCompiler> <Linker> <Add option="-mthreads" /> @@ -142,7 +139,8 @@ <Add library="libwinmm.a" /> <Add library="libmpr.a" /> <Add library="libuxtheme.a" /> - <Add directory="C:\Program Files\C++\Boost\stage\lib" /> + <Add library="libwininet.a" /> + <Add directory="C:/Program Files/C++/Boost/stage/lib" /> </Linker> <Unit filename="WxWizFrame.fbp"> <Option target="<{~None~}>" /> @@ -158,72 +156,72 @@ <Unit filename="comparison.h" /> <Unit filename="file_hierarchy.cpp" /> <Unit filename="file_hierarchy.h" /> - <Unit filename="lib\binary.cpp" /> - <Unit filename="lib\binary.h" /> - <Unit filename="lib\cmp_filetime.h" /> - <Unit filename="lib\db_file.cpp" /> - <Unit filename="lib\db_file.h" /> - <Unit filename="lib\dir_exist_async.h" /> - <Unit filename="lib\dir_lock.cpp" /> - <Unit filename="lib\dir_lock.h" /> - <Unit filename="lib\error_log.h" /> - <Unit filename="lib\ffs_paths.h" /> - <Unit filename="lib\generate_logfile.h" /> - <Unit filename="lib\gtest\main.cpp"> + <Unit filename="lib/binary.cpp" /> + <Unit filename="lib/binary.h" /> + <Unit filename="lib/cmp_filetime.h" /> + <Unit filename="lib/db_file.cpp" /> + <Unit filename="lib/db_file.h" /> + <Unit filename="lib/dir_exist_async.h" /> + <Unit filename="lib/dir_lock.cpp" /> + <Unit filename="lib/dir_lock.h" /> + <Unit filename="lib/error_log.h" /> + <Unit filename="lib/ffs_paths.h" /> + <Unit filename="lib/generate_logfile.h" /> + <Unit filename="lib/gtest/main.cpp"> <Option target="Unit Test" /> </Unit> - <Unit filename="lib\gtest\src\gtest-all.cc"> + <Unit filename="lib/gtest/src/gtest-all.cc"> <Option target="Unit Test" /> </Unit> - <Unit filename="lib\gtest\unittest.h"> + <Unit filename="lib/gtest/unittest.h"> <Option target="Unit Test" /> </Unit> - <Unit filename="lib\gtest\unittest1.cpp"> + <Unit filename="lib/gtest/unittest1.cpp"> <Option target="Unit Test" /> </Unit> - <Unit filename="lib\gtest\unittest2.cpp"> + <Unit filename="lib/gtest/unittest2.cpp"> <Option target="Unit Test" /> </Unit> - <Unit filename="lib\gtest\unittest3.cpp"> + <Unit filename="lib/gtest/unittest3.cpp"> <Option target="Unit Test" /> </Unit> - <Unit filename="lib\hard_filter.cpp" /> - <Unit filename="lib\hard_filter.h" /> - <Unit filename="lib\help_provider.h" /> - <Unit filename="lib\icon_buffer.cpp" /> - <Unit filename="lib\icon_buffer.h" /> - <Unit filename="lib\localization.cpp" /> - <Unit filename="lib\localization.h" /> - <Unit filename="lib\lock_holder.h" /> - <Unit filename="lib\norm_filter.h" /> - <Unit filename="lib\parallel_scan.cpp" /> - <Unit filename="lib\parallel_scan.h" /> - <Unit filename="lib\parse_lng.h" /> - <Unit filename="lib\parse_plural.h" /> - <Unit filename="lib\perf_check.cpp"> - <Option target="Release" /> - <Option target="Debug-DLL" /> - </Unit> - <Unit filename="lib\perf_check.h"> - <Option target="Release" /> - <Option target="Debug-DLL" /> - </Unit> - <Unit filename="lib\process_xml.cpp" /> - <Unit filename="lib\process_xml.h" /> - <Unit filename="lib\resolve_path.cpp" /> - <Unit filename="lib\resolve_path.h" /> - <Unit filename="lib\resources.cpp" /> - <Unit filename="lib\resources.h" /> - <Unit filename="lib\return_codes.h" /> - <Unit filename="lib\shadow.cpp" /> - <Unit filename="lib\shadow.h" /> - <Unit filename="lib\soft_filter.h" /> - <Unit filename="lib\status_handler.cpp" /> - <Unit filename="lib\status_handler.h" /> - <Unit filename="lib\versioning.cpp" /> - <Unit filename="lib\versioning.h" /> - <Unit filename="lib\xml_base.cpp" /> - <Unit filename="lib\xml_base.h" /> + <Unit filename="lib/hard_filter.cpp" /> + <Unit filename="lib/hard_filter.h" /> + <Unit filename="lib/help_provider.h" /> + <Unit filename="lib/icon_buffer.cpp" /> + <Unit filename="lib/icon_buffer.h" /> + <Unit filename="lib/localization.cpp" /> + <Unit filename="lib/localization.h" /> + <Unit filename="lib/lock_holder.h" /> + <Unit filename="lib/norm_filter.h" /> + <Unit filename="lib/parallel_scan.cpp" /> + <Unit filename="lib/parallel_scan.h" /> + <Unit filename="lib/parse_lng.h" /> + <Unit filename="lib/parse_plural.h" /> + <Unit filename="lib/perf_check.cpp"> + <Option target="Release" /> + <Option target="Debug-DLL" /> + </Unit> + <Unit filename="lib/perf_check.h"> + <Option target="Release" /> + <Option target="Debug-DLL" /> + </Unit> + <Unit filename="lib/process_xml.cpp" /> + <Unit filename="lib/process_xml.h" /> + <Unit filename="lib/resolve_path.cpp" /> + <Unit filename="lib/resolve_path.h" /> + <Unit filename="lib/resources.cpp" /> + <Unit filename="lib/resources.h" /> + <Unit filename="lib/return_codes.h" /> + <Unit filename="lib/shadow.cpp" /> + <Unit filename="lib/shadow.h" /> + <Unit filename="lib/soft_filter.h" /> + <Unit filename="lib/status_handler.cpp" /> + <Unit filename="lib/status_handler.h" /> + <Unit filename="lib/versioning.cpp" /> + <Unit filename="lib/versioning.h" /> + <Unit filename="lib/xml_base.cpp" /> + <Unit filename="lib/xml_base.h" /> <Unit filename="resource.rc"> <Option compilerVar="WINDRES" /> <Option target="Release" /> @@ -233,291 +231,295 @@ <Unit filename="structures.h" /> <Unit filename="synchronization.cpp" /> <Unit filename="synchronization.h" /> - <Unit filename="ui\batch_config.cpp"> + <Unit filename="ui/batch_config.cpp"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\batch_config.h"> + <Unit filename="ui/batch_config.h"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\batch_status_handler.cpp"> + <Unit filename="ui/batch_status_handler.cpp"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\batch_status_handler.h" /> - <Unit filename="ui\check_version.cpp"> + <Unit filename="ui/batch_status_handler.h" /> + <Unit filename="ui/check_version.cpp"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\check_version.h"> + <Unit filename="ui/check_version.h"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\custom_grid.cpp"> + <Unit filename="ui/custom_grid.cpp"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\custom_grid.h"> + <Unit filename="ui/custom_grid.h"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\dir_name.cpp"> + <Unit filename="ui/dir_name.cpp"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\dir_name.h"> + <Unit filename="ui/dir_name.h"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\exec_finished_box.cpp"> + <Unit filename="ui/exec_finished_box.cpp"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\exec_finished_box.h"> + <Unit filename="ui/exec_finished_box.h"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\folder_history_box.cpp"> + <Unit filename="ui/folder_history_box.cpp"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\folder_history_box.h"> + <Unit filename="ui/folder_history_box.h"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\folder_pair.h"> + <Unit filename="ui/folder_pair.h"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\grid_view.cpp"> + <Unit filename="ui/grid_view.cpp"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\grid_view.h"> + <Unit filename="ui/grid_view.h"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\gui_generated.cpp"> + <Unit filename="ui/gui_generated.cpp"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\gui_generated.h"> + <Unit filename="ui/gui_generated.h"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\gui_status_handler.cpp"> + <Unit filename="ui/gui_status_handler.cpp"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\gui_status_handler.h"> + <Unit filename="ui/gui_status_handler.h"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\main_dlg.cpp"> + <Unit filename="ui/main_dlg.cpp"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\main_dlg.h"> + <Unit filename="ui/main_dlg.h"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\msg_popup.cpp"> + <Unit filename="ui/msg_popup.cpp"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\msg_popup.h" /> - <Unit filename="ui\progress_indicator.cpp"> + <Unit filename="ui/msg_popup.h" /> + <Unit filename="ui/progress_indicator.cpp"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\progress_indicator.h" /> - <Unit filename="ui\search.cpp"> + <Unit filename="ui/progress_indicator.h" /> + <Unit filename="ui/search.cpp"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\search.h" /> - <Unit filename="ui\small_dlgs.cpp"> + <Unit filename="ui/search.h" /> + <Unit filename="ui/small_dlgs.cpp"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\small_dlgs.h"> + <Unit filename="ui/small_dlgs.h"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\sorting.h"> + <Unit filename="ui/sorting.h"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\switch_to_gui.h"> + <Unit filename="ui/switch_to_gui.h"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\sync_cfg.cpp"> + <Unit filename="ui/sync_cfg.cpp"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\sync_cfg.h"> + <Unit filename="ui/sync_cfg.h"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\taskbar.cpp" /> - <Unit filename="ui\taskbar.h" /> - <Unit filename="ui\tray_icon.cpp"> + <Unit filename="ui/taskbar.cpp" /> + <Unit filename="ui/taskbar.h" /> + <Unit filename="ui/tray_icon.cpp"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\tray_icon.h" /> - <Unit filename="ui\tree_view.cpp"> + <Unit filename="ui/tray_icon.h" /> + <Unit filename="ui/tree_view.cpp"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\tree_view.h"> + <Unit filename="ui/tree_view.h"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\triple_splitter.cpp"> + <Unit filename="ui/triple_splitter.cpp"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\triple_splitter.h"> + <Unit filename="ui/triple_splitter.h"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="ui\wx_form_build_hide_warnings.h"> + <Unit filename="ui/wx_form_build_hide_warnings.h"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="wx+\app_main.h"> + <Unit filename="wx+/app_main.h"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="wx+\button.cpp"> + <Unit filename="wx+/button.cpp"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="wx+\button.h"> + <Unit filename="wx+/button.h"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="wx+\choice_enum.h"> + <Unit filename="wx+/choice_enum.h"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="wx+\dir_picker.h"> + <Unit filename="wx+/dir_picker.h"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="wx+\file_drop.h"> + <Unit filename="wx+/file_drop.h"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="zen\format_unit.cpp" /> - <Unit filename="zen\format_unit.h"> + <Unit filename="wx+/graph.cpp"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="wx+\graph.cpp"> + <Unit filename="wx+/graph.h"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="wx+\graph.h"> + <Unit filename="wx+/grid.cpp"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="wx+\grid.cpp"> + <Unit filename="wx+/grid.h"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="wx+\grid.h"> + <Unit filename="wx+/image_tools.h"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="wx+\image_tools.h"> + <Unit filename="wx+/mouse_move_dlg.cpp"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="wx+\mouse_move_dlg.cpp"> + <Unit filename="wx+/mouse_move_dlg.h"> <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="wx+\mouse_move_dlg.h"> - <Option target="Release" /> - <Option target="Debug-DLL" /> - </Unit> - <Unit filename="wx+\pch.h"> + <Unit filename="wx+/pch.h"> <Option compile="1" /> <Option weight="0" /> <Option target="Debug-DLL" /> <Option target="Unit Test" /> </Unit> - <Unit filename="wx+\shell_execute.h"> - <Option target="Release" /> - <Option target="Debug-DLL" /> - </Unit> - <Unit filename="wx+\string_conv.h"> - <Option target="Release" /> - <Option target="Debug-DLL" /> - </Unit> - <Unit filename="wx+\toggle_button.h"> - <Option target="Release" /> - <Option target="Debug-DLL" /> - </Unit> - <Unit filename="wx+\tooltip.cpp"> - <Option target="Release" /> - <Option target="Debug-DLL" /> - </Unit> - <Unit filename="wx+\tooltip.h"> - <Option target="Release" /> - <Option target="Debug-DLL" /> - </Unit> - <Unit filename="wx+\zlib_wrap.cpp" /> - <Unit filename="wx+\zlib_wrap.h" /> - <Unit filename="zen\assert_static.h" /> - <Unit filename="zen\base64.h" /> - <Unit filename="zen\build_info.h" /> - <Unit filename="zen\com_error.h" /> - <Unit filename="zen\com_ptr.h" /> - <Unit filename="zen\com_util.h" /> - <Unit filename="zen\debug_log.h" /> - <Unit filename="zen\dir_watcher.h" /> - <Unit filename="zen\dll.h" /> - <Unit filename="zen\dst_hack.cpp" /> - <Unit filename="zen\dst_hack.h" /> - <Unit filename="zen\file_error.h" /> - <Unit filename="zen\file_handling.cpp" /> - <Unit filename="zen\file_handling.h" /> - <Unit filename="zen\file_id.cpp" /> - <Unit filename="zen\file_id.h" /> - <Unit filename="zen\file_io.cpp" /> - <Unit filename="zen\file_io.h" /> - <Unit filename="zen\file_traverser.cpp" /> - <Unit filename="zen\file_traverser.h" /> - <Unit filename="zen\fixed_list.h" /> - <Unit filename="zen\guid.h" /> - <Unit filename="zen\i18n.h" /> - <Unit filename="zen\int64.h" /> - <Unit filename="zen\last_error.h" /> - <Unit filename="zen\long_path_prefix.h" /> - <Unit filename="zen\notify_removal.h" /> - <Unit filename="zen\perf.h" /> - <Unit filename="zen\privilege.cpp" /> - <Unit filename="zen\privilege.h" /> - <Unit filename="zen\read_txt.h" /> - <Unit filename="zen\recycler.cpp" /> - <Unit filename="zen\recycler.h" /> - <Unit filename="zen\serialize.h" /> - <Unit filename="zen\stl_tools.h" /> - <Unit filename="zen\string_base.h" /> - <Unit filename="zen\string_tools.h" /> - <Unit filename="zen\string_traits.h" /> - <Unit filename="zen\symlink_target.h" /> - <Unit filename="zen\thread.h" /> - <Unit filename="zen\tick_count.h" /> - <Unit filename="zen\warn_static.h" /> - <Unit filename="zen\win.h" /> - <Unit filename="zen\zstring.cpp" /> - <Unit filename="zen\zstring.h" /> + <Unit filename="wx+/shell_execute.h"> + <Option target="Release" /> + <Option target="Debug-DLL" /> + </Unit> + <Unit filename="wx+/string_conv.h"> + <Option target="Release" /> + <Option target="Debug-DLL" /> + </Unit> + <Unit filename="wx+/toggle_button.h"> + <Option target="Release" /> + <Option target="Debug-DLL" /> + </Unit> + <Unit filename="wx+/tooltip.cpp"> + <Option target="Release" /> + <Option target="Debug-DLL" /> + </Unit> + <Unit filename="wx+/tooltip.h"> + <Option target="Release" /> + <Option target="Debug-DLL" /> + </Unit> + <Unit filename="wx+/zlib_wrap.cpp" /> + <Unit filename="wx+/zlib_wrap.h" /> + <Unit filename="zen/assert_static.h" /> + <Unit filename="zen/base64.h" /> + <Unit filename="zen/build_info.h" /> + <Unit filename="zen/com_error.h" /> + <Unit filename="zen/com_ptr.h" /> + <Unit filename="zen/com_util.h" /> + <Unit filename="zen/debug_log.h" /> + <Unit filename="zen/dir_watcher.h" /> + <Unit filename="zen/dll.h" /> + <Unit filename="zen/dst_hack.cpp" /> + <Unit filename="zen/dst_hack.h" /> + <Unit filename="zen/file_error.h" /> + <Unit filename="zen/file_handling.cpp" /> + <Unit filename="zen/file_handling.h" /> + <Unit filename="zen/file_id.cpp" /> + <Unit filename="zen/file_id.h" /> + <Unit filename="zen/file_io.cpp" /> + <Unit filename="zen/file_io.h" /> + <Unit filename="zen/file_traverser.cpp" /> + <Unit filename="zen/file_traverser.h" /> + <Unit filename="zen/fixed_list.h" /> + <Unit filename="zen/format_unit.cpp" /> + <Unit filename="zen/format_unit.h"> + <Option target="Release" /> + <Option target="Debug-DLL" /> + </Unit> + <Unit filename="zen/guid.h" /> + <Unit filename="zen/i18n.h" /> + <Unit filename="zen/int64.h" /> + <Unit filename="zen/last_error.h" /> + <Unit filename="zen/long_path_prefix.h" /> + <Unit filename="zen/notify_removal.h" /> + <Unit filename="zen/perf.h" /> + <Unit filename="zen/privilege.cpp" /> + <Unit filename="zen/privilege.h" /> + <Unit filename="zen/read_txt.h" /> + <Unit filename="zen/recycler.cpp" /> + <Unit filename="zen/recycler.h" /> + <Unit filename="zen/scroll_window_under_cursor.cpp"> + <Option target="Release" /> + <Option target="Debug-DLL" /> + </Unit> + <Unit filename="zen/serialize.h" /> + <Unit filename="zen/stl_tools.h" /> + <Unit filename="zen/string_base.h" /> + <Unit filename="zen/string_tools.h" /> + <Unit filename="zen/string_traits.h" /> + <Unit filename="zen/symlink_target.h" /> + <Unit filename="zen/thread.h" /> + <Unit filename="zen/tick_count.h" /> + <Unit filename="zen/warn_static.h" /> + <Unit filename="zen/win.h" /> + <Unit filename="zen/zstring.cpp" /> + <Unit filename="zen/zstring.h" /> <Extensions> <code_completion /> <envvars /> diff --git a/FreeFileSync.vcxproj b/FreeFileSync.vcxproj index 706250a5..5b5aeb49 100644 --- a/FreeFileSync.vcxproj +++ b/FreeFileSync.vcxproj @@ -114,7 +114,7 @@ <SubSystem>Windows</SubSystem> <GenerateDebugInformation>true</GenerateDebugInformation> <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> - <AdditionalDependencies>wxmsw28ud_aui.lib;wxmsw28ud_adv.lib;wxmsw28ud_core.lib;wxbase28ud_net.lib;wxbase28ud.lib;wxpngd.lib;wxzlibd.lib;comctl32.lib;ws2_32.lib;Rpcrt4.lib;winmm.lib;%(AdditionalDependencies)</AdditionalDependencies> + <AdditionalDependencies>wxmsw28ud_aui.lib;wxmsw28ud_adv.lib;wxmsw28ud_core.lib;wxbase28ud_net.lib;wxbase28ud.lib;wxpngd.lib;wxzlibd.lib;comctl32.lib;ws2_32.lib;Rpcrt4.lib;winmm.lib;%(AdditionalDependencies);Wininet.lib</AdditionalDependencies> <AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage\lib;C:\Program Files\C++\wxWidgets\lib\vc10_x86_debug_dll</AdditionalLibraryDirectories> </Link> <ResourceCompile> @@ -142,7 +142,7 @@ <SubSystem>Windows</SubSystem> <GenerateDebugInformation>true</GenerateDebugInformation> <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> - <AdditionalDependencies>wxbase28ud.lib;wxmsw28ud_core.lib;wxmsw28ud_adv.lib;wxmsw28ud_aui.lib;wxbase28ud_net.lib;wxpngd.lib;wxzlibd.lib;comctl32.lib;ws2_32.lib;Rpcrt4.lib;winmm.lib;%(AdditionalDependencies)</AdditionalDependencies> + <AdditionalDependencies>wxbase28ud.lib;wxmsw28ud_core.lib;wxmsw28ud_adv.lib;wxmsw28ud_aui.lib;wxbase28ud_net.lib;wxpngd.lib;wxzlibd.lib;comctl32.lib;ws2_32.lib;Rpcrt4.lib;winmm.lib;%(AdditionalDependencies);Wininet.lib</AdditionalDependencies> <AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage_x64\lib;C:\Program Files\C++\wxWidgets\lib\vc10_x64_debug_dll</AdditionalLibraryDirectories> <LinkStatus> </LinkStatus> @@ -173,7 +173,7 @@ <GenerateDebugInformation>false</GenerateDebugInformation> <EnableCOMDATFolding>true</EnableCOMDATFolding> <OptimizeReferences>true</OptimizeReferences> - <AdditionalDependencies>wxmsw28u_aui.lib;wxmsw28u_adv.lib;wxmsw28u_core.lib;wxbase28u.lib;wxpng.lib;wxzlib.lib;wxbase28u_net.lib;comctl32.lib;ws2_32.lib;winmm.lib;Rpcrt4.lib;%(AdditionalDependencies)</AdditionalDependencies> + <AdditionalDependencies>wxmsw28u_aui.lib;wxmsw28u_adv.lib;wxmsw28u_core.lib;wxbase28u.lib;wxpng.lib;wxzlib.lib;wxbase28u_net.lib;comctl32.lib;ws2_32.lib;winmm.lib;Rpcrt4.lib;%(AdditionalDependencies);Wininet.lib</AdditionalDependencies> <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> <AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage\lib;C:\Program Files\C++\wxWidgets\lib\vc10_x86_release_lib</AdditionalLibraryDirectories> </Link> @@ -204,7 +204,7 @@ <GenerateDebugInformation>false</GenerateDebugInformation> <EnableCOMDATFolding>true</EnableCOMDATFolding> <OptimizeReferences>true</OptimizeReferences> - <AdditionalDependencies>wxmsw28u_aui.lib;wxmsw28u_adv.lib;wxmsw28u_core.lib;wxbase28u.lib;wxpng.lib;wxzlib.lib;wxbase28u_net.lib;comctl32.lib;ws2_32.lib;winmm.lib;Rpcrt4.lib;%(AdditionalDependencies)</AdditionalDependencies> + <AdditionalDependencies>wxmsw28u_aui.lib;wxmsw28u_adv.lib;wxmsw28u_core.lib;wxbase28u.lib;wxpng.lib;wxzlib.lib;wxbase28u_net.lib;comctl32.lib;ws2_32.lib;winmm.lib;Rpcrt4.lib;%(AdditionalDependencies);Wininet.lib</AdditionalDependencies> <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> <AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage_x64\lib;C:\Program Files\C++\wxWidgets\lib\vc10_x64_release_lib</AdditionalLibraryDirectories> </Link> @@ -280,6 +280,7 @@ <ClCompile Include="zen\format_unit.cpp" /> <ClCompile Include="zen\privilege.cpp" /> <ClCompile Include="zen\recycler.cpp" /> + <ClCompile Include="zen\scroll_window_under_cursor.cpp" /> <ClCompile Include="zen\zstring.cpp" /> </ItemGroup> <ItemGroup> @@ -287,7 +288,7 @@ </ItemGroup> <ItemGroup> <None Include="WxWizFrame.fbp"> - <SubType>Designer</SubType> + <FileType>Document</FileType> </None> </ItemGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> diff --git a/RealtimeSync/RealtimeSync.cbp b/RealtimeSync/RealtimeSync.cbp index a171b55b..a5f82367 100644 --- a/RealtimeSync/RealtimeSync.cbp +++ b/RealtimeSync/RealtimeSync.cbp @@ -8,16 +8,16 @@ <Option compiler="gcc" /> <Build> <Target title="Release"> - <Option output="..\BUILD\Bin\RealtimeSync_Win32" prefix_auto="1" extension_auto="1" /> - <Option working_dir="..\BUILD" /> - <Option object_output="..\OBJ\RTS_Release_32_MinGW" /> + <Option output="../BUILD/Bin/RealtimeSync_Win32" prefix_auto="1" extension_auto="1" /> + <Option working_dir="../BUILD" /> + <Option object_output="../OBJ/RTS_Release_32_MinGW" /> <Option type="0" /> <Option compiler="gcc" /> <Option projectLinkerOptionsRelation="2" /> <Compiler> <Add option="-O3" /> <Add option="-DNDEBUG" /> - <Add directory="C:\Programme\C++\wxWidgets\lib\mingw_release_lib\mswu" /> + <Add directory="C:/Programme/C++/wxWidgets/lib/mingw_release_lib/mswu" /> </Compiler> <Linker> <Add option="-s" /> @@ -27,19 +27,18 @@ <Add library="libwxbase28u.a" /> <Add library="libwxpng.a" /> <Add library="libwxzlib.a" /> - <Add library="libboost_thread-mgw47-mt-s-1_51.a" /> - <Add library="libboost_system-mgw47-mt-s-1_51.a" /> - <Add library="libboost_chrono-mgw47-mt-s-1_51.a" /> - <Add directory="C:\Programme\C++\wxWidgets\lib\mingw_release_lib" /> + <Add library="libboost_thread-mgw47-mt-s-1_52.a" /> + <Add library="libboost_system-mgw47-mt-s-1_52.a" /> + <Add directory="C:/Programme/C++/wxWidgets/lib/mingw_release_lib" /> </Linker> <ExtraCommands> <Add after='"C:\Program Files\C++\CodeSigning\SignCode.cmd" "$(PROJECT_DIR)$(TARGET_OUTPUT_FILE)"' /> </ExtraCommands> </Target> <Target title="Debug-DLL"> - <Option output="..\BUILD\Bin\RealtimeSync_Debug" prefix_auto="1" extension_auto="1" /> - <Option working_dir="..\BUILD" /> - <Option object_output="..\OBJ\RTS_Debug_32_MinGW" /> + <Option output="../BUILD/Bin/RealtimeSync_Debug" prefix_auto="1" extension_auto="1" /> + <Option working_dir="../BUILD" /> + <Option object_output="../OBJ/RTS_Debug_32_MinGW" /> <Option type="0" /> <Option compiler="gcc" /> <Option projectLinkerOptionsRelation="2" /> @@ -48,7 +47,7 @@ <Add option="-Winvalid-pch" /> <Add option='-include "../wx+/pch.h"' /> <Add option="-D__WXDEBUG__" /> - <Add directory="C:\Program Files\C++\wxWidgets\lib\mingw_debug_dll\mswud" /> + <Add directory="C:/Program Files/C++/wxWidgets/lib/mingw_debug_dll/mswud" /> </Compiler> <Linker> <Add library="libwxmsw28ud_core.a" /> @@ -56,10 +55,9 @@ <Add library="libwxbase28ud.a" /> <Add library="libwxpngd.a" /> <Add library="libwxzlibd.a" /> - <Add library="libboost_thread-mgw47-mt-sd-1_51.a" /> - <Add library="libboost_system-mgw47-mt-sd-1_51.a" /> - <Add library="libboost_chrono-mgw47-mt-sd-1_51.a" /> - <Add directory="C:\Program Files\C++\wxWidgets\lib\mingw_debug_dll" /> + <Add library="libboost_thread-mgw47-mt-sd-1_52.a" /> + <Add library="libboost_system-mgw47-mt-sd-1_52.a" /> + <Add directory="C:/Program Files/C++/wxWidgets/lib/mingw_debug_dll" /> </Linker> </Target> </Build> @@ -83,12 +81,12 @@ <Add option="-DBOOST_THREAD_NO_LIB" /> <Add option="-DBOOST_THREAD_USE_LIB" /> <Add option="-DWXINTL_NO_GETTEXT_MACRO" /> - <Add directory="C:\Programme\C++\wxWidgets\include" /> - <Add directory="C:\Program Files\C++\Boost" /> + <Add directory="C:/Programme/C++/wxWidgets/include" /> + <Add directory="C:/Program Files/C++/Boost" /> <Add directory=".." /> </Compiler> <ResourceCompiler> - <Add directory="C:\Programme\C++\wxWidgets\include" /> + <Add directory="C:/Programme/C++/wxWidgets/include" /> </ResourceCompiler> <Linker> <Add option="-mthreads" /> @@ -104,32 +102,33 @@ <Add library="libwinspool.a" /> <Add library="libmpr.a" /> <Add library="libuxtheme.a" /> - <Add directory="C:\Program Files\C++\Boost\stage\lib" /> + <Add directory="C:/Program Files/C++/Boost/stage/lib" /> </Linker> - <Unit filename="..\lib\localization.cpp" /> - <Unit filename="..\lib\process_xml.cpp" /> - <Unit filename="..\lib\resolve_path.cpp" /> - <Unit filename="..\lib\xml_base.cpp" /> - <Unit filename="..\structures.cpp" /> - <Unit filename="..\ui\dir_name.cpp" /> - <Unit filename="..\ui\dir_name.h" /> - <Unit filename="..\ui\folder_history_box.cpp" /> - <Unit filename="..\ui\folder_history_box.h" /> - <Unit filename="..\wx+\button.cpp" /> - <Unit filename="..\wx+\mouse_move_dlg.cpp" /> - <Unit filename="..\wx+\pch.h"> + <Unit filename="../lib/localization.cpp" /> + <Unit filename="../lib/process_xml.cpp" /> + <Unit filename="../lib/resolve_path.cpp" /> + <Unit filename="../lib/xml_base.cpp" /> + <Unit filename="../structures.cpp" /> + <Unit filename="../ui/dir_name.cpp" /> + <Unit filename="../ui/dir_name.h" /> + <Unit filename="../ui/folder_history_box.cpp" /> + <Unit filename="../ui/folder_history_box.h" /> + <Unit filename="../wx+/button.cpp" /> + <Unit filename="../wx+/mouse_move_dlg.cpp" /> + <Unit filename="../wx+/pch.h"> <Option compile="1" /> <Option weight="0" /> <Option target="Debug-DLL" /> </Unit> - <Unit filename="..\zen\dir_watcher.cpp" /> - <Unit filename="..\zen\dst_hack.cpp" /> - <Unit filename="..\zen\file_handling.cpp" /> - <Unit filename="..\zen\file_io.cpp" /> - <Unit filename="..\zen\file_traverser.cpp" /> - <Unit filename="..\zen\notify_removal.cpp" /> - <Unit filename="..\zen\privilege.cpp" /> - <Unit filename="..\zen\zstring.cpp" /> + <Unit filename="../zen/dir_watcher.cpp" /> + <Unit filename="../zen/dst_hack.cpp" /> + <Unit filename="../zen/file_handling.cpp" /> + <Unit filename="../zen/file_io.cpp" /> + <Unit filename="../zen/file_traverser.cpp" /> + <Unit filename="../zen/notify_removal.cpp" /> + <Unit filename="../zen/privilege.cpp" /> + <Unit filename="../zen/scroll_window_under_cursor.cpp" /> + <Unit filename="../zen/zstring.cpp" /> <Unit filename="WxWizFrame.fbp" /> <Unit filename="application.cpp" /> <Unit filename="application.h" /> diff --git a/RealtimeSync/RealtimeSync.ico b/RealtimeSync/RealtimeSync.ico Binary files differindex c42a3029..1c5a3eb6 100644 --- a/RealtimeSync/RealtimeSync.ico +++ b/RealtimeSync/RealtimeSync.ico diff --git a/RealtimeSync/RealtimeSync.vcxproj b/RealtimeSync/RealtimeSync.vcxproj index 6505cc9a..764ec0a9 100644 --- a/RealtimeSync/RealtimeSync.vcxproj +++ b/RealtimeSync/RealtimeSync.vcxproj @@ -137,6 +137,7 @@ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> <SuppressStartupBanner>true</SuppressStartupBanner> <MinimalRebuild>false</MinimalRebuild> + <ShowIncludes>false</ShowIncludes> </ClCompile> <Link> <SubSystem>Windows</SubSystem> @@ -235,6 +236,7 @@ <ClCompile Include="..\zen\file_traverser.cpp" /> <ClCompile Include="..\zen\notify_removal.cpp" /> <ClCompile Include="..\zen\privilege.cpp" /> + <ClCompile Include="..\zen\scroll_window_under_cursor.cpp" /> <ClCompile Include="..\zen\zstring.cpp" /> <ClCompile Include="application.cpp"> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Use</PrecompiledHeader> diff --git a/RealtimeSync/application.cpp b/RealtimeSync/application.cpp index 172a8cac..435a6b86 100644 --- a/RealtimeSync/application.cpp +++ b/RealtimeSync/application.cpp @@ -18,7 +18,7 @@ #include "lib/error_log.h" #ifdef FFS_WIN -#include <zen/win_ver.h> +#include <zen/win_ver.h> #elif defined FFS_LINUX #include <gtk/gtk.h> #endif @@ -55,13 +55,12 @@ bool Application::OnInit() { #ifdef FFS_WIN std::set_terminate(onTerminationRequested); //unlike wxWidgets uncaught exception handling, this works for all worker threads + assert(!win8OrLater()); //another breadcrumb: test and add new OS entry to "compatibility" in application manifest #ifdef _MSC_VER _set_invalid_parameter_handler(crtInvalidParameterHandler); //see comment in <zen/time.h> #endif #endif - assert(!win8OrLater()); //another breadcrumb: test and add new OS entry to "compatibility" in application manifest - //do not call wxApp::OnInit() to avoid using default commandline parser //Note: initialization is done in the FIRST idle event instead of OnInit. Reason: Commandline mode requires the wxApp eventhandler to be established diff --git a/RealtimeSync/gui_generated.cpp b/RealtimeSync/gui_generated.cpp index 6dad5266..4651099c 100644 --- a/RealtimeSync/gui_generated.cpp +++ b/RealtimeSync/gui_generated.cpp @@ -1,10 +1,12 @@ /////////////////////////////////////////////////////////////////////////// -// C++ code generated with wxFormBuilder (version Apr 10 2012) +// C++ code generated with wxFormBuilder (version Oct 8 2012) // http://www.wxformbuilder.org/ // // PLEASE DO "NOT" EDIT THIS FILE! /////////////////////////////////////////////////////////////////////////// +#include "../wx+/button.h" + #include "gui_generated.h" /////////////////////////////////////////////////////////////////////////// @@ -117,6 +119,7 @@ MainDlgGenerated::MainDlgGenerated( wxWindow* parent, wxWindowID id, const wxStr bSizer781->Add( m_bpButtonRemoveTopFolder, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); m_txtCtrlDirectoryMain = new wxTextCtrl( m_panelMainFolder, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( 300,-1 ), 0 ); + m_txtCtrlDirectoryMain->SetMaxLength( 0 ); bSizer781->Add( m_txtCtrlDirectoryMain, 1, wxALIGN_CENTER_VERTICAL, 5 ); m_buttonSelectDirMain = new wxButton( m_panelMainFolder, wxID_ANY, _("Browse"), wxDefaultPosition, wxDefaultSize, 0 ); @@ -161,6 +164,7 @@ MainDlgGenerated::MainDlgGenerated( wxWindow* parent, wxWindowID id, const wxStr sbSizer3 = new wxStaticBoxSizer( new wxStaticBox( m_panelMain, wxID_ANY, _("Command line") ), wxVERTICAL ); m_textCtrlCommand = new wxTextCtrl( m_panelMain, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 ); + m_textCtrlCommand->SetMaxLength( 0 ); m_textCtrlCommand->SetToolTip( _("The command is triggered if:\n- files or subfolders change\n- new folders arrive (e.g. USB stick insert)") ); sbSizer3->Add( m_textCtrlCommand, 0, wxEXPAND, 5 ); @@ -231,6 +235,7 @@ FolderGenerated::FolderGenerated( wxWindow* parent, wxWindowID id, const wxPoint bSizer20 = new wxBoxSizer( wxHORIZONTAL ); m_txtCtrlDirectory = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 ); + m_txtCtrlDirectory->SetMaxLength( 0 ); bSizer20->Add( m_txtCtrlDirectory, 1, wxALIGN_CENTER_VERTICAL, 5 ); m_buttonSelectDir = new wxButton( this, wxID_ANY, _("Browse"), wxDefaultPosition, wxDefaultSize, 0 ); @@ -269,6 +274,7 @@ ErrorDlgGenerated::ErrorDlgGenerated( wxWindow* parent, wxWindowID id, const wxS bSizer26->Add( m_bitmap10, 0, wxBOTTOM|wxRIGHT|wxLEFT, 5 ); m_textCtrl8 = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( 400,150 ), wxTE_MULTILINE|wxTE_READONLY|wxNO_BORDER ); + m_textCtrl8->SetMaxLength( 0 ); bSizer26->Add( m_textCtrl8, 1, wxEXPAND|wxALIGN_CENTER_VERTICAL, 5 ); diff --git a/RealtimeSync/gui_generated.h b/RealtimeSync/gui_generated.h index 1f475454..aa267a02 100644 --- a/RealtimeSync/gui_generated.h +++ b/RealtimeSync/gui_generated.h @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////// -// C++ code generated with wxFormBuilder (version Apr 10 2012) +// C++ code generated with wxFormBuilder (version Oct 8 2012) // http://www.wxformbuilder.org/ // // PLEASE DO "NOT" EDIT THIS FILE! @@ -11,7 +11,8 @@ #include <wx/artprov.h> #include <wx/xrc/xmlres.h> #include <wx/intl.h> -#include "../wx+/button.h" +namespace zen { class BitmapButton; } + #include <wx/string.h> #include <wx/bitmap.h> #include <wx/image.h> diff --git a/RealtimeSync/tray_menu.cpp b/RealtimeSync/tray_menu.cpp index af37d32e..a30081bf 100644 --- a/RealtimeSync/tray_menu.cpp +++ b/RealtimeSync/tray_menu.cpp @@ -305,6 +305,22 @@ bool reportErrorTimeout(const std::wstring& msg) //return true if timeout or use } return false; } + + +inline +wxString toString(DirWatcher::ActionType type) +{ + switch (type) + { + case DirWatcher::ACTION_CREATE: + return L"CREATE"; + case DirWatcher::ACTION_UPDATE: + return L"UPDATE"; + case DirWatcher::ACTION_DELETE: + return L"DELETE"; + } + return L"ERROR"; +} } /* @@ -341,7 +357,7 @@ rts::AbortReason rts::startDirectoryMonitor(const xmlAccess::XmlRealConfig& conf try { - Zstring lastFileChanged; + DirWatcher::Entry lastChangeDetected; WaitCallbackImpl callback(jobname); auto execMonitoring = [&] //throw FileError, AbortMonitoring @@ -372,7 +388,7 @@ rts::AbortReason rts::startDirectoryMonitor(const xmlAccess::XmlRealConfig& conf break; case CHANGE_DETECTED: - lastFileChanged = res.filename; + lastChangeDetected = res.changedItem_; break; } callback.scheduleNextSync(wxGetLocalTime() + static_cast<long>(config.delay)); @@ -380,8 +396,9 @@ rts::AbortReason rts::startDirectoryMonitor(const xmlAccess::XmlRealConfig& conf } catch (StartSyncNowException&) {} - ::wxSetEnv(L"changed_file", utfCvrtTo<wxString>(lastFileChanged)); //some way to output what file changed to the user - lastFileChanged.clear(); //make sure old name is not shown again after a directory reappears + ::wxSetEnv(L"change_path", utfCvrtTo<wxString>(lastChangeDetected.filename_)); //some way to output what file changed to the user + ::wxSetEnv(L"change_action", toString(lastChangeDetected.action_)); // + lastChangeDetected = DirWatcher::Entry(); //make sure old name is not shown again after a directory reappears //execute command auto cmdLineExp = utfCvrtTo<wxString>(expandMacros(utfCvrtTo<Zstring>(cmdLine))); diff --git a/RealtimeSync/watcher.cpp b/RealtimeSync/watcher.cpp index e66b4723..bfdb79c2 100644 --- a/RealtimeSync/watcher.cpp +++ b/RealtimeSync/watcher.cpp @@ -75,18 +75,18 @@ rts::WaitResult rts::waitForChanges(const std::vector<Zstring>& dirNamesNonFmt, while (!ftDirExists.timed_wait(boost::posix_time::milliseconds(UI_UPDATE_INTERVAL))) statusHandler.requestUiRefresh(); //may throw! if (!ftDirExists.get()) - return WaitResult(CHANGE_DIR_MISSING, dirnameFmt); + return WaitResult(dirnameFmt); watches.push_back(std::make_pair(dirnameFmt, std::make_shared<DirWatcher>(dirnameFmt))); //throw FileError, ErrorNotExisting } catch (ErrorNotExisting&) //nice atomic behavior: *no* second directory existence check!!! { - return WaitResult(CHANGE_DIR_MISSING, dirnameFmt); + return WaitResult(dirnameFmt); } catch (FileError&) //play safe: remedy potential FileErrors that should have been ErrorNotExisting (e.g. Linux: errors during directory traversing) { if (!dirExists(dirnameFmt)) //file system race condition!! - return WaitResult(CHANGE_DIR_MISSING, dirnameFmt); + return WaitResult(dirnameFmt); throw; } } @@ -115,38 +115,38 @@ rts::WaitResult rts::waitForChanges(const std::vector<Zstring>& dirNamesNonFmt, //IMPORTANT CHECK: dirwatcher has problems detecting removal of top watched directories! if (checkDirExistNow) if (!dirExists(dirname)) //catch errors related to directory removal, e.g. ERROR_NETNAME_DELETED - return WaitResult(CHANGE_DIR_MISSING, dirname); + return WaitResult(dirname); try { - std::vector<Zstring> changedFiles = watcher.getChanges([&] { statusHandler.requestUiRefresh(); }); //throw FileError, ErrorNotExisting + std::vector<DirWatcher::Entry> changedItems = watcher.getChanges([&] { statusHandler.requestUiRefresh(); }); //throw FileError, ErrorNotExisting //remove to be ignored changes - vector_remove_if(changedFiles, [](const Zstring& name) + vector_remove_if(changedItems, [](const DirWatcher::Entry& e) { - return endsWith(name, Zstr(".ffs_lock")) || //sync.ffs_lock, sync.Del.ffs_lock - endsWith(name, Zstr(".ffs_db")); //sync.ffs_db, .sync.tmp.ffs_db + return endsWith(e.filename_, Zstr(".ffs_lock")) || //sync.ffs_lock, sync.Del.ffs_lock + endsWith(e.filename_, Zstr(".ffs_db")); //sync.ffs_db, .sync.tmp.ffs_db //no need to ignore temporal recycle bin directory: this must be caused by a file deletion anyway }); - if (!changedFiles.empty()) + if (!changedItems.empty()) { /* - std::for_each(changedFiles.begin(), changedFiles.end(), + std::for_each(changedItems.begin(), changedItems.end(), [](const Zstring& fn) { wxMessageBox(toWx(fn));}); */ - return WaitResult(CHANGE_DETECTED, changedFiles[0]); //directory change detected + return WaitResult(changedItems[0]); //directory change detected } } catch (ErrorNotExisting&) //nice atomic behavior: *no* second directory existence check!!! { - return WaitResult(CHANGE_DIR_MISSING, dirname); + return WaitResult(dirname); } catch (FileError&) //play safe: remedy potential FileErrors that should have been ErrorNotExisting (e.g. Linux: errors during directory traversing) { if (!dirExists(dirname)) //file system race condition!! - return WaitResult(CHANGE_DIR_MISSING, dirname); + return WaitResult(dirname); throw; } } diff --git a/RealtimeSync/watcher.h b/RealtimeSync/watcher.h index 014101da..2fd32119 100644 --- a/RealtimeSync/watcher.h +++ b/RealtimeSync/watcher.h @@ -7,8 +7,7 @@ #ifndef WATCHER_H_INCLUDED #define WATCHER_H_INCLUDED -#include <zen/zstring.h> -#include <vector> +#include <zen/dir_watcher.h> #include <zen/file_error.h> @@ -35,10 +34,12 @@ enum ChangeType struct WaitResult { - WaitResult(ChangeType tp, const Zstring& chgFile) : type(tp), filename(chgFile) {} + WaitResult(const zen::DirWatcher::Entry& changedItem) : type(CHANGE_DETECTED), changedItem_(changedItem) {} + WaitResult(const Zstring& dirname) : type(CHANGE_DIR_MISSING), dirname_(dirname) {} ChangeType type; - Zstring filename; //file or directory name + zen::DirWatcher::Entry changedItem_; //for type == CHANGE_DETECTED: file or directory + Zstring dirname_; //for type == CHANGE_DIR_MISSING }; WaitResult waitForChanges(const std::vector<Zstring>& dirNamesNonFmt, diff --git a/file_hierarchy.cpp b/file_hierarchy.cpp index bf557655..35abf251 100644 --- a/file_hierarchy.cpp +++ b/file_hierarchy.cpp @@ -42,6 +42,8 @@ SyncOperation getIsolatedSyncOperation(CompareFilesResult cmpResult, SyncDirection syncDir, bool hasDirConflict) //perf: std::wstring was wasteful here { + assert(!hasDirConflict || syncDir == SYNC_DIR_NONE); + if (!selectedForSynchronization) return cmpResult == FILE_EQUAL ? SO_EQUAL : diff --git a/file_hierarchy.h b/file_hierarchy.h index 4139a53a..5a5b5818 100644 --- a/file_hierarchy.h +++ b/file_hierarchy.h @@ -86,8 +86,8 @@ class FileMapping; class SymLinkMapping; class FileSystemObject; - //------------------------------------------------------------------ + /* ERD: DirContainer 1 --> 0..n DirContainer @@ -217,8 +217,8 @@ private: BaseDirMapping& root_; }; - //------------------------------------------------------------------ + class BaseDirMapping : public HierarchyObject //synchronization base directory { public: @@ -430,6 +430,8 @@ private: std::unique_ptr<std::wstring> syncDirConflict; //non-empty if we have a conflict setting sync-direction //get rid of std::wstring small string optimization (consumes 32/48 byte on VS2010 x86/x64!) + //Note: we model *four* states with last two variables => "syncDirConflict is empty or syncDir == NONE" is a class invariant!!! + Zstring shortNameLeft_; //slightly redundant under linux, but on windows the "same" filenames can differ in case Zstring shortNameRight_; //use as indicator: an empty name means: not existing! diff --git a/lib/db_file.cpp b/lib/db_file.cpp index 8821fc66..53830afe 100644 --- a/lib/db_file.cpp +++ b/lib/db_file.cpp @@ -299,8 +299,8 @@ public: BinStreamIn& in1stPart = has1stPartL ? inL : inR; BinStreamIn& in2ndPart = has1stPartL ? inR : inL; - const size_t size1stPart = readNumber<std::uint64_t>(in1stPart); - const size_t size2ndPart = readNumber<std::uint64_t>(in2ndPart); + const size_t size1stPart = static_cast<size_t>(readNumber<std::uint64_t>(in1stPart)); + const size_t size2ndPart = static_cast<size_t>(readNumber<std::uint64_t>(in2ndPart)); BinaryStream tmpB; tmpB.resize(size1stPart + size2ndPart); diff --git a/lib/dir_lock.cpp b/lib/dir_lock.cpp index da119f88..bbd97454 100644 --- a/lib/dir_lock.cpp +++ b/lib/dir_lock.cpp @@ -552,7 +552,7 @@ public: ~SharedDirLock() { threadObj.interrupt(); //thread lifetime is subset of this instances's life - threadObj.join(); + threadObj.join(); //we assume precondition "threadObj.joinable()"!!! ::releaseLock(lockfilename_); //throw () } diff --git a/lib/generate_logfile.h b/lib/generate_logfile.h index 651b93cb..beb4f5d3 100644 --- a/lib/generate_logfile.h +++ b/lib/generate_logfile.h @@ -57,7 +57,9 @@ Utf8String generateLogStream_impl(const ErrorLog& log, results.push_back(headerLine); results.push_back(L""); - std::wstring itemsProc = L" " + _("Items processed:") + L" " + toGuiString(itemsSynced); //show always, even if 0! + const wchar_t tabSpace[] = L" "; + + std::wstring itemsProc = tabSpace + _("Items processed:") + L" " + toGuiString(itemsSynced); //show always, even if 0! if (itemsSynced != 0 || dataSynced != 0) //[!] don't show 0 bytes processed if 0 items were processed itemsProc += + L" (" + filesizeToShortString(dataSynced) + L")"; results.push_back(itemsProc); @@ -66,10 +68,10 @@ Utf8String generateLogStream_impl(const ErrorLog& log, { if (itemsSynced != itemsTotal || dataSynced != dataTotal) - results.push_back(L" " + _("Items remaining:") + L" " + toGuiString(itemsTotal - itemsSynced) + L" (" + filesizeToShortString(dataTotal - dataSynced) + L")"); + results.push_back(tabSpace + _("Items remaining:") + L" " + toGuiString(itemsTotal - itemsSynced) + L" (" + filesizeToShortString(dataTotal - dataSynced) + L")"); } - results.push_back(L" " + _("Total time:") + L" " + copyStringTo<std::wstring>(wxTimeSpan::Seconds(totalTime).Format())); + results.push_back(tabSpace + _("Total time:") + L" " + copyStringTo<std::wstring>(wxTimeSpan::Seconds(totalTime).Format())); //calculate max width, this considers UTF-16 only, not true Unicode... size_t sepLineLen = 0; @@ -132,10 +134,10 @@ void saveToLastSyncsLog(const Utf8String& logstream) //throw FileError const size_t newSize = std::min(newStream.size(), std::max<size_t>(logstream.size(), 128 * 1024)); //do not cut in the middle of a row - auto iter = std::search(newStream.begin() + newSize, newStream.end(), std::begin(LINE_BREAK), std::end(LINE_BREAK) - 1); - if (iter != newStream.end()) + auto iter = std::search(newStream.cbegin() + newSize, newStream.cend(), std::begin(LINE_BREAK), std::end(LINE_BREAK) - 1); + if (iter != newStream.cend()) { - newStream.resize(iter - newStream.begin()); + newStream.resize(iter - newStream.cbegin()); newStream += LINE_BREAK; newStream += "[...]"; diff --git a/lib/icon_buffer.cpp b/lib/icon_buffer.cpp index 60f6cbe4..9274a0b4 100644 --- a/lib/icon_buffer.cpp +++ b/lib/icon_buffer.cpp @@ -537,7 +537,7 @@ IconBuffer::~IconBuffer() { setWorkload(std::vector<Zstring>()); //make sure interruption point is always reached! pimpl->worker.interrupt(); - pimpl->worker.join(); + pimpl->worker.join(); //we assume precondition "worker.joinable()"!!! } diff --git a/lib/lock_holder.h b/lib/lock_holder.h index d94b0fd6..6265747b 100644 --- a/lib/lock_holder.h +++ b/lib/lock_holder.h @@ -40,6 +40,8 @@ public: try { + //lock file creation is synchronous and may block noticably for very slow devices (usb sticks, mapped cloud storages) + procCallback.forceUiRefresh(); //=> make sure the right folder name is shown on GUI during this time! lockHolder.insert(std::make_pair(dirnameFmt, DirLock(dirnameFmt + Zstr("sync") + LOCK_FILE_ENDING, &callback))); } catch (const FileError& e) diff --git a/lib/parallel_scan.cpp b/lib/parallel_scan.cpp index e8b13003..b9d29699 100644 --- a/lib/parallel_scan.cpp +++ b/lib/parallel_scan.cpp @@ -539,7 +539,11 @@ void zen::fillBuffer(const std::set<DirectoryKey>& keysToRead, //in zen::ScopeGuard guardWorker = zen::makeGuard([&] { std::for_each(worker.begin(), worker.end(), [](boost::thread& wt) { wt.interrupt(); }); //interrupt all at once, then join - std::for_each(worker.begin(), worker.end(), [](boost::thread& wt) { wt.join(); }); + std::for_each(worker.begin(), worker.end(), [](boost::thread& wt) + { + if (wt.joinable()) //this is a precondition of thread::join()!!! Latter will throw an exception if violated! + wt.join(); //in this context it is possible a thread is *not* joinable anymore due to the thread::timed_join() below! + }); }); std::shared_ptr<AsyncCallback> acb = std::make_shared<AsyncCallback>(); diff --git a/lib/process_xml.h b/lib/process_xml.h index ec1a142f..d21f7ffc 100644 --- a/lib/process_xml.h +++ b/lib/process_xml.h @@ -57,17 +57,19 @@ struct XmlGuiConfig bool showFilteredElements; OnGuiError handleError; //reaction on error situation during synchronization bool showSyncAction; - - bool operator==(const XmlGuiConfig& other) const - { - return mainCfg == other.mainCfg && - showFilteredElements == other.showFilteredElements && - handleError == other.handleError && - showSyncAction == other.showSyncAction; - } }; +inline +bool operator==(const XmlGuiConfig& lhs, const XmlGuiConfig& rhs) +{ + return lhs.mainCfg == rhs.mainCfg && + lhs.showFilteredElements == rhs.showFilteredElements && + lhs.handleError == rhs.handleError && + lhs.showSyncAction == rhs.showSyncAction; +} + + struct XmlBatchConfig { XmlBatchConfig() : diff --git a/lib/resolve_path.cpp b/lib/resolve_path.cpp index 11733136..0b8e80b9 100644 --- a/lib/resolve_path.cpp +++ b/lib/resolve_path.cpp @@ -20,6 +20,7 @@ #elif defined FFS_LINUX #include <zen/file_traverser.h> #include <unistd.h> +#include <stdlib.h> //getenv() #endif using namespace zen; @@ -46,11 +47,30 @@ Zstring resolveRelativePath(const Zstring& relativeName) //note: ::GetFullPathNa #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 '/' { + /* + basic support for '~': strictly speaking this is a shell-layer feature, so "realpath()" won't handle it + http://www.gnu.org/software/bash/manual/html_node/Tilde-Expansion.html + + http://linux.die.net/man/3/getpwuid: An application that wants to determine its user's home directory + should inspect the value of HOME (rather than the value getpwuid(getuid())->pw_dir) since this allows + the user to modify their notion of "the home directory" during a login session. + */ + if (startsWith(relativeName, "~/") || relativeName == "~") + { + const char* homeDir = ::getenv("HOME"); + if (!homeDir) + return relativeName; //error! no further processing! + + if (startsWith(relativeName, "~/")) + return appendSeparator(homeDir) + afterFirst(relativeName, '/'); + else if (relativeName == "~") + return homeDir; + } + + //unfortunately ::realpath only resolves *existing* relative paths, so we need to do it by ourselves std::vector<char> buffer(10000); if (::getcwd(&buffer[0], buffer.size()) != nullptr) return appendSeparator(&buffer[0]) + relativeName; @@ -426,9 +446,9 @@ Zstring expandVolumeName(const Zstring& text) // [volname]:\folder [volna } -#ifdef FFS_WIN void getDirectoryAliasesRecursive(const Zstring& dirname, std::set<Zstring, LessFilename>& output) { +#ifdef FFS_WIN //1. replace volume path by volume name: c:\dirname -> [SYSTEM]\dirname if (dirname.size() >= 3 && std::iswalpha(dirname[0]) && @@ -447,6 +467,7 @@ void getDirectoryAliasesRecursive(const Zstring& dirname, std::set<Zstring, Less if (output.insert(testVolname).second) getDirectoryAliasesRecursive(testVolname, output); //recurse! } +#endif //3. environment variables: C:\Users\username -> %USERPROFILE% { @@ -458,6 +479,7 @@ void getDirectoryAliasesRecursive(const Zstring& dirname, std::set<Zstring, Less if (std::unique_ptr<Zstring> value = getEnvironmentVar(envName)) envToDir.insert(std::make_pair(envName, *value)); }; +#ifdef FFS_WIN addEnvVar(L"AllUsersProfile"); // C:\ProgramData addEnvVar(L"AppData"); // C:\Users\username\AppData\Roaming addEnvVar(L"LocalAppData"); // C:\Users\username\AppData\Local @@ -475,16 +497,27 @@ void getDirectoryAliasesRecursive(const Zstring& dirname, std::set<Zstring, Less const auto& csidlMap = CsidlConstants::get(); envToDir.insert(csidlMap.begin(), csidlMap.end()); +#elif defined FFS_LINUX + addEnvVar("HOME"); // /home/zenju +#endif //substitute paths by symbolic names - Zstring tmp = dirname; - ::makeUpper(tmp); + auto pathStartsWith = [](const Zstring& path, const Zstring& prefix) -> bool + { +#ifdef FFS_WIN + Zstring tmp = path; + Zstring tmp2 = prefix; + ::makeUpper(tmp); + ::makeUpper(tmp2); + return startsWith(tmp, tmp2); +#elif defined FFS_LINUX + return startsWith(path, prefix); +#endif + }; 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(MACRO_SEP + entry.first + MACRO_SEP + (dirname.c_str() + tmp2.size())); + if (pathStartsWith(dirname, entry.second)) + output.insert(MACRO_SEP + entry.first + MACRO_SEP + (dirname.c_str() + entry.second.size())); }); } @@ -513,7 +546,6 @@ std::vector<Zstring> zen::getDirectoryAliases(const Zstring& dirString) return std::vector<Zstring>(tmp.begin(), tmp.end()); } -#endif Zstring zen::getFormattedDirectoryName(const Zstring& dirString) // throw() diff --git a/lib/resolve_path.h b/lib/resolve_path.h index 0132b51f..975ed304 100644 --- a/lib/resolve_path.h +++ b/lib/resolve_path.h @@ -24,11 +24,11 @@ Zstring getFormattedDirectoryName(const Zstring& dirString); //throw() - non-blo //macro substitution only Zstring expandMacros(const Zstring& text); +std::vector<Zstring> getDirectoryAliases(const Zstring& dirString); + #ifdef FFS_WIN //*blocks* if network is not reachable or when showing login prompt dialog! void loginNetworkShare(const Zstring& dirname, bool allowUserInteraction); //throw() - user interaction: show OS password prompt - -std::vector<Zstring> getDirectoryAliases(const Zstring& dirString); #endif } diff --git a/lib/return_codes.h b/lib/return_codes.h index 0bd98a41..6742c975 100644 --- a/lib/return_codes.h +++ b/lib/return_codes.h @@ -12,6 +12,7 @@ namespace zen enum FfsReturnCode { FFS_RC_SUCCESS = 0, + FFS_RC_FINISHED_WITH_WARNINGS, FFS_RC_FINISHED_WITH_ERRORS, FFS_RC_ABORTED, FFS_RC_EXCEPTION, diff --git a/lib/status_handler.h b/lib/status_handler.h index 789293e4..93f9892c 100644 --- a/lib/status_handler.h +++ b/lib/status_handler.h @@ -75,8 +75,8 @@ protected: if (updateUiIsAllowed()) //test if specific time span between ui updates is over forceUiRefresh(); - if (abortRequested) - abortThisProcess(); //abort can be triggered by requestAbortion() + if (abortRequested) //check *after* GUI update, to have up-to-date screen + abortThisProcess(); //triggered by requestAbortion() } virtual void reportStatus(const std::wstring& text) { statusText_ = text; requestUiRefresh(); /*throw AbortThisProcess */ } diff --git a/structures.h b/structures.h index ccf87fa6..0f4695f2 100644 --- a/structures.h +++ b/structures.h @@ -43,9 +43,9 @@ enum CompareFilesResult { FILE_LEFT_SIDE_ONLY = 0, FILE_RIGHT_SIDE_ONLY, - FILE_LEFT_NEWER, - FILE_RIGHT_NEWER, - FILE_DIFFERENT, + FILE_LEFT_NEWER, //CMP_BY_TIME_SIZE only! + FILE_RIGHT_NEWER, // + FILE_DIFFERENT, //CMP_BY_CONTENT only! FILE_EQUAL, FILE_DIFFERENT_METADATA, //both sides equal, but different metadata only: short name case, modification time FILE_CONFLICT @@ -104,18 +104,18 @@ std::wstring getSymbol (SyncOperation op); //method used for exporting .csv struct DirectionSet { DirectionSet() : - exLeftSideOnly( SYNC_DIR_RIGHT), + exLeftSideOnly (SYNC_DIR_RIGHT), exRightSideOnly(SYNC_DIR_LEFT), - leftNewer( SYNC_DIR_RIGHT), - rightNewer( SYNC_DIR_LEFT), - different( SYNC_DIR_NONE), - conflict( SYNC_DIR_NONE) {} + leftNewer (SYNC_DIR_RIGHT), + rightNewer (SYNC_DIR_LEFT), + different (SYNC_DIR_NONE), + conflict (SYNC_DIR_NONE) {} SyncDirection exLeftSideOnly; SyncDirection exRightSideOnly; - SyncDirection leftNewer; - SyncDirection rightNewer; - SyncDirection different; + SyncDirection leftNewer; //CMP_BY_TIME_SIZE only! + SyncDirection rightNewer; // + SyncDirection different; //CMP_BY_CONTENT only! SyncDirection conflict; }; @@ -166,11 +166,6 @@ std::wstring getVariantName(DirectionConfig::Variant var); struct CompConfig { - //CompConfig(CompareVariant cmpVar, - // SymLinkHandling handleSyml) : - // compareVar(cmpVar), - // handleSymlinks(handleSyml) {} - CompConfig() : compareVar(CMP_BY_TIME_SIZE), handleSymlinks(SYMLINK_IGNORE) {} @@ -385,8 +380,8 @@ inline bool operator==(const MainConfiguration& lhs, const MainConfiguration& rhs) { return lhs.cmpConfig == rhs.cmpConfig && - lhs.globalFilter == rhs.globalFilter && lhs.syncCfg == rhs.syncCfg && + lhs.globalFilter == rhs.globalFilter && lhs.firstPair == rhs.firstPair && lhs.additionalPairs == rhs.additionalPairs && lhs.onCompletion == rhs.onCompletion; diff --git a/synchronization.cpp b/synchronization.cpp index dcc119a5..a70aad30 100644 --- a/synchronization.cpp +++ b/synchronization.cpp @@ -431,10 +431,19 @@ public: size_t folderIndex, const Zstring& baseDirPf, //with separator postfix ProcessCallback& procCallback); - ~DeletionHandling() { try { tryCleanup(); } catch (...) {} /*make sure this stays non-blocking!*/ } //always (try to) clean up, even if synchronization is aborted! + ~DeletionHandling() + { + try { tryCleanup(false); } //always (try to) clean up, even if synchronization is aborted! + catch (...) {} + /* + do not allow user callback: + - make sure this stays non-blocking! + - avoid throwing user abort exception again, leading to incomplete clean-up! + */ + } //clean-up temporary directory (recycle bin optimization) - void tryCleanup(); //throw FileError -> call this in non-exceptional coding, i.e. somewhere after sync! + void tryCleanup(bool allowUserCallback = true); //throw FileError -> call this in non-exceptional coding, i.e. somewhere after sync! void removeFile (const Zstring& relativeName); //throw FileError void removeFolder(const Zstring& relativeName) { removeFolderInt(relativeName, nullptr, nullptr); }; //throw FileError @@ -470,9 +479,11 @@ private: const Zstring versioningDir_; const TimeComp timeStamp_; const int versionCountLimit_; - Zstring recyclerTmpDirPf; //temporary folder to move files to, before moving whole folder to recycler (postfixed with file name separator) #ifdef FFS_WIN + Zstring recyclerTmpDirPf; //temporary folder holding files/folders for *deferred* recycling (postfixed with file name separator) + std::vector<Zstring> toBeRecycled; //full path of files located in temporary folder, waiting to be recycled + bool recFallbackDelPermantently; #endif @@ -514,7 +525,6 @@ DeletionHandling::DeletionHandling(DeletionPolicy handleDel, //nothrow! handleDel = DELETE_PERMANENTLY; //Windows' ::SHFileOperation() will do this anyway, but we have a better and faster deletion routine (e.g. on networks) recFallbackDelPermantently = true; } -#endif //assemble temporary recycler bin directory if (!baseDirPf_.empty()) @@ -525,6 +535,7 @@ DeletionHandling::DeletionHandling(DeletionPolicy handleDel, //nothrow! recyclerTmpDirPf = appendSeparator(tempDir); } +#endif setDeletionPolicy(handleDel); } @@ -556,8 +567,33 @@ void DeletionHandling::setDeletionPolicy(DeletionPolicy newPolicy) } } +#ifdef FFS_WIN +namespace +{ +class CallbackMassRecycling : public CallbackRecycling +{ +public: + CallbackMassRecycling(ProcessCallback& statusHandler) : + statusHandler_(statusHandler), + txtRecyclingFile(_("Moving file %x to recycle bin")) {} + + //may throw: first exception is swallowed, updateStatus() is then called again where it should throw again and the exception will propagate as expected + virtual void updateStatus(const Zstring& currentItem) + { + if (!currentItem.empty()) + statusHandler_.reportStatus(replaceCpy(txtRecyclingFile, L"%x", fmtFileName(currentItem))); //throw ? + else + statusHandler_.requestUiRefresh(); //throw ? + } + +private: + ProcessCallback& statusHandler_; + const std::wstring txtRecyclingFile; +}; +} +#endif -void DeletionHandling::tryCleanup() //throw FileError +void DeletionHandling::tryCleanup(bool allowUserCallback) //throw FileError { if (!cleanedUp) { @@ -567,16 +603,29 @@ void DeletionHandling::tryCleanup() //throw FileError break; case DELETE_TO_RECYCLER: - //clean-up temporary directory (recycle bin) - if (!recyclerTmpDirPf.empty()) //folder input pair may be empty - recycleOrDelete(beforeLast(recyclerTmpDirPf, FILE_NAME_SEPARATOR)); //throw FileError +#ifdef FFS_WIN + if (!recyclerTmpDirPf.empty()) + { + //move content of temporary directory to recycle bin in a single call + CallbackMassRecycling cbmr(procCallback_); + recycleOrDelete(toBeRecycled, allowUserCallback ? &cbmr : nullptr); //throw FileError + + //clean up temp directory itself (should contain remnant empty directories only) + removeDirectory(beforeLast(recyclerTmpDirPf, FILE_NAME_SEPARATOR)); //throw FileError + } +#endif break; case DELETE_TO_VERSIONING: if (versioner.get()) { - procCallback_.reportStatus(_("Removing old versions...")); - versioner->limitVersions([&] { procCallback_.requestUiRefresh(); }); //throw FileError + if (allowUserCallback) + { + procCallback_.reportStatus(_("Removing old versions...")); //throw ? + versioner->limitVersions([&] { procCallback_.requestUiRefresh(); /*throw ? */ }); //throw FileError + } + else + versioner->limitVersions([] {}); //throw FileError } break; } @@ -674,36 +723,41 @@ void DeletionHandling::removeFile(const Zstring& relativeName) break; case DELETE_TO_RECYCLER: - { - const Zstring targetFile = recyclerTmpDirPf + relativeName; //ends with path separator - - try - { - //performance optimization: Instead of moving each object into recycle bin separately, - //we rename them one by one into a temporary directory and delete this directory only ONCE! - renameFile(fullName, targetFile); //throw FileError -> try to get away cheaply! - } - catch (FileError&) +#ifdef FFS_WIN { - if (somethingExists(fullName)) //no file at all is not an error (however a directory is *not* expected!) - try + const Zstring targetFile = recyclerTmpDirPf + relativeName; //ends with path separator + + auto moveToTempDir = [&] + { + //performance optimization: Instead of moving each object into recycle bin separately, + //we rename them one by one into a temporary directory and batch-recycle this directory after sync + renameFile(fullName, targetFile); //throw FileError + toBeRecycled.push_back(targetFile); + }; + + try + { + moveToTempDir(); //throw FileError + } + catch (FileError&) + { + if (somethingExists(fullName)) { const Zstring targetDir = beforeLast(targetFile, FILE_NAME_SEPARATOR); - if (!dirExists(targetDir)) //no reason to update gui or overwrite status text! + if (!dirExists(targetDir)) { makeDirectory(targetDir); //throw FileError -> may legitimately fail on Linux if permissions are missing - renameFile(fullName, targetFile); //throw FileError -> this should work now! + moveToTempDir(); //throw FileError -> this should work now! } else throw; } - catch (FileError&) //if anything went wrong, move to recycle bin the standard way (single file processing: slow) - { - recycleOrDelete(fullName); //throw FileError - } + } } - } - break; +#elif defined FFS_LINUX + recycleOrDelete(fullName); //throw FileError +#endif + break; case DELETE_TO_VERSIONING: { @@ -733,42 +787,47 @@ void DeletionHandling::removeFolderInt(const Zstring& relativeName, const int* o break; case DELETE_TO_RECYCLER: - { - const Zstring targetDir = recyclerTmpDirPf + relativeName; - - try - { - //performance optimization: Instead of moving each object into recycle bin separately, - //we rename them one by one into a temporary directory and delete this directory only ONCE! - renameFile(fullName, targetDir); //throw FileError -> try to get away cheaply! - } - catch (FileError&) +#ifdef FFS_WIN { - if (somethingExists(fullName)) - try + const Zstring targetDir = recyclerTmpDirPf + relativeName; + + auto moveToTempDir = [&] + { + //performance optimization: Instead of moving each object into recycle bin separately, + //we rename them one by one into a temporary directory and batch-recycle this directory after sync + renameFile(fullName, targetDir); //throw FileError + toBeRecycled.push_back(targetDir); + }; + + try + { + moveToTempDir(); //throw FileError + } + catch (FileError&) + { + if (somethingExists(fullName)) { const Zstring targetSuperDir = beforeLast(targetDir, FILE_NAME_SEPARATOR); - if (!dirExists(targetSuperDir)) //no reason to update gui or overwrite status text! + if (!dirExists(targetSuperDir)) { makeDirectory(targetSuperDir); //throw FileError -> may legitimately fail on Linux if permissions are missing - renameFile(fullName, targetDir); //throw FileError -> this should work now! + moveToTempDir(); //throw FileError -> this should work now! } else throw; } - catch (FileError&) //if anything went wrong, move to recycle bin the standard way (single file processing: slow) - { - recycleOrDelete(fullName); //throw FileError - } + } } - } +#elif defined FFS_LINUX + recycleOrDelete(fullName); //throw FileError +#endif - if (objectsExpected) //even though we have only one disk access, we completed "objectsExpected" logical operations! - { - procCallback_.updateProcessedData(*objectsExpected, 0); - objectsReported += *objectsExpected; - } - break; + if (objectsExpected) //even though we have only one disk access, we completed "objectsExpected" logical operations! + { + procCallback_.updateProcessedData(*objectsExpected, 0); + objectsReported += *objectsExpected; + } + break; case DELETE_TO_VERSIONING: { @@ -1460,7 +1519,7 @@ void SynchronizeFolderPair::synchronizeFileInt(FileMapping& fileObj, SyncOperati } catch (FileError&) { - if (fileExists(fileObj.getFullName<sideSrc>())) + if (somethingExists(fileObj.getFullName<sideSrc>())) //do not check on type (symlink, file, folder) -> if there is a type change, FFS should not be quiet about it! throw; //source deleted meanwhile...nothing was done (logical point of view!) procCallback_.updateTotalData(-1, -to<zen::Int64>(fileObj.getFileSize<sideSrc>())); @@ -1624,7 +1683,7 @@ void SynchronizeFolderPair::synchronizeLinkInt(SymLinkMapping& linkObj, SyncOper } catch (FileError&) { - if (fileExists(linkObj.getFullName<sideSrc>())) + if (somethingExists(linkObj.getFullName<sideSrc>())) //do not check on type (symlink, file, folder) -> if there is a type change, FFS should not be quiet about it! throw; //source deleted meanwhile...nothing was done (logical point of view!) procCallback_.updateTotalData(-1, 0); @@ -1720,7 +1779,7 @@ void SynchronizeFolderPair::synchronizeFolderInt(DirMapping& dirObj, SyncOperati { case SO_CREATE_NEW_LEFT: case SO_CREATE_NEW_RIGHT: - if (dirExists(dirObj.getFullName<sideSrc>())) + if (somethingExists(dirObj.getFullName<sideSrc>())) //do not check on type (symlink, file, folder) -> if there is a type change, FFS should not be quiet about it! { const Zstring& target = dirObj.getBaseDirPf<sideTrg>() + dirObj.getRelativeName<sideSrc>(); @@ -2244,8 +2303,8 @@ void zen::synchronize(const TimeComp& timeStamp, ScopeGuard guardUpdateDb = makeGuard([&] { if (folderPairCfg.inAutomaticMode) - try { zen::saveLastSynchronousState(*j); } - catch (...) {} //throw FileError + try { zen::saveLastSynchronousState(*j); } //throw FileError + catch (...) {} }); //guarantee removal of invalid entries (where element on both sides is empty) diff --git a/ui/IFileDialog_Vista/ifile_dialog.cpp b/ui/IFileDialog_Vista/ifile_dialog.cpp index 8c3e694c..565dfa1a 100644 --- a/ui/IFileDialog_Vista/ifile_dialog.cpp +++ b/ui/IFileDialog_Vista/ifile_dialog.cpp @@ -19,6 +19,7 @@ namespace { bool showFolderPickerImpl(HWND ownerWindow, //throw ComError; return "false" if cancelled by user const wchar_t* defaultFolder, //optional! + const GUID* persistenceGuid, // std::wstring& selectedFolder) { ComPtr<IFileDialog> fileDlg; @@ -27,6 +28,9 @@ bool showFolderPickerImpl(HWND ownerWindow, //throw ComError; return "false" if CLSCTX_ALL, IID_PPV_ARGS(fileDlg.init()))); + if (persistenceGuid) + ZEN_CHECK_COM(fileDlg->SetClientGuid(*persistenceGuid)); + FILEOPENDIALOGOPTIONS dlgOptions = 0; ZEN_CHECK_COM(fileDlg->GetOptions(&dlgOptions)); //throw ComError ZEN_CHECK_COM(fileDlg->SetOptions(dlgOptions | FOS_PICKFOLDERS | FOS_NOVALIDATE | FOS_FORCEFILESYSTEM)); @@ -57,7 +61,7 @@ bool showFolderPickerImpl(HWND ownerWindow, //throw ComError; return "false" if return true; } -const wchar_t* allocString(const std::wstring& msg) //ownership passed +wchar_t* allocString(const std::wstring& msg) //ownership passed { auto tmp = new wchar_t [msg.size() + 1]; //std::bad_alloc ? ::wmemcpy(tmp, msg.c_str(), msg.size() + 1); //include 0-termination @@ -69,9 +73,10 @@ const wchar_t* allocString(const std::wstring& msg) //ownership passed void ifile::showFolderPicker(void* ownerWindow, const wchar_t* defaultFolder, - const wchar_t*& selectedFolder, + const GuidProxy* guid, + wchar_t*& selectedFolder, bool& cancelled, - const wchar_t*& errorMsg) + wchar_t*& errorMsg) { selectedFolder = nullptr; cancelled = false; @@ -79,8 +84,13 @@ void ifile::showFolderPicker(void* ownerWindow, try { + static_assert(sizeof(GuidProxy) == sizeof(GUID), ""); + GUID winGuid = {}; + if (guid) + ::memcpy(&winGuid, guid, sizeof(GUID)); + std::wstring folderPath; - if (showFolderPickerImpl(static_cast<HWND>(ownerWindow), defaultFolder, folderPath)) //throw ComError + if (showFolderPickerImpl(static_cast<HWND>(ownerWindow), defaultFolder, guid ? &winGuid : nullptr, folderPath)) //throw ComError selectedFolder = allocString(folderPath); else cancelled = true; diff --git a/ui/IFileDialog_Vista/ifile_dialog.h b/ui/IFileDialog_Vista/ifile_dialog.h index d0099dda..5b4dc532 100644 --- a/ui/IFileDialog_Vista/ifile_dialog.h +++ b/ui/IFileDialog_Vista/ifile_dialog.h @@ -25,12 +25,15 @@ namespace ifile //COM needs to be initialized before calling any of these functions! CoInitializeEx/CoUninitialize //Requires Windows Vista and later +typedef char GuidProxy[16]; //= Windows 128-bit GUID; we don't want to include "Guiddef.h" here! + DLL_FUNCTION_DECLARATION void showFolderPicker(void* ownerWindow, //in; ==HWND const wchar_t* defaultFolder, //in, optional! - const wchar_t*& selectedFolder, //out: call freeString() after use! + const GuidProxy* guid, //set nullptr by default: Windows stores dialog state (position, x, y coordinates, ect.) associated with the process executable name => use other GUID when needed + wchar_t*& selectedFolder, //out: call freeString() after use! bool& cancelled, //out - const wchar_t*& errorMsg); //out, optional: call freeString() after use! + wchar_t*& errorMsg); //out, optional: call freeString() after use! DLL_FUNCTION_DECLARATION void freeString(const wchar_t* str); @@ -40,9 +43,10 @@ void freeString(const wchar_t* str); ----------*/ typedef bool (*FunType_showFolderPicker)(void* ownerWindow, const wchar_t* defaultFolder, - const wchar_t*& selectedFolder, + const GuidProxy* guid, + wchar_t*& selectedFolder, bool& cancelled, - const wchar_t*& errorMsg); + wchar_t*& errorMsg); typedef void (*FunType_freeString)(const wchar_t* str); /*-------------- diff --git a/ui/batch_config.cpp b/ui/batch_config.cpp index f07dbc07..d5aa7bc2 100644 --- a/ui/batch_config.cpp +++ b/ui/batch_config.cpp @@ -14,6 +14,7 @@ #include <wx+/mouse_move_dlg.h> #include <wx+/context_menu.h> #include <zen/file_handling.h> +#include "../ui/exec_finished_box.h" #include "../lib/help_provider.h" #include "folder_pair.h" #include "msg_popup.h" diff --git a/ui/batch_status_handler.cpp b/ui/batch_status_handler.cpp index 594ffb7a..b33b0d80 100644 --- a/ui/batch_status_handler.cpp +++ b/ui/batch_status_handler.cpp @@ -135,7 +135,8 @@ BatchStatusHandler::BatchStatusHandler(bool showProgress, BatchStatusHandler::~BatchStatusHandler() { - const int totalErrors = errorLog.getItemCount(TYPE_ERROR | TYPE_FATAL_ERROR); //evaluate before finalizing log + const int totalErrors = errorLog.getItemCount(TYPE_ERROR | TYPE_FATAL_ERROR); //evaluate before finalizing log + const int totalWarnings = errorLog.getItemCount(TYPE_WARNING); //finalize error log std::wstring finalStatus; @@ -149,6 +150,12 @@ BatchStatusHandler::~BatchStatusHandler() { raiseReturnCode(returnCode_, FFS_RC_FINISHED_WITH_ERRORS); finalStatus = _("Synchronization completed with errors!"); + errorLog.logMsg(finalStatus, TYPE_ERROR); + } + else if (totalWarnings > 0) + { + raiseReturnCode(returnCode_, FFS_RC_FINISHED_WITH_WARNINGS); + finalStatus = _("Synchronization completed with warnings!"); errorLog.logMsg(finalStatus, TYPE_WARNING); } else @@ -190,7 +197,7 @@ BatchStatusHandler::~BatchStatusHandler() switchBatchToGui_.execute(); //open FreeFileSync GUI } catch (...) {} - syncStatusFrame.closeWindowDirectly(); //syncStatusFrame is main window => program will quit directly + syncStatusFrame.closeWindowDirectly(); //syncStatusFrame is not main window anymore } else { @@ -220,6 +227,8 @@ BatchStatusHandler::~BatchStatusHandler() syncStatusFrame.processHasFinished(SyncStatus::RESULT_ABORTED, errorLog); //enable okay and close events else if (totalErrors > 0) syncStatusFrame.processHasFinished(SyncStatus::RESULT_FINISHED_WITH_ERROR, errorLog); + else if (totalWarnings > 0) + syncStatusFrame.processHasFinished(SyncStatus::RESULT_FINISHED_WITH_WARNINGS, errorLog); else syncStatusFrame.processHasFinished(SyncStatus::RESULT_FINISHED_WITH_SUCCESS, errorLog); } diff --git a/ui/check_version.cpp b/ui/check_version.cpp index 5bf67b90..c9d2049d 100644 --- a/ui/check_version.cpp +++ b/ui/check_version.cpp @@ -16,50 +16,179 @@ #include "msg_popup.h" #include "../version/version.h" #include "../lib/ffs_paths.h" +#include <zen/scope_guard.h> + +#ifdef FFS_WIN +#include <wininet.h> +#endif using namespace zen; namespace { +#ifdef FFS_WIN +class InternetConnectionError {}; + +class WinInetAccess //using IE proxy settings! :) +{ +public: + WinInetAccess(const wchar_t* url) //throw InternetConnectionError (if url cannot be reached; no need to also call readBytes()) + { + //::InternetAttemptConnect(0) -> not working as expected: succeeds even when there is no internet connection! + + hInternet = ::InternetOpen(L"FreeFileSync", //_In_ LPCTSTR lpszAgent, + INTERNET_OPEN_TYPE_PRECONFIG, //_In_ DWORD dwAccessType, + nullptr, //_In_ LPCTSTR lpszProxyName, + nullptr, //_In_ LPCTSTR lpszProxyBypass, + 0); //_In_ DWORD dwFlags + if (!hInternet) + throw InternetConnectionError(); + zen::ScopeGuard guardInternet = zen::makeGuard([&] { ::InternetCloseHandle(hInternet); }); + + hRequest = ::InternetOpenUrl(hInternet, //_In_ HINTERNET hInternet, + url, //_In_ LPCTSTR lpszUrl, + nullptr, //_In_ LPCTSTR lpszHeaders, + 0, //_In_ DWORD dwHeadersLength, + INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_UI, //_In_ DWORD dwFlags, + 0); //_In_ DWORD_PTR dwContext + if (!hRequest) //won't fail due to unreachable url here! There is no substitute for HTTP_QUERY_STATUS_CODE!!! + throw InternetConnectionError(); + zen::ScopeGuard guardRequest = zen::makeGuard([&] { ::InternetCloseHandle(hRequest); }); + + DWORD statusCode = 0; + DWORD bufferLength = sizeof(statusCode); + if (!::HttpQueryInfo(hRequest, //_In_ HINTERNET hRequest, + HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, //_In_ DWORD dwInfoLevel, + &statusCode, //_Inout_ LPVOID lpvBuffer, + &bufferLength, //_Inout_ LPDWORD lpdwBufferLength, + nullptr)) //_Inout_ LPDWORD lpdwIndex + throw InternetConnectionError(); + + if (statusCode != HTTP_STATUS_OK) + throw InternetConnectionError(); //e.g. 404 - HTTP_STATUS_NOT_FOUND + + guardRequest.dismiss(); + guardInternet.dismiss(); + } + + ~WinInetAccess() + { + ::InternetCloseHandle(hRequest); + ::InternetCloseHandle(hInternet); + } + + template <class OutputIterator> + OutputIterator readBytes(OutputIterator result) //throw InternetConnectionError + { + //internet says "HttpQueryInfo() + HTTP_QUERY_CONTENT_LENGTH" not supported by all http servers... + const DWORD bufferSize = 64 * 1024; + std::vector<char> buffer(bufferSize); + for (;;) + { + DWORD bytesRead = 0; + if (!::InternetReadFile(hRequest, //_In_ HINTERNET hFile, + &buffer[0], //_Out_ LPVOID lpBuffer, + bufferSize, //_In_ DWORD dwNumberOfBytesToRead, + &bytesRead)) //_Out_ LPDWORD lpdwNumberOfBytesRead + throw InternetConnectionError(); + if (bytesRead == 0) + return result; + + result = std::copy(buffer.begin(), buffer.begin() + bytesRead, result); + } + } + +private: + HINTERNET hInternet; + HINTERNET hRequest; +}; + + +inline +bool canAccessUrl(const wchar_t* url) //throw () +{ + try + { + (void)WinInetAccess(url); //throw InternetConnectionError + return true; + } + catch (const InternetConnectionError&) + { + return false; + } +} + + +template <class OutputIterator> inline +OutputIterator readBytesUrl(const wchar_t* url, OutputIterator result) //throw InternetConnectionError +{ + return WinInetAccess(url).readBytes(result); //throw InternetConnectionError +} +#endif + + enum GetVerResult { GET_VER_SUCCESS, - GET_VER_NO_CONNECTION, //no internet connection? + GET_VER_NO_CONNECTION, //no internet connection or just Sourceforge down? GET_VER_PAGE_NOT_FOUND //version file seems to have moved! => trigger an update! }; GetVerResult getOnlineVersion(wxString& version) //empty string on error; { - wxWindowDisabler dummy; +#ifdef FFS_WIN + //internet access supporting proxy connections + std::vector<char> output; + try + { + readBytesUrl(L"http://freefilesync.sourceforge.net/latest_version.txt", std::back_inserter(output)); //throw InternetConnectionError + } + catch (const InternetConnectionError&) + { + return canAccessUrl(L"http://sourceforge.net/") ? GET_VER_PAGE_NOT_FOUND : GET_VER_NO_CONNECTION; + } - wxHTTP webAccess; - webAccess.SetHeader(L"content-type", L"text/html; charset=utf-8"); - webAccess.SetTimeout(5); //5 seconds of timeout instead of 10 minutes(WTF are they thinking???)... + output.push_back('\0'); + version = utfCvrtTo<wxString>(&output[0]); + return GET_VER_SUCCESS; - if (webAccess.Connect(L"freefilesync.sourceforge.net")) //only the server, no pages here yet... - { - //wxApp::IsMainLoopRunning(); // should return true +#else + wxWindowDisabler dummy; - std::unique_ptr<wxInputStream> httpStream(webAccess.GetInputStream(L"/latest_version.txt")); - //must be deleted BEFORE webAccess is closed + auto getStringFromUrl = [](const wxString& server, const wxString& page, int timeout, wxString* output) -> bool //true on successful connection + { + wxHTTP webAccess; + webAccess.SetHeader(L"content-type", L"text/html; charset=utf-8"); + webAccess.SetTimeout(timeout); //default: 10 minutes(WTF are they thinking???)... - if (httpStream && webAccess.GetError() == wxPROTO_NOERR) + if (webAccess.Connect(server)) //will *not* fail for non-reachable url here! { - wxString tmp; - wxStringOutputStream outStream(&tmp); - httpStream->Read(outStream); - version = tmp; - return GET_VER_SUCCESS; + //wxApp::IsMainLoopRunning(); // should return true + + std::unique_ptr<wxInputStream> httpStream(webAccess.GetInputStream(page)); + //must be deleted BEFORE webAccess is closed + + if (httpStream && webAccess.GetError() == wxPROTO_NOERR) + { + if (output) + { + output->clear(); + wxStringOutputStream outStream(output); + httpStream->Read(outStream); + } + return true; + } } - else - return GET_VER_PAGE_NOT_FOUND; - } - else //check if sourceforge in general is reachable - { - webAccess.SetTimeout(1); - return webAccess.Connect(L"sourceforge.net") ? GET_VER_PAGE_NOT_FOUND : GET_VER_NO_CONNECTION; - } + return false; + }; + + if (getStringFromUrl(L"freefilesync.sourceforge.net", L"/latest_version.txt", 5, &version)) + return GET_VER_SUCCESS; + + const bool canConnectToSf = getStringFromUrl(L"sourceforge.net", L"/", 1, nullptr); + return canConnectToSf ? GET_VER_PAGE_NOT_FOUND : GET_VER_NO_CONNECTION; +#endif } diff --git a/ui/custom_grid.cpp b/ui/custom_grid.cpp index 13964d9e..e7152905 100644 --- a/ui/custom_grid.cpp +++ b/ui/custom_grid.cpp @@ -224,7 +224,7 @@ protected: else { //alternate background color to improve readability (while lacking cell borders) - if (getRowDisplayType(row) == DISP_TYPE_NORMAL && row % 2 == 0) + if (getRowDisplayType(row) == DISP_TYPE_NORMAL && row % 2 == 1) { //accessibility, support high-contrast schemes => work with user-defined background color! const auto backCol = getBackGroundColor(row); @@ -1192,7 +1192,7 @@ public: GridDataLeft& provLeft, GridDataMiddle& provMiddle, GridDataRight& provRight) : - gridL_(gridL), gridC_(gridC), gridR_(gridR), + gridL_(gridL), gridC_(gridC), gridR_(gridR), scrollMaster(nullptr), provLeft_(provLeft), provMiddle_(provMiddle), provRight_(provRight) { gridL_.Connect(EVENT_GRID_COL_RESIZE, GridColumnResizeEventHandler(GridEventManager::onResizeColumnL), nullptr, this); @@ -1209,29 +1209,31 @@ public: gridC_.Connect(EVENT_GRID_SELECT_RANGE, GridRangeSelectEventHandler(GridEventManager::onCenterSelectEnd ), nullptr, this); //clear selection of other grid when selecting on - gridL_.Connect(EVENT_GRID_SELECT_RANGE, GridRangeSelectEventHandler(GridEventManager::onGridSelectionL), nullptr, this); - gridR_.Connect(EVENT_GRID_SELECT_RANGE, GridRangeSelectEventHandler(GridEventManager::onGridSelectionR), nullptr, this); + gridL_.Connect(EVENT_GRID_SELECT_RANGE, GridRangeSelectEventHandler(GridEventManager::onGridSelectionL), nullptr, this); + gridR_.Connect(EVENT_GRID_SELECT_RANGE, GridRangeSelectEventHandler(GridEventManager::onGridSelectionR), nullptr, this); //parallel grid scrolling: do NOT use DoPrepareDC() to align grids! GDI resource leak! Use regular paint event instead: - gridL_.getMainWin().Connect(wxEVT_PAINT, wxEventHandler(GridEventManager::onPaintGridL), NULL, this); - gridC_.getMainWin().Connect(wxEVT_PAINT, wxEventHandler(GridEventManager::onPaintGridC), NULL, this); - gridR_.getMainWin().Connect(wxEVT_PAINT, wxEventHandler(GridEventManager::onPaintGridR), NULL, this); - - gridL_.Connect(wxEVT_SCROLLWIN_THUMBTRACK, wxEventHandler(GridEventManager::onGridAccessL), NULL, this); - gridL_.Connect(wxEVT_SCROLLWIN_PAGEUP, wxEventHandler(GridEventManager::onGridAccessL), NULL, this); - gridL_.Connect(wxEVT_SCROLLWIN_PAGEDOWN, wxEventHandler(GridEventManager::onGridAccessL), NULL, this); - gridL_.Connect(wxEVT_SCROLLWIN_TOP, wxEventHandler(GridEventManager::onGridAccessL), NULL, this); - gridL_.Connect(wxEVT_SCROLLWIN_BOTTOM, wxEventHandler(GridEventManager::onGridAccessL), NULL, this); - gridL_.Connect(wxEVT_SCROLLWIN_LINEUP, wxEventHandler(GridEventManager::onGridAccessL), NULL, this); - gridL_.Connect(wxEVT_SCROLLWIN_LINEDOWN, wxEventHandler(GridEventManager::onGridAccessL), NULL, this); - - gridR_.Connect(wxEVT_SCROLLWIN_THUMBTRACK, wxEventHandler(GridEventManager::onGridAccessR), NULL, this); - gridR_.Connect(wxEVT_SCROLLWIN_PAGEUP, wxEventHandler(GridEventManager::onGridAccessR), NULL, this); - gridR_.Connect(wxEVT_SCROLLWIN_PAGEDOWN, wxEventHandler(GridEventManager::onGridAccessR), NULL, this); - gridR_.Connect(wxEVT_SCROLLWIN_TOP, wxEventHandler(GridEventManager::onGridAccessR), NULL, this); - gridR_.Connect(wxEVT_SCROLLWIN_BOTTOM, wxEventHandler(GridEventManager::onGridAccessR), NULL, this); - gridR_.Connect(wxEVT_SCROLLWIN_LINEUP, wxEventHandler(GridEventManager::onGridAccessR), NULL, this); - gridR_.Connect(wxEVT_SCROLLWIN_LINEDOWN, wxEventHandler(GridEventManager::onGridAccessR), NULL, this); + gridL_.getMainWin().Connect(wxEVT_PAINT, wxEventHandler(GridEventManager::onPaintGridL), nullptr, this); + gridC_.getMainWin().Connect(wxEVT_PAINT, wxEventHandler(GridEventManager::onPaintGridC), nullptr, this); + gridR_.getMainWin().Connect(wxEVT_PAINT, wxEventHandler(GridEventManager::onPaintGridR), nullptr, this); + + auto connectGridAccess = [&](Grid& grid, wxObjectEventFunction func) + { + grid.Connect(wxEVT_SCROLLWIN_TOP, func, nullptr, this); + grid.Connect(wxEVT_SCROLLWIN_BOTTOM, func, nullptr, this); + grid.Connect(wxEVT_SCROLLWIN_LINEUP, func, nullptr, this); + grid.Connect(wxEVT_SCROLLWIN_LINEDOWN, func, nullptr, this); + grid.Connect(wxEVT_SCROLLWIN_PAGEUP, func, nullptr, this); + grid.Connect(wxEVT_SCROLLWIN_PAGEDOWN, func, nullptr, this); + grid.Connect(wxEVT_SCROLLWIN_THUMBTRACK, func, nullptr, this); + + grid.getMainWin().Connect(wxEVT_SET_FOCUS, func, nullptr, this); + //on wxEVT_KILL_FOCUS, there's no need to reset "scrollMaster" + + }; + connectGridAccess(gridL_, wxEventHandler(GridEventManager::onGridAccessL)); + connectGridAccess(gridC_, wxEventHandler(GridEventManager::onGridAccessC)); + connectGridAccess(gridR_, wxEventHandler(GridEventManager::onGridAccessR)); Connect(EVENT_ALIGN_SCROLLBARS, wxEventHandler(GridEventManager::onAlignScrollBars), NULL, this); } @@ -1350,8 +1352,9 @@ private: trg.setColumnConfig(cfgTrg); } - void onGridAccessL(wxEvent& event) { gridL_.SetFocus(); event.Skip(); } - void onGridAccessR(wxEvent& event) { gridR_.SetFocus(); event.Skip(); } + void onGridAccessL(wxEvent& event) { scrollMaster = &gridL_; event.Skip(); } + void onGridAccessC(wxEvent& event) { scrollMaster = &gridC_; event.Skip(); } + void onGridAccessR(wxEvent& event) { scrollMaster = &gridR_; event.Skip(); } void onPaintGridL(wxEvent& event) { onPaintGrid(gridL_); event.Skip(); } void onPaintGridC(wxEvent& event) { onPaintGrid(gridC_); event.Skip(); } @@ -1367,9 +1370,9 @@ private: Grid* follow2 = nullptr; auto setGrids = [&](const Grid& l, Grid& f1, Grid& f2) { lead = &l; follow1 = &f1; follow2 = &f2; }; - if (wxWindow::FindFocus() == &gridC_.getMainWin()) + if (&gridC_ == scrollMaster) setGrids(gridC_, gridL_, gridR_); - else if (wxWindow::FindFocus() == &gridR_.getMainWin()) + else if (&gridR_ == scrollMaster) setGrids(gridR_, gridL_, gridC_); else //default: left panel setGrids(gridL_, gridC_, gridR_); @@ -1384,7 +1387,7 @@ private: int yOld = 0; target.GetViewStart(nullptr, &yOld); if (yOld != y) - target.Scroll(-1, y); + target.Scroll(-1, y); //empirical test Windows/Ubuntu: this call does NOT trigger a wxEVT_SCROLLWIN event, which would incorrectly set "scrollMaster" to "&target"! }; int y = 0; lead->GetViewStart(nullptr, &y); @@ -1423,6 +1426,10 @@ private: Grid& gridL_; Grid& gridC_; Grid& gridR_; + + const Grid* scrollMaster; //for address check only; this needn't be the grid having focus! + //e.g. mouse wheel events should set window under cursor as scrollMaster, but *not* change focus + GridDataLeft& provLeft_; GridDataMiddle& provMiddle_; GridDataRight& provRight_; diff --git a/ui/dir_name.cpp b/ui/dir_name.cpp index 406903e6..7d4f7a31 100644 --- a/ui/dir_name.cpp +++ b/ui/dir_name.cpp @@ -185,14 +185,19 @@ void DirectoryName<NameControl>::OnSelectDir(wxCommandEvent& event) const DllFun<FunType_freeString> freeString (getDllName(), funName_freeString); if (showFolderPicker && freeString) { - const wchar_t* selectedFolder = nullptr; - const wchar_t* errorMsg = nullptr; + wchar_t* selectedFolder = nullptr; + wchar_t* errorMsg = nullptr; bool cancelled = false; ZEN_ON_SCOPE_EXIT(freeString(selectedFolder)); ZEN_ON_SCOPE_EXIT(freeString(errorMsg)); + const GuidProxy guid = { '\x0', '\x4a', '\xf9', '\x31', '\xb4', '\x92', '\x40', '\xa0', + '\x8d', '\xc2', '\xc', '\xa5', '\xef', '\x59', '\x6e', '\x3b' + }; //some random GUID => have Windows save IFileDialog state separately from other file/dir pickers! + showFolderPicker(static_cast<HWND>(selectButton_.GetHWND()), //in; ==HWND defaultDirname.empty() ? nullptr : defaultDirname.c_str(), //in, optional! + &guid, selectedFolder, //out: call freeString() after use! cancelled, //out errorMsg); //out, optional: call freeString() after use! diff --git a/ui/folder_history_box.cpp b/ui/folder_history_box.cpp index 2832afed..cefc03c8 100644 --- a/ui/folder_history_box.cpp +++ b/ui/folder_history_box.cpp @@ -36,10 +36,16 @@ FolderHistoryBox::FolderHistoryBox(wxWindow* parent, /*##*/ SetMinSize(wxSize(150, -1)); //## workaround yet another wxWidgets bug: default minimum size is much too large for a wxComboBox //##################################### - Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(FolderHistoryBox::OnKeyEvent ), nullptr, this); - Connect(wxEVT_LEFT_DOWN, wxEventHandler(FolderHistoryBox::OnUpdateList), nullptr, this); + Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(FolderHistoryBox::OnKeyEvent ), nullptr, this); Connect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(FolderHistoryBox::OnSelection ), nullptr, this); - Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler(FolderHistoryBox::OnMouseWheel), nullptr, this); + Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler(FolderHistoryBox::OnMouseWheel), nullptr, this); +#ifdef FFS_WIN //on Win, this event only fires, when clicking on the small down arrow, NOT when clicking on the text field + //thanks to wxWidgets' non-portability it's exactly the converse on Linux! + Connect(wxEVT_LEFT_DOWN, wxEventHandler(FolderHistoryBox::OnUpdateList), nullptr, this); +#elif defined FFS_LINUX //update on each text change: maybe a little too often, but we have no choice as long as we don't have an event right before showing the drop-down list + Connect(wxEVT_COMMAND_TEXT_UPDATED, wxEventHandler(FolderHistoryBox::OnUpdateList), nullptr, this); +#endif + #if wxCHECK_VERSION(2, 9, 1) Connect(wxEVT_COMMAND_COMBOBOX_DROPDOWN, wxCommandEventHandler(FolderHistoryBox::OnShowDropDown), nullptr, this); @@ -73,10 +79,8 @@ void FolderHistoryBox::setValueAndUpdateList(const wxString& dirname) std::list<Zstring> dirList; //add some aliases to allow user changing to volume name and back, if possible -#ifdef FFS_WIN std::vector<Zstring> aliases = getDirectoryAliases(toZ(dirname)); dirList.insert(dirList.end(), aliases.begin(), aliases.end()); -#endif if (sharedHistory_.get()) { diff --git a/ui/gui_generated.cpp b/ui/gui_generated.cpp index 2e99377a..d132d748 100644 --- a/ui/gui_generated.cpp +++ b/ui/gui_generated.cpp @@ -1,10 +1,19 @@ /////////////////////////////////////////////////////////////////////////// -// C++ code generated with wxFormBuilder (version Apr 10 2012) +// C++ code generated with wxFormBuilder (version Oct 8 2012) // http://www.wxformbuilder.org/ // // PLEASE DO "NOT" EDIT THIS FILE! /////////////////////////////////////////////////////////////////////////// +#include "../wx+/button.h" +#include "../wx+/graph.h" +#include "../wx+/grid.h" +#include "../wx+/toggle_button.h" +#include "exec_finished_box.h" +#include "folder_history_box.h" +#include "triple_splitter.h" +#include "wx_form_build_hide_warnings.h" + #include "gui_generated.h" /////////////////////////////////////////////////////////////////////////// @@ -259,6 +268,9 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const wxBoxSizer* bSizer1771; bSizer1771 = new wxBoxSizer( wxVERTICAL ); + + bSizer1771->Add( 0, 0, 1, wxEXPAND, 5 ); + m_bpButtonSwapSides = new wxBitmapButton( m_panelTopMiddle, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), wxBU_AUTODRAW ); m_bpButtonSwapSides->SetToolTip( _("Swap sides") ); @@ -280,6 +292,9 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const bSizer1771->Add( bSizer160, 0, wxALIGN_CENTER_HORIZONTAL, 5 ); + bSizer1771->Add( 0, 0, 1, wxEXPAND, 5 ); + + m_panelTopMiddle->SetSizer( bSizer1771 ); m_panelTopMiddle->Layout(); bSizer1771->Fit( m_panelTopMiddle ); @@ -851,6 +866,7 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const m_bpButtonSave->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnConfigSave ), NULL, this ); m_listBoxHistory->Connect( wxEVT_CHAR, wxKeyEventHandler( MainDialogGenerated::OnCfgHistoryKeyEvent ), NULL, this ); m_listBoxHistory->Connect( wxEVT_COMMAND_LISTBOX_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnLoadFromHistory ), NULL, this ); + m_listBoxHistory->Connect( wxEVT_COMMAND_LISTBOX_DOUBLECLICKED, wxCommandEventHandler( MainDialogGenerated::OnLoadFromHistoryDoubleClick ), NULL, this ); m_bpButtonFilter->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnConfigureFilter ), NULL, this ); m_checkBoxShowExcluded->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnShowExcluded ), NULL, this ); m_bpButtonSyncCreateLeft->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSyncCreateLeft ), NULL, this ); @@ -897,6 +913,7 @@ MainDialogGenerated::~MainDialogGenerated() m_bpButtonSave->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnConfigSave ), NULL, this ); m_listBoxHistory->Disconnect( wxEVT_CHAR, wxKeyEventHandler( MainDialogGenerated::OnCfgHistoryKeyEvent ), NULL, this ); m_listBoxHistory->Disconnect( wxEVT_COMMAND_LISTBOX_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnLoadFromHistory ), NULL, this ); + m_listBoxHistory->Disconnect( wxEVT_COMMAND_LISTBOX_DOUBLECLICKED, wxCommandEventHandler( MainDialogGenerated::OnLoadFromHistoryDoubleClick ), NULL, this ); m_bpButtonFilter->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnConfigureFilter ), NULL, this ); m_checkBoxShowExcluded->Disconnect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnShowExcluded ), NULL, this ); m_bpButtonSyncCreateLeft->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSyncCreateLeft ), NULL, this ); @@ -1015,6 +1032,7 @@ CompareStatusGenerated::CompareStatusGenerated( wxWindow* parent, wxWindowID id, bSizer182 = new wxBoxSizer( wxVERTICAL ); m_textCtrlStatus = new wxTextCtrl( this, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, wxTE_READONLY ); + m_textCtrlStatus->SetMaxLength( 0 ); m_textCtrlStatus->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) ); bSizer182->Add( m_textCtrlStatus, 0, wxEXPAND, 5 ); @@ -2266,6 +2284,7 @@ SyncStatusDlgGenerated::SyncStatusDlgGenerated( wxWindow* parent, wxWindowID id, bSizer1811 = new wxBoxSizer( wxVERTICAL ); m_textCtrlStatus = new wxTextCtrl( m_panelProgress, wxID_ANY, _("dummy"), wxDefaultPosition, wxSize( -1,-1 ), wxTE_READONLY|wxNO_BORDER ); + m_textCtrlStatus->SetMaxLength( 0 ); m_textCtrlStatus->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); bSizer1811->Add( m_textCtrlStatus, 0, wxALIGN_CENTER_VERTICAL|wxEXPAND|wxLEFT, 5 ); @@ -2508,6 +2527,7 @@ LogControlGenerated::LogControlGenerated( wxWindow* parent, wxWindowID id, const bSizer153->Add( m_staticline13, 0, wxEXPAND, 5 ); m_textCtrlInfo = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( -1,-1 ), wxTE_DONTWRAP|wxTE_MULTILINE|wxTE_READONLY|wxNO_BORDER ); + m_textCtrlInfo->SetMaxLength( 0 ); bSizer153->Add( m_textCtrlInfo, 1, wxEXPAND|wxALIGN_CENTER_VERTICAL, 5 ); @@ -2747,7 +2767,7 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS bSizer170->Add( 0, 0, 1, wxEXPAND, 5 ); - m_hyperlink1 = new wxHyperlinkCtrl( this, wxID_ANY, _("Homepage"), wxT("http://sourceforge.net/projects/freefilesync/"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink1 = new wxHyperlinkCtrl( this, wxID_ANY, _("Homepage"), wxT("http://freefilesync.sourceforge.net/"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); m_hyperlink1->SetFont( wxFont( 10, 70, 90, 92, true, wxEmptyString ) ); m_hyperlink1->SetToolTip( _("http://sourceforge.net/projects/freefilesync/") ); @@ -2855,6 +2875,7 @@ MessageDlgGenerated::MessageDlgGenerated( wxWindow* parent, wxWindowID id, const bSizer26->Add( m_bitmapMsgType, 0, wxBOTTOM|wxRIGHT|wxLEFT, 5 ); m_textCtrlMessage = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( 400,130 ), wxTE_MULTILINE|wxTE_READONLY|wxNO_BORDER ); + m_textCtrlMessage->SetMaxLength( 0 ); bSizer26->Add( m_textCtrlMessage, 1, wxALIGN_CENTER_VERTICAL|wxEXPAND, 5 ); @@ -2963,6 +2984,7 @@ DeleteDlgGenerated::DeleteDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer24->Add( m_staticline91, 0, wxEXPAND|wxBOTTOM, 5 ); m_textCtrlFileList = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( 550,200 ), wxTE_DONTWRAP|wxTE_MULTILINE|wxTE_READONLY|wxNO_BORDER ); + m_textCtrlFileList->SetMaxLength( 0 ); bSizer24->Add( m_textCtrlFileList, 1, wxEXPAND|wxLEFT, 5 ); m_staticline9 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); @@ -3107,6 +3129,7 @@ FilterDlgGenerated::FilterDlgGenerated( wxWindow* parent, wxWindowID id, const w sbSizer8->Add( m_bitmapInclude, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL|wxRIGHT, 5 ); m_textCtrlInclude = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( -1,-1 ), wxTE_MULTILINE ); + m_textCtrlInclude->SetMaxLength( 0 ); sbSizer8->Add( m_textCtrlInclude, 1, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL|wxEXPAND, 5 ); @@ -3119,6 +3142,7 @@ FilterDlgGenerated::FilterDlgGenerated( wxWindow* parent, wxWindowID id, const w sbSizer26->Add( m_bitmapExclude, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); m_textCtrlExclude = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( -1,-1 ), wxTE_MULTILINE ); + m_textCtrlExclude->SetMaxLength( 0 ); sbSizer26->Add( m_textCtrlExclude, 1, wxALIGN_CENTER_VERTICAL|wxEXPAND|wxALIGN_CENTER_HORIZONTAL, 5 ); @@ -3693,6 +3717,7 @@ SearchDialogGenerated::SearchDialogGenerated( wxWindow* parent, wxWindowID id, c bSizer162->Add( m_staticText101, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); m_textCtrlSearchTxt = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( 220,-1 ), 0 ); + m_textCtrlSearchTxt->SetMaxLength( 0 ); bSizer162->Add( m_textCtrlSearchTxt, 1, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); diff --git a/ui/gui_generated.h b/ui/gui_generated.h index fc5291e3..14e30f75 100644 --- a/ui/gui_generated.h +++ b/ui/gui_generated.h @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////// -// C++ code generated with wxFormBuilder (version Apr 10 2012) +// C++ code generated with wxFormBuilder (version Oct 8 2012) // http://www.wxformbuilder.org/ // // PLEASE DO "NOT" EDIT THIS FILE! @@ -11,14 +11,15 @@ #include <wx/artprov.h> #include <wx/xrc/xmlres.h> #include <wx/intl.h> -#include "wx_form_build_hide_warnings.h" -#include "../wx+/button.h" -#include "folder_history_box.h" -#include "../wx+/grid.h" -#include "triple_splitter.h" -#include "../wx+/toggle_button.h" -#include "exec_finished_box.h" -#include "../wx+/graph.h" +class ExecFinishedBox; +class FolderHistoryBox; +class ToggleButton; +class wxStaticText; +namespace zen{ class BitmapButton; } +namespace zen{ class Graph2D; } +namespace zen{ class Grid; } +namespace zen{ class TripleSplitter; } + #include <wx/string.h> #include <wx/bitmap.h> #include <wx/image.h> @@ -192,6 +193,7 @@ class MainDialogGenerated : public wxFrame virtual void OnSwapSides( wxCommandEvent& event ) { event.Skip(); } virtual void OnCfgHistoryKeyEvent( wxKeyEvent& event ) { event.Skip(); } virtual void OnLoadFromHistory( wxCommandEvent& event ) { event.Skip(); } + virtual void OnLoadFromHistoryDoubleClick( wxCommandEvent& event ) { event.Skip(); } virtual void OnConfigureFilter( wxCommandEvent& event ) { event.Skip(); } virtual void OnShowExcluded( wxCommandEvent& event ) { event.Skip(); } virtual void OnSyncCreateLeft( wxCommandEvent& event ) { event.Skip(); } diff --git a/ui/gui_status_handler.cpp b/ui/gui_status_handler.cpp index 63e394ff..120eab39 100644 --- a/ui/gui_status_handler.cpp +++ b/ui/gui_status_handler.cpp @@ -194,7 +194,8 @@ SyncStatusHandler::SyncStatusHandler(MainDialog* parentDlg, SyncStatusHandler::~SyncStatusHandler() { - const int totalErrors = errorLog.getItemCount(TYPE_ERROR | TYPE_FATAL_ERROR); //evaluate before finalizing log + const int totalErrors = errorLog.getItemCount(TYPE_ERROR | TYPE_FATAL_ERROR); //evaluate before finalizing log + const int totalWarnings = errorLog.getItemCount(TYPE_WARNING); //finalize error log //finalize error log @@ -207,7 +208,12 @@ SyncStatusHandler::~SyncStatusHandler() else if (totalErrors > 0) { finalStatus = _("Synchronization completed with errors!"); - errorLog.logMsg(finalStatus, TYPE_WARNING); + errorLog.logMsg(finalStatus, TYPE_ERROR); + } + else if (totalWarnings > 0) + { + finalStatus = _("Synchronization completed with warnings!"); + errorLog.logMsg(finalStatus, TYPE_WARNING); //give status code same warning priority as display category! } else { @@ -250,6 +256,8 @@ SyncStatusHandler::~SyncStatusHandler() syncStatusFrame.processHasFinished(SyncStatus::RESULT_ABORTED, errorLog); //enable okay and close events else if (totalErrors > 0) syncStatusFrame.processHasFinished(SyncStatus::RESULT_FINISHED_WITH_ERROR, errorLog); + else if (totalWarnings > 0) + syncStatusFrame.processHasFinished(SyncStatus::RESULT_FINISHED_WITH_WARNINGS, errorLog); else syncStatusFrame.processHasFinished(SyncStatus::RESULT_FINISHED_WITH_SUCCESS, errorLog); } diff --git a/ui/main_dlg.cpp b/ui/main_dlg.cpp index 2d52d45e..0004d2fc 100644 --- a/ui/main_dlg.cpp +++ b/ui/main_dlg.cpp @@ -55,6 +55,7 @@ #include <wx+/no_flicker.h> #include <wx+/grid.h> #include "../lib/error_log.h" +#include "triple_splitter.h" using namespace zen; using namespace std::rel_ops; @@ -309,23 +310,23 @@ void setMenuItemImage(wxMenuItem*& menuItem, const wxBitmap& bmp) if (wxMenu* menu = menuItem->GetMenu()) { - int pos = menu->GetMenuItems().IndexOf(menuItem); - if (pos != wxNOT_FOUND) - { - /* - menu->Remove(item); ->this simple sequence crashes on Kubuntu x64, wxWidgets 2.9.2 - menu->Insert(index, item); - */ - const bool enabled = menuItem->IsEnabled(); - wxMenuItem* newItem = new wxMenuItem(menu, menuItem->GetId(), menuItem->GetItemLabel()); - newItem->SetBitmap(bmp); - - menu->Destroy(menuItem); //actual workaround - menuItem = menu->Insert(pos, newItem); //don't forget to update input item pointer! - - if (!enabled) - menuItem->Enable(false); //do not enable BEFORE appending item! wxWidgets screws up for yet another crappy reason - } + int pos = menu->GetMenuItems().IndexOf(menuItem); + if (pos != wxNOT_FOUND) + { + /* + menu->Remove(item); ->this simple sequence crashes on Kubuntu x64, wxWidgets 2.9.2 + menu->Insert(index, item); + */ + const bool enabled = menuItem->IsEnabled(); + wxMenuItem* newItem = new wxMenuItem(menu, menuItem->GetId(), menuItem->GetItemLabel()); + newItem->SetBitmap(bmp); + + menu->Destroy(menuItem); //actual workaround + menuItem = menu->Insert(pos, newItem); //don't forget to update input item pointer! + + if (!enabled) + menuItem->Enable(false); //do not enable BEFORE appending item! wxWidgets screws up for yet another crappy reason + } } } @@ -578,7 +579,7 @@ MainDialog::MainDialog(const xmlAccess::XmlGuiConfig& guiCfg, //init handling of first folder pair firstFolderPair.reset(new DirectoryPairFirst(*this)); - initViewFilterButtons(); + //initViewFilterButtons(); //init grid settings gridview::init(*m_gridMainL, *m_gridMainC, *m_gridMainR, gridDataView); @@ -783,6 +784,7 @@ void MainDialog::onQueryEndSession() catch (const xmlAccess::FfsXmlError&) {} } + void MainDialog::setGlobalCfgOnInit(const xmlAccess::XmlGlobalSettings& globalSettings) { globalCfg = globalSettings; @@ -930,7 +932,7 @@ void MainDialog::setSyncDirManually(const std::vector<FileSystemObject*>& select } -void MainDialog::setManualFilter(const std::vector<FileSystemObject*>& selection, bool setIncluded) +void MainDialog::setFilterManually(const std::vector<FileSystemObject*>& selection, bool setIncluded) { //if hidefiltered is active, there should be no filtered elements on screen => current element was filtered out assert(currentCfg.showFilteredElements || !setIncluded); @@ -940,7 +942,7 @@ void MainDialog::setManualFilter(const std::vector<FileSystemObject*>& selection std::for_each(selection.begin(), selection.end(), [&](FileSystemObject* fsObj) { zen::setActiveStatus(setIncluded, *fsObj); }); //works recursively for directories - updateGuiAfterFilterChange(400); //call this instead of updateGuiGrid() to add some delay if hideFiltered == true and to handle some graphical artifacts + updateGuiDelayedIf(!currentCfg.showFilteredElements); //show update GUI before removing rows } } @@ -1170,7 +1172,7 @@ void MainDialog::deleteSelectedFiles(const std::vector<FileSystemObject*>& selec if (!selectionLeft.empty() || !selectionRight.empty()) { wxWindow* oldFocus = wxWindow::FindFocus(); - ZEN_ON_SCOPE_EXIT( if (oldFocus) oldFocus->SetFocus(); ) + ZEN_ON_SCOPE_EXIT(if (oldFocus) oldFocus->SetFocus();) if (zen::showDeleteDialog(this, selectionLeft, @@ -1382,7 +1384,8 @@ void MainDialog::disableAllElements(bool enableAbort) //show abort button m_buttonAbort->Enable(); m_buttonAbort->Show(); - if (m_buttonAbort->IsShownOnScreen()) m_buttonAbort->SetFocus(); + if (m_buttonAbort->IsShownOnScreen()) + m_buttonAbort->SetFocus(); m_buttonCompare->Disable(); m_buttonCompare->Hide(); m_panelTopButtons->Layout(); @@ -1536,9 +1539,9 @@ void MainDialog::onTreeButtonEvent(wxKeyEvent& event) case WXK_SPACE: case WXK_NUMPAD_SPACE: { - const auto& selection = getTreeSelection(); + const std::vector<FileSystemObject*>& selection = getTreeSelection(); if (!selection.empty()) - setManualFilter(selection, !selection[0]->isActive()); + setFilterManually(selection, !selection[0]->isActive()); } return; @@ -1622,9 +1625,9 @@ void MainDialog::onGridButtonEvent(wxKeyEvent& event, Grid& grid, bool leftSide) case WXK_SPACE: case WXK_NUMPAD_SPACE: { - const auto& selection = getGridSelection(); + const std::vector<FileSystemObject*>& selection = getGridSelection(); if (!selection.empty()) - setManualFilter(selection, !selection[0]->isActive()); + setFilterManually(selection, !selection[0]->isActive()); } return; @@ -1718,14 +1721,16 @@ void MainDialog::OnGlobalKeyEvent(wxKeyEvent& event) //process key events withou if (!isPartOf(focus, m_gridMainL ) && // !isPartOf(focus, m_gridMainC ) && //don't propagate keyboard commands if grid is already in focus !isPartOf(focus, m_gridMainR ) && // + !isPartOf(focus, m_gridNavi ) && !isPartOf(focus, m_listBoxHistory) && //don't propagate if selecting config !isPartOf(focus, m_directoryLeft) && //don't propagate if changing directory field !isPartOf(focus, m_directoryRight) && - !isPartOf(focus, m_gridNavi ) && !isPartOf(focus, m_scrolledWindowFolderPairs)) - if (wxEvtHandler* evtHandler = m_gridMainL->GetEventHandler()) + if (wxEvtHandler* evtHandler = m_gridMainL->getMainWin().GetEventHandler()) { m_gridMainL->SetFocus(); + + event.SetEventType(wxEVT_KEY_DOWN); //the grid event handler doesn't expect wxEVT_CHAR_HOOK! evtHandler->ProcessEvent(event); //propagating event catched at wxTheApp to child leads to recursion, but we prevented it... event.Skip(false); //definitively handled now! return; @@ -1827,9 +1832,9 @@ void MainDialog::onNaviGridContext(GridClickEvent& event) if (!selection.empty()) { if (selection[0]->isActive()) - menu.addItem(_("Exclude temporarily") + L"\tSpace", [this, &selection] { setManualFilter(selection, false); }, &GlobalResources::getImage(L"checkboxFalse")); + menu.addItem(_("Exclude temporarily") + L"\tSpace", [this, &selection] { setFilterManually(selection, false); }, &GlobalResources::getImage(L"checkboxFalse")); else - menu.addItem(_("Include temporarily") + L"\tSpace", [this, &selection] { setManualFilter(selection, true); }, &GlobalResources::getImage(L"checkboxTrue")); + menu.addItem(_("Include temporarily") + L"\tSpace", [this, &selection] { setFilterManually(selection, true); }, &GlobalResources::getImage(L"checkboxTrue")); } else menu.addItem(_("Exclude temporarily") + L"\tSpace", [] {}, nullptr, false); @@ -1871,7 +1876,7 @@ void MainDialog::onMainGridContextC(GridClickEvent& event) menu.addItem(_("Exclude all"), [&] { zen::setActiveStatus(false, folderCmp); - updateGuiAfterFilterChange(400); //call this instead of updateGuiGrid() to add some delay if hideFiltered == true + updateGuiDelayedIf(!currentCfg.showFilteredElements); //show update GUI before removing rows }, nullptr, gridDataView->rowsTotal() > 0); menu.popup(*this); @@ -1919,9 +1924,9 @@ void MainDialog::onMainGridContextRim(bool leftSide) if (!selection.empty()) { if (selection[0]->isActive()) - menu.addItem(_("Exclude temporarily") + L"\tSpace", [this, &selection] { setManualFilter(selection, false); }, &GlobalResources::getImage(L"checkboxFalse")); + menu.addItem(_("Exclude temporarily") + L"\tSpace", [this, &selection] { setFilterManually(selection, false); }, &GlobalResources::getImage(L"checkboxFalse")); else - menu.addItem(_("Include temporarily") + L"\tSpace", [this, &selection] { setManualFilter(selection, true); }, &GlobalResources::getImage(L"checkboxTrue")); + menu.addItem(_("Include temporarily") + L"\tSpace", [this, &selection] { setFilterManually(selection, true); }, &GlobalResources::getImage(L"checkboxTrue")); } else menu.addItem(_("Exclude temporarily") + L"\tSpace", [] {}, nullptr, false); @@ -2163,7 +2168,8 @@ void MainDialog::onGridLabelContext(Grid& grid, ColumnTypeRim type, const std::v if (showSelectTimespanDlg(this, manualTimeSpanFrom, manualTimeSpanTo) == ReturnSmallDlg::BUTTON_OKAY) { applyTimeSpanFilter(folderCmp, manualTimeSpanFrom, manualTimeSpanTo); //overwrite current active/inactive settings - updateGuiAfterFilterChange(400); + //updateGuiDelayedIf(!currentCfg.showFilteredElements); //show update GUI before removing rows + updateGui(); } }; menu.addItem(_("Select time span..."), selectTimeSpan); @@ -2376,7 +2382,7 @@ void MainDialog::updateUnsavedCfgStatus() setImage(*m_bpButtonSave, allowSave ? GlobalResources::getImage(L"save") : makeBrightGrey(GlobalResources::getImage(L"save"))); m_bpButtonSave->Enable(allowSave); - m_menuItemSave->Enable(allowSave); //bitmap is automatically greyscaled on Win7 (introducing a crappy looking shift), but not on XP + m_menuItemSave->Enable(allowSave); //bitmap is automatically greyscaled on Win7 (introducing a crappy looking shift), but not on XP //set main dialog title wxString title; @@ -2551,31 +2557,50 @@ void MainDialog::OnLoadFromHistory(wxCommandEvent& event) }); if (!filenames.empty()) - { loadConfiguration(filenames); - //in case user cancelled saving old config, selection is wrong: so reapply it! - addFileToCfgHistory(activeConfigFiles); - } + //user changed m_listBoxHistory selection so it's this method's responsibility to synchronize with activeConfigFiles + //- if user cancelled saving old config + //- there's an error loading new config + //- filenames is empty and user tried to unselect the current config + addFileToCfgHistory(activeConfigFiles); } -void MainDialog::loadConfiguration(const wxString& filename) +void MainDialog::OnLoadFromHistoryDoubleClick(wxCommandEvent& event) { + wxArrayInt selections; + m_listBoxHistory->GetSelections(selections); + std::vector<wxString> filenames; - filenames.push_back(filename); + std::for_each(selections.begin(), selections.end(), + [&](int pos) + { + if (auto histData = dynamic_cast<const wxClientHistoryData*>(m_listBoxHistory->GetClientObject(pos))) + filenames.push_back(histData->cfgFile_); + }); - loadConfiguration(filenames); + if (!filenames.empty()) + if (loadConfiguration(filenames)) + { + //simulate button click on "compare" + wxCommandEvent dummy2(wxEVT_COMMAND_BUTTON_CLICKED); + if (wxEvtHandler* evtHandler = m_buttonCompare->GetEventHandler()) + evtHandler->ProcessEvent(dummy2); //synchronous call + } + + //synchronize m_listBoxHistory and activeConfigFiles, see OnLoadFromHistory() + addFileToCfgHistory(activeConfigFiles); } -void MainDialog::loadConfiguration(const std::vector<wxString>& filenames) +bool MainDialog::loadConfiguration(const std::vector<wxString>& filenames) { if (filenames.empty()) - return; + return true; if (!saveOldConfig()) - return; + return false; //cancelled by user //load XML xmlAccess::XmlGuiConfig newGuiCfg; //structure to receive gui settings, already defaulted!! @@ -2586,6 +2611,7 @@ void MainDialog::loadConfiguration(const std::vector<wxString>& filenames) setConfig(newGuiCfg, filenames); //flashStatusInformation(_("Configuration loaded!")); -> irrelvant!? + return true; } catch (const xmlAccess::FfsXmlError& error) { @@ -2597,6 +2623,7 @@ void MainDialog::loadConfiguration(const std::vector<wxString>& filenames) } else wxMessageBox(error.toString(), _("Error"), wxOK | wxICON_ERROR, this); + return false; } } @@ -2670,7 +2697,7 @@ void MainDialog::onCheckRows(CheckRowsEvent& event) selectedRows.insert(i); std::vector<FileSystemObject*> objects = gridDataView->getAllFileRef(selectedRows); - setManualFilter(objects, event.setIncluded_); + setFilterManually(objects, event.setIncluded_); } } @@ -2720,7 +2747,7 @@ void MainDialog::setConfig(const xmlAccess::XmlGuiConfig& newGuiCfg, const std:: //evaluate new settings... //(re-)set view filter buttons - initViewFilterButtons(); + initViewFilterButtons(newGuiCfg.mainCfg); updateFilterButtons(); @@ -2803,18 +2830,18 @@ const wxString& MainDialog::lastRunConfigName() } -void MainDialog::updateGuiAfterFilterChange(int delay) +void MainDialog::updateGuiDelayedIf(bool condition) { - //signal UI that grids need to be refreshed on next Update() + const int delay = 400; - if (!currentCfg.showFilteredElements) + if (condition) { gridview::refresh(*m_gridMainL, *m_gridMainC, *m_gridMainR); m_gridMainL->Update(); m_gridMainC->Update(); m_gridMainR->Update(); - wxMilliSleep(delay); //some delay to show user the rows he has filtered out before they are removed + wxMilliSleep(delay); //some delay to show the changed GUI before removing rows from sight } updateGui(); @@ -2982,7 +3009,7 @@ wxBitmap buttonReleased(const std::string& name) } -void MainDialog::initViewFilterButtons() +void MainDialog::initViewFilterButtons(const MainConfiguration& mainCfg) { //compare result buttons m_bpButtonLeftOnly->init(buttonPressed("leftOnly"), @@ -3061,7 +3088,6 @@ void MainDialog::initViewFilterButtons() m_bpButtonRightOnly-> setActive(true); m_bpButtonLeftNewer-> setActive(true); m_bpButtonRightNewer->setActive(true); - m_bpButtonEqual-> setActive(false); m_bpButtonDifferent-> setActive(true); m_bpButtonConflict-> setActive(true); @@ -3072,7 +3098,46 @@ void MainDialog::initViewFilterButtons() m_bpButtonSyncDeleteRight-> setActive(true); m_bpButtonSyncDirOverwLeft-> setActive(true); m_bpButtonSyncDirOverwRight->setActive(true); - m_bpButtonSyncDirNone-> setActive(true); + + m_bpButtonEqual->setActive(false); + + //special case "m_bpButtonSyncDirNone": set always active, unless sync direction "none" is part of the rule set: + //e.g. for a "custom" config or "update" variant. Otherwise rows with sync direction "none" can only occur on grid if the user manually + //sets them, in which case these rows should not be hidden immediately, so m_bpButtonSyncDirNone must be active + const std::vector<FolderPairCfg>& cmpCfg = extractCompareCfg(mainCfg); + const bool syncDirNonePartOfConfig = std::any_of(cmpCfg.begin(), cmpCfg.end(), + [&](const FolderPairCfg& fpCfg) -> bool + { + //attention: following is quite an amount of implicit/redundant knowledge here... let's hope our model is fundamental enough to not change any time soon! + + if (fpCfg.directionCfg.var == DirectionConfig::AUTOMATIC) + return false; + + const DirectionSet dirSet = extractDirections(fpCfg.directionCfg); + + switch (fpCfg.compareVar) + { + case CMP_BY_TIME_SIZE: + return dirSet.exLeftSideOnly == SYNC_DIR_NONE || + dirSet.exRightSideOnly == SYNC_DIR_NONE || + dirSet.leftNewer == SYNC_DIR_NONE || + dirSet.rightNewer == SYNC_DIR_NONE; + //dirSet.different == SYNC_DIR_NONE || + //dirSet.conflict == SYNC_DIR_NONE; + + case CMP_BY_CONTENT: + return dirSet.exLeftSideOnly == SYNC_DIR_NONE || + dirSet.exRightSideOnly == SYNC_DIR_NONE || + //dirSet.leftNewer == SYNC_DIR_NONE || + //dirSet.rightNewer == SYNC_DIR_NONE || + dirSet.different == SYNC_DIR_NONE; + //dirSet.conflict == SYNC_DIR_NONE; + } + assert(false); + return false; + }); + + m_bpButtonSyncDirNone->setActive(!syncDirNonePartOfConfig); } @@ -3105,7 +3170,7 @@ void MainDialog::OnCompare(wxCommandEvent& event) wxBusyCursor dummy; //show hourglass cursor wxWindow* oldFocus = wxWindow::FindFocus(); - ZEN_ON_SCOPE_EXIT( if (oldFocus) oldFocus->SetFocus(); ); //e.g. keep focus on main grid after pressing F5 + ZEN_ON_SCOPE_EXIT(if (oldFocus) oldFocus->SetFocus();) //e.g. keep focus on main grid after pressing F5 int scrollPosX = 0; int scrollPosY = 0; @@ -3169,7 +3234,7 @@ void MainDialog::OnCompare(wxCommandEvent& event) //add to folder history after successful comparison only folderHistoryLeft ->addItem(toZ(m_directoryLeft->GetValue())); folderHistoryRight->addItem(toZ(m_directoryRight->GetValue())); - + //prepare status information if (allElementsEqual(folderCmp)) flashStatusInformation(_("All folders are in sync!")); @@ -3378,11 +3443,6 @@ void MainDialog::OnStartSync(wxCommandEvent& event) syncProcessCfg, folderCmp, statusHandler); - - //play (optional) sound notification after sync has completed (GUI and batch mode) - const wxString soundFile = toWx(zen::getResourceDir()) + L"Sync_Complete.wav"; - if (fileExists(toZ(soundFile))) - wxSound::Play(soundFile, wxSOUND_ASYNC); } catch (GuiAbortProcess&) { @@ -3674,7 +3734,9 @@ void MainDialog::updateGridViewData() void MainDialog::applyFilterConfig() { applyFiltering(folderCmp, getConfig().mainCfg); - updateGuiAfterFilterChange(400); + + updateGui(); + //updateGuiDelayedIf(!currentCfg.showFilteredElements); //show update GUI before removing rows } @@ -3771,7 +3833,7 @@ void MainDialog::updateGuiForFolderPair() m_bpButtonLocalFilter->Show(showLocalCfgFirstPair); setImage(*m_bpButtonSwapSides, GlobalResources::getImage(showLocalCfgFirstPair ? L"swapSlim" : L"swap")); m_panelTopMiddle->Layout(); //both required to update button size for calculations below!!! - m_panelDirectoryPairs->Layout(); // -> updates size of stretched m_panelTopLeft! + m_panelDirectoryPairs->Layout(); // -> updates size of stretched m_panelTopLeft! int addPairMinimalHeight = 0; int addPairOptimalHeight = 0; diff --git a/ui/main_dlg.h b/ui/main_dlg.h index 47630670..f5f07624 100644 --- a/ui/main_dlg.h +++ b/ui/main_dlg.h @@ -18,6 +18,7 @@ #include "custom_grid.h" #include "tree_view.h" #include <wx+/file_drop.h> +#include "folder_history_box.h" //class FolderHistory; class DirectoryPair; @@ -77,8 +78,7 @@ private: void setGlobalCfgOnInit(const xmlAccess::XmlGlobalSettings& globalSettings); //messes with Maximize(), window sizes, so call just once! xmlAccess::XmlGlobalSettings getGlobalCfgBeforeExit(); //destructive "get" thanks to "Iconize(false), Maximize(false)" - void loadConfiguration(const wxString& filename); - void loadConfiguration(const std::vector<wxString>& filenames); + bool loadConfiguration(const std::vector<wxString>& filenames); //return true if loaded successfully bool trySaveConfig(const wxString* fileName); //return true if saved successfully bool saveOldConfig(); //return false on user abort @@ -89,10 +89,10 @@ private: //used when saving configuration std::vector<wxString> activeConfigFiles; //name of currently loaded config file (may be more than 1) - void initViewFilterButtons(); + void initViewFilterButtons(const zen::MainConfiguration& mainCfg); void updateFilterButtons(); - void addFileToCfgHistory(const std::vector<wxString>& filenames); + void addFileToCfgHistory(const std::vector<wxString>& filenames); //= update/insert + apply selection void addFolderPair(const std::vector<zen::FolderPairEnh>& newPairs, bool addFront = false); void removeAddFolderPair(size_t pos); @@ -102,6 +102,8 @@ private: //main method for putting gridDataView on UI: updates data respecting current view settings void updateGui(); //kitchen-sink update + void updateGuiDelayedIf(bool condition); // 400 ms delay + void updateGridViewData(); // void updateStatistics(); // more fine-grained updaters void updateUnsavedCfgStatus(); // @@ -111,7 +113,7 @@ private: std::vector<zen::FileSystemObject*> getTreeSelection() const; void setSyncDirManually(const std::vector<zen::FileSystemObject*>& selection, zen::SyncDirection direction); - void setManualFilter(const std::vector<zen::FileSystemObject*>& selection, bool setIncluded); + void setFilterManually(const std::vector<zen::FileSystemObject*>& selection, bool setIncluded); void copySelectionToClipboard(); void deleteSelectedFiles(const std::vector<zen::FileSystemObject*>& selectionLeft, const std::vector<zen::FileSystemObject*>& selectionRight); @@ -194,6 +196,7 @@ private: void OnConfigSaveAs (wxCommandEvent& event); void OnConfigLoad (wxCommandEvent& event); void OnLoadFromHistory(wxCommandEvent& event); + void OnLoadFromHistoryDoubleClick(wxCommandEvent& event); void OnCfgHistoryKeyEvent(wxKeyEvent& event); void OnRegularUpdateCheck(wxIdleEvent& event); @@ -212,8 +215,6 @@ private: void OnStartSync (wxCommandEvent& event); void OnClose (wxCloseEvent& event); - void updateGuiAfterFilterChange(int delay); - void excludeExtension(const Zstring& extension); void excludeShortname(const zen::FileSystemObject& fsObj); void excludeItems(const std::vector<zen::FileSystemObject*>& selection); diff --git a/ui/progress_indicator.cpp b/ui/progress_indicator.cpp index 096d7413..ebbc9edd 100644 --- a/ui/progress_indicator.cpp +++ b/ui/progress_indicator.cpp @@ -9,6 +9,7 @@ #include <wx/imaglist.h> #include <wx/stopwatch.h> #include <wx/wupdlock.h> +#include <wx/sound.h> #include <zen/basic_math.h> #include <zen/format_unit.h> #include <wx+/mouse_move_dlg.h> @@ -16,7 +17,9 @@ #include <wx+/image_tools.h> #include <wx+/graph.h> #include <wx+/no_flicker.h> +#include <zen/file_handling.h> #include "gui_generated.h" +#include "../lib/ffs_paths.h" #include "../lib/resources.h" #include "../lib/perf_check.h" #include "tray_icon.h" @@ -361,20 +364,19 @@ private: includedTypes |= TYPE_INFO; //fast replacement for wxString modelling exponential growth - typedef Zbase<wchar_t> zxString; - zxString logText; + MsgString logText; const auto& entries = log_.getEntries(); for (auto iter = entries.begin(); iter != entries.end(); ++iter) if (iter->type & includedTypes) { - logText += copyStringTo<zxString>(formatMessage(*iter)); + logText += formatMessage(*iter); logText += L'\n'; } if (logText.empty()) //if no messages match selected view filter, at least show final status message if (!entries.empty()) - logText = copyStringTo<zxString>(formatMessage(entries.back())); + logText = formatMessage(entries.back()); wxWindowUpdateLocker dummy(m_textCtrlInfo); m_textCtrlInfo->ChangeValue(copyStringTo<wxString>(logText)); @@ -810,6 +812,7 @@ std::wstring getDialogStatusText(const Statistics* syncStat, bool paused, SyncSt case SyncStatus::RESULT_ABORTED: return _("Aborted"); case SyncStatus::RESULT_FINISHED_WITH_ERROR: + case SyncStatus::RESULT_FINISHED_WITH_WARNINGS: case SyncStatus::RESULT_FINISHED_WITH_SUCCESS: return _("Completed"); } @@ -1007,16 +1010,14 @@ std::wstring SyncStatus::SyncStatusImpl::getExecWhenFinishedCommand() const void SyncStatus::SyncStatusImpl::updateDialogStatus() //depends on "syncStat_, paused_, finalResult" { - m_staticTextStatus->SetLabel(getDialogStatusText(syncStat_, paused_, finalResult)); + const wxString dlgStatusTxt = getDialogStatusText(syncStat_, paused_, finalResult); + m_staticTextStatus->SetLabel(dlgStatusTxt); + + //status bitmap if (syncStat_) //sync running { if (paused_) - m_buttonPause->SetLabel(_("Continue")); - else - m_buttonPause->SetLabel(_("Pause")); - - if (paused_) m_bitmapStatus->SetBitmap(GlobalResources::getImage(L"statusPause")); else switch (syncStat_->currentPhase()) @@ -1036,20 +1037,30 @@ void SyncStatus::SyncStatusImpl::updateDialogStatus() //depends on "syncStat_, p m_bitmapStatus->SetBitmap(GlobalResources::getImage(L"statusSyncing")); break; } + + m_bitmapStatus->SetToolTip(dlgStatusTxt); } else //sync finished switch (finalResult) { case RESULT_ABORTED: - m_bitmapStatus->SetBitmap(GlobalResources::getImage(L"statusError")); + m_bitmapStatus->SetBitmap(GlobalResources::getImage(L"statusAborted")); + m_bitmapStatus->SetToolTip(_("Synchronization aborted!")); break; case RESULT_FINISHED_WITH_ERROR: - m_bitmapStatus->SetBitmap(GlobalResources::getImage(L"statusWarning")); + m_bitmapStatus->SetBitmap(GlobalResources::getImage(L"statusFinishedErrors")); + m_bitmapStatus->SetToolTip(_("Synchronization completed with errors!")); + break; + + case RESULT_FINISHED_WITH_WARNINGS: + m_bitmapStatus->SetBitmap(GlobalResources::getImage(L"statusFinishedWarnings")); + m_bitmapStatus->SetToolTip(_("Synchronization completed with warnings!")); break; case RESULT_FINISHED_WITH_SUCCESS: - m_bitmapStatus->SetBitmap(GlobalResources::getImage(L"statusSuccess")); + m_bitmapStatus->SetBitmap(GlobalResources::getImage(L"statusFinishedSuccess")); + m_bitmapStatus->SetToolTip(_("Synchronization completed successfully!")); break; } @@ -1082,12 +1093,22 @@ void SyncStatus::SyncStatusImpl::updateDialogStatus() //depends on "syncStat_, p taskbar_->setStatus(Taskbar::STATUS_ERROR); break; + case RESULT_FINISHED_WITH_WARNINGS: case RESULT_FINISHED_WITH_SUCCESS: taskbar_->setStatus(Taskbar::STATUS_NORMAL); break; } } + //pause button + if (syncStat_) //sync running + { + if (paused_) + m_buttonPause->SetLabel(_("Continue")); + else + m_buttonPause->SetLabel(_("Pause")); + } + m_panelHeader->Layout(); Layout(); } @@ -1228,6 +1249,22 @@ void SyncStatus::SyncStatusImpl::processHasFinished(SyncResult resultId, const E m_panelFooter->Layout(); Layout(); + //play (optional) sound notification after sync has completed -> only play when waiting on results dialog, seems to be pointless otherwise! + switch (finalResult) + { + case SyncStatus::RESULT_ABORTED: + break; + case SyncStatus::RESULT_FINISHED_WITH_ERROR: + case SyncStatus::RESULT_FINISHED_WITH_WARNINGS: + case SyncStatus::RESULT_FINISHED_WITH_SUCCESS: + { + const Zstring soundFile = getResourceDir() + Zstr("Sync_Complete.wav"); + if (fileExists(soundFile)) + wxSound::Play(utfCvrtTo<wxString>(soundFile), wxSOUND_ASYNC); //warning: this may fail and show a wxWidgets error message! => must not play when running FFS as a service! + } + break; + } + //Raise(); -> don't! user may be watching a movie in the meantime ;) } diff --git a/ui/progress_indicator.h b/ui/progress_indicator.h index 5628694f..b3d7ff2f 100644 --- a/ui/progress_indicator.h +++ b/ui/progress_indicator.h @@ -61,6 +61,7 @@ public: { RESULT_ABORTED, RESULT_FINISHED_WITH_ERROR, + RESULT_FINISHED_WITH_WARNINGS, RESULT_FINISHED_WITH_SUCCESS }; //essential to call one of these two methods in StatusUpdater derived class destructor at the LATEST(!) diff --git a/ui/search.cpp b/ui/search.cpp index fd3a11c5..be384f74 100644 --- a/ui/search.cpp +++ b/ui/search.cpp @@ -9,6 +9,7 @@ #include <wx/msgdlg.h> #include <wx/utils.h> #include <utility> +#include <zen/string_tools.h> #include <wx+/mouse_move_dlg.h> using namespace zen; diff --git a/ui/sync_cfg.cpp b/ui/sync_cfg.cpp index d159503d..ef9d2a68 100644 --- a/ui/sync_cfg.cpp +++ b/ui/sync_cfg.cpp @@ -181,7 +181,7 @@ void updateConfigIcons(const DirectionConfig& directionCfg, buttonConflict->SetToolTip(getSyncOpDescription(SO_OVERWRITE_LEFT)); break; case SYNC_DIR_NONE: - buttonConflict->SetBitmapLabel(mirrorIfRtl(GlobalResources::getImage(L"conflict"))); + buttonConflict->SetBitmapLabel(mirrorIfRtl(GlobalResources::getImage(L"conflict"))); //silent dependency to algorithm.cpp::Redetermine!!! buttonConflict->SetToolTip(_("Leave as unresolved conflict")); break; } diff --git a/version/version.h b/version/version.h index 47247163..96636e6d 100644 --- a/version/version.h +++ b/version/version.h @@ -3,7 +3,7 @@ namespace zen { -const wchar_t currentVersion[] = L"5.8"; //internal linkage! +const wchar_t currentVersion[] = L"5.9"; //internal linkage! } #endif diff --git a/version/version.rc b/version/version.rc index 2ce4a64f..9ef55dc3 100644 --- a/version/version.rc +++ b/version/version.rc @@ -1,2 +1,2 @@ -#define FREEFILESYNC_VER 5,8,0,0 -#define FREEFILESYNC_VER_STR "5.8\0" +#define FREEFILESYNC_VER 5,9,0,0 +#define FREEFILESYNC_VER_STR "5.9\0" diff --git a/wx+/button.h b/wx+/button.h index 6aba0788..5a704840 100644 --- a/wx+/button.h +++ b/wx+/button.h @@ -24,9 +24,9 @@ public: const wxValidator& validator = wxDefaultValidator, const wxString& name = wxButtonNameStr); - void setBitmapFront(const wxBitmap& bitmap, size_t spaceAfter = 0); + void setBitmapFront(const wxBitmap& bitmap, size_t spaceAfter = 0); //...and enlarge button if required! void setTextLabel( const wxString& text); - void setBitmapBack( const wxBitmap& bitmap, size_t spaceBefore = 0); + void setBitmapBack( const wxBitmap& bitmap, size_t spaceBefore = 0); // private: wxBitmap createBitmapFromText(const wxString& text); diff --git a/wx+/graph.cpp b/wx+/graph.cpp index a50f3c34..6fefeb93 100644 --- a/wx+/graph.cpp +++ b/wx+/graph.cpp @@ -98,7 +98,7 @@ void drawYLabel(wxDC& dc, double& yMin, double& yMax, const wxRect& clientArea, int optimalBlockHeight = 3 * dc.GetMultiLineTextExtent(L"1").GetHeight(); - double valRangePerBlock = (yMax - yMin) * optimalBlockHeight / clientArea.GetHeight(); + double valRangePerBlock = (yMax - yMin) * optimalBlockHeight / clientArea.GetHeight(); //proposal valRangePerBlock = labelFmt.getOptimalBlockSize(valRangePerBlock); if (isNull(valRangePerBlock)) return; @@ -150,7 +150,7 @@ void drawXLabel(wxDC& dc, double& xMin, double& xMax, const wxRect& clientArea, const int optimalBlockWidth = dc.GetMultiLineTextExtent(L"100000000000000").GetWidth(); - double valRangePerBlock = (xMax - xMin) * optimalBlockWidth / clientArea.GetWidth(); + double valRangePerBlock = (xMax - xMin) * optimalBlockWidth / clientArea.GetWidth(); //proposal valRangePerBlock = labelFmt.getOptimalBlockSize(valRangePerBlock); if (isNull(valRangePerBlock)) return; @@ -224,13 +224,11 @@ void subsample(StdCont& cont, size_t factor) { if (factor <= 1) return; - typedef typename StdCont::iterator IterType; + auto iterOut = cont.begin(); + for (auto iterIn = cont.begin(); cont.end() - iterIn >= static_cast<ptrdiff_t>(factor); iterIn += factor) //don't even let iterator point out of range! + *iterOut++ = std::accumulate(iterIn, iterIn + factor, 0.0) / static_cast<double>(factor); - IterType posWrite = cont.begin(); - for (IterType posRead = cont.begin(); cont.end() - posRead >= static_cast<int>(factor); posRead += factor) //don't even let iterator point out of range! - *posWrite++ = std::accumulate(posRead, posRead + factor, 0.0) / static_cast<double>(factor); - - cont.erase(posWrite, cont.end()); + cont.erase(iterOut, cont.end()); } } @@ -542,11 +540,11 @@ void Graph2D::render(wxDC& dc) const for (GraphList::const_iterator j = curves_.begin(); j != curves_.end(); ++j) { std::vector<double>& yValues = yValuesList[j - curves_.begin()].first; //actual y-values - int offset = yValuesList[j - curves_.begin()].second; //x-value offset in pixel + int offsetX = yValuesList[j - curves_.begin()].second; //x-value offset in pixel std::vector<wxPoint> curve; for (std::vector<double>::const_iterator i = yValues.begin(); i != yValues.end(); ++i) - curve.push_back(wxPoint(i - yValues.begin() + offset, + curve.push_back(wxPoint(i - yValues.begin() + offsetX, dataArea.height - 1 - cvrtY.realToScreen(*i)) + dataOrigin); //screen y axis starts upper left if (!curve.empty()) diff --git a/wx+/grid.cpp b/wx+/grid.cpp index 9f94b7f0..22a8bba1 100644 --- a/wx+/grid.cpp +++ b/wx+/grid.cpp @@ -251,6 +251,18 @@ void drawTextLabelFitting(wxDC& dc, const wxString& text, const wxRect& rect, in { DcClipper clip(dc, rect); //wxDC::DrawLabel doesn't care about width, WTF? + /* + performance notes: + wxDC::DrawLabel() is implemented in terms of both wxDC::GetMultiLineTextExtent() and wxDC::DrawText() + wxDC::GetMultiLineTextExtent() is implemented in terms of wxDC::GetTextExtent() + + average total times: + Windows Linux + single wxDC::DrawText() 7µs 50µs + wxDC::DrawLabel() + 10µs 90µs + repeated GetTextExtent() + */ + //truncate large texts and add ellipsis auto textFits = [&](const wxString& phrase) { return dc.GetTextExtent(phrase).GetWidth() <= rect.GetWidth(); }; dc.DrawLabel(getTruncatedText(text, textFits), rect, alignment); @@ -355,9 +367,9 @@ public: Connect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler(SubWindow::onLeaveWindow ), nullptr, this); Connect(wxEVT_MOUSE_CAPTURE_LOST, wxMouseCaptureLostEventHandler(SubWindow::onMouseCaptureLost), nullptr, this); - Connect(wxEVT_CHAR, wxKeyEventHandler(SubWindow::onChar ), nullptr, this); - Connect(wxEVT_KEY_UP, wxKeyEventHandler(SubWindow::onKeyUp ), nullptr, this); - Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(SubWindow::onKeyDown), nullptr, this); + Connect(wxEVT_CHAR, wxKeyEventHandler(SubWindow::onChar ), nullptr, this); + Connect(wxEVT_KEY_UP, wxKeyEventHandler(SubWindow::onKeyUp ), nullptr, this); + Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(SubWindow::onKeyDown), nullptr, this); } Grid& refParent() { return parent_; } @@ -385,8 +397,8 @@ protected: { //wxWidgets bug: tooltip multiline property is defined by first tooltip text containing newlines or not (same is true for maximum width) if (!tt) - SetToolTip(new wxToolTip(wxT("a b\n\ - a b"))); //ugly, but is working (on Windows) + SetToolTip(new wxToolTip(L"a b\n\ + a b")); //ugly, but is working (on Windows) tt = GetToolTip(); //should be bound by now if (tt) tt->SetTip(text); @@ -966,6 +978,7 @@ private: const int rowHeight = rowLabelWin_.getRowHeight(); + //why again aren't we using RowLabelWin::getRowsOnClient() here? const wxPoint topLeft = refParent().CalcUnscrolledPosition(rect.GetTopLeft()); const wxPoint bottomRight = refParent().CalcUnscrolledPosition(rect.GetBottomRight()); @@ -990,10 +1003,10 @@ private: drawBackground(*prov, dc, wxRect(cellAreaTL + wxPoint(0, row * rowHeight), wxSize(compWidth, rowHeight)), row, compPos); } - //draw single cells + //draw single cells, column by column for (auto iterCol = iterComp->begin(); iterCol != iterComp->end(); ++iterCol) { - const int width = iterCol->width_; //don't use unsigned for calculations! + const int width = iterCol->width_; //don't use unsigned for calculations! if (cellAreaTL.x > rect.GetRight()) return; //done @@ -1629,6 +1642,9 @@ void Grid::scrollDelta(int deltaX, int deltaY) scrollPosX += deltaX; scrollPosY += deltaY; + scrollPosX = std::max(0, scrollPosX); //wxScrollHelper::Scroll() will exit prematurely if input happens to be "-1"! + scrollPosY = std::max(0, scrollPosY); // + //const int unitsTotalX = GetScrollLines(wxHORIZONTAL); //const int unitsTotalY = GetScrollLines(wxVERTICAL); @@ -1819,7 +1835,9 @@ void Grid::SetScrollbar(int orientation, int position, int thumbSize, int range, WXLRESULT Grid::MSWDefWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) { - if (nMsg == WM_MOUSEHWHEEL) + //we land here if wxWindowMSW::MSWWindowProc() couldn't handle the message + //http://msdn.microsoft.com/en-us/library/windows/desktop/ms645614(v=vs.85).aspx + if (nMsg == WM_MOUSEHWHEEL) //horizontal wheel { const int distance = GET_WHEEL_DELTA_WPARAM(wParam); const int delta = WHEEL_DELTA; @@ -1830,8 +1848,8 @@ WXLRESULT Grid::MSWDefWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) if (!::SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &linesPerRotation, 0)) linesPerRotation = 3; - SetFocus(); scrollDelta(rotations * linesPerRotation, 0); //in scroll units + return 0; //"If an application processes this message, it should return zero." } return wxScrolledWindow::MSWDefWindowProc(nMsg, wParam, lParam); @@ -18,7 +18,7 @@ namespace zen { -typedef enum { DUMMY_COLUMN_TYPE = static_cast<size_t>(-1) } ColumnType; +typedef enum { DUMMY_COLUMN_TYPE = static_cast<unsigned int>(-1) } ColumnType; //----- Events ----------------------------------------------------------------------------------------------- extern const wxEventType EVENT_GRID_COL_LABEL_MOUSE_LEFT; //generates: GridClickEvent diff --git a/wx+/zlib_wrap.h b/wx+/zlib_wrap.h index 7c805e23..a5ad2cb1 100644 --- a/wx+/zlib_wrap.h +++ b/wx+/zlib_wrap.h @@ -101,7 +101,7 @@ BinContainer decompress(const BinContainer& stream) //throw ZlibInternalError if (uncompressedSize == 0) //cannot be 0: compress() directly maps empty -> empty container skipping zlib! throw ZlibInternalError(); - contOut.resize(uncompressedSize); //throw std::bad_alloc + contOut.resize(static_cast<size_t>(uncompressedSize)); //throw std::bad_alloc } catch (std::bad_alloc&) //most likely due to data corruption! { @@ -112,7 +112,7 @@ BinContainer decompress(const BinContainer& stream) //throw ZlibInternalError stream.size() - sizeof(uncompressedSize), &*contOut.begin(), uncompressedSize); //throw ZlibInternalError - if (bytesWritten != uncompressedSize) + if (bytesWritten != static_cast<size_t>(uncompressedSize)) throw ZlibInternalError(); } return contOut; diff --git a/zen/IFileOperation/file_op.cpp b/zen/IFileOperation/file_op.cpp index 8acc3d17..3591de12 100644 --- a/zen/IFileOperation/file_op.cpp +++ b/zen/IFileOperation/file_op.cpp @@ -13,6 +13,7 @@ #include <zen/com_ptr.h> #include <zen/com_error.h> #include <zen/scope_guard.h> +#include <zen/stl_tools.h> #include <boost/thread/tss.hpp> @@ -29,8 +30,140 @@ using namespace zen; namespace { -void moveToRecycleBin(const wchar_t* fileNames[], //throw ComError - size_t fileNo) //size of fileNames array +struct Win32Error +{ + Win32Error(DWORD errorCode) : errorCode_(errorCode) {} + DWORD errorCode_; +}; + +std::vector<std::wstring> getLockingProcesses(const wchar_t* filename); //throw Win32Error + + +class RecyclerProgressCallback : public IFileOperationProgressSink +{ + //Sample implementation: %ProgramFiles%\Microsoft SDKs\Windows\v7.1\Samples\winui\shell\appplatform\FileOperationProgressSink + + ~RecyclerProgressCallback() {} //private: do not allow stack usage "thanks" to IUnknown lifetime management! + +public: + RecyclerProgressCallback(fileop::RecyclerCallback callback, void* sink) : + cancellationRequested(false), + callback_(callback), + sink_(sink), + refCount(1) {} + + //IUnknown: reference implementation according to: http://msdn.microsoft.com/en-us/library/office/cc839627.aspx + virtual ULONG STDMETHODCALLTYPE AddRef() + { + return ::InterlockedIncrement(&refCount); + } + + virtual ULONG STDMETHODCALLTYPE Release() + { + ULONG newRefCount = ::InterlockedDecrement(&refCount); + if (newRefCount == 0) //race condition caveat: do NOT check refCount, which might have changed already! + delete this; + return newRefCount; + } + + virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void __RPC_FAR* __RPC_FAR* ppvObject) + { + if (!ppvObject) + return E_INVALIDARG; + + if (riid == IID_IUnknown || riid == IID_IFileOperationProgressSink) + { + *ppvObject = this; + AddRef(); + return S_OK; + } + *ppvObject = NULL; + return E_NOINTERFACE; + } + + //IFileOperationProgressSink + virtual HRESULT STDMETHODCALLTYPE StartOperations() { return S_OK; } + virtual HRESULT STDMETHODCALLTYPE FinishOperations(HRESULT hrResult) { return S_OK; } + virtual HRESULT STDMETHODCALLTYPE PreRenameItem(DWORD dwFlags, __RPC__in_opt IShellItem* psiItem, __RPC__in_opt_string LPCWSTR pszNewName) { return S_OK; } + virtual HRESULT STDMETHODCALLTYPE PostRenameItem(DWORD dwFlags, __RPC__in_opt IShellItem* psiItem, __RPC__in_string LPCWSTR pszNewName, HRESULT hrRename, __RPC__in_opt IShellItem* psiNewlyCreated) { return S_OK; } + virtual HRESULT STDMETHODCALLTYPE PreMoveItem(DWORD dwFlags, __RPC__in_opt IShellItem* psiItem, __RPC__in_opt IShellItem* psiDestinationFolder, __RPC__in_opt_string LPCWSTR pszNewName) { return S_OK; } + virtual HRESULT STDMETHODCALLTYPE PostMoveItem(DWORD dwFlags, __RPC__in_opt IShellItem* psiItem, __RPC__in_opt IShellItem* psiDestinationFolder, __RPC__in_opt_string LPCWSTR pszNewName, HRESULT hrMove, __RPC__in_opt IShellItem* psiNewlyCreated) { return S_OK; } + virtual HRESULT STDMETHODCALLTYPE PreCopyItem(DWORD dwFlags, __RPC__in_opt IShellItem* psiItem, __RPC__in_opt IShellItem* psiDestinationFolder, __RPC__in_opt_string LPCWSTR pszNewName) { return S_OK; } + virtual HRESULT STDMETHODCALLTYPE PostCopyItem(DWORD dwFlags, __RPC__in_opt IShellItem* psiItem, __RPC__in_opt IShellItem* psiDestinationFolder, __RPC__in_opt_string LPCWSTR pszNewName, HRESULT hrCopy, __RPC__in_opt IShellItem* psiNewlyCreated) { return S_OK; } + virtual HRESULT STDMETHODCALLTYPE PreNewItem(DWORD dwFlags, __RPC__in_opt IShellItem* psiDestinationFolder, __RPC__in_opt_string LPCWSTR pszNewName) { return S_OK; } + virtual HRESULT STDMETHODCALLTYPE PostNewItem(DWORD dwFlags, __RPC__in_opt IShellItem* psiDestinationFolder, __RPC__in_opt_string LPCWSTR pszNewName, __RPC__in_opt_string LPCWSTR pszTemplateName, DWORD dwFileAttributes, HRESULT hrNew, __RPC__in_opt IShellItem* psiNewItem) { return S_OK; } + + virtual HRESULT STDMETHODCALLTYPE PreDeleteItem(DWORD dwFlags, __RPC__in_opt IShellItem* psiItem) + { + if (psiItem) + { + LPWSTR itemPath = nullptr; + HRESULT hr = psiItem->GetDisplayName(SIGDN_FILESYSPATH, &itemPath); + if (FAILED(hr)) + return hr; + ZEN_ON_SCOPE_EXIT(::CoTaskMemFree(itemPath)); + + currentItem = itemPath; + } + //"Returns S_OK if successful, or an error value otherwise. In the case of an error value, the delete operation + //and all subsequent operations pending from the call to IFileOperation are canceled." + return cancellationRequested ? HRESULT_FROM_WIN32(ERROR_CANCELLED) : S_OK; + } + + virtual HRESULT STDMETHODCALLTYPE PostDeleteItem(DWORD dwFlags, + __RPC__in_opt IShellItem* psiItem, + HRESULT hrDelete, + __RPC__in_opt IShellItem* psiNewlyCreated) + { + if (FAILED(hrDelete)) + lastError = make_unique<std::pair<std::wstring, HRESULT>>(currentItem, hrDelete); + + currentItem.clear(); + //"Returns S_OK if successful, or an error value otherwise. In the case of an error value, + //all subsequent operations pending from the call to IFileOperation are canceled." + return cancellationRequested ? HRESULT_FROM_WIN32(ERROR_CANCELLED) : S_OK; + } + + virtual HRESULT STDMETHODCALLTYPE UpdateProgress(UINT iWorkTotal, UINT iWorkSoFar) + { + if (callback_) + try + { + if (!callback_(currentItem.c_str(), sink_)) //should not throw! + cancellationRequested = true; + } + catch (...) { return E_UNEXPECTED; } + //"If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code." + //-> this probably means, we cannot rely on returning a custom error code here and have IFileOperation::PerformOperations() fail with same + //=> defer cancellation to PreDeleteItem()/PostDeleteItem() + return S_OK; + } + virtual HRESULT STDMETHODCALLTYPE ResetTimer() { return S_OK; } + virtual HRESULT STDMETHODCALLTYPE PauseTimer() { return S_OK; } + virtual HRESULT STDMETHODCALLTYPE ResumeTimer() { return S_OK; } + + //call after IFileOperation::PerformOperations() + const std::pair<std::wstring, HRESULT>* getLastError() const { return lastError.get(); } //(file path, error code) + +private: + std::wstring currentItem; + bool cancellationRequested; + + std::unique_ptr<std::pair<std::wstring, HRESULT>> lastError; + + //file_op user callback + fileop::RecyclerCallback callback_; + void* sink_; + + //support IUnknown + LONG refCount; +}; + + +void moveToRecycleBin(const wchar_t* fileNames[], //throw ComError + size_t fileCount, + fileop::RecyclerCallback callback, + void* sink) { ComPtr<IFileOperation> fileOp; ZEN_CHECK_COM(::CoCreateInstance(CLSID_FileOperation, //throw ComError @@ -38,21 +171,48 @@ void moveToRecycleBin(const wchar_t* fileNames[], //throw ComError CLSCTX_ALL, IID_PPV_ARGS(fileOp.init()))); - // Set the operation flags. Turn off all UI - // from being shown to the user during the - // operation. This includes error, confirmation - // and progress dialogs. - ZEN_CHECK_COM(fileOp->SetOperationFlags(FOF_ALLOWUNDO | //throw ComError + // Set the operation flags. Turn off all UI from being shown to the user during the + // operation. This includes error, confirmation and progress dialogs. + ZEN_CHECK_COM(fileOp->SetOperationFlags(FOF_ALLOWUNDO | FOF_NOCONFIRMATION | - FOF_SILENT | + FOF_SILENT | //no progress dialog box FOF_NOERRORUI | - FOFX_EARLYFAILURE | + FOFX_EARLYFAILURE | + //without FOFX_EARLYFAILURE, IFileOperationProgressSink::PostDeleteItem() will always report success, even if deletion failed!!? WTF!? + //PerformOperations() will still succeed but set the uselessly generic GetAnyOperationsAborted() instead :((( + //=> always set FOFX_EARLYFAILURE since we prefer good error messages over "doing as much as possible" + //luckily for FreeFileSync we don't expect failures on individual files anyway: FreeFileSync moves files to be + //deleted to a temporary folder first, so there is no reason why a second move (the recycling itself) should fail FOF_NO_CONNECTED_ELEMENTS)); + //use FOFX_RECYCLEONDELETE when Windows 8 is available!? + + ComPtr<RecyclerProgressCallback> opProgress; + *opProgress.init() = new (std::nothrow) RecyclerProgressCallback(callback, sink); + if (!opProgress) + throw ComError(L"Error creating RecyclerProgressCallback.", E_OUTOFMEMORY); + + DWORD callbackID = 0; + ZEN_CHECK_COM(fileOp->Advise(opProgress.get(), &callbackID)); + ZEN_ON_SCOPE_EXIT(fileOp->Unadvise(callbackID)); //RecyclerProgressCallback might outlive current scope, so cut access to "callback, sink" int operationCount = 0; - for (size_t i = 0; i < fileNo; ++i) + for (size_t i = 0; i < fileCount; ++i) { + //SHCreateItemFromParsingName() physically checks file existence => callback + if (callback) + { + bool continueExecution = false; + try + { + continueExecution = callback(fileNames[i], sink); //should not throw! + } + catch (...) { throw ComError(L"Unexpected exception in callback.", E_UNEXPECTED); } + + if (!continueExecution) + throw ComError(L"Operation cancelled.", HRESULT_FROM_WIN32(ERROR_CANCELLED)); + } + //create file/folder item object ComPtr<IShellItem> psiFile; HRESULT hr = ::SHCreateItemFromParsingName(fileNames[i], @@ -63,7 +223,7 @@ void moveToRecycleBin(const wchar_t* fileNames[], //throw ComError if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) || //file not existing anymore hr == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)) continue; - throw ComError(std::wstring(L"Error calling \"SHCreateItemFromParsingName\" for file:\n") + L"\"" + fileNames[i] + L"\".", hr); + throw ComError(std::wstring(L"Error calling \"SHCreateItemFromParsingName\" for file:\n") + L"\'" + fileNames[i] + L"\'.", hr); } ZEN_CHECK_COM(fileOp->DeleteItem(psiFile.get(), nullptr)); @@ -71,13 +231,37 @@ void moveToRecycleBin(const wchar_t* fileNames[], //throw ComError ++operationCount; } - if (operationCount == 0) //calling PerformOperations() without anything to do would result in E_UNEXPECTED + if (operationCount == 0) //calling PerformOperations() without anything to do would yielt E_UNEXPECTED return; - //perform actual operations - ZEN_CHECK_COM(fileOp->PerformOperations()); + //perform planned operations + try + { + ZEN_CHECK_COM(fileOp->PerformOperations()); + } + catch (const ComError&) + { + //first let's check if we have more detailed error information available + if (const std::pair<std::wstring, HRESULT>* lastError = opProgress->getLastError()) + { + try //create an even better error message if we detect a locking issue: + { + std::vector<std::wstring> processes = getLockingProcesses(lastError->first.c_str()); //throw Win32Error + if (!processes.empty()) + { + std::wstring msg = L"The file \'" + lastError->first + L"\' is locked by another process:"; + std::for_each(processes.begin(), processes.end(), [&](const std::wstring& proc) { msg += L'\n'; msg += proc; }); + throw ComError(msg); //message is already descriptive enough, no need to add the HRESULT code + } + } + catch (const Win32Error&) {} - //check if errors occured: if FOFX_EARLYFAILURE is not used, PerformOperations() can return with success despite errors! + throw ComError(std::wstring(L"Error during \"PerformOperations\" for file:\n") + L"\'" + lastError->first + L"\'.", lastError->second); + } + throw; + } + + //if FOF_NOERRORUI without FOFX_EARLYFAILURE is set, PerformOperations() can return with success despite errors, but sets the following "aborted" flag instead BOOL pfAnyOperationsAborted = FALSE; ZEN_CHECK_COM(fileOp->GetAnyOperationsAborted(&pfAnyOperationsAborted)); @@ -110,7 +294,7 @@ void copyFile(const wchar_t* sourceFile, //throw ComError nullptr, IID_PPV_ARGS(psiSourceFile.init())); if (FAILED(hr)) - throw ComError(std::wstring(L"Error calling \"SHCreateItemFromParsingName\" for file:\n") + L"\"" + sourceFile + L"\".", hr); + throw ComError(std::wstring(L"Error calling \"SHCreateItemFromParsingName\" for file:\n") + L"\'" + sourceFile + L"\'.", hr); } const size_t pos = std::wstring(targetFile).find_last_of(L'\\'); @@ -127,7 +311,7 @@ void copyFile(const wchar_t* sourceFile, //throw ComError nullptr, IID_PPV_ARGS(psiTargetFolder.init())); if (FAILED(hr)) - throw ComError(std::wstring(L"Error calling \"SHCreateItemFromParsingName\" for folder:\n") + L"\"" + targetFolder + L"\".", hr); + throw ComError(std::wstring(L"Error calling \"SHCreateItemFromParsingName\" for folder:\n") + L"\'" + targetFolder + L"\'.", hr); } //schedule file copy operation @@ -168,12 +352,6 @@ void getFolderClsid(const wchar_t* dirname, CLSID& pathCLSID) //throw ComError } -struct Win32Error -{ - Win32Error(DWORD errorCode) : errorCode_(errorCode) {} - DWORD errorCode_; -}; - std::vector<std::wstring> getLockingProcesses(const wchar_t* filename) //throw Win32Error { wchar_t sessionKey[CCH_RM_SESSION_KEY + 1] = {}; //fixes two bugs: http://blogs.msdn.com/b/oldnewthing/archive/2012/02/17/10268840.aspx @@ -260,11 +438,14 @@ boost::thread_specific_ptr<std::wstring> lastErrorMessage; //use "thread_local" } -bool fileop::moveToRecycleBin(const wchar_t* fileNames[], size_t fileNo) //size of fileNames array +bool fileop::moveToRecycleBin(const wchar_t* fileNames[], + size_t fileCount, + RecyclerCallback callback, + void* sink) { try { - ::moveToRecycleBin(fileNames, fileNo); //throw ComError + ::moveToRecycleBin(fileNames, fileCount, callback, sink); //throw ComError return true; } catch (const ComError& e) @@ -318,15 +499,11 @@ bool fileop::getLockingProcesses(const wchar_t* filename, const wchar_t*& procLi { try { - std::vector<std::wstring> result = ::getLockingProcesses(filename); //throw Win32Error + std::vector<std::wstring> processes = ::getLockingProcesses(filename); //throw Win32Error std::wstring buffer; - for (auto iter = result.begin(); iter != result.end(); ++iter) - { - buffer += *iter; - buffer += L'\n'; - } - if (!buffer.empty()) + std::for_each(processes.begin(), processes.end(), [&](const std::wstring& proc) { buffer += proc; buffer += L'\n'; }); + if (!processes.empty()) buffer.resize(buffer.size() - 1); //remove last line break auto tmp = new wchar_t [buffer.size() + 1]; //bad_alloc ? diff --git a/zen/IFileOperation/file_op.h b/zen/IFileOperation/file_op.h index 86efc340..2f5d6f30 100644 --- a/zen/IFileOperation/file_op.h +++ b/zen/IFileOperation/file_op.h @@ -25,9 +25,15 @@ namespace fileop //COM needs to be initialized before calling any of these functions! CoInitializeEx/CoUninitialize //minimum OS: Windows Vista or later +//return false to abort operation +typedef bool (*RecyclerCallback)(const wchar_t* filename, //current item; may be empty string! + void* sink); //virtual function mechanism is not guaranteed to be compatible between different compilers, therefore we go the C-way + DLL_FUNCTION_DECLARATION bool moveToRecycleBin(const wchar_t* fileNames[], - size_t fileNo); //size of fileNames array + size_t fileCount, //size of fileNames array + RecyclerCallback callback, //optional + void* sink); // DLL_FUNCTION_DECLARATION bool copyFile(const wchar_t* sourceFile, @@ -49,7 +55,10 @@ const wchar_t* getLastError(); //no nullptr check required! /*---------- |typedefs| ----------*/ -typedef bool (*FunType_moveToRecycleBin)(const wchar_t* fileNames[], size_t fileNo); +typedef bool (*FunType_moveToRecycleBin)(const wchar_t* fileNames[], + size_t fileCount, + RecyclerCallback callback, + void* sink); typedef bool (*FunType_copyFile)(const wchar_t* sourceFile, const wchar_t* targetFile); typedef bool (*FunType_checkRecycler)(const wchar_t* dirname, bool& isRecycler); typedef bool (*FunType_getLockingProcesses)(const wchar_t* filename, const wchar_t*& procList); diff --git a/zen/com_error.h b/zen/com_error.h index eaa7744f..cd643b49 100644 --- a/zen/com_error.h +++ b/zen/com_error.h @@ -202,7 +202,7 @@ inline std::wstring numberToHexString(long number) { wchar_t result[100]; - ::swprintf(result, 100, L"0x%08x", number); + ::swprintf(result, 100, L"0x%08x", static_cast<int>(number)); return std::wstring(result); } diff --git a/zen/com_ptr.h b/zen/com_ptr.h index bb52b5cb..030a0801 100644 --- a/zen/com_ptr.h +++ b/zen/com_ptr.h @@ -34,16 +34,16 @@ template <class T> class ComPtr { public: - ComPtr() : ptr(nullptr) {} + ComPtr() : ptr(nullptr) {} // + ComPtr(const ComPtr& other) : ptr(other.ptr) { if (ptr) ptr->AddRef(); } //noexcept in C++11 + ComPtr( ComPtr&& other) : ptr(other.ptr) { other.ptr = nullptr; } // + ~ComPtr() { if (ptr) ptr->Release(); } //has exception spec of compiler-generated destructor by default - ComPtr(const ComPtr& other) : ptr(other.ptr) { if (ptr) ptr->AddRef(); } - ComPtr( ComPtr&& other) : ptr(other.ptr) { other.ptr = nullptr; } + ComPtr& operator=(const ComPtr& other) { ComPtr(other).swap(*this); return *this; } //noexcept in C++11 + ComPtr& operator=(ComPtr&& tmp) { swap(tmp); return *this; } // + //don't use unifying assignment but save one move-construction in the r-value case instead! - ~ComPtr() { if (ptr) ptr->Release(); } - - ComPtr& operator=(ComPtr other) { swap(other); return *this; } //unifying assignment: no need for r-value reference assignment! - - void swap(ComPtr& rhs) { std::swap(ptr, rhs.ptr); } //throw() + void swap(ComPtr& rhs) { std::swap(ptr, rhs.ptr); } //noexcept in C++11 T** init() //get pointer for use with ::CoCreateInstance() { @@ -56,7 +56,7 @@ public: T* operator->() const { return ptr; } T& operator* () const { return *ptr; } - T* release() //throw() + T* release() //noexcept in C++11 { T* tmp = ptr; ptr = nullptr; @@ -74,7 +74,7 @@ public: template <class S, class T> -ComPtr<S> com_dynamic_cast(const ComPtr<T>& other); //throw() +ComPtr<S> com_dynamic_cast(const ComPtr<T>& other); //noexcept in C++11 @@ -99,7 +99,7 @@ ComPtr<S> com_dynamic_cast(const ComPtr<T>& other); //throw() //################# implementation ############################# -//we cannot specialize std::swap() for a class template and are not allowed to overload it => offer swap in own namespace +//we cannot partially specialize std::swap() for a class template and are not allowed to overload it => offer swap in own namespace template <class T> inline void swap(zen::ComPtr<T>& lhs, zen::ComPtr<T>& rhs) { @@ -108,7 +108,7 @@ void swap(zen::ComPtr<T>& lhs, zen::ComPtr<T>& rhs) template <class S, class T> inline -ComPtr<S> com_dynamic_cast(const ComPtr<T>& other) //throw() +ComPtr<S> com_dynamic_cast(const ComPtr<T>& other) //noexcept in C++11 { ComPtr<S> outPtr; if (other) diff --git a/zen/debug_new.cpp b/zen/debug_new.cpp index ea7771b4..40fb88c7 100644 --- a/zen/debug_new.cpp +++ b/zen/debug_new.cpp @@ -5,7 +5,9 @@ // ************************************************************************** #include "debug_new.h" - +#include <string> +#include <sstream> +#include <cstdlib> //malloc(), free() #include "win.h" //includes "windows.h" #include "DbgHelp.h" //available for MSC only #pragma comment(lib, "Dbghelp.lib") @@ -44,7 +46,7 @@ struct Dummy { Dummy() { ::SetUnhandledExceptionFilter(writeDumpOnException); }} } -void mem_check::writeMinidump() +void debug_tools::writeMinidump() { //force exception to catch the state of this thread and hopefully get a valid call stack __try @@ -53,3 +55,60 @@ void mem_check::writeMinidump() } __except (writeDumpOnException(GetExceptionInformation()), EXCEPTION_CONTINUE_EXECUTION) {} } + + +/* +No need to include the "operator new" declarations into every compilation unit: + +[basic.stc.dynamic] +"A C++ program shall provide at most one definition of a replaceable allocation or deallocation function. +Any such function definition replaces the default version provided in the library (17.6.4.6). +The following allocation and deallocation functions (18.6) are implicitly declared in global scope in each translation unit of a program. +void* operator new(std::size_t); +void* operator new[](std::size_t); +void operator delete(void*); +void operator delete[](void*);" +*/ + +namespace +{ +class BadAllocDetailed : public std::bad_alloc +{ +public: + explicit BadAllocDetailed(size_t allocSize) + { + errorMsg = "Memory allocation failed: "; + errorMsg += numberToString(allocSize); + } + + virtual const char* what() const throw() + { + return errorMsg.c_str(); + } + +private: + template <class T> + static std::string numberToString(const T& number) //convert number to string the C++ way + { + std::ostringstream ss; + ss << number; + return ss.str(); + } + + std::string errorMsg; +}; +} + +void* operator new(size_t size) +{ + if (void* ptr = ::malloc(size)) + return ptr; + + debug_tools::writeMinidump(); + throw debug_tools::BadAllocDetailed(size); +} + +void operator delete(void* ptr) { ::free(ptr); } + +void* operator new[](size_t size) { return operator new(size); } +void operator delete[](void* ptr) { operator delete(ptr); } diff --git a/zen/debug_new.h b/zen/debug_new.h index 6007344d..4ef0106e 100644 --- a/zen/debug_new.h +++ b/zen/debug_new.h @@ -7,10 +7,6 @@ #ifndef DEBUGNEW_H_INCLUDED #define DEBUGNEW_H_INCLUDED -#include <string> -#include <sstream> -#include <cstdlib> //malloc(), free() - #ifndef _MSC_VER #error currently for use with MSC only #endif @@ -21,10 +17,9 @@ Better std::bad_alloc overwrite "operator new" to automatically write mini dump and get info about bytes requested 1. Compile "debug_new.cpp" -2. C/C++ -> Advanced: Forced Include File: zen/debug_new.h Minidumps http://msdn.microsoft.com/en-us/library/windows/desktop/ee416349(v=vs.85).aspx ---------- +---------------------------------------------------------------------------------------- 1. Compile "debug_new.cpp" 2. Compile "release" build with: - C/C++ -> General: Debug Information Format: "Program Database" (/Zi). @@ -36,74 +31,9 @@ Optional: - C/C++ -> Optimization: Disabled (/Od) */ -namespace mem_check -{ -class BadAllocDetailed : public std::bad_alloc +namespace debug_tools { -public: - explicit BadAllocDetailed(size_t allocSize) - { - errorMsg = "Memory allocation failed: "; - errorMsg += numberToString(allocSize); - } - - ~BadAllocDetailed() throw() {} - - virtual const char* what() const throw() - { - return errorMsg.c_str(); - } - -private: - template <class T> - static std::string numberToString(const T& number) //convert number to string the C++ way - { - std::ostringstream ss; - ss << number; - return ss.str(); - } - - std::string errorMsg; -}; - -#ifdef _MSC_VER void writeMinidump(); -#endif -} - -inline -void* operator new(size_t size) -{ - void* newMem = ::malloc(size); - if (!newMem) - { -#ifdef _MSC_VER - mem_check::writeMinidump(); -#endif - throw mem_check::BadAllocDetailed(size); - } - return newMem; -} - - -inline -void operator delete(void* ptr) -{ - ::free(ptr); -} - - -inline -void* operator new[](size_t size) -{ - return operator new(size); -} - - -inline -void operator delete[](void* ptr) -{ - operator delete(ptr); } #endif // DEBUGNEW_H_INCLUDED diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp index c6f0b5e6..c02453c6 100644 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -14,7 +14,6 @@ #include "notify_removal.h" #include "win.h" //includes "windows.h" #include "long_path_prefix.h" -//#include "privilege.h" #elif defined FFS_LINUX #include <sys/inotify.h> @@ -36,10 +35,8 @@ public: { boost::lock_guard<boost::mutex> dummy(lockAccess); - std::set<Zstring>& output = changedFiles; - if (bytesWritten == 0) //according to docu this may happen in case of internal buffer overflow: report some "dummy" change - output.insert(L"Overflow!"); + changedFiles.push_back(DirWatcher::Entry(DirWatcher::ACTION_CREATE, L"Overflow!")); else { const char* bufPos = &buffer[0]; @@ -50,10 +47,11 @@ public: const Zstring fullname = dirname + Zstring(notifyInfo.FileName, notifyInfo.FileNameLength / sizeof(WCHAR)); //skip modifications sent by changed directories: reason for change, child element creation/deletion, will notify separately! + //and if this child element is a .ffs_lock file we'll want to ignore all associated events! [&] { - if (notifyInfo.Action == FILE_ACTION_RENAMED_OLD_NAME) //reporting FILE_ACTION_RENAMED_NEW_NAME should suffice - return; + //if (notifyInfo.Action == FILE_ACTION_RENAMED_OLD_NAME) //reporting FILE_ACTION_RENAMED_NEW_NAME should suffice; + // return; //note: this is NOT a cross-directory move, which will show up as create + delete if (notifyInfo.Action == FILE_ACTION_MODIFIED) { @@ -63,7 +61,20 @@ public: return; } - output.insert(fullname); + switch (notifyInfo.Action) + { + case FILE_ACTION_ADDED: + case FILE_ACTION_RENAMED_NEW_NAME: //harmonize with "move" which is notified as "create + delete" + changedFiles.push_back(DirWatcher::Entry(DirWatcher::ACTION_CREATE, fullname)); + break; + case FILE_ACTION_REMOVED: + case FILE_ACTION_RENAMED_OLD_NAME: + changedFiles.push_back(DirWatcher::Entry(DirWatcher::ACTION_DELETE, fullname)); + break; + case FILE_ACTION_MODIFIED: + changedFiles.push_back(DirWatcher::Entry(DirWatcher::ACTION_UPDATE, fullname)); + break; + } }(); if (notifyInfo.NextEntryOffset == 0) @@ -73,16 +84,16 @@ public: } } - //context of main thread - void addChange(const Zstring& dirname) //throw () - { - boost::lock_guard<boost::mutex> dummy(lockAccess); - changedFiles.insert(dirname); - } + ////context of main thread + //void addChange(const Zstring& dirname) //throw () + //{ + // boost::lock_guard<boost::mutex> dummy(lockAccess); + // changedFiles.insert(dirname); + //} //context of main thread - void getChanges(std::vector<Zstring>& output) //throw FileError, ErrorNotExisting + void getChanges(std::vector<DirWatcher::Entry>& output) //throw FileError, ErrorNotExisting { boost::lock_guard<boost::mutex> dummy(lockAccess); @@ -97,7 +108,7 @@ public: throw FileError(msg); } - output.assign(changedFiles.begin(), changedFiles.end()); + output.swap(changedFiles); changedFiles.clear(); } @@ -113,7 +124,7 @@ private: typedef Zbase<wchar_t> BasicWString; //thread safe string class for UI texts boost::mutex lockAccess; - std::set<Zstring> changedFiles; //get rid of duplicate entries (actually occur!) + std::vector<DirWatcher::Entry> changedFiles; std::pair<BasicWString, DWORD> errorMsg; //non-empty if errors occured in thread }; @@ -269,7 +280,7 @@ private: { //must release hDir immediately => stop monitoring! worker_.interrupt(); - worker_.join(); + worker_.join(); //we assume precondition "worker.joinable()"!!! //now hDir should have been released removalRequested = true; @@ -314,9 +325,9 @@ DirWatcher::~DirWatcher() } -std::vector<Zstring> DirWatcher::getChanges(const std::function<void()>& processGuiMessages) //throw FileError +std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void()>& processGuiMessages) //throw FileError { - std::vector<Zstring> output; + std::vector<Entry> output; //wait until device removal is confirmed, to prevent locking hDir again by some new watch! if (pimpl_->volRemoval->requestReceived()) @@ -330,7 +341,7 @@ std::vector<Zstring> DirWatcher::getChanges(const std::function<void()>& process boost::thread::sleep(boost::get_system_time() + boost::posix_time::milliseconds(50)); } - output.push_back(pimpl_->dirname); //report removal as change to main directory + output.push_back(Entry(ACTION_DELETE, pimpl_->dirname)); //report removal as change to main directory } else //the normal case... pimpl_->shared->getChanges(output); //throw FileError @@ -412,12 +423,13 @@ DirWatcher::DirWatcher(const Zstring& directory) : //throw FileError int wd = ::inotify_add_watch(pimpl_->notifDescr, subdir.c_str(), IN_ONLYDIR | //watch directories only IN_DONT_FOLLOW | //don't follow symbolic links + IN_CREATE | IN_MODIFY | IN_CLOSE_WRITE | - IN_MOVE | - IN_CREATE | IN_DELETE | IN_DELETE_SELF | + IN_MOVED_FROM | + IN_MOVED_TO | IN_MOVE_SELF); if (wd == -1) { @@ -440,7 +452,7 @@ DirWatcher::~DirWatcher() } -std::vector<Zstring> DirWatcher::getChanges(const std::function<void()>&) //throw FileError +std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void()>&) //throw FileError { //non-blocking call, see O_NONBLOCK std::vector<char> buffer(1024 * (sizeof(struct inotify_event) + 16)); @@ -450,12 +462,12 @@ std::vector<Zstring> DirWatcher::getChanges(const std::function<void()>&) //thro { if (errno == EINTR || //Interrupted function call; When this happens, you should try the call again. errno == EAGAIN) //Non-blocking I/O has been selected using O_NONBLOCK and no data was immediately available for reading - return std::vector<Zstring>(); + return std::vector<Entry>(); throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(pimpl_->dirname)) + L"\n\n" + getLastErrorFormatted()); } - std::set<Zstring> tmp; //get rid of duplicate entries (actually occur!) + std::vector<Entry> output; ssize_t bytePos = 0; while (bytePos < bytesRead) @@ -469,14 +481,26 @@ std::vector<Zstring> DirWatcher::getChanges(const std::function<void()>&) //thro { //Note: evt.len is NOT the size of the evt.name c-string, but the array size including all padding 0 characters! //It may be even 0 in which case evt.name must not be used! - tmp.insert(iter->second + evt.name); + const Zstring fullname = iter->second + evt.name; + + if ((evt.mask & IN_CREATE) || + (evt.mask & IN_MOVED_TO)) + output.push_back(Entry(ACTION_CREATE, fullname)); + else if ((evt.mask & IN_MODIFY) || + (evt.mask & IN_CLOSE_WRITE)) + output.push_back(Entry(ACTION_UPDATE, fullname)); + else if ((evt.mask & IN_DELETE ) || + (evt.mask & IN_DELETE_SELF) || + (evt.mask & IN_MOVE_SELF ) || + (evt.mask & IN_MOVED_FROM)) + output.push_back(Entry(ACTION_DELETE, fullname)); } } bytePos += sizeof(struct inotify_event) + evt.len; } - return std::vector<Zstring>(tmp.begin(), tmp.end()); + return output; } #endif diff --git a/zen/dir_watcher.h b/zen/dir_watcher.h index 56497040..b0df48bd 100644 --- a/zen/dir_watcher.h +++ b/zen/dir_watcher.h @@ -24,10 +24,10 @@ namespace zen removal of top watched directory is NOT notified! Windows: removal of top watched directory also NOT notified (e.g. brute force usb stick removal) however manual unmount IS notified (e.g. usb stick removal, then re-insert), but watching is stopped! - Renaming of top watched directory handled incorrectly: Not notified(!) + changes in subfolders - report FILE_ACTION_MODIFIED for directory (check that should prevent this fails!) + Renaming of top watched directory handled incorrectly: Not notified(!) + additional changes in subfolders + now do report FILE_ACTION_MODIFIED for directory (check that should prevent this fails!) - Overcome all issues portably: check existence of watched directory externally + reinstall watch after changes in directory structure (added directories) are possible + Overcome all issues portably: check existence of top watched directory externally + reinstall watch after changes in directory structure (added directories) are possible */ class DirWatcher { @@ -35,8 +35,24 @@ public: DirWatcher(const Zstring& directory); //throw FileError, ErrorNotExisting ~DirWatcher(); + enum ActionType + { + ACTION_CREATE, + ACTION_UPDATE, + ACTION_DELETE, + }; + + struct Entry + { + Entry() : action_(ACTION_CREATE) {} + Entry(ActionType action, const Zstring& filename) : action_(action), filename_(filename) {} + + ActionType action_; + Zstring filename_; + }; + //extract accumulated changes since last call - std::vector<Zstring> getChanges(const std::function<void()>& processGuiMessages); //throw FileError + std::vector<Entry> getChanges(const std::function<void()>& processGuiMessages); //throw FileError private: DirWatcher(const DirWatcher&); @@ -20,12 +20,12 @@ Manage DLL function and library ownership - full value semantics Usage: - typedef BOOL (WINAPI* IsWow64ProcessFun)(HANDLE hProcess, PBOOL Wow64Process); - const zen::SysDllFun<IsWow64ProcessFun> isWow64Process(L"kernel32.dll", "IsWow64Process"); + typedef BOOL (WINAPI* FunType_IsWow64Process)(HANDLE hProcess, PBOOL Wow64Process); + const zen::SysDllFun<FunType_IsWow64Process> isWow64Process(L"kernel32.dll", "IsWow64Process"); if (isWow64Process) ... use function ptr ... Usage 2: - #define DEF_DLL_FUN(name) DllFun<dll_ns::FunType_##name> name(getDllName(), dll_ns::funName_##name); + #define DEF_DLL_FUN(name) DllFun<dll_ns::FunType_##name> name(dll_ns::getDllName(), dll_ns::funName_##name); DEF_DLL_FUN(funname1); DEF_DLL_FUN(funname2); DEF_DLL_FUN(funname3); */ diff --git a/zen/error_log.h b/zen/error_log.h index e42ca89d..401581d7 100644 --- a/zen/error_log.h +++ b/zen/error_log.h @@ -7,11 +7,13 @@ #ifndef ERRORLOGGING_H_INCLUDED #define ERRORLOGGING_H_INCLUDED +#include <cassert> #include <algorithm> #include <vector> #include <string> -#include <zen/time.h> -#include <zen/i18n.h> +#include "time.h" +#include "i18n.h" +#include "string_base.h" namespace zen { @@ -23,20 +25,23 @@ enum MessageType TYPE_FATAL_ERROR = 0x8, }; +typedef Zbase<wchar_t> MsgString; //std::wstring may employ small string optimization: we cannot accept bloating the "logEntries" memory block below (think 1 million entries) + struct LogEntry { - time_t time; - MessageType type; - std::wstring message; + time_t time; + MessageType type; + MsgString message; }; -std::wstring formatMessage(const LogEntry& msg); +MsgString formatMessage(const LogEntry& msg); class ErrorLog { public: - void logMsg(const std::wstring& message, MessageType type); + template <class String> + void logMsg(const String& message, MessageType type); int getItemCount(int typeFilter = TYPE_INFO | TYPE_WARNING | TYPE_ERROR | TYPE_FATAL_ERROR) const; @@ -58,18 +63,11 @@ private: - - - - - - //######################## implementation ########################## - -inline -void ErrorLog::logMsg(const std::wstring& message, zen::MessageType type) +template <class String> inline +void ErrorLog::logMsg(const String& message, zen::MessageType type) { - const LogEntry newEntry = { std::time(nullptr), type, message }; + const LogEntry newEntry = { std::time(nullptr), type, copyStringTo<MsgString>(message) }; logEntries.push_back(newEntry); } @@ -83,7 +81,7 @@ int ErrorLog::getItemCount(int typeFilter) const namespace { -std::wstring formatMessageImpl(const LogEntry& entry) //internal linkage +MsgString formatMessageImpl(const LogEntry& entry) //internal linkage { auto getTypeName = [&]() -> std::wstring { @@ -98,10 +96,11 @@ std::wstring formatMessageImpl(const LogEntry& entry) //internal linkage case TYPE_FATAL_ERROR: return _("Fatal Error"); } + assert(false); return std::wstring(); }; - std::wstring formattedText = L"[" + formatTime<std::wstring>(FORMAT_TIME, localTime(entry.time)) + L"] " + getTypeName() + L": "; + MsgString formattedText = L"[" + formatTime<MsgString>(FORMAT_TIME, localTime(entry.time)) + L"] " + copyStringTo<MsgString>(getTypeName()) + L": "; const size_t prefixLen = formattedText.size(); for (auto iter = entry.message.begin(); iter != entry.message.end(); ) @@ -109,7 +108,7 @@ std::wstring formatMessageImpl(const LogEntry& entry) //internal linkage { formattedText += L'\n'; - std::wstring blanks; + MsgString blanks; blanks.resize(prefixLen, L' '); formattedText += blanks; @@ -126,8 +125,8 @@ std::wstring formatMessageImpl(const LogEntry& entry) //internal linkage } } -inline std::wstring formatMessage(const LogEntry& entry) { return formatMessageImpl(entry); } - +inline +MsgString formatMessage(const LogEntry& entry) { return formatMessageImpl(entry); } } #endif //ERRORLOGGING_H_INCLUDED diff --git a/zen/file_handling.cpp b/zen/file_handling.cpp index a646a13f..589057ad 100644 --- a/zen/file_handling.cpp +++ b/zen/file_handling.cpp @@ -680,227 +680,247 @@ void zen::setFileTime(const Zstring& filename, const Int64& modificationTime, Pr } //####################################### DST hack ########################################### - //privilege SE_BACKUP_NAME doesn't seem to be required here for symbolic links - //note: setting privileges requires admin rights! - - //opening newly created target file may fail due to some AV-software scanning it: no error, we will wait! - //http://support.microsoft.com/?scid=kb%3Ben-us%3B316609&x=17&y=20 - //-> enable as soon it turns out it is required! - - /*const int retryInterval = 50; - const int maxRetries = 2000 / retryInterval; - for (int i = 0; i < maxRetries; ++i) { - */ + //extra scope for debug check below - /* - if (hTarget == INVALID_HANDLE_VALUE && ::GetLastError() == ERROR_SHARING_VIOLATION) - ::Sleep(retryInterval); //wait then retry - else //success or unknown failure - break; - } - */ + //privilege SE_BACKUP_NAME doesn't seem to be required here for symbolic links + //note: setting privileges requires admin rights! - //temporarily reset read-only flag if required - DWORD attribs = INVALID_FILE_ATTRIBUTES; - ZEN_ON_SCOPE_EXIT( - if (attribs != INVALID_FILE_ATTRIBUTES) - ::SetFileAttributes(applyLongPathPrefix(filename).c_str(), attribs); - ); + //opening newly created target file may fail due to some AV-software scanning it: no error, we will wait! + //http://support.microsoft.com/?scid=kb%3Ben-us%3B316609&x=17&y=20 + //-> enable as soon it turns out it is required! - auto removeReadonly = [&]() -> bool //may need to remove the readonly-attribute (e.g. on FAT usb drives) - { - if (attribs == INVALID_FILE_ATTRIBUTES) + /*const int retryInterval = 50; + const int maxRetries = 2000 / retryInterval; + for (int i = 0; i < maxRetries; ++i) { - const DWORD tmpAttr = ::GetFileAttributes(applyLongPathPrefix(filename).c_str()); - if (tmpAttr == INVALID_FILE_ATTRIBUTES) - throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted()); + */ - if (tmpAttr & FILE_ATTRIBUTE_READONLY) + /* + if (hTarget == INVALID_HANDLE_VALUE && ::GetLastError() == ERROR_SHARING_VIOLATION) + ::Sleep(retryInterval); //wait then retry + else //success or unknown failure + break; + } + */ + //temporarily reset read-only flag if required + DWORD attribs = INVALID_FILE_ATTRIBUTES; + ZEN_ON_SCOPE_EXIT( + if (attribs != INVALID_FILE_ATTRIBUTES) + ::SetFileAttributes(applyLongPathPrefix(filename).c_str(), attribs); + ); + + auto removeReadonly = [&]() -> bool //may need to remove the readonly-attribute (e.g. on FAT usb drives) + { + if (attribs == INVALID_FILE_ATTRIBUTES) { - if (!::SetFileAttributes(applyLongPathPrefix(filename).c_str(), FILE_ATTRIBUTE_NORMAL)) - throw FileError(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted()); + const DWORD tmpAttr = ::GetFileAttributes(applyLongPathPrefix(filename).c_str()); + if (tmpAttr == INVALID_FILE_ATTRIBUTES) + throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted()); - attribs = tmpAttr; //reapplied on scope exit - return true; + if (tmpAttr & FILE_ATTRIBUTE_READONLY) + { + if (!::SetFileAttributes(applyLongPathPrefix(filename).c_str(), FILE_ATTRIBUTE_NORMAL)) + throw FileError(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted()); + + attribs = tmpAttr; //reapplied on scope exit + return true; + } } - } - return false; - }; - - auto openFile = [&](bool conservativeApproach) - { - return ::CreateFile(applyLongPathPrefix(filename).c_str(), - (conservativeApproach ? - //some NAS seem to have issues with FILE_WRITE_ATTRIBUTES, even worse, they may fail silently! - //http://sourceforge.net/tracker/?func=detail&atid=1093081&aid=3536680&group_id=234430 - //Citrix shares seem to have this issue, too, but at least fail with "access denied" => try generic access first: - GENERIC_READ | GENERIC_WRITE : - //avoids mysterious "access denied" when using "GENERIC_READ | GENERIC_WRITE" on a read-only file, even *after* read-only was removed directly before the call! - //http://sourceforge.net/tracker/?func=detail&atid=1093080&aid=3514569&group_id=234430 - //since former gives an error notification we may very well try FILE_WRITE_ATTRIBUTES second. - FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES), - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - nullptr, - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS | //needed to open a directory - (procSl == SYMLINK_DIRECT ? FILE_FLAG_OPEN_REPARSE_POINT : 0), //process symlinks - nullptr); - }; - - HANDLE hFile = INVALID_HANDLE_VALUE; - for (int i = 0; i < 2; ++i) //we will get this handle, no matter what! :) - { - //1. be conservative - hFile = openFile(true); - if (hFile == INVALID_HANDLE_VALUE) - { - if (::GetLastError() == ERROR_ACCESS_DENIED) //fails if file is read-only (or for "other" reasons) - if (removeReadonly()) - continue; + return false; + }; - //2. be a *little* fancy - hFile = openFile(false); + auto openFile = [&](bool conservativeApproach) + { + return ::CreateFile(applyLongPathPrefix(filename).c_str(), + (conservativeApproach ? + //some NAS seem to have issues with FILE_WRITE_ATTRIBUTES, even worse, they may fail silently! + //http://sourceforge.net/tracker/?func=detail&atid=1093081&aid=3536680&group_id=234430 + //Citrix shares seem to have this issue, too, but at least fail with "access denied" => try generic access first: + GENERIC_READ | GENERIC_WRITE : + //avoids mysterious "access denied" when using "GENERIC_READ | GENERIC_WRITE" on a read-only file, even *after* read-only was removed directly before the call! + //http://sourceforge.net/tracker/?func=detail&atid=1093080&aid=3514569&group_id=234430 + //since former gives an error notification we may very well try FILE_WRITE_ATTRIBUTES second. + FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES), + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | //needed to open a directory + (procSl == SYMLINK_DIRECT ? FILE_FLAG_OPEN_REPARSE_POINT : 0), //process symlinks + nullptr); + }; + + HANDLE hFile = INVALID_HANDLE_VALUE; + for (int i = 0; i < 2; ++i) //we will get this handle, no matter what! :) + { + //1. be conservative + hFile = openFile(true); if (hFile == INVALID_HANDLE_VALUE) { - if (::GetLastError() == ERROR_ACCESS_DENIED) + if (::GetLastError() == ERROR_ACCESS_DENIED) //fails if file is read-only (or for "other" reasons) if (removeReadonly()) continue; - //3. after these herculean stunts we give up... - throw FileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted()); + //2. be a *little* fancy + hFile = openFile(false); + if (hFile == INVALID_HANDLE_VALUE) + { + if (::GetLastError() == ERROR_ACCESS_DENIED) + if (removeReadonly()) + continue; + + //3. after these herculean stunts we give up... + throw FileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted()); + } } + break; } - break; - } - ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile)); - + ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile)); - auto isNullTime = [](const FILETIME& ft) { return ft.dwLowDateTime == 0 && ft.dwHighDateTime == 0; }; - if (!::SetFileTime(hFile, //__in HANDLE hFile, - !isNullTime(creationTime) ? &creationTime : nullptr, //__in_opt const FILETIME *lpCreationTime, - nullptr, //__in_opt const FILETIME *lpLastAccessTime, - &lastWriteTime)) //__in_opt const FILETIME *lpLastWriteTime - { - auto lastErr = ::GetLastError(); + auto isNullTime = [](const FILETIME& ft) { return ft.dwLowDateTime == 0 && ft.dwHighDateTime == 0; }; - //function may fail if file is read-only: https://sourceforge.net/tracker/?func=detail&atid=1093080&aid=3514569&group_id=234430 - if (lastErr == ERROR_ACCESS_DENIED) + if (!::SetFileTime(hFile, //__in HANDLE hFile, + !isNullTime(creationTime) ? &creationTime : nullptr, //__in_opt const FILETIME *lpCreationTime, + nullptr, //__in_opt const FILETIME *lpLastAccessTime, + &lastWriteTime)) //__in_opt const FILETIME *lpLastWriteTime { - //dynamically load windows API function: available with Windows Vista and later - typedef BOOL (WINAPI* SetFileInformationByHandleFunc)(HANDLE hFile, FILE_INFO_BY_HANDLE_CLASS FileInformationClass, LPVOID lpFileInformation, DWORD dwBufferSize); + auto lastErr = ::GetLastError(); - const SysDllFun<SetFileInformationByHandleFunc> setFileInformationByHandle(L"kernel32.dll", "SetFileInformationByHandle"); - if (setFileInformationByHandle) //if not: let the original error propagate! + //function may fail if file is read-only: https://sourceforge.net/tracker/?func=detail&atid=1093080&aid=3514569&group_id=234430 + if (lastErr == ERROR_ACCESS_DENIED) { - auto setFileInfo = [&](FILE_BASIC_INFO basicInfo) //throw FileError; no const& since SetFileInformationByHandle() requires non-const parameter! - { - if (!setFileInformationByHandle(hFile, //__in HANDLE hFile, - FileBasicInfo, //__in FILE_INFO_BY_HANDLE_CLASS FileInformationClass, - &basicInfo, //__in LPVOID lpFileInformation, - sizeof(basicInfo))) //__in DWORD dwBufferSize - throw FileError(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted()); - }; + //dynamically load windows API function: available with Windows Vista and later + typedef BOOL (WINAPI* SetFileInformationByHandleFunc)(HANDLE hFile, FILE_INFO_BY_HANDLE_CLASS FileInformationClass, LPVOID lpFileInformation, DWORD dwBufferSize); - auto toLargeInteger = [](const FILETIME& ft) -> LARGE_INTEGER + const SysDllFun<SetFileInformationByHandleFunc> setFileInformationByHandle(L"kernel32.dll", "SetFileInformationByHandle"); + if (setFileInformationByHandle) //if not: let the original error propagate! { - LARGE_INTEGER tmp = {}; - tmp.LowPart = ft.dwLowDateTime; - tmp.HighPart = ft.dwHighDateTime; - return tmp; - }; - //--------------------------------------------------------------------------- - - BY_HANDLE_FILE_INFORMATION fileInfo = {}; - if (::GetFileInformationByHandle(hFile, &fileInfo)) - if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY) + auto setFileInfo = [&](FILE_BASIC_INFO basicInfo) //throw FileError; no const& since SetFileInformationByHandle() requires non-const parameter! { - FILE_BASIC_INFO basicInfo = {}; //undocumented: file times of "0" stand for "don't change" - basicInfo.FileAttributes = FILE_ATTRIBUTE_NORMAL; //[!] the bug in the ticket above requires we set this together with file times!!! - basicInfo.LastWriteTime = toLargeInteger(lastWriteTime); // - if (!isNullTime(creationTime)) - basicInfo.CreationTime = toLargeInteger(creationTime); - - //set file time + attributes - setFileInfo(basicInfo); //throw FileError - - try //... to restore original file attributes + if (!setFileInformationByHandle(hFile, //__in HANDLE hFile, + FileBasicInfo, //__in FILE_INFO_BY_HANDLE_CLASS FileInformationClass, + &basicInfo, //__in LPVOID lpFileInformation, + sizeof(basicInfo))) //__in DWORD dwBufferSize + throw FileError(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted()); + }; + + auto toLargeInteger = [](const FILETIME& ft) -> LARGE_INTEGER + { + LARGE_INTEGER tmp = {}; + tmp.LowPart = ft.dwLowDateTime; + tmp.HighPart = ft.dwHighDateTime; + return tmp; + }; + //--------------------------------------------------------------------------- + + BY_HANDLE_FILE_INFORMATION fileInfo = {}; + if (::GetFileInformationByHandle(hFile, &fileInfo)) + if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY) { - FILE_BASIC_INFO basicInfo2 = {}; - basicInfo2.FileAttributes = fileInfo.dwFileAttributes; - setFileInfo(basicInfo2); //throw FileError + FILE_BASIC_INFO basicInfo = {}; //undocumented: file times of "0" stand for "don't change" + basicInfo.FileAttributes = FILE_ATTRIBUTE_NORMAL; //[!] the bug in the ticket above requires we set this together with file times!!! + basicInfo.LastWriteTime = toLargeInteger(lastWriteTime); // + if (!isNullTime(creationTime)) + basicInfo.CreationTime = toLargeInteger(creationTime); + + //set file time + attributes + setFileInfo(basicInfo); //throw FileError + + try //... to restore original file attributes + { + FILE_BASIC_INFO basicInfo2 = {}; + basicInfo2.FileAttributes = fileInfo.dwFileAttributes; + setFileInfo(basicInfo2); //throw FileError + } + catch (FileError&) {} + + lastErr = ERROR_SUCCESS; } - catch (FileError&) {} - - lastErr = ERROR_SUCCESS; - } + } } - } - std::wstring errorMsg = replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted(lastErr); + std::wstring errorMsg = replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted(lastErr); - //add more meaningful message: FAT accepts only a subset of the NTFS date range - if (lastErr == ERROR_INVALID_PARAMETER && - dst::isFatDrive(filename)) - { - //we need a low-level reliable routine to format a potentially invalid date => don't use strftime!!! - auto fmtDate = [](const FILETIME& ft) -> Zstring + //add more meaningful message: FAT accepts only a subset of the NTFS date range + if (lastErr == ERROR_INVALID_PARAMETER && + dst::isFatDrive(filename)) { - SYSTEMTIME st = {}; - if (!::FileTimeToSystemTime(&ft, //__in const FILETIME *lpFileTime, - &st)) //__out LPSYSTEMTIME lpSystemTime - return Zstring(); - - Zstring dateTime; + //we need a low-level reliable routine to format a potentially invalid date => don't use strftime!!! + auto fmtDate = [](const FILETIME& ft) -> Zstring { - const int bufferSize = ::GetDateFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, nullptr, 0); - if (bufferSize > 0) + SYSTEMTIME st = {}; + if (!::FileTimeToSystemTime(&ft, //__in const FILETIME *lpFileTime, + &st)) //__out LPSYSTEMTIME lpSystemTime + return Zstring(); + + Zstring dateTime; { - std::vector<wchar_t> buffer(bufferSize); - if (::GetDateFormat(LOCALE_USER_DEFAULT, //_In_ LCID Locale, - 0, //_In_ DWORD dwFlags, - &st, //_In_opt_ const SYSTEMTIME *lpDate, - nullptr, //_In_opt_ LPCTSTR lpFormat, - &buffer[0], //_Out_opt_ LPTSTR lpDateStr, - bufferSize) > 0) //_In_ int cchDate - dateTime = &buffer[0]; //GetDateFormat() returns char count *including* 0-termination! + const int bufferSize = ::GetDateFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, nullptr, 0); + if (bufferSize > 0) + { + std::vector<wchar_t> buffer(bufferSize); + if (::GetDateFormat(LOCALE_USER_DEFAULT, //_In_ LCID Locale, + 0, //_In_ DWORD dwFlags, + &st, //_In_opt_ const SYSTEMTIME *lpDate, + nullptr, //_In_opt_ LPCTSTR lpFormat, + &buffer[0], //_Out_opt_ LPTSTR lpDateStr, + bufferSize) > 0) //_In_ int cchDate + dateTime = &buffer[0]; //GetDateFormat() returns char count *including* 0-termination! + } } - } - const int bufferSize = ::GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, nullptr, 0); - if (bufferSize > 0) - { - std::vector<wchar_t> buffer(bufferSize); - if (::GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, &buffer[0], bufferSize) > 0) + const int bufferSize = ::GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, nullptr, 0); + if (bufferSize > 0) { - dateTime += L" "; - dateTime += &buffer[0]; //GetDateFormat() returns char count *including* 0-termination! + std::vector<wchar_t> buffer(bufferSize); + if (::GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, &buffer[0], bufferSize) > 0) + { + dateTime += L" "; + dateTime += &buffer[0]; //GetDateFormat() returns char count *including* 0-termination! + } } - } - return dateTime; - }; + return dateTime; + }; - errorMsg += std::wstring(L"\nA FAT volume can only store dates between 1980 and 2107:\n") + - L"\twrite (UTC): \t" + fmtDate(lastWriteTime) + - (!isNullTime(creationTime) ? L"\n\tcreate (UTC): \t" + fmtDate(creationTime) : L""); - } + errorMsg += std::wstring(L"\nA FAT volume can only store dates between 1980 and 2107:\n") + + L"\twrite (UTC): \t" + fmtDate(lastWriteTime) + + (!isNullTime(creationTime) ? L"\n\tcreate (UTC): \t" + fmtDate(creationTime) : L""); + } - if (lastErr != ERROR_SUCCESS) - throw FileError(errorMsg); + if (lastErr != ERROR_SUCCESS) + throw FileError(errorMsg); + } } - #ifndef NDEBUG //dst hack: verify data written if (dst::isFatDrive(filename) && !dirExists(filename)) //throw() { - WIN32_FILE_ATTRIBUTE_DATA debugeAttr = {}; - assert(::GetFileAttributesEx(applyLongPathPrefix(filename).c_str(), //__in LPCTSTR lpFileName, - GetFileExInfoStandard, //__in GET_FILEEX_INFO_LEVELS fInfoLevelId, - &debugeAttr)); //__out LPVOID lpFileInformation + FILETIME creationTimeDbg = {}; + FILETIME lastWriteTimeDbg = {}; + + HANDLE hFile = ::CreateFile(applyLongPathPrefix(filename).c_str(), + FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, + OPEN_EXISTING, + 0, + nullptr); + assert(hFile != INVALID_HANDLE_VALUE); + ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile)); - assert(::CompareFileTime(&debugeAttr.ftCreationTime, &creationTime) == 0); - assert(::CompareFileTime(&debugeAttr.ftLastWriteTime, &lastWriteTime) == 0); + assert(::GetFileTime(hFile, //probably more up to date than GetFileAttributesEx()!? + &creationTimeDbg, + nullptr, + &lastWriteTimeDbg)); + + assert(::CompareFileTime(&creationTimeDbg, &creationTime) == 0); + assert(::CompareFileTime(&lastWriteTimeDbg, &lastWriteTime) == 0); } + //CAVEAT on FAT/FAT32: the sequence of deleting the target file and renaming "file.txt.ffs_tmp" to "file.txt" seems to + //NOT PRESERVE the creation time of the .ffs_tmp file, but "reuses" whatever creation time the old "file.txt" had! + //this problem is therefore NOT detected by the check above! + //However during the next comparison the DST hack will be applied correctly. + #endif #elif defined FFS_LINUX @@ -1736,7 +1756,7 @@ void copyFileWindowsSparse(const Zstring& sourceFile, //stream-copy sourceFile to targetFile bool eof = false; - bool silentFailure = true; //try to detect failure reading encrypted files + bool someBytesWritten = false; //try to detect failure reading encrypted files do { DWORD bytesRead = 0; @@ -1775,14 +1795,14 @@ void copyFileWindowsSparse(const Zstring& sourceFile, callback->updateCopyStatus(Int64(bytesRead)); //throw X! if (bytesRead > 0) - silentFailure = false; + someBytesWritten = true; } while (!eof); //DST hack not required, since both source and target volumes cannot be FAT! //::BackupRead() silently fails reading encrypted files -> double check! - if (silentFailure && UInt64(fileInfoSource.nFileSizeLow, fileInfoSource.nFileSizeHigh) != 0U) + if (!someBytesWritten && UInt64(fileInfoSource.nFileSizeLow, fileInfoSource.nFileSizeHigh) != 0U) //note: there is no guaranteed ordering relation beween bytes transferred and file size! Consider ADS (>) and compressed/sparse files (<)! throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(sourceFile)) + L"\n\n" + L"(unknown error)"); @@ -2044,7 +2064,7 @@ void copyFileWindowsDefault(const Zstring& sourceFile, //don't suppress "lastError == ERROR_REQUEST_ABORTED": a user aborted operation IS an error condition! - //trying to copy huge sparse files may fail with ERROR_DISK_FULL + //trying to copy huge sparse files may directly fail with ERROR_DISK_FULL before entering the callback function if (canCopyAsSparse(sourceFile, targetFile)) //throw () throw ErrorShouldCopyAsSparse(L"sparse dummy value2"); diff --git a/zen/file_handling.h b/zen/file_handling.h index 878c467c..e9e1685d 100644 --- a/zen/file_handling.h +++ b/zen/file_handling.h @@ -100,7 +100,7 @@ struct CallbackCopyFile //may throw: //Linux: unconditionally - //Windows: first exception is swallowed, updateCopyStatus() is then called again where it should throw again and exception will propagate as expected + //Windows: first exception is swallowed, updateCopyStatus() is then called again where it should throw again and the exception will propagate as expected virtual void updateCopyStatus(Int64 bytesDelta) = 0; //accummulated delta != file size! consider ADS, sparse, compressed files }; } diff --git a/zen/last_error.h b/zen/last_error.h index 356192ab..72d54d48 100644 --- a/zen/last_error.h +++ b/zen/last_error.h @@ -76,18 +76,18 @@ ErrorCode getLastError() #ifdef FFS_WIN return ::GetLastError(); #elif defined FFS_LINUX - return errno; + return errno; //don't use "::", errno is a macro! #endif } inline std::wstring getLastErrorFormatted(ErrorCode lastError) { -#ifdef FFS_WIN //determine error code if none was specified if (lastError == 0) - lastError = ::GetLastError(); + lastError = getLastError(); +#ifdef FFS_WIN std::wstring output = _("Windows Error Code %x:"); replace(output, L"%x", numberTo<std::wstring>(lastError)); @@ -108,10 +108,6 @@ std::wstring getLastErrorFormatted(ErrorCode lastError) return output; #elif defined FFS_LINUX - //determine error code if none was specified - if (lastError == 0) - lastError = errno; //don't use "::", errno is a macro! - std::wstring output = _("Linux Error Code %x:"); replace(output, L"%x", numberTo<std::wstring>(lastError)); diff --git a/zen/recycler.cpp b/zen/recycler.cpp index 6593540a..c35dca56 100644 --- a/zen/recycler.cpp +++ b/zen/recycler.cpp @@ -29,9 +29,9 @@ using namespace zen; +#ifdef FFS_WIN namespace { -#ifdef FFS_WIN /* Performance test: delete 1000 files ------------------------------------ @@ -42,41 +42,42 @@ IFileOperation - multiple files 2,1s => SHFileOperation and IFileOperation have nearly IDENTICAL performance characteristics! -Nevertheless, let's use IFileOperation for better error reporting! +Nevertheless, let's use IFileOperation for better error reporting (including details on locked files)! */ const bool useIFileOperation = vistaOrLater(); //caveat: function scope static initialization is not thread-safe in VS 2010! -//(try to) enhance error messages by showing which processed lock the file -Zstring getLockingProcessNames(const Zstring& filename) //throw(), empty string if none found or error occurred +struct CallbackData { - if (vistaOrLater()) - { - using namespace fileop; - const DllFun<FunType_getLockingProcesses> getLockingProcesses(getDllName(), funName_getLockingProcesses); - const DllFun<FunType_freeString> freeString (getDllName(), funName_freeString); + CallbackData(CallbackRecycling* cb) : + userCallback(cb), + exceptionInUserCallback(false) {} - if (getLockingProcesses && freeString) + CallbackRecycling* const userCallback; //optional! + bool exceptionInUserCallback; +}; + +bool recyclerCallback(const wchar_t* filename, void* sink) +{ + CallbackData& cbd = *static_cast<CallbackData*>(sink); //sink is NOT optional here + + if (cbd.userCallback) + try { - const wchar_t* procList = nullptr; - if (getLockingProcesses(filename.c_str(), procList)) - { - ZEN_ON_SCOPE_EXIT(freeString(procList)); - return procList; - } + cbd.userCallback->updateStatus(filename); //throw ? } - } - return Zstring(); + catch (...) + { + cbd.exceptionInUserCallback = true; //try again outside the C call stack! + return false; + } + return true; } -#endif } - -bool zen::recycleOrDelete(const Zstring& filename) //throw FileError +void zen::recycleOrDelete(const std::vector<Zstring>& filenames, CallbackRecycling* callback) { - 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 + if (filenames.empty()) + return; //::SetFileAttributes(applyLongPathPrefix(filename).c_str(), FILE_ATTRIBUTE_NORMAL); //warning: moving long file paths to recycler does not work! //both ::SHFileOperation() and ::IFileOperation() cannot delete a folder named "System Volume Information" with normal attributes but shamelessly report success @@ -89,32 +90,40 @@ bool zen::recycleOrDelete(const Zstring& filename) //throw FileError const DllFun<FunType_getLastError> getLastError (getDllName(), funName_getLastError); if (!moveToRecycler || !getLastError) - throw FileError(replaceCpy(_("Unable to move %x to the Recycle Bin!"), L"%x", fmtFileName(filename)) + L"\n\n" + + throw FileError(replaceCpy(_("Unable to move %x to the Recycle Bin!"), L"%x", fmtFileName(filenames[0])) + L"\n\n" + replaceCpy(_("Cannot load file %x."), L"%x", fmtFileName(getDllName()))); - std::vector<const wchar_t*> filenames; - filenames.push_back(filename.c_str()); + std::vector<const wchar_t*> cNames; + for (auto iter = filenames.begin(); iter != filenames.end(); ++iter) //caution to not create temporary strings here!! + cNames.push_back(iter->c_str()); - if (!moveToRecycler(&filenames[0], filenames.size())) + CallbackData cbd(callback); + if (!moveToRecycler(&cNames[0], cNames.size(), recyclerCallback, &cbd)) { - const std::wstring shortMsg = replaceCpy(_("Unable to move %x to the Recycle Bin!"), L"%x", fmtFileName(filename)); + if (cbd.exceptionInUserCallback) //now we may throw... + callback->updateStatus(Zstring()); //should throw again! - //if something is locking our file -> emit better error message! - const Zstring procList = getLockingProcessNames(filename); //throw() - if (!procList.empty()) - throw FileError(shortMsg + L"\n\n" + _("The file is locked by another process:") + L"\n" + procList); + std::wstring filenameFmt = fmtFileName(filenames[0]); //probably not the correct file name for file lists larger than 1! + if (filenames.size() > 1) + filenameFmt += L", ..."; //give at least some hint that there are multiple files, and the error need not be related to the first one - throw FileError(shortMsg + L"\n\n" + getLastError()); + throw FileError(replaceCpy(_("Unable to move %x to the Recycle Bin!"), L"%x", filenameFmt) + + L"\n\n" + getLastError()); //already includes details about locking errors! } } else //regular recycle bin usage: available since XP { - const Zstring& filenameDoubleNull = filename + L'\0'; + Zstring filenamesDoubleNull; + for (auto iter = filenames.begin(); iter != filenames.end(); ++iter) + { + filenamesDoubleNull += *iter; + filenamesDoubleNull += L'\0'; + } SHFILEOPSTRUCT fileOp = {}; fileOp.hwnd = nullptr; fileOp.wFunc = FO_DELETE; - fileOp.pFrom = filenameDoubleNull.c_str(); + fileOp.pFrom = filenamesDoubleNull.c_str(); fileOp.pTo = nullptr; fileOp.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_SILENT | FOF_NOERRORUI; fileOp.fAnyOperationsAborted = false; @@ -124,9 +133,22 @@ bool zen::recycleOrDelete(const Zstring& filename) //throw FileError //"You should use fully-qualified path names with this function. Using it with relative path names is not thread safe." if (::SHFileOperation(&fileOp) != 0 || fileOp.fAnyOperationsAborted) { - throw FileError(replaceCpy(_("Unable to move %x to the Recycle Bin!"), L"%x", fmtFileName(filename))); + throw FileError(replaceCpy(_("Unable to move %x to the Recycle Bin!"), L"%x", fmtFileName(filenames[0]))); //probably not the correct file name for file list larger than 1! } } +} +#endif + + +bool zen::recycleOrDelete(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 + std::vector<Zstring> filenames; + filenames.push_back(filename); + recycleOrDelete(filenames, nullptr); //throw FileError #elif defined FFS_LINUX GFile* file = g_file_new_for_path(filename.c_str()); //never fails according to docu diff --git a/zen/recycler.h b/zen/recycler.h index cdadf371..4d33477d 100644 --- a/zen/recycler.h +++ b/zen/recycler.h @@ -7,6 +7,7 @@ #ifndef RECYCLER_H_INCLUDED #define RECYCLER_H_INCLUDED +#include <vector> #include <zen/file_error.h> #include <zen/zstring.h> @@ -40,8 +41,18 @@ enum StatusRecycler STATUS_REC_MISSING, STATUS_REC_UNKNOWN }; - StatusRecycler recycleBinStatus(const Zstring& pathName); //test existence of Recycle Bin API for certain path + +struct CallbackRecycling +{ + virtual ~CallbackRecycling() {} + + //may throw: first exception is swallowed, updateStatus() is then called again where it should throw again and the exception will propagate as expected + virtual void updateStatus(const Zstring& currentItem) = 0; +}; + +void recycleOrDelete(const std::vector<Zstring>& filenames, //throw FileError, return "true" if file/dir was actually deleted + CallbackRecycling* callback); //optional #endif } diff --git a/zen/scroll_window_under_cursor.cpp b/zen/scroll_window_under_cursor.cpp new file mode 100644 index 00000000..6031cd88 --- /dev/null +++ b/zen/scroll_window_under_cursor.cpp @@ -0,0 +1,77 @@ +// ************************************************************************** +// * 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) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#include <cassert> +#include "win.h" //includes "windows.h" +#include "Windowsx.h" //WM_MOUSEWHEEL + +//redirect mouse wheel events directly to window under cursor rather than window having input focus +//implementing new Windows Vista UI guidelines: http://msdn.microsoft.com/en-us/library/bb545459.aspx#wheel +//this is confirmed to be required for at least Windows 2000 to Windows 8 +//on Ubuntu Linux, this is already the default behavior + +//Usage: just include this file into a Windows project + +namespace +{ +#ifndef WM_MOUSEHWHEEL //MinGW is clueless... +#define WM_MOUSEHWHEEL 0x020E +#endif + +LRESULT CALLBACK mouseInputHook(int nCode, WPARAM wParam, LPARAM lParam) +{ + //"if nCode is less than zero, the hook procedure must pass the message to the CallNextHookEx function + //without further processing and should return the value returned by CallNextHookEx" + if (nCode >= 0) + { + MSG& msgInfo = *reinterpret_cast<MSG*>(lParam); + + if (msgInfo.message == WM_MOUSEWHEEL || + msgInfo.message == WM_MOUSEHWHEEL) + { + POINT pt = {}; + pt.x = GET_X_LPARAM(msgInfo.lParam); //yes, there's also msgInfo.pt, but let's not take chances + pt.y = GET_Y_LPARAM(msgInfo.lParam); // + + //visible child window directly under cursor; attention: not necessarily from our process! + //http://blogs.msdn.com/b/oldnewthing/archive/2010/12/30/10110077.aspx + if (HWND hWin = ::WindowFromPoint(pt)) + if (msgInfo.hwnd != hWin && ::GetCapture() == nullptr) + { + DWORD winProcessId = 0; + ::GetWindowThreadProcessId( //no-fail! + hWin, //_In_ HWND hWnd, + &winProcessId); //_Out_opt_ LPDWORD lpdwProcessId + if (winProcessId == ::GetCurrentProcessId()) //no-fail! + msgInfo.hwnd = hWin; //it would be a bug to set handle from another process here + } + } + } + + return ::CallNextHookEx(nullptr, nCode, wParam, lParam); +} + +struct Dummy +{ + Dummy() + { + hHook = ::SetWindowsHookEx(WH_GETMESSAGE, //__in int idHook, + mouseInputHook, //__in HOOKPROC lpfn, + nullptr, //__in HINSTANCE hMod, + ::GetCurrentThreadId()); //__in DWORD dwThreadId + assert(hHook); + } + + ~Dummy() + { + if (hHook) + ::UnhookWindowsHookEx(hHook); + } + +private: + HHOOK hHook; +} dummy; +} diff --git a/zen/string_base.h b/zen/string_base.h index 19bf6267..c3ddde36 100644 --- a/zen/string_base.h +++ b/zen/string_base.h @@ -209,10 +209,12 @@ public: typedef Char& reference; typedef const Char& const_reference; typedef Char value_type; - const Char* begin() const; - const Char* end() const; Char* begin(); - Char* end(); + Char* end (); + const Char* begin() const; + const Char* end () const; + const Char* cbegin() const { return begin(); } + const Char* cend () const { return end(); } //std::string functions size_t length() const; @@ -235,7 +237,8 @@ public: void swap(Zbase& other); void push_back(Char val) { operator+=(val); } //STL access - Zbase& operator=(Zbase source); + Zbase& operator=(const Zbase& source); + Zbase& operator=(Zbase&& tmp); Zbase& operator=(const Char* source); Zbase& operator=(Char source); Zbase& operator+=(const Zbase& other); @@ -653,9 +656,18 @@ Zbase<Char, SP, AP>& Zbase<Char, SP, AP>::append(const Char* source, size_t len) template <class Char, template <class, class> class SP, class AP> inline -Zbase<Char, SP, AP>& Zbase<Char, SP, AP>::operator=(Zbase<Char, SP, AP> other) //unifying assignment: no need for r-value reference optimization! +Zbase<Char, SP, AP>& Zbase<Char, SP, AP>::operator=(const Zbase<Char, SP, AP>& other) +{ + Zbase<Char, SP, AP>(other).swap(*this); + return *this; +} + + +template <class Char, template <class, class> class SP, class AP> inline +Zbase<Char, SP, AP>& Zbase<Char, SP, AP>::operator=(Zbase<Char, SP, AP>&& tmp) { - swap(other); + //don't use unifying assignment but save one move-construction in the r-value case instead! + swap(tmp); return *this; } diff --git a/zen/thread.h b/zen/thread.h index 440940ce..432a521e 100644 --- a/zen/thread.h +++ b/zen/thread.h @@ -20,6 +20,7 @@ #endif #ifdef _MSC_VER #pragma warning(disable : 4702) //unreachable code +#pragma warning(disable : 4913) //user defined binary operator ',' exists but no overload could convert all operands, default built-in binary operator ',' used #endif #include <boost/thread.hpp> @@ -28,7 +29,8 @@ #pragma GCC diagnostic pop #endif #ifdef _MSC_VER -#pragma warning(default : 4702) //unreachable code +#pragma warning(default : 4702) +#pragma warning(default : 4913) #endif namespace zen diff --git a/zen/tick_count.h b/zen/tick_count.h index 962ebcb0..4f1a047e 100644 --- a/zen/tick_count.h +++ b/zen/tick_count.h @@ -24,7 +24,7 @@ class TickVal; std::int64_t operator-(const TickVal& lhs, const TickVal& rhs); std::int64_t ticksPerSec(); //return 0 on error -TickVal getTicks(); //return invalid value on error +TickVal getTicks(); //return invalid value on error: !TickVal::isValid() |