summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Wilhelm <daniel@wili.li>2014-04-18 17:21:59 +0200
committerDaniel Wilhelm <daniel@wili.li>2014-04-18 17:21:59 +0200
commitd4af25c52a28b93484ffb55e0a8027bc4ce7856f (patch)
tree853d57468d6b370711e7a5dd2c3dc7d5bac81b10
parent5.8 (diff)
downloadFreeFileSync-d4af25c52a28b93484ffb55e0a8027bc4ce7856f.tar.gz
FreeFileSync-d4af25c52a28b93484ffb55e0a8027bc4ce7856f.tar.bz2
FreeFileSync-d4af25c52a28b93484ffb55e0a8027bc4ce7856f.zip
5.9
-rw-r--r--Application.cpp12
-rw-r--r--BUILD/Changelog.txt17
-rw-r--r--BUILD/FreeFileSync.chmbin433104 -> 433358 bytes
-rw-r--r--BUILD/Help/html/Backup Strategies.html16
-rw-r--r--BUILD/Help/html/Batch Scripting.html5
-rw-r--r--BUILD/Help/html/Macros.html10
-rw-r--r--BUILD/Help/html/RealtimeSync.html26
-rw-r--r--BUILD/Languages/english_uk.lng14
-rw-r--r--BUILD/Languages/german.lng15
-rw-r--r--BUILD/Resources.zipbin222370 -> 225594 bytes
-rw-r--r--BUILD/Sync_Complete.wavbin364076 -> 460504 bytes
-rw-r--r--FreeFileSync.cbp428
-rw-r--r--FreeFileSync.vcxproj11
-rw-r--r--RealtimeSync/RealtimeSync.cbp79
-rw-r--r--RealtimeSync/RealtimeSync.icobin130761 -> 136291 bytes
-rw-r--r--RealtimeSync/RealtimeSync.vcxproj2
-rw-r--r--RealtimeSync/application.cpp5
-rw-r--r--RealtimeSync/gui_generated.cpp8
-rw-r--r--RealtimeSync/gui_generated.h5
-rw-r--r--RealtimeSync/tray_menu.cpp25
-rw-r--r--RealtimeSync/watcher.cpp26
-rw-r--r--RealtimeSync/watcher.h9
-rw-r--r--file_hierarchy.cpp2
-rw-r--r--file_hierarchy.h6
-rw-r--r--lib/db_file.cpp4
-rw-r--r--lib/dir_lock.cpp2
-rw-r--r--lib/generate_logfile.h14
-rw-r--r--lib/icon_buffer.cpp2
-rw-r--r--lib/lock_holder.h2
-rw-r--r--lib/parallel_scan.cpp6
-rw-r--r--lib/process_xml.h18
-rw-r--r--lib/resolve_path.cpp52
-rw-r--r--lib/resolve_path.h4
-rw-r--r--lib/return_codes.h1
-rw-r--r--lib/status_handler.h4
-rw-r--r--structures.h29
-rw-r--r--synchronization.cpp179
-rw-r--r--ui/IFileDialog_Vista/ifile_dialog.cpp18
-rw-r--r--ui/IFileDialog_Vista/ifile_dialog.h12
-rw-r--r--ui/batch_config.cpp1
-rw-r--r--ui/batch_status_handler.cpp13
-rw-r--r--ui/check_version.cpp177
-rw-r--r--ui/custom_grid.cpp63
-rw-r--r--ui/dir_name.cpp9
-rw-r--r--ui/folder_history_box.cpp14
-rw-r--r--ui/gui_generated.cpp29
-rw-r--r--ui/gui_generated.h20
-rw-r--r--ui/gui_status_handler.cpp12
-rw-r--r--ui/main_dlg.cpp188
-rw-r--r--ui/main_dlg.h15
-rw-r--r--ui/progress_indicator.cpp63
-rw-r--r--ui/progress_indicator.h1
-rw-r--r--ui/search.cpp1
-rw-r--r--ui/sync_cfg.cpp2
-rw-r--r--version/version.h2
-rw-r--r--version/version.rc4
-rw-r--r--wx+/button.h4
-rw-r--r--wx+/graph.cpp18
-rw-r--r--wx+/grid.cpp36
-rw-r--r--wx+/grid.h2
-rw-r--r--wx+/zlib_wrap.h4
-rw-r--r--zen/IFileOperation/file_op.cpp241
-rw-r--r--zen/IFileOperation/file_op.h13
-rw-r--r--zen/com_error.h2
-rw-r--r--zen/com_ptr.h24
-rw-r--r--zen/debug_new.cpp63
-rw-r--r--zen/debug_new.h74
-rw-r--r--zen/dir_watcher.cpp78
-rw-r--r--zen/dir_watcher.h24
-rw-r--r--zen/dll.h6
-rw-r--r--zen/error_log.h43
-rw-r--r--zen/file_handling.cpp378
-rw-r--r--zen/file_handling.h2
-rw-r--r--zen/last_error.h10
-rw-r--r--zen/recycler.cpp98
-rw-r--r--zen/recycler.h13
-rw-r--r--zen/scroll_window_under_cursor.cpp77
-rw-r--r--zen/string_base.h24
-rw-r--r--zen/thread.h4
-rw-r--r--zen/tick_count.h2
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
index f680d349..e4eba632 100644
--- a/BUILD/FreeFileSync.chm
+++ b/BUILD/FreeFileSync.chm
Binary files differ
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 &quot;Versioning&quot;
-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 &quot;Versioning&quot; 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 &quot;<I>ignore
errors</I>&quot; 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. &quot;<FONT FACE="Courier New, monospace">201340</FONT>&quot;<BR><FONT FACE="Courier New, monospace">%date%&nbsp;&nbsp;&nbsp;&nbsp;-
</FONT>current date, e. g. &quot;<FONT FACE="Courier New, monospace">2010-07-13</FONT>&quot;<BR><BR><FONT FACE="Courier New, monospace">%weekday%&nbsp;-
</FONT>day of the week, e. g. &quot;<FONT FACE="Courier New, monospace">Monday</FONT>&quot;<BR><FONT FACE="Courier New, monospace">%week%&nbsp;&nbsp;&nbsp;&nbsp;-
- </FONT>calendar week, e. g. &quot;<FONT FACE="Courier New, monospace">28</FONT>&quot;<BR><BR><FONT FACE="Courier New, monospace">%day%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-
- </FONT>current day, e. g. &quot;<FONT FACE="Courier New, monospace">21</FONT>&quot;<BR><FONT FACE="Courier New, monospace">%month%&nbsp;&nbsp;&nbsp;-
- </FONT>current month, e. g. &quot;<FONT FACE="Courier New, monospace">July</FONT>&quot;<BR><FONT FACE="Courier New, monospace">%year%&nbsp;&nbsp;&nbsp;&nbsp;-
- </FONT>current year, e. g. &quot;<FONT FACE="Courier New, monospace">2010</FONT>&quot;<BR><BR><FONT FACE="Courier New, monospace">%hour%&nbsp;&nbsp;&nbsp;&nbsp;-
+ </FONT>calendar week, e. g. &quot;<FONT FACE="Courier New, monospace">28</FONT>&quot;<BR><BR><FONT FACE="Courier New, monospace">%year%&nbsp;&nbsp;&nbsp;&nbsp;-
+ </FONT>current year, e. g. &quot;<FONT FACE="Courier New, monospace">2010</FONT>&quot;<BR><FONT FACE="Courier New, monospace">%month%&nbsp;&nbsp;&nbsp;-
+ </FONT>current month, e. g. &quot;<FONT FACE="Courier New, monospace">July</FONT>&quot;<BR><FONT FACE="Courier New, monospace">%day%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-
+ </FONT>current day, e. g. &quot;<FONT FACE="Courier New, monospace">21</FONT>&quot;<BR><BR><FONT FACE="Courier New, monospace">%hour%&nbsp;&nbsp;&nbsp;&nbsp;-
</FONT>current hour, e. g. &quot;<FONT FACE="Courier New, monospace">20</FONT>&quot;<BR><FONT FACE="Courier New, monospace">%min%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-
</FONT>current minute, e. g. &quot;<FONT FACE="Courier New, monospace">13</FONT>&quot;<BR><FONT FACE="Courier New, monospace">%sec%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-
</FONT>current second, e. g. &quot;<FONT FACE="Courier New, monospace">40</FONT>&quot;</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 &quot;</FONT><FONT FACE="Tahoma, sans-serif"><I>Start</I></FONT><FONT FACE="Tahoma, sans-serif">&quot;
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>&nbsp;</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 &quot;</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">&quot;changed_file&quot;.</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">&quot;change_path&quot;</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">&quot;change_action&quot;.</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>&nbsp;&nbsp;&nbsp;&nbsp;cmd /c echo
- &quot;%changed_file%&quot; &amp; pause<BR><BR><FONT FACE="Tahoma, sans-serif">Write
+ %change_action% &quot;%change_path%&quot; &amp; pause<BR><BR><FONT FACE="Tahoma, sans-serif">Write
a list of all changes to a logfile:</FONT><BR>&nbsp;&nbsp;&nbsp;&nbsp;cmd
- /c echo &quot;%changed_file%&quot; &gt;&gt; c:\log.txt</FONT></P>
+ /c echo %change_action% &quot;%change_path%&quot; &gt;&gt;
+ 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
index 676a8d0d..1f602205 100644
--- a/BUILD/Resources.zip
+++ b/BUILD/Resources.zip
Binary files differ
diff --git a/BUILD/Sync_Complete.wav b/BUILD/Sync_Complete.wav
index 3fe95e5d..96dd2a15 100644
--- a/BUILD/Sync_Complete.wav
+++ b/BUILD/Sync_Complete.wav
Binary files differ
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='&quot;C:\Program Files\C++\CodeSigning\SignCode.cmd&quot; &quot;$(PROJECT_DIR)$(TARGET_OUTPUT_FILE)&quot;' />
</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 &quot;wx+/pch.h&quot;' />
<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 &quot;wx+/pch.h&quot;' />
<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="&lt;{~None~}&gt;" />
@@ -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='&quot;C:\Program Files\C++\CodeSigning\SignCode.cmd&quot; &quot;$(PROJECT_DIR)$(TARGET_OUTPUT_FILE)&quot;' />
</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 &quot;../wx+/pch.h&quot;' />
<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
index c42a3029..1c5a3eb6 100644
--- a/RealtimeSync/RealtimeSync.ico
+++ b/RealtimeSync/RealtimeSync.ico
Binary files differ
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);
diff --git a/wx+/grid.h b/wx+/grid.h
index fd07d2c7..89926e00 100644
--- a/wx+/grid.h
+++ b/wx+/grid.h
@@ -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&);
diff --git a/zen/dll.h b/zen/dll.h
index 6f139ac3..3e7a0655 100644
--- a/zen/dll.h
+++ b/zen/dll.h
@@ -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()
bgstack15